From 64ffc11def6abc1c039bca3ee7cb55b4ca2078fa Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Wed, 27 Sep 2023 14:23:48 +0200 Subject: [PATCH] Fix object menus transforms --- src/bit-components.js | 7 +- src/bit-systems/link-hover-menu.ts | 52 ++-------- src/bit-systems/media-loading.ts | 38 +------- .../object-menu-transform-system.ts | 49 ++++++++++ src/bit-systems/object-menu.ts | 95 ++++++++----------- src/systems/hubs-systems.ts | 3 + src/utils/jsx-entity.ts | 7 +- src/utils/three-utils.js | 33 +++++++ 8 files changed, 150 insertions(+), 134 deletions(-) create mode 100644 src/bit-systems/object-menu-transform-system.ts diff --git a/src/bit-components.js b/src/bit-components.js index e81d24954a..bc95c86773 100644 --- a/src/bit-components.js +++ b/src/bit-components.js @@ -280,7 +280,9 @@ export const ObjectMenu = defineComponent({ rotateButtonRef: Types.eid, mirrorButtonRef: Types.eid, scaleButtonRef: Types.eid, - targetRef: Types.eid + targetRef: Types.eid, + handlingTargetRef: Types.eid, + flags: Types.ui8 }); // TODO: Store this data elsewhere, since only one or two will ever exist. export const LinkHoverMenu = defineComponent({ @@ -381,3 +383,6 @@ export const LinearScale = defineComponent({ export const Quack = defineComponent(); export const TrimeshTag = defineComponent(); export const HeightFieldTag = defineComponent(); +export const ObjectMenuTransform = defineComponent({ + targetObjectRef: Types.eid +}); diff --git a/src/bit-systems/link-hover-menu.ts b/src/bit-systems/link-hover-menu.ts index 5c7ace3d75..b22d1b00c7 100644 --- a/src/bit-systems/link-hover-menu.ts +++ b/src/bit-systems/link-hover-menu.ts @@ -1,5 +1,4 @@ -import { Not, defineQuery, entityExists } from "bitecs"; -import { Matrix4, Vector3 } from "three"; +import { Not, addComponent, defineQuery, entityExists, removeComponent } from "bitecs"; import type { HubsWorld } from "../app"; import { Link, @@ -8,7 +7,8 @@ import { TextTag, Interacted, LinkHoverMenuItem, - LinkInitializing + LinkInitializing, + ObjectMenuTransform } from "../bit-components"; import { findAncestorWithComponent, findChildWithComponent } from "../utils/bit-utils"; import { hubIdFromUrl } from "../utils/media-url-utils"; @@ -16,7 +16,6 @@ import { Text as TroikaText } from "troika-three-text"; import { handleExitTo2DInterstitial } from "../utils/vr-interstitial"; import { changeHub } from "../change-hub"; import { EntityID } from "../utils/networking-types"; -import { setMatrixWorld } from "../utils/three-utils"; import { LinkType } from "../inflators/link"; const menuQuery = defineQuery([LinkHoverMenu]); @@ -89,43 +88,6 @@ async function handleLinkClick(world: HubsWorld, button: EntityID) { } } -const _moveTargetPos = new Vector3(); -const _lookAtTargetPos = new Vector3(); -const _objectPos = new Vector3(); -const _mat4 = new Matrix4(); - -// Move the menu object to target object position but a little bit closer -// to the camera and make the menu object look at the camera. -// TODO: Similar code in object-menu system. Expose as util and reuse? -function moveToTarget(world: HubsWorld, menu: EntityID) { - const menuObj = world.eid2obj.get(menu)!; - - const targetObj = world.eid2obj.get(LinkHoverMenu.targetObjectRef[menu])!; - targetObj.updateMatrices(); - - // TODO: Remove the dependency with AFRAME - const camera = AFRAME.scenes[0].systems["hubs-systems"].cameraSystem.viewingCamera; - camera.updateMatrices(); - - _moveTargetPos.setFromMatrixPosition(targetObj.matrixWorld); - _lookAtTargetPos.setFromMatrixPosition(camera.matrixWorld); - - // Place the menu object a little bit closer to the camera in the scene - _objectPos - .copy(_lookAtTargetPos) - .sub(_moveTargetPos) - .normalize() - // TODO: 0.5 is an arbitrary number. 0.5 might be too small for - // huge target object. Using bounding box may be safer? - // TODO: What if camera is between the menu and the target object? - .multiplyScalar(0.5) - .add(_moveTargetPos); - - _mat4.copy(camera.matrixWorld).setPosition(_objectPos); - setMatrixWorld(menuObj, _mat4); - menuObj.lookAt(_lookAtTargetPos); -} - function updateButtonText(world: HubsWorld, menu: EntityID, button: EntityID) { const text = findChildWithComponent(world, TextTag, button)!; const textObj = world.eid2obj.get(text)! as TroikaText; @@ -157,6 +119,13 @@ function flushToObject3Ds(world: HubsWorld, menu: EntityID, frozen: boolean, for const target = LinkHoverMenu.targetObjectRef[menu]; const visible = !!target && !frozen; + if (visible) { + addComponent(world, ObjectMenuTransform, menu); + ObjectMenuTransform.targetObjectRef[menu] = target; + } else { + removeComponent(world, ObjectMenuTransform, menu); + } + const obj = world.eid2obj.get(menu)!; obj.visible = visible; @@ -182,7 +151,6 @@ export function linkHoverMenuSystem(world: HubsWorld, sceneIsFrozen: boolean) { updateLinkMenuTarget(world, menu, sceneIsFrozen); const currTarget = LinkHoverMenu.targetObjectRef[menu]; if (currTarget) { - moveToTarget(world, menu); clickedMenuItemQuery(world).forEach(eid => handleLinkClick(world, eid)); } flushToObject3Ds(world, menu, sceneIsFrozen, prevTarget !== currTarget); diff --git a/src/bit-systems/media-loading.ts b/src/bit-systems/media-loading.ts index 9195112acb..6becfc52b8 100644 --- a/src/bit-systems/media-loading.ts +++ b/src/bit-systems/media-loading.ts @@ -1,5 +1,5 @@ import { addComponent, defineQuery, enterQuery, exitQuery, hasComponent, removeComponent, removeEntity } from "bitecs"; -import { Box3, Euler, Vector3 } from "three"; +import { Box3, Vector3 } from "three"; import { HubsWorld } from "../app"; import { GLTFModel, @@ -17,7 +17,6 @@ import { ErrorObject } from "../prefabs/error-object"; import { LoadingObject } from "../prefabs/loading-object"; import { animate } from "../utils/animate"; import { setNetworkedDataWithoutRoot } from "../utils/assign-network-ids"; -import { computeObjectAABB } from "../utils/auto-box-collider"; import { crClearTimeout, crNextFrame, crTimeout } from "../utils/coroutine"; import { ClearFunction, JobRunner, withRollback } from "../utils/coroutine-utils"; import { easeOutQuadratic } from "../utils/easing"; @@ -30,37 +29,7 @@ import { loadAudio } from "../utils/load-audio"; import { loadHtml } from "../utils/load-html"; import { MediaType, mediaTypeName, resolveMediaInfo } from "../utils/media-utils"; import { EntityID } from "../utils/networking-types"; - -const getBox = (() => { - const rotation = new Euler(); - return (world: HubsWorld, eid: EntityID, rootEid: EntityID, worldSpace?: boolean) => { - const box = new Box3(); - const obj = world.eid2obj.get(eid)!; - const rootObj = world.eid2obj.get(rootEid)!; - - rotation.copy(obj.rotation); - obj.rotation.set(0, 0, 0); - obj.updateMatrices(true, true); - rootObj.updateMatrices(true, true); - rootObj.updateMatrixWorld(true); - - computeObjectAABB(rootObj, box, false); - - if (!box.isEmpty()) { - if (!worldSpace) { - obj.worldToLocal(box.min); - obj.worldToLocal(box.max); - } - obj.rotation.copy(rotation); - obj.matrixNeedsUpdate = true; - } - - rootObj.matrixWorldNeedsUpdate = true; - rootObj.updateMatrices(); - - return box; - }; -})(); +import { getBox } from "../utils/three-utils"; export function* waitForMediaLoaded(world: HubsWorld, eid: EntityID) { while (hasComponent(world, MediaLoader, eid)) { @@ -234,6 +203,7 @@ function* loadMedia(world: HubsWorld, eid: EntityID) { } const tmpVector = new Vector3(); +const box = new Box3(); function* loadAndAnimateMedia(world: HubsWorld, eid: EntityID, clearRollbacks: ClearFunction) { if (MediaLoader.flags[eid] & MEDIA_LOADER_FLAGS.IS_OBJECT_MENU_TARGET) { addComponent(world, ObjectMenuTarget, eid); @@ -251,7 +221,7 @@ function* loadAndAnimateMedia(world: HubsWorld, eid: EntityID, clearRollbacks: C if (media) { if (hasComponent(world, MediaLoaded, media)) { - const box = getBox(world, eid, media); + getBox(world, eid, media, box); addComponent(world, MediaContentBounds, eid); box.getSize(tmpVector); MediaContentBounds.bounds[eid].set(tmpVector.toArray()); diff --git a/src/bit-systems/object-menu-transform-system.ts b/src/bit-systems/object-menu-transform-system.ts new file mode 100644 index 0000000000..51beef69ee --- /dev/null +++ b/src/bit-systems/object-menu-transform-system.ts @@ -0,0 +1,49 @@ +import { defineQuery } from "bitecs"; +import { HubsWorld } from "../app"; +import { ObjectMenuTransform } from "../bit-components"; +import { EntityID } from "../utils/networking-types"; +import { Box3, Matrix4, Quaternion, Sphere, Vector3 } from "three"; +import { getBox, setMatrixWorld } from "../utils/three-utils"; + +const _vec3_1 = new Vector3(); +const _vec3_2 = new Vector3(); +const _quat = new Quaternion(); +const _mat4 = new Matrix4(); +const aabb = new Box3(); +const sphere = new Sphere(); + +function moveToTarget(world: HubsWorld, menu: EntityID) { + const targetEid = ObjectMenuTransform.targetObjectRef[menu]; + const targetObj = world.eid2obj.get(targetEid); + if (!targetObj) return; + + getBox(world, targetEid, targetEid, aabb, true); + aabb.getBoundingSphere(sphere); + + // Keeps world scale (1, 1, 1) because + // a menu object is a child of a target object + // and the target object's scale can be changed. + // Another option may be making the menu object + // a sibling of the target object. + _mat4.copy(targetObj.matrixWorld); + _mat4.decompose(_vec3_1, _quat, _vec3_2); + _vec3_2.set(1.0, 1.0, 1.0); + _mat4.compose(sphere.center, _quat, _vec3_2); + + const menuObj = world.eid2obj.get(menu)!; + setMatrixWorld(menuObj, _mat4); + + // TODO: Remove the dependency with AFRAME + const camera = AFRAME.scenes[0].systems["hubs-systems"].cameraSystem.viewingCamera; + camera.updateMatrices(); + menuObj.lookAt(sphere.center.setFromMatrixPosition(camera.matrixWorld)); + menuObj.translateZ(sphere.radius); +} + +const menuQuery = defineQuery([ObjectMenuTransform]); + +export function objectMenuTransformSystem(world: HubsWorld) { + menuQuery(world).forEach(menu => { + moveToTarget(world, menu); + }); +} diff --git a/src/bit-systems/object-menu.ts b/src/bit-systems/object-menu.ts index 31bfb5d6db..7a8f56f9be 100644 --- a/src/bit-systems/object-menu.ts +++ b/src/bit-systems/object-menu.ts @@ -1,8 +1,9 @@ -import { addComponent, defineQuery, enterQuery, entityExists, exitQuery, hasComponent } from "bitecs"; -import { Matrix4, Quaternion, Vector3 } from "three"; +import { addComponent, defineQuery, enterQuery, entityExists, exitQuery, hasComponent, removeComponent } from "bitecs"; +import { Matrix4, Vector3 } from "three"; import type { HubsWorld } from "../app"; import { EntityStateDirty, + ObjectMenuTransform, HeldRemoteRight, HoveredRemoteRight, Interacted, @@ -27,17 +28,13 @@ import { canPin, setPinned } from "../utils/bit-pinning-helper"; // Working variables. const _vec3_1 = new Vector3(); const _vec3_2 = new Vector3(); -const _quat = new Quaternion(); const _mat4 = new Matrix4(); let scalingHandler: ScalingHandler | null = null; -// Needs to remember rotating/scaling entity id because -// rotation/scaling are operated by dragging so that -// rotation/scaling operation can continue even if the cursor -// hover off the target entity. -// TODO: Should this data stored in Component? -let handlingTargetEid: EntityID = 0; +export const enum ObjectMenuFlags { + Visible = 1 << 0 +} function clicked(world: HubsWorld, eid: EntityID) { return hasComponent(world, Interacted, eid); @@ -49,7 +46,10 @@ function objectMenuTarget(world: HubsWorld, menu: EntityID, sceneIsFrozen: boole } const target = hoveredQuery(world).map(eid => findAncestorWithComponent(world, ObjectMenuTarget, eid))[0]; - if (target) return target; + if (target) { + ObjectMenu.flags[menu] |= ObjectMenuFlags.Visible; + return target; + } if (entityExists(world, ObjectMenu.targetRef[menu])) { return ObjectMenu.targetRef[menu]; @@ -58,39 +58,10 @@ function objectMenuTarget(world: HubsWorld, menu: EntityID, sceneIsFrozen: boole return 0; } -function moveToTarget(world: HubsWorld, menu: EntityID) { - const targetObj = world.eid2obj.get(ObjectMenu.targetRef[menu])!; - targetObj.updateMatrices(); - - // TODO: position the menu more carefully... - // For example, if a menu object is just placed at a target - // object's position the menu object can be hidden by a large - // target object or the menu object looks too small for a far - // target object. - _mat4.copy(targetObj.matrixWorld); - - // Keeps world scale (1, 1, 1) because - // a menu object is a child of a target object - // and the target object's scale can be changed. - // Another option may be making the menu object - // a sibling of the target object. - _mat4.decompose(_vec3_1, _quat, _vec3_2); - _vec3_2.set(1.0, 1.0, 1.0); - _mat4.compose(_vec3_1, _quat, _vec3_2); - - const menuObj = world.eid2obj.get(menu)!; - setMatrixWorld(menuObj, _mat4); - - // TODO: Remove the dependency with AFRAME - const camera = AFRAME.scenes[0].systems["hubs-systems"].cameraSystem.viewingCamera; - camera.updateMatrices(); - menuObj.lookAt(_vec3_1.setFromMatrixPosition(camera.matrixWorld)); -} - // TODO: startRotation/Scaling() and stopRotation/Scaling() are // temporary implementation that rely on the old systems. // They should be rewritten more elegantly with bitecs. -function startRotation(world: HubsWorld, targetEid: EntityID) { +function startRotation(world: HubsWorld, menuEid: EntityID, targetEid: EntityID) { if (hasComponent(world, Networked, targetEid)) { takeOwnership(world, targetEid); } @@ -101,27 +72,29 @@ function startRotation(world: HubsWorld, targetEid: EntityID) { transformSystem.startTransform(world.eid2obj.get(targetEid)!, world.eid2obj.get(rightCursorEid)!, { mode: TRANSFORM_MODE.CURSOR }); - handlingTargetEid = targetEid; + ObjectMenu.handlingTargetRef[menuEid] = targetEid; } -function stopRotation(world: HubsWorld) { +function stopRotation(world: HubsWorld, menuEid: EntityID) { // TODO: More proper handling in case the target entity is already removed. // In the worst scenario the entity has been already recycled at this moment // and this code doesn't handled such a case correctly. // We may refactor when we will reimplement the object menu system // by removing A-Frame dependency. + const handlingTargetEid = ObjectMenu.handlingTargetRef[menuEid]; if (entityExists(world, handlingTargetEid) && hasComponent(world, Networked, handlingTargetEid)) { addComponent(world, EntityStateDirty, handlingTargetEid); } const transformSystem = APP.scene!.systems["transform-selected-object"]; transformSystem.stopTransform(); - handlingTargetEid = 0; + ObjectMenu.handlingTargetRef[menuEid] = 0; } -function startScaling(world: HubsWorld, targetEid: EntityID) { +function startScaling(world: HubsWorld, menuEid: EntityID, targetEid: EntityID) { if (hasComponent(world, Networked, targetEid)) { takeOwnership(world, targetEid); } + // TODO: Don't use any // TODO: Remove the dependency with AFRAME const transformSystem = (AFRAME as any).scenes[0].systems["transform-selected-object"]; @@ -131,17 +104,18 @@ function startScaling(world: HubsWorld, targetEid: EntityID) { scalingHandler = new ScalingHandler(world.eid2obj.get(targetEid), transformSystem); scalingHandler!.objectToScale = world.eid2obj.get(targetEid); scalingHandler!.startScaling(world.eid2obj.get(rightCursorEid)); - handlingTargetEid = targetEid; + ObjectMenu.handlingTargetRef[menuEid] = targetEid; } -function stopScaling(world: HubsWorld) { +function stopScaling(world: HubsWorld, menuEid: EntityID) { + const handlingTargetEid = ObjectMenu.handlingTargetRef[menuEid]; if (entityExists(world, handlingTargetEid) && hasComponent(world, Networked, handlingTargetEid)) { addComponent(world, EntityStateDirty, handlingTargetEid); } const rightCursorEid = anyEntityWith(world, RemoteRight)!; scalingHandler!.endScaling(world.eid2obj.get(rightCursorEid)); scalingHandler = null; - handlingTargetEid = 0; + ObjectMenu.handlingTargetRef[menuEid] = 0; } function openLink(world: HubsWorld, eid: EntityID) { @@ -187,6 +161,7 @@ function handleClicks(world: HubsWorld, menu: EntityID, hubChannel: HubChannel) } else if (clicked(world, ObjectMenu.cameraTrackButtonRef[menu])) { console.log("Clicked track"); } else if (clicked(world, ObjectMenu.removeButtonRef[menu])) { + ObjectMenu.flags[menu] &= ~ObjectMenuFlags.Visible; deleteTheDeletableAncestor(world, ObjectMenu.targetRef[menu]); } else if (clicked(world, ObjectMenu.dropButtonRef[menu])) { console.log("Clicked drop"); @@ -208,10 +183,12 @@ function handleClicks(world: HubsWorld, menu: EntityID, hubChannel: HubChannel) function handleHeldEnter(world: HubsWorld, eid: EntityID, menuEid: EntityID) { switch (eid) { case ObjectMenu.rotateButtonRef[menuEid]: - startRotation(world, ObjectMenu.targetRef[menuEid]); + ObjectMenu.flags[menuEid] &= ~ObjectMenuFlags.Visible; + startRotation(world, menuEid, ObjectMenu.targetRef[menuEid]); break; case ObjectMenu.scaleButtonRef[menuEid]: - startScaling(world, ObjectMenu.targetRef[menuEid]); + ObjectMenu.flags[menuEid] &= ~ObjectMenuFlags.Visible; + startScaling(world, menuEid, ObjectMenu.targetRef[menuEid]); break; } } @@ -219,17 +196,26 @@ function handleHeldEnter(world: HubsWorld, eid: EntityID, menuEid: EntityID) { function handleHeldExit(world: HubsWorld, eid: EntityID, menuEid: EntityID) { switch (eid) { case ObjectMenu.rotateButtonRef[menuEid]: - stopRotation(world); + ObjectMenu.flags[menuEid] |= ObjectMenuFlags.Visible; + stopRotation(world, menuEid); break; case ObjectMenu.scaleButtonRef[menuEid]: - stopScaling(world); + ObjectMenu.flags[menuEid] |= ObjectMenuFlags.Visible; + stopScaling(world, menuEid); break; } } function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) { const target = ObjectMenu.targetRef[menu]; - const visible = !!(target && frozen); + const visible = !!(target && frozen) && (ObjectMenu.flags[menu] & ObjectMenuFlags.Visible) !== 0; + + if (visible) { + addComponent(world, ObjectMenuTransform, menu); + ObjectMenuTransform.targetObjectRef[menu] = target; + } else { + removeComponent(world, ObjectMenuTransform, menu); + } const obj = world.eid2obj.get(menu)!; obj.visible = visible; @@ -273,7 +259,8 @@ export function objectMenuSystem(world: HubsWorld, sceneIsFrozen: boolean, hubCh handleHeldExit(world, eid, menu); }); - ObjectMenu.targetRef[menu] = objectMenuTarget(world, menu, sceneIsFrozen); + const targetEid = objectMenuTarget(world, menu, sceneIsFrozen); + ObjectMenu.targetRef[menu] = targetEid; if (ObjectMenu.targetRef[menu]) { handleClicks(world, menu, hubChannel); @@ -285,8 +272,6 @@ export function objectMenuSystem(world: HubsWorld, sceneIsFrozen: boolean, hubCh if (scalingHandler !== null) { scalingHandler.tick(); } - - moveToTarget(world, menu); } updateVisibility(world, menu, sceneIsFrozen); } diff --git a/src/systems/hubs-systems.ts b/src/systems/hubs-systems.ts index 1c47683ebc..f438212437 100644 --- a/src/systems/hubs-systems.ts +++ b/src/systems/hubs-systems.ts @@ -80,6 +80,7 @@ import { quackSystem } from "../bit-systems/quack"; import { mixerAnimatableSystem } from "../bit-systems/mixer-animatable"; import { loopAnimationSystem } from "../bit-systems/loop-animation"; import { linkSystem } from "../bit-systems/link-system"; +import { objectMenuTransformSystem } from "../bit-systems/object-menu-transform-system"; declare global { interface Window { @@ -265,6 +266,8 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene linearTransformSystem(world); quackSystem(world); + objectMenuTransformSystem(world); + mixerAnimatableSystem(world); loopAnimationSystem(world); diff --git a/src/utils/jsx-entity.ts b/src/utils/jsx-entity.ts index 524a95fca8..9ac28cd468 100644 --- a/src/utils/jsx-entity.ts +++ b/src/utils/jsx-entity.ts @@ -3,7 +3,6 @@ import { preloadFont } from "troika-three-text"; import { $isStringType, CameraTool, - ObjectMenu, LinkHoverMenu, LinkHoverMenuItem, PDFMenu, @@ -39,7 +38,9 @@ import { VideoTextureSource, Quack, Mirror, - MixerAnimatableInitialize + MixerAnimatableInitialize, + ObjectMenu, + ObjectMenuTransform } from "../bit-components"; import { inflateMediaLoader } from "../inflators/media-loader"; import { inflateMediaFrame } from "../inflators/media-frame"; @@ -359,6 +360,7 @@ export interface JSXComponentData extends ComponentData { waypointPreview?: boolean; pdf?: PDFParams; loopAnimation?: LoopAnimationParams; + objectMenuTransform?: true; } export interface GLTFComponentData extends ComponentData { @@ -471,6 +473,7 @@ const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = { image: inflateImage, video: inflateVideo, link: inflateLink, + objectMenuTransform: createDefaultInflator(ObjectMenuTransform) }; export const gltfInflators: Required<{ [K in keyof GLTFComponentData]: InflatorFn }> = { diff --git a/src/utils/three-utils.js b/src/utils/three-utils.js index 654246f2cc..ac0f341dab 100644 --- a/src/utils/three-utils.js +++ b/src/utils/three-utils.js @@ -365,6 +365,8 @@ export function createPlaneBufferGeometry(width, height, widthSegments, heightSe } import { Layers } from "../camera-layers"; +import { computeObjectAABB } from "./auto-box-collider"; +import { Euler } from "three"; // This code is from three-vrm. We will likely be using that in the future and this inlined code can go away function excludeTriangles(triangles, bws, skinIndex, exclude) { @@ -497,3 +499,34 @@ export function traverseSome(obj, fn) { } } } + +// Gets the bounding box of the entity hierarchy starting at boxRootEid without accounting for the eid entity rotation +export const getBox = (() => { + const rotation = new Euler(); + return (world, eid, boxRootEid, box, worldSpace = false) => { + const obj = world.eid2obj.get(eid); + const boxRootObj = world.eid2obj.get(boxRootEid); + + rotation.copy(obj.rotation); + obj.rotation.set(0, 0, 0); + obj.updateMatrices(true, true); + boxRootObj.updateMatrices(true, true); + boxRootObj.updateMatrixWorld(true); + + computeObjectAABB(boxRootObj, box, false); + + if (!box.isEmpty()) { + if (!worldSpace) { + obj.worldToLocal(box.min); + obj.worldToLocal(box.max); + } + obj.rotation.copy(rotation); + obj.matrixNeedsUpdate = true; + } + + boxRootObj.matrixWorldNeedsUpdate = true; + boxRootObj.updateMatrices(); + + return box; + }; +})();