Skip to content

Commit b86fa2a

Browse files
committed
Enhance Terrain System Documentation and Editor Functionality
- Expanded the terrain system implementation document to include context, goals, current pain points, and a proposed solution with detailed architecture and directory structure. - Introduced a new lighting category in the Enhanced Add Object Menu, allowing users to add various light types (Directional, Point, Spot, Ambient) to the scene. - Updated the entity creation handlers to support new light types, enhancing the editor's capabilities for 3D scene development. - Improved the geometry generation for spiral stairs, optimizing the creation process and ensuring better performance. This commit aims to provide a comprehensive guide for the terrain system while enriching the editor's functionality for lighting and geometry creation.
1 parent c2800e7 commit b86fa2a

File tree

4 files changed

+173
-61
lines changed

4 files changed

+173
-61
lines changed

docs/implementation/terrain-system.md

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,70 @@
22

33
## Overview
44

5-
This document outlines the implementation plan for a comprehensive terrain system in Vibe Coder 3D. The terrain system will integrate seamlessly with the existing Unity-like editor architecture while providing specialized capabilities for large-scale landscape creation and editing.
5+
### Context & Goals
66

7-
## Architecture Decision: Hybrid Approach
7+
- **Landscape Creation Need**: Vibe Coder 3D currently lacks specialized tools for creating large-scale natural landscapes and environmental terrain, limiting users to basic geometric shapes for ground surfaces
8+
- **Unity-like Workflow**: Users expect familiar terrain creation tools similar to Unity's terrain system, with heightmap support, multi-layer texturing, and performance-optimized rendering
9+
- **Performance Requirements**: Large terrain systems need specialized LOD (Level of Detail) management and chunk-based rendering to maintain 60+ FPS with complex landscapes spanning hundreds of square kilometers
10+
- **Physics Integration**: Terrain must integrate seamlessly with the existing Rapier3D physics system using optimized `HeightfieldCollider` for realistic collision detection without performance degradation
811

9-
After analyzing the current Vibe Coder 3D architecture, we've chosen a **hybrid approach** that leverages the existing shape system while introducing specialized terrain subsystems. This approach provides the best balance of integration and performance optimization.
12+
### Current Pain Points
1013

11-
### Why Hybrid Approach?
14+
- **Limited Ground Creation**: Users must create ground surfaces using scaled cubes or planes, which don't support natural landscape features like hills, valleys, or complex terrain topology
15+
- **No Heightmap Support**: The current system lacks ability to import real-world elevation data or create procedural terrain using noise functions and heightmaps
16+
- **Performance Issues with Large Surfaces**: Creating large ground areas using multiple primitive shapes causes performance bottlenecks due to excessive draw calls and physics calculations
17+
- **Missing Terrain Tools**: No specialized editing tools for terrain sculpting, texture blending, or environmental detail placement that are standard in modern 3D engines
1218

