npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vibe-starter-3d-test

v0.1.4

Published

3D library for React applications

Readme

Vibe Starter 3D TEST

This project is a foundational library for 3D projects used in Verse8. It provides essential components to start 3D development.

Introduction

Vibe Starter 3D is a library designed to easily integrate 3D elements into React applications. Built on Three.js, React Three Fiber, and related technologies, it specifically offers functionality for easily rendering and animating 3D character models in VRM and GLTF/GLB formats.

Installation

npm install vibe-starter-3d

Architecture Overview

Controller-Player Separation

Vibe Starter 3D follows a clear separation between control systems and player objects:

  • Controllers (FreeViewController, FirstPersonViewController, etc.) are independent input and camera management systems
  • RigidBodyPlayer is the actual player character in the physics world
  • Controllers automatically detect and remotely control the RigidBodyPlayer component in the scene
  • This architecture allows for flexible controller switching without affecting the player object itself

Key Features

Components

  • CharacterRenderer: Unified component for rendering VRM and GLTF/GLB models
  • View Controllers: Components that automatically detect and control existing RigidBodyPlayer in the scene
    • FreeViewController: Free camera perspective controller
    • FirstPersonViewController: First-person perspective controller
    • QuarterViewController: Quarter view perspective controller
    • SideViewController: Side view perspective controller
    • SimulationViewController: Simulation view perspective controller with edge scrolling and camera controls
    • FlightViewController: Flight perspective controller with configurable handle
    • PointToMoveController: Point-to-move perspective controller with configurable camera mode and zoom
  • Physics Components: Components for physics-based interactions
    • RigidBodyObject: Base component for physics objects with trigger event handling
    • RigidBodyPlayer: Player-specific physics component with automatic collider creation
  • NetworkObject: Component for handling network-connected objects
    • NetworkObjectProps: Defines network object properties
    • NetworkObjectHandle: Handle for manipulating network objects
  • FollowLight: Component for creating a configurable light source that can follow a target.

Utilities

  • controllerUtils: Controller-related utilities
    • position, camInitDis, camMinDis, camMaxDis, floatHeight
  • collisionUtils: Collision detection and handling utilities
    • isInCollisionGroup, createLocalPlayerCollisionGroups, collisionGroups, etc.
  • typeUtils: Type conversion utilities between Three.js and React Three Fiber
    • toVector3, toVector3Array, toQuaternion, toQuaternionArray etc.
  • characterUtils: Character calculation utilities
    • CharacterUtils.targetHeight(): Returns target height with fallback to default (1.6m)
    • CharacterUtils.capsuleRadius(): Calculates capsule collider radius based on character height
    • CharacterUtils.capsuleHalfHeight(): Calculates capsule collider half-height based on character height

Hooks

  • useMouseControls: Hook for handling mouse input controls
  • useControllerState: Hook for accessing and managing controller state
    • setEnableInput, setPosition, setRotation, setVelocity, setMoveToPoint, isPointMoving

