0.2.5-dev: official scenes merge chunks that share a material / lightmap into one mesh at load (draw calls on the 3D board back within budget, e.g. act2autochess_m02 prep 22 → 13); the boss-round real-server test reads the actual random pairing instead of assuming the host on the left half; golden: unchanged
This commit is contained in:
1 parent
e2bc005c88
commit
3bb7fef74c
6 files changed
+232
-17
No files matched your search
@@ -195,6 +195,74 @@ export function parseSceneChunks(doc, buf) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mesh bucket of a scene chunk. One material and one lightmap page are one draw. Water ignores the page: that shader
|
||||
* has no lightmap (`scene.js` `sceneWaterMaterial`).
|
||||
* @param {{ material?: string, lightmap?: number }} chunk
|
||||
* @param {{ kind?: string }|null} info scene.json materials[name]
|
||||
*/
|
||||
export function sceneMaterialKey(chunk, info) {
|
||||
return `${chunk?.material}|${info?.kind === 'water' ? '' : chunk?.lightmap}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* One scene chunk from several that share a material bucket (`sceneMaterialKey`). Vertex attributes stay in the
|
||||
* scene.bin layout (position f32×3, uv f32×2, lightmap uv f32×2, normal i8×4, colour u8×4) and indices are rewritten
|
||||
* with the vertex offset; past 65535 vertices the index is 32-bit (WebGL2). A single chunk is returned as-is, so the
|
||||
* pack's views of scene.bin stay shared. The caller passes chunks of one bucket that are on screen together: a
|
||||
* file-level merge would bake a switched-off HIDDEN part into the base mesh.
|
||||
* @param {object[]|null} chunks
|
||||
* @returns {object|null}
|
||||
*/
|
||||
export function mergeSceneChunks(chunks) {
|
||||
if (!Array.isArray(chunks) || chunks.length === 0) return null;
|
||||
if (chunks.length === 1) return chunks[0];
|
||||
const first = chunks[0];
|
||||
let nv = 0, ni = 0, index32 = false;
|
||||
for (const c of chunks) {
|
||||
const n = c?.position?.length / 3;
|
||||
if (!Number.isInteger(n) || n <= 0) throw new Error(`scene chunk ${c?.material}: ${c?.position?.length ?? 0} position components`);
|
||||
if (c.material !== first.material) throw new Error(`scene merge mixes ${first.material} and ${c.material}`);
|
||||
if (c.uv?.length !== n * 2 || c.uv1?.length !== n * 2 || c.normal?.length !== n * 4 || c.color?.length !== n * 4) {
|
||||
throw new Error(`scene chunk ${c.material}: attributes do not match ${n} vertices`);
|
||||
}
|
||||
if (!c.index || c.index.length === 0) throw new Error(`scene chunk ${c.material}: no indices`);
|
||||
nv += n;
|
||||
ni += c.index.length;
|
||||
if (c.index instanceof Uint32Array) index32 = true;
|
||||
}
|
||||
if (nv > 65535) index32 = true;
|
||||
const position = new Float32Array(nv * 3);
|
||||
const uv = new Float32Array(nv * 2);
|
||||
const uv1 = new Float32Array(nv * 2);
|
||||
const normal = new Int8Array(nv * 4);
|
||||
const color = new Uint8Array(nv * 4);
|
||||
const index = index32 ? new Uint32Array(ni) : new Uint16Array(ni);
|
||||
let vo = 0, io = 0, bounds = null;
|
||||
for (const c of chunks) {
|
||||
const n = c.position.length / 3;
|
||||
position.set(c.position, vo * 3);
|
||||
uv.set(c.uv, vo * 2);
|
||||
uv1.set(c.uv1, vo * 2);
|
||||
normal.set(c.normal, vo * 4);
|
||||
color.set(c.color, vo * 4);
|
||||
for (let i = 0; i < c.index.length; i++) {
|
||||
const v = c.index[i];
|
||||
if (v < 0 || v >= n) throw new Error(`scene chunk ${c.material}: index ${v} out of ${n}`);
|
||||
index[io + i] = v + vo;
|
||||
}
|
||||
vo += n;
|
||||
io += c.index.length;
|
||||
if (Array.isArray(c.bounds) && c.bounds.length >= 6) {
|
||||
const b = c.bounds;
|
||||
bounds = bounds
|
||||
? [Math.min(bounds[0], b[0]), Math.min(bounds[1], b[1]), Math.min(bounds[2], b[2]), Math.max(bounds[3], b[3]), Math.max(bounds[4], b[4]), Math.max(bounds[5], b[5])]
|
||||
: b.slice();
|
||||
}
|
||||
}
|
||||
return { material: first.material, lightmap: first.lightmap, group: '', bounds, position, uv, uv1, normal, color, index };
|
||||
}
|
||||
|
||||
/** Ground rect [x0, y0, x1, y1] (board space) of every named group of chunks (the scene's switchable parts). */
|
||||
export function sceneGroupRects(chunks) {
|
||||
const out = {};
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
// static crates (merged, same material),
|
||||
// decals (MT_autochess_common), fence pipes, blowers (merged, unlit), gate boxes (additive + alpha, merged per
|
||||
// material), cyan field edges, terrain overlays (≤ 4), background plane + its shadow catcher — ~12–16 in total.
|
||||
// An official scene replaces the tiles, glass, decals, pipes and background with one mesh per material and lightmap
|
||||
// page of the parts the built areas show (load.js mergeSceneChunks). Devices, gates, edges and terrain stay.
|
||||
// The key light's shadow map is rendered only when the geometry changes (autoUpdate off).
|
||||
|
||||
import { buildBoard, objToBoard, boxProjectUV, ROWS, COLS, DEVICE_H, AREAS } from './layout.js';
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
waterMaterial, mireMaterial, infectionMaterial, smogMaterial, dashTexture, environmentMap, sceneMaterial, sceneWaterMaterial,
|
||||
} from './materials.js';
|
||||
import { syncThreeCamera } from '../projection.js';
|
||||
import { mergeSceneChunks, sceneMaterialKey } from './load.js';
|
||||
|
||||
/** Lighting rig (tuned against the official screenshots: bright even tops, darker sides, soft shadows). */
|
||||
export const LIGHTING = Object.freeze({
|
||||
@@ -343,18 +346,29 @@ export class BoardScene {
|
||||
this.sceneRes.delete(pack.key);
|
||||
}
|
||||
|
||||
/** The official scene's meshes: one per chunk of a shown part (the base + the fields of the built areas). */
|
||||
/**
|
||||
* The official scene's meshes: the base plus the fields of the built areas, one mesh per material and lightmap
|
||||
* page (`mergeSceneChunks`). A switched-off HIDDEN part is left out before the merge.
|
||||
*/
|
||||
_buildScene(pack) {
|
||||
const T = this.THREE;
|
||||
const res = this._sceneResources(pack);
|
||||
const infos = pack.doc?.materials || {};
|
||||
this.sceneWater = [];
|
||||
let n = 0;
|
||||
const buckets = new Map();
|
||||
for (const c of pack.chunks || []) {
|
||||
if (c.group && !sceneGroupVisible(pack.groups?.[c.group], this.area)) continue;
|
||||
const info = infos[c.material] || null;
|
||||
const mk = sceneMaterialKey(c, info);
|
||||
let bucket = buckets.get(mk);
|
||||
if (!bucket) { bucket = { info, chunks: [] }; buckets.set(mk, bucket); }
|
||||
bucket.chunks.push(c);
|
||||
}
|
||||
let n = 0;
|
||||
for (const [mk, bucket] of buckets) {
|
||||
const c = mergeSceneChunks(bucket.chunks);
|
||||
const info = bucket.info;
|
||||
const water = info?.kind === 'water';
|
||||
const mk = `${c.material}|${water ? '' : c.lightmap}`;
|
||||
let mat = res.mats.get(mk);
|
||||
if (!mat) {
|
||||
mat = water
|
||||
|
||||
Reference in new issue
Block a user