terrain-component.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import {THREE, Float32ToFloat16} from '../three-defs.js';
  2. import * as shaders from '../../game/render/shaders.js';
  3. import * as entity from "../entity.js";
  4. import * as math from '../math.js';
  5. import { render_component } from '../render-component.js';
  6. import * as grass_component from './grass-component.js';
  7. import * as bugs_component from './bugs-component.js';
  8. import * as wind_component from './wind-component.js';
  9. import * as water_component from './water-component.js';
  10. function GetImageData_(image) {
  11. const canvas = document.createElement('canvas');
  12. canvas.width = image.width;
  13. canvas.height = image.height;
  14. const context = canvas.getContext( '2d' );
  15. context.drawImage(image, 0, 0);
  16. return context.getImageData(0, 0, image.width, image.height);
  17. }
  18. class Heightmap {
  19. constructor(params, img) {
  20. this.params_ = params;
  21. this.data_ = GetImageData_(img);
  22. }
  23. Get(x, y) {
  24. const _GetPixelAsFloat = (x, y) => {
  25. const position = (x + this.data_.width * y) * 4;
  26. const data = this.data_.data;
  27. return data[position] / 255.0;
  28. }
  29. // Bilinear filter
  30. const offset = this.params_.offset;
  31. const dimensions = this.params_.dimensions;
  32. const xf = math.sat((x - offset.x) / dimensions.x);
  33. const yf = 1.0 - math.sat((y - offset.y) / dimensions.y);
  34. const w = this.data_.width - 1;
  35. const h = this.data_.height - 1;
  36. const x1 = Math.floor(xf * w);
  37. const y1 = Math.floor(yf * h);
  38. const x2 = math.clamp(x1 + 1, 0, w);
  39. const y2 = math.clamp(y1 + 1, 0, h);
  40. const xp = xf * w - x1;
  41. const yp = yf * h - y1;
  42. const p11 = _GetPixelAsFloat(x1, y1);
  43. const p21 = _GetPixelAsFloat(x2, y1);
  44. const p12 = _GetPixelAsFloat(x1, y2);
  45. const p22 = _GetPixelAsFloat(x2, y2);
  46. const px1 = math.lerp(xp, p11, p21);
  47. const px2 = math.lerp(xp, p12, p22);
  48. return math.lerp(yp, px1, px2) * this.params_.height;
  49. }
  50. }
  51. const TERRAIN_HEIGHT = 75;
  52. const TERRAIN_OFFSET = 50;
  53. // const TERRAIN_HEIGHT = 0;
  54. // const TERRAIN_OFFSET = 0;
  55. const TERRAIN_DIMS = 2000;
  56. export class TerrainComponent extends entity.Component {
  57. static CLASS_NAME = 'TerrainComponent';
  58. get NAME() {
  59. return TerrainComponent.CLASS_NAME;
  60. }
  61. #params_;
  62. #heightmap_;
  63. #mesh_;
  64. constructor(params) {
  65. super();
  66. this.#params_ = params;
  67. this.#heightmap_ = null;
  68. this.#mesh_ = null;
  69. }
  70. Destroy() {
  71. this.#mesh_.removeFromParent();
  72. }
  73. GetHeight(x, y) {
  74. const xn = (x + TERRAIN_DIMS * 0.5) / TERRAIN_DIMS;
  75. const yn = 1 - (y + TERRAIN_DIMS * 0.5) / TERRAIN_DIMS;
  76. return this.#heightmap_.Get(xn, yn) - TERRAIN_OFFSET;
  77. }
  78. IsReady() {
  79. return this.#heightmap_ != null;
  80. }
  81. InitEntity() {
  82. const threejs = this.FindEntity('threejs').GetComponent('ThreeJSController');
  83. const geometry = new THREE.PlaneGeometry(TERRAIN_DIMS, TERRAIN_DIMS, 256, 256);
  84. const textureLoader = new THREE.TextureLoader();
  85. textureLoader.load(
  86. './textures/' + 'terrain.png',
  87. (heightmapTexture) => {
  88. const heightmapGenerator = new Heightmap({
  89. dimensions: new THREE.Vector2(1.0, 1.0),
  90. offset: new THREE.Vector2(0.0, 0.0),
  91. height: TERRAIN_HEIGHT
  92. }, heightmapTexture.image);
  93. this.#heightmap_ = heightmapGenerator;
  94. const positions = geometry.attributes.position;
  95. const uv = geometry.attributes.uv;
  96. for (let i = 0; i < positions.count; i++) {
  97. const h = heightmapGenerator.Get(uv.array[i*2+0], uv.array[i*2+1]) - TERRAIN_OFFSET;
  98. positions.array[i*3+2] = h;
  99. }
  100. geometry.computeVertexNormals();
  101. geometry.computeTangents();
  102. const position16 = Float32ToFloat16(geometry.attributes.position.array);
  103. const normal16 = Float32ToFloat16(geometry.attributes.normal.array);
  104. const tangent16 = Float32ToFloat16(geometry.attributes.tangent.array);
  105. const uv16 = Float32ToFloat16(geometry.attributes.uv.array);
  106. geometry.setAttribute('position', new THREE.Float16BufferAttribute(position16, 3));
  107. geometry.setAttribute('normal', new THREE.Float16BufferAttribute(normal16, 3));
  108. geometry.setAttribute('tangent', new THREE.Float16BufferAttribute(tangent16, 3));
  109. geometry.setAttribute('uv', new THREE.Float16BufferAttribute(uv16, 2));
  110. geometry.rotateX(-Math.PI / 2);
  111. heightmapTexture.colorSpace = THREE.LinearSRGBColorSpace;
  112. const LOAD_ = (name) => {
  113. const albedo = textureLoader.load('./textures/' + name);
  114. albedo.magFilter = THREE.LinearFilter;
  115. albedo.minFilter = THREE.LinearMipMapLinearFilter;
  116. albedo.wrapS = THREE.RepeatWrapping;
  117. albedo.wrapT = THREE.RepeatWrapping;
  118. albedo.anisotropy = 16;
  119. albedo.repeat.set(40, 40);
  120. return albedo;
  121. }
  122. // const grassAlbedo = LOAD_('wispy-grass-meadow_albedo.png');
  123. const grid = LOAD_('grid.png');
  124. grid.anisotropy = 16;
  125. grid.repeat.set(1, 1);
  126. const terrainMaterial = new shaders.GamePBRMaterial('TERRAIN', {});
  127. terrainMaterial.setTexture('heightmap', heightmapTexture);
  128. terrainMaterial.setTexture('grid', grid);
  129. terrainMaterial.setVec4('heightParams', new THREE.Vector4(TERRAIN_DIMS, TERRAIN_DIMS, TERRAIN_HEIGHT, TERRAIN_OFFSET));
  130. this.#mesh_ = new THREE.Mesh(geometry, terrainMaterial);
  131. this.#mesh_.position.set(0, 0, 0);
  132. this.#mesh_.receiveShadow = true;
  133. this.#mesh_.castShadow = false;
  134. threejs.AddSceneObject(this.#mesh_);
  135. this.Broadcast({
  136. topic: 'render.loaded',
  137. value: this.#mesh_,
  138. });
  139. const mountain = new entity.Entity();
  140. mountain.AddComponent(new render_component.RenderComponent({
  141. resourcePath: './models/',
  142. resourceName: 'mountain.glb',
  143. scale: new THREE.Vector3(1, 1, 1),
  144. emissive: new THREE.Color(0x000000),
  145. color: new THREE.Color(0xFFFFFF),
  146. receiveShadow: false,
  147. castShadow: false,
  148. }));
  149. mountain.SetPosition(new THREE.Vector3(0, -100, 0));
  150. mountain.SetActive(false);
  151. mountain.Init();
  152. const water = new entity.Entity();
  153. water.AddComponent(new water_component.WaterComponent({
  154. terrain: this.Parent,
  155. height: TERRAIN_HEIGHT,
  156. offset: TERRAIN_OFFSET,
  157. heightmap: heightmapTexture
  158. }));
  159. water.SetActive(true);
  160. water.Init(this.Parent);
  161. const grass = new entity.Entity();
  162. grass.AddComponent(new grass_component.GrassComponent({
  163. terrain: this.Parent,
  164. height: TERRAIN_HEIGHT,
  165. offset: TERRAIN_OFFSET,
  166. dims: TERRAIN_DIMS,
  167. heightmap: heightmapTexture
  168. }));
  169. grass.SetActive(true);
  170. grass.Init(this.Parent);
  171. const bugs = new entity.Entity();
  172. bugs.AddComponent(new bugs_component.BugsComponent({
  173. terrain: this.Parent,
  174. height: TERRAIN_HEIGHT,
  175. offset: TERRAIN_OFFSET,
  176. dims: TERRAIN_DIMS,
  177. heightmap: heightmapTexture
  178. }));
  179. bugs.SetActive(true);
  180. bugs.Init(this.Parent);
  181. const wind = new entity.Entity();
  182. wind.AddComponent(new wind_component.WindComponent({
  183. terrain: this.Parent,
  184. height: TERRAIN_HEIGHT,
  185. offset: TERRAIN_OFFSET,
  186. dims: TERRAIN_DIMS,
  187. heightmap: heightmapTexture
  188. }));
  189. wind.SetActive(true);
  190. wind.Init(this.Parent);
  191. });
  192. }
  193. Update(timeElapsed) {
  194. }
  195. }