13-
1. **Seamless Editor Integration**: Works with existing hierarchy, inspector, and entity management
14-
2. **Performance Optimization**: Enables terrain-specific optimizations without affecting other systems
15-
3. **Physics Compatibility**: Integrates with existing Rapier3D physics system
16-
4. **Familiar Workflow**: Maintains Unity-like entity creation and property editing patterns
17-
5. **Future Extensibility**: Clear path for advanced terrain features
19+
## Proposed Solution
20+
21+
### High-level Summary
22+
23+
- **Hybrid Integration Approach**: Extend the existing ECS component system with a specialized `Terrain` component that leverages current architecture while adding terrain-specific optimizations
24+
- **HeightfieldCollider Physics**: Utilize Rapier3D's native `HeightfieldCollider` for memory-efficient, high-performance terrain physics without requiring additional dependencies
25+
- **Multi-layer Texture System**: Implement custom shader-based texture blending supporting up to 8 terrain layers with height-based and slope-based automatic blending rules
26+
- **Chunk-based LOD Rendering**: Develop a streaming LOD system that dynamically loads/unloads terrain chunks based on camera distance, supporting massive terrains (1000x1000+ world units)
27+
- **Editor Tool Integration**: Create intuitive terrain creation and editing tools within the existing Unity-like editor interface, including heightmap import and basic sculpting capabilities
28+
29+
### Architecture & Directory Structure
30+
31+
```typescript
32+
src/
33+
├── core/
34+
│ ├── lib/
35+
│ │ ├── ecs/
36+
│ │ │ ├── components/
37+
│ │ │ │ ├── definitions/
38+
│ │ │ │ │ └── TerrainComponent.ts # New terrain ECS component
39+
│ │ │ │ └── ComponentDefinitions.ts # Register terrain component
40+
│ │ └── terrain/ # New terrain subsystem
41+
│ │ ├── TerrainGeometry.ts # Heightmap-based geometry generation
42+
│ │ ├── TerrainMaterial.ts # Multi-layer texture blending
43+
│ │ ├── TerrainPhysics.ts # HeightfieldCollider integration
44+
│ │ ├── TerrainLOD.ts # Level-of-detail management
45+
│ │ └── TerrainStreaming.ts # Chunk-based streaming system
46+
├── editor/
47+
│ ├── components/
48+
│ │ ├── menus/
49+
│ │ │ └── EnhancedAddObjectMenu.tsx # Add terrain to menu
50+
│ │ ├── panels/
51+
│ │ │ ├── ViewportPanel/
52+
│ │ │ │ ├── components/
53+
│ │ │ │ │ ├── TerrainGeometry.tsx # React Three Fiber integration
54+
│ │ │ │ │ ├── TerrainMaterial.tsx # Material rendering component
55+
│ │ │ │ │ └── TerrainCollider.tsx # Physics visualization
56+
│ │ │ └── InspectorPanel/
57+
│ │ │ └── Terrain/ # New terrain inspector sections
58+
│ │ │ ├── TerrainSection.tsx # Basic terrain properties
59+
│ │ │ ├── HeightmapSection.tsx # Heightmap import/export
60+
│ │ │ ├── TextureSection.tsx # Multi-layer texturing
61+
│ │ │ └── LODSection.tsx # Performance settings
62+
│ │ └── terrain/ # New terrain-specific UI
63+
│ │ ├── HeightmapImporter.tsx # Heightmap file handling
64+
│ │ ├── TextureLayerEditor.tsx # Layer configuration
65+
│ │ └── TerrainPreview.tsx # Real-time preview
66+
│ └── types/
67+
│ └── shapes.ts # Add Terrain to ShapeType enum
68+
```
1869
1970
## Implementation Phases
2071

