diff options
Diffstat (limited to 'models.ha')
-rw-r--r-- | models.ha | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/models.ha b/models.ha new file mode 100644 index 0000000..71a8e05 --- /dev/null +++ b/models.ha @@ -0,0 +1,653 @@ +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; +}; |