Types

  • Animation-related Types:

    • AnimationType: A string type used to identify specific animations. Developers can define unique string keys for various actions a character can perform, such as 'IDLE', 'WALK', 'RUN', 'JUMP', 'ATTACK', etc. This AnimationType serves as the key in AnimationConfigMap to map to specific animation configurations (AnimationConfig). It is also used with currentAnimationRef to specify the animation currently playing and is passed as an argument to the onAnimationComplete callback to indicate which animation has finished.
    • AnimationConfig: An interface defining the configuration for a single animation. It includes the following properties:
      • url: string: The path or URL to the animation file (e.g., an .fbx or .glb file containing the animation data). This is a required field.
      • loop: boolean: Specifies whether the animation should repeat from the beginning once it reaches the end. Defaults to true.
      • duration?: number: An optional duration for the animation in seconds. If not provided, the animation's own inherent duration will be used. This can be useful for overriding or standardizing animation lengths.
      • clampWhenFinished?: boolean: If set to true, and the animation is not looping (loop: false), the animation will hold on its last frame when it finishes playing. If false, it will revert to the model's base pose (or the first frame of the animation, depending on the renderer's behavior). Defaults to false.
    • AnimationConfigMap: This type is defined as Record<AnimationType, AnimationConfig>. It represents an object where keys are AnimationType strings (e.g., 'IDLE', 'WALK', 'RUN') and values are their respective AnimationConfig objects. It serves as a dictionary or a central registry for all animations available to a character model, allowing easy access to the configuration of each animation by its type. Example:
      const characterAnimations: AnimationConfigMap = {
        idle: { url: '/animations/character-idle.glb', loop: true },
        jump: { url: '/animations/character-jump.glb', loop: false, clampWhenFinished: true },
        attack: { url: '/animations/character-attack.glb', loop: false, duration: 0.75 },
      };
      This map is then passed to the CharacterRenderer to provide all necessary animation data.
  • Controller-related Types:

    • ControllerProps: Defines controller component properties
    • FollowLightProps: Settings for following light (position, intensity, color, etc.)
    • ControllerMode: Controller modes such as 'CameraBasedMovement', 'FixedCamera', 'PointToMove', etc.
  • Physics-related Types:

    • RigidBodyObjectProps: Properties for physics objects with trigger event handling
    • RigidBodyPlayerProps: Properties for player physics objects with auto-collider creation
    • RigidBodyObjectRef: Reference interface for player physics objects that extends RapierRigidBody
    • RigidBodyPlayerRef: Reference interface for player physics objects that extends RapierRigidBody with additional bottomY property

Constants

  • Animation Constants:

    • ANIMATION_KEYWORDS: Keyword mapping for identifying animation types
    • rigmap: Rig mapping constants for character animation
  • Collision Group Constants:

    • CollisionGroup: Defines collision detection groups (Default, Player, Enemy, NPC, Projectile, etc.)

Character Utilities (CharacterUtils)

The CharacterUtils object provides utility functions for calculating player character dimensions and collider properties. These utilities are particularly useful when working with RigidBodyPlayer components and custom player setups.

CharacterUtils Methods

| Method | Parameters | Return Type | Description | | ---------------------------- | ----------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | targetHeight(value?) | value?: number | number | Returns the target height for a player character. If no value is provided, defaults to 1.6 meters. | | capsuleRadius(height?) | height?: number | number | Calculates the appropriate capsule collider radius based on character height. Formula: Math.max(height / 5, 0.3) | | capsuleHalfHeight(height?) | height?: number | number | Calculates the capsule collider half-height based on character height. Formula: Math.max((height - radius * 2) / 2, 0.25) |

Usage Examples

import { CharacterUtils } from 'vibe-starter-3d';

// Get default player height
const defaultHeight = CharacterUtils.targetHeight(); // Returns 1.6

// Get custom player height with fallback
const customHeight = CharacterUtils.targetHeight(1.8); // Returns 1.8
const fallbackHeight = CharacterUtils.targetHeight(undefined); // Returns 1.6

// Calculate collider dimensions for a 1.8m tall character
const height = 1.8;
const radius = CharacterUtils.capsuleRadius(height); // Returns 0.36
const halfHeight = CharacterUtils.capsuleHalfHeight(height); // Returns 0.54

// Use with RigidBodyPlayer
function CustomPlayer() {
  const playerHeight = 1.75;

  return (
    <RigidBodyPlayer targetHeight={CharacterUtils.targetHeight(playerHeight)}>
      <CharacterRenderer
        url="/models/player.vrm"
        targetHeight={playerHeight}
        // ... other props
      />
    </RigidBodyPlayer>
  );
}

// Manual collider setup (if autoCreateCollider is false)
function ManualColliderPlayer() {
  const height = 1.9;
  const radius = CharacterUtils.capsuleRadius(height);
  const halfHeight = CharacterUtils.capsuleHalfHeight(height);

  return (
    <RigidBodyPlayer targetHeight={height} autoCreateCollider={false}>
      <CapsuleCollider args={[halfHeight, radius]} />
      <CharacterRenderer url="/models/tall-player.vrm" targetHeight={height} />
    </RigidBodyPlayer>
  );
}

Design Rationale

The CharacterUtils calculations are designed to create proportional and realistic colliders:

  • Radius Calculation: Uses 1/5 of the character height with a minimum of 0.3m to ensure reasonable collision detection
  • Half-Height Calculation: Accounts for the capsule's rounded ends by subtracting the radius diameter from the total height
  • Minimum Values: Ensures colliders never become too small to function properly in physics simulations

