block object

This commit is contained in:
2026-07-04 18:07:55 -04:00
parent b5c318de1b
commit 3ba219697e
6 changed files with 136 additions and 70 deletions

View File

@@ -37,8 +37,6 @@ export class Blockstate {
this.variants = json.variants;
this.multipart = json.multipart;
// Memoize evaluated property combinations to prevent console spam
// and speed up Sweep 2 recalculations.
this._resolutionCache = new Map();
}
@@ -95,7 +93,6 @@ export class Blockstate {
}
resolveParts(properties) {
// Create a deterministic cache key from the properties object
const cacheKey = Object.keys(properties).sort().map(k => `${k}=${properties[k]}`).join(',');
if (this._resolutionCache.has(cacheKey)) {

View File

@@ -1,4 +1,4 @@
export class ResourceCache {
export class Cache {
constructor(root) {
this.root = root;
this.handlers = new Map();

View File

@@ -80,12 +80,12 @@ export class Diorama {
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
for (const block of this.world.blocks.values()) {
minX = Math.min(minX, block.x);
minY = Math.min(minY, block.y);
minZ = Math.min(minZ, block.z);
maxX = Math.max(maxX, block.x + 1);
maxY = Math.max(maxY, block.y + 1);
maxZ = Math.max(maxZ, block.z + 1);
minX = Math.min(minX, block.pos[0]);
minY = Math.min(minY, block.pos[1]);
minZ = Math.min(minZ, block.pos[2]);
maxX = Math.max(maxX, block.pos[0] + 1);
maxY = Math.max(maxY, block.pos[1] + 1);
maxZ = Math.max(maxZ, block.pos[2] + 1);
}
this.target = [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2];

View File

@@ -1,6 +1,6 @@
// engine.js
import {mat4Identity, mat4Translate} from './math.js';
import {ResourceCache} from "./resource-cache.js";
import {Cache} from "./cache.js";
import {Blockstate, BlockstateHandler} from "./blockstate.js";
import {ModelHandler} from "./model.js";
import {TextureHandler} from "./texture.js";
@@ -94,7 +94,7 @@ export class Engine {
this.atlasTexture = gl.createTexture();
// --- Initialize the new Resource Pipeline ---
this.cache = new ResourceCache('assets');
this.cache = new Cache('assets');
this.atlas = new TextureHandler(256);
this.cache.register('blockstates', new BlockstateHandler());
this.cache.register('models', new ModelHandler());
@@ -143,7 +143,7 @@ export class Engine {
addDiorama(diorama) {
this.dioramas.push(diorama);
if (!this.observedWorlds.has(diorama.world)) {
if (!this.observedWorlds.has(diorama.worlds)) {
this.observedWorlds.add(diorama.world);
diorama.world.subscribe(() => this.requestUpdate());
}
@@ -168,7 +168,7 @@ export class Engine {
const parsedBlocks = [];
for (const block of world.blocks.values()) {
const state = await this.cache.get(block.id, 'blockstates');
const parts = state.resolveParts(block.props);
const parts = state.resolveParts(block.state);
parsedBlocks.push({block, parts});
for (const p of parts) uniqueModelIds.add(p.model);
}
@@ -190,12 +190,12 @@ export class Engine {
}
const matrix = mat4Identity(new Float32Array(16));
mat4Translate(matrix, matrix, [block.x, block.y, block.z]);
mat4Translate(matrix, matrix, block.pos);
const pool = instancePool.get(hash);
pool.matrices.push(...matrix);
if (block.props.power) {
const p = parseInt(block.props.power, 10);
if (block.state.power) {
const p = parseInt(block.state.power, 10);
pool.colors.push((0x4B + (p * 12)) / 255, 0.0, 0.0);
} else {
pool.colors.push(1.0, 1.0, 1.0);

View File

@@ -25,16 +25,20 @@
<main>
<h1>Visualizer</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper ut mauris sed tempor. Nunc
vehicula tempor purus, non vestibulum libero ornare non. Morbi fringilla sapien diam, sed rhoncus lacus egestas
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ullamcorper ut mauris sed
tempor. Nunc
vehicula tempor purus, non vestibulum libero ornare non. Morbi fringilla sapien diam, sed
rhoncus lacus egestas
vel.
</p>
<div class="figure" id="demo-1"></div>
<p>
Curabitur vestibulum vitae orci ac laoreet. Donec vel imperdiet tortor. Vestibulum lobortis aliquam tellus,
vitae viverra nisi porttitor quis. Pellentesque id efficitur arcu. Nunc laoreet pulvinar ligula eu maximus.
Curabitur vestibulum vitae orci ac laoreet. Donec vel imperdiet tortor. Vestibulum lobortis
aliquam tellus,
vitae viverra nisi porttitor quis. Pellentesque id efficitur arcu. Nunc laoreet pulvinar
ligula eu maximus.
Mauris ullamcorper accumsan dui vel pulvinar.
</p>
@@ -44,11 +48,13 @@
</div>
<p>
Curabitur tincidunt sapien et diam fringilla, eu accumsan odio faucibus. Orci varius natoque penatibus et magnis
Curabitur tincidunt sapien et diam fringilla, eu accumsan odio faucibus. Orci varius natoque
penatibus et magnis
dis parturient montes, nascetur ridiculus mus.
</p>
<div id="atlas-container" style="position: fixed; left: 0; top: 0; border: 1px solid white;"></div>
<div id="atlas-container"
style="position: fixed; left: 0; top: 0; border: 1px solid white;"></div>
</main>
<script type="module">
@@ -62,45 +68,57 @@
document.getElementById('atlas-container')?.appendChild(engine.atlas.canvas);
const world1 = new World();
world1.setBlock('minecraft:redstone_wire', -1, 0, 0, {
world1.set('minecraft:redstone_wire', -1, 0, 0, {
east: 'side',
west: 'none',
north: 'none',
south: 'none',
power: '15'
});
world1.setBlock('minecraft:repeater', 0, 0, 0, {
world1.set('minecraft:repeater', 0, 0, 0, {
facing: 'east',
delay: '1',
locked: 'false',
powered: 'true'
});
world1.setBlock('minecraft:oak_trapdoor', 2, 0, 0, {facing: 'east', half: 'bottom', open: 'true'});
world1.setBlock('lodestone', 0, -1, 0);
world1.setBlock('piston', 0, -2, 0, {facing: 'north', extended: 'false'});
world1.set('minecraft:oak_trapdoor', 2, 0, 0, {
facing: 'east',
half: 'bottom',
open: 'true'
});
world1.set('lodestone', 0, -1, 0);
world1.set('piston', 0, -2, 0, {facing: 'north', extended: 'false'});
const world2 = new World();
world2.setBlock('minecraft:redstone_wire', 0, 0, 0, {
world2.set('minecraft:redstone_wire', 0, 0, 0, {
east: 'side',
west: 'none',
north: 'none',
south: 'none',
power: '0'
});
world2.setBlock('minecraft:repeater', 1, 0, 0, {
world2.set('minecraft:repeater', 1, 0, 0, {
facing: 'east',
delay: '4',
locked: 'false',
powered: 'false'
});
world2.setBlock('minecraft:sticky_piston', 2, 0, 0, {facing: 'east', extended: 'false'});
world2.setBlock('minecraft:sticky_piston', 2, 0, 1, {facing: 'east', extended: 'true'});
world2.setBlock('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'true'});
world2.setBlock('minecraft:piston_head', 2.5, 0, 1, {facing: 'east', short: 'true', type: 'sticky'});
world2.setBlock('minecraft:piston_head', 3, 0, 2, {facing: 'east', short: 'false', type: 'sticky'});
world2.setBlock('minecraft:glass', 3, 0, 0);
world2.setBlock('minecraft:observer', 3.5, 0, 1, {facing: 'up', powered: 'true'});
world2.setBlock('minecraft:redstone_block', 4, 0, 2);
world2.set('minecraft:sticky_piston', 2, 0, 0, {facing: 'east', extended: 'false'});
world2.set('minecraft:sticky_piston', 2, 0, 1, {facing: 'east', extended: 'true'});
world2.set('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'true'});
world2.set('minecraft:piston_head', 2.5, 0, 1, {
facing: 'east',
short: 'true',
type: 'sticky'
});
world2.set('minecraft:piston_head', 3, 0, 2, {
facing: 'east',
short: 'false',
type: 'sticky'
});
world2.set('minecraft:glass', 3, 0, 0);
world2.set('minecraft:observer', 3.5, 0, 1, {facing: 'up', powered: 'true'});
world2.set('minecraft:redstone_block', 4, 0, 2);
const demo1 = new Diorama('demo-1', world1, () => engine.requestRender())
demo1.radius = 2;
@@ -130,34 +148,46 @@
await engine.updateAll();
function set0() {
world2.setBlock('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'false'});
world2.setBlock('minecraft:redstone_block', 3, 0, 2);
world2.removeBlock(2.5, 0, 2);
world2.removeBlock(3.5, 0, 2);
world2.set('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'false'});
world2.set('minecraft:redstone_block', 3, 0, 2);
world2.del(2.5, 0, 2);
world2.del(3.5, 0, 2);
setTimeout(set1, 250)
}
function set1() {
world2.setBlock('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'true'});
world2.removeBlock(3, 0, 2)
world2.setBlock('minecraft:piston_head', 2.5, 0, 2, {facing: 'east', short: 'true', type: 'sticky'});
world2.setBlock('minecraft:redstone_block', 3.5, 0, 2);
world2.set('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'true'});
world2.del(3, 0, 2)
world2.set('minecraft:piston_head', 2.5, 0, 2, {
facing: 'east',
short: 'true',
type: 'sticky'
});
world2.set('minecraft:redstone_block', 3.5, 0, 2);
setTimeout(set2, 250)
}
function set2() {
world2.setBlock('minecraft:piston_head', 3, 0, 2, {facing: 'east', short: 'false', type: 'sticky'});
world2.removeBlock(2.5, 0, 2);
world2.removeBlock(3.5, 0, 2);
world2.setBlock('minecraft:redstone_block', 4, 0, 2);
world2.set('minecraft:piston_head', 3, 0, 2, {
facing: 'east',
short: 'false',
type: 'sticky'
});
world2.del(2.5, 0, 2);
world2.del(3.5, 0, 2);
world2.set('minecraft:redstone_block', 4, 0, 2);
setTimeout(set3, 250)
}
function set3() {
world2.removeBlock(4, 0, 2)
world2.removeBlock(3, 0, 2)
world2.setBlock('minecraft:piston_head', 2.5, 0, 2, {facing: 'east', short: 'true', type: 'sticky'});
world2.setBlock('minecraft:redstone_block', 3.5, 0, 2);
world2.del(4, 0, 2)
world2.del(3, 0, 2)
world2.set('minecraft:piston_head', 2.5, 0, 2, {
facing: 'east',
short: 'true',
type: 'sticky'
});
world2.set('minecraft:redstone_block', 3.5, 0, 2);
setTimeout(set0, 250)
}

View File

@@ -1,42 +1,81 @@
// world.js
export class Block {
/**
* @param {string} id
* @param {[number, number, number]} pos
* @param {Object} state
* @param {Array<World>} world
*/
constructor(id, pos, state, world = []) {
this.id = id;
this.pos = pos;
this.state = state;
this.worlds = world;
}
delete() {
for (let world of this.worlds) {
world.blocks
}
}
}
export class World {
constructor() {
this.blocks = new Map();
this.blocks = new Map();
this.listeners = new Set();
}
// Subscribe to changes (returns an unsubscribe function)
/**
* Subscribe to modify events.
* @param {function()} callback. Called when this world is modified.
* @returns {function()} Unsubscribe function.
*/
subscribe(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
/**
* Notify all listeners of a modification.
*/
notify() {
for (const listener of this.listeners) listener();
}
setBlock(id, x, y, z, props = {}) {
/**
* Set a block and state at a position.
* @param {string} id
* @param {number} x
* @param {number} y
* @param {number} z
* @param {Object} state
*/
set(id, x, y, z, state = {}) {
const key = `${x},${y},${z}`;
this.blocks.set(key, { id, x, y, z, props });
this.blocks.set(key, new Block(id, [x, y, z], state));
this.notify();
}
removeBlock(x, y, z) {
/**
* Remove a block at a position.
* @param {number} x
* @param {number} y
* @param {number} z
*/
del(x, y, z) {
if (this.blocks.delete(`${x},${y},${z}`)) {
this.notify();
}
}
updateBlock(x, y, z, newProps) {
const key = `${x},${y},${z}`;
const block = this.blocks.get(key);
if (block) {
block.props = { ...block.props, ...newProps };
this.notify();
}
}
getBlock(x, y, z) {
/**
* Get a block at a position.
* @param {number} x
* @param {number} y
* @param {number} z
* @returns {any}
*/
get(x, y, z) {
return this.blocks.get(`${x},${y},${z}`);
}
}