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 🙏

© 2025 – Pkg Stats / Ryan Hefner

vibe-starter-3d-environment

v3.0.9

Published

vibe-starter-3d-environment

Readme

Vibe Starter 3D Environment

A React-based procedural terrain generation system for creating interactive 3D environments with realistic texturing and physics.

Quick Start Guide by Game Type

For FPS Games

Build maze-based environments using available tools:

  • Stage Component - Creates procedurally generated maze structures with walls and corridors
  • Navigator Component - Enables AI pathfinding and navigation through the maze

For Other Games

Create open-world environments by decorating the map with these components:

Apply the following three essential systems:

  1. Map Texture and Terrain

    • Use Procedural Mesh Generation Terrain System for open-world environments
    • Includes realistic texture splatting based on height and slope
    • See: Terrain Component documentation below
  2. 3D Object Placement

    • Use ModelPlacer for strategic positioning of objects, props, and decorations
    • Automatic terrain adaptation and collision detection
    • See: ModelPlacer Component documentation below
  3. Environmental Effects

    • Add atmosphere with weather systems (rain, snow, fog)
    • Create realistic water bodies with wave simulation
    • Generate dense grass fields with wind effects
    • See: Weather, Water, and Grass Component documentation below

Choose appropriate components based on your game requirements after reviewing the detailed documentation for each component.

Features

  • Procedural terrain generation with customizable parameters
  • Realistic texture splatting based on height and slope
  • Physics integration with React Three Fiber and Rapier
  • Customizable terrain characteristics (roughness, detail, size)
  • Seed-based generation for reproducible results
  • Utility functions for positioning objects on terrain

Installation

npm install vibe-starter-3d-environment

Usage

Basic Terrain

import { Terrain } from 'vibe-starter-3d-environment';

const World: React.FC = () => {
  return <Terrain width={100} depth={100} maxHeight={10} seed="my-world-seed" />;
};

Positioning Objects on Terrain

import { Terrain, TerrainData } from 'vibe-starter-3d-environment';
// IMPORTANT: terrainUtil must be imported to use calculatePositionOnTerrain
import { terrainUtil } from 'vibe-starter-3d-environment';
import { useRef, useState } from 'react';
import { Vector3 } from 'three';

const WorldWithObjects: React.FC = () => {
  const [terrainData, setTerrainData] = useState<TerrainData | null>(null);

  // Position an object on the terrain
  const treePosition: Vector3 = terrainUtil.calculatePositionOnTerrain(
    terrainData,
    [10, 20], // x, z coordinates
    0.5, // height offset
  );

  return (
    <>
      <Terrain width={256} depth={256} maxHeight={5} seed="my-world-seed" onTerrainDataReady={setTerrainData} />

      {terrainData && <mesh position={treePosition}>{/* Tree model */}</mesh>}
    </>
  );
};

Critical: Player Positioning System

The following pattern for setting a player's position on terrain is ABSOLUTELY CRITICAL and must NEVER be modified. This specific implementation ensures proper player positioning on the dynamically generated terrain.

1. Initialize Player Position State

Always start with a high Y value to prevent falling through terrain before the terrain is generated:

const [playerPosition, setPlayerPosition] = useState<[number, number, number]>([0, 100, 0]);

2. CRITICAL: Set Player Position DIRECTLY in handleTerrainDataReady Callback

  • This ensures the position is calculated and set immediately when terrain is ready
  • The offset of 20 is critical to prevent player from being too close to the terrain
  • NEVER use separate useMemo or useLayoutEffect for position calculation
  • ALWAYS calculate and set position within the terrain ready callback
import { useCallback } from 'react';
// IMPORTANT: terrainUtil MUST be imported from 'vibe-starter-3d-environment' package
// terrainUtil is definitely included in the vibe-starter-3d-environment package
import { TerrainData, terrainUtil } from 'vibe-starter-3d-environment';

export const terrainDataRef = { current: null as TerrainData | null };

const handleTerrainDataReady = useCallback(
  (newTerrainData: TerrainData) => {
    if (terrainDataRef.current) {
      return;
    }
    setTerrainData(newTerrainData);
    terrainDataRef.current = newTerrainData;

    // Calculate player position immediately when terrain data is ready
    const pos = terrainUtil.calculatePositionOnTerrain(
      newTerrainData,
      [0, 0], // Initial X, Z position for the player
      20, // CRITICAL: minimum initial offset must be 20
    );
    setPlayerPosition([pos.x, pos.y, pos.z]);

    // Set map physics ready to true once terrain data is available
    setMapPhysicsReady(true);
  },
  [setMapPhysicsReady],
);

3. CRITICAL: calculatePositionOnTerrain Function Signature

  • This function accepts ONLY 3 parameters: terrain data, position array, and offset
  • NEVER add width or depth as parameters - they are not needed
  • The function returns a Vector3 with x, y, z properties
  • IMPORTANT: You MUST import terrainUtil from 'vibe-starter-3d-environment' to use this function
  • terrainUtil is definitely included in the vibe-starter-3d-environment package
import { terrainUtil } from 'vibe-starter-3d-environment';

const pos = terrainUtil.calculatePositionOnTerrain(
  terrainData, // TerrainData object
  [x, z], // Initial position as [x, z] array
  offset, // Height offset (number)
);

4. Apply Position to Player Component

This ensures the player is properly positioned on the terrain. ALWAYS wait for terrainDataRef.current to be available before rendering Player:

{
  terrainDataRef.current && <Player position={playerPosition} />;
}

5. EXTREMELY CRITICAL: Do NOT Use Separate Position Calculation Logic

  • NEVER use useMemo for initial position calculation
  • NEVER use useLayoutEffect or useEffect for setting position
  • ALWAYS calculate and set position directly in handleTerrainDataReady
  • This prevents timing issues and ensures position is set exactly when terrain is ready

6. CRITICALLY IMPORTANT: terrainDataRef Pattern

Always use a ref to store terrain data to prevent re-renders. Check if data already exists to prevent duplicate processing:

export const terrainDataRef = { current: null as TerrainData | null };

7. Texture Splatting Configuration

  • Always set texture repeat values between 1-3 for proper rendering results

8. Terrain Component Properties

  • Always use maxHeight ≤ 10 unless there is a specific requirement for higher values
  • By default, keep maxHeight at 10 or lower for optimal performance and stability
  • Only use values above 10 when explicitly requested and absolutely necessary
  • Using maxHeight > 10 without specific requirements can cause rendering issues
<Terrain maxHeight={10} {...otherProps} /> // CORRECT for standard usage
<Terrain maxHeight={15} {...otherProps} /> // Only use when specifically required

WARNING: The direct position setting in handleTerrainDataReady is ESSENTIAL. This pattern ensures:

  • Player position is calculated with actual terrain data (not null)
  • Position is set at the exact moment terrain becomes available
  • No race conditions or timing issues
  • No falling through terrain