Collision Groups (CollisionGroup)

Below is the complete list of collision groups used in the Rapier physics engine. Each value represents a group index between 0 and 15.

| Group | Value | Description | | -------------- | ----- | ----------------------------------------------- | | Default | 0 | General object (default) | | Player | 1 | Player character | | Enemy | 2 | Enemy character | | NPC | 3 | Neutral character | | Projectile | 4 | Projectile | | Environment | 5 | Walls, floors, roads, structures, etc. | | Item | 6 | Interactive or acquirable item | | Trigger | 7 | Event trigger | | UI | 8 | For UI raycast | | Sensor | 9 | Invisible sensor (vision, proximity, etc.) | | DeadBody | 10 | Deceased unit | | LocalPlayer | 11 | Self in multiplayer | | RemotePlayer | 12 | Other user characters in multiplayer | | Vehicle | 13 | Vehicle/mount | | Terrain | 14 | Terrain for special judgment | | Particle | 15 | Particle effect (for hit judgment or to ignore) |

Detailed Interface Descriptions

CharacterRendererProps

The CharacterRenderer component is a unified component for rendering and animating 3D character models in VRM and GLTF/GLB formats. It allows controlling various aspects like visibility, resource information, animation configuration, and current action state through its props.

The CharacterRenderer component accepts the following CharacterRendererProps:

| Property | Type | Description | Default Value | | :-------------------------------------------- | :-------------------------------------- | :----------------------------------------------------------------------------------------- | :------------ | | visible | boolean | Determines whether the character model is displayed on the screen. | true | | url | string | Model path (file path or URL). | Required | | animationConfigMap | AnimationConfigMap<AnimationType> | A map defining animation settings for each action type. | Required | | currentAnimationRef | RefObject<AnimationType \| undefined> | A Ref object pointing to the current character animation type. | Required | | targetHeight | number | Target height for the character model. | Optional | | disableAnimationAdjustmentToModelProportion | boolean | If true, animation won't be adjusted to fit the model's body proportion (GLTF/GLB only). | Optional | | onAnimationComplete | (type: AnimationType) => void | Optional callback function invoked when a specific animation completes. | Optional | | onSizeChange | (boundingBox: THREE.Box3) => void | Optional callback function invoked when the model's bounding box size information changes. | Optional | | fallback | React.ReactNode | Optional fallback component for Suspense when loading unsupported file formats. | Optional | | children | React.ReactNode | Optional child components to render inside the CharacterRenderer. | Optional |

Usage Example:

import { CharacterRenderer } from 'vibe-starter-3d';
import { useRef, useState } from 'react';
import * as THREE from 'three';

function MyCharacter() {
  const currentAnimationRef = useRef<'idle' | 'walk' | 'run'>('idle');
  const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);

  const animationConfigMap = {
    idle: { url: '/animations/idle.glb', loop: true },
    walk: { url: '/animations/walk.glb', loop: true },
    run: { url: '/animations/run.glb', loop: true },
  };

  const handleSizeChange = (box: THREE.Box3) => {
    setBoundingBox(box);
    console.log('Character bounding box updated:', box);
  };

  const handleAnimationComplete = (type: string) => {
    console.log('Animation completed:', type);
  };

  return (
    <CharacterRenderer
      url="/models/character.vrm"
      animationConfigMap={animationConfigMap}
      currentAnimationRef={currentAnimationRef}
      targetHeight={1.8}
      onSizeChange={handleSizeChange}
      onAnimationComplete={handleAnimationComplete}
    />
  );
}

FollowLightProps

Defines settings for the light that follows the target object.

