multi-view support
This commit is contained in:
64
engine.js
64
engine.js
@@ -16,6 +16,7 @@ layout(location=9) in vec3 i_color;
|
||||
|
||||
uniform mat4 u_viewProj;
|
||||
uniform vec3 u_lightDir;
|
||||
uniform vec3 u_viewDir;
|
||||
|
||||
out vec2 v_uv;
|
||||
out float v_light;
|
||||
@@ -24,8 +25,13 @@ out vec3 v_color;
|
||||
void main() {
|
||||
gl_Position = u_viewProj * i_matrix * vec4(a_position, 1.0);
|
||||
v_uv = a_uv;
|
||||
|
||||
vec3 normal = normalize(mat3(i_matrix) * a_normal);
|
||||
float baseLight = max(dot(normal, normalize(u_lightDir)), 0.0) * 0.6 + 0.4;
|
||||
|
||||
float sun = max(dot(normal, normalize(u_lightDir)), 0.0);
|
||||
float head = max(dot(normal, normalize(u_viewDir)), 0.0);
|
||||
float baseLight = clamp(sun + head * 0.3, 0.0, 1.0) * 0.6 + 0.4;
|
||||
|
||||
v_light = mix(1.0, baseLight, a_shade);
|
||||
v_color = mix(vec3(1.0), i_color, a_tint);
|
||||
}
|
||||
@@ -80,6 +86,7 @@ export class Engine {
|
||||
this.uniforms = {
|
||||
viewProj: gl.getUniformLocation(this.program, "u_viewProj"),
|
||||
lightDir: gl.getUniformLocation(this.program, "u_lightDir"),
|
||||
viewDir: gl.getUniformLocation(this.program, "u_viewDir"),
|
||||
texture: gl.getUniformLocation(this.program, "u_texture")
|
||||
};
|
||||
|
||||
@@ -87,10 +94,13 @@ export class Engine {
|
||||
this.atlas = new TextureAtlas(256);
|
||||
|
||||
this.dioramas = [];
|
||||
this.dioramaMeshes = new Map(); // Global Mesh Storage: Diorama -> Array of meshes
|
||||
this.worldMeshes = new Map();
|
||||
this.renderRequested = false;
|
||||
|
||||
window.addEventListener('resize', () => { this.resize(); this.requestRender(); });
|
||||
window.addEventListener('resize', () => {
|
||||
this.resize();
|
||||
this.requestRender();
|
||||
});
|
||||
window.addEventListener('scroll', () => this.requestRender(), {passive: true});
|
||||
this.resize();
|
||||
}
|
||||
@@ -112,41 +122,42 @@ export class Engine {
|
||||
|
||||
addDiorama(diorama) {
|
||||
this.dioramas.push(diorama);
|
||||
this.dioramaMeshes.set(diorama, []);
|
||||
}
|
||||
|
||||
// O(1) Cascade: Fetch all resources breadth-first across ALL dioramas
|
||||
async updateAll() {
|
||||
// Extract all unique worlds currently being viewed
|
||||
const uniqueWorlds = new Set(this.dioramas.map(d => d.world));
|
||||
|
||||
// --- SWEEP 1: Collect & Fetch Blockstates ---
|
||||
const uniqueBlockIds = new Set();
|
||||
for (const d of this.dioramas) {
|
||||
for (const block of d.world.blocks.values()) uniqueBlockIds.add(block.id);
|
||||
for (const world of uniqueWorlds) {
|
||||
for (const block of world.blocks.values()) uniqueBlockIds.add(block.id);
|
||||
}
|
||||
await Promise.all(Array.from(uniqueBlockIds).map(id => loadBlockstate(id)));
|
||||
|
||||
// --- SWEEP 2: Resolve Parts & Fetch Models ---
|
||||
const uniqueModelIds = new Set();
|
||||
const parsedDioramas = new Map(); // Diorama -> Array of {block, parts}
|
||||
const parsedWorlds = new Map(); // World -> Array of {block, parts}
|
||||
|
||||
for (const d of this.dioramas) {
|
||||
for (const world of uniqueWorlds) {
|
||||
const parsedBlocks = [];
|
||||
for (const block of d.world.blocks.values()) {
|
||||
const stateJSON = await loadBlockstate(block.id); // Returns instantly from cache
|
||||
for (const block of world.blocks.values()) {
|
||||
const stateJSON = await loadBlockstate(block.id);
|
||||
const parts = resolveBlock(stateJSON, block.props);
|
||||
parsedBlocks.push({block, parts});
|
||||
for (const p of parts) uniqueModelIds.add(p.model);
|
||||
}
|
||||
parsedDioramas.set(d, parsedBlocks);
|
||||
parsedWorlds.set(world, parsedBlocks);
|
||||
}
|
||||
await Promise.all(Array.from(uniqueModelIds).map(id => loadModel(id)));
|
||||
|
||||
// --- SWEEP 3: Pool Instances & Fetch Textures ---
|
||||
const uniqueTexturePaths = new Set();
|
||||
const dioramaPools = new Map(); // Diorama -> instancePool
|
||||
const worldPools = new Map(); // World -> instancePool
|
||||
|
||||
for (const d of this.dioramas) {
|
||||
for (const world of uniqueWorlds) {
|
||||
const instancePool = new Map();
|
||||
for (const {block, parts} of parsedDioramas.get(d)) {
|
||||
for (const {block, parts} of parsedWorlds.get(world)) {
|
||||
for (const part of parts) {
|
||||
const hash = getVariantHash(part);
|
||||
if (!instancePool.has(hash)) {
|
||||
@@ -167,9 +178,8 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Collect required textures from the resolved models
|
||||
for (const pool of instancePool.values()) {
|
||||
const modelJSON = await loadModel(pool.partDef.model); // Instant
|
||||
const modelJSON = await loadModel(pool.partDef.model);
|
||||
for (const el of modelJSON.elements || []) {
|
||||
for (const face of Object.values(el.faces || {})) {
|
||||
const texPath = resolveTexture(modelJSON, face.texture);
|
||||
@@ -177,25 +187,24 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
dioramaPools.set(d, instancePool);
|
||||
worldPools.set(world, instancePool);
|
||||
}
|
||||
|
||||
// Fire all texture image requests concurrently
|
||||
await Promise.all(Array.from(uniqueTexturePaths).map(path => this.atlas.load(path)));
|
||||
this.updateAtlasTexture();
|
||||
|
||||
// --- SWEEP 4: Bake Geometry ---
|
||||
for (const d of this.dioramas) {
|
||||
for (const world of uniqueWorlds) {
|
||||
const newMeshes = [];
|
||||
for (const pool of dioramaPools.get(d).values()) {
|
||||
const modelJSON = await loadModel(pool.partDef.model); // Instant
|
||||
for (const pool of worldPools.get(world).values()) {
|
||||
const modelJSON = await loadModel(pool.partDef.model);
|
||||
const geometry = buildGeometry(modelJSON, this.atlas, pool.partDef);
|
||||
const matrixArray = new Float32Array(pool.matrices);
|
||||
const colorArray = new Float32Array(pool.colors);
|
||||
const instanceCount = pool.matrices.length / 16;
|
||||
newMeshes.push(this.createInstancedMesh(geometry, instanceCount, matrixArray, colorArray));
|
||||
}
|
||||
this.dioramaMeshes.set(d, newMeshes);
|
||||
this.worldMeshes.set(world, newMeshes);
|
||||
}
|
||||
|
||||
this.requestRender();
|
||||
@@ -275,11 +284,14 @@ export class Engine {
|
||||
gl.scissor(rect.left, bottom, rect.width, rect.height);
|
||||
|
||||
const aspect = rect.width / rect.height;
|
||||
const viewProj = diorama.camera.updateMatrices(aspect);
|
||||
const viewProj = diorama.updateMatrices(aspect);
|
||||
gl.uniformMatrix4fv(this.uniforms.viewProj, false, viewProj);
|
||||
gl.uniform3f(this.uniforms.lightDir, 1.0, 2.0, 0.5);
|
||||
gl.uniform3f(this.uniforms.lightDir, 1.0, 3.0, 2);
|
||||
gl.uniform3f(this.uniforms.viewDir,
|
||||
diorama.viewDir[0], diorama.viewDir[1], diorama.viewDir[2]
|
||||
);
|
||||
|
||||
const meshes = this.dioramaMeshes.get(diorama) || [];
|
||||
const meshes = this.worldMeshes.get(diorama.world) || [];
|
||||
for (const mesh of meshes) {
|
||||
gl.bindVertexArray(mesh.vao);
|
||||
gl.drawElementsInstanced(gl.TRIANGLES, mesh.indexCount, gl.UNSIGNED_SHORT, 0, mesh.instanceCount);
|
||||
|
||||
Reference in New Issue
Block a user