TomatitoToho commited on
Commit
f85ffd4
·
verified ·
1 Parent(s): bb87cb7

Upload src/engine/voxel/meshBuilder.ts with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/engine/voxel/meshBuilder.ts +198 -0
src/engine/voxel/meshBuilder.ts ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Chunk Mesh Builder with Texture Atlas support
3
+ * Converts chunk block data into Three.js renderable geometry with real MC textures
4
+ */
5
+
6
+ import {
7
+ CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, MIN_BUILD_HEIGHT,
8
+ BLOCK_AIR, BLOCK_DEFS, BLOCK_FACES, ATLAS_SIZE, TILES_PER_ROW, TEXTURE_SIZE,
9
+ } from '../constants'
10
+ import type { ChunkData, ChunkMeshData } from '@/types'
11
+
12
+ const FACE_INDICES = [0, 1, 2, 0, 2, 3]
13
+
14
+ // UV coordinates for a single tile in the atlas
15
+ const TILE_UV = 1 / TILES_PER_ROW
16
+
17
+ // Cache for atlas mapping
18
+ let atlasMap: Record<string, { u: number; v: number }> | null = null
19
+
20
+ function getAtlasMap(): Record<string, { u: number; v: number }> {
21
+ if (atlasMap) return atlasMap
22
+ // Will be loaded asynchronously; for now use fallback
23
+ return {}
24
+ }
25
+
26
+ export function setAtlasMap(map: Record<string, { u: number; v: number }>) {
27
+ atlasMap = map
28
+ }
29
+
30
+ /**
31
+ * Get UV coordinates for a texture name in the atlas
32
+ */
33
+ function getTextureUV(texName: string): [number, number] | null {
34
+ if (!texName) return null
35
+ const map = getAtlasMap()
36
+ const entry = map[texName]
37
+ if (!entry) return null
38
+ // Convert pixel coords to UV coords
39
+ const u = entry.u / ATLAS_SIZE
40
+ const v = 1 - (entry.v + TEXTURE_SIZE) / ATLAS_SIZE // Flip V for WebGL
41
+ return [u, v]
42
+ }
43
+
44
+ /**
45
+ * Build mesh geometry for a chunk with texture atlas UVs
46
+ */
47
+ export function buildChunkMesh(chunk: ChunkData, adjacentChunks?: Map<string, ChunkData>): ChunkMeshData | null {
48
+ const positions: number[] = []
49
+ const normals: number[] = []
50
+ const uvs: number[] = []
51
+ const colors: number[] = []
52
+ const indices: number[] = []
53
+ let vertexCount = 0
54
+
55
+ for (let ly = 0; ly < CHUNK_HEIGHT; ly++) {
56
+ for (let lz = 0; lz < CHUNK_DEPTH; lz++) {
57
+ for (let lx = 0; lx < CHUNK_WIDTH; lx++) {
58
+ const blockIdx = lx + lz * CHUNK_WIDTH + ly * CHUNK_WIDTH * CHUNK_DEPTH
59
+ const blockId = chunk.blocks[blockIdx]
60
+
61
+ if (blockId === BLOCK_AIR) continue
62
+
63
+ const def = BLOCK_DEFS[blockId]
64
+ if (!def) continue
65
+
66
+ const wx = chunk.position.x * CHUNK_WIDTH + lx
67
+ const wy = ly + MIN_BUILD_HEIGHT
68
+ const wz = chunk.position.z * CHUNK_DEPTH + lz
69
+
70
+ const color = hexToRgb(def.color)
71
+
72
+ for (const [faceName, face] of Object.entries(BLOCK_FACES)) {
73
+ const nx = wx + face.dir[0]
74
+ const ny = wy + face.dir[1]
75
+ const nz = wz + face.dir[2]
76
+
77
+ const neighborId = getNeighborBlock(chunk, nx, ny, nz, adjacentChunks)
78
+ const neighborDef = BLOCK_DEFS[neighborId]
79
+
80
+ // Skip face if neighbor is solid and opaque
81
+ if (neighborDef && neighborDef.solid && !neighborDef.transparent) continue
82
+ // Skip if same transparent block (e.g., water next to water)
83
+ if (neighborId === blockId && def.transparent) continue
84
+
85
+ // Determine which texture to use for this face
86
+ let texName = def.texSide
87
+ if (faceName === 'top') texName = def.texTop
88
+ else if (faceName === 'bottom') texName = def.texBottom
89
+
90
+ const uvOffset = getTextureUV(texName)
91
+
92
+ // Add 4 vertices
93
+ for (let ci = 0; ci < 4; ci++) {
94
+ const corner = face.corners[ci]
95
+ positions.push(
96
+ lx + corner[0],
97
+ ly + (corner[1] === 1 ? 1 : 0),
98
+ lz + corner[2]
99
+ )
100
+ normals.push(face.dir[0], face.dir[1], face.dir[2])
101
+
102
+ // UV coordinates
103
+ if (uvOffset) {
104
+ const cu = ci === 0 || ci === 3 ? 0 : 1
105
+ const cv = ci < 2 ? 1 : 0
106
+ uvs.push(uvOffset[0] + cu * TILE_UV, uvOffset[1] + cv * TILE_UV)
107
+ } else {
108
+ // Fallback: simple UV
109
+ const cu = ci === 0 || ci === 3 ? 0 : 1
110
+ const cv = ci < 2 ? 1 : 0
111
+ uvs.push(cu, cv)
112
+ }
113
+
114
+ // Color with face shading
115
+ const shade = getFaceShade(faceName)
116
+ if (def.transparent && blockId !== BLOCK_LEAVES) {
117
+ colors.push(
118
+ (color.r / 255) * shade * 0.7,
119
+ (color.g / 255) * shade * 0.7,
120
+ (color.b / 255) * shade * 0.7
121
+ )
122
+ } else {
123
+ colors.push(
124
+ (color.r / 255) * shade,
125
+ (color.g / 255) * shade,
126
+ (color.b / 255) * shade
127
+ )
128
+ }
129
+ }
130
+
131
+ for (const i of FACE_INDICES) {
132
+ indices.push(vertexCount + i)
133
+ }
134
+ vertexCount += 4
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ if (positions.length === 0) return null
141
+
142
+ return {
143
+ vertices: new Float32Array(positions),
144
+ indices: new Uint32Array(indices),
145
+ normals: new Float32Array(normals),
146
+ uvs: new Float32Array(uvs),
147
+ colors: new Float32Array(colors),
148
+ ao: new Float32Array(0),
149
+ }
150
+ }
151
+
152
+ function getNeighborBlock(chunk: ChunkData, wx: number, wy: number, wz: number, adjacentChunks?: Map<string, ChunkData>): number {
153
+ if (wy < MIN_BUILD_HEIGHT || wy > 319) return BLOCK_AIR
154
+
155
+ const lx = wx - chunk.position.x * CHUNK_WIDTH
156
+ const lz = wz - chunk.position.z * CHUNK_DEPTH
157
+
158
+ if (lx >= 0 && lx < CHUNK_WIDTH && lz >= 0 && lz < CHUNK_DEPTH) {
159
+ const ly = wy - MIN_BUILD_HEIGHT
160
+ if (ly < 0 || ly >= CHUNK_HEIGHT) return BLOCK_AIR
161
+ return chunk.blocks[lx + lz * CHUNK_WIDTH + ly * CHUNK_WIDTH * CHUNK_DEPTH] || BLOCK_AIR
162
+ }
163
+
164
+ // Cross-chunk lookup
165
+ if (adjacentChunks) {
166
+ const ncx = Math.floor(wx / CHUNK_WIDTH)
167
+ const ncz = Math.floor(wz / CHUNK_DEPTH)
168
+ const nChunk = adjacentChunks.get(`${ncx},${ncz}`)
169
+ if (nChunk) {
170
+ const nlx = ((wx % CHUNK_WIDTH) + CHUNK_WIDTH) % CHUNK_WIDTH
171
+ const nlz = ((wz % CHUNK_DEPTH) + CHUNK_DEPTH) % CHUNK_DEPTH
172
+ const nly = wy - MIN_BUILD_HEIGHT
173
+ if (nly < 0 || nly >= CHUNK_HEIGHT) return BLOCK_AIR
174
+ return nChunk.blocks[nlx + nlz * CHUNK_WIDTH + nly * CHUNK_WIDTH * CHUNK_DEPTH] || BLOCK_AIR
175
+ }
176
+ }
177
+
178
+ return BLOCK_AIR
179
+ }
180
+
181
+ function getFaceShade(faceName: string): number {
182
+ switch (faceName) {
183
+ case 'top': return 1.0
184
+ case 'bottom': return 0.5
185
+ case 'north': return 0.8
186
+ case 'south': return 0.8
187
+ case 'east': return 0.6
188
+ case 'west': return 0.6
189
+ default: return 0.8
190
+ }
191
+ }
192
+
193
+ function hexToRgb(hex: string): { r: number; g: number; b: number } {
194
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
195
+ return result
196
+ ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) }
197
+ : { r: 128, g: 128, b: 128 }
198
+ }