[READ-ONLY] Mirror of https://github.com/flo-bit/tiny-planets. procedurally generated tiny planets in the browsers flo-bit.dev/tiny-planets/
low-poly planets procedural-generation threejs
0

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #1 from flo-bit/main

stuff

authored by

Florian and committed by
GitHub
(Sep 23, 2024, 1:02 PM +0200) 5fe29b2e b88f3745

+941 -375
+6 -2
Readme.md
··· 1 1 # tiny planets 2 2 3 - procedurally generated tiny planets in three.js 3 + procedurally generated tiny planets in three.js. work in progress. 4 + 5 + 6 + https://github.com/user-attachments/assets/55ba1872-d064-483b-9a31-2c95adaa201a 7 + 4 8 5 9 ## features 6 10 7 11 - generated using web workers 8 12 - adjustable detail level 9 13 - with vegetation 10 - - presets 14 + - presets
+11 -3
features.md
··· 9 9 - [x] height 10 10 - [ ] slope 11 11 - [ ] different materials 12 + - [ ] make vegetation sway in the wind 13 + - [ ] make all random stuff dependent on seed 12 14 13 15 ### weather 14 16 - [ ] clouds 15 17 - [ ] particles (snow, rain, etc) 16 18 17 19 ### water 18 - - [ ] moving water 20 + - [x] moving water 19 21 - [ ] water reflections 20 22 - [ ] water refractions 21 - - [ ] water caustics 23 + - [x] water caustics 22 24 - [ ] water foam 23 25 24 26 ### biomes ··· 34 36 - [ ] shadows 35 37 - [ ] ambient occlusion 36 38 39 + - [ ] physics 37 40 38 - - [ ] physics 41 + ## filters 42 + - [ ] selective bloom (for glowy things) 43 + - [ ] tilt shift 44 + 45 + ## other 46 + - [ ]
+8 -5
index.html
··· 11 11 <link rel="stylesheet" href="./index.css" /> 12 12 </head> 13 13 14 - <body class="bg-black" 15 - > 16 - <button id="button" class="p-2 px-3 border border-white/20 rounded-md text-white z-10 bg-white/10 absolute bottom-2 right-2 font-semibold text-lg hover:bg-white/20">random</button> 14 + <body class="bg-black"></body> 15 + <button 16 + id="button" 17 + class="hidden p-2 px-3 border border-white/20 rounded-md text-white z-10 bg-white/10 absolute bottom-2 right-2 font-semibold text-lg hover:bg-white/20" 18 + > 19 + random 20 + </button> 17 21 18 - <canvas id="root" ></canvas> 22 + <canvas id="root"></canvas> 19 23 20 24 <script src="src/script.ts" type="module"></script> 21 - 22 25 </body> 23 26 </html>
+6 -2
src/script.ts
··· 58 58 const ambientLight = new THREE.AmbientLight(0xffffff, 0.1); 59 59 scene.add(ambientLight); 60 60 61 + 61 62 let total = 0; 62 63 let lastDelta = 0; 63 64 renderer.setAnimationLoop((delta) => { ··· 72 73 73 74 if (!hasPlanet) { 74 75 console.log("Creating planet"); 75 - createPlanet(); 76 + createPlanet("beach"); 76 77 hasPlanet = true; 77 78 } 78 79 }); ··· 104 105 } 105 106 106 107 console.time("planet"); 107 - const planet = new Planet({ preset }); 108 + const planet = new Planet({ 109 + detail: 50, 110 + biome: { preset }, 111 + }); 108 112 let mesh = await planet.create(); 109 113 scene.remove(planetMesh); 110 114 scene.add(mesh);
+35 -11
src/worlds/biome.ts
··· 6 6 type ColorGradientOptions, 7 7 } from "./helper/colorgradient"; 8 8 import { biomePresets } from "./presets"; 9 + import { Octree } from "./helper/octree"; 10 + 11 + export type VegetationItem = { 12 + name: string; 13 + density: number; 14 + 15 + minimumHeight?: number; 16 + maximumHeight?: number; 17 + 18 + minimumSlope?: number; 19 + maximumSlope?: number; 20 + 21 + minimumDistance?: number; 22 + maximumDistance?: number; 23 + colors?: Record<string, { array?: number[] }>; 24 + }; 9 25 10 26 export type BiomeOptions = { 11 27 name?: string; ··· 21 37 tintColor?: number; 22 38 23 39 vegetation?: { 24 - items: { 25 - name: string; 26 - density: number; 27 - 28 - minimumHeight?: number; 29 - maximumHeight?: number; 30 - 31 - minimumDistance?: number; 32 - maximumDistance?: number; 33 - colors?: Record<string, { array?: number[] }>; 34 - }[]; 40 + defaults?: { 41 + density?: number; 42 + }; 43 + items: VegetationItem[]; 35 44 }; 36 45 }; 37 46 ··· 43 52 seaColors: ColorGradient | undefined; 44 53 45 54 options: BiomeOptions; 55 + 56 + vegetationPositions: Octree = new Octree(); 46 57 47 58 constructor(opts: BiomeOptions = {}) { 48 59 if (opts.preset) { ··· 121 132 } 122 133 123 134 return undefined; 135 + } 136 + 137 + addVegetation( 138 + item: VegetationItem, 139 + position: Vector3, 140 + normalizedHeight: number, 141 + steepness: number, 142 + ) { 143 + this.vegetationPositions.insert(position, item); 144 + } 145 + 146 + itemsAround(position: Vector3, radius: number): Vector3[] { 147 + return this.vegetationPositions.query(position, radius); 124 148 } 125 149 }
+78 -17
src/worlds/planet.ts
··· 6 6 Object3D, 7 7 Quaternion, 8 8 Vector3, 9 + IcosahedronGeometry, 9 10 } from "three"; 10 11 import { Biome, type BiomeOptions } from "./biome"; 11 12 import { loadModels } from "./models"; 13 + 14 + import oceansCausticMaterial from "./materials/OceanCausticsMaterial"; 15 + import { createAtmosphereMaterial } from "./materials/AtmosphereMaterial"; 12 16 13 17 export type PlanetOptions = { 14 18 scatter?: number; ··· 16 20 ground?: number; 17 21 18 22 detail?: number; 23 + 24 + atmosphere?: { 25 + color?: Vector3; 26 + height?: number; 27 + }; 28 + 29 + biome?: BiomeOptions; 19 30 }; 20 31 21 32 export class Planet { ··· 31 42 32 43 vegetationPositions?: Record<string, Vector3[]>; 33 44 34 - constructor(biomeOptions: BiomeOptions, planetOptions: PlanetOptions = {}) { 35 - this.options = planetOptions; 45 + constructor(options: PlanetOptions = {}) { 46 + this.options = options; 36 47 37 - this.biome = new Biome(biomeOptions); 48 + this.biome = new Biome(options.biome); 38 49 this.biomeOptions = this.biome.options; 39 50 40 51 this.worker = new Worker(new URL("worker.ts", import.meta.url), { ··· 56 67 oceanColors: number[]; 57 68 oceanNormals: number[]; 58 69 vegetation: Record<string, Vector3[]>; 70 + oceanMorphPositions: number[]; 71 + oceanMorphNormals: number[]; 59 72 }; 60 73 requestId: number; 61 74 }; ··· 92 105 "color", 93 106 new Float32BufferAttribute(new Float32Array(data.oceanColors), 3), 94 107 ); 95 - 96 - oceanGeometry.computeVertexNormals(); 108 + oceanGeometry.setAttribute( 109 + "normal", 110 + new Float32BufferAttribute(new Float32Array(data.oceanNormals), 3), 111 + ); 112 + // set morph targets 113 + oceanGeometry.morphAttributes.position = [ 114 + new Float32BufferAttribute( 115 + new Float32Array(data.oceanMorphPositions), 116 + 3, 117 + ), 118 + ]; 119 + oceanGeometry.morphAttributes.normal = [ 120 + new Float32BufferAttribute(new Float32Array(data.oceanMorphNormals), 3), 121 + ]; 97 122 98 123 this.vegetationPositions = data.vegetation; 99 124 100 - const planetMesh = new Mesh( 101 - geometry, 102 - new MeshStandardMaterial({ 103 - vertexColors: true, 104 - }), 105 - ); 125 + const planetMesh = new Mesh(geometry, oceansCausticMaterial); 106 126 planetMesh.castShadow = true; 127 + 128 + planetMesh.onBeforeRender = ( 129 + renderer, 130 + scene, 131 + camera, 132 + geometry, 133 + material, 134 + ) => { 135 + if (material.userData.shader?.uniforms?.time) { 136 + material.userData.shader.uniforms.time.value = 137 + performance.now() / 1000; 138 + } 139 + //material.userData.shader.uniforms.time.value = performance.now() / 1000; 140 + }; 107 141 108 142 const oceanMesh = new Mesh( 109 143 oceanGeometry, ··· 117 151 ); 118 152 119 153 planetMesh.add(oceanMesh); 154 + oceanMesh.onBeforeRender = ( 155 + renderer, 156 + scene, 157 + camera, 158 + geometry, 159 + material, 160 + ) => { 161 + // update morph targets 162 + if (oceanMesh.morphTargetInfluences) 163 + oceanMesh.morphTargetInfluences[0] = 164 + Math.sin(performance.now() / 1000) * 0.5 + 0.5; 165 + }; 166 + 167 + this.addAtmosphere(planetMesh); 120 168 callback(planetMesh); 121 169 } 122 170 ··· 124 172 } 125 173 126 174 async create(): Promise<Mesh> { 175 + // let collection = "stylized_nature"; 176 + 127 177 const models = this.biomeOptions.vegetation?.items.map((item) => { 128 178 return item.name; 129 179 }); ··· 131 181 const loaded: Promise<Object3D[] | Mesh>[] = []; 132 182 133 183 for (const model of models ?? []) { 134 - const loadedModels = loadModels(model); 184 + const loadedModels = loadModels(model); //, collection); 135 185 loaded.push(loadedModels); 136 186 } 137 187 ··· 185 235 return planetPromise; 186 236 } 187 237 188 - createMesh(): Promise<Mesh> { 238 + async createMesh(): Promise<Mesh> { 189 239 return new Promise((resolve) => { 190 240 const requestId = this.requestId++; 191 241 this.callbacks[requestId] = resolve; ··· 193 243 this.worker.postMessage({ 194 244 type: "createGeometry", 195 245 requestId, 196 - data: { 197 - biomeOptions: this.biome.options, 198 - planetOptions: this.options, 199 - }, 246 + data: this.options, 200 247 }); 201 248 }); 249 + } 250 + 251 + addAtmosphere(planet: Mesh) { 252 + // Create the atmosphere geometry 253 + const atmosphereGeometry = new IcosahedronGeometry( 254 + this.options.atmosphere?.height ?? 1.2, 255 + this.options.detail ?? 20, 256 + ); 257 + const atmosphere = new Mesh( 258 + atmosphereGeometry, 259 + createAtmosphereMaterial(this.options.atmosphere?.color), 260 + ); 261 + atmosphere.renderOrder = 1; 262 + planet.add(atmosphere); 202 263 } 203 264 204 265 updatePosition(item: Object3D, pos: Vector3) {
+4 -4
src/worlds/presets.ts
··· 25 25 26 26 seaColors: [ 27 27 [-1, 0x000066], 28 - [-0.52, 0x0000aa], 28 + [-0.55, 0x0000aa], 29 29 [-0.1, 0x00f2e5], 30 30 ], 31 31 seaNoise: { 32 - min: -0.005, 33 - max: 0.005, 34 - scale: 5, 32 + min: -0.008, 33 + max: 0.008, 34 + scale: 6, 35 35 }, 36 36 37 37 vegetation: {
+239 -56
src/worlds/worker.ts
··· 1 - import { IcosahedronGeometry, Vector3, BufferAttribute } from "three"; 1 + import { 2 + IcosahedronGeometry, 3 + Vector3, 4 + BufferAttribute, 5 + Float32BufferAttribute, 6 + Color, 7 + } from "three"; 2 8 3 9 import { Biome, type BiomeOptions } from "./biome"; 4 10 import { type PlanetOptions } from "./planet"; ··· 17 23 const oceanPositions = oceanGeometry.getAttribute("position").array.buffer; 18 24 const oceanColors = oceanGeometry.getAttribute("color").array.buffer; 19 25 const oceanNormals = oceanGeometry.getAttribute("normal").array.buffer; 26 + const oceanMorphPositions = 27 + oceanGeometry.morphAttributes.position[0].array.buffer; 28 + const oceanMorphNormals = 29 + oceanGeometry.morphAttributes.normal[0].array.buffer; 20 30 21 31 postMessage( 22 32 { ··· 29 39 oceanColors, 30 40 oceanNormals, 31 41 vegetation, 42 + oceanMorphPositions, 43 + oceanMorphNormals, 32 44 }, 33 45 requestId, 34 46 }, 35 47 // @ts-expect-error - hmm 36 - [positions, colors, normals, oceanPositions, oceanColors, oceanNormals], 48 + [ 49 + positions, 50 + colors, 51 + normals, 52 + oceanPositions, 53 + oceanColors, 54 + oceanNormals, 55 + oceanMorphPositions, 56 + oceanMorphNormals, 57 + ], 37 58 ); 38 59 } else { 39 60 console.error("Unknown message type", type); 40 61 } 41 62 }; 42 63 43 - function createGeometry({ 44 - biomeOptions, 45 - planetOptions, 46 - }: { 47 - biomeOptions: BiomeOptions; 48 - planetOptions: PlanetOptions; 49 - }): [IcosahedronGeometry, IcosahedronGeometry, Record<string, Vector3[]>] { 64 + function createGeometry( 65 + planetOptions: PlanetOptions, 66 + ): [IcosahedronGeometry, IcosahedronGeometry, Record<string, Vector3[]>] { 50 67 const sphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 51 68 const oceanSphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 52 69 53 - const biome = new Biome(biomeOptions); 70 + const biome = new Biome(planetOptions.biome); 54 71 55 72 const vertices = sphere.getAttribute("position"); 56 73 const oceanVertices = oceanSphere.getAttribute("position"); ··· 62 79 string, 63 80 { 64 81 height: number; 65 - seaHeight: number; 66 82 scatter: Vector3; 83 + 84 + seaHeight: number; 85 + seaMorph: number; 67 86 } 68 87 >(); 88 + 89 + const calculatedVerticesArray: { 90 + height: number; 91 + scatter: Vector3; 92 + 93 + seaHeight: number; 94 + seaMorph: number; 95 + }[] = new Array(faceCount); 69 96 70 97 const colors = new Float32Array(vertices.count * 3); 71 98 const oceanColors = new Float32Array(oceanVertices.count * 3); ··· 84 111 b.fromBufferAttribute(vertices, 1); 85 112 86 113 // default to scatter = distance of first edge 87 - const scatterAmount = planetOptions.scatter ?? b.distanceTo(a); 114 + const scatterAmount = (planetOptions.scatter ?? 1) * b.distanceTo(a); 88 115 const scatterScale = 100; 89 116 90 117 const scatterNoise = new UberNoise({ ··· 94 121 seed: 0, 95 122 }); 96 123 124 + oceanSphere.morphAttributes.position = []; 125 + oceanSphere.morphAttributes.normal = []; 126 + 127 + const oceanMorphPositions: number[] = []; 128 + const oceanMorphNormals: number[] = []; 129 + 130 + const oceanA = new Vector3(), 131 + oceanB = new Vector3(), 132 + oceanC = new Vector3(), 133 + oceanD = new Vector3(), 134 + oceanE = new Vector3(), 135 + oceanF = new Vector3(); 136 + 137 + const temp = new Vector3(); 138 + 139 + let normHeightMax = 0; 140 + let normHeightMin = 0; 141 + 97 142 for (let i = 0; i < vertices.count; i += 3) { 98 143 a.fromBufferAttribute(vertices, i); 99 144 b.fromBufferAttribute(vertices, i + 1); 100 145 c.fromBufferAttribute(vertices, i + 2); 146 + 147 + oceanA.fromBufferAttribute(oceanVertices, i); 148 + oceanB.fromBufferAttribute(oceanVertices, i + 1); 149 + oceanC.fromBufferAttribute(oceanVertices, i + 2); 101 150 102 151 mid.set(0, 0, 0); 103 152 mid.addVectors(a, b).add(c).divideScalar(3); ··· 108 157 let v = a; 109 158 if (j === 1) v = b; 110 159 if (j === 2) v = c; 160 + 111 161 const key = `${v.x.toFixed(5)},${v.y.toFixed(5)},${v.z.toFixed(5)}`; 112 162 113 163 let move = calculatedVertices.get(key); ··· 126 176 v.y - scatterScale * 200, 127 177 ); 128 178 const seaHeight = biome.getSeaHeight(v) + 1; 179 + const secondSeaHeight = biome.getSeaHeight(v.addScalar(100)) + 1; 180 + 181 + v.subScalar(100); 129 182 130 183 move = { 131 184 height, 132 185 scatter: new Vector3(scatterX, scatterY, scatterZ), 133 186 seaHeight, 187 + seaMorph: secondSeaHeight, 134 188 }; 135 189 calculatedVertices.set(key, move); 136 190 } 137 191 192 + calculatedVerticesArray[i + j] = move; 193 + 138 194 normalizedHeight += move.height - 1; 139 195 v.add(move.scatter).normalize().multiplyScalar(move.height); 140 - 141 196 vertices.setXYZ(i + j, v.x, v.y, v.z); 142 197 143 - const oceanV = v.clone().normalize().multiplyScalar(move.seaHeight); 198 + let oceanV = oceanA; 199 + if (j === 1) oceanV = oceanB; 200 + if (j === 2) oceanV = oceanC; 201 + 202 + oceanV.add(move.scatter).normalize().multiplyScalar(move.seaMorph); 203 + oceanMorphPositions.push(oceanV.x, oceanV.y, oceanV.z); 204 + 205 + if (j === 0) { 206 + oceanD.copy(oceanV); 207 + } else if (j === 1) { 208 + oceanE.copy(oceanV); 209 + } else if (j === 2) { 210 + oceanF.copy(oceanV); 211 + } 212 + 213 + oceanV.normalize().multiplyScalar(move.seaHeight); 144 214 oceanVertices.setXYZ(i + j, oceanV.x, oceanV.y, oceanV.z); 145 215 } 146 216 147 217 normalizedHeight /= 3; 148 - normalizedHeight = (normalizedHeight - biome.min) / (biome.max - biome.min); 149 - normalizedHeight = normalizedHeight * 2 - 1; 150 - // now averageHeight is between -1 and 1 (0 is sea level) 151 218 152 - for ( 153 - let j = 0; 154 - biome.options.vegetation && j < biome.options.vegetation.items.length; 155 - j++ 156 - ) { 157 - const vegetation = biome.options.vegetation.items[j]; 158 - if (Math.random() < faceSize * vegetation.density) { 159 - if ( 160 - vegetation.minimumHeight !== undefined && 161 - normalizedHeight < vegetation.minimumHeight 162 - ) { 163 - continue; 164 - } 219 + normalizedHeight = 220 + Math.min(-normalizedHeight / biome.min, 0) + 221 + Math.max(normalizedHeight / biome.max, 0); 222 + // now normalizedHeight should be between -1 and 1 (0 is sea level) 165 223 166 - if (vegetation.minimumHeight === undefined && normalizedHeight < 0) { 167 - continue; 168 - } 169 - 170 - if ( 171 - vegetation.maximumHeight !== undefined && 172 - normalizedHeight > vegetation.maximumHeight 173 - ) { 174 - continue; 175 - } 176 - if (!placedVegetation[vegetation.name]) { 177 - placedVegetation[vegetation.name] = []; 178 - } 179 - placedVegetation[vegetation.name].push(a.clone()); 180 - break; 181 - } 182 - } 224 + normHeightMax = Math.max(normHeightMax, normalizedHeight); 225 + normHeightMin = Math.min(normHeightMin, normalizedHeight); 183 226 184 227 // calculate new normal 185 - const normal = new Vector3(); 186 - normal.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize(); 228 + temp.crossVectors(b.clone().sub(a), c.clone().sub(a)).normalize(); 187 229 188 230 // flat shading, so all normals for the face are the same 189 - normals.setXYZ(i, normal.x, normal.y, normal.z); 190 - normals.setXYZ(i + 1, normal.x, normal.y, normal.z); 191 - normals.setXYZ(i + 2, normal.x, normal.y, normal.z); 231 + normals.setXYZ(i, temp.x, temp.y, temp.z); 232 + normals.setXYZ(i + 1, temp.x, temp.y, temp.z); 233 + normals.setXYZ(i + 2, temp.x, temp.y, temp.z); 192 234 193 235 // calculate steepness (acos of dot product of normal and up vector) 194 236 // (up vector = old mid point on sphere) 195 - const steepness = Math.acos(Math.abs(normal.dot(mid))); 237 + const steepness = Math.acos(Math.abs(temp.dot(mid))); 196 238 // steepness is between 0 and PI/2 197 239 198 240 const color = biome.getColor(mid, normalizedHeight, steepness); ··· 212 254 colors[i * 3 + 8] = color.b; 213 255 } 214 256 257 + // place vegetation 258 + for ( 259 + let j = 0; 260 + biome.options.vegetation && j < biome.options.vegetation.items.length; 261 + j++ 262 + ) { 263 + const vegetation = biome.options.vegetation.items[j]; 264 + if (Math.random() < faceSize * vegetation.density) { 265 + // discard if point is below or above height limits 266 + if ( 267 + vegetation.minimumHeight !== undefined && 268 + normalizedHeight < vegetation.minimumHeight 269 + ) { 270 + continue; 271 + } 272 + // default minimumHeight is 0 (= above sea level) 273 + if (vegetation.minimumHeight === undefined && normalizedHeight < 0) { 274 + continue; 275 + } 276 + if ( 277 + vegetation.maximumHeight !== undefined && 278 + normalizedHeight > vegetation.maximumHeight 279 + ) { 280 + continue; 281 + } 282 + 283 + // discard if point is below or above slope limits 284 + if ( 285 + vegetation.minimumSlope !== undefined && 286 + steepness < vegetation.minimumSlope 287 + ) { 288 + continue; 289 + } 290 + if ( 291 + vegetation.maximumSlope !== undefined && 292 + steepness > vegetation.maximumSlope 293 + ) { 294 + continue; 295 + } 296 + 297 + if (!placedVegetation[vegetation.name]) { 298 + placedVegetation[vegetation.name] = []; 299 + } 300 + let height = a.length(); 301 + placedVegetation[vegetation.name].push( 302 + a 303 + .clone() 304 + .normalize() 305 + .multiplyScalar(height + 0.005), 306 + ); 307 + 308 + biome.addVegetation( 309 + vegetation, 310 + a.normalize(), 311 + normalizedHeight, 312 + steepness, 313 + ); 314 + break; 315 + } 316 + } 317 + 215 318 // calculate ocean vertices 216 319 const oceanColor = biome.getSeaColor(mid, normalizedHeight); 217 320 ··· 229 332 oceanColors[i * 3 + 8] = oceanColor.b; 230 333 } 231 334 232 - oceanNormals.setXYZ(i, mid.x, mid.y, mid.z); 233 - oceanNormals.setXYZ(i + 1, mid.x, mid.y, mid.z); 234 - oceanNormals.setXYZ(i + 2, mid.x, mid.y, mid.z); 335 + // calculate ocean normals 336 + temp 337 + .crossVectors(oceanB.clone().sub(oceanA), oceanC.clone().sub(oceanA)) 338 + .normalize(); 339 + 340 + oceanNormals.setXYZ(i, temp.x, temp.y, temp.z); 341 + oceanNormals.setXYZ(i + 1, temp.x, temp.y, temp.z); 342 + oceanNormals.setXYZ(i + 2, temp.x, temp.y, temp.z); 343 + 344 + temp 345 + .crossVectors(oceanE.clone().sub(oceanD), oceanF.clone().sub(oceanD)) 346 + .normalize(); 347 + 348 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 349 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 350 + oceanMorphNormals.push(temp.x, temp.y, temp.z); 235 351 } 352 + 353 + console.log("normHeightMax", normHeightMax); 354 + console.log("normHeightMin", normHeightMin); 355 + 356 + const maxDist = 0.14; 357 + // go through all vertices again and update height and color based on vegetation 358 + for (let i = 0; i < vertices.count; i += 3) { 359 + let found = false; 360 + let closestDistAll = 1; 361 + for (let j = 0; j < 3; j++) { 362 + a.fromBufferAttribute(vertices, i + j); 363 + a.normalize(); 364 + 365 + let p = biome.itemsAround(a, maxDist); 366 + if (p.length > 0) { 367 + // find closest point 368 + let closest = p[0]; 369 + let closestDist = a.distanceTo(closest); 370 + for (let k = 1; k < p.length; k++) { 371 + let dist = a.distanceTo(p[k]); 372 + if (dist < closestDist) { 373 + closest = p[k]; 374 + closestDist = dist; 375 + } 376 + } 377 + 378 + let moveInfo = calculatedVerticesArray[i + j]; 379 + 380 + a.multiplyScalar( 381 + moveInfo.height + ((maxDist - closestDist) / maxDist) * 0.015, 382 + ); 383 + 384 + vertices.setXYZ(i + j, a.x, a.y, a.z); 385 + 386 + closestDistAll = Math.min(closestDist, closestDistAll); 387 + found = true; 388 + } 389 + } 390 + 391 + if (found) { 392 + let existingColor = new Color( 393 + colors[i * 3], 394 + colors[i * 3 + 1], 395 + colors[i * 3 + 2], 396 + ); 397 + 398 + // set color 399 + let newColor = new Color(0.1, 0.3, 0); 400 + 401 + newColor.lerp(existingColor, closestDistAll / maxDist); 402 + 403 + for (let j = 0; j < 3; j++) { 404 + colors[(i + j) * 3] = newColor.r; 405 + colors[(i + j) * 3 + 1] = newColor.g; 406 + colors[(i + j) * 3 + 2] = newColor.b; 407 + } 408 + } 409 + } 410 + 411 + oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute( 412 + oceanMorphPositions, 413 + 3, 414 + ); 415 + oceanSphere.morphAttributes.normal[0] = new Float32BufferAttribute( 416 + oceanMorphNormals, 417 + 3, 418 + ); 236 419 237 420 sphere.setAttribute("color", new BufferAttribute(colors, 3)); 238 421 oceanSphere.setAttribute("color", new BufferAttribute(oceanColors, 3));
+291
src/worlds/helper/octree.ts
··· 1 + import { 2 + Vector3, 3 + Box3, 4 + Material, 5 + Object3D, 6 + Mesh, 7 + BoxGeometry, 8 + MeshStandardMaterial, 9 + BufferAttribute, 10 + BufferGeometry, 11 + Points, 12 + PointsMaterial, 13 + } from "three"; 14 + 15 + export type OctreeOptions = { 16 + bounds?: Box3; 17 + 18 + size?: number; 19 + 20 + min?: Vector3; 21 + max?: Vector3; 22 + 23 + points?: Vector3[]; 24 + 25 + capacity?: number; 26 + }; 27 + 28 + export type Vector3Data = Vector3 & { data?: unknown }; 29 + 30 + export class Octree { 31 + boundary: Box3; 32 + 33 + points: Vector3[]; 34 + 35 + capacity: number; 36 + 37 + subdivisions: Octree[] | undefined = undefined; 38 + 39 + constructor(opts: OctreeOptions = {}) { 40 + opts ??= {}; 41 + 42 + this.points = []; 43 + 44 + if (opts.bounds) { 45 + this.boundary = opts.bounds.clone(); 46 + } else if (opts.size) { 47 + const s = opts.size; 48 + this.boundary = new Box3(new Vector3(-s, -s, -s), new Vector3(s, s, s)); 49 + } else if (opts.min || opts.max) { 50 + const min = opts.min || new Vector3(-1, -1, -1); 51 + const max = opts.max || new Vector3(1, 1, 1); 52 + this.boundary = new Box3(min, max); 53 + } else if (opts.points && opts.points.length > 0) { 54 + const min = opts.points[0].clone(); 55 + const max = opts.points[0].clone(); 56 + for (const p of opts.points) { 57 + min.x = Math.min(min.x, p.x); 58 + min.y = Math.min(min.y, p.y); 59 + min.z = Math.min(min.z, p.z); 60 + 61 + max.x = Math.max(max.x, p.x); 62 + max.y = Math.max(max.y, p.y); 63 + max.z = Math.max(max.z, p.z); 64 + } 65 + this.boundary = new Box3(min, max); 66 + } else { 67 + this.boundary = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)); 68 + } 69 + 70 + this.capacity = opts.capacity || 4; 71 + 72 + if (opts.points) { 73 + for (const p of opts.points) { 74 + this.insertXYZ(p.x, p.y, p.z); 75 + } 76 + } 77 + } 78 + 79 + subdivide() { 80 + // if already subdivided exit silently 81 + if (this.subdivisions != undefined) return; 82 + 83 + // divide each dimension => 2 * 2 * 2 = 8 subdivisions 84 + const size = new Vector3(); 85 + const subdivisions: Octree[] = []; 86 + for (let x = 0; x < 2; x++) { 87 + for (let y = 0; y < 2; y++) { 88 + for (let z = 0; z < 2; z++) { 89 + const min = this.boundary.min.clone(); 90 + const max = this.boundary.max.clone(); 91 + this.boundary.getSize(size); 92 + size.divideScalar(2); 93 + 94 + min.x += x * size.x; 95 + min.y += y * size.y; 96 + min.z += z * size.z; 97 + max.x -= (1 - x) * size.x; 98 + max.y -= (1 - y) * size.y; 99 + max.z -= (1 - z) * size.z; 100 + 101 + subdivisions.push( 102 + new Octree({ 103 + min: min, 104 + max: max, 105 + capacity: this.capacity, 106 + }), 107 + ); 108 + } 109 + } 110 + } 111 + this.subdivisions = subdivisions; 112 + } 113 + 114 + // returns array of points where 115 + // distance between pos and point is less than dist 116 + query(pos: Vector3Data, dist = 1) { 117 + const points = this.queryXYZ(pos.x, pos.y, pos.z, dist); 118 + for (let i = points.length - 1; i >= 0; i--) { 119 + if (points[i].distanceTo(pos) > dist) points.splice(i, 1); 120 + } 121 + return points; 122 + } 123 + 124 + // vector3 free version, returns points in box around xyz 125 + queryXYZ(x: number, y: number, z: number, s: number) { 126 + const min = new Vector3(x - s, y - s, z - s), 127 + max = new Vector3(x + s, y + s, z + s); 128 + const box = new Box3(min, max); 129 + 130 + return this.queryBox(box); 131 + } 132 + 133 + queryBox(box: Box3, found: Vector3Data[] = []) { 134 + found ??= []; 135 + 136 + if (!box.intersectsBox(this.boundary)) return found; 137 + 138 + for (const p of this.points) { 139 + if (box.containsPoint(p)) found.push(p); 140 + } 141 + if (this.subdivisions) { 142 + for (const sub of this.subdivisions) { 143 + sub.queryBox(box, found); 144 + } 145 + } 146 + return found; 147 + } 148 + 149 + // returns true if no points are closer than dist to point 150 + minDist(pos: Vector3, dist: number) { 151 + return this.query(pos, dist).length < 1; 152 + } 153 + 154 + // insert point with optional data (sets vec.data = data) 155 + insert(pos: Vector3Data, data: unknown = undefined) { 156 + return this.insertPoint(pos, data); 157 + } 158 + 159 + // vector3 free version 160 + insertXYZ(x: number, y: number, z: number, data: unknown = undefined) { 161 + return this.insertPoint(new Vector3(x, y, z), data); 162 + } 163 + 164 + insertPoint(p: Vector3, data: unknown = undefined) { 165 + p = p.clone(); 166 + 167 + // @ts-expect-error - data is not a property of Vector3 168 + if (data) p.data = data; 169 + 170 + if (!this.boundary.containsPoint(p)) return false; 171 + 172 + if (this.points.length < this.capacity) { 173 + this.points.push(p); 174 + return true; 175 + } else { 176 + this.subdivide(); 177 + let added = false; 178 + for (const sub of this.subdivisions ?? []) { 179 + if (sub.insertPoint(p, data)) added = true; 180 + } 181 + return added; 182 + } 183 + } 184 + 185 + showBoxes(mat: Material, parent: Object3D | undefined = undefined) { 186 + const size = new Vector3(); 187 + this.boundary.getSize(size); 188 + 189 + const box = new BoxGeometry(size.x * 2, size.y * 2, size.z * 2); 190 + const mesh = new Mesh( 191 + box, 192 + mat || 193 + new MeshStandardMaterial({ 194 + wireframe: true, 195 + }), 196 + ); 197 + this.boundary.getCenter(mesh.position); 198 + 199 + parent ??= new Object3D(); 200 + parent.add(mesh); 201 + 202 + if (this.subdivisions) { 203 + for (const sub of this.subdivisions) sub.showBoxes(mat, parent); 204 + } 205 + return parent; 206 + } 207 + 208 + show( 209 + opts: { 210 + pointsOnly?: boolean; 211 + mat?: Material; 212 + size?: number; 213 + sizeAttenuation?: boolean; 214 + p?: Vector3; 215 + min?: number; 216 + } = {}, 217 + ) { 218 + opts ??= {}; 219 + 220 + const pointsOnly = opts.pointsOnly; 221 + let mat = opts.mat; 222 + const points = this.all(); 223 + 224 + const pointsGeo = new BufferGeometry(); 225 + const positionData = new Float32Array(points.length * 3); 226 + const colorData = new Float32Array(points.length * 3); 227 + 228 + let q; 229 + 230 + if (opts.p && opts.min) { 231 + for (const point of points) { 232 + // @ts-expect-error - close is not a property of Vector3 233 + point.close = false; 234 + } 235 + q = this.query(opts.p, opts.min); 236 + 237 + for (const point of q) { 238 + // @ts-expect-error - close is not a property of Vector3 239 + point.close = true; 240 + } 241 + } 242 + 243 + for (let i = 0; i < points.length; i++) { 244 + positionData[i * 3] = points[i].x; 245 + positionData[i * 3 + 1] = points[i].y; 246 + positionData[i * 3 + 2] = points[i].z; 247 + 248 + // @ts-expect-error - close is not a property of Vector3 249 + colorData[i * 3] = points[i].close ? 1 : 0.7; 250 + 251 + // @ts-expect-error - close is not a property of Vector3 252 + colorData[i * 3 + 1] = points[i].close ? 0 : 0.7; 253 + 254 + // @ts-expect-error - close is not a property of Vector3 255 + colorData[i * 3 + 2] = points[i].close ? 0 : 0.7; 256 + } 257 + pointsGeo.setAttribute("position", new BufferAttribute(positionData, 3)); 258 + pointsGeo.setAttribute("color", new BufferAttribute(colorData, 3)); 259 + const pointMesh = new Points( 260 + pointsGeo, 261 + new PointsMaterial({ 262 + size: opts.size || 1, 263 + sizeAttenuation: opts.sizeAttenuation || false, 264 + vertexColors: true, 265 + }), 266 + ); 267 + if (pointsOnly) return pointMesh; 268 + 269 + mat = 270 + mat || 271 + new MeshStandardMaterial({ 272 + transparent: true, 273 + opacity: 0.01, 274 + depthTest: false, 275 + }); 276 + const boxes = this.showBoxes(mat); 277 + boxes.add(pointMesh); 278 + return boxes; 279 + } 280 + 281 + all(arr: Vector3Data[] = []) { 282 + arr ??= []; 283 + for (const p of this.points) { 284 + arr.push(p); 285 + } 286 + if (this.subdivisions) { 287 + for (const subs of this.subdivisions) subs.all(arr); 288 + } 289 + return arr; 290 + } 291 + }
-275
src/worlds/helper/octtree.ts
··· 1 - import * as THREE from 'three'; 2 - 3 - export type OcttreeOptions = { 4 - bounds?: THREE.Box3; 5 - 6 - size?: number; 7 - 8 - min?: THREE.Vector3; 9 - max?: THREE.Vector3; 10 - 11 - points?: THREE.Vector3[]; 12 - 13 - capacity?: number; 14 - }; 15 - 16 - export class Octtree { 17 - boundary: THREE.Box3; 18 - 19 - points: THREE.Vector3[]; 20 - 21 - capacity: number; 22 - 23 - subdivisions: Octtree[] | undefined = undefined; 24 - 25 - constructor(opts: OcttreeOptions = {}) { 26 - opts ??= {}; 27 - 28 - this.points = []; 29 - 30 - if (opts.bounds) { 31 - this.boundary = opts.bounds.clone(); 32 - } else if (opts.size) { 33 - const s = opts.size; 34 - this.boundary = new THREE.Box3(new THREE.Vector3(-s, -s, -s), new THREE.Vector3(s, s, s)); 35 - } else if (opts.min || opts.max) { 36 - const min = opts.min || new THREE.Vector3(-1, -1, -1); 37 - const max = opts.max || new THREE.Vector3(1, 1, 1); 38 - this.boundary = new THREE.Box3(min, max); 39 - } else if (opts.points && opts.points.length > 0) { 40 - const min = opts.points[0].clone(); 41 - const max = opts.points[0].clone(); 42 - for (const p of opts.points) { 43 - min.x = Math.min(min.x, p.x); 44 - min.y = Math.min(min.y, p.y); 45 - min.z = Math.min(min.z, p.z); 46 - 47 - max.x = Math.max(max.x, p.x); 48 - max.y = Math.max(max.y, p.y); 49 - max.z = Math.max(max.z, p.z); 50 - } 51 - this.boundary = new THREE.Box3(min, max); 52 - } else { 53 - this.boundary = new THREE.Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1)); 54 - } 55 - 56 - this.capacity = opts.capacity || 4; 57 - 58 - if (opts.points) { 59 - for (const p of opts.points) { 60 - this.insertXYZ(p.x, p.y, p.z); 61 - } 62 - } 63 - } 64 - 65 - subdivide() { 66 - // if already subdivided exit silently 67 - if (this.subdivisions != undefined) return; 68 - 69 - // divide each dimension => 2 * 2 * 2 = 8 subdivisions 70 - const size = new THREE.Vector3(); 71 - const subdivisions: Octtree[] = []; 72 - for (let x = 0; x < 2; x++) { 73 - for (let y = 0; y < 2; y++) { 74 - for (let z = 0; z < 2; z++) { 75 - const min = this.boundary.min.clone(); 76 - const max = this.boundary.max.clone(); 77 - this.boundary.getSize(size); 78 - size.divideScalar(2); 79 - 80 - min.x += x * size.x; 81 - min.y += y * size.y; 82 - min.z += z * size.z; 83 - max.x -= (1 - x) * size.x; 84 - max.y -= (1 - y) * size.y; 85 - max.z -= (1 - z) * size.z; 86 - 87 - subdivisions.push( 88 - new Octtree({ 89 - min: min, 90 - max: max, 91 - capacity: this.capacity 92 - }) 93 - ); 94 - } 95 - } 96 - } 97 - this.subdivisions = subdivisions; 98 - } 99 - 100 - // returns array of points where 101 - // distance between pos and point is less than dist 102 - query(pos: THREE.Vector3, dist = 1) { 103 - const points = this.queryXYZ(pos.x, pos.y, pos.z, dist); 104 - for (let i = points.length - 1; i >= 0; i--) { 105 - if (points[i].distanceTo(pos) > dist) points.splice(i, 1); 106 - } 107 - return points; 108 - } 109 - 110 - // vector3 free version, returns points in box around xyz 111 - queryXYZ(x: number, y: number, z: number, s: number) { 112 - const min = new THREE.Vector3(x - s, y - s, z - s), 113 - max = new THREE.Vector3(x + s, y + s, z + s); 114 - const box = new THREE.Box3(min, max); 115 - 116 - return this.queryBox(box); 117 - } 118 - 119 - queryBox(box: THREE.Box3, found: THREE.Vector3[] = []) { 120 - found ??= []; 121 - 122 - if (!box.intersectsBox(this.boundary)) return found; 123 - 124 - for (const p of this.points) { 125 - if (box.containsPoint(p)) found.push(p); 126 - } 127 - if (this.subdivisions) { 128 - for (const sub of this.subdivisions) { 129 - sub.queryBox(box, found); 130 - } 131 - } 132 - return found; 133 - } 134 - 135 - // returns true if no points are closer than dist to point 136 - minDist(pos: THREE.Vector3, dist: number) { 137 - return this.query(pos, dist).length < 1; 138 - } 139 - 140 - // insert point with optional data (sets vec.data = data) 141 - insert(pos: THREE.Vector3, data: unknown = undefined) { 142 - return this.insertPoint(pos, data); 143 - } 144 - // vector3 free version 145 - insertXYZ(x: number, y: number, z: number, data: unknown = undefined) { 146 - return this.insertPoint(new THREE.Vector3(x, y, z), data); 147 - } 148 - insertPoint(p: THREE.Vector3, data: unknown = undefined) { 149 - p = p.clone(); 150 - 151 - // @ts-expect-error - data is not a property of Vector3 152 - if (data) p.data = data; 153 - 154 - if (!this.boundary.containsPoint(p)) return false; 155 - 156 - if (this.points.length < this.capacity) { 157 - this.points.push(p); 158 - return true; 159 - } else { 160 - this.subdivide(); 161 - let added = false; 162 - for (const sub of this.subdivisions ?? []) { 163 - if (sub.insertPoint(p, data)) added = true; 164 - } 165 - return added; 166 - } 167 - } 168 - 169 - showBoxes(mat: THREE.Material, parent: THREE.Object3D | undefined = undefined) { 170 - const size = new THREE.Vector3(); 171 - this.boundary.getSize(size); 172 - 173 - const box = new THREE.BoxGeometry(size.x * 2, size.y * 2, size.z * 2); 174 - const mesh = new THREE.Mesh( 175 - box, 176 - mat || 177 - new THREE.MeshStandardMaterial({ 178 - wireframe: true 179 - }) 180 - ); 181 - this.boundary.getCenter(mesh.position); 182 - 183 - parent ??= new THREE.Object3D(); 184 - parent.add(mesh); 185 - 186 - if (this.subdivisions) { 187 - for (const sub of this.subdivisions) sub.showBoxes(mat, parent); 188 - } 189 - return parent; 190 - } 191 - 192 - show( 193 - opts: { 194 - pointsOnly?: boolean; 195 - mat?: THREE.Material; 196 - size?: number; 197 - sizeAttenuation?: boolean; 198 - p?: THREE.Vector3; 199 - min?: number; 200 - } = {} 201 - ) { 202 - opts ??= {}; 203 - 204 - const pointsOnly = opts.pointsOnly; 205 - let mat = opts.mat; 206 - const points = this.all(); 207 - 208 - const pointsGeo = new THREE.BufferGeometry(); 209 - const positionData = new Float32Array(points.length * 3); 210 - const colorData = new Float32Array(points.length * 3); 211 - 212 - let q; 213 - 214 - if (opts.p && opts.min) { 215 - for (const point of points) { 216 - // @ts-expect-error - close is not a property of Vector3 217 - point.close = false; 218 - } 219 - q = this.query(opts.p, opts.min); 220 - 221 - for (const point of q) { 222 - // @ts-expect-error - close is not a property of Vector3 223 - point.close = true; 224 - } 225 - } 226 - 227 - for (let i = 0; i < points.length; i++) { 228 - positionData[i * 3] = points[i].x; 229 - positionData[i * 3 + 1] = points[i].y; 230 - positionData[i * 3 + 2] = points[i].z; 231 - 232 - // @ts-expect-error - close is not a property of Vector3 233 - colorData[i * 3] = points[i].close ? 1 : 0.7; 234 - 235 - // @ts-expect-error - close is not a property of Vector3 236 - colorData[i * 3 + 1] = points[i].close ? 0 : 0.7; 237 - 238 - // @ts-expect-error - close is not a property of Vector3 239 - colorData[i * 3 + 2] = points[i].close ? 0 : 0.7; 240 - } 241 - pointsGeo.setAttribute('position', new THREE.BufferAttribute(positionData, 3)); 242 - pointsGeo.setAttribute('color', new THREE.BufferAttribute(colorData, 3)); 243 - const pointMesh = new THREE.Points( 244 - pointsGeo, 245 - new THREE.PointsMaterial({ 246 - size: opts.size || 1, 247 - sizeAttenuation: opts.sizeAttenuation || false, 248 - vertexColors: true 249 - }) 250 - ); 251 - if (pointsOnly) return pointMesh; 252 - 253 - mat = 254 - mat || 255 - new THREE.MeshStandardMaterial({ 256 - transparent: true, 257 - opacity: 0.01, 258 - depthTest: false 259 - }); 260 - const boxes = this.showBoxes(mat); 261 - boxes.add(pointMesh); 262 - return boxes; 263 - } 264 - 265 - all(arr: THREE.Vector3[] = []) { 266 - arr ??= []; 267 - for (const p of this.points) { 268 - arr.push(p); 269 - } 270 - if (this.subdivisions) { 271 - for (const subs of this.subdivisions) subs.all(arr); 272 - } 273 - return arr; 274 - } 275 - }
+77
src/worlds/materials/AtmosphereMaterial.ts
··· 1 + import { ShaderMaterial, Vector3 } from "three"; 2 + 3 + export function createAtmosphereMaterial( 4 + color: Vector3 | undefined = undefined, 5 + lightDirection: Vector3 | undefined = undefined, 6 + ) { 7 + const uniforms = { 8 + lightDirection: { 9 + value: (lightDirection ?? new Vector3(1.0, 1.0, 0.0)).normalize(), 10 + }, 11 + atmosphereColor: { value: color ?? new Vector3(0.3, 0.6, 1.0) }, 12 + }; 13 + 14 + const atmosphereShader = { 15 + uniforms, 16 + vertexShader: ` 17 + varying vec3 vNormal; 18 + varying vec3 vViewLightDirection; // Light direction relative to the camera 19 + varying vec3 vViewPosition; // View position 20 + 21 + uniform vec3 lightDirection; // Light direction in world space 22 + 23 + void main() { 24 + // Transform the vertex normal to world space 25 + vNormal = normalize(normalMatrix * normal); 26 + // Transform the light direction to view space 27 + vViewLightDirection = (viewMatrix * vec4(lightDirection, 0.0)).xyz; 28 + vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; 29 + 30 + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 31 + } 32 + `, 33 + fragmentShader: ` 34 + varying vec3 vNormal; 35 + varying vec3 vViewLightDirection; 36 + varying vec3 vViewPosition; 37 + uniform vec3 atmosphereColor; 38 + 39 + void main() { 40 + 41 + // Normalize the normal and the view direction 42 + vec3 viewDirection = normalize(vViewPosition); 43 + 44 + // Calculate how much of the surface is perpendicular to the view direction 45 + float viewFactor = dot(normalize(vNormal), -viewDirection); 46 + 47 + // Calculate the dot product of the light direction and the surface normal 48 + float lightFactor = dot(normalize(vNormal), normalize(vViewLightDirection)); 49 + 50 + // Use smoothstep to soften the transition from light to dark side 51 + lightFactor = smoothstep(-0.2, 0.4, lightFactor); 52 + 53 + // Ensure a minimum glow, even on the dark side 54 + float minGlow = 0.2; // Adjust this to control how much it glows on the dark side 55 + lightFactor = mix(minGlow, 1.0, lightFactor); 56 + 57 + // Adjust the intensity for the atmosphere's glow, including the minimum glow 58 + float dotProduct = dot(normalize(vNormal), vec3(0, 0.0, 1.0)); 59 + float intensity = pow(dotProduct, 8.0); 60 + intensity *= lightFactor; 61 + 62 + viewFactor = clamp(1.0 - viewFactor, 0.0, 1.0); 63 + 64 + // Use smoothstep for a smooth transition on the edges 65 + float atmosphereFactor = smoothstep(0.2, 0.6, viewFactor); 66 + 67 + // Output the final color 68 + gl_FragColor = vec4(atmosphereColor, intensity * atmosphereFactor); 69 + // Set the final fragment color with the computed intensity 70 + //gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity; 71 + }`, 72 + transparent: true, 73 + depthWrite: false, 74 + }; 75 + 76 + return new ShaderMaterial(atmosphereShader); 77 + }
+64
src/worlds/materials/OceanCausticsMaterial.ts
··· 1 + import { MeshStandardMaterial } from "three"; 2 + import { noise } from "./noise"; 3 + 4 + const oceansCausticMaterial = new MeshStandardMaterial({ 5 + vertexColors: true, 6 + }); 7 + oceansCausticMaterial.onBeforeCompile = (shader) => { 8 + const caustics = ` 9 + float caustics(vec4 vPos) { 10 + // More intricate warping for marble patterns 11 + // float warpFactor = 2.0; 12 + // vec4 warpedPos = vPos * warpFactor + snoise(vPos * warpFactor * 0.5); 13 + // vec4 warpedPos2 = warpedPos * warpFactor * 0.3 + snoise(warpedPos * warpFactor * 0.5 + vec4(0, 2, 4, 8)) + vPos; 14 + 15 + // // Modulate the color intensity based on the noise 16 + // float vein = snoise(warpedPos2 * warpFactor) * snoise(warpedPos); 17 + 18 + // float a = 1.0 - (sin(vein * 12.0) + 1.0) * 0.5; 19 + // float diff = snoise(vPos * warpFactor); 20 + // diff = diff * snoise(diff * vPos) * a; 21 + // return vec3((diff)); 22 + 23 + vec4 warpedPos = vPos * 2.0 + snoise(vPos * 3.0); 24 + vec4 warpedPos2 = warpedPos * 0.3 + snoise(warpedPos * 2.0 + vec4(0, 2, 4, 8)) + vPos; 25 + float vein = snoise(warpedPos2) * snoise(warpedPos); 26 + float a = 1.0 - (sin(vein * 2.0) + 1.0) * 0.5; 27 + 28 + return snoise(vPos + warpedPos + warpedPos2) * a * 1.5; 29 + }`; 30 + shader.vertexShader = `varying vec3 vPos;\n${shader.vertexShader}`.replace( 31 + `#include <begin_vertex>`, 32 + `#include <begin_vertex>\nvPos = position;`, 33 + ); 34 + 35 + shader.fragmentShader = ` 36 + uniform float time; 37 + varying vec3 vPos; 38 + ${noise} 39 + ${caustics} 40 + ${shader.fragmentShader}`; 41 + 42 + shader.fragmentShader = shader.fragmentShader.replace( 43 + "#include <color_fragment>", 44 + `#include <color_fragment> 45 + vec3 pos = vPos * 3.0; 46 + float len = length(vPos); 47 + // Fade in 48 + float fadeIn = smoothstep(0.96, 0.985, len); 49 + // Fade out 50 + float fadeOut = 1.0 - smoothstep(0.994, 0.999, len); 51 + float causticIntensity = fadeIn * fadeOut * 0.7; 52 + diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), causticIntensity * smoothstep(0.0, 1.0, caustics(vec4(pos, time * 0.05)))); 53 + `, 54 + ); 55 + 56 + shader.uniforms.time = { value: 0 }; 57 + oceansCausticMaterial.userData.shader = shader; 58 + 59 + // console.log("FRAGMENT", shader.fragmentShader); 60 + // console.log(); 61 + // console.log("VERTEX", shader.vertexShader); 62 + }; 63 + 64 + export default oceansCausticMaterial;
+122
src/worlds/materials/noise.ts
··· 1 + export const noise = /* GLSL */ ` 2 + // Description : Array and textureless GLSL 2D/3D/4D simplex 3 + // noise functions. 4 + // Author : Ian McEwan, Ashima Arts. 5 + // Maintainer : stegu 6 + // Lastmod : 20110822 (ijm) 7 + // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 8 + // Distributed under the MIT License. See LICENSE file. 9 + // https://github.com/ashima/webgl-noise 10 + // https://github.com/stegu/webgl-noise 11 + // 12 + 13 + vec4 mod289(vec4 x) { 14 + return x - floor(x * (1.0 / 289.0)) * 289.0; 15 + } 16 + 17 + float mod289(float x) { 18 + return x - floor(x * (1.0 / 289.0)) * 289.0; 19 + } 20 + 21 + vec4 permute(vec4 x) { 22 + return mod289(((x * 34.0) + 10.0) * x); 23 + } 24 + 25 + float permute(float x) { 26 + return mod289(((x * 34.0) + 10.0) * x); 27 + } 28 + 29 + vec4 taylorInvSqrt(vec4 r) { 30 + return 1.79284291400159 - 0.85373472095314 * r; 31 + } 32 + 33 + float taylorInvSqrt(float r) { 34 + return 1.79284291400159 - 0.85373472095314 * r; 35 + } 36 + 37 + vec4 grad4(float j, vec4 ip) { 38 + const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); 39 + vec4 p, s; 40 + 41 + p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 42 + p.w = 1.5 - dot(abs(p.xyz), ones.xyz); 43 + s = vec4(lessThan(p, vec4(0.0))); 44 + p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; 45 + 46 + return p; 47 + } 48 + 49 + // (sqrt(5) - 1)/4 = F4, used once below 50 + #define F4 0.309016994374947451 51 + 52 + float snoise(vec4 v) { 53 + const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 54 + 0.276393202250021, // 2 * G4 55 + 0.414589803375032, // 3 * G4 56 + -0.447213595499958); // -1 + 4 * G4 57 + 58 + // First corner 59 + vec4 i = floor(v + dot(v, vec4(F4))); 60 + vec4 x0 = v - i + dot(i, C.xxxx); 61 + 62 + // Other corners 63 + 64 + // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) 65 + vec4 i0; 66 + vec3 isX = step(x0.yzw, x0.xxx); 67 + vec3 isYZ = step(x0.zww, x0.yyz); 68 + // i0.x = dot( isX, vec3( 1.0 ) ); 69 + i0.x = isX.x + isX.y + isX.z; 70 + i0.yzw = 1.0 - isX; 71 + // i0.y += dot( isYZ.xy, vec2( 1.0 ) ); 72 + i0.y += isYZ.x + isYZ.y; 73 + i0.zw += 1.0 - isYZ.xy; 74 + i0.z += isYZ.z; 75 + i0.w += 1.0 - isYZ.z; 76 + 77 + // i0 now contains the unique values 0,1,2,3 in each channel 78 + vec4 i3 = clamp(i0, 0.0, 1.0); 79 + vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); 80 + vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); 81 + 82 + // x0 = x0 - 0.0 + 0.0 * C.xxxx 83 + // x1 = x0 - i1 + 1.0 * C.xxxx 84 + // x2 = x0 - i2 + 2.0 * C.xxxx 85 + // x3 = x0 - i3 + 3.0 * C.xxxx 86 + // x4 = x0 - 1.0 + 4.0 * C.xxxx 87 + vec4 x1 = x0 - i1 + C.xxxx; 88 + vec4 x2 = x0 - i2 + C.yyyy; 89 + vec4 x3 = x0 - i3 + C.zzzz; 90 + vec4 x4 = x0 + C.wwww; 91 + 92 + // Permutations 93 + i = mod289(i); 94 + float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); 95 + vec4 j1 = permute(permute(permute(permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x + vec4(i1.x, i2.x, i3.x, 1.0)); 96 + 97 + // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope 98 + // 7*7*6 = 294, which is close to the ring size 17*17 = 289. 99 + vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); 100 + 101 + vec4 p0 = grad4(j0, ip); 102 + vec4 p1 = grad4(j1.x, ip); 103 + vec4 p2 = grad4(j1.y, ip); 104 + vec4 p3 = grad4(j1.z, ip); 105 + vec4 p4 = grad4(j1.w, ip); 106 + 107 + // Normalise gradients 108 + vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); 109 + p0 *= norm.x; 110 + p1 *= norm.y; 111 + p2 *= norm.z; 112 + p3 *= norm.w; 113 + p4 *= taylorInvSqrt(dot(p4, p4)); 114 + 115 + // Mix contributions from the five corners 116 + vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); 117 + vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); 118 + m0 = m0 * m0; 119 + m1 = m1 * m1; 120 + 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)))); 121 + 122 + }`;