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
|
python3 serve.py 8141 # no-cache dev server
|
||||||
open http://localhost:8141
|
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
|
## How it's built
|
||||||
- **three.js**, no engine, no bundler — plain ES modules.
|
- **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
|
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
|
// ---------------------------------------------------------------- build visuals
|
||||||
function zoneColor(x, z, h) {
|
function zoneColor(x, z, h) {
|
||||||
const c = new THREE.Color();
|
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)
|
// trees/furniture now come from park_kit (PROPS below, loaded by main.js) —
|
||||||
const trunkM = new THREE.MeshLambertMaterial({ color: 0x5b4632 });
|
// the procedural cone-figs retired when the textured Moreton Bays arrived
|
||||||
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);
|
|
||||||
|
|
||||||
// Caxton St + a nod to Suncorp across the road
|
// Caxton St + a nod to Suncorp across the road
|
||||||
const road = new THREE.Mesh(new THREE.BoxGeometry(130, 0.08, 7),
|
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.)
|
// Three disciplines: SKATE, BMX, and BLADES (locals throw rubbish at bladers. tradition.)
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
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 { KITS, RUBBISH_INSULTS, GAP_SCORE, LETTER_SCORE, ALL_LETTERS_SCORE } from './tricks.js';
|
||||||
import { Player } from './player.js';
|
import { Player } from './player.js';
|
||||||
import { Hud } from './hud.js';
|
import { Hud } from './hud.js';
|
||||||
@ -46,6 +46,20 @@ async function boot() {
|
|||||||
const { letterMeshes } = buildLevel(scene);
|
const { letterMeshes } = buildLevel(scene);
|
||||||
letters = letterMeshes;
|
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] =
|
const [rigG, deck, deckBank, skateRider, bike, bikeBank, bmxRider, pairsSkate, pairsBmx] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
load('assets/woman_raver_01.glb'), load('assets/deck.glb'),
|
load('assets/woman_raver_01.glb'), load('assets/deck.glb'),
|
||||||
|
|||||||
26
serve.py
26
serve.py
@ -1,11 +1,33 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""BOOKQUOY dev server — http.server with caching disabled so module edits show up."""
|
"""BOOKQUOY dev server — http.server with caching disabled so module edits show up.
|
||||||
import http.server, functools, sys
|
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):
|
class NoCache(http.server.SimpleHTTPRequestHandler):
|
||||||
def end_headers(self):
|
def end_headers(self):
|
||||||
self.send_header('Cache-Control', 'no-store, must-revalidate')
|
self.send_header('Cache-Control', 'no-store, must-revalidate')
|
||||||
super().end_headers()
|
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
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8141
|
||||||
http.server.ThreadingHTTPServer(('', port), NoCache).serve_forever()
|
http.server.ThreadingHTTPServer(('', port), NoCache).serve_forever()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user