use dejson; use encoding::json; use glm; use math; use strings; use trace; type Model = struct { Object, decl: nullable *ModelDecl, parent: nullable *Model, geom: nullable *ModelGeom, owns_geom: bool, textures: []*Sprite, }; type ModelGeom = struct { textures: []str, faces: []Face, }; type Face = struct { dir: Dir, // used for uvlock; probably a temporary hack. cull_face: CullFace, texture: u8, normal: [3]i8, texcoord: [4]u8, rotation: u8, vertices: [4][3]u16, }; type CullFace = enum u8 { WEST, EAST, DOWN, UP, NORTH, SOUTH, NONE, }; let MODELS = OBJREG_EMPTY; fn models_find(ident: str) nullable *Model = objreg_find(&MODELS, ident): nullable *Model; type ModelsIter = struct { inner: ObjectRegistryIterator, }; fn models_iter() ModelsIter = ModelsIter { inner = objreg_iter(&MODELS) }; fn models_next(it: *ModelsIter) nullable *Model = objreg_next(&it.inner): nullable *Model; fn load_models(assets: *Pack) void = { const tr = &trace::root; trace::info(tr, "loading models..."); objreg_register(&MODELS, alloc(Model { name = strings::dup(BUILTIN_MISSING), ... })); objreg_register(&MODELS, alloc(Model { name = strings::dup("minecraft:builtin/entity"), ... })); objreg_register(&MODELS, alloc(Model { name = strings::dup("minecraft:builtin/generated"), ... })); const results = resource_search(assets, "models", ".json"); for (let i = 0z; i < len(results); i+= 1) { const (ident, ext) = results[i]; const tr = trace::ctx(tr, "load model {}", ident); const model = alloc(Model { name = strings::dup(ident), ... }); objreg_register(&MODELS, model); const json = match (resource_load_json( assets, "models", ident, ".json", &tr)) { case let json: json::value => yield json; case trace::failed => continue; }; defer json::finish(json); const decl = { const deser = dejson::newdeser(&json); yield match (deser_model_decl(&deser)) { case let x: ModelDecl => yield x; case let err: dejson::error => defer free(err); trace::error(&tr, "deser: {}", ident, err): void; continue; }; }; model.decl = alloc(decl); }; let it = models_iter(); for (true) match (models_next(&it)) { case let model: *Model => if (model.decl == null) model.decl = alloc(missingno_model_decl()); case null => break; }; let it = models_iter(); for (true) match (models_next(&it)) { case let model: *Model => const tr = trace::ctx(tr, "load model {}", model.name); const decl = model.decl as *ModelDecl; match (decl.parent) { case let parent: str => model.parent = models_find(parent); if (model.parent == null) { trace::error(&tr, "Unknown parent {}", parent): void; model.parent = models_find(BUILTIN_MISSING) as *Model; }; case void => void; }; const elements = match (decl.elements) { case let elements: []ModelDeclElement => yield elements; case void => void; if (model.parent == null) yield []: []ModelDeclElement; yield void; // TODO: harec bug? }; match (elements) { case let elements: []ModelDeclElement => model.geom = alloc(build_model_geom(elements)); model.owns_geom = true; case void => void; }; case null => break; }; let it = models_iter(); for (true) match (models_next(&it)) { case let model: *Model => const tr = trace::ctx(tr, "load model {}", model.name); if (model.geom == null) { let i = model.parent; for (true) match (i) { case let model_: *Model => i = model_.parent; match (model_.geom) { case let geom: *ModelGeom => model.geom = geom; break; case null => void; }; case null => abort(); }; }; const geom = model.geom as *ModelGeom; model.textures = alloc([], len(geom.textures)); for (let i = 0z; i < len(geom.textures); i += 1) { const spr = model_resolve_texture_name( model, geom.textures[i], &tr); const spr = match (spr) { case let spr: *Sprite => yield spr; case trace::failed => yield atlas_findsprite(&ATLAS_BLOCKS, MISSINGNO) as *Sprite; }; static append(model.textures, spr); }; case null => break; }; }; fn build_model_geom(elements: []ModelDeclElement) ModelGeom = { // west, east, down, up, north, south // (naxis, uaxis, vaxis, vertices) // vertices = [top_left, bottom_left, bottom_right, top_right] // vertices[_] = 0bZYX where 0: from, 1: to static const face_info: [6](u8, u8, u8, [4]u8) = [ (0, 2, 1, [0b010, 0b000, 0b100, 0b110]), (0, 2, 1, [0b111, 0b101, 0b001, 0b011]), (1, 0, 2, [0b100, 0b000, 0b001, 0b101]), (1, 0, 2, [0b010, 0b110, 0b111, 0b011]), (2, 0, 1, [0b011, 0b001, 0b000, 0b010]), (2, 0, 1, [0b110, 0b100, 0b101, 0b111]), ]; let textures: []str = []; let faces: []Face = []; for (let i = 0z; i < len(elements); i += 1) { const element = &elements[i]; let trans = glm::m4_new_ident(); let (rot_axis, rescale_axes): ([3]f32, [3]f32) = switch (element.rot_axis) { case Axis::X => yield ([1.0, 0.0, 0.0], [0.0, 1.0, 1.0]); case Axis::Y => yield ([0.0, 1.0, 0.0], [1.0, 0.0, 1.0]); case Axis::Z => yield ([0.0, 0.0, 1.0], [1.0, 1.0, 0.0]); }; glm::translate(&trans, &({ let v = element.rot_origin; glm::v3_negate(&v); yield v; })); const angle = glm::rad(element.rot_angle); glm::rotate(&trans, angle, &rot_axis); if (element.rot_rescale) { const sin = math::sinf64(angle): f32; const cos = math::cosf64(angle): f32; const rescale = 1.0 / (if (cos > sin) cos else sin) - 1.0; glm::v3_scale(&rescale_axes, rescale); rescale_axes = glm::v3_add(&rescale_axes, &glm::v3_new(1.0, 1.0, 1.0)); glm::scale(&trans, &rescale_axes); }; glm::translate(&trans, &element.rot_origin); const from = element.from; const to = element.to; const from_to = [from, to]; let vertices: [8][3]u16 = [[0...]...]; for (let j = 0z; j < 8; j += 1) { let v: [3]f32 = [from_to[j & 1][0], from_to[j >> 1 & 1][1], from_to[j >> 2 & 1][2]]; v = glm::affine_mul_v3(&trans, &v); for (let k = 0z; k < 3; k += 1) vertices[j][k] = ((v[k] + 16.0) * 128.0): u16; }; for (let j = 0z; j < 6; j += 1) { const finfo = &face_info[j]; const naxis = finfo.0; const uaxis = finfo.1; const vaxis = finfo.2; const faceverts = &finfo.3; const face = match (element.faces[j]) { case let x: ModelDeclFace => yield x; case void => continue; }; let texnum = 0u8; for (texnum < len(textures); texnum += 1) { if (textures[texnum] == face.texture) { break; }; }; if (texnum == len(textures)) { append(textures, face.texture); }; let normal: [3]f32 = [0.0...]; normal[naxis] = if (faceverts[0] & 1 << naxis != 0) 127.0 else -127.0; normal = glm::affine_mul_v3(&trans, &normal); const intnormal = [normal[0]: i8, normal[1]: i8, normal[2]: i8]; const uv: [4]f32 = match (face.uv) { case let uv: [4]f32 => yield uv; case void => const u = if (faceverts[0] & 1 << uaxis == 0) [from[uaxis], to[uaxis]] else [16.0 - to[uaxis], 16.0 - from[uaxis]]; const v = if (faceverts[0] & 1 << vaxis == 0) [from[vaxis], to[vaxis]] else [16.0 - to[vaxis], 16.0 - from[vaxis]]; yield [u[0], v[0], u[1], v[1]]; }; let intuv: [4]u8 = [0...]; for (let k = 0z; k < 4; k += 1) { const a = uv[k]; if (a < 0.0) a = 0.0; if (a > 16.0) a = 16.0; intuv[k] = (a * 8.0): u8; }; append(faces, Face { dir = j: Dir, cull_face = face.cull_face, texture = texnum, normal = intnormal, texcoord = intuv, rotation = face.rotation, vertices = [ vertices[faceverts[0]], vertices[faceverts[1]], vertices[faceverts[2]], vertices[faceverts[3]], ], }); }; }; return ModelGeom { textures = textures, faces = faces, }; }; fn model_resolve_texture_name(model: *Model, name: str, tr: *trace::tracer) (*Sprite | trace::failed) = { let names: []str = []; defer free(names); let model_ = model; for :resolve (true) { if (!strings::hasprefix(name, '#')) break; const var = strings::trimprefix(name, "#"); const decl = model_.decl as *ModelDecl; for (let j = 0z; j < len(decl.textures); j += 1) { if (decl.textures[j].0 != var) { continue; }; name = decl.textures[j].1; for (let k = 0z; k < len(names); k += 1) { if (names[k] == name) { break :resolve; }; }; append(names, name); model_ = model; continue :resolve; }; match (model_.parent) { case let parent: *Model => model_ = parent; case null => break; }; }; if (strings::hasprefix(name, "#")) { return trace::error(tr, "Failed to resolve texture name {}", name); }; const texident = ident_qual(name); defer free(texident); match (atlas_findsprite(&ATLAS_BLOCKS, texident)) { case let spr: *Sprite => return spr; case null => return trace::error(tr, "Unknown texture {}", texident); }; }; type ModelDecl = struct { parent: (str | void), textures: [](str, str), elements: ([]ModelDeclElement | void), }; type ModelDeclElement = struct { from: [3]f32, to: [3]f32, rot_origin: [3]f32, rot_axis: Axis, rot_angle: f32, rot_rescale: bool, faces: [6](ModelDeclFace | void), // west, east, down, up, north, south }; type Axis = enum u8 { X, Y, Z, }; type ModelDeclFace = struct { uv: ([4]f32 | void), rotation: u8, // [0,4) 90° steps texture: str, cull_face: CullFace, }; fn model_decl_finish(decl: ModelDecl) void = { match (decl.parent) { case let parent: str => free(parent); case void => void; }; for (let i = 0z; i < len(decl.textures); i += 1) { free(decl.textures[i].0); free(decl.textures[i].1); }; match (decl.elements) { case let elements: []ModelDeclElement => for (let i = 0z; i < len(elements); i += 1) { model_decl_element_finish(elements[i]); }; case void => void; }; }; fn model_decl_element_finish(element: ModelDeclElement) void = { for (let i = 0z; i < 6; i += 1) { match (element.faces[i]) { case let face: ModelDeclFace => free(face.texture); case void => void; }; }; }; fn missingno_model_decl() ModelDecl = { let faces: [6](ModelDeclFace | void) = [void...]; for (let i = 0u8; i < 6; i += 1) { faces[i] = ModelDeclFace { uv = void, rotation = 0, texture = strings::dup(MISSINGNO), cull_face = i: CullFace, }; }; return ModelDecl { parent = void, textures = [], elements = alloc([ModelDeclElement { from = [0.0...], to = [16.0...], rot_origin = [0.0...], rot_axis = Axis::X, rot_angle = 0.0, rot_rescale = false, faces = faces, }]), }; }; fn deser_model_decl(de: *dejson::deser) (ModelDecl | dejson::error) = { let success = false; wassert_fields(de, "parent", "textures", "elements")?; let res = ModelDecl { parent = void, elements = void, ... }; defer if (!success) model_decl_finish(res); match (dejson::optfield(de, "parent")?) { case let de_parent: dejson::deser => res.parent = ident_qual(dejson::string(&de_parent)?); case void => void; }; match (dejson::optfield(de, "textures")?) { case let de_textures: dejson::deser => res.textures = alloc([], dejson::count(&de_textures)?); let it = dejson::iter(&de_textures)?; for (true) match (dejson::next(&it)) { case let entry: (str, dejson::deser) => const var = strings::dup(entry.0); const tex = strings::dup(dejson::string(&entry.1)?); append(res.textures, (var, tex)); case void => break; }; case void => void; }; match (dejson::optfield(de, "elements")?) { case let de_elements: dejson::deser => const nelements = dejson::length(&de_elements)?; res.elements = alloc([], nelements); for (let i = 0z; i < nelements; i += 1) { // XXX: waiting for tagged union overhaul... let elements = res.elements: []ModelDeclElement; append(elements, deser_model_decl_element( &dejson::index(&de_elements, i)?)?); res.elements = elements; }; case void => void; }; success = true; return res; }; fn deser_model_decl_element(de: *dejson::deser) (ModelDeclElement | dejson::error) = { wassert_fields(de, "from", "to", "faces", "rotation")?; static const facenames = ["west", "east", "down", "up", "north", "south"]; const de_faces = dejson::field(de, "faces")?; wassert_fields(&de_faces, facenames...)?; const faces: [6](ModelDeclFace | void) = [void...]; for (let j = 0z; j < 6; j += 1) { match (dejson::optfield(&de_faces, facenames[j])?) { case let de_face: dejson::deser => faces[j] = deser_model_decl_face(&de_face)?; case void => void; }; }; let res = ModelDeclElement { from = deser_model_decl_pos( &dejson::field(de, "from")?)?, to = deser_model_decl_pos( &dejson::field(de, "to")?)?, rot_origin = [0.0...], rot_axis = Axis::X, rot_angle = 0.0, rot_rescale = false, faces = faces, }; match (dejson::optfield(de, "rotation")?) { case let de_rotation: dejson::deser => wassert_fields(&de_rotation, "origin", "axis", "angle", "rescale")?; res.rot_origin = deser_model_decl_pos( &dejson::field(&de_rotation, "origin")?)?; const de_rot_axis = dejson::field(&de_rotation, "axis")?; const rot_axis = dejson::string(&de_rot_axis)?; res.rot_axis = switch (rot_axis) { case "x" => yield Axis::X; case "y" => yield Axis::Y; case "z" => yield Axis::Z; case => const s = dejson::strfield(rot_axis); defer free(s); return dejson::fail(&de_rot_axis, "Invalid axis {}", s); }; res.rot_angle = dejson::number( &dejson::field(&de_rotation, "angle")?)?: f32; match (dejson::optfield(&de_rotation, "rescale")) { case let de_rescale: dejson::deser => res.rot_rescale = dejson::boolean(&de_rescale)?; case void => void; }; case void => void; }; return res; }; fn deser_model_decl_pos(de: *dejson::deser) ([3]f32 | dejson::error) = { dejson::assert_length(de, 3)?; // TODO: check valid range? return [ dejson::number(&dejson::index(de, 0)?)?: f32, dejson::number(&dejson::index(de, 1)?)?: f32, dejson::number(&dejson::index(de, 2)?)?: f32, ]; }; fn deser_model_decl_face(de: *dejson::deser) (ModelDeclFace | dejson::error) = { wassert_fields(de, "uv", "rotation", "cullface", "texture")?; const uv = match (dejson::optfield(de, "uv")?) { case let de_uv: dejson::deser => dejson::assert_length(&de_uv, 4)?; let uv: [4]f32 = [0.0...]; for (let i = 0z; i < 4; i += 1) uv[i] = dejson::number_frange( &dejson::index(&de_uv, i)?, 0.0, 16.0)?: f32; yield uv; case void => void; }; const rotation = match (dejson::optfield(de, "rotation")?) { case let de_rotation: dejson::deser => yield deser_90deg_angle(&de_rotation)?; case void => yield 0u8; }; const cull_face = match (dejson::optfield(de, "cullface")?) { case let de_cull_face: dejson::deser => const s = dejson::string(&de_cull_face)?; yield switch (s) { case "west" => yield CullFace::WEST; case "east" => yield CullFace::EAST; case "down" => yield CullFace::DOWN; case "up" => yield CullFace::UP; case "north" => yield CullFace::NORTH; case "south" => yield CullFace::SOUTH; case => const s = dejson::strfield(s); defer free(s); trace::warn(&trace::root, "Invalid face {}", s); yield CullFace::NONE; }; case void => yield CullFace::NONE; }; const texture = strings::dup(dejson::string( &dejson::field(de, "texture")?)?); return ModelDeclFace { uv = uv, rotation = rotation, texture = texture, cull_face = cull_face, }; }; fn deser_90deg_angle(de: *dejson::deser) (u8 | dejson::error) = { const v = dejson::number_i64(de)?; if (v % 90 != 0) return dejson::fail(de, "Angle {} is not aligned to 90° increments", v); return (v / 90 & 3): u8; };