DO NOT attempt to "optimize" by separating position calculation from terrain ready callback. This unified approach has been carefully designed to handle terrain generation timing.

FAILURE TO FOLLOW THESE REQUIREMENTS WILL RESULT IN PLAYERS FALLING THROUGH TERRAIN.

Components

The library provides various components for creating immersive 3D environments:

Terrain Components

React-based package offering procedural terrain generation with texture splatting, water simulation, weather effects, and instanced grass rendering. Integrates with React Three Fiber and Rapier physics, providing seed-based generation for reproducible, customizable 3D environments. Implemented through the Terrain component from the vibe-starter-3d-environment package, allowing for seamless integration into React applications.

The terrain module provides procedural terrain generation with realistic texturing:

import React, { useState } from 'react';
import { Terrain, TerrainData, SplattingTexture, Water, GrassField, AnimatedFog } from 'vibe-starter-3d-environment';
import { DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';
import { Vector2 } from 'three';
import { EffectComposer, DepthOfField } from '@react-three/postprocessing';

// Basic usage
<Terrain width={100} depth={100} maxHeight={10} seed="my-seed" />;

// Advanced usage with texture splatting, water, grass, and fog
const TerrainExample: React.FC = () => {
  const [terrainData, setTerrainData] = useState<TerrainData | null>(null);

  const handleTerrainData = (data: TerrainData): void => {
    console.log('Terrain data ready:', data);
    setTerrainData(data);
  };

  const textures = React.useMemo<SplattingTexture[]>(
    () => [
      {
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_NORMAL,
          roughness: 0.8,
          metalness: 0.1,
          normalScale: new Vector2(1.5, 1.5),
        },
        repeat: 2,
        heightRange: [0.6, 1.0] as [number, number], // Top 40% for grass
        slopeRange: [0, Math.PI / 6] as [number, number], // Flatter areas
        heightBlendRange: 0.1,
      },
      {
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL,
          roughness: 0.9,
          metalness: 0.05,
        },
        repeat: 3,
        heightRange: [0.2, 1.0] as [number, number],
        slopeRange: [Math.PI / 6, Math.PI / 2] as [number, number], // Steeper areas
        heightBlendRange: 0.2,
      },
    ],
    [],
  );

  return (
    <>
      <Terrain
        width={200}
        depth={200}
        maxHeight={10}
        splatting={{
          textures,
          mode: 'both',
          blendingStrength: 1.2,
        }}
        onTerrainDataReady={handleTerrainData}
      />

      {/* Water with foam effects */}
      {terrainData && <Water position={[0, terrainData.waterLevel, 0]} size={[100, 100]} enableFoam={true} foamIntensity={0.5} />}

      {/* Grass field */}
      {terrainData && (
        <GrassField terrainWidth={200} terrainDepth={200} terrainHeightFunc={terrainData.getHeight} grassDensity={0.1} waterLevel={terrainData.waterLevel} />
      )}

      {/* Animated fog */}
      <AnimatedFog />

      {/* Post-processing effects */}
      <EffectComposer>
        <DepthOfField focusDistance={0} focalLength={0.1} bokehScale={3} height={window.innerHeight} />
      </EffectComposer>
    </>
  );
};

Terrain Properties

| Property | Type | Default | Description | | -------------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------- | | width | number | 100 | The physical width of the terrain in world units (max: 256) | | depth | number | 100 | The physical depth of the terrain in world units (max: 256) | | maxHeight | number | 10 | Maximum terrain peak height, controls the vertical scale of the landscape | | seed | string | 'default11' | Seed string for procedural generation, using the same seed creates identical terrain | | roughness | number | 0.5 | Controls how rough/jagged the terrain is, higher values create more rugged terrain | | detail | number | 4 | Level of fine detail added to the terrain, higher values add more small bumps and variations | | friction | number | 1 | Physics friction value, affects how objects slide on the terrain surface | | restitution | number | 0 | Physics bounciness, determines how much objects bounce when colliding with the terrain | | color | string | '#6b8e23' | Base color of the terrain (when not using texture splatting) | | flatThreshold | number | 0.2 | Controls how much of the terrain becomes flat areas, higher values create more flat areas | | splatting | object | - | Configuration for texture splatting including texture paths, blend modes, and height/slope ranges | | onTerrainDataReady | function | - | Callback function that receives terrain data including height functions and min/max height values | | onMeshCreated | function | - | Callback function that receives the generated terrain mesh for custom manipulations | | onTerrainDataCreated | function | - | Callback function that receives early terrain data (before displacement) for external components | | debug | boolean | false | Enables debug logging for development and troubleshooting |

Texture Splatting Options

Texture splatting allows you to apply different textures to terrain based on height and slope:

| Property | Type | Default | Description | | ----------------- | ---------------- | ------- | ----------------------------------------------------------------------------- | | textures | array | [] | Array of texture configurations (SplattingTexture objects) | | mode | string | 'both' | Splatting mode: 'height', 'slope', or 'both' | | defaultBlendRange | number | 0.1 | Default blend radius between different textures | | blendingStrength | number | 1 | Controls texture blending strength (higher values create sharper transitions) | | terrainMinHeight | number | auto | Minimum height of terrain for height normalization | | terrainMaxHeight | number | auto | Maximum height of terrain for height normalization | | materialParams | object | {} | Additional material parameters applied to the final material | | directionalLight | DirectionalLight | - | DirectionalLight object to use for terrain lighting | | ambientIntensity | number | - | Ambient light intensity (0-1) | | debug | boolean | false | Enable debug mode for splatting |

SplattingTexture Properties

Each splatting texture defines where and how a texture should be applied on the terrain:

| Property | Type | Default | Description | | ---------------- | --------------- | ------------- | ------------------------------------------------------------- | | materialProps | MaterialProps | required | Material properties including texture maps and PBR values | | repeat | number | 1 | Texture repeat factor (higher values create smaller textures) | | heightRange | [number,number] | [0,1] | Height range [min,max] where texture is applied (normalized) | | slopeRange | [number,number] | [0,Math.PI/2] | Slope range [min,max] where texture is applied (in radians) | | heightBlendRange | number | 0.1 | Controls the height-based blending transition width | | slopeBlendRange | number | 0.1 | Controls the slope-based blending transition width |

MaterialProps Interface

The MaterialProps interface allows for comprehensive customization of terrain materials:

