import { Box3, Object3D, Vector3, Vector3Like } from 'three/webgpu'; import { DIVEMovable } from '../../interfaces/Movable.ts'; import { DIVESelectable } from '../../interfaces/Selectable.ts'; import { TransformControls } from 'three/examples/jsm/controls/TransformControls.ts'; import { DIVEEntityEventMap } from '../../types/events/index.ts'; import { DIVEComponent, DIVEComponentClass } from '../component/Component.ts'; /** * A node in the scene tree. * * Nodes carry the transform and the identity; everything they *do* comes from * the components attached to them. `children` holds both -- it has to, because * three builds its render list from the scene graph -- so use `nodes` for the * logical child tree and `components` for the attached capabilities. * * @module */ export declare class DIVENode extends Object3D implements DIVESelectable, DIVEMovable { readonly isSelectable: true; readonly isMovable: true; readonly isDIVENode: true; gizmo: TransformControls | null; protected _positionWorldBuffer: Vector3; protected _boundingBox: Box3; private _components; /** The scene this node belongs to, cached so it survives detachment. */ private _scene; /** * The components attached to this node. */ get components(): readonly DIVEComponent[]; /** * The child nodes of this node -- the logical scene tree. * * Filters `children` by `isDIVENode`, which excludes components as well as * raw geometry a component happens to own. Uncached on purpose: direct * `children` assignment bypasses three's events, so a cache would be able * to go stale, and the list is short enough that filtering costs nothing. */ get nodes(): DIVENode[]; constructor(); /** * Attaches a component to this node. * * Uses `add`, never `attach`: a component has no meaningful transform of its * own, and `attach` would apply an inverse world matrix to it. * * Announces itself with `componentadded`, named after three's `childadded` * because it is the same kind of fact about the same node. A component is not * a child, so three's own events say nothing about one, and without this * nothing could observe what a node is made of. * * @param component - The component to attach. * @returns The component, for chaining. */ addComponent(component: T): T; /** * Detaches a component from this node. * * Announces itself with `componentremoved`, after the component is gone, so a * listener that re-reads the node sees what is left rather than what was. * * @param component - The component to detach. */ removeComponent(component: DIVEComponent): this; /** * Finds the first attached component of the given type. * * Matches with `instanceof`, so a base class finds its subclasses -- asking * for `MeshComponent` also returns a `PrimitiveComponent`, and asking * for an abstract base such as `DIVELightComponent` works too. * * @param Ctor - The component class to look for. */ getComponent(Ctor: DIVEComponentClass): T | undefined; /** * Finds every attached component of the given type. * * @param Ctor - The component class to look for. */ getComponents(Ctor: DIVEComponentClass): T[]; /** * Like {@link getComponent}, but throws when the component is missing. * * Use this wherever a component is part of the node's contract -- a model * node always has mesh geometry, a light node always has a light. Throwing * once beats threading an `undefined` through every caller. * * @param Ctor - The component class to look for. */ requireComponent(Ctor: DIVEComponentClass): T; /** * Removes all child nodes and raw children, keeping what components own. * * `Object3D.clear()` would take the contributions with it, which would * silently strip a node of its geometry or its light. A component gives up * its own content through `withdraw`, never through the node. */ clear(): this; /** * Copies another node, its components included. * * Child nodes are cloned, contributed content is not: a component brings its * own along, and cloning it here as well would leave the copy with ownerless * geometry beside a component that knows nothing about it. * * **Replaces the components this node already has, disposing them.** A node * that attaches components in its constructor -- `DIVERoot` and its floor -- * would otherwise end up with two of each, and the ones being replaced hold * GPU resources that nobody else will ever release. Unexpected for a copy * operation, hence spelled out here. * * @param source - The node to copy from. * @param recursive - Whether to copy children. */ copy(source: this, recursive?: boolean): this; /** * Re-evaluates whether a component should be ticking. * * @param component - The component whose tick state changed. * @internal */ refreshComponentTick(component: DIVEComponent): void; setPosition(position: Vector3Like): void; setRotation(rotation: Vector3Like): void; setScale(scale: Vector3Like): void; /** * Writes a transform **without reporting it for this node**. * * For a caller that announces the change itself: the state's apply path * writes a whole patch and its action announces it once. Going through * `setPosition`, `setRotation` and `setScale` there meant three reports for * one patch, on top of the action's own — which is what this exists to avoid. * * Members are still woken, because nothing else tells them: their own * transform did not change, but their world transform did, and no caller * knows about them. * * @param patch - The parts of the transform to write. Anything absent is left * alone. */ applyTransform(patch: { position?: Vector3Like | null; rotation?: Vector3Like | null; scale?: Vector3Like | null; }): void; /** * Reports this node's transform, and its members' new world transforms. * * One event for every kind of move, so a listener never has to ask what * caused it: a gizmo drag and a `setPosition` both arrive as * `object-transform`. * * Can be called when the object is moved from a foreign object (gizmo, * parent, etc.) to update the object's position. */ onMove(): void; /** * Writes whatever the patch carries, guarded field by field. * * @returns Whether anything actually changed. Nothing reports otherwise, so * a patch that repeats the current transform stays silent. */ private _writeTransform; /** * Has every member report its new world transform, recursively. * * Members are reached through their own `onMove`, so a nested group carries * on down its own members. */ private _reportMembers; /** * Rests this node on the highest surface below it, and never below the * ground plane. * * The floor is deliberately not a raycast target: it is a flat plane at * y = 0, so "hit the floor" and "hit nothing" produce the same answer. The * clamp to y = 0 covers the remaining case, geometry parked below the floor, * which must not drag this node underground. */ dropIt(): void; setVisibility(visible: boolean): void; setToWorldOrigin(): void; onSelect(): void; onDeselect(): void; /** Whether an object is this node or hangs anywhere below it. */ private _contains; private _handleAddedToTree; private _handleRemovedFromTree; private _attachToScene; private _detachFromScene; }