compile blockstates
This commit is contained in:
@@ -24,11 +24,9 @@
|
|||||||
|
|
||||||
<mc-diorama>
|
<mc-diorama>
|
||||||
<script type="text/mc-world">
|
<script type="text/mc-world">
|
||||||
p stone_bricks
|
|
||||||
p stone
|
p stone
|
||||||
p deepslate
|
p stone
|
||||||
p stone_brick_stairs
|
p stone
|
||||||
p stone_brick_wall
|
|
||||||
</script>
|
</script>
|
||||||
</mc-diorama>
|
</mc-diorama>
|
||||||
|
|
||||||
|
|||||||
144
src/minerama.js
144
src/minerama.js
@@ -3,20 +3,39 @@ export class Renderer {
|
|||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.canvas.style.display = 'none';
|
this.canvas.style.display = 'none';
|
||||||
document.body.appendChild(this.canvas);
|
document.body.appendChild(this.canvas);
|
||||||
this.gl = this.canvas.getContext('webgl2', {antialias: false, alpha: true})
|
this.gl = this.canvas.getContext('webgl2', {antialias: false, alpha: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderVariantCondition(key) {
|
function renderVariantCondition(key) {
|
||||||
if (key) {
|
return key.split(',')
|
||||||
return key.split(',').map(
|
.map(it => it.split("="))
|
||||||
it => it.split("=")
|
.map(([n, v]) => `state[${JSON.stringify(n)}] === ${JSON.stringify(v)}`)
|
||||||
).map(
|
.join(" && ");
|
||||||
([n, v]) => `state[${JSON.stringify(n)}] === ${JSON.stringify(v)}`
|
}
|
||||||
).join(" && ")
|
|
||||||
} else {
|
function renderWhenCondition(when) {
|
||||||
return 'true';
|
if (!when) return "true";
|
||||||
|
|
||||||
|
if (when["OR"]) {
|
||||||
|
return when["OR"]
|
||||||
|
.map(it => `(${renderWhenCondition(it)})`)
|
||||||
|
.join(" || ");
|
||||||
}
|
}
|
||||||
|
if (when["AND"]) {
|
||||||
|
return when["AND"]
|
||||||
|
.map(it => `(${renderWhenCondition(it)})`)
|
||||||
|
.join(" && ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.entries(when).length === 0) return 'true';
|
||||||
|
|
||||||
|
return Object.entries(when)
|
||||||
|
.map(([key, vals]) => vals.toString().split('|')
|
||||||
|
.map(val => `state[${JSON.stringify(key)}] === ${JSON.stringify(val)}`)
|
||||||
|
.join(' || '))
|
||||||
|
.map(it => `(${it})`)
|
||||||
|
.join(' && ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderApply(data) {
|
function renderApply(data) {
|
||||||
@@ -34,12 +53,12 @@ function renderApply(data) {
|
|||||||
if (accum === total) {
|
if (accum === total) {
|
||||||
parts.push(JSON.stringify(elem));
|
parts.push(JSON.stringify(elem));
|
||||||
} else {
|
} else {
|
||||||
parts.push(`(hash % ${total}) < ${accum} ? ${JSON.stringify(elem)} : `)
|
parts.push(`h < ${accum} ? ${JSON.stringify(elem)} : `);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parts.join('');
|
return `(h => ${parts.join('')})(Math.abs(hash) % ${total})`;
|
||||||
} else {
|
} else {
|
||||||
return JSON.stringify(data)
|
return JSON.stringify(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,20 +70,66 @@ function renderBlockstateResolver(data) {
|
|||||||
const {variants, multipart} = data;
|
const {variants, multipart} = data;
|
||||||
|
|
||||||
if (variants) {
|
if (variants) {
|
||||||
if (Object.keys(variants).length === 1 && variants[""]) {
|
let cases = [];
|
||||||
return `return ${(renderApply(variants[""]))};`;
|
for (const [key, apply] of Object.entries(variants)) {
|
||||||
} else {
|
if (!key || key === "normal") continue;
|
||||||
let cases = [];
|
cases.push(`if (${(renderVariantCondition(key))}) return [${(renderApply(apply))}];`);
|
||||||
for (const [key, defn] of Object.entries(variants)) {
|
|
||||||
cases.push(`if (${(renderVariantCondition(key))}) return ${(renderApply(defn))};`)
|
|
||||||
}
|
|
||||||
cases.push(`return {"model": ":missing"};`)
|
|
||||||
return cases.join('else ')
|
|
||||||
}
|
}
|
||||||
|
let fallback = variants[""] ?? variants["normal"] ?? {"model": ":invalid"};
|
||||||
|
cases.push(`return [${renderApply(fallback)}]`)
|
||||||
|
return cases.join('else ');
|
||||||
} else if (multipart) {
|
} else if (multipart) {
|
||||||
return 'console.warn("NOT IMPLEMENTED")'
|
const parts = [];
|
||||||
|
parts.push('const parts = [];')
|
||||||
|
for (const {when, apply} of multipart) {
|
||||||
|
parts.push(`if (${renderWhenCondition(when)}) parts.push(${renderApply(apply)});`);
|
||||||
|
}
|
||||||
|
parts.push("return parts;");
|
||||||
|
return parts.join('');
|
||||||
} else {
|
} else {
|
||||||
console.warn('invalid blockstate', data)
|
console.warn('invalid blockstate', data);
|
||||||
|
return 'return [{"model": ":invalid"}]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class BlockstateResolver {
|
||||||
|
constructor() {
|
||||||
|
this.resolvers = new Map();
|
||||||
|
this.resolvers.set(':pending', (state, hash) => [{"model": ":pending"}]);
|
||||||
|
this.resolvers.set(':invalid', (state, hash) => [{"model": ":invalid"}]);
|
||||||
|
|
||||||
|
this.pending = new Map();
|
||||||
|
|
||||||
|
this.onResolve = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResolver(id) {
|
||||||
|
id = id.includes(':') ? id : `minecraft:${id}`;
|
||||||
|
if (this.resolvers.has(id)) {
|
||||||
|
return this.resolvers.get(id);
|
||||||
|
}
|
||||||
|
if (!this.pending.has(id)) {
|
||||||
|
const [space, name] = id.split(':');
|
||||||
|
this.pending.set(id, fetch(`assets/${space}/blockstates/${name}.json`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const source = renderBlockstateResolver(data);
|
||||||
|
const resolver = new Function(`return function (state, hash) { ${source} }`)();
|
||||||
|
this.resolvers.set(id, resolver);
|
||||||
|
this.pending.delete(id);
|
||||||
|
if (this.onResolve) {
|
||||||
|
this.onResolve(id, resolver);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
this.resolvers.set(id, this.resolvers.get(':invalid'));
|
||||||
|
this.pending.delete(id);
|
||||||
|
console.log(`failed to resolve blockstate ${id}: ${reason}`)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.resolvers.get(':pending');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,27 +140,30 @@ function renderBlockstateResolver(data) {
|
|||||||
async function parse(src) {
|
async function parse(src) {
|
||||||
const lines = src.replace(/#.*?(?:\n|$)/, '\n').trim().split(/(?:\n[ \t]*)+/);
|
const lines = src.replace(/#.*?(?:\n|$)/, '\n').trim().split(/(?:\n[ \t]*)+/);
|
||||||
|
|
||||||
for (const line of lines) {
|
let count = 0;
|
||||||
const args = line.split(/[ \t]+/);
|
const res = new BlockstateResolver();
|
||||||
const cmd = args[0].toLowerCase();
|
|
||||||
|
|
||||||
if (cmd === 'p') {
|
function ff() {
|
||||||
const block = args[1];
|
for (const line of lines) {
|
||||||
|
const args = line.split(/[ \t]+/);
|
||||||
|
const cmd = args[0].toLowerCase();
|
||||||
|
|
||||||
try {
|
if (cmd === 'p') {
|
||||||
const res = await fetch(`assets/minecraft/blockstates/${block}.json`);
|
const id = args[1];
|
||||||
let data = await res.json();
|
const hash = count;
|
||||||
let resolver_src = renderBlockstateResolver(data);
|
count += 1;
|
||||||
// console.log(beautify.js(`${block} = (state, hash) => {${resolver_src}}`))
|
|
||||||
|
|
||||||
let resolver = new Function("state", "hash", resolver_src);
|
console.log(id, JSON.stringify(res.getResolver(id)({}, hash)));
|
||||||
console.log(resolver)
|
|
||||||
|
|
||||||
} catch (ex) {
|
|
||||||
console.warn(`Fetch for ${block} failed:`, ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.onResolve = (id, fn) => {
|
||||||
|
console.log('resolved', id, 'to', fn)
|
||||||
|
ff()
|
||||||
|
}
|
||||||
|
|
||||||
|
ff()
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MCDioramaElement extends HTMLElement {
|
export class MCDioramaElement extends HTMLElement {
|
||||||
|
|||||||
Reference in New Issue
Block a user