UNPKG

@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.

392 lines (341 loc) • 14.9 kB
import { Matrix4, Object3D, Quaternion, Vector3 } from "three"; import { TransformControlsGizmo } from "three/examples/jsm/controls/TransformControls.js"; import { addComponent, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, removeComponent } from "../../engine/engine_components.js"; import { destroy, isActiveSelf, setActive } from "../../engine/engine_gameobject.js"; import { getTempQuaternion, getTempVector, getWorldPosition, getWorldQuaternion, getWorldRotation, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldRotation, setWorldScale } from "../../engine/engine_three_utils.js"; import type { ComponentInit, Constructor, ConstructorConcrete, HideFlags, IComponent as Component, IComponent } from "../../engine/engine_types.js"; import { NEEDLE_ENGINE_FEATURE_FLAGS } from "../engine_feature_flags.js"; import { markHierarchyDirty } from "../engine_mainloop_utils.js"; import { applyPrototypeExtensions, registerPrototypeExtensions } from "./ExtensionUtils.js"; // #region Type Declarations // NOTE: keep in sync with method declarations below declare module 'three' { export interface Object3D { get guid(): string | undefined; set guid(value: string | undefined); /** * Allows to control e.g. if an object should be exported */ hideFlags: HideFlags; /** * If false the object will be ignored for raycasting (e.g. pointer events). Default is true. * @default true */ raycastAllowed: boolean; /** * Set a raycast preference for the object: * - `lod` will use the raycast mesh lod if available (default). This is usually a simplified mesh for raycasting. * - `bounds` will use the bounding box of the object for raycasting. This is very fast but not very accurate. * - `full` will use the full mesh for raycasting. This is the most accurate but also the slowest option. * * **NOTE:** Needle Engine's Raycast system will use Mesh BVH by default - so event 'full' is usually faster than default three.js raycasting. */ raycastPreference?: 'lod' | 'bounds' | 'full'; /** * Add a Needle Engine component to the {@link Object3D}. * @param comp The component instance or constructor to add. * @param init Optional initialization data for the component. * @returns The added component instance. * @example Directly pass in constructor and properties: * ```ts * const obj = new Object3D(); * obj.addComponent(MyComponent, { myProperty: 42 }); * ``` * @example Create a component instance, assign properties and then add it: * ```ts * const obj = new Object3D(); * const comp = new MyComponent(); * comp.myProperty = 42; * obj.addComponent(comp); * ``` */ addComponent<T extends IComponent>(comp: T | ConstructorConcrete<T>, init?: ComponentInit<T>): T; /** * Remove a Needle Engine component from the {@link Object3D}. */ removeComponent(inst: IComponent): IComponent; /** * Get or add a Needle Engine component to the Object3D. * If the component already exists, it will be returned. Otherwise, a new component will be added. * @param typeName The component constructor to get or add. * @param init Optional initialization data for the component. * @returns The component instance. */ getOrAddComponent<T extends IComponent>(typeName: ConstructorConcrete<T>, init?: ComponentInit<T>): T; /** * Get a Needle Engine component from the {@link Object3D}. * @returns The component instance or null if not found. */ getComponent<T extends IComponent>(type: Constructor<T>): T | null; /** * Get all components of a specific type from the {@link Object3D}. * @param arr Optional array to fill with the found components. * @returns An array of components. */ getComponents<T extends IComponent>(type: Constructor<T>, arr?: []): T[]; /** * Get a Needle Engine component from the {@link Object3D} or its children. This will search on the current Object and all its children. * @param type The type of the component to search for. * @param includeInactive If true, also inactive components are considered. Default is false. * @returns The component instance or null if not found. */ getComponentInChildren<T extends IComponent>(type: Constructor<T>, includeInactive?: boolean): T | null; /** * Get all components of a specific type from the {@link Object3D} or its children. This will search on the current Object and all its children. * @param arr Optional array to fill with the found components. * @returns An array of components. */ getComponentsInChildren<T extends IComponent>(type: Constructor<T>, arr?: []): T[]; /** * Get a Needle Engine component from the {@link Object3D} or its parents. This will search on the current Object and all its parents. * @param type The type of the component to search for. * @param includeInactive If true, also inactive components are considered. Default is false. * @returns The component instance or null if not found. */ getComponentInParent<T extends IComponent>(type: Constructor<T>, includeInactive?: boolean): T | null; /** * Get all Needle Engine components of a specific type from the {@link Object3D} or its parents. This will search on the current Object and all its parents. * @param arr Optional array to fill with the found components. * @returns An array of components. */ getComponentsInParent<T extends IComponent>(type: Constructor<T>, arr?: []): T[]; /** * Destroys the {@link Object3D} and all its Needle Engine components. */ destroy(): void; /** * Get or set the world position of the {@link Object3D}. * Added by Needle Engine. */ worldPosition: Vector3; /** * Get or set the world quaternion of the {@link Object3D}. * Added by Needle Engine. */ worldQuaternion: Quaternion; /** * Get or set the world rotation of the {@link Object3D}. * Added by Needle Engine. */ worldRotation: Vector3; /** * Get or set the world scale of the {@link Object3D}. * Added by Needle Engine. */ worldScale: Vector3; /** * Get the world forward vector of the {@link Object3D}. * Added by Needle Engine. */ get worldForward(): Vector3; set worldForward(v: Vector3); /** * Get the world right vector of the {@link Object3D}. * Added by Needle Engine. */ get worldRight(): Vector3; /** * Get the world up vector of the {@link Object3D}. * Added by Needle Engine. */ get worldUp(): Vector3; /** * Check if the given object is contained in the hierarchy of this object or if it's the same object. * @param object The object to check. * @returns True if the object is contained in the hierarchy, false otherwise. */ contains(object: Object3D | null | undefined): boolean; } } /** * @internal * used to decorate cloned object3D objects with the same added components defined above **/ export function apply(object: Object3D) { if (object && object.isObject3D === true) { applyPrototypeExtensions(object, Object3D); } } if (NEEDLE_ENGINE_FEATURE_FLAGS.experimentalSmartHierarchyUpdate) { const addFn = Object3D.prototype.add; Object3D.prototype.add = function (...args: any) { markHierarchyDirty(); return addFn.apply(this, args); } const attachFn = Object3D.prototype.attach; Object3D.prototype.attach = function (...args: any) { markHierarchyDirty(); return attachFn.apply(this, args); } const removeFn = Object3D.prototype.remove; Object3D.prototype.remove = function (...args: any) { markHierarchyDirty(); return removeFn.apply(this, args); } } // #region Prototype Method Implementations Object3D.prototype["SetActive"] = function (active: boolean) { this.visible = active; } // e.g. when called via a UnityEvent Object3D.prototype["setActive"] = function (active: boolean) { this.visible = active; } Object3D.prototype["destroy"] = function () { destroy(this); } Object3D.prototype["addComponent"] = function <T extends IComponent>(comp: T | ConstructorConcrete<T>, init?: ComponentInit<T>) { return addComponent(this, comp, init); } Object3D.prototype["addNewComponent"] = function <T extends Component>(type: ConstructorConcrete<T>, init?: ComponentInit<T>) { return addComponent(this, type, init); } Object3D.prototype["removeComponent"] = function (inst: Component) { return removeComponent(this, inst); } Object3D.prototype["getOrAddComponent"] = function <T extends IComponent>(typeName: ConstructorConcrete<T>, init?: ComponentInit<T>): T { return getOrAddComponent<T>(this, typeName, init); } Object3D.prototype["getComponent"] = function <T extends IComponent>(type: Constructor<T>) { return getComponent(this, type); } Object3D.prototype["getComponents"] = function <T extends IComponent>(type: Constructor<T>, arr?: []) { return getComponents(this, type, arr); } Object3D.prototype["getComponentInChildren"] = function <T extends IComponent>(type: Constructor<T>, includeInactive: boolean = false) { return getComponentInChildren(this, type, includeInactive); } Object3D.prototype["getComponentsInChildren"] = function <T extends IComponent>(type: Constructor<T>, arr?: []) { return getComponentsInChildren(this, type, arr); } Object3D.prototype["getComponentInParent"] = function <T extends IComponent>(type: Constructor<T>, includeInactive: boolean = false) { return getComponentInParent(this, type, includeInactive); } Object3D.prototype["getComponentsInParent"] = function <T extends IComponent>(type: Constructor<T>, arr?: []) { return getComponentsInParent(this, type, arr); } // #region Prototype Property Implementations // this is a fix to allow gameObject active animation be applied to a three object if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "activeSelf")) { Object.defineProperty(Object3D.prototype, "activeSelf", { get: function () { return isActiveSelf(this) }, set: function (val: boolean | number) { setActive(this, val); } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "raycastAllowed")) { Object.defineProperty(Object3D.prototype, "raycastAllowed", { get: function () { return this.userData && this.userData.raycastAllowed !== false; }, set: function (val: boolean) { const self = this as Object3D; if (!self.userData) self.userData = {}; self.userData.raycastAllowed = val; } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldPosition")) { Object.defineProperty(Object3D.prototype, "worldPosition", { get: function () { // TODO: would be great to remove this - just a workaround because the TransformControlsGizmo also defines this if (this instanceof TransformControlsGizmo) { return getWorldPosition(this["object"]); } return getWorldPosition(this); }, set: function (val: Vector3) { setWorldPosition(this, val) } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldQuaternion")) { Object.defineProperty(Object3D.prototype, "worldQuaternion", { get: function () { if (this instanceof TransformControlsGizmo) { return getWorldQuaternion(this["object"]); } return getWorldQuaternion(this); }, set: function (val: Quaternion) { setWorldQuaternion(this, val) } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldRotation")) { Object.defineProperty(Object3D.prototype, "worldRotation", { get: function () { return getWorldRotation(this); }, set: function (val: Vector3) { setWorldRotation(this, val) } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldScale")) { Object.defineProperty(Object3D.prototype, "worldScale", { get: function () { return getWorldScale(this); }, set: function (val: Vector3) { setWorldScale(this, val) } }); } const tempMatrix = new Matrix4(); const zero = new Vector3(0, 0, 0); const up = new Vector3(0, 1, 0); if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldForward")) { Object.defineProperty(Object3D.prototype, "worldForward", { get: function () { return getTempVector().set(0, 0, 1).applyQuaternion(getWorldQuaternion(this)); }, set: function (v: Vector3) { const quat = getTempQuaternion().setFromRotationMatrix(tempMatrix.lookAt(zero.set(0, 0, 0), v, up.set(0, 1, 0))); this.worldQuaternion = quat; } }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldRight")) { Object.defineProperty(Object3D.prototype, "worldRight", { get: function () { return getTempVector().set(1, 0, 0).applyQuaternion(getWorldQuaternion(this)); }, }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldUp")) { Object.defineProperty(Object3D.prototype, "worldUp", { get: function () { return getTempVector().set(0, 1, 0).applyQuaternion(getWorldQuaternion(this)); }, }); } if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "contains")) { Object.defineProperty(Object3D.prototype, "contains", { value: function (object: Object3D | null | undefined): boolean { if (!object) return false; if (this === object) return true; for (const child of this.children) { if (child.contains(object)) return true; } return false; } }); } // do this after adding the component extensions registerPrototypeExtensions(Object3D);