custom-buffergeometry.html 23 KB

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