src/editor/components/menus/EnhancedAddObjectMenu.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { FiBox, FiFolder } from 'react-icons/fi';
2+
import { FiBox, FiFolder, FiSun, FiZap } from 'react-icons/fi';
33
import {
44
TbBox,
55
TbBuildingBridge,
@@ -10,6 +10,7 @@ import {
1010
TbDiamond,
1111
TbHeart,
1212
TbHexagon,
13+
TbLamp,
1314
TbMath,
1415
TbOctagon,
1516
TbPlus,
@@ -30,7 +31,7 @@ import { IMenuCategory, IMenuItemOption, NestedDropdownMenu } from './NestedDrop
3031

3132
export interface IEnhancedAddObjectMenuProps {
3233
anchorRef: React.RefObject<HTMLElement>;
33-
onAdd: (type: ShapeType) => void;
34+
onAdd: (type: ShapeType | string) => void;
3435
onCustomModel?: () => void;
3536
}
3637

@@ -203,6 +204,32 @@ const OBJECT_CATEGORIES: IMenuCategory[] = [
203204
},
204205
],
205206
},
207+
{
208+
label: 'Lighting',
209+
icon: <FiSun size={18} />,
210+
items: [
211+
{
212+
type: 'DirectionalLight',
213+
label: 'Directional Light',
214+
icon: <TbLamp size={18} />,
215+
},
216+
{
217+
type: 'PointLight',
218+
label: 'Point Light',
219+
icon: <FiZap size={18} />,
220+
},
221+
{
222+
type: 'SpotLight',
223+
label: 'Spot Light',
224+
icon: <TbLamp size={18} />,
225+
},
226+
{
227+
type: 'AmbientLight',
228+
label: 'Ambient Light',
229+
icon: <FiSun size={18} />,
230+
},
231+
],
232+
},
206233
{
207234
label: 'Assets',
208235
icon: <FiFolder size={18} />,
@@ -230,6 +257,13 @@ export const EnhancedAddObjectMenu: React.FC<IEnhancedAddObjectMenuProps> = ({
230257
return;
231258
}
232259

260+
// Handle light types
261+
const lightTypes = ['DirectionalLight', 'PointLight', 'SpotLight', 'AmbientLight'];
262+
if (lightTypes.includes(item.type as string)) {
263+
onAdd(item.type as string);
264+
return;
265+
}
266+
233267
// Handle all primitive shapes
234268
const validTypes: ShapeType[] = [
235269
ShapeType.Cube,

src/editor/components/panels/ViewportPanel/components/CustomGeometries.tsx

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -342,66 +342,73 @@ export const SpiralStairsGeometry: React.FC<{
342342

343343
let vertexOffset = 0;
344344

345-
// Create central pole - complete cylinder with caps
345+
// --- Create Central Pole ---
346+
// This creates a complete, solid cylinder with correct normals and caps.
346347
const poleSegments = 8;
348+
const poleVertices: number[] = [];
349+
const poleIndices: number[] = [];
350+
const poleUvs: number[] = [];
351+
let poleVertexCount = 0;
352+
353+
// Helper to add a vertex
354+
const addVertex = (x: number, y: number, z: number, u: number, v: number) => {
355+
poleVertices.push(x, y, z);
356+
poleUvs.push(u, v);
357+
return poleVertexCount++;
358+
};
347359

348-
// Center vertices for caps
349-
vertices.push(0, 0, 0); // Bottom center
350-
vertices.push(0, height, 0); // Top center
351-
uvs.push(0.5, 0.5);
352-
uvs.push(0.5, 0.5);
353-
354-
const bottomCenterIndex = vertexOffset;
355-
const topCenterIndex = vertexOffset + 1;
356-
vertexOffset += 2;
357-
358-
// Create circle vertices for both top and bottom
359-
const bottomVertices: number[] = [];
360-
const topVertices: number[] = [];
361-
362-
for (let i = 0; i < poleSegments; i++) {
360+
// --- Side Vertices ---
361+
const sideVertexStartIndex = poleVertexCount;
362+
for (let i = 0; i <= poleSegments; i++) {
363363
const angle = (i / poleSegments) * 2 * Math.PI;
364364
const x = Math.cos(angle) * innerRadius;
365365
const z = Math.sin(angle) * innerRadius;
366-
367-
// Bottom circle
368-
vertices.push(x, 0, z);
369-
uvs.push(0.5 + 0.5 * Math.cos(angle), 0.5 + 0.5 * Math.sin(angle));
370-
bottomVertices.push(vertexOffset);
371-
vertexOffset++;
372-
373-
// Top circle
374-
vertices.push(x, height, z);
375-
uvs.push(0.5 + 0.5 * Math.cos(angle), 0.5 + 0.5 * Math.sin(angle));
376-
topVertices.push(vertexOffset);
377-
vertexOffset++;
366+
addVertex(x, 0, z, i / poleSegments, 0); // Bottom vertex
367+
addVertex(x, height, z, i / poleSegments, 1); // Top vertex
378368
}
379369

380-
// Create side faces
370+
// --- Side Faces ---
381371
for (let i = 0; i < poleSegments; i++) {
382-
const next = (i + 1) % poleSegments;
372+
const bl = sideVertexStartIndex + i * 2;
373+
const tl = sideVertexStartIndex + i * 2 + 1;
374+
const br = sideVertexStartIndex + (i + 1) * 2;
375+
const tr = sideVertexStartIndex + (i + 1) * 2 + 1;
376+
poleIndices.push(bl, br, tl, br, tr, tl);
377+
}
383378

384-
const bottom1 = bottomVertices[i];
385-
const bottom2 = bottomVertices[next];
386-
const top1 = topVertices[i];
387-
const top2 = topVertices[next];
379+
// --- Cap Vertices & Faces ---
380+
const createCap = (isTop: boolean) => {
381+
const y = isTop ? height : 0;
382+
const centerIdx = addVertex(0, y, 0, 0.5, 0.5);
383+
const ringStartIndex = poleVertexCount;
384+
385+
for (let i = 0; i <= poleSegments; i++) {
386+
const angle = (i / poleSegments) * 2 * Math.PI;
387+
const x = Math.cos(angle) * innerRadius;
388+
const z = Math.sin(angle) * innerRadius;
389+
addVertex(x, y, z, 0.5 + Math.cos(angle) * 0.5, 0.5 + Math.sin(angle) * 0.5);
390+
}
388391

389-
// Side quad (two triangles)
390-
indices.push(bottom1, top1, bottom2);
391-
indices.push(bottom2, top1, top2);
392-
}
392+
for (let i = 0; i < poleSegments; i++) {
393+
const p1 = ringStartIndex + i;
394+
const p2 = ringStartIndex + i + 1;
395+
if (isTop) {
396+
poleIndices.push(centerIdx, p1, p2);
397+
} else {
398+
poleIndices.push(centerIdx, p2, p1); // Flip winding for bottom cap
399+
}
400+
}
401+
};
393402

394-
// Create bottom cap
395-
for (let i = 0; i < poleSegments; i++) {
396-
const next = (i + 1) % poleSegments;
397-
indices.push(bottomCenterIndex, bottomVertices[next], bottomVertices[i]);
398-
}
403+
createCap(false); // Bottom cap
404+
createCap(true); // Top cap
399405

400-
// Create top cap
401-
for (let i = 0; i < poleSegments; i++) {
402-
const next = (i + 1) % poleSegments;
403-
indices.push(topCenterIndex, topVertices[i], topVertices[next]);
404-
}
406+
// --- Add pole to the main geometry ---
407+
const baseVertexIndex = vertexOffset;
408+
vertices.push(...poleVertices);
409+
uvs.push(...poleUvs);
410+
poleIndices.forEach((i) => indices.push(i + baseVertexIndex));
411+
vertexOffset += poleVertexCount;
405412

406413
// Create steps - simple rectangular treads
407414
for (let step = 0; step < steps; step++) {

src/editor/hooks/useEditorHandlers.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export const useEditorHandlers = ({
6060
createTube,
6161
createCross,
6262
createCustomModel,
63+
createDirectionalLight,
64+
createPointLight,
65+
createSpotLight,
66+
createAmbientLight,
6367
} = useEntityCreation();
6468

6569
// Scene action hooks - get both new and legacy methods
@@ -80,7 +84,7 @@ export const useEditorHandlers = ({
8084

8185
// Entity creation handler
8286
const handleAddObject = useCallback(
83-
async (type: ShapeType, modelPath?: string) => {
87+
async (type: ShapeType | string, modelPath?: string) => {
8488
try {
8589
let entity;
8690
switch (type) {
@@ -172,8 +176,20 @@ export const useEditorHandlers = ({
172176
throw new Error('CustomModel requires a modelPath');
173177
}
174178
break;
179+
case 'DirectionalLight':
180+
entity = createDirectionalLight();
181+
break;
182+
case 'PointLight':
183+
entity = createPointLight();
184+
break;
185+
case 'SpotLight':
186+
entity = createSpotLight();
187+
break;
188+
case 'AmbientLight':
189+
entity = createAmbientLight();
190+
break;
175191
default:
176-
entity = createEntity(type);
192+
entity = createEntity(type as string);
177193
break;
178194
}
179195

@@ -218,6 +234,10 @@ export const useEditorHandlers = ({
218234
createCross,
219235
createCamera,
220236
createCustomModel,
237+
createDirectionalLight,
238+
createPointLight,
239+
createSpotLight,
240+
createAmbientLight,
221241
setSelectedId,
222242
setStatusMessage,
223243
setShowAddMenu,

0 commit comments

Comments
 (0)