perf(climate-sim): ⚡ Optimize WebGL buffer updates for hexagonal grid rendering in HexGLRenderer
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
0adb923687
commit
ac5cc84e99
1 changed files with 84 additions and 45 deletions
|
|
@ -87,7 +87,54 @@ function buildPolarHexGrid(
|
|||
}
|
||||
}
|
||||
|
||||
// Build geometry: each hex = center + 6 corners = 6 triangles
|
||||
return buildHexGeometry(tiles, hexR)
|
||||
}
|
||||
|
||||
/** Update hex mesh vertex colors from simulation data. Shared by equator and polar meshes. */
|
||||
function updateHexMeshColors(
|
||||
colorAttr: THREE.BufferAttribute,
|
||||
dataCoords: Array<{ col: number; row: number }>,
|
||||
texB: Float32Array,
|
||||
): void {
|
||||
const vertsPerHex = 7
|
||||
for (let h = 0; h < dataCoords.length; h++) {
|
||||
const { col, row } = dataCoords[h]!
|
||||
const texIdx = (row * MAP_W + col) * 4
|
||||
const terrainEnc = texB[texIdx + 2] ?? 0
|
||||
const [r, g, b] = terrainColorJS(terrainEnc)
|
||||
for (let v = 0; v < vertsPerHex; v++) {
|
||||
const ci = (h * vertsPerHex + v) * 3
|
||||
colorAttr.array[ci] = r
|
||||
colorAttr.array[ci + 1] = g
|
||||
colorAttr.array[ci + 2] = b
|
||||
}
|
||||
}
|
||||
colorAttr.needsUpdate = true
|
||||
}
|
||||
|
||||
// ── equator hex mesh builder ──────────────────────────────────────────────
|
||||
// Standard flat hex grid: 40 cols × 24 rows. Same hexCenter() positioning
|
||||
// as the GLSL shader, ensuring identical tile mapping.
|
||||
function buildEquatorHexGrid(): PolarHexData {
|
||||
const hexR = HEX_W / 2
|
||||
const tiles: Array<{ x: number; y: number; col: number; row: number }> = []
|
||||
|
||||
for (let row = 0; row < MAP_H; row++) {
|
||||
for (let col = 0; col < MAP_W; col++) {
|
||||
const cx = col * HEX_W * 0.75 + HEX_W / 2
|
||||
const cy = row * HEX_H + (col % 2 === 1 ? HEX_H / 2 : 0) + HEX_H / 2
|
||||
tiles.push({ x: cx, y: cy, col, row })
|
||||
}
|
||||
}
|
||||
|
||||
return buildHexGeometry(tiles, hexR)
|
||||
}
|
||||
|
||||
/** Shared geometry builder: takes positioned tiles and creates a BufferGeometry. */
|
||||
function buildHexGeometry(
|
||||
tiles: Array<{ x: number; y: number; col: number; row: number }>,
|
||||
hexR: number,
|
||||
): PolarHexData {
|
||||
const hexCount = tiles.length
|
||||
const vertsPerHex = 7
|
||||
const positions = new Float32Array(hexCount * vertsPerHex * 3)
|
||||
|
|
@ -97,13 +144,9 @@ function buildPolarHexGrid(
|
|||
for (let h = 0; h < hexCount; h++) {
|
||||
const tile = tiles[h]!
|
||||
const base = h * vertsPerHex
|
||||
|
||||
// Center vertex
|
||||
positions[base * 3] = tile.x
|
||||
positions[base * 3 + 1] = tile.y
|
||||
positions[base * 3 + 2] = 0
|
||||
|
||||
// 6 corner vertices (flat-top hex)
|
||||
for (let v = 0; v < 6; v++) {
|
||||
const a = (Math.PI / 3) * v
|
||||
const vi = base + 1 + v
|
||||
|
|
@ -111,8 +154,6 @@ function buildPolarHexGrid(
|
|||
positions[vi * 3 + 1] = tile.y + hexR * Math.sin(a)
|
||||
positions[vi * 3 + 2] = 0
|
||||
}
|
||||
|
||||
// 6 triangles (fan from center)
|
||||
for (let v = 0; v < 6; v++) {
|
||||
indices.push(base, base + 1 + v, base + 1 + (v + 1) % 6)
|
||||
}
|
||||
|
|
@ -122,33 +163,7 @@ function buildPolarHexGrid(
|
|||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
|
||||
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
|
||||
geometry.setIndex(indices)
|
||||
|
||||
const dataCoords = tiles.map((t) => ({ col: t.col, row: t.row }))
|
||||
return { geometry, dataCoords, hexCount }
|
||||
}
|
||||
|
||||
/** Update polar mesh vertex colors from the simulation data textures. */
|
||||
function updatePolarColors(
|
||||
colorAttr: THREE.BufferAttribute,
|
||||
dataCoords: Array<{ col: number; row: number }>,
|
||||
texA: Float32Array,
|
||||
texB: Float32Array,
|
||||
): void {
|
||||
const vertsPerHex = 7
|
||||
for (let h = 0; h < dataCoords.length; h++) {
|
||||
const { col, row } = dataCoords[h]!
|
||||
const texIdx = (row * MAP_W + col) * 4
|
||||
const terrainEnc = texB[texIdx + 2] ?? 0
|
||||
const [r, g, b] = terrainColorJS(terrainEnc)
|
||||
// Set all 7 vertices of this hex to the same color
|
||||
for (let v = 0; v < vertsPerHex; v++) {
|
||||
const ci = (h * vertsPerHex + v) * 3
|
||||
colorAttr.array[ci] = r
|
||||
colorAttr.array[ci + 1] = g
|
||||
colorAttr.array[ci + 2] = b
|
||||
}
|
||||
}
|
||||
colorAttr.needsUpdate = true
|
||||
return { geometry, dataCoords: tiles.map((t) => ({ col: t.col, row: t.row })), hexCount }
|
||||
}
|
||||
|
||||
function hexCenter(col: number, row: number): [number, number] {
|
||||
|
|
@ -173,18 +188,25 @@ interface PolarMeshState {
|
|||
numRings: number
|
||||
}
|
||||
|
||||
interface HexMeshState {
|
||||
mesh: THREE.Mesh
|
||||
dataCoords: Array<{ col: number; row: number }>
|
||||
}
|
||||
|
||||
interface GLObjects {
|
||||
renderer: THREE.WebGLRenderer
|
||||
camera: THREE.OrthographicCamera
|
||||
fieldScene: THREE.Scene
|
||||
overlayScene: THREE.Scene
|
||||
polarScene: THREE.Scene
|
||||
equatorScene: THREE.Scene
|
||||
texA: THREE.DataTexture
|
||||
texB: THREE.DataTexture
|
||||
texC: THREE.DataTexture
|
||||
refTexA: THREE.DataTexture
|
||||
fieldMaterial: THREE.ShaderMaterial
|
||||
fieldMesh: THREE.Mesh
|
||||
equatorHex: HexMeshState
|
||||
polarNorth: PolarMeshState
|
||||
polarSouth: PolarMeshState
|
||||
windGeo: THREE.BufferGeometry
|
||||
|
|
@ -234,6 +256,8 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
const overlayScene = new THREE.Scene()
|
||||
const polarScene = new THREE.Scene()
|
||||
polarScene.background = new THREE.Color(0x04030a)
|
||||
const equatorScene = new THREE.Scene()
|
||||
equatorScene.background = new THREE.Color(0x08060f)
|
||||
|
||||
const mkTex = (data: Float32Array): THREE.DataTexture => {
|
||||
const t = new THREE.DataTexture(data, MAP_W, MAP_H, THREE.RGBAFormat, THREE.FloatType)
|
||||
|
|
@ -320,13 +344,23 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
polarScene.add(mesh)
|
||||
// Initialize colors from current snapshot
|
||||
const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
|
||||
updatePolarColors(colorAttr, dataCoords, snapshot.texA, snapshot.texB)
|
||||
updateHexMeshColors(colorAttr, dataCoords, snapshot.texB)
|
||||
return { mesh, dataCoords, numRings: rings }
|
||||
}
|
||||
|
||||
let polarNorth = makePolarMesh('north', DEFAULT_POLAR_RINGS)
|
||||
let polarSouth = makePolarMesh('south', DEFAULT_POLAR_RINGS)
|
||||
|
||||
// ── equator hex mesh (same geometry approach as polar, flat grid layout) ──
|
||||
const equatorData = buildEquatorHexGrid()
|
||||
const equatorMesh = new THREE.Mesh(equatorData.geometry, polarMat)
|
||||
equatorScene.add(equatorMesh)
|
||||
updateHexMeshColors(
|
||||
equatorData.geometry.getAttribute('color') as THREE.BufferAttribute,
|
||||
equatorData.dataCoords, snapshot.texB,
|
||||
)
|
||||
const equatorHex: HexMeshState = { mesh: equatorMesh, dataCoords: equatorData.dataCoords }
|
||||
|
||||
const snapshotRef = { current: snapshot }
|
||||
buildLeyLines(leyGroup, snapshot.ley_edges)
|
||||
buildWonderMarkers(wonderGeo, snapshot.wonder_positions)
|
||||
|
|
@ -356,19 +390,21 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
renderer.autoClear = true
|
||||
const currentView = gl.current?.activeView ?? 0
|
||||
if (currentView === 0) {
|
||||
renderer.render(fieldScene, camera)
|
||||
// Equator: geometry hex mesh + overlays
|
||||
renderer.render(equatorScene, camera)
|
||||
renderer.autoClear = false
|
||||
renderer.render(overlayScene, camera)
|
||||
} else {
|
||||
// Polar: geodesic hex mesh
|
||||
renderer.render(polarScene, camera)
|
||||
}
|
||||
}
|
||||
tick()
|
||||
|
||||
gl.current = {
|
||||
renderer, camera, fieldScene, overlayScene, polarScene,
|
||||
renderer, camera, fieldScene, overlayScene, polarScene, equatorScene,
|
||||
texA, texB, texC, refTexA, fieldMaterial, fieldMesh,
|
||||
polarNorth, polarSouth,
|
||||
equatorHex, polarNorth, polarSouth,
|
||||
windGeo, windPositions, windVelocities, windPoints,
|
||||
leyGroup, wonderGeo, wonderPoints,
|
||||
animId, startTime, snapshotRef, activeView,
|
||||
|
|
@ -383,6 +419,7 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
refTexA.dispose()
|
||||
fieldGeo.dispose()
|
||||
fieldMaterial.dispose()
|
||||
equatorData.geometry.dispose()
|
||||
polarNorth.mesh.geometry.dispose()
|
||||
polarSouth.mesh.geometry.dispose()
|
||||
polarMat.dispose()
|
||||
|
|
@ -406,11 +443,13 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
}
|
||||
buildLeyLines(g.leyGroup, snapshot.ley_edges)
|
||||
buildWonderMarkers(g.wonderGeo, snapshot.wonder_positions)
|
||||
// Update polar mesh colors from new data
|
||||
// Update all hex mesh colors from new data (equator + both poles use same function)
|
||||
const colorE = g.equatorHex.mesh.geometry.getAttribute('color') as THREE.BufferAttribute
|
||||
updateHexMeshColors(colorE, g.equatorHex.dataCoords, snapshot.texB)
|
||||
const colorN = g.polarNorth.mesh.geometry.getAttribute('color') as THREE.BufferAttribute
|
||||
updatePolarColors(colorN, g.polarNorth.dataCoords, snapshot.texA, snapshot.texB)
|
||||
updateHexMeshColors(colorN, g.polarNorth.dataCoords, snapshot.texB)
|
||||
const colorS = g.polarSouth.mesh.geometry.getAttribute('color') as THREE.BufferAttribute
|
||||
updatePolarColors(colorS, g.polarSouth.dataCoords, snapshot.texA, snapshot.texB)
|
||||
updateHexMeshColors(colorS, g.polarSouth.dataCoords, snapshot.texB)
|
||||
}, [snapshot])
|
||||
|
||||
// ── update reference texture for delta layer ────────────────────────────
|
||||
|
|
@ -457,12 +496,12 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
setCameraView(g, cx, cy, halfW, halfH)
|
||||
}
|
||||
|
||||
/** Fit the camera to the equator map with current zoom */
|
||||
/** Fit the camera to the equator hex mesh with current zoom */
|
||||
const fitEquatorCamera = (g: GLObjects): void => {
|
||||
const w = mapPxWRef.current
|
||||
const h = mapPxHRef.current
|
||||
const halfW = (w / 2) * zoomRef.current
|
||||
const halfH = (h / 2) * zoomRef.current
|
||||
const halfW = (w / 2) / zoomRef.current
|
||||
const halfH = (h / 2) / zoomRef.current
|
||||
setCameraView(g, w / 2, h / 2, halfW, halfH)
|
||||
}
|
||||
|
||||
|
|
@ -511,7 +550,7 @@ export function HexGLRenderer({ snapshot, referenceSnapshot, layerMask, width, h
|
|||
mesh.visible = old.mesh.visible
|
||||
g.polarScene.add(mesh)
|
||||
const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
|
||||
updatePolarColors(colorAttr, dataCoords, g.snapshotRef.current.texA, g.snapshotRef.current.texB)
|
||||
updateHexMeshColors(colorAttr, dataCoords, g.snapshotRef.current.texB)
|
||||
return { mesh, dataCoords, numRings: rings }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue