summaryrefslogtreecommitdiff
path: root/render_bstates.ha
diff options
context:
space:
mode:
authorLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:11:21 +0200
committerLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:51:35 +0200
commitae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch)
tree5f462459ae4b47d22114eed717d1382d08cf4dfe /render_bstates.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'render_bstates.ha')
-rw-r--r--render_bstates.ha559
1 files changed, 559 insertions, 0 deletions
diff --git a/render_bstates.ha b/render_bstates.ha
new file mode 100644
index 0000000..e4bcad2
--- /dev/null
+++ b/render_bstates.ha
@@ -0,0 +1,559 @@
+use dejson;
+use encoding::json;
+use strings;
+use trace;
+
+type BstateRender = struct {
+ parts: []BstateRenderPart,
+};
+
+type BstateRenderPart = struct {
+ model: *Model,
+ flags: BstateRenderPartFlags,
+ swizzle: u8, // X,Y,Z = 0,1,2; axis_ = swizzle >> 2 * axis & 3
+ // bits 0-5: west,east,down,up,north,south
+ swap_uv: u8,
+ flip_u: u8,
+ flip_v: u8,
+};
+
+type BstateRenderPartFlags = enum u8 {
+ FLIP_X = 0x1,
+ FLIP_Y = 0x2,
+ FLIP_Z = 0x4,
+};
+
+let BSTATES_RENDER: []BstateRender = [];
+
+fn load_render_bstates(assets: *Pack) void = {
+ const tr = &trace::root;
+ trace::info(tr, "loading blockstate decls...");
+
+ for (let blk: BlockId = 0; blk < blocks_count(); blk += 1) {
+ const bstates = BLOCKS_BSTATES[blk];
+ const ident = block_getident(blk);
+ const tr = trace::ctx(tr, "load blockstate information for {}",
+ ident);
+
+ const decl = match (resource_load_json(
+ assets, "blockstates", ident, ".json", &tr)) {
+ case let json: json::value =>
+ defer json::finish(json);
+
+ const deser = dejson::newdeser(&json);
+ yield match (deser_bstate_decl(&deser)) {
+ case let x: BstateDecl =>
+ yield x;
+ case let err: dejson::error =>
+ trace::error(&tr, "deser: {}", err): void;
+ yield bstate_decl_missingno();
+ };
+ case trace::failed =>
+ yield bstate_decl_missingno();
+ };
+ defer bstate_decl_finish(decl);
+
+ for (let i = 0z; i < len(decl.cases); i += 1) {
+ const case_ = &decl.cases[i];
+ if (!bstate_decl_cond_validate(&case_.0, blk, &tr)) {
+ bstate_decl_cond_finish(case_.0);
+ bstate_decl_variant_finish(case_.1);
+ delete(decl.cases[i]);
+ i -= 1;
+ };
+ };
+
+ for (let i = 0u32; i < bstates.nstates; i += 1) {
+ append(BSTATES_RENDER, load_bstate_render(
+ &decl, bstates.state0 + i, &tr));
+ };
+ };
+};
+
+fn load_bstate_render(
+ decl: *BstateDecl,
+ bstate: BstateId,
+ tr: *trace::tracer,
+) BstateRender = {
+ let parts: []BstateRenderPart = [];
+
+ for (let i = 0z; i < len(decl.cases); i += 1) {
+ const case_ = &decl.cases[i];
+ const variant = &case_.1;
+
+ if (!bstate_decl_cond_test(&case_.0, bstate))
+ continue;
+
+ if (!decl.multipart && len(parts) != 0) {
+ // TODO: print blockstate properties.
+ trace::error(tr,
+ "Multiple variants match blockstate {}",
+ bstate: u32): void;
+ parts[0] = missingno_bstate_render_part();
+ break;
+ };
+
+ const model = match (models_find(variant.model)) {
+ case let model: *Model =>
+ yield model;
+ case null =>
+ trace::error(tr, "Unknown model {}",
+ variant.model): void;
+ yield models_find(BUILTIN_MISSING) as *Model;
+ };
+
+ let swizzle: [3]u8 = [0, 1, 2]; // X, Y, Z
+ let flip: [3]bool = [false...];
+
+ load_bstate_render_rotate(
+ variant.x, 2, 1, &swizzle, &flip);
+ load_bstate_render_rotate(
+ variant.y, 0, 2, &swizzle, &flip);
+
+ let flags: BstateRenderPartFlags = 0;
+ if (flip[0]) flags |= BstateRenderPartFlags::FLIP_X;
+ if (flip[1]) flags |= BstateRenderPartFlags::FLIP_Y;
+ if (flip[2]) flags |= BstateRenderPartFlags::FLIP_Z;
+
+ let flip_u = 0u8;
+ let flip_v = 0u8;
+ let swap_uv = 0u8;
+ if (variant.uvlock) {
+ // Mojang's uvlock implementation is an incomprehensible
+ // clusterfuck, and I'm not convinced this is any better
+ // in that regard. :/
+ // this is also incompatible when rotated or flipped
+ // textures are used, but those cases never appear in
+ // vanilla resources, and honestly just feel like bugs
+ // on Mojang's part.
+
+ let face_rots: [6]u8 = [0...];
+
+ face_rots[Dir::WEST] += variant.x;
+ face_rots[Dir::EAST] -= variant.x;
+ switch (variant.x) {
+ case 1 =>
+ face_rots[Dir::UP] += 2;
+ face_rots[Dir::NORTH] += 2;
+ case 2 =>
+ face_rots[Dir::SOUTH] += 2;
+ face_rots[Dir::NORTH] += 2;
+ case 3 =>
+ face_rots[Dir::DOWN] += 2;
+ face_rots[Dir::NORTH] += 2;
+ case => void;
+ };
+
+ static const round: [4]Dir =
+ [Dir::DOWN, Dir::NORTH, Dir::UP, Dir::SOUTH];
+ const down = round[variant.x];
+ const up = round[2 + variant.x & 3];
+ face_rots[down] += variant.y;
+ face_rots[up] -= variant.y;
+
+ for (let j = 0u8; j < 6; j += 1) {
+ const mask = 1 << j;
+ switch (face_rots[j] & 3) {
+ case 1 =>
+ swap_uv |= mask;
+ flip_v |= mask;
+ case 2 =>
+ flip_u |= mask;
+ flip_v |= mask;
+ case 3 =>
+ swap_uv |= mask;
+ flip_u |= mask;
+ case => void;
+ };
+ };
+ };
+
+ append(parts, BstateRenderPart {
+ model = model,
+ flags = flags,
+ swizzle = swizzle[0]
+ | swizzle[1] << 2
+ | swizzle[2] << 4,
+ flip_u = flip_u,
+ flip_v = flip_v,
+ swap_uv = swap_uv,
+ });
+ };
+
+ if (!decl.multipart && len(parts) == 0) {
+ // TODO: print blockstate properties.
+ trace::error(tr, "No variants match blockstate {}",
+ bstate: u32): void;
+ append(parts, missingno_bstate_render_part());
+ };
+
+ return BstateRender {
+ parts = parts,
+ };
+};
+
+fn load_bstate_render_rotate(
+ angle: u8,
+ a: u8,
+ b: u8,
+ swizzle: *[3]u8,
+ flip: *[3]bool,
+) void = {
+ if (angle & 1 == 1) {
+ const tmp = swizzle[a];
+ swizzle[a] = swizzle[b];
+ swizzle[b] = tmp;
+ const tmp = flip[a];
+ flip[a] = flip[b];
+ flip[b] = tmp;
+ };
+
+ flip[a] ^^= (angle == 1 || angle == 2);
+ flip[b] ^^= (angle == 3 || angle == 2);
+};
+
+fn missingno_bstate_render_part() BstateRenderPart =
+ BstateRenderPart {
+ model = models_find(BUILTIN_MISSING) as *Model,
+ flags = 0,
+ swizzle = 0b100100,
+ flip_u = 0,
+ flip_v = 0,
+ swap_uv = 0,
+ };
+
+type BstateDecl = struct {
+ multipart: bool,
+ cases: [](BstateDeclCond, BstateDeclVariant),
+};
+
+type BstateDeclCond = ((str, str) | BstateDeclAnd | BstateDeclOr);
+type BstateDeclAnd = []BstateDeclCond;
+type BstateDeclOr = []BstateDeclCond;
+type BstateDeclVariant = struct {
+ model: str,
+ uvlock: bool,
+ // rotations in steps of 90 degrees
+ x: u8,
+ y: u8,
+};
+
+fn bstate_decl_missingno() BstateDecl =
+ BstateDecl {
+ multipart = false,
+ cases = alloc([([]: BstateDeclAnd, BstateDeclVariant {
+ model = strings::dup(BUILTIN_MISSING),
+ ...
+ })]...),
+ };
+
+fn bstate_decl_finish(decl: BstateDecl) void = {
+ for (let i = 0z; i < len(decl.cases); i += 1) {
+ bstate_decl_cond_finish(decl.cases[i].0);
+ bstate_decl_variant_finish(decl.cases[i].1);
+ };
+ free(decl.cases);
+};
+
+fn bstate_decl_cond_finish(cond: BstateDeclCond) void = {
+ const operands = match (cond) {
+ case let ops: BstateDeclAnd =>
+ yield ops: []BstateDeclCond;
+ case let ops: BstateDeclOr =>
+ yield ops: []BstateDeclCond;
+ case let kv: (str, str) =>
+ free(kv.0);
+ free(kv.1);
+ return;
+ };
+ for (let i = 0z; i < len(operands); i += 1) {
+ bstate_decl_cond_finish(operands[i]);
+ };
+ free(operands);
+};
+
+fn bstate_decl_variant_finish(variant: BstateDeclVariant) void = {
+ free(variant.model);
+};
+
+fn bstate_decl_cond_validate(
+ cond: *BstateDeclCond,
+ blk: BlockId,
+ tr: *trace::tracer,
+) bool = {
+ let ok = true;
+
+ match (*cond) {
+ case let conds: BstateDeclAnd =>
+ for (let i = 0z; i < len(conds); i += 1) {
+ if (!bstate_decl_cond_validate(&conds[i], blk, tr)) {
+ ok = false;
+ };
+ };
+ case let conds: BstateDeclOr =>
+ for (let i = 0z; i < len(conds); i += 1) {
+ if (!bstate_decl_cond_validate(&conds[i], blk, tr)) {
+ ok = false;
+ };
+ };
+ case let kv: (str, str) =>
+ match (block_findprop(blk, kv.0)) {
+ case let prop: BlockPropId =>
+ if (block_findpropval(blk, prop, kv.1) is void) {
+ trace::error(tr,
+ "Property '{}' has no value '{}'",
+ kv.0, kv.1): void;
+ ok = false;
+ };
+ case void =>
+ trace::error(tr,
+ "Block has no property '{}'",
+ kv.0): void;
+ ok = false;
+ };
+ };
+
+ return ok;
+};
+
+fn bstate_decl_cond_test(cond: *BstateDeclCond, bstate: BstateId) bool = {
+ match (*cond) {
+ case let conds: BstateDeclAnd =>
+ for (let i = 0z; i < len(conds); i += 1) {
+ if (!bstate_decl_cond_test(&conds[i], bstate)) {
+ return false;
+ };
+ };
+ return true;
+ case let conds: BstateDeclOr =>
+ for (let i = 0z; i < len(conds); i += 1) {
+ if (bstate_decl_cond_test(&conds[i], bstate)) {
+ return true;
+ };
+ };
+ return false;
+ case let kv: (str, str) =>
+ const blk = bstate_getblock(bstate);
+ const prop = block_findprop(blk, kv.0) as BlockPropId;
+ const val = bstate_getprop(bstate, blk, prop);
+ return block_propvalname(blk, prop, val) == kv.1;
+ };
+};
+
+fn deser_bstate_decl(de: *dejson::deser) (BstateDecl | dejson::error) = {
+ wassert_fields(de, "variants", "multipart")?;
+
+ const de_variants = dejson::optfield(de, "variants")?;
+ const de_multipart = dejson::optfield(de, "multipart")?;
+
+ if (de_variants is void && de_multipart is void)
+ return dejson::fail(de,
+ `One of the keys "variants" and "multipart" must be present`);
+
+ if (de_variants is dejson::deser) {
+ if (de_multipart is dejson::deser)
+ return dejson::fail(de,
+ `Can't specify both "variants" and "multipart"`);
+
+ return BstateDecl {
+ multipart = false,
+ cases = deser_bstate_decl_variants(
+ &(de_variants as dejson::deser))?,
+ };
+ } else {
+ return BstateDecl {
+ multipart = true,
+ cases = deser_bstate_decl_multipart(
+ &(de_multipart as dejson::deser))?,
+ };
+ };
+};
+
+fn deser_bstate_decl_variants(de: *dejson::deser)
+ ([](BstateDeclCond, BstateDeclVariant) | dejson::error) = {
+ let success = false;
+
+ let cases: [](BstateDeclCond, BstateDeclVariant) =
+ alloc([], dejson::count(de)?);
+ defer if (!success) free(cases);
+ defer if (!success) for (let i = 0z; i < len(cases); i += 1) {
+ bstate_decl_cond_finish(cases[i].0);
+ bstate_decl_variant_finish(cases[i].1);
+ };
+
+ // TODO: this is reliant on hash table order, so it's probably incorrect
+ let it = dejson::iter(de)?;
+ for (true) match (dejson::next(&it)) {
+ case let entry: (str, dejson::deser) =>
+ let success = false;
+
+ let conds: []BstateDeclCond = [];
+ defer if (!success)
+ bstate_decl_cond_finish(conds: BstateDeclAnd);
+ // TODO: non-standard format: verify/correct details.
+ let tok = strings::tokenize(entry.0, ",");
+ for (true) match (strings::next_token(&tok)) {
+ case let s: str =>
+ const (k, v) = if (strings::contains(s, "=")) {
+ yield strings::cut(s, "=");
+ } else {
+ yield ("", "");
+ };
+ if (len(k) == 0 || len(v) == 0)
+ return dejson::fail(&entry.1,
+ "Invalid key-value pair");
+ append(conds, (strings::dup(k), strings::dup(v)));
+ case done => break;
+ };
+ const cond = conds: BstateDeclAnd;
+
+ const variant = deser_bstate_decl_variant(&entry.1)?;
+
+ success = true;
+ static append(cases, (cond, variant));
+ case void => break;
+ };
+
+ success = true;
+ return cases;
+};
+
+fn deser_bstate_decl_multipart(de: *dejson::deser)
+ ([](BstateDeclCond, BstateDeclVariant) | dejson::error) = {
+ let success = false;
+
+ const ncases = dejson::length(de)?;
+
+ let cases: [](BstateDeclCond, BstateDeclVariant) = alloc([], ncases);
+ defer if (!success) free(cases);
+ defer if (!success) for (let i = 0z; i < len(cases); i += 1) {
+ bstate_decl_cond_finish(cases[i].0);
+ bstate_decl_variant_finish(cases[i].1);
+ };
+
+ for (let i = 0z; i < ncases; i += 1) {
+ let success = false;
+
+ const de_case = dejson::index(de, i)?;
+ wassert_fields(&de_case, "when", "apply")?;
+ const cond: BstateDeclCond = match (
+ dejson::optfield(&de_case, "when")?) {
+ case let de_when: dejson::deser =>
+ yield deser_bstate_decl_cond(&de_when)?;
+ case void =>
+ yield []: BstateDeclAnd;
+ };
+ defer if (!success) bstate_decl_cond_finish(cond);
+
+ const variant = deser_bstate_decl_variant(
+ &dejson::field(&de_case, "apply")?)?;
+
+ success = true;
+ static append(cases, (cond, variant));
+ };
+
+ success = true;
+ return cases;
+};
+
+fn deser_bstate_decl_cond(de: *dejson::deser)
+ (BstateDeclCond | dejson::error) = {
+ let success = false;
+
+ const de_and = dejson::optfield(de, "AND")?;
+ const de_or = dejson::optfield(de, "OR")?;
+
+ if (de_and is dejson::deser || de_or is dejson::deser) {
+ if (dejson::count(de)? != 1)
+ return dejson::fail(de,
+ `No other keys allowed when "AND" or "OR" is present`);
+
+ const de_conds = if (de_and is dejson::deser)
+ de_and: dejson::deser
+ else de_or: dejson::deser;
+ const nconds = dejson::length(&de_conds)?;
+ let conds: []BstateDeclCond = alloc([], nconds);
+ defer if (!success)
+ bstate_decl_cond_finish(conds: BstateDeclAnd);
+ for (let i = 0z; i < nconds; i += 1) {
+ static append(conds, deser_bstate_decl_cond(
+ &dejson::index(&de_conds, i)?)?);
+ };
+ const cond = if (de_and is dejson::deser)
+ conds: BstateDeclAnd
+ else conds: BstateDeclOr;
+
+ success = true;
+ return cond;
+ };
+
+ let conds: []BstateDeclCond = alloc([], dejson::count(de)?);
+ defer if (!success) bstate_decl_cond_finish(conds: BstateDeclAnd);
+ let it = dejson::iter(de)?;
+ for (true) match (dejson::next(&it)) {
+ case let entry: (str, dejson::deser) =>
+ const value = dejson::string(&entry.1)?;
+ // TODO: validate value properly
+ if (strings::contains(value, "|")) {
+ let conds_: []BstateDeclCond = [];
+ let tok = strings::tokenize(value, "|");
+ for (true) match (strings::next_token(&tok)) {
+ case let v: str =>
+ append(conds_, (strings::dup(entry.0),
+ strings::dup(v)));
+ case done => break;
+ };
+ static append(conds, conds_: BstateDeclOr);
+ } else {
+ static append(conds,
+ (strings::dup(entry.0), strings::dup(value)));
+ };
+ case void => break;
+ };
+ const cond = conds: BstateDeclAnd;
+
+ success = true;
+ return cond;
+};
+
+fn deser_bstate_decl_variant(de: *dejson::deser)
+ (BstateDeclVariant | dejson::error) = {
+ const de = match (*de.val) {
+ case json::object =>
+ yield *de;
+ case []json::value =>
+ // TODO: random variants
+ yield dejson::index(de, 0)?;
+ case =>
+ return dejson::fail(de, "Expected object or array, found {}",
+ dejson::typename(de.val));
+ };
+
+ const uvlock = match (dejson::optfield(&de, "uvlock")?) {
+ case let de_uvlock: dejson::deser =>
+ yield dejson::boolean(&de_uvlock)?;
+ case void =>
+ yield false;
+ };
+ const x = match (dejson::optfield(&de, "x")?) {
+ case let de_x: dejson::deser =>
+ yield deser_90deg_angle(&de_x)?;
+ case void =>
+ yield 0u8;
+ };
+ const y = match (dejson::optfield(&de, "y")?) {
+ case let de_y: dejson::deser =>
+ yield deser_90deg_angle(&de_y)?;
+ case void =>
+ yield 0u8;
+ };
+ const model = dejson::string(&dejson::field(&de, "model")?)?;
+ const model = ident_qual(model);
+
+ return BstateDeclVariant {
+ model = model,
+ uvlock = uvlock,
+ x = x,
+ y = y,
+ };
+};