[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.

add plane version

Florian (Oct 6, 2024, 5:39 PM +0200) 555180d1 2800468d

+157 -61
+7 -1
src/script.ts
··· 61 61 62 62 let total = 0; 63 63 let lastDelta = 0; 64 + 65 + let rotate = true; 64 66 renderer.setAnimationLoop((delta) => { 65 67 renderer.render(scene, camera); 66 68 67 - planetMesh.rotation.y += 0.001; 69 + if (rotate) planetMesh.rotation.y += 0.001; 68 70 69 71 if (lastDelta > 0) { 70 72 total += delta - lastDelta; ··· 90 92 createPlanet("forest"); 91 93 } else if (event.key === "3") { 92 94 createPlanet("snowForest"); 95 + } 96 + 97 + if (event.key === " ") { 98 + rotate = !rotate; 93 99 } 94 100 }); 95 101
+1 -2
src/worlds/biome.ts
··· 1 1 import { UberNoise, type NoiseOptions } from "uber-noise"; 2 - import { Color, ColorRepresentation, Vector3 } from "three"; 2 + import { Color, type ColorRepresentation, Vector3 } from "three"; 3 3 4 4 import { 5 5 ColorGradient, ··· 7 7 } from "./helper/colorgradient"; 8 8 import { biomePresets } from "./presets"; 9 9 import { Octree } from "./helper/octree"; 10 - import { type VertexInfo } from "./types"; 11 10 12 11 export type VegetationItem = { 13 12 name: string;
+6 -4
src/worlds/materials/OceanCausticsMaterial.ts
··· 5 5 import { noise } from "./noise"; 6 6 7 7 export class PlanetMaterialWithCaustics extends MeshStandardMaterial { 8 - constructor(parameters: MeshStandardMaterialParameters) { 8 + constructor( 9 + parameters: MeshStandardMaterialParameters & { shape: "sphere" | "plane" }, 10 + ) { 9 11 super(parameters); 10 12 11 13 this.onBeforeCompile = (shader) => { ··· 48 50 "#include <color_fragment>", 49 51 `#include <color_fragment> 50 52 vec3 pos = vPos * 3.0; 51 - float len = length(vPos); 53 + ${parameters.shape == "plane" ? "float len = vPos.y;" : "float len = length(vPos) - 1.0;"} 52 54 // Fade in 53 - float fadeIn = smoothstep(0.96, 0.985, len); 55 + float fadeIn = smoothstep(-0.04, -0.015, len); 54 56 // Fade out 55 - float fadeOut = 1.0 - smoothstep(0.994, 0.999, len); 57 + float fadeOut = 1.0 - smoothstep(-0.006, -0.001, len); 56 58 float causticIntensity = fadeIn * fadeOut * 0.7; 57 59 diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), causticIntensity * smoothstep(0.0, 1.0, caustics(vec4(pos, time * 0.05)))); 58 60 `,
+20 -7
src/worlds/planet.ts
··· 31 31 material?: "normal" | "caustics"; 32 32 33 33 biome?: BiomeOptions; 34 + 35 + shape?: "sphere" | "plane"; 34 36 }; 35 37 36 38 export class Planet { ··· 46 48 47 49 vegetationPositions?: Record<string, Vector3[]>; 48 50 51 + shape: "sphere" | "plane" = "sphere"; 52 + 53 + tempQuaternion = new Quaternion(); 54 + 49 55 constructor(options: PlanetOptions = {}) { 56 + this.shape = options.shape ?? this.shape; 57 + options.shape = this.shape; 58 + 50 59 this.options = options; 51 60 52 61 this.biome = new Biome(options.biome); ··· 110 119 111 120 const material = 112 121 this.options.material === "caustics" 113 - ? new PlanetMaterialWithCaustics(materialOptions) 122 + ? new PlanetMaterialWithCaustics({ 123 + ...materialOptions, 124 + shape: this.shape, 125 + }) 114 126 : new MeshStandardMaterial(materialOptions); 115 127 116 128 const planetMesh = new Mesh(geometry, material); ··· 257 269 updatePosition(item: Object3D, pos: Vector3) { 258 270 item.position.copy(pos); 259 271 260 - const currentRotation = new Quaternion(); 261 - const a = item.up.clone().normalize(); 262 - const b = pos.clone().normalize(); 272 + if (this.shape === "sphere") { 273 + const a = item.up.clone().normalize(); 274 + const b = pos.clone().normalize(); 263 275 264 - currentRotation.setFromUnitVectors(a, b); 276 + this.tempQuaternion.setFromUnitVectors(a, b); 265 277 266 - item.quaternion.copy(currentRotation); 278 + item.quaternion.copy(this.tempQuaternion); 267 279 268 - item.up = b; 280 + item.up = b; 281 + } 269 282 } 270 283 }
+1 -1
src/worlds/presets.ts
··· 1 1 import { type BiomeOptions } from "./biome"; 2 - import { PlanetOptions } from "./planet"; 2 + import { type PlanetOptions } from "./planet"; 3 3 4 4 const beachBiome: BiomeOptions = { 5 5 noise: {
+122 -46
src/worlds/worker.ts
··· 4 4 BufferAttribute, 5 5 Float32BufferAttribute, 6 6 Color, 7 + PlaneGeometry, 8 + BufferGeometry, 7 9 } from "three"; 8 10 9 - import { Biome, type VegetationItem } from "./biome"; 11 + import { Biome } from "./biome"; 10 12 import { type PlanetOptions } from "./planet"; 11 13 import UberNoise from "uber-noise"; 12 14 import { type VertexInfo } from "./types"; ··· 64 66 65 67 function createGeometry( 66 68 planetOptions: PlanetOptions, 67 - ): [IcosahedronGeometry, IcosahedronGeometry, Record<string, Vector3[]>] { 68 - const sphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 69 - const oceanSphere = new IcosahedronGeometry(1, planetOptions.detail ?? 50); 69 + ): [BufferGeometry, BufferGeometry, Record<string, Vector3[]>] { 70 + const detail = planetOptions.detail ?? 50; 71 + 72 + const mainGeometry = 73 + planetOptions.shape == "plane" 74 + ? new PlaneGeometry(3, 3, detail, detail).toNonIndexed() 75 + : new IcosahedronGeometry(1, detail); 76 + const oceanGeometry = 77 + planetOptions.shape == "plane" 78 + ? new PlaneGeometry(3, 3, detail, detail).toNonIndexed() 79 + : new IcosahedronGeometry(1, detail); 70 80 71 81 const biome = new Biome(planetOptions.biome); 72 82 73 - const vertices = sphere.getAttribute("position"); 74 - const oceanVertices = oceanSphere.getAttribute("position"); 83 + const vertices = mainGeometry.getAttribute("position"); 84 + const oceanVertices = oceanGeometry.getAttribute("position"); 75 85 const faceCount = vertices.count / 3; 76 86 const faceSize = (Math.PI * 4) / faceCount; 77 87 console.log("faces:", faceCount); ··· 85 95 const colors = new Float32Array(vertices.count * 3); 86 96 const oceanColors = new Float32Array(oceanVertices.count * 3); 87 97 88 - const normals = sphere.getAttribute("normal"); 89 - const oceanNormals = oceanSphere.getAttribute("normal"); 98 + const normals = mainGeometry.getAttribute("normal"); 99 + const oceanNormals = oceanGeometry.getAttribute("normal"); 100 + 101 + const planeUp = new Vector3(0, 1, 0); 90 102 91 103 const a = new Vector3(), 92 104 b = new Vector3(), ··· 111 123 seed: 0, 112 124 }); 113 125 114 - oceanSphere.morphAttributes.position = []; 115 - oceanSphere.morphAttributes.normal = []; 126 + oceanGeometry.morphAttributes.position = []; 127 + oceanGeometry.morphAttributes.normal = []; 116 128 117 129 const oceanMorphPositions: number[] = []; 118 130 const oceanMorphNormals: number[] = []; ··· 142 154 oceanB.fromBufferAttribute(oceanVertices, i + 1); 143 155 oceanC.fromBufferAttribute(oceanVertices, i + 2); 144 156 157 + if (planetOptions.shape == "plane") { 158 + // switch y and z 159 + let temp = a.y; 160 + a.y = a.z; 161 + a.z = temp; 162 + 163 + temp = b.y; 164 + b.y = b.z; 165 + b.z = temp; 166 + 167 + temp = c.y; 168 + c.y = c.z; 169 + c.z = temp; 170 + 171 + temp = oceanA.y; 172 + oceanA.y = oceanA.z; 173 + oceanA.z = temp; 174 + 175 + temp = oceanB.y; 176 + oceanB.y = oceanB.z; 177 + oceanB.z = temp; 178 + 179 + temp = oceanC.y; 180 + oceanC.y = oceanC.z; 181 + oceanC.z = temp; 182 + 183 + // switch a and c 184 + let tempVector = a.clone(); 185 + a.copy(c); 186 + c.copy(tempVector); 187 + 188 + tempVector = oceanA.clone(); 189 + oceanA.copy(oceanC); 190 + oceanC.copy(tempVector); 191 + } 192 + 145 193 mid.set(0, 0, 0); 146 194 mid.addVectors(a, b).add(c).divideScalar(3); 147 195 ··· 160 208 // if not, calculate it 161 209 if (!move) { 162 210 // calculate height and scatter 163 - const height = biome.getHeight(v) + 1; 211 + const height = biome.getHeight(v); 164 212 const scatterX = scatterNoise.get(v); 165 213 const scatterY = scatterNoise.get( 166 214 v.y + scatterScale * 100, ··· 173 221 v.y - scatterScale * 200, 174 222 ); 175 223 // calculate sea height and sea morph height 176 - const seaHeight = biome.getSeaHeight(v) + 1; 177 - const secondSeaHeight = biome.getSeaHeight(v.addScalar(100)) + 1; 224 + const seaHeight = biome.getSeaHeight(v); 225 + const secondSeaHeight = biome.getSeaHeight(v.addScalar(100)); 178 226 179 227 v.subScalar(100); 180 228 ··· 191 239 calculatedVerticesArray[i + j] = move; 192 240 193 241 // we add height here so we can calculate the average normalized height of the face later 194 - normalizedHeight += move.height - 1; 242 + normalizedHeight += move.height; 195 243 196 244 // move vertex based on height and scatter 197 - v.add(move.scatter).normalize().multiplyScalar(move.height); 245 + v.add(move.scatter); 246 + if (planetOptions.shape == "plane") { 247 + v.y = move.height; 248 + } else { 249 + v.normalize().multiplyScalar(move.height + 1); 250 + } 251 + 198 252 vertices.setXYZ(i + j, v.x, v.y, v.z); 199 253 200 - // move ocean vertex based on sea height and scatter 254 + // move ocean morph vertex based on sea morph height and scatter 201 255 let oceanV = oceanA; 202 256 if (j === 1) oceanV = oceanB; 203 257 if (j === 2) oceanV = oceanC; 204 - oceanV.add(move.scatter).normalize().multiplyScalar(move.seaMorph); 258 + oceanV.add(move.scatter); 259 + 260 + if (planetOptions.shape == "plane") { 261 + oceanV.y = move.seaMorph; 262 + } else { 263 + oceanV.normalize().multiplyScalar(move.seaMorph + 1); 264 + } 205 265 oceanMorphPositions.push(oceanV.x, oceanV.y, oceanV.z); 206 266 207 - // move ocean morph vertex based on sea height and scatter 267 + // move ocean vertex based on sea height and scatter 208 268 if (j === 0) { 209 269 oceanD.copy(oceanV); 270 + oceanV = oceanD; 210 271 } else if (j === 1) { 211 272 oceanE.copy(oceanV); 273 + oceanV = oceanE; 212 274 } else if (j === 2) { 213 275 oceanF.copy(oceanV); 276 + oceanV = oceanF; 214 277 } 215 - oceanV.normalize().multiplyScalar(move.seaHeight); 278 + if (planetOptions.shape == "plane") { 279 + oceanV.y = move.seaHeight; 280 + } else { 281 + oceanV.normalize().multiplyScalar(move.seaHeight + 1); 282 + } 216 283 oceanVertices.setXYZ(i + j, oceanV.x, oceanV.y, oceanV.z); 217 284 } 218 285 ··· 233 300 234 301 // calculate steepness (acos of dot product of normal and up vector) 235 302 // (up vector = old mid point on sphere) 236 - const steepness = Math.acos(Math.abs(temp.dot(mid))); 303 + const steepness = Math.acos( 304 + Math.abs(temp.dot(planetOptions.shape == "plane" ? planeUp : mid)), 305 + ); 237 306 // steepness is between 0 and PI/2 238 307 // this will be used for color calculation and vegetation placement 239 308 ··· 330 399 if (!placedVegetation[vegetation.name]) { 331 400 placedVegetation[vegetation.name] = []; 332 401 } 333 - let height = a.length(); 334 - placedVegetation[vegetation.name].push( 335 - a 336 - .clone() 337 - .normalize() 338 - .multiplyScalar(height + 0.005), 339 - ); 402 + 403 + placedVegetation[vegetation.name].push(a.clone()); 340 404 341 - biome.addVegetation( 342 - vegetation, 343 - a.normalize(), 344 - normalizedHeight, 345 - steepness, 346 - ); 405 + if (planetOptions.shape == "plane") { 406 + a.y = 0; 407 + } else { 408 + a.normalize(); 409 + } 410 + 411 + biome.addVegetation(vegetation, a, normalizedHeight, steepness); 347 412 break; 348 413 } 349 414 } 350 415 } 351 416 352 - const maxDist = 0.14; 353 - 354 417 const color = new Color(); 355 418 356 419 // go through all vertices again and update height and color based on vegetation 357 420 for (let i = 0; i < vertices.count; i += 3) { 358 421 a.fromBufferAttribute(vertices, i); 359 - a.normalize(); 360 422 b.fromBufferAttribute(vertices, i + 1); 361 - b.normalize(); 362 423 c.fromBufferAttribute(vertices, i + 2); 363 - c.normalize(); 424 + 425 + if (planetOptions.shape == "plane") { 426 + a.y = 0; 427 + b.y = 0; 428 + c.y = 0; 429 + } else { 430 + a.normalize(); 431 + b.normalize(); 432 + c.normalize(); 433 + } 364 434 365 435 color.setRGB(colors[i * 3], colors[i * 3 + 1], colors[i * 3 + 2]); 366 436 ··· 377 447 const moveDataC = calculatedVerticesArray[i + 2]; 378 448 379 449 // update height based on vegetation 380 - a.normalize().multiplyScalar(moveDataA.height + output.heightA); 381 - b.normalize().multiplyScalar(moveDataB.height + output.heightB); 382 - c.normalize().multiplyScalar(moveDataC.height + output.heightC); 450 + if (planetOptions.shape == "plane") { 451 + a.y = moveDataA.height + output.heightA; 452 + b.y = moveDataB.height + output.heightB; 453 + c.y = moveDataC.height + output.heightC; 454 + } else { 455 + a.normalize().multiplyScalar(moveDataA.height + output.heightA + 1); 456 + b.normalize().multiplyScalar(moveDataB.height + output.heightB + 1); 457 + c.normalize().multiplyScalar(moveDataC.height + output.heightC + 1); 458 + } 383 459 384 460 vertices.setXYZ(i, a.x, a.y, a.z); 385 461 vertices.setXYZ(i + 1, b.x, b.y, b.z); ··· 399 475 colors[i * 3 + 8] = output.color.b; 400 476 } 401 477 402 - oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute( 478 + oceanGeometry.morphAttributes.position[0] = new Float32BufferAttribute( 403 479 oceanMorphPositions, 404 480 3, 405 481 ); 406 - oceanSphere.morphAttributes.normal[0] = new Float32BufferAttribute( 482 + oceanGeometry.morphAttributes.normal[0] = new Float32BufferAttribute( 407 483 oceanMorphNormals, 408 484 3, 409 485 ); 410 486 411 - sphere.setAttribute("color", new BufferAttribute(colors, 3)); 412 - oceanSphere.setAttribute("color", new BufferAttribute(oceanColors, 3)); 487 + mainGeometry.setAttribute("color", new BufferAttribute(colors, 3)); 488 + oceanGeometry.setAttribute("color", new BufferAttribute(oceanColors, 3)); 413 489 414 - return [sphere, oceanSphere, placedVegetation]; 490 + return [mainGeometry, oceanGeometry, placedVegetation]; 415 491 }