import { MaterialProps } from 'vibe-starter-3d-environment';
import { DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';
import { Vector2 } from 'three';

// Example of using MaterialProps for terrain texture
const grassMaterial: MaterialProps = {
  // Textures (can be paths or Texture objects)
  map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS, // Color/diffuse map
  normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_NORMAL, // Normal map for surface detail
  aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_AO, // Ambient occlusion map

  // PBR parameters
  roughness: 0.8, // Surface roughness (0=smooth, 1=rough)
  metalness: 0.1, // Metallic property (0=dielectric, 1=metal)
  normalScale: new Vector2(1.5, 1.5), // Normal map intensity
  aoMapIntensity: 0.8, // Intensity of AO effect

  // New UV noise parameter
  uvNoiseIntensity: 0.15, // Noise intensity for naturalizing tiling patterns (0.0-1.0)
};

Important Texture Handling Improvements: The terrain splatting system now offers more efficient texture usage with the following optimizations:

  1. ARM Map Integration: Instead of using separate aoMap, roughnessMap, and metalnessMap (which would consume 3 texture units), you can now use a single armMap that combines all three channels (R=Ambient Occlusion, G=Roughness, B=Metalness).
  2. Displacement Map Exemption: displacementMap is now processed separately and doesn't count against the WebGL texture unit constraints for the splatting system.
  3. UV Noise Support: Added uvNoiseIntensity parameter to help reduce visible tiling patterns in textures.
  4. Optimal Texture Configuration: A recommended texture configuration would be:
import { MaterialProps } from 'vibe-starter-3d-environment';
import { DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';

const materialProps: MaterialProps = {
  map: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE, // Diffuse/color
  normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL, // Surface detail
  aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO, // Using AO map (armMap not available)
  roughness: 0.8, // Base roughness value
  metalness: 0.1, // Base metalness value
  uvNoiseIntensity: 0.2, // Reduce tiling visibility
};

This configuration provides complete PBR rendering while efficiently managing texture units. You can still use individual maps (aoMap, roughnessMap, metalnessMap) but using the combined armMap is more efficient.

The following texture maps and properties are supported:

| Property | Type | Description | | ------------------ | ----------------- | ---------------------------------------------------------- | | map | string | Texture | Color/diffuse map | | normalMap | string | Texture | Normal map for surface detail | | roughnessMap | string | Texture | Surface roughness map | | metalnessMap | string | Texture | Surface metalness map | | emissiveMap | string | Texture | Emissive/glow map | | bumpMap | string | Texture | Bump map for simpler normal mapping | | displacementMap | string | Texture | Height/displacement map for surface displacement | | aoMap | string | Texture | Ambient occlusion map | | armMap | string | Texture | Combined Ambient Occlusion, Roughness, Metalness map | | envMap | string | Texture | Environment map for reflections | | lightMap | string | Texture | Light map for baked lighting | | alphaMap | string | Texture | Alpha map for transparency | | color | string | Color | Base color (hex string or Three.js Color) | | roughness | number | Surface roughness value (0-1) | | metalness | number | Surface metalness value (0-1) | | emissive | string | Color | Emissive color | | emissiveIntensity | number | Intensity of emissive effect | | lightMapIntensity | number | Intensity of light map | | aoMapIntensity | number | Intensity of ambient occlusion map | | bumpScale | number | Scale of bump map effect | | normalMapType | NormalMapTypes | Type of normal map (ObjectSpace or TangentSpace) | | normalScale | Vector2 | Normal map intensity | | displacementScale | number | Scale factor for displacement | | displacementBias | number | Bias for displacement | | envMapRotation | Euler | Rotation of environment map | | envMapIntensity | number | Intensity of environment map | | wireframe | boolean | Enable wireframe rendering | | wireframeLinewidth | number | Width of wireframe lines | | flatShading | boolean | Enable flat shading vs smooth shading | | transparent | boolean | Enable transparency | | opacity | number | Opacity value when transparent is true | | fog | boolean | Whether material is affected by scene fog | | uvNoiseIntensity | number | Noise intensity for naturalizing tiling patterns (0.0-1.0) |

Advanced Texture Splatting Example

import { Terrain, SplattingTexture, TerrainData } from 'vibe-starter-3d-environment';
import { Vector2 } from 'three';
import { DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';

const textureSplatting = React.useMemo<{
  textures: SplattingTexture[];
  mode: 'both' | 'height' | 'slope';
  blendingStrength: number;
}>(
  () => ({
    textures: [
      {
        // Grass texture - Used on high regions with low slope
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_NORMAL,
          aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_AO, // Using diffuse as AO map
          metalness: 0.4,
          roughness: 0.6,
          uvNoiseIntensity: 0.15, // Add noise to reduce tiling
        },
        repeat: 2,
        heightRange: [0.1, 1.0] as [number, number], // Top 90% for grass
        slopeRange: [0, Math.PI / 4] as [number, number], // Flatter areas
        heightBlendRange: 0.1,
        slopeBlendRange: 0.1,
      },
      {
        // Rock texture - Used on all heights with high slope
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL,
          aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO, // Using AO map
          roughness: 0.4,
          metalness: 0.6,
          uvNoiseIntensity: 0.2,
        },
        repeat: 2,
        heightRange: [0.1, 1.0] as [number, number], // All heights based on slope
        slopeRange: [Math.PI / 4, Math.PI] as [number, number], // Steeper slopes
        heightBlendRange: 0.5,
        slopeBlendRange: 0.5,
      },
      {
        // Dirt texture - Used on medium height and slope regions
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT_NORMAL,
          aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT_AO, // Using diffuse as AO map
          roughness: 0.4,
          metalness: 0.6,
          uvNoiseIntensity: 0.1,
        },
        repeat: 2,
        heightRange: [0.0, 0.1] as [number, number], // Lower 10% for dirt
        slopeRange: [Math.PI / 4, Math.PI / 2] as [number, number], // Medium slopes
        heightBlendRange: 0.5,
        slopeBlendRange: 0.5,
      },
      {
        // Gravel texture - Used on low regions (near water)
        materialProps: {
          map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRAVEL,
          normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRAVEL_NORMAL,
          aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRAVEL_AO, // Using diffuse as AO map
          roughness: 0.4,
          metalness: 0.6,
          uvNoiseIntensity: 0.1,
        },
        repeat: 2,
        heightRange: [0.0, 0.1] as [number, number], // Lower areas
        slopeRange: [0, Math.PI] as [number, number], // All slopes
        heightBlendRange: 0.1,
        slopeBlendRange: 0.1,
      },
    ],
    mode: 'both' as const,
    blendingStrength: 1.3,
  }),
  [],
);

<Terrain width={200} depth={200} maxHeight={10} seed="terrain-seed" roughness={0.6} detail={5} splatting={textureSplatting} />;

Terrain Usage Example

Complete example showcasing the Terrain component with production-ready texture splatting settings and proper terrain data handling:

⚠️ CRITICAL PERFORMANCE WARNING: Texture Resource Setup

When setting up texture resources for splatting configurations, there are specific patterns you MUST follow to avoid severe performance degradation.

✅ CORRECT: Direct URL References with Empty Dependencies

