From 7afae85599907d783457fd25e8e3e4293361797d Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 3 Aug 2026 21:15:19 +1000 Subject: [PATCH] park_kit dressing: textured figs, furniture, fence line + /kit/ mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 5 +++++ js/level.js | 41 ++++++++++++++++++++++++++--------------- js/main.js | 16 +++++++++++++++- serve.py | 26 ++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1964b82..b0f800a 100644 --- a/README.md +++ b/README.md @@ -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..js` with this exact +level.js contract and drop straight in. ## How it's built - **three.js**, no engine, no bundler — plain ES modules. diff --git a/js/level.js b/js/level.js index c7fbced..df17a65 100644 --- a/js/level.js +++ b/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), diff --git a/js/main.js b/js/main.js index 11959f7..6970797 100644 --- a/js/main.js +++ b/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'), diff --git a/serve.py b/serve.py index fb33699..34aa719 100644 --- a/serve.py +++ b/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()