diff --git a/features.md b/features.md index c9521dd..f0a9a09 100644 --- a/features.md +++ b/features.md @@ -12,6 +12,7 @@ - [ ] different materials - [ ] make vegetation sway in the wind - [ ] make all random stuff dependent on seed +- [ ] instanced vegetation ### weather - [ ] clouds @@ -45,4 +46,4 @@ - [ ] tilt shift ## other -- [ ] \ No newline at end of file +- [ ] walking on planet \ No newline at end of file diff --git a/index.html b/index.html index d868700..537b895 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,24 @@ + - three.js scaffold + + + tiny planets + + @@ -21,6 +32,8 @@ + made by flo-bit + diff --git a/src/script.ts b/src/script.ts index 3b4f1bf..a34c36e 100644 --- a/src/script.ts +++ b/src/script.ts @@ -2,6 +2,7 @@ import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { Planet } from "./worlds/planet"; import { Stars } from "./worlds/stars"; +import { planetPresets } from "./worlds/presets"; const presets = ["beach", "forest", "snowForest"]; @@ -36,7 +37,7 @@ let sphereMaterial = new THREE.MeshStandardMaterial({ wireframe: true, wireframeLinewidth: 10, }); -let planetMesh = new THREE.Mesh(sphereGeometry, sphereMaterial); +let planetMesh: THREE.Mesh = new THREE.Mesh(sphereGeometry, sphereMaterial); scene.add(planetMesh); const light = new THREE.DirectionalLight(); @@ -110,12 +111,16 @@ async function createPlanet(preset: string | undefined = undefined) { console.time("planet"); const planet = new Planet({ detail: 50, - biome: { preset }, + ...planetPresets[preset], }); let mesh = await planet.create(); scene.remove(planetMesh); scene.add(mesh); planetMesh = mesh; + + // planetMesh.add(camera); + // planet.updatePosition(camera, new THREE.Vector3(0, 0, 1.1)); + console.timeEnd("planet"); } diff --git a/src/worlds/biome.ts b/src/worlds/biome.ts index 10b723a..a5ee007 100644 --- a/src/worlds/biome.ts +++ b/src/worlds/biome.ts @@ -60,7 +60,7 @@ export class Biome { options: BiomeOptions; - vegetationPositions: Octree = new Octree(); + vegetationPositions: Octree = new Octree(); constructor(opts: BiomeOptions = {}) { if (opts.preset) { @@ -150,10 +150,31 @@ export class Biome { this.vegetationPositions.insert(position, item); } + closestVegetationDistance( + position: Vector3, + radius: number, + ): number | undefined { + const items = this.vegetationPositions.queryBoxXYZ( + position.x, + position.y, + position.z, + radius, + ); + if (items.length === 0) return undefined; + + let closest = Infinity; + for (const item of items) { + const distance = position.distanceTo(item); + if (distance < closest) closest = distance; + } + + return closest < radius ? closest : undefined; + } + itemsAround( position: Vector3, radius: number, - ): (Vector3 & { data: VegetationItem })[] { + ): (Vector3 & { data?: VegetationItem })[] { return this.vegetationPositions.query(position, radius); } } diff --git a/src/worlds/helper/helper.ts b/src/worlds/helper/helper.ts new file mode 100644 index 0000000..c4ad9ea --- /dev/null +++ b/src/worlds/helper/helper.ts @@ -0,0 +1,28 @@ +import { BufferGeometry, Float32BufferAttribute } from "three"; + +export function createBufferGeometry( + positions: number[], + colors?: number[], + normals?: number[], +) { + const geometry = new BufferGeometry(); + geometry.setAttribute( + "position", + new Float32BufferAttribute(new Float32Array(positions), 3), + ); + + if (colors) { + geometry.setAttribute( + "color", + new Float32BufferAttribute(new Float32Array(colors), 3), + ); + } + if (normals) { + geometry.setAttribute( + "normal", + new Float32BufferAttribute(new Float32Array(normals), 3), + ); + } + + return geometry; +} diff --git a/src/worlds/helper/octree.ts b/src/worlds/helper/octree.ts index 92e5aac..542ae35 100644 --- a/src/worlds/helper/octree.ts +++ b/src/worlds/helper/octree.ts @@ -25,12 +25,10 @@ export type OctreeOptions = { capacity?: number; }; -export type Vector3Data = Vector3 & { data?: unknown }; - -export class Octree { +export class Octree { boundary: Box3; - points: Vector3[]; + points: (Vector3 & { data?: T })[]; capacity: number; @@ -113,7 +111,7 @@ export class Octree { // returns array of points where // distance between pos and point is less than dist - query(pos: Vector3Data, dist = 1): Vector3Data[] { + query(pos: Vector3 & { data?: T }, dist = 1): (Vector3 & { data?: T })[] { const points = this.queryBoxXYZ(pos.x, pos.y, pos.z, dist); return points.filter((p) => p.distanceTo(pos) < dist); @@ -134,7 +132,7 @@ export class Octree { return this.queryBox(box); } - queryBox(box: Box3, found: Vector3Data[] = []) { + queryBox(box: Box3, found: (Vector3 & { data?: T })[] = []) { found ??= []; if (!box.intersectsBox(this.boundary)) return found; @@ -156,16 +154,16 @@ export class Octree { } // insert point with optional data (sets vec.data = data) - insert(pos: Vector3Data, data: unknown = undefined) { + insert(pos: Vector3 & { data?: T }, data: T | undefined = undefined) { return this.insertPoint(pos, data); } // vector3 free version - insertXYZ(x: number, y: number, z: number, data: unknown = undefined) { + insertXYZ(x: number, y: number, z: number, data: T | undefined = undefined) { return this.insertPoint(new Vector3(x, y, z), data); } - insertPoint(p: Vector3, data: unknown = undefined) { + insertPoint(p: Vector3, data: T | undefined = undefined) { p = p.clone(); // @ts-expect-error - data is not a property of Vector3 @@ -281,7 +279,7 @@ export class Octree { return boxes; } - all(arr: Vector3Data[] = []) { + all(arr: (Vector3 & { data?: T })[] = []) { arr ??= []; for (const p of this.points) { arr.push(p); diff --git a/src/worlds/materials/OceanCausticsMaterial.ts b/src/worlds/materials/OceanCausticsMaterial.ts index c5aa7c1..7b28907 100644 --- a/src/worlds/materials/OceanCausticsMaterial.ts +++ b/src/worlds/materials/OceanCausticsMaterial.ts @@ -1,64 +1,73 @@ -import { MeshStandardMaterial } from "three"; +import { + MeshStandardMaterial, + type MeshStandardMaterialParameters, +} from "three"; import { noise } from "./noise"; -const oceansCausticMaterial = new MeshStandardMaterial({ - vertexColors: true, -}); -oceansCausticMaterial.onBeforeCompile = (shader) => { - const caustics = ` -float caustics(vec4 vPos) { - // More intricate warping for marble patterns - // float warpFactor = 2.0; - // vec4 warpedPos = vPos * warpFactor + snoise(vPos * warpFactor * 0.5); - // vec4 warpedPos2 = warpedPos * warpFactor * 0.3 + snoise(warpedPos * warpFactor * 0.5 + vec4(0, 2, 4, 8)) + vPos; +export class PlanetMaterialWithCaustics extends MeshStandardMaterial { + constructor(parameters: MeshStandardMaterialParameters) { + super(parameters); - // // Modulate the color intensity based on the noise - // float vein = snoise(warpedPos2 * warpFactor) * snoise(warpedPos); + this.onBeforeCompile = (shader) => { + const caustics = ` + float caustics(vec4 vPos) { + // More intricate warping for marble patterns + // float warpFactor = 2.0; + // vec4 warpedPos = vPos * warpFactor + snoise(vPos * warpFactor * 0.5); + // vec4 warpedPos2 = warpedPos * warpFactor * 0.3 + snoise(warpedPos * warpFactor * 0.5 + vec4(0, 2, 4, 8)) + vPos; + + // // Modulate the color intensity based on the noise + // float vein = snoise(warpedPos2 * warpFactor) * snoise(warpedPos); + + // float a = 1.0 - (sin(vein * 12.0) + 1.0) * 0.5; + // float diff = snoise(vPos * warpFactor); + // diff = diff * snoise(diff * vPos) * a; + // return vec3((diff)); + + vec4 warpedPos = vPos * 2.0 + snoise(vPos * 3.0); + vec4 warpedPos2 = warpedPos * 0.3 + snoise(warpedPos * 2.0 + vec4(0, 2, 4, 8)) + vPos; + float vein = snoise(warpedPos2) * snoise(warpedPos); + float a = 1.0 - (sin(vein * 2.0) + 1.0) * 0.5; + + return snoise(vPos + warpedPos + warpedPos2) * a * 1.5; + }`; + shader.vertexShader = + `varying vec3 vPos;\n${shader.vertexShader}`.replace( + `#include `, + `#include \nvPos = position;`, + ); - // float a = 1.0 - (sin(vein * 12.0) + 1.0) * 0.5; - // float diff = snoise(vPos * warpFactor); - // diff = diff * snoise(diff * vPos) * a; - // return vec3((diff)); + shader.fragmentShader = ` + uniform float time; + varying vec3 vPos; + ${noise} + ${caustics} + ${shader.fragmentShader}`; - vec4 warpedPos = vPos * 2.0 + snoise(vPos * 3.0); - vec4 warpedPos2 = warpedPos * 0.3 + snoise(warpedPos * 2.0 + vec4(0, 2, 4, 8)) + vPos; -float vein = snoise(warpedPos2) * snoise(warpedPos); -float a = 1.0 - (sin(vein * 2.0) + 1.0) * 0.5; + shader.fragmentShader = shader.fragmentShader.replace( + "#include ", + `#include + vec3 pos = vPos * 3.0; + float len = length(vPos); + // Fade in + float fadeIn = smoothstep(0.96, 0.985, len); + // Fade out + float fadeOut = 1.0 - smoothstep(0.994, 0.999, len); + float causticIntensity = fadeIn * fadeOut * 0.7; + diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), causticIntensity * smoothstep(0.0, 1.0, caustics(vec4(pos, time * 0.05)))); + `, + ); - return snoise(vPos + warpedPos + warpedPos2) * a * 1.5; -}`; - shader.vertexShader = `varying vec3 vPos;\n${shader.vertexShader}`.replace( - `#include `, - `#include \nvPos = position;`, - ); + shader.uniforms.time = { value: 0 }; + this.userData.shader = shader; + }; + } - shader.fragmentShader = ` - uniform float time; - varying vec3 vPos; - ${noise} - ${caustics} - ${shader.fragmentShader}`; + update() { + if (this.userData.shader?.uniforms?.time) { + this.userData.shader.uniforms.time.value = performance.now() / 1000; + } + } +} - shader.fragmentShader = shader.fragmentShader.replace( - "#include ", - `#include - vec3 pos = vPos * 3.0; - float len = length(vPos); - // Fade in - float fadeIn = smoothstep(0.96, 0.985, len); - // Fade out - float fadeOut = 1.0 - smoothstep(0.994, 0.999, len); - float causticIntensity = fadeIn * fadeOut * 0.7; - diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), causticIntensity * smoothstep(0.0, 1.0, caustics(vec4(pos, time * 0.05)))); -`, - ); - - shader.uniforms.time = { value: 0 }; - oceansCausticMaterial.userData.shader = shader; - - // console.log("FRAGMENT", shader.fragmentShader); - // console.log(); - // console.log("VERTEX", shader.vertexShader); -}; - -export default oceansCausticMaterial; +export default PlanetMaterialWithCaustics; diff --git a/src/worlds/materials/noise.ts b/src/worlds/materials/noise.ts index 9f97be5..d0ecf22 100644 --- a/src/worlds/materials/noise.ts +++ b/src/worlds/materials/noise.ts @@ -118,5 +118,4 @@ float snoise(vec4 v) { m0 = m0 * m0; m1 = m1 * m1; return 49.0 * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); - }`; diff --git a/src/worlds/planet.ts b/src/worlds/planet.ts index e637083..9505f3e 100644 --- a/src/worlds/planet.ts +++ b/src/worlds/planet.ts @@ -11,8 +11,9 @@ import { import { Biome, type BiomeOptions } from "./biome"; import { loadModels } from "./models"; -import oceansCausticMaterial from "./materials/OceanCausticsMaterial"; +import { PlanetMaterialWithCaustics } from "./materials/OceanCausticsMaterial"; import { createAtmosphereMaterial } from "./materials/AtmosphereMaterial"; +import { createBufferGeometry } from "./helper/helper"; export type PlanetOptions = { scatter?: number; @@ -22,10 +23,13 @@ export type PlanetOptions = { detail?: number; atmosphere?: { + enabled?: boolean; color?: Vector3; height?: number; }; + material?: "normal" | "caustics"; + biome?: BiomeOptions; }; @@ -73,7 +77,7 @@ export class Planet { requestId: number; }; }) { - const { type, data, requestId } = event.data; + const { data, requestId } = event.data; const callback = this.callbacks[requestId]; if (!callback) { @@ -81,50 +85,38 @@ export class Planet { return; } - if (type === "geometry") { - const geometry = new BufferGeometry(); - const oceanGeometry = new BufferGeometry(); - geometry.setAttribute( - "position", - new Float32BufferAttribute(new Float32Array(data.positions), 3), - ); - geometry.setAttribute( - "color", - new Float32BufferAttribute(new Float32Array(data.colors), 3), - ); - geometry.setAttribute( - "normal", - new Float32BufferAttribute(new Float32Array(data.normals), 3), - ); - - oceanGeometry.setAttribute( - "position", - new Float32BufferAttribute(new Float32Array(data.oceanPositions), 3), - ); - oceanGeometry.setAttribute( - "color", - new Float32BufferAttribute(new Float32Array(data.oceanColors), 3), - ); - oceanGeometry.setAttribute( - "normal", - new Float32BufferAttribute(new Float32Array(data.oceanNormals), 3), - ); - // set morph targets - oceanGeometry.morphAttributes.position = [ - new Float32BufferAttribute( - new Float32Array(data.oceanMorphPositions), - 3, - ), - ]; - oceanGeometry.morphAttributes.normal = [ - new Float32BufferAttribute(new Float32Array(data.oceanMorphNormals), 3), - ]; - - this.vegetationPositions = data.vegetation; - - const planetMesh = new Mesh(geometry, oceansCausticMaterial); - planetMesh.castShadow = true; + const geometry = createBufferGeometry( + data.positions, + data.colors, + data.normals, + ); + + const oceanGeometry = createBufferGeometry( + data.oceanPositions, + data.oceanColors, + data.oceanNormals, + ); + + oceanGeometry.morphAttributes.position = [ + new Float32BufferAttribute(data.oceanMorphPositions, 3), + ]; + oceanGeometry.morphAttributes.normal = [ + new Float32BufferAttribute(data.oceanMorphNormals, 3), + ]; + this.vegetationPositions = data.vegetation; + + const materialOptions = { vertexColors: true }; + + const material = + this.options.material === "caustics" + ? new PlanetMaterialWithCaustics(materialOptions) + : new MeshStandardMaterial(materialOptions); + + const planetMesh = new Mesh(geometry, material); + planetMesh.castShadow = true; + + if (this.options.material === "caustics") { planetMesh.onBeforeRender = ( renderer, scene, @@ -132,41 +124,41 @@ export class Planet { geometry, material, ) => { - if (material.userData.shader?.uniforms?.time) { - material.userData.shader.uniforms.time.value = - performance.now() / 1000; + if (material instanceof PlanetMaterialWithCaustics) { + material.update(); } - //material.userData.shader.uniforms.time.value = performance.now() / 1000; }; + } - const oceanMesh = new Mesh( - oceanGeometry, - new MeshStandardMaterial({ - vertexColors: true, - transparent: true, - opacity: 0.7, - metalness: 0.5, - roughness: 0.5, - }), - ); - - planetMesh.add(oceanMesh); - oceanMesh.onBeforeRender = ( - renderer, - scene, - camera, - geometry, - material, - ) => { - // update morph targets - if (oceanMesh.morphTargetInfluences) - oceanMesh.morphTargetInfluences[0] = - Math.sin(performance.now() / 1000) * 0.5 + 0.5; - }; + const oceanMesh = new Mesh( + oceanGeometry, + new MeshStandardMaterial({ + vertexColors: true, + transparent: true, + opacity: 0.7, + metalness: 0.5, + roughness: 0.5, + }), + ); + + planetMesh.add(oceanMesh); + oceanMesh.onBeforeRender = ( + renderer, + scene, + camera, + geometry, + material, + ) => { + // update morph targets + if (oceanMesh.morphTargetInfluences) + oceanMesh.morphTargetInfluences[0] = + Math.sin(performance.now() / 1000) * 0.5 + 0.5; + }; + if (this.options.atmosphere?.enabled !== false) { this.addAtmosphere(planetMesh); - callback(planetMesh); } + callback(planetMesh); delete this.callbacks[requestId]; } @@ -193,7 +185,7 @@ export class Planet { const planet = await planetPromise; for (let i = 0; i < loaded.length - 1; i++) { - const models = await loaded[i]; + const models = (await loaded[i]) as Object3D[]; const name = models[0].userData.name; const positions = this.vegetationPositions?.[name]; @@ -214,7 +206,7 @@ export class Planet { model.traverse((child) => { if (child instanceof Mesh) { let color = item?.colors?.[child.material.name]; - if (color && color.array) { + if (color?.array) { let randomColor = color.array[Math.floor(Math.random() * color.array.length)]; child.material.color.setHex(randomColor); diff --git a/src/worlds/presets.ts b/src/worlds/presets.ts index 2148e4e..b08a4b2 100644 --- a/src/worlds/presets.ts +++ b/src/worlds/presets.ts @@ -1,6 +1,7 @@ import { type BiomeOptions } from "./biome"; +import { PlanetOptions } from "./planet"; -const beach: BiomeOptions = { +const beachBiome: BiomeOptions = { noise: { min: -0.05, max: 0.05, @@ -45,6 +46,9 @@ const beach: BiomeOptions = { Green: { array: [0x22851e, 0x22a51e] }, DarkGreen: { array: [0x006400] }, }, + ground: { + color: 0x338800, + }, }, { name: "Rock", @@ -58,7 +62,7 @@ const beach: BiomeOptions = { }, }; -const forest: BiomeOptions = { +const forestBiome: BiomeOptions = { noise: { min: -0.05, max: 0.05, @@ -154,7 +158,7 @@ const forest: BiomeOptions = { }, }; -const snowForest: BiomeOptions = { +const snowForestBiome: BiomeOptions = { noise: { min: -0.05, max: 0.05, @@ -251,7 +255,37 @@ const snowForest: BiomeOptions = { }; export const biomePresets: Record = { - beach, - forest, - snowForest, + beach: beachBiome, + forest: forestBiome, + snowForest: snowForestBiome, +}; + +const beachPlanet: PlanetOptions = { + biome: { + preset: "beach", + }, + + material: "caustics", +}; + +const forestPlanet: PlanetOptions = { + biome: { + preset: "forest", + }, + + material: "normal", +}; + +const snowForestPlanet: PlanetOptions = { + biome: { + preset: "snowForest", + }, + + material: "normal", +}; + +export const planetPresets: Record = { + beach: beachPlanet, + forest: forestPlanet, + snowForest: snowForestPlanet, }; diff --git a/src/worlds/worker.ts b/src/worlds/worker.ts index 11ebc1d..087eece 100644 --- a/src/worlds/worker.ts +++ b/src/worlds/worker.ts @@ -6,7 +6,7 @@ import { Color, } from "three"; -import { Biome } from "./biome"; +import { Biome, type VegetationItem } from "./biome"; import { type PlanetOptions } from "./planet"; import UberNoise from "uber-noise"; @@ -110,7 +110,7 @@ function createGeometry( a.fromBufferAttribute(vertices, 0); b.fromBufferAttribute(vertices, 1); - // default to scatter = distance of first edge + // scatterAmount is based on side length of face (all faces are the same size) const scatterAmount = (planetOptions.scatter ?? 1) * b.distanceTo(a); const scatterScale = 100; @@ -136,9 +136,13 @@ function createGeometry( const temp = new Vector3(); - let normHeightMax = 0; - let normHeightMin = 0; - + // go through all faces + // - calculate height and scatter for vertices + // - calculate height for ocean vertices + // - calculate height for ocean morph vertices + // - calculate color for vertices and ocean vertices + // - calculate normal for vertices and ocean vertices + // - add vegetation for (let i = 0; i < vertices.count; i += 3) { a.fromBufferAttribute(vertices, i); b.fromBufferAttribute(vertices, i + 1); @@ -153,16 +157,19 @@ function createGeometry( let normalizedHeight = 0; + // go through all vertices of the face for (let j = 0; j < 3; j++) { let v = a; if (j === 1) v = b; if (j === 2) v = c; + // lets see if we already have info for this vertex const key = `${v.x.toFixed(5)},${v.y.toFixed(5)},${v.z.toFixed(5)}`; - let move = calculatedVertices.get(key); + // if not, calculate it if (!move) { + // calculate height and scatter const height = biome.getHeight(v) + 1; const scatterX = scatterNoise.get(v); const scatterY = scatterNoise.get( @@ -175,6 +182,7 @@ function createGeometry( v.x + scatterScale * 200, v.y - scatterScale * 200, ); + // calculate sea height and sea morph height const seaHeight = biome.getSeaHeight(v) + 1; const secondSeaHeight = biome.getSeaHeight(v.addScalar(100)) + 1; @@ -189,19 +197,24 @@ function createGeometry( calculatedVertices.set(key, move); } + // we store this info for later use (vegetation placement) calculatedVerticesArray[i + j] = move; + // we add height here so we can calculate the average normalized height of the face later normalizedHeight += move.height - 1; + + // move vertex based on height and scatter v.add(move.scatter).normalize().multiplyScalar(move.height); vertices.setXYZ(i + j, v.x, v.y, v.z); + // move ocean vertex based on sea height and scatter let oceanV = oceanA; if (j === 1) oceanV = oceanB; if (j === 2) oceanV = oceanC; - oceanV.add(move.scatter).normalize().multiplyScalar(move.seaMorph); oceanMorphPositions.push(oceanV.x, oceanV.y, oceanV.z); + // move ocean morph vertex based on sea height and scatter if (j === 0) { oceanD.copy(oceanV); } else if (j === 1) { @@ -209,24 +222,20 @@ function createGeometry( } else if (j === 2) { oceanF.copy(oceanV); } - oceanV.normalize().multiplyScalar(move.seaHeight); oceanVertices.setXYZ(i + j, oceanV.x, oceanV.y, oceanV.z); } + // calculate normalized height for the face (between -1 and 1, 0 is sea level) normalizedHeight /= 3; - normalizedHeight = Math.min(-normalizedHeight / biome.min, 0) + Math.max(normalizedHeight / biome.max, 0); // now normalizedHeight should be between -1 and 1 (0 is sea level) + // this will be used for color calculation and vegetation placement - normHeightMax = Math.max(normHeightMax, normalizedHeight); - normHeightMin = Math.min(normHeightMin, normalizedHeight); - - // calculate new normal + // calculate face normal temp.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize(); - // flat shading, so all normals for the face are the same normals.setXYZ(i, temp.x, temp.y, temp.z); normals.setXYZ(i + 1, temp.x, temp.y, temp.z); @@ -236,9 +245,10 @@ function createGeometry( // (up vector = old mid point on sphere) const steepness = Math.acos(Math.abs(temp.dot(mid))); // steepness is between 0 and PI/2 + // this will be used for color calculation and vegetation placement + // calculate color for face const color = biome.getColor(mid, normalizedHeight, steepness); - // flat shading, so all colors for the face are the same if (color) { colors[i * 3] = color.r; @@ -254,6 +264,39 @@ function createGeometry( colors[i * 3 + 8] = color.b; } + // calculate ocean face color + const oceanColor = biome.getSeaColor(mid, normalizedHeight); + + if (oceanColor) { + oceanColors[i * 3] = oceanColor.r; + oceanColors[i * 3 + 1] = oceanColor.g; + oceanColors[i * 3 + 2] = oceanColor.b; + + oceanColors[i * 3 + 3] = oceanColor.r; + oceanColors[i * 3 + 4] = oceanColor.g; + oceanColors[i * 3 + 5] = oceanColor.b; + + oceanColors[i * 3 + 6] = oceanColor.r; + oceanColors[i * 3 + 7] = oceanColor.g; + oceanColors[i * 3 + 8] = oceanColor.b; + } + + // calculate ocean normals + temp + .crossVectors(oceanB.clone().sub(oceanA), oceanC.clone().sub(oceanA)) + .normalize(); + oceanNormals.setXYZ(i, temp.x, temp.y, temp.z); + oceanNormals.setXYZ(i + 1, temp.x, temp.y, temp.z); + oceanNormals.setXYZ(i + 2, temp.x, temp.y, temp.z); + + // calculate ocean morph normals + temp + .crossVectors(oceanE.clone().sub(oceanD), oceanF.clone().sub(oceanD)) + .normalize(); + oceanMorphNormals.push(temp.x, temp.y, temp.z); + oceanMorphNormals.push(temp.x, temp.y, temp.z); + oceanMorphNormals.push(temp.x, temp.y, temp.z); + // place vegetation for ( let j = 0; @@ -314,40 +357,6 @@ function createGeometry( break; } } - - // calculate ocean vertices - const oceanColor = biome.getSeaColor(mid, normalizedHeight); - - if (oceanColor) { - oceanColors[i * 3] = oceanColor.r; - oceanColors[i * 3 + 1] = oceanColor.g; - oceanColors[i * 3 + 2] = oceanColor.b; - - oceanColors[i * 3 + 3] = oceanColor.r; - oceanColors[i * 3 + 4] = oceanColor.g; - oceanColors[i * 3 + 5] = oceanColor.b; - - oceanColors[i * 3 + 6] = oceanColor.r; - oceanColors[i * 3 + 7] = oceanColor.g; - oceanColors[i * 3 + 8] = oceanColor.b; - } - - // calculate ocean normals - temp - .crossVectors(oceanB.clone().sub(oceanA), oceanC.clone().sub(oceanA)) - .normalize(); - - oceanNormals.setXYZ(i, temp.x, temp.y, temp.z); - oceanNormals.setXYZ(i + 1, temp.x, temp.y, temp.z); - oceanNormals.setXYZ(i + 2, temp.x, temp.y, temp.z); - - temp - .crossVectors(oceanE.clone().sub(oceanD), oceanF.clone().sub(oceanD)) - .normalize(); - - oceanMorphNormals.push(temp.x, temp.y, temp.z); - oceanMorphNormals.push(temp.x, temp.y, temp.z); - oceanMorphNormals.push(temp.x, temp.y, temp.z); } const maxDist = 0.14; @@ -355,6 +364,8 @@ function createGeometry( for (let i = 0; i < vertices.count; i += 3) { let found = false; let closestDistAll = 1; + let closestVegetation: VegetationItem | undefined = undefined; + for (let j = 0; j < 3; j++) { a.fromBufferAttribute(vertices, i + j); a.normalize(); @@ -380,20 +391,26 @@ function createGeometry( vertices.setXYZ(i + j, a.x, a.y, a.z); + if (closestDist < closestDistAll) { + closestVegetation = closest.data; + } + closestDistAll = Math.min(closestDist, closestDistAll); found = true; } } - if (found) { - let existingColor = new Color( - colors[i * 3], - colors[i * 3 + 1], - colors[i * 3 + 2], - ); + if (!found) continue; + + let existingColor = new Color( + colors[i * 3], + colors[i * 3 + 1], + colors[i * 3 + 2], + ); + if (closestVegetation?.ground?.color) { // set color - let newColor = new Color(0.1, 0.3, 0); + let newColor = new Color(closestVegetation.ground.color); newColor.lerp(existingColor, closestDistAll / maxDist);