custom-buffergeometry.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Custom BufferGeometry</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – Custom BufferGeometry">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <!-- Import maps polyfill -->
  14. <!-- Remove this when import maps will be widely supported -->
  15. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  16. <script type="importmap">
  17. {
  18. "imports": {
  19. "three": "../../build/three.module.js"
  20. }
  21. }
  22. </script>
  23. </head>
  24. <body>
  25. <div class="container">
  26. <div class="lesson-title">
  27. <h1>Custom BufferGeometry</h1>
  28. </div>
  29. <div class="lesson">
  30. <div class="lesson-main">
  31. <p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> is three.js's way of representing all geometry. A <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>
  32. essentially a collection <em>named</em> of <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s.
  33. Each <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> represents an array of one type of data: positions,
  34. normals, colors, uv, etc... Together, the named <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s represent
  35. <em>parallel arrays</em> of all the data for each vertex.</p>
  36. <div class="threejs_center"><img src="../resources/threejs-attributes.svg" style="width: 700px"></div>
  37. <p>Above you can see we have 4 attributes: <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, <code class="notranslate" translate="no">color</code>, <code class="notranslate" translate="no">uv</code>.
  38. They represent <em>parallel arrays</em> which means that the Nth set of data in each
  39. attribute belongs to the same vertex. The vertex at index = 4 is highlighted
  40. to show that the parallel data across all attributes defines one vertex.</p>
  41. <p>This brings up a point, here's a diagram of a cube with one corner highlighted.</p>
  42. <div class="threejs_center"><img src="../resources/cube-faces-vertex.svg" style="width: 500px"></div>
  43. <p>Thinking about it that single corner needs a different normal for each face of the
  44. cube. A normal is info about which direction something faces. In the diagram
  45. the normals are presented by the arrows around the corner vertex showing that each
  46. face that shares that vertex position needs a normal that points in a different direction.</p>
  47. <p>That corner needs different UVs for each face as well. UVs are texture coordinates
  48. that specify which part of a texture being drawn on a triangle corresponds to that
  49. vertex position. You can see the green face needs that vertex to have a UV that corresponds
  50. to the top right corner of the F texture, the blue face needs a UV that corresponds to the
  51. top left corner of the F texture, and the red face needs a UV that corresponds to the bottom
  52. left corner of the F texture.</p>
  53. <p>A single <em>vertex</em> is the combination of all of its parts. If a vertex needs any
  54. part to be different then it must be a different vertex.</p>
  55. <p>As a simple example let's make a cube using <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. A cube is interesting
  56. because it appears to share vertices at the corners but really
  57. does not. For our example we'll list out all the vertices with all their data
  58. and then convert that data into parallel arrays and finally use those to make
  59. <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s and add them to a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
  60. <p>We start with a list of all the data needed for the cube. Remember again
  61. that if a vertex has any unique parts it has to be a separate vertex. As such
  62. to make a cube requires 36 vertices. 2 triangles per face, 3 vertices per triangle,
  63. 6 faces = 36 vertices.</p>
  64. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  65. // front
  66. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], },
  67. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  68. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  69. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  70. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  71. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], },
  72. // right
  73. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], },
  74. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  75. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  76. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  77. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  78. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], },
  79. // back
  80. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], },
  81. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  82. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  83. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  84. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  85. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], },
  86. // left
  87. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], },
  88. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  89. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  90. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  91. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  92. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], },
  93. // top
  94. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], },
  95. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  96. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  97. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  98. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  99. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], },
  100. // bottom
  101. { pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], },
  102. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  103. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  104. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  105. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  106. { pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], },
  107. ];
  108. </pre>
  109. <p>We can then translate all of that into 3 parallel arrays</p>
  110. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const positions = [];
  111. const normals = [];
  112. const uvs = [];
  113. for (const vertex of vertices) {
  114. positions.push(...vertex.pos);
  115. normals.push(...vertex.norm);
  116. uvs.push(...vertex.uv);
  117. }
  118. </pre>
  119. <p>Finally we can create a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> and then a <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> for each array
  120. and add it to the <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
  121. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> const geometry = new THREE.BufferGeometry();
  122. const positionNumComponents = 3;
  123. const normalNumComponents = 3;
  124. const uvNumComponents = 2;
  125. geometry.setAttribute(
  126. 'position',
  127. new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  128. geometry.setAttribute(
  129. 'normal',
  130. new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  131. geometry.setAttribute(
  132. 'uv',
  133. new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  134. </pre>
  135. <p>Note that the names are significant. You must name your attributes the names
  136. that match what three.js expects (unless you are creating a custom shader).
  137. In this case <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, and <code class="notranslate" translate="no">uv</code>. If you want vertex colors then
  138. name your attribute <code class="notranslate" translate="no">color</code>.</p>
  139. <p>Above we created 3 JavaScript native arrays, <code class="notranslate" translate="no">positions</code>, <code class="notranslate" translate="no">normals</code> and <code class="notranslate" translate="no">uvs</code>.
  140. We then convert those into
  141. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a>
  142. of type <code class="notranslate" translate="no">Float32Array</code>. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> requires a TypedArray not a native
  143. array. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> also requires you to tell it how many components there
  144. are per vertex. For the positions and normals we have 3 components per vertex,
  145. x, y, and z. For the UVs we have 2, u and v.</p>
  146. <p></p><div translate="no" class="threejs_example_container notranslate">
  147. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube.html"></iframe></div>
  148. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube.html" target="_blank">click here to open in a separate window</a>
  149. </div>
  150. <p></p>
  151. <p>That's a lot of data. A small thing we can do is use indices to reference
  152. the vertices. Looking back at our cube data, each face is made from 2 triangles
  153. with 3 vertices each, 6 vertices total, but 2 of those vertices are exactly the same;
  154. The same position, the same normal, and the same uv.
  155. So, we can remove the matching vertices and then
  156. reference them by index. First we remove the matching vertices.</p>
  157. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  158. // front
  159. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], }, // 0
  160. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], }, // 1
  161. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], }, // 2
  162. -
  163. - { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  164. - { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  165. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], }, // 3
  166. // right
  167. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], }, // 4
  168. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], }, // 5
  169. -
  170. - { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  171. - { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  172. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], }, // 6
  173. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], }, // 7
  174. // back
  175. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], }, // 8
  176. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], }, // 9
  177. -
  178. - { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  179. - { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  180. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], }, // 10
  181. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], }, // 11
  182. // left
  183. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], }, // 12
  184. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], }, // 13
  185. -
  186. - { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  187. - { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  188. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], }, // 14
  189. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], }, // 15
  190. // top
  191. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], }, // 16
  192. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], }, // 17
  193. -
  194. - { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  195. - { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  196. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], }, // 18
  197. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], }, // 19
  198. // bottom
  199. { pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], }, // 20
  200. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], }, // 21
  201. -
  202. - { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  203. - { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  204. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], }, // 22
  205. { pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], }, // 23
  206. ];
  207. </pre>
  208. <p>So now we have 24 unique vertices. Then we specify 36 indices
  209. for the 36 vertices we need drawn to make 12 triangles by calling <a href="/docs/#api/en/core/BufferGeometry.setIndex"><code class="notranslate" translate="no">BufferGeometry.setIndex</code></a> with an array of indices.</p>
  210. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.setAttribute(
  211. 'position',
  212. new THREE.BufferAttribute(positions, positionNumComponents));
  213. geometry.setAttribute(
  214. 'normal',
  215. new THREE.BufferAttribute(normals, normalNumComponents));
  216. geometry.setAttribute(
  217. 'uv',
  218. new THREE.BufferAttribute(uvs, uvNumComponents));
  219. +geometry.setIndex([
  220. + 0, 1, 2, 2, 1, 3, // front
  221. + 4, 5, 6, 6, 5, 7, // right
  222. + 8, 9, 10, 10, 9, 11, // back
  223. + 12, 13, 14, 14, 13, 15, // left
  224. + 16, 17, 18, 18, 17, 19, // top
  225. + 20, 21, 22, 22, 21, 23, // bottom
  226. +]);
  227. </pre>
  228. <p></p><div translate="no" class="threejs_example_container notranslate">
  229. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-indexed.html"></iframe></div>
  230. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-indexed.html" target="_blank">click here to open in a separate window</a>
  231. </div>
  232. <p></p>
  233. <p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> has a <a href="/docs/#api/en/core/BufferGeometry#computeVertexNormals"><code class="notranslate" translate="no">computeVertexNormals</code></a> method for computing normals if you
  234. are not supplying them. Unfortunately,
  235. since positions can not be shared if any other part of a vertex is different,
  236. the results of calling <code class="notranslate" translate="no">computeVertexNormals</code> will generate seams if your
  237. geometry is supposed to connect to itself like a sphere or a cylinder.</p>
  238. <div class="spread">
  239. <div>
  240. <div data-diagram="bufferGeometryCylinder"></div>
  241. </div>
  242. </div>
  243. <p>For the cylinder above the normals were created using <code class="notranslate" translate="no">computeVertexNormals</code>.
  244. If you look closely there is a seam on the cylinder. This is because there
  245. is no way to share the vertices at the start and end of the cylinder since they
  246. require different UVs so the function to compute them has no idea those are
  247. the same vertices to smooth over them. Just a small thing to be aware of.
  248. The solution is to supply your own normals.</p>
  249. <p>We can also use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a> from the start instead of native JavaScript arrays.
  250. The disadvantage to TypedArrays is you must specify their size up front. Of
  251. course that's not that large of a burden but with native arrays we can just
  252. <code class="notranslate" translate="no">push</code> values onto them and look at what size they end up by checking their
  253. <code class="notranslate" translate="no">length</code> at the end. With TypedArrays there is no push function so we need
  254. to do our own bookkeeping when adding values to them.</p>
  255. <p>In this example knowing the length up front is pretty easy since we're using
  256. a big block of static data to start.</p>
  257. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const positions = [];
  258. -const normals = [];
  259. -const uvs = [];
  260. +const numVertices = vertices.length;
  261. +const positionNumComponents = 3;
  262. +const normalNumComponents = 3;
  263. +const uvNumComponents = 2;
  264. +const positions = new Float32Array(numVertices * positionNumComponents);
  265. +const normals = new Float32Array(numVertices * normalNumComponents);
  266. +const uvs = new Float32Array(numVertices * uvNumComponents);
  267. +let posNdx = 0;
  268. +let nrmNdx = 0;
  269. +let uvNdx = 0;
  270. for (const vertex of vertices) {
  271. - positions.push(...vertex.pos);
  272. - normals.push(...vertex.norm);
  273. - uvs.push(...vertex.uv);
  274. + positions.set(vertex.pos, posNdx);
  275. + normals.set(vertex.norm, nrmNdx);
  276. + uvs.set(vertex.uv, uvNdx);
  277. + posNdx += positionNumComponents;
  278. + nrmNdx += normalNumComponents;
  279. + uvNdx += uvNumComponents;
  280. }
  281. geometry.setAttribute(
  282. 'position',
  283. - new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  284. + new THREE.BufferAttribute(positions, positionNumComponents));
  285. geometry.setAttribute(
  286. 'normal',
  287. - new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  288. + new THREE.BufferAttribute(normals, normalNumComponents));
  289. geometry.setAttribute(
  290. 'uv',
  291. - new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  292. + new THREE.BufferAttribute(uvs, uvNumComponents));
  293. geometry.setIndex([
  294. 0, 1, 2, 2, 1, 3, // front
  295. 4, 5, 6, 6, 5, 7, // right
  296. 8, 9, 10, 10, 9, 11, // back
  297. 12, 13, 14, 14, 13, 15, // left
  298. 16, 17, 18, 18, 17, 19, // top
  299. 20, 21, 22, 22, 21, 23, // bottom
  300. ]);
  301. </pre>
  302. <p></p><div translate="no" class="threejs_example_container notranslate">
  303. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-typedarrays.html"></iframe></div>
  304. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-typedarrays.html" target="_blank">click here to open in a separate window</a>
  305. </div>
  306. <p></p>
  307. <p>A good reason to use typedarrays is if you want to dynamically update any
  308. part of the vertices.</p>
  309. <p>I couldn't think of a really good example of dynamically updating the vertices
  310. so I decided to make a sphere and move each quad in and out from the center. Hopefully
  311. it's a useful example.</p>
  312. <p>Here's the code to generate positions and indices for a sphere. The code
  313. is sharing vertices within a quad but it's not sharing vertices between
  314. quads because we want to be able to move each quad separately.</p>
  315. <p>Because I'm lazy I used a small hierarchy of 3 <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects to compute
  316. sphere points. How this works is explained in <a href="optimize-lots-of-objects.html">the article on optimizing lots of objects</a>.</p>
  317. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeSpherePositions(segmentsAround, segmentsDown) {
  318. const numVertices = segmentsAround * segmentsDown * 6;
  319. const numComponents = 3;
  320. const positions = new Float32Array(numVertices * numComponents);
  321. const indices = [];
  322. const longHelper = new THREE.Object3D();
  323. const latHelper = new THREE.Object3D();
  324. const pointHelper = new THREE.Object3D();
  325. longHelper.add(latHelper);
  326. latHelper.add(pointHelper);
  327. pointHelper.position.z = 1;
  328. const temp = new THREE.Vector3();
  329. function getPoint(lat, long) {
  330. latHelper.rotation.x = lat;
  331. longHelper.rotation.y = long;
  332. longHelper.updateMatrixWorld(true);
  333. return pointHelper.getWorldPosition(temp).toArray();
  334. }
  335. let posNdx = 0;
  336. let ndx = 0;
  337. for (let down = 0; down &lt; segmentsDown; ++down) {
  338. const v0 = down / segmentsDown;
  339. const v1 = (down + 1) / segmentsDown;
  340. const lat0 = (v0 - 0.5) * Math.PI;
  341. const lat1 = (v1 - 0.5) * Math.PI;
  342. for (let across = 0; across &lt; segmentsAround; ++across) {
  343. const u0 = across / segmentsAround;
  344. const u1 = (across + 1) / segmentsAround;
  345. const long0 = u0 * Math.PI * 2;
  346. const long1 = u1 * Math.PI * 2;
  347. positions.set(getPoint(lat0, long0), posNdx); posNdx += numComponents;
  348. positions.set(getPoint(lat1, long0), posNdx); posNdx += numComponents;
  349. positions.set(getPoint(lat0, long1), posNdx); posNdx += numComponents;
  350. positions.set(getPoint(lat1, long1), posNdx); posNdx += numComponents;
  351. indices.push(
  352. ndx, ndx + 1, ndx + 2,
  353. ndx + 2, ndx + 1, ndx + 3,
  354. );
  355. ndx += 4;
  356. }
  357. }
  358. return {positions, indices};
  359. }
  360. </pre>
  361. <p>We can then call it like this</p>
  362. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const segmentsAround = 24;
  363. const segmentsDown = 16;
  364. const {positions, indices} = makeSpherePositions(segmentsAround, segmentsDown);
  365. </pre>
  366. <p>Because positions returned are unit sphere positions so they are exactly the same
  367. values we need for normals so we can just duplicated them for the normals.</p>
  368. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const normals = positions.slice();
  369. </pre>
  370. <p>And then we setup the attributes like before</p>
  371. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.BufferGeometry();
  372. const positionNumComponents = 3;
  373. const normalNumComponents = 3;
  374. +const positionAttribute = new THREE.BufferAttribute(positions, positionNumComponents);
  375. +positionAttribute.setUsage(THREE.DynamicDrawUsage);
  376. geometry.setAttribute(
  377. 'position',
  378. + positionAttribute);
  379. geometry.setAttribute(
  380. 'normal',
  381. new THREE.BufferAttribute(normals, normalNumComponents));
  382. geometry.setIndex(indices);
  383. </pre>
  384. <p>I've highlighted a few differences. We save a reference to the position attribute.
  385. We also mark it as dynamic. This is a hint to THREE.js that we're going to be changing
  386. the contents of the attribute often.</p>
  387. <p>In our render loop we update the positions based off their normals every frame.</p>
  388. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const temp = new THREE.Vector3();
  389. ...
  390. for (let i = 0; i &lt; positions.length; i += 3) {
  391. const quad = (i / 12 | 0);
  392. const ringId = quad / segmentsAround | 0;
  393. const ringQuadId = quad % segmentsAround;
  394. const ringU = ringQuadId / segmentsAround;
  395. const angle = ringU * Math.PI * 2;
  396. temp.fromArray(normals, i);
  397. temp.multiplyScalar(THREE.MathUtils.lerp(1, 1.4, Math.sin(time + ringId + angle) * .5 + .5));
  398. temp.toArray(positions, i);
  399. }
  400. positionAttribute.needsUpdate = true;
  401. </pre>
  402. <p>And we set <code class="notranslate" translate="no">positionAttribute.needsUpdate</code> to tell THREE.js to use our changes.</p>
  403. <p></p><div translate="no" class="threejs_example_container notranslate">
  404. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-dynamic.html"></iframe></div>
  405. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-dynamic.html" target="_blank">click here to open in a separate window</a>
  406. </div>
  407. <p></p>
  408. <p>I hope these were useful examples of how to use <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> directly to
  409. make your own geometry and how to dynamically update the contents of a
  410. <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>.</p>
  411. <!-- needed in English only to prevent warning from outdated translations -->
  412. <p><a href="resources/threejs-geometry.svg"></a>
  413. <a href="custom-geometry.html"></a></p>
  414. <p><canvas id="c"></canvas></p>
  415. <script type="module" src="../resources/threejs-custom-buffergeometry.js"></script>
  416. </div>
  417. </div>
  418. </div>
  419. <script src="../resources/prettify.js"></script>
  420. <script src="../resources/lesson.js"></script>
  421. </body></html>