diff --git a/index.html b/index.html index 74fdc533..afc98889 100644 --- a/index.html +++ b/index.html @@ -24,11 +24,9 @@ diff --git a/src/minerama.js b/src/minerama.js index 4983c58c..768ba8ee 100644 --- a/src/minerama.js +++ b/src/minerama.js @@ -3,20 +3,39 @@ export class Renderer { this.canvas = document.createElement('canvas'); this.canvas.style.display = 'none'; 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) { - if (key) { - return key.split(',').map( - it => it.split("=") - ).map( - ([n, v]) => `state[${JSON.stringify(n)}] === ${JSON.stringify(v)}` - ).join(" && ") - } else { - return 'true'; + return key.split(',') + .map(it => it.split("=")) + .map(([n, v]) => `state[${JSON.stringify(n)}] === ${JSON.stringify(v)}`) + .join(" && "); +} + +function renderWhenCondition(when) { + 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) { @@ -34,12 +53,12 @@ function renderApply(data) { if (accum === total) { parts.push(JSON.stringify(elem)); } 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 { - return JSON.stringify(data) + return JSON.stringify(data); } } @@ -51,20 +70,66 @@ function renderBlockstateResolver(data) { const {variants, multipart} = data; if (variants) { - if (Object.keys(variants).length === 1 && variants[""]) { - return `return ${(renderApply(variants[""]))};`; - } else { - let cases = []; - 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 cases = []; + for (const [key, apply] of Object.entries(variants)) { + if (!key || key === "normal") continue; + cases.push(`if (${(renderVariantCondition(key))}) return [${(renderApply(apply))}];`); } + let fallback = variants[""] ?? variants["normal"] ?? {"model": ":invalid"}; + cases.push(`return [${renderApply(fallback)}]`) + return cases.join('else '); } 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 { - 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) { const lines = src.replace(/#.*?(?:\n|$)/, '\n').trim().split(/(?:\n[ \t]*)+/); - for (const line of lines) { - const args = line.split(/[ \t]+/); - const cmd = args[0].toLowerCase(); + let count = 0; + const res = new BlockstateResolver(); - if (cmd === 'p') { - const block = args[1]; + function ff() { + for (const line of lines) { + const args = line.split(/[ \t]+/); + const cmd = args[0].toLowerCase(); - try { - const res = await fetch(`assets/minecraft/blockstates/${block}.json`); - let data = await res.json(); - let resolver_src = renderBlockstateResolver(data); - // console.log(beautify.js(`${block} = (state, hash) => {${resolver_src}}`)) + if (cmd === 'p') { + const id = args[1]; + const hash = count; + count += 1; - let resolver = new Function("state", "hash", resolver_src); - console.log(resolver) - - } catch (ex) { - console.warn(`Fetch for ${block} failed:`, ex); + console.log(id, JSON.stringify(res.getResolver(id)({}, hash))); } } } + + res.onResolve = (id, fn) => { + console.log('resolved', id, 'to', fn) + ff() + } + + ff() } export class MCDioramaElement extends HTMLElement {