| Property | Type | Description | Default Value | | -------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | offset | Vector3 | Light position offset relative to the followObjectRef or RigidBodyPlayer. Defaults to [30, 100, 30] (shining down from above and behind). This only takes effect if followObjectRef is set, or if it defaults to the RigidBodyPlayer group. Has no effect if the light is not following any object. | [30, 100, 30] | | intensity | number | Light intensity. | 1 | | color | Color | Light color. | 0xffffff (white) | | shadowCameraNear | number | Near plane for the shadow camera's perspective frustum. | 0.5 | | shadowCameraFar | number | Far plane for the shadow camera's perspective frustum. | 500 | | shadowCameraLeft | number | Left plane for the shadow camera's perspective frustum. | -75 | | shadowCameraRight | number | Right plane for the shadow camera's perspective frustum. | 75 | | shadowCameraTop | number | Top plane for the shadow camera's perspective frustum. | 75 | | shadowCameraBottom | number | Bottom plane for the shadow camera's perspective frustum. | -75 | | shadowMapSize | number[]|Vector2 | Shadow map size (width, height). Must be powers of 2. Higher values improve shadow quality but increase computation time. | [8192, 8192] | | shadowBias | number | Shadow map bias. Very tiny adjustments (e.g., 0.0001) can help reduce shadow artifacts. | 0 | | shadowNormalBias | number | Offset along the object normal for shadow map queries. Helps reduce shadow acne, especially at shallow angles. | 0 | | followObjectRef | React.RefObject<THREE.Object3D> | null | Optional reference to a THREE.Object3D that the light should follow. If not set, the light will follow the RigidBodyPlayer group. | null |

FollowLight Component

The FollowLight component creates a directional light that can be configured to follow an object within the scene. It is typically used to ensure a character or a specific area remains well-lit as it moves. The behavior and appearance of the light, including its offset from the target, intensity, color, and shadow casting properties, are defined by FollowLightProps.

Usage Example:

This component is designed to be used within your 3D scene setup. By default, if not provided with a followObjectRef (see FollowLightProps), it will attempt to follow the RigidBodyPlayer group.

import { FollowLight } from 'vibe-starter-3d';
import { Canvas } from '@react-three/fiber'; // Or your existing R3F setup

function MySceneWithFollowLight() {
  return (
    <>
      {' '}
      {/* Or <Canvas> if this is the root of your R3F scene */}
      {/* ... other lights, environment, and 3D objects ... */}
      {/* Add FollowLight to the scene */}
      {/* It can be customized using FollowLightProps */}
      <FollowLight offset={[30, 100, 30]} intensity={1.2} color="white" />
      {/* ... your player character and other elements ... */}
      {/* For example, if used with a FreeViewController: */}
      {/*
      <FreeViewController />
      <RigidBodyPlayer>
        <PlayerModel />
      </RigidBodyPlayer>
      */}
      {/* The FollowLight will automatically try to follow the RigidBodyPlayer in this setup */}
    </>
  );
}

Note: For detailed information on the available properties, refer to the FollowLightProps section.

ControllerProps

Defines the basic properties of all controller components. Controllers are independent components that automatically detect and control any RigidBodyPlayer present in the scene. FreeViewController, FirstPersonViewController, QuarterViewController, SideViewController, and SimulationViewController all inherit (extend) this interface.

| Property | Type | Description | Default Value | | --------------------- | --------- | ---------------------------------- | ------------- | | camInitDis | number | Initial camera distance | -4 | | camMinDis | number | Minimum camera zoom-in distance | -4 | | camMaxDis | number | Maximum camera zoom-out distance | -4 | | floatHeight | number | Height above ground (m) | 0.01 | | followCameraForward | boolean | Whether to follow camera direction | false |

QuarterViewControllerProps

Defines properties for the quarter view controller. (extends ControllerProps)

| Property | Type | Description | Default Value | | ----------------- | ----------------------------------- | ------------------------------- | --------------- | | followCharacter | boolean | Whether to follow the character | false | | inputMode | 'keyboard' | 'pointToMove' | Input mode | 'pointToMove' | | cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' |

Note: This interface inherits all properties from ControllerProps.

SideViewControllerProps

Defines properties for the side view controller. (extends ControllerProps)

| Property | Type | Description | Default Value | | ------------ | ----------------------------------- | ----------- | ---------------- | | cameraMode | 'perspective' | 'orthographic' | Camera mode | 'orthographic' |

Note: This interface inherits all properties from ControllerProps.

SimulationViewControllerProps

Defines properties for the simulation view controller. (extends ControllerProps)

| Property | Type | Description | Default Value | | ------------ | ----------------------------------- | ----------- | --------------- | | cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' |

