nova64 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/index.html +6 -1
- package/package.json +9 -2
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +20 -0
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
# Nova64 Voxel Engine API Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Nova64 Voxel Engine enables Minecraft-style block-based games with infinite procedurally generated worlds. It features:
|
|
6
|
+
|
|
7
|
+
- **Chunk-based world management** - Efficient memory usage with 16x64x16 chunks
|
|
8
|
+
- **Greedy meshing** - Optimized rendering with minimal draw calls
|
|
9
|
+
- **Procedural terrain generation** - Perlin noise with biomes, caves, and height variation
|
|
10
|
+
- **Multiple block types** - 15 different block materials
|
|
11
|
+
- **Real-time editing** - Place and break blocks instantly
|
|
12
|
+
- **Collision detection** - Full physics support for player and entities
|
|
13
|
+
- **Infinite worlds** - Chunks load/unload dynamically
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
export function init() {
|
|
19
|
+
// Setup lighting
|
|
20
|
+
setAmbientLight(0x666666);
|
|
21
|
+
setDirectionalLight([1, 2, 1], 0xffffff, 0.6);
|
|
22
|
+
|
|
23
|
+
// Generate world around player
|
|
24
|
+
updateVoxelWorld(playerX, playerZ);
|
|
25
|
+
|
|
26
|
+
// Add some trees
|
|
27
|
+
placeVoxelTree(10, 35, 10);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function update(dt) {
|
|
31
|
+
// Update chunks as player moves
|
|
32
|
+
updateVoxelWorld(playerX, playerZ);
|
|
33
|
+
|
|
34
|
+
// Get block at position
|
|
35
|
+
const block = getVoxelBlock(x, y, z);
|
|
36
|
+
|
|
37
|
+
// Place or remove blocks
|
|
38
|
+
if (isKeyPressed('Space')) {
|
|
39
|
+
setVoxelBlock(x, y, z, BLOCK_TYPES.STONE);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Block Types
|
|
45
|
+
|
|
46
|
+
### Available Blocks
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
BLOCK_TYPES.AIR; // 0 - Empty space
|
|
50
|
+
BLOCK_TYPES.GRASS; // 1 - Green grass block
|
|
51
|
+
BLOCK_TYPES.DIRT; // 2 - Brown dirt
|
|
52
|
+
BLOCK_TYPES.STONE; // 3 - Gray stone
|
|
53
|
+
BLOCK_TYPES.SAND; // 4 - Yellow sand
|
|
54
|
+
BLOCK_TYPES.WATER; // 5 - Blue water (transparent)
|
|
55
|
+
BLOCK_TYPES.WOOD; // 6 - Dark brown wood log
|
|
56
|
+
BLOCK_TYPES.LEAVES; // 7 - Green tree leaves
|
|
57
|
+
BLOCK_TYPES.COBBLESTONE; // 8 - Gray cobblestone
|
|
58
|
+
BLOCK_TYPES.PLANKS; // 9 - Wooden planks
|
|
59
|
+
BLOCK_TYPES.GLASS; // 10 - Light blue glass (transparent)
|
|
60
|
+
BLOCK_TYPES.BRICK; // 11 - Red brick
|
|
61
|
+
BLOCK_TYPES.SNOW; // 12 - White snow
|
|
62
|
+
BLOCK_TYPES.ICE; // 13 - Light blue ice
|
|
63
|
+
BLOCK_TYPES.BEDROCK; // 14 - Dark gray bedrock (bottom layer)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## World Management
|
|
67
|
+
|
|
68
|
+
### updateVoxelWorld(playerX, playerZ)
|
|
69
|
+
|
|
70
|
+
Updates visible chunks around the player position. Call this in your `update()` function when the player moves significantly.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
|
|
74
|
+
- `playerX` - Player's X world coordinate
|
|
75
|
+
- `playerZ` - Player's Z world coordinate
|
|
76
|
+
|
|
77
|
+
**Example:**
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
export function update(dt) {
|
|
81
|
+
// Update every few frames to reduce overhead
|
|
82
|
+
if (Math.random() < 0.1) {
|
|
83
|
+
updateVoxelWorld(player.pos[0], player.pos[2]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Performance:** Automatically loads chunks within render distance (4 chunks = 64 blocks) and unloads distant chunks to manage memory.
|
|
89
|
+
|
|
90
|
+
## Block Manipulation
|
|
91
|
+
|
|
92
|
+
### getVoxelBlock(x, y, z)
|
|
93
|
+
|
|
94
|
+
Returns the block type at the specified world coordinates.
|
|
95
|
+
|
|
96
|
+
**Parameters:**
|
|
97
|
+
|
|
98
|
+
- `x` - World X coordinate
|
|
99
|
+
- `y` - World Y coordinate (0-63)
|
|
100
|
+
- `z` - World Z coordinate
|
|
101
|
+
|
|
102
|
+
**Returns:** Block type constant (0-14) or `BLOCK_TYPES.AIR` if out of bounds
|
|
103
|
+
|
|
104
|
+
**Example:**
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const blockUnderPlayer = getVoxelBlock(
|
|
108
|
+
Math.floor(playerX),
|
|
109
|
+
Math.floor(playerY) - 1,
|
|
110
|
+
Math.floor(playerZ)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (blockUnderPlayer === BLOCK_TYPES.WATER) {
|
|
114
|
+
console.log('Player is standing on water!');
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### setVoxelBlock(x, y, z, blockType)
|
|
119
|
+
|
|
120
|
+
Places or removes a block at the specified world coordinates. Automatically updates chunk meshes.
|
|
121
|
+
|
|
122
|
+
**Parameters:**
|
|
123
|
+
|
|
124
|
+
- `x` - World X coordinate
|
|
125
|
+
- `y` - World Y coordinate (0-63)
|
|
126
|
+
- `z` - World Z coordinate
|
|
127
|
+
- `blockType` - Block type constant (use `BLOCK_TYPES.AIR` to remove)
|
|
128
|
+
|
|
129
|
+
**Example:**
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// Break block (set to air)
|
|
133
|
+
setVoxelBlock(10, 35, 10, BLOCK_TYPES.AIR);
|
|
134
|
+
|
|
135
|
+
// Place stone block
|
|
136
|
+
setVoxelBlock(10, 35, 10, BLOCK_TYPES.STONE);
|
|
137
|
+
|
|
138
|
+
// Build a wall
|
|
139
|
+
for (let y = 0; y < 5; y++) {
|
|
140
|
+
setVoxelBlock(20, 30 + y, 20, BLOCK_TYPES.BRICK);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Performance:** Efficiently updates only affected chunks. Adjacent chunks are automatically marked dirty if the block is on a boundary.
|
|
145
|
+
|
|
146
|
+
## Raycasting
|
|
147
|
+
|
|
148
|
+
### raycastVoxelBlock(origin, direction, maxDistance)
|
|
149
|
+
|
|
150
|
+
Casts a ray from a point in a direction to find the first solid block hit. Perfect for mining/building mechanics.
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
|
|
154
|
+
- `origin` - Starting point `[x, y, z]`
|
|
155
|
+
- `direction` - Ray direction `[dx, dy, dz]` (should be normalized)
|
|
156
|
+
- `maxDistance` - Maximum ray distance (default: 10)
|
|
157
|
+
|
|
158
|
+
**Returns:** Object with:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
{
|
|
162
|
+
hit: true, // Whether a block was hit
|
|
163
|
+
position: [x, y, z], // Block coordinates
|
|
164
|
+
blockType: 3, // Type of block hit
|
|
165
|
+
distance: 5.2 // Distance to block
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Example:**
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
// Camera/player looking direction
|
|
173
|
+
const lookDir = [
|
|
174
|
+
-Math.sin(yaw) * Math.cos(pitch),
|
|
175
|
+
Math.sin(pitch),
|
|
176
|
+
-Math.cos(yaw) * Math.cos(pitch),
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const result = raycastVoxelBlock(
|
|
180
|
+
[playerX, playerY + eyeHeight, playerZ],
|
|
181
|
+
lookDir,
|
|
182
|
+
10 // 10 block reach
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (result.hit) {
|
|
186
|
+
// Left click to break
|
|
187
|
+
if (isMousePressed(0)) {
|
|
188
|
+
const [x, y, z] = result.position;
|
|
189
|
+
setVoxelBlock(x, y, z, BLOCK_TYPES.AIR);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Right click to place
|
|
193
|
+
if (isMousePressed(2)) {
|
|
194
|
+
// Calculate adjacent block position
|
|
195
|
+
const placeX = Math.floor(result.position[0] - Math.sign(lookDir[0]) * 0.5);
|
|
196
|
+
const placeY = Math.floor(result.position[1] - Math.sign(lookDir[1]) * 0.5);
|
|
197
|
+
const placeZ = Math.floor(result.position[2] - Math.sign(lookDir[2]) * 0.5);
|
|
198
|
+
|
|
199
|
+
setVoxelBlock(placeX, placeY, placeZ, BLOCK_TYPES.COBBLESTONE);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Collision Detection
|
|
205
|
+
|
|
206
|
+
### checkVoxelCollision(position, size)
|
|
207
|
+
|
|
208
|
+
Checks if an axis-aligned bounding box collides with any solid blocks.
|
|
209
|
+
|
|
210
|
+
**Parameters:**
|
|
211
|
+
|
|
212
|
+
- `position` - Center position `[x, y, z]`
|
|
213
|
+
- `size` - Half-size of bounding box (radius)
|
|
214
|
+
|
|
215
|
+
**Returns:** `true` if colliding with solid block, `false` otherwise
|
|
216
|
+
|
|
217
|
+
**Example:**
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
const playerSize = 0.3; // 0.6 block width
|
|
221
|
+
const playerHeight = 1.8; // 1.8 blocks tall
|
|
222
|
+
|
|
223
|
+
// Check if player's feet are on solid ground
|
|
224
|
+
const onGround = checkVoxelCollision([player.pos[0], player.pos[1], player.pos[2]], playerSize);
|
|
225
|
+
|
|
226
|
+
// Check head collision
|
|
227
|
+
const hitCeiling = checkVoxelCollision(
|
|
228
|
+
[player.pos[0], player.pos[1] + playerHeight, player.pos[2]],
|
|
229
|
+
playerSize
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Simple physics
|
|
233
|
+
if (onGround && player.vel[1] <= 0) {
|
|
234
|
+
player.vel[1] = 0;
|
|
235
|
+
player.canJump = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (hitCeiling && player.vel[1] > 0) {
|
|
239
|
+
player.vel[1] = 0;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Structure Generation
|
|
244
|
+
|
|
245
|
+
### placeVoxelTree(x, y, z)
|
|
246
|
+
|
|
247
|
+
Generates a tree structure at the specified coordinates. Trees consist of wood trunk with leaf canopy.
|
|
248
|
+
|
|
249
|
+
**Parameters:**
|
|
250
|
+
|
|
251
|
+
- `x` - World X coordinate for tree base
|
|
252
|
+
- `y` - World Y coordinate for ground level
|
|
253
|
+
- `z` - World Z coordinate for tree base
|
|
254
|
+
|
|
255
|
+
**Example:**
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
export function init() {
|
|
259
|
+
// Generate initial world
|
|
260
|
+
updateVoxelWorld(0, 0);
|
|
261
|
+
|
|
262
|
+
// Add trees to landscape
|
|
263
|
+
placeVoxelTree(10, 35, 10);
|
|
264
|
+
placeVoxelTree(20, 33, 15);
|
|
265
|
+
placeVoxelTree(-5, 36, 8);
|
|
266
|
+
|
|
267
|
+
// Cluster of trees
|
|
268
|
+
for (let i = 0; i < 10; i++) {
|
|
269
|
+
const x = Math.random() * 100 - 50;
|
|
270
|
+
const z = Math.random() * 100 - 50;
|
|
271
|
+
const y = getGroundHeight(x, z);
|
|
272
|
+
placeVoxelTree(x, y, z);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Tree Structure:**
|
|
278
|
+
|
|
279
|
+
- Trunk: 4-6 blocks tall of `BLOCK_TYPES.WOOD`
|
|
280
|
+
- Leaves: Spherical canopy of `BLOCK_TYPES.LEAVES`
|
|
281
|
+
- Automatic variation in size
|
|
282
|
+
|
|
283
|
+
## Complete Minecraft-Style Game Example
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
const player = {
|
|
287
|
+
pos: [8, 40, 8],
|
|
288
|
+
vel: [0, 0, 0],
|
|
289
|
+
rot: [0, 0], // yaw, pitch
|
|
290
|
+
onGround: false,
|
|
291
|
+
selectedBlock: BLOCK_TYPES.GRASS,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
let mouseLocked = false;
|
|
295
|
+
|
|
296
|
+
export function init() {
|
|
297
|
+
// Setup world
|
|
298
|
+
setAmbientLight(0x666666);
|
|
299
|
+
setDirectionalLight([1, 2, 1], 0xffffff, 0.6);
|
|
300
|
+
setFog(0x87ceeb, 80, 200);
|
|
301
|
+
|
|
302
|
+
// Generate terrain
|
|
303
|
+
updateVoxelWorld(player.pos[0], player.pos[2]);
|
|
304
|
+
|
|
305
|
+
// Add trees
|
|
306
|
+
for (let i = 0; i < 20; i++) {
|
|
307
|
+
const x = Math.random() * 200 - 100;
|
|
308
|
+
const z = Math.random() * 200 - 100;
|
|
309
|
+
placeVoxelTree(x, 35, z);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Pointer lock for mouse control
|
|
313
|
+
const canvas = document.getElementById('screen');
|
|
314
|
+
canvas.addEventListener('click', () => {
|
|
315
|
+
if (!mouseLocked) {
|
|
316
|
+
canvas.requestPointerLock();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
document.addEventListener('pointerlockchange', () => {
|
|
321
|
+
mouseLocked = document.pointerLockElement === canvas;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
canvas.addEventListener('mousemove', e => {
|
|
325
|
+
if (mouseLocked) {
|
|
326
|
+
player.rot[0] -= e.movementX * 0.002;
|
|
327
|
+
player.rot[1] -= e.movementY * 0.002;
|
|
328
|
+
player.rot[1] = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, player.rot[1]));
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function update(dt) {
|
|
334
|
+
if (!mouseLocked) return;
|
|
335
|
+
|
|
336
|
+
// Movement
|
|
337
|
+
const forward = [-Math.sin(player.rot[0]), 0, -Math.cos(player.rot[0])];
|
|
338
|
+
const right = [-Math.sin(player.rot[0] + Math.PI / 2), 0, -Math.cos(player.rot[0] + Math.PI / 2)];
|
|
339
|
+
|
|
340
|
+
const speed = 4.0;
|
|
341
|
+
if (isKeyDown('KeyW')) {
|
|
342
|
+
player.vel[0] += forward[0] * speed * dt;
|
|
343
|
+
player.vel[2] += forward[2] * speed * dt;
|
|
344
|
+
}
|
|
345
|
+
if (isKeyDown('KeyS')) {
|
|
346
|
+
player.vel[0] -= forward[0] * speed * dt;
|
|
347
|
+
player.vel[2] -= forward[2] * speed * dt;
|
|
348
|
+
}
|
|
349
|
+
if (isKeyDown('KeyA')) {
|
|
350
|
+
player.vel[0] += right[0] * speed * dt;
|
|
351
|
+
player.vel[2] += right[2] * speed * dt;
|
|
352
|
+
}
|
|
353
|
+
if (isKeyDown('KeyD')) {
|
|
354
|
+
player.vel[0] -= right[0] * speed * dt;
|
|
355
|
+
player.vel[2] -= right[2] * speed * dt;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Jump
|
|
359
|
+
if (isKeyPressed('Space') && player.onGround) {
|
|
360
|
+
player.vel[1] = 8.0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Gravity
|
|
364
|
+
player.vel[1] -= 20.0 * dt;
|
|
365
|
+
|
|
366
|
+
// Apply velocity
|
|
367
|
+
player.pos[0] += player.vel[0] * dt;
|
|
368
|
+
player.pos[1] += player.vel[1] * dt;
|
|
369
|
+
player.pos[2] += player.vel[2] * dt;
|
|
370
|
+
|
|
371
|
+
// Collision
|
|
372
|
+
const collision = checkVoxelCollision(player.pos, 0.3);
|
|
373
|
+
if (collision && player.vel[1] <= 0) {
|
|
374
|
+
player.pos[1] = Math.floor(player.pos[1]) + 1;
|
|
375
|
+
player.vel[1] = 0;
|
|
376
|
+
player.onGround = true;
|
|
377
|
+
} else {
|
|
378
|
+
player.onGround = false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Damping
|
|
382
|
+
player.vel[0] *= 0.8;
|
|
383
|
+
player.vel[2] *= 0.8;
|
|
384
|
+
|
|
385
|
+
// Camera
|
|
386
|
+
setCameraPosition(player.pos[0], player.pos[1] + 1.6, player.pos[2]);
|
|
387
|
+
const lookDir = [
|
|
388
|
+
-Math.sin(player.rot[0]) * Math.cos(player.rot[1]),
|
|
389
|
+
Math.sin(player.rot[1]),
|
|
390
|
+
-Math.cos(player.rot[0]) * Math.cos(player.rot[1]),
|
|
391
|
+
];
|
|
392
|
+
setCameraLookAt(lookDir);
|
|
393
|
+
|
|
394
|
+
// Block interaction
|
|
395
|
+
const rayOrigin = [player.pos[0], player.pos[1] + 1.6, player.pos[2]];
|
|
396
|
+
const result = raycastVoxelBlock(rayOrigin, lookDir, 10);
|
|
397
|
+
|
|
398
|
+
if (result.hit) {
|
|
399
|
+
const [x, y, z] = result.position;
|
|
400
|
+
|
|
401
|
+
// Break block
|
|
402
|
+
if (isMousePressed(0)) {
|
|
403
|
+
setVoxelBlock(x, y, z, BLOCK_TYPES.AIR);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Place block
|
|
407
|
+
if (isMousePressed(2)) {
|
|
408
|
+
const placeX = x - Math.sign(lookDir[0]);
|
|
409
|
+
const placeY = y - Math.sign(lookDir[1]);
|
|
410
|
+
const placeZ = z - Math.sign(lookDir[2]);
|
|
411
|
+
setVoxelBlock(placeX, placeY, placeZ, player.selectedBlock);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Update world chunks
|
|
416
|
+
if (Math.random() < 0.1) {
|
|
417
|
+
updateVoxelWorld(player.pos[0], player.pos[2]);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Block selection
|
|
421
|
+
for (let i = 1; i <= 9; i++) {
|
|
422
|
+
if (isKeyPressed(i.toString())) {
|
|
423
|
+
player.selectedBlock = i;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function draw() {
|
|
429
|
+
cls();
|
|
430
|
+
|
|
431
|
+
// Draw crosshair
|
|
432
|
+
if (mouseLocked) {
|
|
433
|
+
const cx = 320,
|
|
434
|
+
cy = 180,
|
|
435
|
+
size = 8;
|
|
436
|
+
rectfill(cx - size, cy - 1, cx + size, cy + 1, rgba8(1, 1, 1, 1));
|
|
437
|
+
rectfill(cx - 1, cy - size, cx + 1, cy + size, rgba8(1, 1, 1, 1));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// HUD
|
|
441
|
+
print(`FPS: ${Math.round(1 / getDeltaTime())}`, 4, 4, rgba8(1, 1, 1, 1));
|
|
442
|
+
print(
|
|
443
|
+
`Pos: ${Math.floor(player.pos[0])}, ${Math.floor(player.pos[1])}, ${Math.floor(player.pos[2])}`,
|
|
444
|
+
4,
|
|
445
|
+
12,
|
|
446
|
+
rgba8(1, 1, 1, 1)
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Terrain Generation
|
|
452
|
+
|
|
453
|
+
The voxel engine uses **Perlin noise** for realistic terrain with:
|
|
454
|
+
|
|
455
|
+
### Features:
|
|
456
|
+
|
|
457
|
+
- **Height variation** - Hills and valleys (20-52 blocks high)
|
|
458
|
+
- **Biomes** - Grass, sand, snow based on temperature/moisture
|
|
459
|
+
- **Cave systems** - Underground tunnels and caverns
|
|
460
|
+
- **Water level** - Ocean at Y=30
|
|
461
|
+
- **Bedrock layer** - Indestructible bottom (Y=0)
|
|
462
|
+
|
|
463
|
+
### Biome Selection:
|
|
464
|
+
|
|
465
|
+
```javascript
|
|
466
|
+
Temperature > 0.7 → Snow biome
|
|
467
|
+
Moisture < 0.3 → Desert (sand)
|
|
468
|
+
Default → Grass/dirt
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Layer Structure:
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
Y=0 → Bedrock (indestructible)
|
|
475
|
+
Y=1-30 → Stone
|
|
476
|
+
Y=31-33 → Dirt
|
|
477
|
+
Y=34 → Surface (grass/sand/snow)
|
|
478
|
+
Y=35-64 → Air (or water below Y=30)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Performance Tips
|
|
482
|
+
|
|
483
|
+
### Chunk Management
|
|
484
|
+
|
|
485
|
+
- **Render distance**: 4 chunks (64 blocks) - adjustable in code
|
|
486
|
+
- **Chunk size**: 16x64x16 blocks per chunk
|
|
487
|
+
- **Auto-unloading**: Distant chunks automatically removed from memory
|
|
488
|
+
|
|
489
|
+
### Optimization Techniques Used:
|
|
490
|
+
|
|
491
|
+
1. **Greedy meshing** - Combines adjacent faces to reduce vertices
|
|
492
|
+
2. **Face culling** - Hidden faces not rendered
|
|
493
|
+
3. **Chunk dirty flags** - Only rebuild when blocks change
|
|
494
|
+
4. **BufferGeometry** - GPU-optimized mesh storage
|
|
495
|
+
5. **Flat shading** - Fast lighting calculation
|
|
496
|
+
6. **Ambient occlusion** - Per-face brightness variation
|
|
497
|
+
|
|
498
|
+
### Best Practices:
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
// ✅ Good - Update chunks occasionally
|
|
502
|
+
if (frameCount % 10 === 0) {
|
|
503
|
+
updateVoxelWorld(playerX, playerZ);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ❌ Bad - Don't update every frame
|
|
507
|
+
export function update(dt) {
|
|
508
|
+
updateVoxelWorld(playerX, playerZ); // Too expensive!
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ✅ Good - Batch block changes
|
|
512
|
+
for (let i = 0; i < 100; i++) {
|
|
513
|
+
setVoxelBlock(x + i, y, z, BLOCK_TYPES.STONE);
|
|
514
|
+
}
|
|
515
|
+
// Chunks updated once after loop
|
|
516
|
+
|
|
517
|
+
// ✅ Good - Check collision efficiently
|
|
518
|
+
const onGround = checkVoxelCollision(playerFeetPos, 0.3);
|
|
519
|
+
|
|
520
|
+
// ❌ Bad - Don't check every nearby block manually
|
|
521
|
+
for (let x = -5; x < 5; x++) {
|
|
522
|
+
for (let z = -5; z < 5; z++) {
|
|
523
|
+
if (getVoxelBlock(px + x, py, pz + z) !== BLOCK_TYPES.AIR) {
|
|
524
|
+
// This is way too slow!
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Common Patterns
|
|
531
|
+
|
|
532
|
+
### Finding Ground Height
|
|
533
|
+
|
|
534
|
+
```javascript
|
|
535
|
+
function getGroundHeight(x, z) {
|
|
536
|
+
for (let y = 63; y >= 0; y--) {
|
|
537
|
+
const block = getVoxelBlock(x, y, z);
|
|
538
|
+
if (block !== BLOCK_TYPES.AIR && block !== BLOCK_TYPES.WATER) {
|
|
539
|
+
return y + 1;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Building Structures
|
|
547
|
+
|
|
548
|
+
```javascript
|
|
549
|
+
// House
|
|
550
|
+
function buildHouse(x, y, z) {
|
|
551
|
+
// Floor
|
|
552
|
+
for (let dx = 0; dx < 8; dx++) {
|
|
553
|
+
for (let dz = 0; dz < 8; dz++) {
|
|
554
|
+
setVoxelBlock(x + dx, y, z + dz, BLOCK_TYPES.PLANKS);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Walls
|
|
559
|
+
for (let dy = 1; dy < 5; dy++) {
|
|
560
|
+
for (let dx = 0; dx < 8; dx++) {
|
|
561
|
+
setVoxelBlock(x + dx, y + dy, z, BLOCK_TYPES.WOOD);
|
|
562
|
+
setVoxelBlock(x + dx, y + dy, z + 7, BLOCK_TYPES.WOOD);
|
|
563
|
+
}
|
|
564
|
+
for (let dz = 0; dz < 8; dz++) {
|
|
565
|
+
setVoxelBlock(x, y + dy, z + dz, BLOCK_TYPES.WOOD);
|
|
566
|
+
setVoxelBlock(x + 7, y + dy, z + dz, BLOCK_TYPES.WOOD);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Roof
|
|
571
|
+
for (let dx = 0; dx < 8; dx++) {
|
|
572
|
+
for (let dz = 0; dz < 8; dz++) {
|
|
573
|
+
setVoxelBlock(x + dx, y + 5, z + dz, BLOCK_TYPES.BRICK);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Door
|
|
578
|
+
setVoxelBlock(x + 3, y + 1, z, BLOCK_TYPES.AIR);
|
|
579
|
+
setVoxelBlock(x + 3, y + 2, z, BLOCK_TYPES.AIR);
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Mining System
|
|
584
|
+
|
|
585
|
+
```javascript
|
|
586
|
+
let mining = false;
|
|
587
|
+
let miningProgress = 0;
|
|
588
|
+
let miningBlock = null;
|
|
589
|
+
|
|
590
|
+
function updateMining(dt) {
|
|
591
|
+
const result = raycastVoxelBlock(eyePos, lookDir, 10);
|
|
592
|
+
|
|
593
|
+
if (result.hit && isMouseDown(0)) {
|
|
594
|
+
// Same block
|
|
595
|
+
if (
|
|
596
|
+
miningBlock &&
|
|
597
|
+
miningBlock[0] === result.position[0] &&
|
|
598
|
+
miningBlock[1] === result.position[1] &&
|
|
599
|
+
miningBlock[2] === result.position[2]
|
|
600
|
+
) {
|
|
601
|
+
miningProgress += dt;
|
|
602
|
+
|
|
603
|
+
// Break after 1 second
|
|
604
|
+
if (miningProgress >= 1.0) {
|
|
605
|
+
const [x, y, z] = miningBlock;
|
|
606
|
+
setVoxelBlock(x, y, z, BLOCK_TYPES.AIR);
|
|
607
|
+
miningProgress = 0;
|
|
608
|
+
miningBlock = null;
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
// New block
|
|
612
|
+
miningBlock = result.position;
|
|
613
|
+
miningProgress = 0;
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
miningProgress = 0;
|
|
617
|
+
miningBlock = null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Troubleshooting
|
|
623
|
+
|
|
624
|
+
### Blocks Not Appearing
|
|
625
|
+
|
|
626
|
+
- Call `updateVoxelWorld()` after placing blocks
|
|
627
|
+
- Check if coordinates are within valid range (Y: 0-63)
|
|
628
|
+
- Verify chunks are loading (check console logs)
|
|
629
|
+
|
|
630
|
+
### Performance Issues
|
|
631
|
+
|
|
632
|
+
- Reduce render distance in code (change `RENDER_DISTANCE`)
|
|
633
|
+
- Update chunks less frequently
|
|
634
|
+
- Limit block placement/breaking rate
|
|
635
|
+
|
|
636
|
+
### Collision Not Working
|
|
637
|
+
|
|
638
|
+
- Ensure player size matches collision box
|
|
639
|
+
- Check Y coordinate (height above ground)
|
|
640
|
+
- Verify blocks are solid (not AIR or WATER)
|
|
641
|
+
|
|
642
|
+
### Raycasting Missing Blocks
|
|
643
|
+
|
|
644
|
+
- Increase ray step precision (reduce step size)
|
|
645
|
+
- Check max distance parameter
|
|
646
|
+
- Ensure camera position is correct
|
|
647
|
+
|
|
648
|
+
## API Reference Summary
|
|
649
|
+
|
|
650
|
+
| Function | Description | Parameters |
|
|
651
|
+
| -------------------------------------- | ---------------------------------- | --------------------------- |
|
|
652
|
+
| `updateVoxelWorld(x, z)` | Load/update chunks around position | x, z: world coords |
|
|
653
|
+
| `getVoxelBlock(x, y, z)` | Get block type at position | x, y, z: world coords |
|
|
654
|
+
| `setVoxelBlock(x, y, z, type)` | Place/remove block | coords + block type |
|
|
655
|
+
| `raycastVoxelBlock(origin, dir, dist)` | Find block along ray | origin, direction, distance |
|
|
656
|
+
| `checkVoxelCollision(pos, size)` | Check AABB collision | position, half-size |
|
|
657
|
+
| `placeVoxelTree(x, y, z)` | Generate tree structure | x, y, z: base position |
|
|
658
|
+
| `BLOCK_TYPES.*` | Block type constants | 15 types (AIR to BEDROCK) |
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
**Ready to build your Minecraft clone!** Check out the complete example in `examples/minecraft-demo/` for a fully playable game with all features implemented.
|