@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
664 lines • 33 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
import { Euler, Matrix4, Object3D, Quaternion, Vector3 } from "three";
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
import { hasProLicense } from "../../../engine/engine_license.js";
import { serializable } from "../../../engine/engine_serialization.js";
import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
import { WebXRButtonFactory } from "../../../engine/webcomponents/WebXRButtons.js";
import { InternalUSDZRegistry } from "../../../engine/xr/usdz.js";
import { InstancingHandler } from "../../../engine-components/RendererInstancing.js";
import { Collider } from "../../Collider.js";
import { Behaviour, GameObject } from "../../Component.js";
import { ContactShadows } from "../../ContactShadows.js";
import { GroundProjectedEnv } from "../../GroundProjection.js";
import { Renderer } from "../../Renderer.js";
import { Rigidbody } from "../../RigidBody.js";
import { SpriteRenderer } from "../../SpriteRenderer.js";
import { WebARSessionRoot } from "../../webxr/WebARSessionRoot.js";
import { WebXR } from "../../webxr/WebXR.js";
import { XRState, XRStateFlag } from "../../webxr/XRFlag.js";
import { AnimationExtension } from "./extensions/Animation.js";
import { AudioExtension } from "./extensions/behavior/AudioExtension.js";
import { BehaviorExtension } from "./extensions/behavior/Behaviour.js";
import { PhysicsExtension } from "./extensions/behavior/PhysicsExtension.js";
import { TextExtension } from "./extensions/USDZText.js";
import { USDZUIExtension } from "./extensions/USDZUI.js";
import { USDZExporter as ThreeUSDZExporter } from "./ThreeUSDZExporter.js";
import { disableObjectsAtStart, registerAnimatorsImplictly, registerAudioSourcesImplictly } from "./utils/animationutils.js";
import { ensureQuicklookLinkIsCreated } from "./utils/quicklook.js";
const debug = getParam("debugusdz");
const debugUsdzPruning = getParam("debugusdzpruning");
/**
* Custom branding for the QuickLook overlay, used by {@link USDZExporter}.
*/
export class CustomBranding {
/** The call to action button text. If not set, the button will close the QuickLook overlay. */
callToAction;
/** The title of the overlay. */
checkoutTitle;
/** The subtitle of the overlay. */
checkoutSubtitle;
/** if assigned the call to action button in quicklook will open the URL. Otherwise it will just close quicklook. */
callToActionURL;
}
__decorate([
serializable()
], CustomBranding.prototype, "callToAction", void 0);
__decorate([
serializable()
], CustomBranding.prototype, "checkoutTitle", void 0);
__decorate([
serializable()
], CustomBranding.prototype, "checkoutSubtitle", void 0);
__decorate([
serializable()
], CustomBranding.prototype, "callToActionURL", void 0);
/**
* Exports the current scene or a specific object as USDZ file and opens it in QuickLook on iOS/iPadOS/visionOS.
* The USDZ file is generated using the Needle Engine ThreeUSDZExporter.
* The exporter supports various extensions to add custom behaviors and interactions to the USDZ file.
* The exporter can automatically collect Animations and AudioSources and export them as playing at the start.
* The exporter can also add a custom QuickLook overlay with a call to action button and custom branding.
* @example
* ```typescript
* const usdz = new USDZExporter();
* usdz.objectToExport = myObject;
* usdz.autoExportAnimations = true;
* usdz.autoExportAudioSources = true;
* usdz.exportAsync();
* ```
* @category XR
* @group Components
*/
export class USDZExporter extends Behaviour {
/**
* Assign the object to export as USDZ file. If undefined or null, the whole scene will be exported.
*/
objectToExport = undefined;
/** Collect all Animations/Animators automatically on export and emit them as playing at the start.
* Animator state chains and loops will automatically be collected and exported in order as well.
* If this setting is off, Animators need to be registered by components – for example from PlayAnimationOnClick.
*/
autoExportAnimations = true;
/** Collect all AudioSources automatically on export and emit them as playing at the start.
* They will loop according to their settings.
* If this setting is off, Audio Sources need to be registered by components – for example from PlayAudioOnClick.
*/
autoExportAudioSources = true;
exportFileName = undefined;
customUsdzFile = undefined;
customBranding;
// Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
anchoringType = "plane";
maxTextureSize = 2048;
// Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
planeAnchoringAlignment = "horizontal";
/** Enabling this option will export QuickLook-specific preliminary behaviours along with the USDZ files.
* These extensions are only supported on QuickLook on iOS/visionOS/MacOS.
* Keep this option off for general USDZ usage.
*/
interactive = true;
/** Enabling this option will export the USDZ file with RealityKit physics components.
* Rigidbody and Collider components will be converted to their RealityKit counterparts.
* Physics are supported on QuickLook in iOS 18+ and VisionOS 1+.
* Physics export is automatically turned off when there are no Rigidbody components anywhere on the exported object.
*/
physics = true;
allowCreateQuicklookButton = true;
quickLookCompatible = true;
/**
* Extensions to add custom behaviors and interactions to the USDZ file.
* You can add your own extensions here by extending {@link IUSDExporterExtension}.
*/
extensions = [];
link;
button;
/** @internal */
start() {
if (debug) {
console.log("USDZExporter", this);
console.log("Debug USDZ Mode. Press 'T' to export");
window.addEventListener("keydown", (evt) => {
switch (evt.key) {
case "t":
this.exportAndOpen();
break;
}
});
}
// fall back to this object or to the scene if it's empty and doesn't have a mesh
if (!this.objectToExport)
this.objectToExport = this.gameObject;
if (!this.objectToExport?.children?.length && !this.objectToExport?.isMesh)
this.objectToExport = this.context.scene;
}
/** @internal */
onEnable() {
const supportsQuickLook = DeviceUtilities.supportsQuickLookAR();
const ios = DeviceUtilities.isiOS() || DeviceUtilities.isiPad();
if (!this.button && (debug || supportsQuickLook || ios)) {
if (this.allowCreateQuicklookButton)
this.button = this.createQuicklookButton();
this.lastCallback = this.quicklookCallback.bind(this);
this.link = ensureQuicklookLinkIsCreated(this.context, supportsQuickLook);
this.link.addEventListener('message', this.lastCallback);
}
if (debug)
showBalloonMessage("USDZ Exporter enabled: " + this.name);
document.getElementById("open-in-ar")?.addEventListener("click", this.onClickedOpenInARElement);
InternalUSDZRegistry.registerExporter(this);
}
/** @internal */
onDisable() {
this.button?.remove();
this.link?.removeEventListener('message', this.lastCallback);
if (debug)
showBalloonMessage("USDZ Exporter disabled: " + this.name);
document.getElementById("open-in-ar")?.removeEventListener("click", this.onClickedOpenInARElement);
InternalUSDZRegistry.unregisterExporter(this);
}
onClickedOpenInARElement = (evt) => {
evt.preventDefault();
this.exportAndOpen();
};
/**
* Creates an USDZ file from the current scene or assigned objectToExport and opens it in QuickLook.
* Use the various public properties of USDZExporter to customize export behaviour.
* @deprecated use {@link exportAndOpen} instead
*/
async exportAsync() {
return this.exportAndOpen();
}
/**
* Creates an USDZ file from the current scene or assigned objectToExport and opens it in QuickLook.
* @returns a Promise<Blob> containing the USDZ file
*/
async exportAndOpen() {
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
if (!hasProLicense()) {
if (name !== "")
name += "-";
name += "MadeWithNeedle";
}
if (!this.link)
this.link = ensureQuicklookLinkIsCreated(this.context, DeviceUtilities.supportsQuickLookAR());
// ability to specify a custom USDZ file to be used instead of a dynamic one
if (this.customUsdzFile) {
if (debug)
console.log("Exporting custom usdz", this.customUsdzFile);
this.openInQuickLook(this.customUsdzFile, name);
return null;
}
if (!this.objectToExport) {
console.warn("No object to export", this);
return null;
}
const blob = await this.export(this.objectToExport);
if (!blob) {
console.error("USDZ generation failed. Please report a bug", this);
return null;
}
if (debug)
console.log("USDZ generation done. Downloading as " + name);
// TODO Potentially we have to detect QuickLook availability here,
// and download the file instead. But browsers keep changing how they deal with non-user-initiated downloads...
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/#:~:text=inside%20the%20anchor.-,Feature%20Detection,-To%20detect%20support
/*
if (!DeviceUtilities.supportsQuickLookAR())
this.download(blob, name);
else
*/
this.openInQuickLook(blob, name);
return blob;
}
/**
* Creates an USDZ file from the current scene or assigned objectToExport and opens it in QuickLook.
* @returns a Promise<Blob> containing the USDZ file
*/
async export(objectToExport) {
// make sure we have an object to export
if (!objectToExport) {
console.warn("No object to export");
return null;
}
// if we are already exporting, wait for the current export to finish
const taskForThisObject = this._currentExportTasks.get(objectToExport);
if (taskForThisObject) {
return taskForThisObject;
}
// start the export
const task = this.internalExport(objectToExport);
// store the task
if (task instanceof Promise) {
this._currentExportTasks.set(objectToExport, task);
return task.then((blob) => {
this._currentExportTasks.delete(objectToExport);
return blob;
}).catch((e) => {
this._currentExportTasks.delete(objectToExport);
console.error("Error during USDZ export – please report a bug!", e);
return null;
});
}
return task;
}
_currentExportTasks = new Map();
_previousTimeScale = 1;
async internalExport(objectToExport) {
Progress.start("export-usdz", {
onProgress: (progress) => {
this.dispatchEvent(new CustomEvent("export-progress", { detail: { progress } }));
}
});
Progress.report("export-usdz", { message: "Starting export", totalSteps: 40, currentStep: 0 });
Progress.report("export-usdz", { message: "Load progressive textures", autoStep: 5 });
Progress.start("export-usdz-textures", "export-usdz");
// force Sprites to be created
const sprites = GameObject.getComponentsInChildren(objectToExport, SpriteRenderer);
for (const sprite of sprites) {
if (sprite && sprite.enabled) {
sprite.updateSprite(true); // force create
}
}
// trigger progressive textures to be loaded:
const renderers = GameObject.getComponentsInChildren(objectToExport, Renderer);
const progressiveLoading = new Array();
let progressiveTasks = 0;
// TODO: it would be better to directly integrate this into the exporter and *on export* request the correct LOD level for textures and meshes instead of relying on the renderer etc
for (const rend of renderers) {
for (const mesh of rend.sharedMeshes) {
if (mesh) {
const task = NEEDLE_progressive.assignMeshLOD(mesh, 0);
if (task instanceof Promise)
progressiveLoading.push(new Promise((resolve, reject) => {
task.then(() => {
progressiveTasks++;
Progress.report("export-usdz-textures", { message: "Loaded progressive mesh", currentStep: progressiveTasks, totalSteps: progressiveLoading.length });
resolve();
}).catch((err) => reject(err));
}));
}
}
for (const mat of rend.sharedMaterials) {
if (mat) {
const task = NEEDLE_progressive.assignTextureLOD(mat, 0);
if (task instanceof Promise)
progressiveLoading.push(new Promise((resolve, reject) => {
task.then(() => {
progressiveTasks++;
Progress.report("export-usdz-textures", { message: "Loaded progressive texture", currentStep: progressiveTasks, totalSteps: progressiveLoading.length });
resolve();
}).catch((err) => reject(err));
}));
}
}
}
if (debug)
showBalloonMessage("Progressive Loading: " + progressiveLoading.length);
await Promise.all(progressiveLoading);
if (debug)
showBalloonMessage("Progressive Loading: done");
Progress.end("export-usdz-textures");
// apply XRFlags
const currentXRState = XRState.Global.Mask;
XRState.Global.Set(XRStateFlag.AR);
const exporter = new ThreeUSDZExporter();
// We're creating a new animation extension on each export to avoid issues with multiple exports.
// TODO we probably want to do that with all the extensions...
// Ordering of extensions is important
const animExt = new AnimationExtension(this.quickLookCompatible);
let physicsExt = undefined;
const defaultExtensions = [];
if (this.interactive) {
defaultExtensions.push(new BehaviorExtension());
defaultExtensions.push(new AudioExtension());
// If physics are enabled, and there are any Rigidbody components in the scene,
// add the PhysicsExtension to the default extensions.
if (globalThis["NEEDLE_USE_RAPIER"]) {
const rigidbodies = GameObject.getComponentsInChildren(objectToExport, Rigidbody);
if (rigidbodies.length > 0) {
if (this.physics) {
physicsExt = new PhysicsExtension();
defaultExtensions.push(physicsExt);
}
else if (isDevEnvironment()) {
console.warn("USDZExporter: Physics export is disabled, but there are active Rigidbody components in the scene. They will not be exported.");
}
}
}
defaultExtensions.push(new TextExtension());
defaultExtensions.push(new USDZUIExtension());
}
const extensions = [animExt, ...defaultExtensions, ...this.extensions];
const eventArgs = { self: this, exporter: exporter, extensions: extensions, object: objectToExport };
Progress.report("export-usdz", "Invoking before-export");
this.dispatchEvent(new CustomEvent("before-export", { detail: eventArgs }));
// make sure we apply the AR scale
this.applyWebARSessionRoot();
// freeze time
this._previousTimeScale = this.context.time.timeScale;
this.context.time.timeScale = 0;
// Implicit registration and actions for Animators and Animation components
// Currently, Animators properly build PlayAnimation actions, but Animation components don't.
Progress.report("export-usdz", "auto export animations and audio sources");
const implicitBehaviors = new Array();
if (this.autoExportAnimations) {
implicitBehaviors.push(...registerAnimatorsImplictly(objectToExport, animExt));
}
const audioExt = extensions.find(ext => ext.extensionName === "Audio");
if (audioExt && this.autoExportAudioSources)
implicitBehaviors.push(...registerAudioSourcesImplictly(objectToExport, audioExt));
//@ts-ignore
exporter.debug = debug;
exporter.pruneUnusedNodes = !debugUsdzPruning;
const instancedRenderers = InstancingHandler.instance.objs.map(x => x.batchedMesh);
exporter.keepObject = (object) => {
let keep = true;
// This explicitly removes geometry and material data from disabled renderers.
// Note that this is different to the object itself being active –
// here, we have an active object with a disabled renderer.
const renderer = GameObject.getComponent(object, Renderer);
if (renderer && !renderer.enabled)
keep = false;
// Check if this is an instancing container.
// Instances are already included in the export.
if (keep && instancedRenderers.includes(object))
keep = false;
if (keep && GameObject.getComponentInParent(object, ContactShadows))
keep = false;
if (keep && GameObject.getComponentInParent(object, GroundProjectedEnv))
keep = false;
if (debug && !keep)
console.log("USDZExporter: Discarding object", object);
return keep;
};
exporter.beforeWritingDocument = () => {
// Warn if there are any physics components on animated objects or their children
if (isDevEnvironment() && animExt && physicsExt) {
const animatedObjects = animExt.animatedRoots;
for (const object of animatedObjects) {
const rigidBodySources = GameObject.getComponentsInChildren(object, Rigidbody).filter(c => c.enabled);
const colliderSources = GameObject.getComponents(object, Collider).filter(c => c.enabled && !c.isTrigger);
if (rigidBodySources.length > 0 || colliderSources.length > 0) {
console.error("An animated object has physics components in its child hierarchy. This can lead to undefined behaviour due to a bug in Apple's QuickLook (FB15925487). Remove the physics components from child objects or verify that you get the expected results.", object);
}
}
}
};
// Collect invisible objects so that we can disable them if
// - we're exporting for QuickLook
// - and interactive behaviors are allowed.
// When exporting for regular USD, we're supporting the "visibility" attribute anyways.
const objectsToDisableAtSceneStart = new Array();
if (this.objectToExport && this.quickLookCompatible && this.interactive) {
this.objectToExport.traverse((obj) => {
if (!obj.visible) {
objectsToDisableAtSceneStart.push(obj);
}
});
}
const behaviorExt = extensions.find(ext => ext.extensionName === "Behaviour");
if (this.interactive && behaviorExt && objectsToDisableAtSceneStart.length > 0) {
behaviorExt.addBehavior(disableObjectsAtStart(objectsToDisableAtSceneStart));
}
let exportInvisible = true;
// The only case where we want to strip out invisible objects is
// when we're exporting for QuickLook and we're NOT adding interactive behaviors,
// since QuickLook on iOS does not support "visibility" tokens.
if (this.quickLookCompatible && !this.interactive)
exportInvisible = false;
// sanitize anchoring types
if (this.anchoringType !== "plane" && this.anchoringType !== "none" && this.anchoringType !== "image" && this.anchoringType !== "face")
this.anchoringType = "plane";
if (this.planeAnchoringAlignment !== "horizontal" && this.planeAnchoringAlignment !== "vertical" && this.planeAnchoringAlignment !== "any")
this.planeAnchoringAlignment = "horizontal";
Progress.report("export-usdz", "Invoking exporter.parse");
//@ts-ignore
const arraybuffer = await exporter.parse(this.objectToExport, {
ar: {
anchoring: {
type: this.anchoringType,
},
planeAnchoring: {
alignment: this.planeAnchoringAlignment,
},
},
extensions: extensions,
quickLookCompatible: this.quickLookCompatible,
maxTextureSize: this.maxTextureSize,
exportInvisible: exportInvisible,
});
const blob = new Blob([arraybuffer], { type: 'model/vnd.usdz+zip' });
this.revertWebARSessionRoot();
// unfreeze time
this.context.time.timeScale = this._previousTimeScale;
Progress.report("export-usdz", "Invoking after-export");
this.dispatchEvent(new CustomEvent("after-export", { detail: eventArgs }));
// cleanup – implicit animation behaviors need to be removed again
for (const go of implicitBehaviors) {
GameObject.destroy(go);
}
// restore XR flags
XRState.Global.Set(currentXRState);
// second file: USDA (without assets)
//@ts-ignore
// const usda = exporter.lastUsda;
// const blob2 = new Blob([usda], { type: 'text/plain' });
// this.link.download = name + ".usda";
// this.link.href = URL.createObjectURL(blob2);
// this.link.click();
Progress.end("export-usdz");
return blob;
}
/**
* Opens QuickLook on iOS/iPadOS/visionOS with the given content in AR mode.
* @param content The URL to the .usdz or .reality file or a blob containing an USDZ file.
* @param name Download filename
*/
openInQuickLook(content, name) {
const url = content instanceof Blob ? URL.createObjectURL(content) : content;
// see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
const overlay = this.buildQuicklookOverlay();
if (debug)
console.log("QuickLook Overlay", overlay);
const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
this.link.href = url + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}&callToActionURL=${overlay.callToActionURL}`;
if (!this.lastCallback) {
this.lastCallback = this.quicklookCallback.bind(this);
this.link.addEventListener('message', this.lastCallback);
}
// Open QuickLook
this.link.download = name + ".usdz";
this.link.click();
// cleanup
// TODO check if we can do that immediately or need to wait until the user returns
// if (content instanceof Blob) URL.revokeObjectURL(url);
}
/**
* Downloads the given blob as a file.
*/
download(blob, name) {
USDZExporter.save(blob, name);
}
// Matches GltfExport.save(blob, filename)
static save(blob, filename) {
const link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link); // Firefox workaround, see #6594
if (typeof blob === "string")
link.href = blob;
else
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
link.remove();
// console.log(link.href);
// URL.revokeObjectURL( url ); breaks Firefox...
}
lastCallback;
quicklookCallback(event) {
if (event?.data == '_apple_ar_quicklook_button_tapped') {
if (debug)
showBalloonWarning("Quicklook closed via call to action button");
var evt = new CustomEvent("quicklook-button-tapped", { detail: this });
this.dispatchEvent(evt);
if (!evt.defaultPrevented) {
const url = new URLSearchParams(this.link.href);
if (url) {
const callToActionURL = url.get("callToActionURL");
if (debug)
showBalloonMessage("Quicklook url: " + callToActionURL);
if (callToActionURL) {
if (!hasProLicense()) {
console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL);
}
else {
globalThis.open(callToActionURL, "_blank");
}
}
}
}
}
}
buildQuicklookOverlay() {
const obj = {};
if (this.customBranding)
Object.assign(obj, this.customBranding);
if (!hasProLicense()) {
console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
obj.callToAction = "Close";
obj.checkoutTitle = "🌵 Made with Needle";
obj.checkoutSubtitle = "_";
}
const needsDefaultValues = obj.callToAction?.length || obj.checkoutTitle?.length || obj.checkoutSubtitle?.length;
if (needsDefaultValues) {
if (!obj.callToAction?.length)
obj.callToAction = "\0";
if (!obj.checkoutTitle?.length)
obj.checkoutTitle = "\0";
if (!obj.checkoutSubtitle?.length)
obj.checkoutSubtitle = "\0";
}
// Use the quicklook-overlay event to customize the overlay
this.dispatchEvent(new CustomEvent("quicklook-overlay", { detail: obj }));
return obj;
}
static invertForwardMatrix = new Matrix4().makeRotationY(Math.PI);
static invertForwardQuaternion = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
_rootSessionRootWasAppliedTo = null;
_rootPositionBeforeExport = new Vector3();
_rootRotationBeforeExport = new Quaternion();
_rootScaleBeforeExport = new Vector3();
getARScaleAndTarget() {
if (!this.objectToExport)
return { scale: 1, _invertForward: false, target: this.gameObject, sessionRoot: null };
const xr = GameObject.findObjectOfType(WebXR);
let sessionRoot = GameObject.getComponentInParent(this.objectToExport, WebARSessionRoot);
if (!sessionRoot)
sessionRoot = GameObject.getComponentInChildren(this.objectToExport, WebARSessionRoot);
let arScale = 1;
let _invertForward = false;
const target = this.objectToExport;
// Note: when USDZ is exported, SessionRoot might not have the correct AR scale, since that is populated upon EnterXR from WebXR.
if (xr) {
arScale = xr.arScale;
}
else if (sessionRoot) {
arScale = sessionRoot.arScale;
// eslint-disable-next-line deprecation/deprecation
_invertForward = sessionRoot.invertForward;
}
const scale = 1 / arScale;
const result = { scale, _invertForward, target, sessionRoot: sessionRoot?.gameObject ?? null };
return result;
}
applyWebARSessionRoot() {
if (!this.objectToExport)
return;
const { scale, _invertForward, target, sessionRoot } = this.getARScaleAndTarget();
const sessionRootMatrixWorld = sessionRoot?.matrixWorld.clone().invert();
this._rootSessionRootWasAppliedTo = target;
this._rootPositionBeforeExport.copy(target.position);
this._rootRotationBeforeExport.copy(target.quaternion);
this._rootScaleBeforeExport.copy(target.scale);
target.scale.multiplyScalar(scale);
if (_invertForward)
target.quaternion.multiply(USDZExporter.invertForwardQuaternion);
// udate childs as well
target.updateMatrix();
target.updateMatrixWorld(true);
if (sessionRoot && sessionRootMatrixWorld)
target.matrix.premultiply(sessionRootMatrixWorld);
}
revertWebARSessionRoot() {
if (!this.objectToExport)
return;
if (!this._rootSessionRootWasAppliedTo)
return;
const target = this._rootSessionRootWasAppliedTo;
target.position.copy(this._rootPositionBeforeExport);
target.quaternion.copy(this._rootRotationBeforeExport);
target.scale.copy(this._rootScaleBeforeExport);
// udate childs as well
target.updateMatrix();
target.updateMatrixWorld(true);
this._rootSessionRootWasAppliedTo = null;
}
createQuicklookButton() {
const buttoncontainer = WebXRButtonFactory.getOrCreate();
const button = buttoncontainer.createQuicklookButton();
if (!button.parentNode)
this.context.menu.appendChild(button);
return button;
}
}
__decorate([
serializable(Object3D)
], USDZExporter.prototype, "objectToExport", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "autoExportAnimations", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "autoExportAudioSources", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "exportFileName", void 0);
__decorate([
serializable(URL)
], USDZExporter.prototype, "customUsdzFile", void 0);
__decorate([
serializable(CustomBranding)
], USDZExporter.prototype, "customBranding", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "anchoringType", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "maxTextureSize", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "planeAnchoringAlignment", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "interactive", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "physics", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "allowCreateQuicklookButton", void 0);
__decorate([
serializable()
], USDZExporter.prototype, "quickLookCompatible", void 0);
//# sourceMappingURL=USDZExporter.js.map