diff --git a/public/assets/houses/house_type01.backup b/public/assets/houses/house_type01.backup new file mode 100644 index 0000000..44a34ae Binary files /dev/null and b/public/assets/houses/house_type01.backup differ diff --git a/public/assets/houses/house_type01.glb b/public/assets/houses/house_type01.glb index 44a34ae..d9797d7 100644 Binary files a/public/assets/houses/house_type01.glb and b/public/assets/houses/house_type01.glb differ diff --git a/public/assets/nature/bush2.glb b/public/assets/nature/bush2.glb new file mode 100644 index 0000000..be4eff8 Binary files /dev/null and b/public/assets/nature/bush2.glb differ diff --git a/public/assets/textures/window_blinds.png b/public/assets/textures/window_blinds.png new file mode 100644 index 0000000..adb26d8 Binary files /dev/null and b/public/assets/textures/window_blinds.png differ diff --git a/public/assets/textures/window_blinds_emissive.png b/public/assets/textures/window_blinds_emissive.png new file mode 100644 index 0000000..20c9dc3 Binary files /dev/null and b/public/assets/textures/window_blinds_emissive.png differ diff --git a/src/state/SceneState.ts b/src/state/SceneState.ts index e241f69..6547180 100644 --- a/src/state/SceneState.ts +++ b/src/state/SceneState.ts @@ -1,8 +1,9 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; +import { Sky } from 'three/examples/jsm/objects/Sky.js'; import { CanvasListener } from '../utils/CanvasListener'; -import { HouseName, ModelLoader, ModelNames, RoadName, VehicleName } from '../utils/ModelLoader'; +import { HouseName, ModelLoader, ModelNames, RoadName, VehicleName, FoliageName } from '../utils/ModelLoader'; import { MouseListener } from '../utils/MouseListener'; import { Prop } from '../model/Prop'; import { Road } from '../model/Road'; @@ -38,6 +39,7 @@ export class SceneState { modelNames.roads = Object.values(RoadName); modelNames.vehicles = Object.values(VehicleName); modelNames.houses = Object.values(HouseName); + modelNames.foliage = Object.values(FoliageName); this.modelLoader.loadModels(modelNames, () => this.buildScene()); } @@ -89,7 +91,7 @@ export class SceneState { private setupLights() { // Ambient - const ambientLight = new THREE.HemisphereLight(0xebf6ff, 0x5fa36b, 0.25); + const ambientLight = new THREE.HemisphereLight(0xebf6ff, 0x5fa36b, 0.05); this.scene.add(ambientLight); // Directional @@ -117,7 +119,35 @@ export class SceneState { sunGroup.add(helper); sunGroup.add(directionalLight); sunGroup.add(directionalLight.target); + + const moon = directionalLight.clone(true); + moon.color = new THREE.Color(0x2d7fa8); + moon.intensity = 0.1; + const moonGroup = new THREE.Group(); + moonGroup.name = 'MoonGroup'; + moonGroup.add(moon); + moonGroup.add(moon.target); + this.scene.add(sunGroup); + //this.scene.add(moonGroup); + + // Sky + const sky = new Sky(); + sky.scale.setScalar( 450000 ); + this.scene.add(sky); + const sunPosition = new THREE.Vector3(); + const uniforms = sky.material.uniforms; + + // elevation + const phi = THREE.MathUtils.degToRad( 90 - 5 ); + // azimuth + const theta = THREE.MathUtils.degToRad( 15 ); + + sunPosition.setFromSphericalCoords( 1, phi, theta ); + const dirLightPos = sunPosition.clone().multiplyScalar(15); + directionalLight.position.copy(dirLightPos); + directionalLight.target.position.set(0,0,0); + uniforms[ 'sunPosition' ].value.copy( sunPosition ); } private setupCamera() { diff --git a/src/textures/windowBlinds.psd b/src/textures/windowBlinds.psd new file mode 100644 index 0000000..10805b0 Binary files /dev/null and b/src/textures/windowBlinds.psd differ diff --git a/src/textures/window_blinds.png b/src/textures/window_blinds.png new file mode 100644 index 0000000..adb26d8 Binary files /dev/null and b/src/textures/window_blinds.png differ diff --git a/src/textures/window_blinds_emissive.png b/src/textures/window_blinds_emissive.png new file mode 100644 index 0000000..20c9dc3 Binary files /dev/null and b/src/textures/window_blinds_emissive.png differ diff --git a/src/utils/ModelLoader.ts b/src/utils/ModelLoader.ts index 82df1f6..c3bf917 100644 --- a/src/utils/ModelLoader.ts +++ b/src/utils/ModelLoader.ts @@ -1,17 +1,19 @@ import * as THREE from 'three'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { NumberUtils } from './NumberUtils'; export class ModelNames { vehicles: VehicleName[] = []; roads: RoadName[] = []; houses: HouseName[] = []; + foliage: FoliageName[] = []; get modelCount() { - return this.vehicles.length + this.roads.length + this.houses.length; + return this.vehicles.length + this.roads.length + this.houses.length + this.foliage.length; } } -type ModelName = VehicleName | RoadName | HouseName; +type ModelName = VehicleName | RoadName | HouseName | FoliageName; export enum VehicleName { DELIVERY = 'delivery', @@ -56,6 +58,10 @@ export enum HouseName { TYPE_21 = 'house_type21', } +export enum FoliageName { + BUSH = 'bush2', +} + /** * Responsible for loading models and storing them during runtime. */ @@ -63,6 +69,7 @@ export class ModelLoader { private readonly vehicleScaleModifer = 0.3; private readonly roadScaleModifier = 2; private readonly houseScaleModifier = 1.5; + private readonly foliageScaleModifier = 0.25; private modelMap = new Map(); private loadedModels = 0; private modelsToLoad = 0; @@ -97,6 +104,9 @@ export class ModelLoader { // Load houses modelNames.houses.forEach((hName) => this.loadHouse(hName, loader)); + + // Load foliage + modelNames.foliage.forEach((fName) => this.loadFoliage(fName, loader)); } public getModel(name: ModelName): THREE.Group { @@ -198,6 +208,10 @@ export class ModelLoader { } private loadHouse(hName: HouseName, loader: GLTFLoader) { + const glassDiffuse = new THREE.TextureLoader().load('assets/textures/window_blinds_diffuse.png'); // should move this out of here + const glassEmissive = new THREE.TextureLoader().load('assets/textures/window_blinds_emissive.png'); // should move this out of here + const glassMat = new THREE.MeshStandardMaterial({roughness: 1, emissive: 0xffb73b, emissiveIntensity: 1}); + loader.load( this.baseAssetPath + `houses/${hName}.glb`, (model: GLTF) => { @@ -209,6 +223,10 @@ export class ModelLoader { ((node as THREE.Mesh).material as THREE.MeshStandardMaterial).metalness = 0; node.castShadow = true; node.receiveShadow = true; + + if (((node as THREE.Mesh).material as THREE.MeshStandardMaterial).name === 'window') { + (node as THREE.Mesh).material = glassMat; + } } }); @@ -237,4 +255,83 @@ export class ModelLoader { return parent; } + + private loadFoliage(fName: FoliageName, loader: GLTFLoader) { + loader.load( + this.baseAssetPath + `nature/${fName}.glb`, + (model: GLTF) => { + // Traverse model nodes + model.scene.traverse((node) => { + // If it's a mesh + if ((node as THREE.Mesh).isMesh) { + // Adjust metalness + ((node as THREE.Mesh).material as THREE.MeshStandardMaterial).metalness = 0; + + if (node.name === 'Leaves') { + const alpha = new THREE.TextureLoader().load('assets/textures/bushUpper_alpha.png'); // should move this out of here + const diffuse = new THREE.TextureLoader().load('assets/textures/bushUpper_diffuse.png'); // should move this out of here + (node as THREE.Mesh).material = new THREE.MeshStandardMaterial({alphaMap: alpha, map: diffuse, transparent: true, alphaTest: 0.9, roughness: 0.66}); + } else { + (node as THREE.Mesh).geometry.computeBoundingSphere(); + const bSphere = (node as THREE.Mesh).geometry.boundingSphere; + const vec = new THREE.Vector3(); + const vertArr = (node as THREE.Mesh).geometry.attributes.position.array; + const vertColors = new Float32Array(vertArr.length * 3); + const baseColor = [46, 38, 28]; + const tipColor = [76, 84, 47]; + const weightStrength = 1; + //const baseColor = [255, 0, 0]; + for (let i = 0; i < vertArr.length; i+=3) { + vec.set( + vertArr[i], + vertArr[i+1], + vertArr[i+2], + ); + + const length = vec.length() / bSphere.radius; + const alpha = length * weightStrength; + // vertColors[i] = (baseColor[0] / 255) * (1 - length * weightStrength); + // vertColors[i+1] = (baseColor[1] / 255) * (1 - length * weightStrength); + // vertColors[i+2] = (baseColor[2] / 255) * (1 - length * weightStrength); + + vertColors[i] = NumberUtils.lerp((baseColor[0] / 255), (tipColor[0] / 255), alpha); + vertColors[i+1] = NumberUtils.lerp((baseColor[1] / 255), (tipColor[1] / 255), alpha); + vertColors[i+2] = NumberUtils.lerp((baseColor[2] / 255), (tipColor[2] / 255), alpha); + } + (node as THREE.Mesh).geometry.setAttribute('color', new THREE.BufferAttribute(vertColors, 3)); + (node as THREE.Mesh).material = new THREE.MeshStandardMaterial({vertexColors: true}); + //((node as THREE.Mesh).material as THREE.MeshStandardMaterial).vertexColors = true; + } + + //node.castShadow = true; + //node.receiveShadow = true; + } + }); + + const foliage = this.setupFoliage(model.scene); + this.onLoadModel(fName, foliage); + }, + undefined, + this.onLoadError + ); + } + + private setupFoliage(foliage: THREE.Group) { + // Adjust scale of houses + //house.scale.set(this.houseScaleModifier, this.houseScaleModifier, this.houseScaleModifier); + foliage.scale.set(this.foliageScaleModifier, this.foliageScaleModifier, this.foliageScaleModifier); + + // Set transform origin to center of the model + const box = new THREE.Box3().setFromObject(foliage); + box.getCenter(foliage.position).multiplyScalar(-1); + + // Models are centered, move to sit at ground level + foliage.position.y = 0; + + // Wrap in parent to avoid losing above transforms + const parent = new THREE.Group(); + parent.add(foliage); + + return parent; + } } diff --git a/src/utils/NumberUtils.ts b/src/utils/NumberUtils.ts index b03e7a6..45b8b93 100644 --- a/src/utils/NumberUtils.ts +++ b/src/utils/NumberUtils.ts @@ -68,4 +68,8 @@ export class NumberUtils { return closest; } + + public static lerp (a: number, b: number, t: number) { + return (1 - t) * a + t * b; + } } diff --git a/src/utils/SceneBuilder.ts b/src/utils/SceneBuilder.ts index abc5322..1325211 100644 --- a/src/utils/SceneBuilder.ts +++ b/src/utils/SceneBuilder.ts @@ -1,13 +1,12 @@ import * as THREE from 'three'; -import { HouseName, ModelLoader, RoadName, VehicleName } from './ModelLoader'; +import { HouseName, ModelLoader, RoadName, VehicleName, FoliageName } from './ModelLoader'; import { Prop } from '../model/Prop'; import { Road } from '../model/Road'; import { RoadFactory } from './RoadFactory'; import { RoadUtils } from './RoadUtils'; import { Vehicle } from '../model/Vehicle'; import { VehicleFactory } from './VehicleFactory'; -import { BufferAttribute } from 'three'; enum GroundType { GRASS = 'grass', @@ -550,6 +549,8 @@ export class SceneBuilder { new THREE.Vector2(1, 1), new THREE.Vector3(8, 0, -4) ); + const f1 = this.addFoliage(FoliageName.BUSH, new THREE.Vector3(8, 0, -4)); + const r1 = [g16, f1]; return [ ...block1, @@ -565,7 +566,7 @@ export class SceneBuilder { ...block11, ...block12, ...block13, - g16, + ...r1, ]; } @@ -634,9 +635,9 @@ export class SceneBuilder { if (type === GroundType.GRASS) { const grass = this.addGrass(size, pos, 1000); - const bushes = this.addBushes(size, pos, 2); + //const bushes = this.addBushes(size, pos, 2); group.add(grass); - group.add(bushes); + //group.add(bushes); } const prop = new Prop(group); @@ -644,6 +645,19 @@ export class SceneBuilder { return prop; } + private addFoliage(name: FoliageName, pos: THREE.Vector3) { + const foliageModel = this.modelLoader.getModel(name); + const foliage = new Prop(foliageModel); + + foliage.position.x = pos.x; + foliage.position.y = pos.y; + foliage.position.z = pos.z; + + foliage.rotation.y = Math.random() * Math.PI * 2; + + return foliage; + } + private addGrass(size: THREE.Vector2, pos: THREE.Vector3, density: number): THREE.Group { const totalCount = size.x * size.y * density; @@ -700,7 +714,7 @@ export class SceneBuilder { grassGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( square, 3 ) ); grassGeo.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); grassGeo.setAttribute( 'uv', new THREE.Float32BufferAttribute( uv, 2 ) ); - grassGeo.setAttribute('color', new BufferAttribute(vertColors, 3)); + grassGeo.setAttribute('color', new THREE.BufferAttribute(vertColors, 3)); //const grass = new THREE.Mesh( grassGeo, grassMat ); const mesh = new THREE.InstancedMesh(grassGeo, grassMat, totalCount); @@ -804,7 +818,7 @@ export class SceneBuilder { bushGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( square, 3 ) ); bushGeo.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); bushGeo.setAttribute( 'uv', new THREE.Float32BufferAttribute( uv, 2 ) ); - bushGeo.setAttribute('color', new BufferAttribute(vertColors, 3)); + bushGeo.setAttribute('color', new THREE.BufferAttribute(vertColors, 3)); const mesh = new THREE.InstancedMesh(bushGeo, bushMat, totalCount);