Note: This interface inherits all properties from ControllerProps. The SimulationViewController provides edge scrolling functionality and smooth camera controls optimized for simulation environments.

FlightViewControllerProps

Defines properties for the flight view controller. (Does not inherit ControllerProps)

| Property | Type | Description | Default Value | | ------------------------------- | ------------------------- | ---------------------------------------------------------------- | ------------- | | minSpeed | number | Minimum flight speed (m/s) | 0 | | maxSpeed | number | Maximum flight speed (m/s) | 120 | | speedIncreasePerSecond | number | Speed increase per second (m/s) | 20 | | speedDecreasePerSecond | number | Speed decrease per second (m/s) | 80 | | pitchRotationSpeed | number | Pitch rotation speed | 0.5 | | rollRotationSpeed | number | Roll rotation speed | Math.PI | | directionRotationAcceleration | number | Direction rotation acceleration | 0.3 | | maxRollAngle | number | Maximum roll angle | Math.PI / 2 | | maxPitchAngle | number | Maximum pitch angle | Math.PI / 2 | | cameraOffset | Vector3 | Camera offset position | [0, 3, 15] | | onSpeedChange | (speed: number) => void | Optional callback function invoked when the flight speed changes | Optional |

PointToMoveControllerProps

Defines properties for the point-to-move view controller. (extends ControllerProps)

| Property | Type | Description | Default Value | | ----------------- | ----------------------------------- | ---------------------------------------- | --------------- | | followCharacter | boolean | Whether the camera follows the character | false | | cameraMode | 'perspective' | 'orthographic' | Camera mode | 'perspective' | | maxVelLimit | number | Maximum velocity limit for the character | 3 |

Note: This interface inherits all properties from ControllerProps.

RigidBodyObject Component

The RigidBodyObject component is an enhanced wrapper around the standard RigidBody component from @react-three/rapier. It provides simplified trigger event handling, making it extremely easy to implement interactions between different objects in your physics simulation.

Why use RigidBodyObject instead of RigidBody?

We strongly recommend using RigidBodyObject instead of the standard RigidBody component because:

  1. Unified Collision Events: Automatically handles both physical collisions and sensor intersections, providing unified onTriggerEnter and onTriggerExit callbacks for all collision types
  2. Event Deduplication: Prevents duplicate trigger events that can occur with rapid collision detection
  3. Comprehensive Detection: Triggers on both solid collisions and sensor intersections, making it easier to handle all types of object interactions
  4. Seamless Integration: Drop-in replacement for RigidBody with all the same props plus enhanced functionality

Usage Example:

import { RigidBodyObject } from 'vibe-starter-3d';
import { CuboidCollider } from '@react-three/rapier';

function InteractiveObject() {
  const handleTriggerEnter = (payload) => {
    console.log('Collision detected with:', payload.other.rigidBodyObject);
    // Handle interaction logic here (works for both physical collisions and sensor triggers)
  };

  const handleTriggerExit = (payload) => {
    console.log('Collision ended with:', payload.other.rigidBodyObject);
    // Handle cleanup logic here (works for both physical collisions and sensor triggers)
  };

  return (
    <RigidBodyObject type="fixed" onTriggerEnter={handleTriggerEnter} onTriggerExit={handleTriggerExit}>
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="blue" />
      </mesh>
      <CuboidCollider args={[0.5, 0.5, 0.5]} sensor />
    </RigidBodyObject>
  );
}

RigidBodyObjectProps

Defines properties for physics objects with trigger event handling. (extends RigidBodyProps)

| Property | Type | Description | Default Value | | ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------- | ------------- | | onTriggerEnter | (payload: CollisionPayload) => void | Callback when collision starts (includes both physical collisions and sensor intersections) | Optional | | onTriggerExit | (payload: CollisionPayload) => void | Callback when collision ends (includes both physical collisions and sensor intersections) | Optional |

Note: This interface extends RigidBodyProps from @react-three/rapier and adds trigger event handling capabilities. All standard RigidBody properties are available.

RigidBodyPlayer Component

The RigidBodyPlayer component is a specialized physics component designed specifically for player characters. It extends RigidBodyObject with player-specific functionality and automatic controller integration.

⚠️ CRITICAL ARCHITECTURE UNDERSTANDING:

