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.

204 lines (203 loc) 9.81 kB
import { Box3, Object3D, Plane, Vector3 } from "three"; import type { Context } from "../engine/engine_setup.js"; import { GameObject } from "./Component.js"; /** * Context passed to {@link IDragConstraint.init} at drag start and whenever the drag context * resets (e.g. transitioning from multi-touch back to single-pointer). * Provides a snapshot of the dragged object and attachment data a constraint needs * to initialize or re-initialize its locked reference values. */ export interface IDragConstraintContext { /** The object being dragged. Cast to IGameObject for world-space properties. */ readonly gameObject: Object3D; /** Drag attachment point in the dragged object's local space. */ readonly hitPointInLocalSpace: Vector3; /** Surface normal at drag attachment in the dragged object's local space. */ readonly hitNormalInLocalSpace: Vector3; /** * Local-space bounding box at scale=1. Non-null only for multi-touch drags * where scale-aware constraints (e.g. keeping the bounds bottom grounded) are needed. */ readonly boundsAtScaleOne: Box3 | null; /** * World scale of the object captured at the start of the current drag phase. * Non-null alongside boundsAtScaleOne for multi-touch; null for single-pointer. */ readonly initialWorldScale: Vector3 | null; } /** * Contract for drag constraints applied after a follow object's position is resolved each frame. * Implement this interface to add custom position/rotation/scale restrictions. */ export interface IDragConstraint { /** * Called once at drag start and again whenever the drag context resets * (e.g. multi-touch → single-pointer transition). Capture any object snapshot * (position, rotation, scale) you need to hold fixed during the drag. * Constraints that need no dynamic initialization may omit this method. */ init?(context: IDragConstraintContext): void; /** Modifies followObject in-place. Invoked after position is resolved each frame. */ apply(followObject: GameObject): void; } /** Snaps the follow object's world position to a uniform grid. Resolution ≤ 0 is a no-op. * For XZ-plane mode, use {@link GrabPointPlaneConstraint} instead (it handles both * plane-clamping and optional grid-snapping on the plane). */ export declare class GridSnapConstraint implements IDragConstraint { snapGridResolution: number; constructor(snapGridResolution: number); apply(followObject: GameObject): void; } /** Projects the grabbed attachment point back onto a plane after position is resolved, * with optional grid snapping on that plane. * Owned by the strategy that requires plane-clamping (e.g. {@link XZPlaneDragStrategy}). */ export declare class GrabPointPlaneConstraint implements IDragConstraint { /** Plane to project the grabbed point onto. Typically the strategy's fixed drag plane. */ readonly plane: Plane; /** Attachment point in dragged-object local space. Set once at drag start; the handler * mutates this Vector3 in-place so updates are reflected automatically. */ hitPointInLocalSpace: Vector3 | null; /** Grid snap resolution on the plane. 0 = projection only, no snapping. */ snapResolution: number; constructor(plane: Plane); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** Locks the follow object's world rotation to the quaternion captured at drag-start. */ export declare class KeepRotationConstraint implements IDragConstraint { private readonly _savedQuat; init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** Locks the follow object's world scale to the value captured at drag-start. */ export declare class KeepScaleConstraint implements IDragConstraint { private readonly _savedScale; init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** * Clamps the world scale of the dragged object to a [min, max] range. * * When `relativeToInitialScale` is `true` (default), `min` and `max` are treated as * multipliers of the object's world scale captured at drag start — for example, `min=0.01` * means the object cannot shrink below 1% of its original size. * * When `relativeToInitialScale` is `false`, `min` and `max` are applied directly as * absolute world-space scale values to each axis independently. * * @example Prevent negative / near-zero scale with default relative mode: * ```ts * new ScaleLimitConstraint(0.01, 10000, true, object.worldScale.clone()); * ``` */ export declare class ScaleLimitConstraint implements IDragConstraint { min: number; max: number; relativeToInitialScale: boolean; /** * @param min Lower bound. Relative mode: fraction of initial scale. Raw mode: absolute world-scale per axis. * @param max Upper bound. Relative mode: fraction of initial scale. Raw mode: absolute world-scale per axis. * @param relativeToInitialScale When `true`, clamp is relative to `initialWorldScale`. When `false`, each axis is clamped independently. * @param initialWorldScale Reference to the object's world scale captured at drag start. Required for relative mode. */ private readonly _initialWorldScale; constructor(min: number, max: number, relativeToInitialScale?: boolean); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** * Flags controlling which rotation axes are frozen by {@link FixedRotationAxesConstraint}. * Values can be combined with the bitwise OR operator (`|`). * @example Freeze X and Z: `RotationAxis.X | RotationAxis.Z` */ export declare enum RotationAxis { X = 1, Y = 2, Z = 4 } /** * Freezes individual rotation axes (X, Y, and/or Z) of the dragged object. * The locked axis values are captured once at construction time and restored every frame. * * Set {@link useLocalSpace} to `true` to lock axes in the object's local space; * leave it `false` (default) to lock axes in world space. * * @example Lock Y-axis rotation in world space: * ```ts * const c = new FixedRotationAxesConstraint(RotationAxis.Y, false); * // init is called automatically by DragControls, or call manually: c.init(ctx); * ``` */ export declare class FixedRotationAxesConstraint implements IDragConstraint { frozenAxes: RotationAxis; useLocalSpace: boolean; private readonly _startEuler; private readonly _eulerCache; /** * @param frozenAxes Bitfield of {@link RotationAxis} values indicating which axes to lock. * @param useLocalSpace When `true`, axes are locked in the object's local space; otherwise world space. */ constructor(frozenAxes: RotationAxis, useLocalSpace?: boolean); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** * Constrains a dragged object to rotate only around a fixed world-space axis. * Uses swing-twist decomposition to extract only the twist component around * the given axis from the delta rotation since drag start, discarding any * perpendicular swing that would tilt the object. * * This is the correct constraint for XZ-plane dragging when the parent is * rotated — it restricts the object to yaw around the plane normal regardless * of how the plane is oriented in world space. */ export declare class AxisRotationConstraint implements IDragConstraint { private readonly _startQuat; private readonly _axis; constructor(axis: Vector3); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** Locks the follow object's signed distance from a plane to a value set at drag start. * Owned by {@link XZPlaneDragStrategy} to prevent slow Y drift in the side-view fallback path. * Immune to grab-point projection errors, stale matrixWorld, and epsilon guards. * * When {@link boundsBottomSignedDistFromPivot} is non-zero (set by {@link MultiTouchDragHandler} * for XZPlane scaling), the effective locked height is shifted each frame so the **bottom of the * object's bounds** stays at the height captured at drag start instead of the pivot. */ export declare class PlaneHeightLockConstraint implements IDragConstraint { private readonly plane; private _lockedHeight; private _boundsBottomSignedDistFromPivot; /** Current pinch-scale ratio, updated each frame by {@link MultiTouchDragHandler}. */ currentScaleRatio: number; constructor(plane: Plane); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** * Used by {@link SnapToSurfacesDragStrategy} for two-pointer (multi-touch) drags. * Casts a downward ray each frame from the follow object's world position and adjusts * it so the dragged object's bounding-box contact face rests on the detected surface. * * Only injected into the constraint pipeline for multi-touch; the single-pointer path * continues to use {@link SnapToSurfacesDragStrategy.update} (pointer ray + drag plane * intersection) unchanged. */ export declare class SnapToSurfaceConstraint implements IDragConstraint { private readonly _context; private _gameObject; private _boundsAtScaleOne; private readonly _t1; private readonly _t2; private readonly _t3; private readonly _t4; constructor(_context: Context); init(ctx: IDragConstraintContext): void; apply(followObject: GameObject): void; } /** * Runs the constraint pipeline — applies each constraint to followObject in order. * Shared by {@link DragPointerHandler} and {@link MultiTouchDragHandler}. */ export declare function applyFollowObjectConstraints(followObject: GameObject, constraints: readonly IDragConstraint[]): void;