const textureSplattingSettings = React.useMemo(
  () => ({
    textures: [
      {
        materialProps: {
          map: Assets.textures['concrete-floor-painted-diff'].url,
          normalMap: Assets.textures['concrete-floor-painted-nor'].url,
          aoMap: Assets.textures['concrete-floor-painted-arm'].url,
          aoMapIntensity: 0.8,
          roughness: 0.8,
          metalness: 0.1,
        },
        repeat: 2,
        heightRange: [0.0, 1.0] as [number, number],
        slopeRange: [0, Math.PI] as [number, number],
      },
    ] as SplattingTexture[],
    mode: 'both' as const,
    blendingStrength: 1.0,
    debug: false,
  }),
  [], // ← CRITICAL: Empty dependency array
);

❌ NEVER DO THIS: Using useTexture with Dependencies

// ❌ WRONG - This causes severe performance issues!
const concreteTextures = useTexture({
  map: Assets.textures['concrete-floor-painted-diff'].url,
  normalMap: Assets.textures['concrete-floor-painted-nor'].url,
  aoMap: Assets.textures['concrete-floor-painted-arm'].url,
});

const textureSplattingSettings = React.useMemo(
  () => ({
    textures: [
      {
        materialProps: {
          map: concreteTextures.map, // ❌ Using loaded texture objects
          normalMap: concreteTextures.normalMap,
          aoMap: concreteTextures.aoMap,
          aoMapIntensity: 0.8,
          roughness: 0.8,
          metalness: 0.1,
        },
        repeat: 2,
        heightRange: [0.0, 1.0] as [number, number],
        slopeRange: [0, Math.PI] as [number, number],
      },
    ] as SplattingTexture[],
    mode: 'both' as const,
    blendingStrength: 1.0,
    debug: false,
  }),
  [concreteTextures], // ❌ WRONG: Dependency causes constant re-renders
);

Why This Matters:

  1. Texture Loading: The Terrain component handles texture loading internally. Pre-loading with useTexture is unnecessary.
  2. Dependency Issues: Including texture objects in dependencies causes the useMemo to recalculate on every frame because texture objects change reference frequently.
  3. Performance Impact: This can cause 100x more re-renders, severely impacting frame rates and causing stuttering.
  4. Memory Leaks: Constantly recreating texture configurations can lead to memory leaks and GPU resource exhaustion.

Best Practices:

  • Always use direct URL strings in materialProps
  • Keep the dependency array empty [] for static configurations
  • Let the Terrain component handle texture loading internally
  • Only add dependencies if you need dynamic texture changes (rare)
import React, { useState, useRef, useCallback } from 'react';
import { Terrain, Water, DEFAULT_TEXTURE_PATHS, TerrainData, SplattingTexture, MaterialProps } from 'vibe-starter-3d-environment';
import { Vector2 } from 'three';

const GameMap: React.FC = () => {
  const [terrainData, setTerrainData] = useState<TerrainData | null>(null);

  const terrainWidth = 256;
  const terrainDepth = 256;

  // Texture splatting configuration for realistic terrain
  const textureSplattingSettings = React.useMemo<{
    textures: SplattingTexture[];
    mode: 'both' | 'height' | 'slope';
    blendingStrength: number;
    debug: boolean;
  }>(
    () => ({
      textures: [
        {
          // Grass texture - High regions with low slope
          materialProps: {
            map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS,
            normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_NORMAL,
            aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_AO,
            roughness: 0.8,
            metalness: 0.1,
            normalScale: new Vector2(1.5, 1.5),
            uvNoiseIntensity: 0.15,
          },
          repeat: 2,
          heightRange: [0.1, 1.0] as [number, number],
          slopeRange: [0, Math.PI / 4] as [number, number],
          heightBlendRange: 0.1,
          slopeBlendRange: 0.1,
        },
        {
          // Stone texture - All heights with high slope
          materialProps: {
            map: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE,
            normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL,
            aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO,
            roughness: 0.9,
            metalness: 0.05,
            uvNoiseIntensity: 0.2,
          },
          repeat: 2,
          heightRange: [0.1, 1.0] as [number, number],
          slopeRange: [Math.PI / 4, Math.PI] as [number, number],
          heightBlendRange: 0.5,
          slopeBlendRange: 0.5,
        },
        {
          // Dirt texture - Lower regions
          materialProps: {
            map: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT,
            normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT_NORMAL,
            aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.DIRT_AO,
            roughness: 0.7,
            metalness: 0.1,
            uvNoiseIntensity: 0.1,
          },
          repeat: 2,
          heightRange: [0.0, 0.1] as [number, number],
          slopeRange: [Math.PI / 4, Math.PI / 2] as [number, number],
          heightBlendRange: 0.5,
          slopeBlendRange: 0.5,
        },
      ],
      mode: 'both' as const,
      blendingStrength: 1.2,
      debug: false,
    }),
    [],
  );

  const handleTerrainDataReady = useCallback((data: TerrainData) => {
    setTerrainData(data);
    console.log('Terrain ready with water level:', data.waterLevel);
  }, []);

  return (
    <group>
      {/* Terrain with Texture Splatting */}
      <Terrain
        width={terrainWidth}
        depth={terrainDepth}
        maxHeight={10}
        seed="default11"
        roughness={0.5}
        detail={6}
        color="#ffffff"
        friction={1}
        restitution={0}
        flatThreshold={0.2}
        splatting={textureSplattingSettings}
        onTerrainDataReady={handleTerrainDataReady}
        debug={false}
      />

      {/* Water with foam effects */}
      {terrainData && <Water position={[0, terrainData.waterLevel, 0]} size={[128, 128]} enableFoam={true} foamColor="#ffffff" foamIntensity={0.5} />}
    </group>
  );
};

Displacement Mapping Example

Displacement maps add fine surface details to the terrain by physically adjusting vertex positions. This example demonstrates how to apply displacement maps to rock and red terrain textures:

