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.

212 lines (211 loc) 9.46 kB
import { Behaviour } from "../Component.js"; /** * [CursorFollow](https://engine.needle.tools/docs/api/CursorFollow) makes an object smoothly follow the cursor or touch position in 3D space. * The component tracks pointer movement and updates the object's position to follow it, with optional damping for smooth motion. * * ![](https://cloud.needle.tools/-/media/GDspQGC_kB85Bc9IyEtr9Q.gif) * * **How It Works:** * The component creates a ray from the camera through the cursor position and places the object along that ray. * By default, it maintains the object's initial distance from the camera, creating a natural cursor-following effect * that works consistently regardless of camera movement. * * **Key Features:** * - Smooth cursor following with configurable damping * - Works with both mouse and touch input * - Can follow cursor across the entire page or just within the canvas * - Maintains consistent distance from camera by default * - Optional surface snapping using raycasts * - Responds to camera movement automatically * * **Common Use Cases:** * - Interactive 3D cursors or pointers * - Look-at effects combined with {@link LookAtConstraint} * - Floating UI elements that track cursor * - Interactive product showcases * - 3D header effects and hero sections * - Virtual laser pointers in XR experiences * * @example Basic cursor follow with smooth damping * ```ts * const follower = new Object3D(); * follower.position.set(0, 0, -5); // Initial position 5 units from camera * follower.addComponent(CursorFollow, { * damping: 0.2, // Smooth following with 200ms damping * keepDistance: true, // Maintain initial distance * useFullPage: true // Track cursor across entire page * }); * scene.add(follower); * ``` * * @example Surface-snapping cursor with raycast * ```ts * const cursor = new Object3D(); * cursor.addComponent(CursorFollow, { * snapToSurface: true, // Snap to surfaces in the scene * keepDistance: false, // Don't maintain distance when snapping * damping: 0.1 // Quick, responsive movement * }); * scene.add(cursor); * ``` * * @example Instant cursor following (no damping) * ```ts * gameObject.addComponent(CursorFollow, { * damping: 0, // Instant movement * useFullPage: false // Only track within canvas * }); * ``` * * @example Interactive 3D header that looks at cursor * ```ts * const character = loadModel("character.glb"); * const lookTarget = new Object3D(); * lookTarget.addComponent(CursorFollow, { damping: 0.3 }); * character.addComponent(LookAtConstraint, { target: lookTarget }); * scene.add(lookTarget, character); * ``` * * - Example: [Look At Cursor sample](https://engine.needle.tools/samples/look-at-cursor-interactive-3d-header/) - Combines CursorFollow with LookAt for an interactive 3D header * * @see {@link LookAtConstraint} - Commonly combined with CursorFollow for look-at effects * @see {@link PointerEvents} - For more complex pointer interaction handling * @see {@link DragControls} - For dragging objects in 3D space * @see {@link OrbitControls} - For camera controls that work alongside CursorFollow * @see {@link Context.input} - The input system that provides cursor position * @see {@link Context.physics.raycastFromRay} - Used when snapToSurface is enabled * * @summary Makes objects follow the cursor/touch position in 3D space * @category Interactivity * @category Web * @group Components * @component */ export declare class CursorFollow extends Behaviour { static readonly NAME = "CursorFollow"; /** * Damping factor controlling how smoothly the object follows the cursor (in seconds). * * This value determines the "lag" or smoothness of the following motion: * - `0`: Instant movement, no damping (object snaps directly to cursor position) * - `0.1-0.2`: Quick, responsive following with slight smoothing * - `0.3-0.5`: Noticeable smooth trailing effect * - `1.0+`: Slow, heavily damped movement * * The damping uses delta time, so the movement speed is framerate-independent and * provides consistent behavior across different devices. * * **Tip:** For look-at effects, values between 0.2-0.4 typically feel most natural. * For cursor indicators, 0.1 or less provides better responsiveness. * * @default 0 */ damping: number; /** * Whether the object should track the cursor across the entire webpage or only within the canvas. * * **When `true` (default):** * - The object follows the cursor anywhere on the page, even outside the canvas bounds * - Perfect for look-at effects where you want continuous tracking * - Great for embedded 3D elements that should feel aware of the whole page * - Example: A 3D character in a hero section that watches the cursor as you scroll * * **When `false`:** * - The object only follows the cursor when it's inside the Needle Engine canvas * - Useful for contained experiences where the 3D element shouldn't react to external cursor movement * - Better for multi-canvas scenarios or when you want isolated 3D interactions * * **Note:** When enabled, the component listens to `window.pointermove` events to track the * full-page cursor position. When disabled, it uses the context's input system which is * canvas-relative. * * @see {@link Context.input.mousePositionRC} for canvas-relative cursor position * @default true */ useFullPage: boolean; /** * Whether to maintain the object's initial distance from the camera while following the cursor. * * **When `true` (default):** * - The object stays at a constant distance from the camera, moving in a spherical arc around it * - Creates a natural "floating at cursor position" effect * - The object's depth remains consistent as you move the cursor around * - Perfect for cursors, pointers, or look-at targets * * **When `false`:** * - The object's distance can change based on where the cursor projects in 3D space * - More useful when combined with {@link snapToSurface} to follow surface geometry * - Can create unusual depth behavior if not carefully configured * * **How it works:** * On the first update, the component measures the distance from the object to the camera. * This initial distance is then maintained throughout the object's lifetime (unless {@link updateDistance} is called). * The object moves along a ray from the camera through the cursor, staying at this fixed distance. * * @see {@link updateDistance} to manually recalculate the distance * @default true */ keepDistance: boolean; /** * When enabled, the object snaps to the surfaces of other objects in the scene using raycasting. * * **How it works:** * After positioning the object at the cursor location, a raycast is performed backwards toward the camera. * If the ray hits any surface, the object is moved to that hit point, effectively "snapping" to the surface. * * **Use cases:** * - 3D paint or decal placement tools * - Surface markers or waypoints * - Interactive object placement in AR/VR * - Cursor that follows terrain or mesh surfaces * * **Important notes:** * - Requires objects in the scene to have colliders for raycasting to work * - Works best with {@link keepDistance} set to `false` to allow depth changes * - Can be combined with {@link damping} for smooth surface following * - The raycast uses the physics system's raycast functionality * * **Debug mode:** * Add `?debugcursor` to your URL to visualize the raycast hits with green debug lines. * * @see {@link Context.physics.raycastFromRay} for the underlying raycast implementation * @see {@link keepDistance} should typically be false when using surface snapping * @default false */ snapToSurface: boolean; private _distance; /** * Manually recalculates the distance between the object and the camera. * * By default, the distance is calculated once when the component starts and then maintained * when {@link keepDistance} is enabled. Use this method to update the reference distance * if the camera or object has moved significantly. * * **Use cases:** * - After teleporting the camera or object * - When switching between different camera positions * - After zoom operations that change the desired following distance * - Dynamically adjusting the cursor's depth in response to user input * * @param force - If `true`, forces a recalculation even if {@link keepDistance} is enabled and distance was already set * * @example Recalculate distance after camera movement * ```ts * const cursorFollow = gameObject.getComponent(CursorFollow); * camera.position.set(0, 0, 10); // Move camera * cursorFollow?.updateDistance(true); // Update the reference distance * ``` */ updateDistance(force?: boolean): void; /** @internal */ awake(): void; /** @internal */ onEnable(): void; /** @internal */ onDisable(): void; private _ndc_x; private _ndc_y; private _onPointerMove; /** @internal */ lateUpdate(): void; }