park_kit dressing: textured figs, furniture, fence line + /kit/ mount
serve.py mounts the sibling park_kit repo at /kit/ (PARK_KIT env to repoint) — same convention as skatemakerpro, so editor-exported levels work unchanged. level.js PROPS lists the dressing (island Moreton Bays, gums, benches, bin, picnic table, bleachers, shade sail, fence sections, floodlights, hydrant, graffiti wall); main.js loads them onto heightAt. Procedural cone-figs retired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
53ad2030af
commit
7afae85599
@ -42,6 +42,11 @@ Land on rails and ledges to grind. Chain tricks before the wheels settle to mult
|
||||
python3 serve.py 8141 # no-cache dev server
|
||||
open http://localhost:8141
|
||||
```
|
||||
Textured trees + street furniture come from the **park_kit** sibling repo
|
||||
(`monster/park_kit`, checked out next to this one — serve.py mounts it at
|
||||
`/kit/`, or point `PARK_KIT=` elsewhere). Without it the park runs bare.
|
||||
Parks built in **skatemakerpro** export a `level.<name>.js` with this exact
|
||||
level.js contract and drop straight in.
|
||||
|
||||
## How it's built
|
||||
- **three.js**, no engine, no bundler — plain ES modules.
|
||||
|
||||
41
js/level.js
41
js/level.js
@ -168,6 +168,30 @@ export const LETTERS = [
|
||||
|
||||
export const SPAWN = { x: -32, z: -18, heading: 0.85 }; // tennis-courts entrance, facing SE into the park
|
||||
|
||||
// park_kit dressing — textured GLBs from the sibling park_kit repo, served at
|
||||
// /kit/ (see serve.py). Visual only, same as the old cone-figs: no colliders.
|
||||
// main.js loads these after buildLevel; y sits on heightAt(x,z).
|
||||
export const PROPS = [
|
||||
// the island Moreton Bays + parkland trees
|
||||
{ id: 'tree_fig', x: 0, z: -3, s: 1.15 }, { id: 'tree_fig', x: 4.5, z: -1, s: 0.9 },
|
||||
{ id: 'tree_fig', x: -1, z: 1, s: 0.8, rot: 2.1 },
|
||||
{ id: 'tree_fig', x: -34, z: 16, s: 1.0, rot: 0.8 }, { id: 'tree_fig', x: 30, z: 20, s: 1.2 },
|
||||
{ id: 'tree_fig', x: -14, z: -30, s: 1.05, rot: 4.0 },
|
||||
{ id: 'tree_gum', x: -40, z: 24, rot: 1.2 }, { id: 'tree_gum', x: 40, z: 10, rot: 3.4 },
|
||||
// furniture along the south parkland + plaza edge
|
||||
{ id: 'bench_park', x: 24, z: 15, rot: Math.PI }, { id: 'bin_council', x: 26.2, z: 15 },
|
||||
{ id: 'bench_park', x: -8, z: 17, rot: Math.PI }, { id: 'picnic_table', x: -16, z: 20, rot: 0.4 },
|
||||
{ id: 'bleachers_3', x: -24, z: 22, rot: Math.PI }, // watching the bowl line
|
||||
{ id: 'shade_sail', x: 18, z: 23 },
|
||||
// Caxton St side: fence sections + lights + a hydrant on the footpath
|
||||
{ id: 'fence_chain_3m', x: -16, z: -38.5 }, { id: 'fence_chain_3m', x: -13, z: -38.5 },
|
||||
{ id: 'fence_chain_3m', x: 19, z: -38.5 }, { id: 'fence_chain_3m', x: 22, z: -38.5 },
|
||||
{ id: 'floodlight', x: -38, z: -20 }, { id: 'floodlight', x: 38, z: 16 },
|
||||
{ id: 'hydrant', x: 14, z: -37 },
|
||||
// the wallride wall in the west parkland
|
||||
{ id: 'wall_graffiti', x: -45, z: 4, rot: Math.PI / 2 },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------- build visuals
|
||||
function zoneColor(x, z, h) {
|
||||
const c = new THREE.Color();
|
||||
@ -223,21 +247,8 @@ export function buildLevel(scene) {
|
||||
}
|
||||
}
|
||||
|
||||
// the Moreton Bay figs on the island (+ a couple in the parkland)
|
||||
const trunkM = new THREE.MeshLambertMaterial({ color: 0x5b4632 });
|
||||
const leafM = new THREE.MeshLambertMaterial({ color: 0x2e5b2a });
|
||||
const fig = (x, z, s) => {
|
||||
const grp = new THREE.Group();
|
||||
const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.28 * s, 0.42 * s, 2.6 * s, 7), trunkM);
|
||||
trunk.position.y = 1.3 * s; grp.add(trunk);
|
||||
for (const [ox, oy, oz, r] of [[0, 3.1, 0, 2.4], [1.4, 2.6, 0.7, 1.7], [-1.3, 2.7, -0.6, 1.6]]) {
|
||||
const m = new THREE.Mesh(new THREE.SphereGeometry(r * s, 10, 8), leafM);
|
||||
m.position.set(ox * s, oy * s, oz * s); grp.add(m);
|
||||
}
|
||||
grp.position.set(x, heightAt(x, z), z); scene.add(grp);
|
||||
};
|
||||
fig(0, -3, 1.25); fig(4.5, -1, 1.0); fig(-1, 1, 0.9);
|
||||
fig(-34, 16, 1.1); fig(30, 20, 1.3); fig(-14, -30, 1.2);
|
||||
// trees/furniture now come from park_kit (PROPS below, loaded by main.js) —
|
||||
// the procedural cone-figs retired when the textured Moreton Bays arrived
|
||||
|
||||
// Caxton St + a nod to Suncorp across the road
|
||||
const road = new THREE.Mesh(new THREE.BoxGeometry(130, 0.08, 7),
|
||||
|
||||
16
js/main.js
16
js/main.js
@ -2,7 +2,7 @@
|
||||
// Three disciplines: SKATE, BMX, and BLADES (locals throw rubbish at bladers. tradition.)
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
import { buildLevel, GAPS, SPAWN, heightAt } from './level.js';
|
||||
import { buildLevel, GAPS, SPAWN, heightAt, PROPS } from './level.js';
|
||||
import { KITS, RUBBISH_INSULTS, GAP_SCORE, LETTER_SCORE, ALL_LETTERS_SCORE } from './tricks.js';
|
||||
import { Player } from './player.js';
|
||||
import { Hud } from './hud.js';
|
||||
@ -46,6 +46,20 @@ async function boot() {
|
||||
const { letterMeshes } = buildLevel(scene);
|
||||
letters = letterMeshes;
|
||||
|
||||
// park_kit dressing (textured trees + street furniture). Served at /kit/ from
|
||||
// the sibling park_kit repo; if the mount is missing the park just goes bare.
|
||||
const KIT_BASE = '/kit/';
|
||||
for (const p of PROPS) {
|
||||
loader.load(KIT_BASE + 'props/' + p.id + '.glb', g => {
|
||||
const o = g.scene;
|
||||
o.traverse(m => { if (m.isMesh) m.castShadow = m.receiveShadow = true; });
|
||||
o.position.set(p.x, heightAt(p.x, p.z) + (p.y || 0), p.z);
|
||||
o.rotation.y = p.rot || 0;
|
||||
o.scale.setScalar(p.s || 1);
|
||||
scene.add(o);
|
||||
}, undefined, () => console.warn('kit prop missing (is ../park_kit checked out?)', p.id));
|
||||
}
|
||||
|
||||
const [rigG, deck, deckBank, skateRider, bike, bikeBank, bmxRider, pairsSkate, pairsBmx] =
|
||||
await Promise.all([
|
||||
load('assets/woman_raver_01.glb'), load('assets/deck.glb'),
|
||||
|
||||
26
serve.py
26
serve.py
@ -1,11 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BOOKQUOY dev server — http.server with caching disabled so module edits show up."""
|
||||
import http.server, functools, sys
|
||||
"""BOOKQUOY dev server — http.server with caching disabled so module edits show up.
|
||||
Also mounts the park_kit sibling repo at /kit/ (textured props + surfaces), same
|
||||
convention as skatemakerpro: override with PARK_KIT=/path/to/park_kit."""
|
||||
import http.server, mimetypes, os, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
KIT = os.environ.get('PARK_KIT', os.path.join(os.path.dirname(ROOT), 'park_kit'))
|
||||
|
||||
class NoCache(http.server.SimpleHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
self.send_header('Cache-Control', 'no-store, must-revalidate')
|
||||
super().end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/kit/'):
|
||||
rel = os.path.normpath(self.path[5:].split('?')[0]).lstrip('/')
|
||||
f = os.path.join(KIT, rel)
|
||||
if f.startswith(KIT + os.sep) and os.path.isfile(f):
|
||||
ctype = mimetypes.guess_type(f)[0] or 'application/octet-stream'
|
||||
data = open(f, 'rb').read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', ctype)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
else:
|
||||
self.send_error(404)
|
||||
return
|
||||
super().do_GET()
|
||||
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8141
|
||||
http.server.ThreadingHTTPServer(('', port), NoCache).serve_forever()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user