custom-geometry-heightmap.html 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <!-- Licensed under a BSD license. See license.html for license -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  7. <title>Three.js - Custom Geometry - Heightmap</title>
  8. <style>
  9. html, body {
  10. height: 100%;
  11. margin: 0;
  12. }
  13. #c {
  14. width: 100%;
  15. height: 100%;
  16. display: block;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <canvas id="c"></canvas>
  22. </body>
  23. <script type="module">
  24. import * as THREE from '../../build/three.module.js';
  25. import {OrbitControls} from '../../examples/jsm/controls/OrbitControls.js';
  26. function main() {
  27. const canvas = document.querySelector('#c');
  28. const renderer = new THREE.WebGLRenderer({canvas});
  29. const fov = 75;
  30. const aspect = 2; // the canvas default
  31. const near = 0.1;
  32. const far = 200;
  33. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  34. camera.position.set(20, 20, 20);
  35. const controls = new OrbitControls(camera, canvas);
  36. controls.target.set(0, 0, 0);
  37. controls.update();
  38. const scene = new THREE.Scene();
  39. function addLight(...pos) {
  40. const color = 0xFFFFFF;
  41. const intensity = 1;
  42. const light = new THREE.DirectionalLight(color, intensity);
  43. light.position.set(...pos);
  44. scene.add(light);
  45. }
  46. addLight(-1, 2, 4);
  47. addLight(1, 2, -2);
  48. const imgLoader = new THREE.ImageLoader();
  49. imgLoader.load('resources/images/heightmap-96x64.png', createHeightmap);
  50. function createHeightmap(image) {
  51. // extract the data from the image by drawing it to a canvas
  52. // and calling getImageData
  53. const ctx = document.createElement('canvas').getContext('2d');
  54. const {width, height} = image;
  55. ctx.canvas.width = width;
  56. ctx.canvas.height = height;
  57. ctx.drawImage(image, 0, 0);
  58. const {data} = ctx.getImageData(0, 0, width, height);
  59. const geometry = new THREE.Geometry();
  60. const cellsAcross = width - 1;
  61. const cellsDeep = height - 1;
  62. for (let z = 0; z < cellsDeep; ++z) {
  63. for (let x = 0; x < cellsAcross; ++x) {
  64. // compute row offsets into the height data
  65. // we multiply by 4 because the data is R,G,B,A but we
  66. // only care about R
  67. const base0 = (z * width + x) * 4;
  68. const base1 = base0 + (width * 4);
  69. // look up the height for the for points
  70. // around this cell
  71. const h00 = data[base0] / 32;
  72. const h01 = data[base0 + 4] / 32;
  73. const h10 = data[base1] / 32;
  74. const h11 = data[base1 + 4] / 32;
  75. // compute the average height
  76. const hm = (h00 + h01 + h10 + h11) / 4;
  77. // the corner positions
  78. const x0 = x;
  79. const x1 = x + 1;
  80. const z0 = z;
  81. const z1 = z + 1;
  82. // remember the first index of these 5 vertices
  83. const ndx = geometry.vertices.length;
  84. // add the 4 corners for this cell and the midpoint
  85. geometry.vertices.push(
  86. new THREE.Vector3(x0, h00, z0),
  87. new THREE.Vector3(x1, h01, z0),
  88. new THREE.Vector3(x0, h10, z1),
  89. new THREE.Vector3(x1, h11, z1),
  90. new THREE.Vector3((x0 + x1) / 2, hm, (z0 + z1) / 2),
  91. );
  92. // 2----3
  93. // |\ /|
  94. // | \/4|
  95. // | /\ |
  96. // |/ \|
  97. // 0----1
  98. // create 4 triangles
  99. geometry.faces.push(
  100. new THREE.Face3(ndx , ndx + 4, ndx + 1),
  101. new THREE.Face3(ndx + 1, ndx + 4, ndx + 3),
  102. new THREE.Face3(ndx + 3, ndx + 4, ndx + 2),
  103. new THREE.Face3(ndx + 2, ndx + 4, ndx + 0),
  104. );
  105. // add the texture coordinates for each vertex of each face.
  106. const u0 = x / cellsAcross;
  107. const v0 = z / cellsDeep;
  108. const u1 = (x + 1) / cellsAcross;
  109. const v1 = (z + 1) / cellsDeep;
  110. const um = (u0 + u1) / 2;
  111. const vm = (v0 + v1) / 2;
  112. geometry.faceVertexUvs[0].push(
  113. [ new THREE.Vector2(u0, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v0) ],
  114. [ new THREE.Vector2(u1, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v1) ],
  115. [ new THREE.Vector2(u1, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v1) ],
  116. [ new THREE.Vector2(u0, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v0) ],
  117. );
  118. }
  119. }
  120. geometry.computeFaceNormals();
  121. // center the geometry
  122. geometry.translate(width / -2, 0, height / -2);
  123. const loader = new THREE.TextureLoader();
  124. const texture = loader.load('resources/images/star.png');
  125. const material = new THREE.MeshPhongMaterial({color: 'green', map: texture});
  126. const cube = new THREE.Mesh(geometry, material);
  127. scene.add(cube);
  128. }
  129. function resizeRendererToDisplaySize(renderer) {
  130. const canvas = renderer.domElement;
  131. const width = canvas.clientWidth;
  132. const height = canvas.clientHeight;
  133. const needResize = canvas.width !== width || canvas.height !== height;
  134. if (needResize) {
  135. renderer.setSize(width, height, false);
  136. }
  137. return needResize;
  138. }
  139. function render() {
  140. if (resizeRendererToDisplaySize(renderer)) {
  141. const canvas = renderer.domElement;
  142. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  143. camera.updateProjectionMatrix();
  144. }
  145. renderer.render(scene, camera);
  146. requestAnimationFrame(render);
  147. }
  148. requestAnimationFrame(render);
  149. }
  150. main();
  151. </script>
  152. </html>