diff options
author | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:11:21 +0200 |
---|---|---|
committer | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:51:35 +0200 |
commit | ae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch) | |
tree | 5f462459ae4b47d22114eed717d1382d08cf4dfe /render_bstates.ha |
Diffstat (limited to 'render_bstates.ha')
-rw-r--r-- | render_bstates.ha | 559 |
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, + }; +}; |