terrain.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {graphics} from './graphics.js';
  3. import {math} from './math.js';
  4. import {noise} from './noise.js';
  5. import {quadtree} from './quadtree.js';
  6. import {spline} from './spline.js';
  7. import {terrain_chunk} from './terrain-chunk.js';
  8. import {terrain_shader} from './terrain-shader.js';
  9. import {terrain_builder} from './terrain-builder.js';
  10. import {terrain_builder_threaded} from './terrain-builder-threaded.js';
  11. import {texture_splatter} from './texture-splatter.js';
  12. import {textures} from './textures.js';
  13. import {utils} from './utils.js';
  14. export const terrain = (function() {
  15. const _MIN_CELL_SIZE = 250;
  16. const _MIN_CELL_RESOLUTION = 64;
  17. const _PLANET_RADIUS = 4000;
  18. class TerrainChunkManager {
  19. constructor(params) {
  20. this._Init(params);
  21. }
  22. _Init(params) {
  23. this._params = params;
  24. const loader = new THREE.TextureLoader();
  25. const noiseTexture = loader.load('./resources/simplex-noise.png');
  26. noiseTexture.wrapS = THREE.RepeatWrapping;
  27. noiseTexture.wrapT = THREE.RepeatWrapping;
  28. const diffuse = new textures.TextureAtlas(params);
  29. diffuse.Load('diffuse', [
  30. './resources/dirt_01_diffuse-1024.png',
  31. './resources/grass1-albedo3-1024.png',
  32. './resources/sandyground-albedo-1024.png',
  33. './resources/worn-bumpy-rock-albedo-1024.png',
  34. './resources/rock-snow-ice-albedo-1024.png',
  35. './resources/snow-packed-albedo-1024.png',
  36. './resources/rough-wet-cobble-albedo-1024.png',
  37. './resources/sandy-rocks1-albedo-1024.png',
  38. ]);
  39. diffuse.onLoad = () => {
  40. this._material.uniforms.diffuseMap.value = diffuse.Info['diffuse'].atlas;
  41. };
  42. const normal = new textures.TextureAtlas(params);
  43. normal.Load('normal', [
  44. './resources/dirt_01_normal-1024.jpg',
  45. './resources/grass1-normal-1024.jpg',
  46. './resources/sandyground-normal-1024.jpg',
  47. './resources/worn-bumpy-rock-normal-1024.jpg',
  48. './resources/rock-snow-ice-normal-1024.jpg',
  49. './resources/snow-packed-normal-1024.jpg',
  50. './resources/rough-wet-cobble-normal-1024.jpg',
  51. './resources/sandy-rocks1-normal-1024.jpg',
  52. ]);
  53. normal.onLoad = () => {
  54. this._material.uniforms.normalMap.value = normal.Info['normal'].atlas;
  55. };
  56. this._material = new THREE.MeshStandardMaterial({
  57. wireframe: false,
  58. wireframeLinewidth: 1,
  59. color: 0xFFFFFF,
  60. side: THREE.FrontSide,
  61. vertexColors: THREE.VertexColors,
  62. // normalMap: texture,
  63. });
  64. this._material = new THREE.RawShaderMaterial({
  65. uniforms: {
  66. diffuseMap: {
  67. },
  68. normalMap: {
  69. },
  70. noiseMap: {
  71. value: noiseTexture
  72. },
  73. },
  74. vertexShader: terrain_shader.VS,
  75. fragmentShader: terrain_shader.PS,
  76. side: THREE.FrontSide
  77. });
  78. this._builder = new terrain_builder_threaded.TerrainChunkRebuilder_Threaded();
  79. // this._builder = new terrain_builder.TerrainChunkRebuilder();
  80. this._InitNoise(params);
  81. this._InitBiomes(params);
  82. this._InitTerrain(params);
  83. }
  84. _InitNoise(params) {
  85. params.guiParams.noise = {
  86. octaves: 10,
  87. persistence: 0.5,
  88. lacunarity: 1.6,
  89. exponentiation: 7.5,
  90. height: 900.0,
  91. scale: 1800.0,
  92. seed: 1
  93. };
  94. const onNoiseChanged = () => {
  95. this._builder.Rebuild(this._chunks);
  96. };
  97. const noiseRollup = params.gui.addFolder('Terrain.Noise');
  98. noiseRollup.add(params.guiParams.noise, "scale", 32.0, 4096.0).onChange(
  99. onNoiseChanged);
  100. noiseRollup.add(params.guiParams.noise, "octaves", 1, 20, 1).onChange(
  101. onNoiseChanged);
  102. noiseRollup.add(params.guiParams.noise, "persistence", 0.25, 1.0).onChange(
  103. onNoiseChanged);
  104. noiseRollup.add(params.guiParams.noise, "lacunarity", 0.01, 4.0).onChange(
  105. onNoiseChanged);
  106. noiseRollup.add(params.guiParams.noise, "exponentiation", 0.1, 10.0).onChange(
  107. onNoiseChanged);
  108. noiseRollup.add(params.guiParams.noise, "height", 0, 20000).onChange(
  109. onNoiseChanged);
  110. this._noise = new noise.Noise(params.guiParams.noise);
  111. this._noiseParams = params.guiParams.noise;
  112. params.guiParams.heightmap = {
  113. height: 16,
  114. };
  115. const heightmapRollup = params.gui.addFolder('Terrain.Heightmap');
  116. heightmapRollup.add(params.guiParams.heightmap, "height", 0, 128).onChange(
  117. onNoiseChanged);
  118. }
  119. _InitBiomes(params) {
  120. params.guiParams.biomes = {
  121. octaves: 2,
  122. persistence: 0.5,
  123. lacunarity: 2.0,
  124. scale: 2048.0,
  125. noiseType: 'simplex',
  126. seed: 2,
  127. exponentiation: 1,
  128. height: 1.0
  129. };
  130. const onNoiseChanged = () => {
  131. this._builder.Rebuild(this._chunks);
  132. };
  133. const noiseRollup = params.gui.addFolder('Terrain.Biomes');
  134. noiseRollup.add(params.guiParams.biomes, "scale", 64.0, 4096.0).onChange(
  135. onNoiseChanged);
  136. noiseRollup.add(params.guiParams.biomes, "octaves", 1, 20, 1).onChange(
  137. onNoiseChanged);
  138. noiseRollup.add(params.guiParams.biomes, "persistence", 0.01, 1.0).onChange(
  139. onNoiseChanged);
  140. noiseRollup.add(params.guiParams.biomes, "lacunarity", 0.01, 4.0).onChange(
  141. onNoiseChanged);
  142. noiseRollup.add(params.guiParams.biomes, "exponentiation", 0.1, 10.0).onChange(
  143. onNoiseChanged);
  144. this._biomes = new noise.Noise(params.guiParams.biomes);
  145. this._biomesParams = params.guiParams.biomes;
  146. const colourParams = {
  147. octaves: 1,
  148. persistence: 0.5,
  149. lacunarity: 2.0,
  150. exponentiation: 1.0,
  151. scale: 256.0,
  152. noiseType: 'simplex',
  153. seed: 2,
  154. height: 1.0,
  155. };
  156. this._colourNoise = new noise.Noise(colourParams);
  157. this._colourNoiseParams = colourParams;
  158. }
  159. _InitTerrain(params) {
  160. params.guiParams.terrain= {
  161. wireframe: false,
  162. };
  163. this._groups = [...new Array(6)].map(_ => new THREE.Group());
  164. params.scene.add(...this._groups);
  165. const terrainRollup = params.gui.addFolder('Terrain');
  166. terrainRollup.add(params.guiParams.terrain, "wireframe").onChange(() => {
  167. for (let k in this._chunks) {
  168. this._chunks[k].chunk._plane.material.wireframe = params.guiParams.terrain.wireframe;
  169. }
  170. });
  171. this._chunks = {};
  172. this._params = params;
  173. }
  174. _CellIndex(p) {
  175. const xp = p.x + _MIN_CELL_SIZE * 0.5;
  176. const yp = p.z + _MIN_CELL_SIZE * 0.5;
  177. const x = Math.floor(xp / _MIN_CELL_SIZE);
  178. const z = Math.floor(yp / _MIN_CELL_SIZE);
  179. return [x, z];
  180. }
  181. _CreateTerrainChunk(group, offset, width, resolution) {
  182. const params = {
  183. group: group,
  184. material: this._material,
  185. width: width,
  186. offset: offset,
  187. radius: _PLANET_RADIUS,
  188. resolution: resolution,
  189. biomeGenerator: this._biomes,
  190. colourGenerator: new texture_splatter.TextureSplatter(
  191. {biomeGenerator: this._biomes, colourNoise: this._colourNoise}),
  192. heightGenerators: [new texture_splatter.HeightGenerator(
  193. this._noise, offset, 100000, 100000 + 1)],
  194. noiseParams: this._noiseParams,
  195. colourNoiseParams: this._colourNoiseParams,
  196. biomesParams: this._biomesParams,
  197. colourGeneratorParams: {
  198. biomeGeneratorParams: this._biomesParams,
  199. colourNoiseParams: this._colourNoiseParams,
  200. },
  201. heightGeneratorsParams: {
  202. min: 100000,
  203. max: 100000 + 1,
  204. }
  205. };
  206. return this._builder.AllocateChunk(params);
  207. }
  208. Update(_) {
  209. this._builder.Update();
  210. if (!this._builder.Busy) {
  211. this._UpdateVisibleChunks_Quadtree();
  212. }
  213. }
  214. _UpdateVisibleChunks_Quadtree() {
  215. function _Key(c) {
  216. return c.position[0] + '/' + c.position[1] + ' [' + c.size + ']' + ' [' + c.index + ']';
  217. }
  218. const q = new quadtree.CubeQuadTree({
  219. radius: _PLANET_RADIUS,
  220. min_node_size: _MIN_CELL_SIZE,
  221. });
  222. q.Insert(this._params.camera.position);
  223. const sides = q.GetChildren();
  224. let newTerrainChunks = {};
  225. const center = new THREE.Vector3();
  226. const dimensions = new THREE.Vector3();
  227. for (let i = 0; i < sides.length; i++) {
  228. this._groups[i].matrix = sides[i].transform;
  229. this._groups[i].matrixAutoUpdate = false;
  230. for (let c of sides[i].children) {
  231. c.bounds.getCenter(center);
  232. c.bounds.getSize(dimensions);
  233. const child = {
  234. index: i,
  235. group: this._groups[i],
  236. position: [center.x, center.y, center.z],
  237. bounds: c.bounds,
  238. size: dimensions.x,
  239. };
  240. const k = _Key(child);
  241. newTerrainChunks[k] = child;
  242. }
  243. }
  244. const intersection = utils.DictIntersection(this._chunks, newTerrainChunks);
  245. const difference = utils.DictDifference(newTerrainChunks, this._chunks);
  246. const recycle = Object.values(utils.DictDifference(this._chunks, newTerrainChunks));
  247. this._builder.RetireChunks(recycle);
  248. newTerrainChunks = intersection;
  249. for (let k in difference) {
  250. const [xp, yp, zp] = difference[k].position;
  251. const offset = new THREE.Vector3(xp, yp, zp);
  252. newTerrainChunks[k] = {
  253. position: [xp, zp],
  254. chunk: this._CreateTerrainChunk(
  255. difference[k].group, offset, difference[k].size, _MIN_CELL_RESOLUTION),
  256. };
  257. }
  258. this._chunks = newTerrainChunks;
  259. }
  260. }
  261. return {
  262. TerrainChunkManager: TerrainChunkManager
  263. }
  264. })();