Controllers are NOT Player Objects - They are Control Systems

  1. Controllers are Independent: Controller components (FreeViewController, FirstPersonViewController, etc.) are standalone control systems that do NOT create or own any player objects
  2. RigidBodyPlayer is the Actual Player: The RigidBodyPlayer component represents the actual player character in the physics world
  3. Automatic Detection and Control: Controllers automatically scan the scene to find the RigidBodyPlayer component and control it remotely
  4. Single Player Requirement: There must be EXACTLY ONE RigidBodyPlayer in the scene for controllers to function - multiple instances will cause conflicts
  5. Separation of Concerns: Controllers handle input and camera logic, while RigidBodyPlayer handles physics and character representation

Key Features:

  • Automatic Collider Creation: Automatically generates a CapsuleCollider based on character height
  • Controller Detection Target: Serves as the target that controller components automatically detect and control
  • Physics Initialization: Handles ground detection and proper positioning automatically
  • Player-Specific Properties: Includes bottomY property for ground-relative positioning
  • Player Collision Events: Supports onTriggerEnter and onTriggerExit for handling all types of player collisions (physical collisions and sensor intersections)

Usage Example:

import { RigidBodyPlayer, FreeViewController, CharacterRenderer } from 'vibe-starter-3d';

function PlayerScene() {
  // Handle player collisions with other objects
  const handlePlayerTriggerEnter = (payload) => {
    console.log('Player collision started with:', payload.other.rigidBodyObject);
    // Handle player collision logic (e.g., item pickup, door opening, obstacle hit, etc.)
  };

  const handlePlayerTriggerExit = (payload) => {
    console.log('Player collision ended with:', payload.other.rigidBodyObject);
    // Handle cleanup logic (e.g., hide interaction UI, stop collision effects, etc.)
  };

  return (
    <>
      {/* Controller automatically finds and controls the RigidBodyPlayer below */}
      <FreeViewController />

      {/* CRITICAL: Only ONE RigidBodyPlayer per scene - this is what the controller will control */}
      <RigidBodyPlayer targetHeight={1.6} position={[0, 0, 0]} onTriggerEnter={handlePlayerTriggerEnter} onTriggerExit={handlePlayerTriggerExit}>
        <CharacterRenderer url="/models/player-character.vrm" animationConfigMap={animationConfig} currentAnimationRef={currentAnimationRef} />
      </RigidBodyPlayer>
    </>
  );
}

// ❌ WRONG: Multiple RigidBodyPlayer instances
function WrongUsage() {
  return (
    <>
      {/* Controller cannot determine which player to control! */}
      <FreeViewController />
      <RigidBodyPlayer>
        <CharacterRenderer url="/player1.vrm" />
      </RigidBodyPlayer>
      <RigidBodyPlayer>
        {/* This will cause conflicts - controller needs exactly ONE target! */}
        <CharacterRenderer url="/player2.vrm" />
      </RigidBodyPlayer>
    </>
  );
}

// ✅ CORRECT: Use RigidBodyObject for non-player characters
function CorrectUsage() {
  return (
    <>
      {/* Controller automatically detects and controls the single RigidBodyPlayer */}
      <FreeViewController />

      {/* The ONE player that the controller will control */}
      <RigidBodyPlayer>
        <CharacterRenderer url="/player.vrm" />
      </RigidBodyPlayer>

      {/* Use RigidBodyObject for NPCs and other characters - these are NOT controlled by the controller */}
      <RigidBodyObject type="dynamic">
        <CharacterRenderer url="/npc1.vrm" />
      </RigidBodyObject>
      <RigidBodyObject type="dynamic">
        <CharacterRenderer url="/npc2.vrm" />
      </RigidBodyObject>
    </>
  );
}

RigidBodyPlayerProps

Defines properties for player physics objects with automatic collider creation. (extends RigidBodyObjectProps)

| Property | Type | Description | Default Value | | -------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------- | | targetHeight | number | Height of the character to be controlled (unit: meters) | 1.6 | | autoCreateCollider | boolean | Whether to automatically create a default CapsuleCollider | true | | offsetY | number | Vertical offset for the children group (unit: meters) | Optional | | onTriggerEnter | (payload: CollisionPayload) => void | Callback when player collision starts (includes both physical collisions and sensor intersections) | Optional | | onTriggerExit | (payload: CollisionPayload) => void | Callback when player collision ends (includes both physical collisions and sensor intersections) | Optional |

