diff --git a/assets/city/Building_Large_2.glb b/assets/city/Building_Large_2.glb new file mode 100644 index 0000000..da13b4e Binary files /dev/null and b/assets/city/Building_Large_2.glb differ diff --git a/assets/city/Building_Medium_2_001.glb b/assets/city/Building_Medium_2_001.glb new file mode 100644 index 0000000..9b2fafb Binary files /dev/null and b/assets/city/Building_Medium_2_001.glb differ diff --git a/assets/city/Building_Small_1.glb b/assets/city/Building_Small_1.glb new file mode 100644 index 0000000..418549e Binary files /dev/null and b/assets/city/Building_Small_1.glb differ diff --git a/assets/city/Cop.glb b/assets/city/Cop.glb new file mode 100644 index 0000000..3552b36 Binary files /dev/null and b/assets/city/Cop.glb differ diff --git a/assets/city/NormalCar1.glb b/assets/city/NormalCar1.glb new file mode 100644 index 0000000..2077432 Binary files /dev/null and b/assets/city/NormalCar1.glb differ diff --git a/assets/city/Prop_Bollard.glb b/assets/city/Prop_Bollard.glb new file mode 100644 index 0000000..61e678e Binary files /dev/null and b/assets/city/Prop_Bollard.glb differ diff --git a/assets/city/Prop_Planter_Single.glb b/assets/city/Prop_Planter_Single.glb new file mode 100644 index 0000000..3acbb50 Binary files /dev/null and b/assets/city/Prop_Planter_Single.glb differ diff --git a/assets/city/Sidewalk_Straight_3m.glb b/assets/city/Sidewalk_Straight_3m.glb new file mode 100644 index 0000000..458e7cb Binary files /dev/null and b/assets/city/Sidewalk_Straight_3m.glb differ diff --git a/assets/city/Street_2Lane.glb b/assets/city/Street_2Lane.glb new file mode 100644 index 0000000..090f010 Binary files /dev/null and b/assets/city/Street_2Lane.glb differ diff --git a/assets/city/Street_4WayIntersection.glb b/assets/city/Street_4WayIntersection.glb new file mode 100644 index 0000000..75c5744 Binary files /dev/null and b/assets/city/Street_4WayIntersection.glb differ diff --git a/assets/city/Street_Asphalt_9x9.glb b/assets/city/Street_Asphalt_9x9.glb new file mode 100644 index 0000000..a0075ac Binary files /dev/null and b/assets/city/Street_Asphalt_9x9.glb differ diff --git a/assets/city/Taxi.glb b/assets/city/Taxi.glb new file mode 100644 index 0000000..590167e Binary files /dev/null and b/assets/city/Taxi.glb differ diff --git a/src/main.js b/src/main.js index 4747cd9..0645734 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ import { GLTFLoader } from '../vendor/GLTFLoader.js'; import { OrbitControls } from '../vendor/OrbitControls.js'; import { RetroScreen, retroState, retrofyObject, patchRetroMaterial } from './retro.js'; -const FOG_COLOR = 0x2a2036; +const FOG_COLOR = 0x8d84a8; const renderer = new THREE.WebGLRenderer({ antialias: false }); renderer.setPixelRatio(1); @@ -12,70 +12,98 @@ document.getElementById('view').appendChild(renderer.domElement); const scene = new THREE.Scene(); scene.background = new THREE.Color(FOG_COLOR); -scene.fog = new THREE.Fog(FOG_COLOR, 6, 26); +scene.fog = new THREE.Fog(FOG_COLOR, 14, 55); const camera = new THREE.PerspectiveCamera(60, 4 / 3, 0.1, 100); -camera.position.set(3.2, 2.2, 4.2); +camera.position.set(6.5, 3.2, 9.5); const controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 1, 0); controls.enableDamping = true; -scene.add(new THREE.HemisphereLight(0xcabfe8, 0x4a3c5e, 1.6)); -const sun = new THREE.DirectionalLight(0xffeedd, 2.2); +scene.add(new THREE.HemisphereLight(0xe8e2f5, 0x8a7a95, 2.4)); +const sun = new THREE.DirectionalLight(0xfff2dd, 3.2); sun.position.set(4, 6, 3); scene.add(sun); const screen = new RetroScreen(renderer); -// --- procedural retro textures (canvas, nearest) ------------------------------ -function canvasTexture(draw, size = 64) { - const c = document.createElement('canvas'); - c.width = c.height = size; - draw(c.getContext('2d'), size); - const t = new THREE.CanvasTexture(c); - t.magFilter = THREE.NearestFilter; - t.minFilter = THREE.NearestFilter; - t.generateMipmaps = false; - t.wrapS = t.wrapT = THREE.RepeatWrapping; - return t; +// --- downtown block (paid Downtown City MegaKit, texture-crushed) -------------- +const cityLoader = new GLTFLoader(); +function loadPiece(name) { + return new Promise((res, rej) => + cityLoader.load('assets/city/' + name + '.glb', (g) => { + retrofyObject(g.scene); + res(g.scene); + }, undefined, rej)); +} +function sizeOf(obj) { + const b = new THREE.Box3().setFromObject(obj); + return { size: b.getSize(new THREE.Vector3()), min: b.min, max: b.max }; +} +function put(proto, x, z, ry = 0) { + const c = proto.clone(true); + c.position.set(x, 0, z); + c.rotation.y = ry; + scene.add(c); + return c; } -const checkerTex = canvasTexture((g, s) => { - for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) { - g.fillStyle = (x + y) % 2 ? '#4a3f66' : '#5d5080'; - g.fillRect(x * s / 8, y * s / 8, s / 8, s / 8); - } -}); -checkerTex.repeat.set(12, 12); -const brickTex = canvasTexture((g, s) => { - g.fillStyle = '#6e5a4e'; g.fillRect(0, 0, s, s); - g.fillStyle = '#8a7263'; - for (let y = 0; y < 4; y++) for (let x = 0; x < 4; x++) - g.fillRect(x * 16 + (y % 2 ? 8 : 0) + 1, y * 16 + 1, 14, 14); -}); -const ground = new THREE.Mesh( - new THREE.PlaneGeometry(40, 40), - patchRetroMaterial(new THREE.MeshLambertMaterial({ map: checkerTex })) +// dark asphalt base plane so there are no holes at the fog line +const base = new THREE.Mesh( + new THREE.PlaneGeometry(120, 120), + patchRetroMaterial(new THREE.MeshLambertMaterial({ color: 0x2b2b30 })) ); -ground.rotation.x = -Math.PI / 2; -scene.add(ground); +base.rotation.x = -Math.PI / 2; +base.position.y = -0.03; +scene.add(base); -// scatter some chunky geometry so the wobble has something to chew on -const brickMat = patchRetroMaterial(new THREE.MeshLambertMaterial({ map: brickTex })); -const shapes = []; -for (let i = 0; i < 14; i++) { - const kind = i % 3; - const geo = kind === 0 ? new THREE.BoxGeometry(1.4, 1 + (i % 4), 1.4) - : kind === 1 ? new THREE.ConeGeometry(0.9, 1.8, 6) - : new THREE.CylinderGeometry(0.6, 0.6, 1.4, 8); - const m = new THREE.Mesh(geo, brickMat); - const a = (i / 14) * Math.PI * 2; - const r = 6 + (i % 3) * 3; - m.position.set(Math.cos(a) * r, geo.parameters.height ? geo.parameters.height / 2 : 0.7, Math.sin(a) * r); - m.rotation.y = a; - scene.add(m); - shapes.push(m); -} +(async () => { + try { + const [street, walk, bSmall, bMed, bLarge, bollard, planter, taxi, car, cop] = + await Promise.all([ + loadPiece('Street_2Lane'), loadPiece('Sidewalk_Straight_3m'), + loadPiece('Building_Small_1'), loadPiece('Building_Medium_2_001'), + loadPiece('Building_Large_2'), loadPiece('Prop_Bollard'), + loadPiece('Prop_Planter_Single'), loadPiece('Taxi'), + loadPiece('NormalCar1'), loadPiece('Cop'), + ]); + const st = sizeOf(street); + const stepX = st.size.x || 3; // tile the road along X + const roadHalf = (st.size.z || 9) / 2; + for (let i = -5; i <= 5; i++) put(street, i * stepX, 0); + const wk = sizeOf(walk); + const wkStep = wk.size.x || 3; + for (let i = -10; i <= 10; i++) { + put(walk, i * wkStep, roadHalf + wk.size.z / 2); + put(walk, i * wkStep, -(roadHalf + wk.size.z / 2), Math.PI); + } + // buildings lining both sides, fronts to the road + const row = roadHalf + wk.size.z + 0.2; + const north = [bSmall, bMed, bLarge, bSmall]; + let x = -22; + for (const b of north) { + const bs = sizeOf(b); + put(b, x + bs.size.x / 2, row + bs.size.z / 2, Math.PI); + x += bs.size.x + 1.2; + } + const south = [bLarge, bSmall, bMed]; + x = -18; + for (const b of south) { + const bs = sizeOf(b); + put(b, x + bs.size.x / 2, -(row + bs.size.z / 2)); + x += bs.size.x + 1.5; + } + // kerbside cars + props + put(taxi, -4, roadHalf - 1.2, Math.PI); + put(car, 5, roadHalf - 1.2, Math.PI); + put(cop, 10, -(roadHalf - 1.2)); + for (let i = -3; i <= 3; i++) put(bollard, i * 6 + 1.5, roadHalf + 0.6); + put(planter, -8, roadHalf + 1.4); + put(planter, 8, roadHalf + 1.4); + } catch (e) { + console.error('city load failed', e); + } +})(); // --- character ---------------------------------------------------------------- let mixer = null;