From 986a694af277fcceda0dd41ab0ee3603af99cac8 Mon Sep 17 00:00:00 2001
From: Marshall Quander <marshall@quander.me>
Date: Tue, 3 Jul 2018 19:15:04 -0700
Subject: [PATCH] Only inflate entities that need to exist

If we know ahead of time which entities will have components on
them or a template attached to them, then we can identify scene
graph subtrees which can act as leaves of the entity tree and
refrain from inflating extra entities underneath them.
---
 src/components/gltf-model-plus.js |  51 ++++--
 src/hub.html                      | 270 ++++++++++++++++--------------
 2 files changed, 189 insertions(+), 132 deletions(-)

diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js
index 686245b49..8056ce8cf 100644
--- a/src/components/gltf-model-plus.js
+++ b/src/components/gltf-model-plus.js
@@ -74,7 +74,36 @@ function cloneGltf(gltf) {
   return clone;
 }
 
+const getBehaviorNodes = function(root, templates) {
+  const nodes = new Set();
+  if (root.userData.components || root.name in templates) {
+    nodes.add(root);
+  }
+  for (const child of root.children) {
+    for (const other of getBehaviorNodes(child, templates)) {
+      nodes.add(other);
+    }
+  }
+  return nodes;
+};
+
+const markLeaves = function(root, behaviorNodes) {
+  root.userData.leaf = !behaviorNodes.has(root);
+  for (const child of root.children) {
+    if (!markLeaves(child, behaviorNodes)) {
+      root.userData.leaf = false;
+    }
+  }
+  return root.userData.leaf;
+};
+
 const inflateEntities = function(parentEl, node, gltfPath) {
+  if (node.userData.leaf) {
+    // we don't need an entity for this node
+    console.log("Not doing ", node);
+    return null;
+  }
+
   // setObject3D mutates the node's parent, so we have to copy
   const children = node.children.slice(0);
 
@@ -145,8 +174,8 @@ const inflateEntities = function(parentEl, node, gltfPath) {
   return el;
 };
 
-function attachTemplate(root, { selector, templateRoot }) {
-  const targetEls = root.querySelectorAll(selector);
+function attachTemplate(root, name, templateRoot) {
+  const targetEls = root.querySelectorAll("." + name);
   for (const el of targetEls) {
     const root = templateRoot.cloneNode(true);
     // Merge root element attributes with the target element
@@ -217,12 +246,10 @@ AFRAME.registerComponent("gltf-model-plus", {
 
   loadTemplates() {
     this.templates = [];
-    this.el.querySelectorAll(":scope > template").forEach(templateEl =>
-      this.templates.push({
-        selector: templateEl.getAttribute("data-selector"),
-        templateRoot: document.importNode(templateEl.firstElementChild || templateEl.content.firstElementChild, true)
-      })
-    );
+    this.el.querySelectorAll(":scope > template").forEach(templateEl => {
+      const root = document.importNode(templateEl.firstElementChild || templateEl.content.firstElementChild, true);
+      this.templates[templateEl.getAttribute("data-name")] = root;
+    });
   },
 
   async applySrc(src) {
@@ -260,17 +287,21 @@ AFRAME.registerComponent("gltf-model-plus", {
       this.el.setObject3D("mesh", this.model);
 
       if (this.data.inflate) {
+        const behaviorNodes = getBehaviorNodes(this.model, this.templates);
+        markLeaves(this.model, behaviorNodes);
         this.inflatedEl = inflateEntities(this.el, this.model, gltfPath);
         // TODO: Still don't fully understand the lifecycle here and how it differs between browsers, we should dig in more
         // Wait one tick for the appended custom elements to be connected before attaching templates
         await nextTick();
         if (src != this.lastSrc) return; // TODO: there must be a nicer pattern for this
-        this.templates.forEach(attachTemplate.bind(null, this.el));
+        for (const name in this.templates) {
+          attachTemplate(this.el, name, this.templates[name]);
+        }
       }
 
       this.el.emit("model-loaded", { format: "gltf", model: this.model });
     } catch (e) {
-      console.error("Failed to load glTF model", e.message, this);
+      console.error("Failed to load glTF model", e, this);
       this.el.emit("model-error", { format: "gltf", src });
     }
   },
diff --git a/src/hub.html b/src/hub.html
index f71396377..a6f61a120 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -100,11 +100,11 @@
                     <a-entity class="right-controller"></a-entity>
 
                     <a-entity class="model" gltf-model-plus="inflate: true">
-                        <template data-selector=".RootScene">
+                        <template data-name="RootScene">
                             <a-entity ik-controller hand-pose__left hand-pose__right animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity>
                         </template>
 
-                        <template data-selector=".Neck">
+                        <template data-name="Neck">
                             <a-entity>
                                 <a-entity
                                    class="nametag"
@@ -116,7 +116,7 @@
                             </a-entity>
                         </template>
 
-                        <template data-selector=".Chest">
+                        <template data-name="Chest">
                             <a-entity>
                               <a-entity personal-space-invader="radius: 0.2; useMaterial: true;" bone-visibility> </a-entity>
                               <a-entity billboard>
@@ -126,29 +126,44 @@
                             </a-entity>
                         </template>
 
-                        <template data-selector=".Head">
+                        <template data-name="Head">
                             <a-entity
                                 networked-audio-source
                                 networked-audio-analyser
                                 personal-space-invader="radius: 0.15; useMaterial: true;"
                                 bone-visibility
                             >
-                            <a-cylinder
-                                static-body
-                                radius="0.13"
-                                height="0.2"
-                                position="0 0.07 0.05"
-                                visible="false"
-                            ></a-cylinder>
+                              <a-cylinder
+                                  static-body
+                                  radius="0.13"
+                                  height="0.2"
+                                  position="0 0.07 0.05"
+                                  visible="false"
+                              ></a-cylinder>
                             </a-entity>
                         </template>
 
-                        <template data-selector=".LeftHand">
-                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        <!-- needs to exist for the benefit of the personal space calculator  -->
+                        <template data-name="Bot_Skinned">
+                          <a-entity></a-entity>
                         </template>
 
-                        <template data-selector=".RightHand">
-                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        <!-- needs to exist for the benefit of the IK calculation  -->
+                        <template data-name="LeftEye">
+                          <a-entity></a-entity>
+                        </template>
+
+                        <!-- needs to exist for the benefit of the IK calculation  -->
+                        <template data-name="RightEye">
+                          <a-entity></a-entity>
+                        </template>
+
+                        <template data-name="LeftHand">
+                          <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        </template>
+
+                        <template data-name="RightHand">
+                          <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
                         </template>
                     </a-entity>
                 </a-entity>
@@ -230,14 +245,14 @@
             ></a-mixin>
 
             <a-mixin id="controller-super-hands"
-                super-hands="
-                    colliderEvent: collisions; colliderEventProperty: els;
-                    colliderEndEvent: collisions; colliderEndEventProperty: clearedEls;
-                    grabStartButtons: hand_grab; grabEndButtons: hand_release;
-                    stretchStartButtons: hand_grab; stretchEndButtons: hand_release;
-                    dragDropStartButtons: hand_grab; dragDropEndButtons: hand_release;"
-                collision-filter="collisionForces: false"
-                physics-collider
+                     super-hands="
+                         colliderEvent: collisions; colliderEventProperty: els;
+                         colliderEndEvent: collisions; colliderEndEventProperty: clearedEls;
+                         grabStartButtons: hand_grab; grabEndButtons: hand_release;
+                         stretchStartButtons: hand_grab; stretchEndButtons: hand_release;
+                         dragDropStartButtons: hand_grab; dragDropEndButtons: hand_release;"
+                     collision-filter="collisionForces: false"
+                     physics-collider
             ></a-mixin>
         </a-assets>
 
@@ -283,35 +298,35 @@
             networked-avatar
             cardboard-controls
         >
-            <a-entity
-                id="player-hud"
-                hud-controller="head: #player-camera;"
-                vr-mode-toggle-visibility
-                vr-mode-toggle-playing__hud-controller
-            >
-                <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0">
-                    <a-rounded height="0.13" width="0.48" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Enable Bubble; activeTooltipText: Disable Bubble; image: #bubble-off; hoverImage: #bubble-off-hover; activeImage: #bubble-on; activeHoverImage: #bubble-on-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud bubble" material="alphaTest:0.1;"></a-image>
-                    <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
-                        <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
-                    </a-rounded>
-                </a-entity>
+          <a-entity
+              id="player-hud"
+              hud-controller="head: #player-camera;"
+              vr-mode-toggle-visibility
+              vr-mode-toggle-playing__hud-controller
+          >
+            <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0">
+              <a-rounded height="0.13" width="0.48" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Enable Bubble; activeTooltipText: Disable Bubble; image: #bubble-off; hoverImage: #bubble-off-hover; activeImage: #bubble-on; activeHoverImage: #bubble-on-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud bubble" material="alphaTest:0.1;"></a-image>
+              <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
+                <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
+              </a-rounded>
             </a-entity>
-
+          </a-entity>
+
+          <a-entity
+              id="player-camera"
+              class="camera"
+              camera
+              position="0 1.6 0"
+              personal-space-bubble="radius: 0.4"
+              pitch-yaw-rotator
+          >
             <a-entity
-                id="player-camera"
-                class="camera"
-                camera
-                position="0 1.6 0"
-                personal-space-bubble="radius: 0.4"
-                pitch-yaw-rotator
-            >
-                <a-entity
-                    id="gaze-teleport"
-                    position = "0.15 0 0"
-                    teleport-controls="
+                id="gaze-teleport"
+                position = "0.15 0 0"
+                teleport-controls="
                     cameraRig: #player-rig;
                     teleportOrigin: #player-camera;
                     button: gaze-teleport_;
@@ -320,84 +335,95 @@
                     incrementalDrawMs: 600;
                     hitOpacity: 0.3;
                     missOpacity: 0.2;"
-                ></a-entity>
-                <a-entity id="player-camera-reverse-z" rotation="0 180 0"></a-entity>
-            </a-entity>
+            ></a-entity>
+            <a-entity id="player-camera-reverse-z" rotation="0 180 0"></a-entity>
+          </a-entity>
+
+          <a-entity
+              id="player-left-controller"
+              class="left-controller"
+              hand-controls2="left"
+              tracked-controls
+              teleport-controls="
+                  cameraRig: #player-rig;
+                  teleportOrigin: #player-camera;
+                  button: cursor-teleport_;
+                  collisionEntities: [nav-mesh];
+                  drawIncrementally: true;
+                  incrementalDrawMs: 600;
+                  hitOpacity: 0.3;
+                  missOpacity: 0.2;"
+              haptic-feedback
+              body="type: static; shape: none;"
+              mixin="controller-super-hands"
+              controls-shape-offset
+          >
+            <a-entity id="player-left-controller-reverse-z" rotation="0 180 0"></a-entity>
+          </a-entity>
+
+          <a-entity
+              id="player-right-controller"
+              class="right-controller"
+              hand-controls2="right"
+              tracked-controls
+              teleport-controls="
+                  cameraRig: #player-rig;
+                  teleportOrigin: #player-camera;
+                  button: cursor-teleport_;
+                  collisionEntities: [nav-mesh];
+                  drawIncrementally: true;
+                  incrementalDrawMs: 600;
+                  hitOpacity: 0.3;
+                  missOpacity: 0.2;"
+              haptic-feedback
+              body="type: static; shape: none;"
+              mixin="controller-super-hands"
+              controls-shape-offset
+          >
+            <a-entity id="player-right-controller-reverse-z" rotation="0 180 0"></a-entity>
+          </a-entity>
+
+          <a-entity gltf-model-plus="inflate: true;"
+                    class="model">
+            <template data-name="RootScene">
+              <a-entity
+                  ik-controller
+                  animation-mixer
+                  hand-pose__left
+                  hand-pose__right
+                  hand-pose-controller__left="networkedAvatar:#player-rig;eventSrc:#player-left-controller"
+                  hand-pose-controller__right="networkedAvatar:#player-rig;eventSrc:#player-right-controller"
+              ></a-entity>
+            </template>
 
-            <a-entity
-                id="player-left-controller"
-                class="left-controller"
-                hand-controls2="left"
-                tracked-controls
-                teleport-controls="
-                    cameraRig: #player-rig;
-                    teleportOrigin: #player-camera;
-                    button: cursor-teleport_;
-                    collisionEntities: [nav-mesh];
-                    drawIncrementally: true;
-                    incrementalDrawMs: 600;
-                    hitOpacity: 0.3;
-                    missOpacity: 0.2;"
-                haptic-feedback
-                body="type: static; shape: none;"
-                mixin="controller-super-hands"
-                controls-shape-offset
-            >
-                <a-entity id="player-left-controller-reverse-z" rotation="0 180 0"></a-entity>
-            </a-entity>
+            <template data-name="Neck">
+              <a-entity>
+                <a-entity class="nametag" visible="false" text ></a-entity>
+              </a-entity>
+            </template>
 
-            <a-entity
-                id="player-right-controller"
-                class="right-controller"
-                hand-controls2="right"
-                tracked-controls
-                teleport-controls="
-                    cameraRig: #player-rig;
-                    teleportOrigin: #player-camera;
-                    button: cursor-teleport_;
-                    collisionEntities: [nav-mesh];
-                    drawIncrementally: true;
-                    incrementalDrawMs: 600;
-                    hitOpacity: 0.3;
-                    missOpacity: 0.2;"
-                haptic-feedback
-                body="type: static; shape: none;"
-                mixin="controller-super-hands"
-                controls-shape-offset
-            >
-                <a-entity id="player-right-controller-reverse-z" rotation="0 180 0"></a-entity>
-            </a-entity>
+            <!-- needs to exist for the benefit of the IK calculation  -->
+            <template data-name="LeftEye">
+              <a-entity></a-entity>
+            </template>
 
-            <a-entity gltf-model-plus="inflate: true;"
-                      class="model">
-                <template data-selector=".RootScene">
-                    <a-entity
-                        ik-controller
-                        animation-mixer
-                        hand-pose__left
-                        hand-pose__right
-                        hand-pose-controller__left="networkedAvatar:#player-rig;eventSrc:#player-left-controller"
-                        hand-pose-controller__right="networkedAvatar:#player-rig;eventSrc:#player-right-controller"
-                    ></a-entity>
-                </template>
-
-                <template data-selector=".Neck">
-                    <a-entity>
-                        <a-entity class="nametag" visible="false" text ></a-entity>
-                    </a-entity>
-                </template>
+            <!-- needs to exist for the benefit of the IK calculation  -->
+            <template data-name="RightEye">
+              <a-entity></a-entity>
+            </template>
 
-                <template data-selector=".Head">
-                    <a-entity visible="false" bone-visibility></a-entity>
-                </template>
 
-                <template data-selector=".LeftHand">
-                    <a-entity bone-visibility></a-entity>
-                </template>
+            <template data-name="Head">
+              <a-entity visible="false" bone-visibility></a-entity>
+            </template>
 
-                <template data-selector=".RightHand">
-                    <a-entity bone-visibility></a-entity>
-                </template>
+            <template data-name="LeftHand">
+              <a-entity bone-visibility></a-entity>
+            </template>
+
+            <template data-name="RightHand">
+              <a-entity bone-visibility></a-entity>
+            </template>
 
             </a-entity>
         </a-entity>
-- 
GitLab