Note: This interface extends RigidBodyObjectProps and provides player-specific functionality including automatic capsule collider creation based on character height. This component serves as the target that controller systems automatically detect and control. Use onTriggerEnter and onTriggerExit to handle all types of player collisions with other objects in the scene (e.g., item pickups, door triggers, interaction zones, physical obstacles).

RigidBodyPlayerRef Interface

⚠️ CRITICAL: RigidBodyPlayerRef is completely identical to RapierRigidBody interface!

RigidBodyPlayerRef is an interface that extends RapierRigidBody, allowing you to use all RapierRigidBody methods and properties exactly as they are, with an additional bottomY property.

export interface RigidBodyPlayerRef extends RapierRigidBody {
  readonly bottomY: number;
}

Key Features:

  1. Complete RapierRigidBody Compatibility: All methods and properties of RapierRigidBody can be used identically
  2. Additional bottomY Property: A read-only property for easy access to the player's bottom Y coordinate
  3. Type Safety: Full TypeScript type support

Usage Examples:

import { RigidBodyPlayer, RigidBodyPlayerRef } from 'vibe-starter-3d';
import { useRef, useEffect } from 'react';

function PlayerComponent() {
  const playerRef = useRef<RigidBodyPlayerRef>(null);

  useEffect(() => {
    if (!playerRef.current) return;

    // ✅ All RapierRigidBody methods are available
    const position = playerRef.current.translation(); // { x, y, z }
    const velocity = playerRef.current.linvel(); // { x, y, z }
    const rotation = playerRef.current.rotation(); // Quaternion

    // ✅ Use additional bottomY property
    const groundLevel = playerRef.current.bottomY; // number

    // ✅ Control player using RapierRigidBody methods
    playerRef.current.setTranslation({ x: 0, y: 10, z: 0 }, true);
    playerRef.current.setLinvel({ x: 0, y: 0, z: 5 }, true);
    playerRef.current.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true);

    // ✅ Set physics properties
    playerRef.current.setGravityScale(1.0, true);
    playerRef.current.setEnabled(true);

    console.log('Player position:', position);
    console.log('Player velocity:', velocity);
    console.log('Player bottom Y:', groundLevel);
  }, []);

  return (
    <RigidBodyPlayer ref={playerRef} targetHeight={1.8}>
      {/* Character content */}
    </RigidBodyPlayer>
  );
}

