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 {