summaryrefslogtreecommitdiff
path: root/models.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 /models.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'models.ha')
-rw-r--r--models.ha653
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;
+};