ソースを参照

update morphtargets-w-colors for r119

Gregg Tavares 5 年 前
コミット
f8cb2099a7

+ 66 - 39
threejs/lessons/threejs-optimize-lots-of-objects-animated.md

@@ -648,55 +648,82 @@ const material = new THREE.MeshBasicMaterial({
 
 Three.js also sorts morphtargets and applies only the highest influences.
 This lets it allow many more morphtargets as long as only a few are used at
-a time. We need to figure out how it sorted the morphtargets and then set
-our color attributes to match. We can do this by first removing all our
-color attributes and then checking the `morphTarget` attributes and and
-seeing which `BufferAttribute` was assigned. Using the name of the
-`BufferAttribute` we can tell which corresponding color attribute needed.
+a time. Unfortunately three.js does not provide any way to know how many
+morph targets will be used nor which attributes the morph targets will be
+assigned. So, we'll have to look into the code and reproduce what it does
+here. If that algorithm changes in three.js we'll need to refactor this
+code.
 
-First we'll go change the names of the morphtarget `BufferAttributes` so they
-are easier to parse later
+First removing all our color attributes. It doesn't matter if we added them
+before as it's safe to remove an attribute that was not previously added.
+Then we'll compute which targets we think three.js will use and finally
+assign those targets to the attribute we think three.js would assign them.
 
 ```js
-// use the first geometry as the base
-// and add all the geometries as morphtargets
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) => {
-  const attribute = geometry.getAttribute('position');
--  const name = `target${ndx}`;
-+  // put the number in front so we can more easily parse it later
-+  const name = `${ndx}target`;
-  attribute.name = name;
-  return attribute;
-});
-
-```
-
-Then we can setup the corresponding color attributes in
-`Object3D.onBeforeRender` which is a property of our `Mesh`. Three.js will call
-it just before rendering giving us a chance to fix things up.
 
-```js
 const mesh = new THREE.Mesh(baseGeometry, material);
 scene.add(mesh);
-+mesh.onBeforeRender = function(renderer, scene, camera, geometry) {
+
++function updateMorphTargets() {
 +  // remove all the color attributes
 +  for (const {name} of colorAttributes) {
-+    geometry.deleteAttribute(name);
++    baseGeometry.deleteAttribute(name);
 +  }
 +
-+  for (let i = 0; i < colorAttributes.length; ++i) {
-+    const attrib = geometry.getAttribute(`morphTarget${i}`);
-+    if (!attrib) {
-+      break;
-+    }
-+    // The name will be something like "2target" as we named it above
-+    // where 2 is the index of the data set
-+    const ndx = parseInt(attrib.name);
-+    const name = `morphColor${i}`;
-+    geometry.setAttribute(name, colorAttributes[ndx].attribute);
-+  }
-+};
++  // three.js provides no way to query this so we have to guess and hope it doesn't change.
++  const maxInfluences = 8;
++
++  // three provides no way to query which morph targets it will use
++  // nor which attributes it will assign them to so we'll guess.
++  // If the algorithm in three.js changes we'll need to refactor this.
++  mesh.morphTargetInfluences
++    .map((influence, i) => [i, influence])            // map indices to influence
++    .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))  // sort by highest influence first
++    .slice(0, maxInfluences)                          // keep only top influences
++    .sort((a, b) => a[0] - b[0])                      // sort by index
++    .filter(a => !!a[1])                              // remove no influence entries
++    .forEach(([ndx], i) => {                          // assign the attributes
++      const name = `morphColor${i}`;
++      baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
++    });
++}
+```
+
+We'll return this function from our `loadAll` function. This way we don't
+need to leak any variables.
+
+```js
+async function loadAll() {
+  ...
+
++  return updateMorphTargets;
+}
+
++// use a no-op update function until the data is ready
++let updateMorphTargets = () => {};
+-loadAll();
++loadAll().then(fn => {
++  updateMorphTargets = fn;
++});
+```
+
+And finally we need to call `updateMorphTargets` after we've let the values
+be updated by the tween manager and before rendering.
+
+```js
+function render() {
+
+  ...
+
+  if (tweenManager.update()) {
+    requestRenderIfNotRequested();
+  }
+
++  updateMorphTargets();
+
+  controls.update();
+  renderer.render(scene, camera);
+}
 ```
 
 And with that we should have the colors animating as well as the boxes.

+ 34 - 19
threejs/threejs-lots-of-objects-morphtargets-w-colors.html

@@ -367,24 +367,6 @@ function main() {
     };
     const mesh = new THREE.Mesh(baseGeometry, material);
     scene.add(mesh);
-    mesh.onBeforeRender = function(renderer, scene, camera, geometry) {
-      // remove all the color attributes
-      for (const {name} of colorAttributes) {
-        geometry.deleteAttribute(name);
-      }
-
-      for (let i = 0; i < colorAttributes.length; ++i) {
-        const attrib = geometry.getAttribute(`morphTarget${i}`);
-        if (!attrib) {
-          break;
-        }
-        // The name will be something like "2target" as we named it above
-        // where 2 is the index of the data set
-        const ndx = parseInt(attrib.name);
-        const name = `morphColor${i}`;
-        geometry.setAttribute(name, colorAttributes[ndx].attribute);
-      }
-    };
 
     // show the selected data, hide the rest
     function showFileInfo(fileInfos, fileInfo) {
@@ -417,8 +399,39 @@ function main() {
     });
     // show the first set of data
     showFileInfo(fileInfos, fileInfos[0]);
+
+    function updateMorphTargets() {
+      // remove all the color attributes
+      for (const {name} of colorAttributes) {
+        baseGeometry.deleteAttribute(name);
+      }
+
+      // three.js provides no way to query this so we have to guess and hope it doesn't change.
+      const maxInfluences = 8;
+
+      // three provides no way to query which morph targets it will use
+      // nor which attributes it will assign them to so we'll guess.
+      // If the algorithm in three.js changes we'll need to refactor this.
+      mesh.morphTargetInfluences
+        .map((influence, i) => [i, influence])            // map indices to influence
+        .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))  // sort by highest influence first
+        .slice(0, maxInfluences)                          // keep only top influences
+        .sort((a, b) => a[0] - b[0])                      // sort by index
+        .filter(a => !!a[1])                              // remove no influence entries
+        .forEach(([ndx], i) => {                          // assign the attributes
+          const name = `morphColor${i}`;
+          baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
+        });
+    }
+
+    return updateMorphTargets;
   }
-  loadAll();
+
+  // use a no-op update function until the data is ready
+  let updateMorphTargets = () => {};
+  loadAll().then(fn => {
+    updateMorphTargets = fn;
+  });
 
   function resizeRendererToDisplaySize(renderer) {
     const canvas = renderer.domElement;
@@ -446,6 +459,8 @@ function main() {
       requestRenderIfNotRequested();
     }
 
+    updateMorphTargets();
+
     controls.update();
     renderer.render(scene, camera);
   }