import { Terrain, TerrainData, SplattingTexture } from 'vibe-starter-3d-environment';
import { Vector2 } from 'three';
import { DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';

const displacementTextures = React.useMemo<SplattingTexture[]>(
  () => [
    {
      // Stone texture example
      materialProps: {
        map: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE,
        normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL,
        aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO,
        roughness: 0.8,
        metalness: 0.1,
        uvNoiseIntensity: 0.15,
      },
      repeat: 2,
      heightRange: [0.5, 1.0] as [number, number], // Applied to higher elevations
      slopeRange: [Math.PI / 6, Math.PI / 2] as [number, number], // Medium to steep slopes
      heightBlendRange: 0.2,
      slopeBlendRange: 0.3,
    },
    {
      // Sand texture example
      materialProps: {
        map: DEFAULT_TEXTURE_PATHS.TERRAIN.SAND,
        normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.SAND_NORMAL,
        aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.SAND_AO,
        roughness: 0.9,
        metalness: 0.05,
        uvNoiseIntensity: 0.1,
      },
      repeat: 3,
      heightRange: [0.0, 0.6] as [number, number], // Lower to mid elevations
      slopeRange: [0, Math.PI / 4] as [number, number], // Gentle slopes
      heightBlendRange: 0.15,
      slopeBlendRange: 0.1,
    },
  ],
  [],
);

const handleTerrainReady = (terrainData: TerrainData): void => {
  console.log('Final terrain height range:', terrainData.minHeight, terrainData.maxHeight);
  console.log('Height function with displacement is now available');
};

<Terrain
  width={200}
  depth={200}
  maxHeight={10}
  seed="terrain-seed"
  roughness={0.6}
  detail={5}
  splatting={{
    textures: displacementTextures,
    mode: 'both',
    blendingStrength: 1.0,
    // Material parameters applied to the entire terrain
    materialParams: {
      displacementScale: 1.0, // Global displacement scale
      displacementBias: 0.0, // Global displacement bias
    },
  }}
  // Receive final terrain data with displacement applied
  onTerrainDataReady={handleTerrainReady}
/>;

Important considerations when using displacement maps:

  • The terrain automatically calculates appropriate segment counts based on size for displacement detail.
  • Adjust displacementScale to control the intensity of the displacement effect.
  • Use displacementBias to offset the base level of displacement (positive values push everything up).
  • Apply different displacement maps and settings to each texture to create varied terrain characteristics.

TerrainData and Improved Displacement

The Terrain component now supports improved displacement mapping and provides more accurate terrain data:

import { Terrain, TerrainData } from 'vibe-starter-3d-environment';

const handleTerrainData = (terrainData: TerrainData): void => {
  // terrainData now includes the final displaced height function
  console.log('Min height:', terrainData.minHeight);
  console.log('Max height:', terrainData.maxHeight);
  console.log('Water level:', terrainData.waterLevel);

  // Use the getHeight to find the height at any point
  // This function now considers displacement maps
  const height: number = terrainData.getHeight(10, 20); // x=10, z=20
};

<Terrain width={200} depth={200} maxHeight={10} onTerrainDataReady={handleTerrainData} />;

The terrainData object includes:

| Property | Type | Description | | ---------- | -------- | --------------------------------------------------------------------------- | | getHeight | function | Height function that considers displacement maps and final geometry | | minHeight | number | Actual minimum height after displacement | | maxHeight | number | Actual maximum height after displacement | | waterLevel | number | Water level for the terrain (automatically calculated by Terrain component) | | width | number | Width of the terrain | | depth | number | Depth of the terrain |

This improved system ensures that objects placed on the terrain will align correctly with the visible surface, even when using displacement maps.

Terrain Utility Functions

calculatePositionOnTerrain

IMPORTANT: You MUST import terrainUtil from 'vibe-starter-3d-environment' to use this function. terrainUtil is definitely included in the vibe-starter-3d-environment package.

import { terrainUtil } from 'vibe-starter-3d-environment';

terrainUtil.calculatePositionOnTerrain(terrain, initialPosition, offset, terrainWidth, terrainDepth);

Calculates the correct position for an object to be placed on the terrain surface.

| Parameter | Type | Default | Description | | --------------- | -------------------------------------------- | ------- | -------------------------------------------------- | | terrain | TerrainData | null | Terrain data object containing the height function | | initialPosition | [number, number] or [number, number, number] | [0,0,0] | Initial position [x,z] or [x,y,z] in world space | | offset | number | 0 | Height offset from the terrain surface | | terrainWidth | number | 256 | Width of the terrain for coordinate mapping | | terrainDepth | number | 256 | Depth of the terrain for coordinate mapping |

Returns a Vector3 object with the final position adjusted to the terrain height.

Terrain Navigation Functions
import { terrainNavigation, calculateTerrainNormal, calculateTerrainBasedRotation, TerrainNavigation, TerrainData } from 'vibe-starter-3d-environment';
import { Vector3, Object3D } from 'three';

// Assuming you have terrainData available
const terrainData: TerrainData | null = null;
const x: number = 10;
const z: number = 20;
const sampleDistance: number = 0.5;

// Calculate normal at a point
const normal: Vector3 = calculateTerrainNormal(terrainData!, [x, z], sampleDistance);

// Calculate rotation based on terrain slope
const rotation: [number, number, number] = calculateTerrainBasedRotation(
  terrainData!,
  [x, z] as [number, number],
  [0, 0, 0] as [number, number, number], // base rotation
  0.5, // sample distance
  1.0, // rotation weight (0-1)
);

// Use TerrainNavigation class for advanced navigation
const myObject3D: Object3D = new Object3D();
const targetX: number = 50;
const targetZ: number = 50;

const navigation = new TerrainNavigation();
navigation.setNavigationObject(myObject3D);
navigation.setTerrainData(terrainData!);
navigation.navigateToPoint(targetX, targetZ, {
  heightOffset: 1.0,
  duration: 2000,
  onNavigationStart: () => console.log('Started'),
  onNavigationComplete: () => console.log('Completed'),
});
calculateTerrainNormal

Calculates the normal vector at a specific terrain position.

| Parameter | Type | Default | Description | | -------------- | ----------- | ------- | ------------------------------------- | | terrain | TerrainData | - | Terrain data object | | position | [x, z] | - | Position to calculate normal at | | sampleDistance | number | 0.5 | Distance for sampling adjacent points |

calculateTerrainBasedRotation

Calculates rotation angles to align object with terrain slope.

| Parameter | Type | Default | Description | | -------------- | ------------ | --------- | ------------------------------------------------- | | terrain | TerrainData | - | Terrain data object | | position | [x, z] | - | Position to calculate rotation at | | baseRotation | [rx, ry, rz] | [0, 0, 0] | Base rotation to add terrain rotation to | | sampleDistance | number | 0.5 | Distance for sampling adjacent points | | rotationWeight | number | 1.0 | Weight for terrain rotation (0=flat, 1=full tilt) |

Water Components

React Three Fiber-based water simulation system with realistic wave physics, dynamic reflections, and foam effects. Features customizable wave parameters, color transitions based on depth, and performance-optimized rendering with LOD support. Seamlessly integrates with terrain systems for natural water bodies in 3D environments through the Water component from vibe-starter-3d-environment.

Important: Water Height is Automatically Calculated

The Terrain component automatically calculates the appropriate water level based on the terrain characteristics:

  • For flat terrain (heightDiff < 0.5 or maxHeight < 0.5): waterLevel = -10 (underground)
  • For normal terrain: waterLevel = minHeight + (heightDiff × 0.1)

Always use terrainData.waterLevel instead of calculating it manually:

const waterLevel = terrainData?.waterLevel || 0;

The water module provides realistic water surfaces with advanced wave simulation, dynamic reflections, and color transitions:

import { Water } from 'vibe-starter-3d-environment';

// Use waterLevel from terrainData
const waterLevel: number = terrainData?.waterLevel || 0;

<Water
  position={[0, waterLevel, 0]}
  opacity={0.5}
  wavesAmplitude={0.525}
  wavesSpeed={0.5}
  surfaceColor="#9bd8ff"
  dynamicReflections={true}
  enableFoam={true}
  foamIntensity={0.5}
  edgeColor="#8B7355"
/>;

Water Properties

| Property | Type | Default | Description | | ------------------ | ---------------------- | --------- | --------------------------------------------------------------- | | resolution | number | 256 | Resolution of the water mesh, higher values create more detail | | position | [number,number,number] | [0,0,0] | 3D position of the water surface | | size | [number,number] | [128,128] | Water surface size [width, depth] | | opacity | number | 0.5 | Transparency level of the water (0.0 to 1.0) | | wavesAmplitude | number | 0.525 | Height of the waves | | wavesFrequency | number | 6.7 | Frequency of the wave patterns | | wavesPersistence | number | 0.3 | How waves persist across multiple octaves | | wavesLacunarity | number | 2.18 | How wave frequency changes across octaves | | wavesIterations | number | 9 | Number of noise iterations for wave detail | | wavesSpeed | number | 0.5 | Speed of wave animation | | troughColor | string | "#3344aa" | Color of the deepest parts of the waves | | surfaceColor | string | "#9bd8ff" | Color of the water surface at rest | | peakColor | string | "#bbd8ff" | Color of the wave peaks | | peakThreshold | number | 0.08 | Height threshold for peak color | | peakTransition | number | 0.05 | Smoothness of transition to peak color | | troughThreshold | number | -0.01 | Depth threshold for trough color | | troughTransition | number | 0.15 | Smoothness of transition to trough color | | fresnelScale | number | 1.5 | Strength of the fresnel effect (edge highlighting) | | fresnelPower | number | 0.8 | Sharpness of the fresnel effect | | reflectionQuality | number | 256 | Resolution of reflection cubemap | | dynamicReflections | boolean | true | Whether reflections update over time | | reflectionInterval | number | 1000 | Milliseconds between reflection updates when dynamic is enabled | | enableFoam | boolean | true | Enable depth-based foam effects | | foamColor | string | "#ccddff" | Color of the foam | | foamIntensity | number | 0.5 | Intensity of the foam effect | | foamRange | number | 1 | Range of the foam effect | | foamScale | number | 1 | Scale of the foam texture | | edgeColor | string | "#ffffff" | Color at water edges/contact surfaces | | edgeIntensity | number | 2 | Intensity of edge color effect | | edgeWidth | number | 0.5 | Width of edge effect (multiple of foamRange) |

Usage Examples

Basic Water Surface
import { Water, TerrainData } from 'vibe-starter-3d-environment';

interface BasicWaterProps {
  terrainData: TerrainData | null;
}

const BasicWater: React.FC<BasicWaterProps> = ({ terrainData }) => {
  const waterLevel = terrainData?.waterLevel || 0;

  return <Water position={[0, waterLevel, 0]} size={[100, 100]} opacity={0.7} />;
};
Advanced Water with Foam
import { Water, TerrainData } from 'vibe-starter-3d-environment';

interface AdvancedWaterProps {
  terrainData: TerrainData | null;
}

const AdvancedWater: React.FC<AdvancedWaterProps> = ({ terrainData }) => {
  const waterLevel = terrainData?.waterLevel || 0;

  return (
    <Water
      position={[0, waterLevel, 0]}
      size={[200, 200]}
      opacity={0.5}
      // Wave configuration
      wavesAmplitude={0.8}
      wavesFrequency={4.5}
      wavesSpeed={0.3}
      wavesIterations={12}
      // Color configuration
      surfaceColor="#4d9de0"
      troughColor="#15395b"
      peakColor="#7bb3d8"
      // Foam effects
      enableFoam={true}
      foamColor="#ffffff"
      foamIntensity={0.7}
      foamRange={2}
      // Edge effects
      edgeColor="#8B7355"
      edgeIntensity={3}
      edgeWidth={0.8}
      // Reflections
      dynamicReflections={true}
      reflectionQuality={512}
      reflectionInterval={2000}
    />
  );
};
Calm Lake Water
import { Water, TerrainData } from 'vibe-starter-3d-environment';

interface LakeWaterProps {
  terrainData: TerrainData | null;
}

const LakeWater: React.FC<LakeWaterProps> = ({ terrainData }) => {
  const waterLevel = terrainData?.waterLevel || 0;

  return (
    <Water
      position={[0, waterLevel, 0]}
      size={[300, 300]}
      opacity={0.6}
      // Gentle waves
      wavesAmplitude={0.2}
      wavesFrequency={2.0}
      wavesSpeed={0.1}
      // Lake colors
      surfaceColor="#1e88e5"
      troughColor="#0d47a1"
      peakColor="#42a5f5"
      // Minimal foam
      enableFoam={true}
      foamIntensity={0.2}
      foamRange={0.5}
      // Strong reflections
      dynamicReflections={true}
      reflectionQuality={1024}
      fresnelScale={2.0}
      fresnelPower={1.2}
    />
  );
};
Stormy Ocean Water
import { Water, TerrainData } from 'vibe-starter-3d-environment';

interface OceanWaterProps {
  terrainData: TerrainData | null;
}

const OceanWater: React.FC<OceanWaterProps> = ({ terrainData }) => {
  const waterLevel = terrainData?.waterLevel || 0;

  return (
    <Water
      position={[0, waterLevel, 0]}
      size={[500, 500]}
      opacity={0.4}
      // Large waves
      wavesAmplitude={2.5}
      wavesFrequency={8.0}
      wavesSpeed={1.5}
      wavesPersistence={0.5}
      wavesLacunarity={2.5}
      wavesIterations={15}
      // Ocean colors
      surfaceColor="#006994"
      troughColor="#001f3f"
      peakColor="#4fc3f7"
      // Strong foam
      enableFoam={true}
      foamColor="#f5f5f5"
      foamIntensity={1.0}
      foamRange={3}
      foamScale={2}
      // Edge effects for shoreline
      edgeColor="#f4e4c1"
      edgeIntensity={4}
      edgeWidth={1.2}
    />
  );
};

Integration with Terrain

import { Terrain, Water, TerrainData } from 'vibe-starter-3d-environment';
import { useState } from 'react';

const TerrainWithWater: React.FC = () => {
  const [terrainData, setTerrainData] = useState<TerrainData | null>(null);

  const handleTerrainDataReady = (data: TerrainData) => {
    setTerrainData(data);
    console.log(`Terrain ready with waterLevel: ${data.waterLevel}`);
  };

  return (
    <>
      <Terrain width={256} depth={256} maxHeight={10} onTerrainDataReady={handleTerrainDataReady} />

      {terrainData && (
        <Water position={[0, terrainData.waterLevel, 0]} size={[128, 128]} opacity={0.5} enableFoam={true} foamIntensity={0.6} edgeColor="#8B7355" />
      )}
    </>
  );
};

Performance Considerations

  1. Resolution: Higher resolution values create more detailed water surfaces but impact performance. Use 256 for most cases, 512 for high quality, and 128 for better performance.

  2. Dynamic Reflections: While beautiful, dynamic reflections can be expensive. Consider disabling them or increasing the reflectionInterval for better performance.

  3. Wave Iterations: More iterations create more detailed waves but cost more performance. 9-12 iterations work well for most cases.

  4. Foam Effects: Foam adds visual richness but requires additional shader calculations. Disable if not needed for your scene.

Tips for Realistic Water

  1. Color Selection: Use darker colors for troughColor and lighter colors for peakColor to create realistic wave shading.

  2. Opacity: Lower opacity (0.4-0.6) often looks more realistic than fully transparent water.

  3. Fresnel Effects: Adjust fresnelScale and fresnelPower to control how water reflects at different viewing angles.

  4. Wave Speed: Keep wave speed relatively low (0.1-0.5) for realistic motion. Higher values can look unnatural.

  5. Foam Placement: Use foam to indicate shallow areas or water interaction with terrain. Adjust foamRange based on your scene scale.

  6. Edge Effects: Use edgeColor to simulate wet sand or shoreline effects where water meets terrain.

Stage Components

React Three Fiber and Rapier-based maze generation system for level design, featuring procedurally generated maps with central plazas and strategic corridors. Suitable for RPG, FPS, TPS and dungeon crawlers with customizable parameters, tactical cover positioning, and optimized collision handling. Implemented via the Stage component from vibe-starter-3d-environment for React integration.

Important: You must install the package using pnpm add vibe-starter-3d-environment before you can use it.

⚠️ Required Component: Stage component MUST be used with Terrain component. The Stage component requires terrain data to properly position walls and structures in the 3D environment.

⚠️ CRITICAL WARNING: FLOOR COMPONENT REQUIREMENT ⚠️

ABSOLUTELY MANDATORY: Stage Component CANNOT Be Used Standalone

🚨 EXTREME IMPORTANCE - READ THIS FIRST 🚨

The Stage component is NOT A STANDALONE COMPONENT and MUST ALWAYS be used with a floor/ground component like Terrain. This is ABSOLUTELY CRITICAL for the following reasons:

  1. NO FLOOR = NO LANDING: Without a floor component (like Terrain), your character will have NO GROUND TO LAND ON and will FALL INFINITELY through the world.

  2. PHYSICS REQUIREMENT: The Stage component ONLY CREATES WALLS - it does NOT create any floor or ground surface. Characters and objects need a physical surface to stand on.

  3. MANDATORY PAIRING: You MUST use Stage with one of these floor components:

    • Terrain Component (RECOMMENDED) - Provides procedural terrain with physics collision
    • Any other ground/floor component that provides a physics collider
  4. TERRAIN DATA DEPENDENCY: The Stage component REQUIRES terrainData prop from the Terrain component to:

    • Calculate proper wall heights
    • Align walls with terrain surface
    • Position structures correctly in 3D space

🔴 CRITICAL: terrainData Property is MANDATORY when using with Terrain

When using Stage with Terrain component, you MUST pass the terrainData prop:

// ⚠️ CRITICAL: terrainData prop is REQUIRED!
<Stage
  terrainData={terrainData} // ← THIS IS MANDATORY!
  // ... other props
/>

Without terrainData prop:

  • Walls will NOT align with terrain height
  • Walls may float in the air or sink underground
  • Height calculations will be incorrect
  • The stage will not integrate properly with the terrain

❌ NEVER DO THIS (WILL FAIL):

// ❌ WRONG - Character will fall through the world!
<Stage
  seed="my-stage"
  size={256}
  // Missing Terrain component!
/>

// ❌ WRONG - Missing terrainData prop!
<>
  <Terrain onTerrainDataReady={setTerrainData} />

  {terrainData && (
    <Stage
      seed="my-stage"
      size={256}
      // terrainData prop is MISSING! This will cause problems!
    />
  )}
</>

✅ ALWAYS DO THIS (CORRECT):

// ✅ CORRECT - Character has ground to stand on AND proper terrain integration
<>
  <Terrain
    onTerrainDataReady={setTerrainData}
    // ... other terrain props
  />

  {terrainData && (
    <Stage
      terrainData={terrainData} // ← MANDATORY prop!
      // ... other stage props
    />
  )}
</>

🔴 FAILURE TO INCLUDE A FLOOR COMPONENT WILL RESULT IN:

  • Characters falling through the world
  • No ground collision detection
  • Completely broken gameplay
  • Stage walls floating in empty space

🔴 FAILURE TO PASS terrainData PROP WILL RESULT IN:

  • Walls not following terrain height
  • Incorrect wall positioning
  • Visual misalignment between terrain and walls
  • Broken terrain integration

Remember: Stage = Walls Only, You MUST provide the floor AND pass terrainData!


Default Recommendations

When setting up Stage for the first time, it is recommended to follow these guidelines:

  1. Set Terrain maxHeight to 0: Start with flat terrain when initially testing and setting up the Stage component.

    <Terrain maxHeight={0} ... />
  2. Do not use Water component: Since maxHeight is 0 initially, do not use the Water component. It is recommended to add it after the Stage structure is completed and when maxHeight is increased to create elevated terrain.

  3. Simple Texture Splatting Configuration: Start with a single texture for textureSplattingSettings.

    const textureSplattingSettings = React.useMemo(
      () => ({
        textures: [
          {
            materialProps: {
              map: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS,
              normalMap: DEFAULT_TEXTURE_PATHS.TERRAIN.GRASS_NORMAL,
              roughness: 0.8,
              metalness: 0.1,
            },
            repeat: 2,
            heightRange: [0.0, 1.0],
            slopeRange: [0, Math.PI],
          },
        ],
        mode: 'both',
        blendingStrength: 1.0,
      }),
      [],
    );

Following these guidelines will help you understand the basic behavior of the Stage component more clearly and troubleshoot issues more easily.

The stage module provides procedural 3D environment generation with maze-like structures:

import { Stage, TerrainData } from 'vibe-starter-3d-environment';

const terrainData: TerrainData | null = null; // Should be provided from Terrain component

<Stage
  seed="my-level-seed"
  size={256}
  borderOffset={5}
  wallHeight={3}
  wallShape="cube"
  terrainData={terrainData}
  followTerrain={true}
  onReady={() => console.log('Stage loaded!')}
/>;

Stage Properties

| Property | Type | Default | Description | | --------------------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- | | seed | string | 'default11' | Seed string for procedural generation, same seed creates identical maze | | size | number | 256 | The physical size of the stage environment in world units | | borderOffset | number | 5 | Distance between boundary walls and exterior walls | | onReady | function | - | Callback function triggered when the stage is fully loaded | | disablePhysics | boolean | false | Disable physics simulation for performance testing | | debug | boolean | false | Enable debug mode for logging | | wallHeight | number | 3 | Height of maze walls | | wallHeightVariation | number | 1 | Random variation in wall height (±this value) | | textureRepeat | number | 2 | Number of times textures repeat on surfaces | | textures | object | - | Custom texture configurations for walls (diffuse, normal, aoMap) | | gridDensity | number | 0.8 | Maze grid density (0-2 range, automatically constrained) | | cellSize | number | 10 | Size of maze cells (5-30 range, automatically constrained) | | wallShape | ShapeType | 'cube' | Shape of walls ('cube', 'rounded_cube', 'cylinder', 'cone', 'capsule', 'pyramid', 'octahedron') | | roundedCubeRadius | number | 0.1 | Radius of rounded corners (0.05-0.5 range, for 'rounded_cube' shape) | | roundedCubeSmoothness | number | 4 | Smoothness of rounded corners (0.1-1.0 range, for 'rounded_cube' shape) | | terrainData | TerrainData | null | REQUIRED when using with Terrain component. Terrain data for height calculation | | followTerrain | boolean | true | Whether walls should follow terrain height | | terrainHeightOffset | number | 0 | Additional height offset above terrain |

Shape Types

Available wall shapes through the SHAPE_TYPES constant:

import { SHAPE_TYPES } from 'vibe-starter-3d-environment';

// Available shapes:
const cubeShape: string = SHAPE_TYPES.CUBE; // Standard cube shape (default)
const roundedCubeShape: string = SHAPE_TYPES.ROUNDED_CUBE; // Cube with rounded corners
const cylinderShape: string = SHAPE_TYPES.CYLINDER; // Cylindrical walls
const coneShape: string = SHAPE_TYPES.CONE; // Cone-shaped walls
const capsuleShape: string = SHAPE_TYPES.CAPSULE; // Capsule-shaped walls
const pyramidShape: string = SHAPE_TYPES.PYRAMID; // Pyramid-shaped walls
const octahedronShape: string = SHAPE_TYPES.OCTAHEDRON; // Octahedral walls

Textures Configuration

The Stage component provides comprehensive texture customization for walls through the textures property. This allows you to change the visual appearance of all walls in your maze environment.

Texture Property Structure
interface StageTextures {
  wall?: {
    diffuse?: string | null; // Main color/albedo texture path
    normal?: string | null; // Normal map for surface detail
    aoMap?: string | null; // Ambient occlusion map for shadows
    aoMapIntensity?: number; // AO effect strength (0-1, default: 0.8)
    metalness?: number; // Metallic appearance (0-1, default: 0.2)
    roughness?: number; // Surface roughness (0-1, default: 0.7)
  };
}
Default Texture Values

If you don't specify textures, Stage uses these defaults:

// Default textures (automatically applied if not specified)
{
  wall: {
    diffuse: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS,
    normal: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_NORMAL,
    aoMap: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_AO,
    aoMapIntensity: 0.8,
    metalness: 0.2,
    roughness: 0.7,
  }
}
Available Built-in Textures

The package includes a comprehensive set of textures that can be accessed using the DEFAULT_TEXTURE_PATHS constant:

Wall Textures (Used by Stage)

// Wall texture constants
DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS; // Stone blocks diffuse texture
DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_NORMAL; // Stone blocks normal map
DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_AO; // Stone blocks ambient occlusion

Terrain Textures (Can be used for walls)

// Stone textures - great for dungeon walls
DEFAULT_TEXTURE_PATHS.TERRAIN.STONE; // Stone diffuse
DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL; // Stone normal map
DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO; // Stone ambient occlusion

// Asphalt - for modern/urban environments
DEFAULT_TEXTURE_PATHS.TERRAIN.ASPHALT; // Asphalt diffuse
DEFAULT_TEXTURE_PATHS.TERRAIN.ASPHALT_NORMAL; // Asphalt normal map
DEFAULT_TEXTURE_PATHS.TERRAIN.ASPHALT_AO; // Asphalt ambient occlusion

// Also available: GRASS, DIRT, GRAVEL, SAND, SNOW (each with diffuse, normal, and AO maps)
How to Change Textures

1. Using Built-in Texture Paths

import { Stage, DEFAULT_TEXTURE_PATHS } from 'vibe-starter-3d-environment';

// Using default wall textures (stone blocks)
<Stage
  textures={{
    wall: {
      diffuse: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS,
      normal: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_NORMAL,
      aoMap: DEFAULT_TEXTURE_PATHS.WALL.WALL_BLOCKS_AO,
      aoMapIntensity: 0.8,
      metalness: 0.2,
      roughness: 0.8,
    },
  }}
/>

// Example: Using terrain stone textures for dungeon walls
<Stage
  textures={{
    wall: {
      diffuse: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE,
      normal: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_NORMAL,
      aoMap: DEFAULT_TEXTURE_PATHS.TERRAIN.STONE_AO,
      aoMapIntensity: 0.9,
      metalness: 0.1,
      roughness: 0.9,
    },
  }}
/>

2. Using Custom Texture Files

<Stage
  textures={{
    wall: {
      diffuse: 'YOUR_TEXTURE_URL', // URL to your diffuse texture
      normal: 'YOUR_NORMAL_MAP_URL', // URL to your normal map texture
      aoMap: 'YOUR_AO_MAP_URL', // URL to your ambient occlusion texture
      aoMapIntensity: 0.9,
      metalness: 0.1,
      roughness: 0.9,
    },
  }}
/>

Note: Replace the URL placeholders with actual URLs to your texture resources (e.g., from a CDN, cloud storage, or any publicly accessible URL).

3. Disabling Specific Textures

Set any texture to null to disable it:

<Stage
  textures={{
    wall: {
      diffuse: 'YOUR_TEXTURE_URL', // Only use diffuse texture
      normal: null, // No normal mapping
      aoMap: null, // No ambient occlusion
      metalness: 0.0,
      roughness: 1.0,
    },
  }}
/>

4. Material-Only Configuration (No Textures)

For a solid color without textures:

<Stage
  textures={{
    wall: {
      diffuse: null, // No texture, uses default gray color
      normal: null,
      aoMap: null,
      metalness: 0.3,
      roughness: 0.7,
    },
  }}
/>
Texture Examples by Environment Type

Stone Dungeon Walls

<Stage
  textures={{
    wall: {
      diffuse: 'STONE_TEXTURE_URL', // URL to stone diffuse texture
      normal: 'STON