// Basic RigidBodyPlayerRef usage example
function PlayerWithRef() {
  const playerRef = useRef<RigidBodyPlayerRef>(null);

  useEffect(() => {
    if (!playerRef.current) return;

    // Monitor player state
    const logPlayerState = () => {
      const position = playerRef.current!.translation();
      const velocity = playerRef.current!.linvel();
      const bottomY = playerRef.current!.bottomY;

      console.log('Player position:', position);
      console.log('Player velocity:', velocity);
      console.log('Player bottom Y:', bottomY);
    };

    // Periodically check player state
    const interval = setInterval(logPlayerState, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <RigidBodyPlayer ref={playerRef} targetHeight={1.8}>
      <CharacterRenderer url="/models/player.vrm" animationConfigMap={animationConfig} currentAnimationRef={currentAnimationRef} />
    </RigidBodyPlayer>
  );
}

Available Key RapierRigidBody Methods:

| Method | Description | Example | | -------------------------------- | -------------------------------------------- | --------------------------------------------------------------- | | translation() | Get current position | const pos = playerRef.current.translation() | | setTranslation(pos, wakeUp) | Set position | playerRef.current.setTranslation({x: 0, y: 5, z: 0}, true) | | linvel() | Get linear velocity | const vel = playerRef.current.linvel() | | setLinvel(vel, wakeUp) | Set linear velocity | playerRef.current.setLinvel({x: 0, y: 0, z: 0}, true) | | rotation() | Get rotation | const rot = playerRef.current.rotation() | | setRotation(rot, wakeUp) | Set rotation | playerRef.current.setRotation({x: 0, y: 0, z: 0, w: 1}, true) | | angvel() | Get angular velocity | const angVel = playerRef.current.angvel() | | setAngvel(vel, wakeUp) | Set angular velocity | playerRef.current.setAngvel({x: 0, y: 0, z: 0}, true) | | setGravityScale(scale, wakeUp) | Set gravity scale | playerRef.current.setGravityScale(1.0, true) | | setEnabled(enabled) | Enable/disable physics | playerRef.current.setEnabled(true) | | bottomY | Additional Property: Bottom Y coordinate | const groundLevel = playerRef.current.bottomY |

CollisionPayload Interface

The CollisionPayload interface is provided by @react-three/rapier and contains detailed information about collision events. It is used in both onTriggerEnter and onTriggerExit callbacks to provide context about the collision.

CollisionPayload Structure

interface CollisionPayload {
  target: {
    rigidBody: RapierRigidBody; // The RigidBody that triggered the event
    collider: Collider; // The Collider that triggered the event
  };
  other: {
    rigidBody: RapierRigidBody; // The other RigidBody involved in the collision
    collider: Collider; // The other Collider involved in the collision
  };
}

Key Properties

| Property | Type | Description | | ------------------ | ----------------- | ----------------------------------------------------------------------------------- | | target.rigidBody | RapierRigidBody | The RigidBody component that owns the trigger event (usually your player or object) | | target.collider | Collider | The Collider component that detected the collision | | other.rigidBody | RapierRigidBody | The RigidBody of the object that entered/exited the trigger zone | | other.collider | Collider | The Collider of the object that entered/exited the trigger zone |

Useful Methods and Properties

RigidBody Methods:

  • rigidBody.translation() - Get the current position as {x, y, z}
  • rigidBody.rotation() - Get the current rotation as a quaternion
  • rigidBody.userData - Access custom data attached to the RigidBody

Collider Methods:

  • collider.translation() - Get the collider's position
  • collider.isSensor() - Check if the collider is a sensor (trigger zone)
  • collider.handle - Unique identifier for the collider

Practical Usage Examples

// Example 1: Item pickup system
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
  const otherType = payload.other.rigidBody?.userData?.type;

  if (otherType === 'ITEM') {
    const itemId = payload.other.rigidBody?.userData?.itemId;
    const itemPosition = payload.other.rigidBody.translation();

    console.log(`Player picked up item ${itemId} at position:`, itemPosition);
    // Handle item pickup logic
  }
};

// Example 2: Area detection with position checking
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
  const otherType = payload.other.rigidBody?.userData?.type;

  if (otherType === 'SAFE_ZONE') {
    const playerPosition = payload.target.rigidBody.translation();
    const zonePosition = payload.other.rigidBody.translation();

    console.log(`Player entered safe zone at ${zonePosition.x}, ${zonePosition.y}, ${zonePosition.z}`);
    // Enable safe zone effects
  }
};

// Example 3: Enemy detection with distance calculation
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
  const otherType = payload.other.rigidBody?.userData?.type;

  if (otherType === 'ENEMY') {
    const playerPos = payload.target.rigidBody.translation();
    const enemyPos = payload.other.rigidBody.translation();

    const distance = Math.sqrt(Math.pow(playerPos.x - enemyPos.x, 2) + Math.pow(playerPos.y - enemyPos.y, 2) + Math.pow(playerPos.z - enemyPos.z, 2));

    console.log(`Enemy detected at distance: ${distance}`);
    // Handle combat logic
  }
};

// Example 4: Checking collision handles to avoid duplicate events
const handlePlayerTriggerEnter = (payload: CollisionPayload) => {
  const targetHandle = payload.target.collider.handle;
  const otherHandle = payload.other.collider.handle;

  // Use handles for unique identification
  console.log(`Collision between ${targetHandle} and ${otherHandle}`);
};

Best Practices

  1. Always check userData.type: Use payload.other.rigidBody?.userData?.type to identify object types
  2. Handle null cases: RigidBody or userData might be null, always use optional chaining (?.)
  3. Use handles for deduplication: Collider handles are unique and useful for preventing duplicate events
  4. Access position data: Use translation() method to get current positions for distance calculations
  5. Store custom data in userData: Attach relevant information like IDs, types, or states to userData for easy access