From 97ff275467979933ae8c1c8291190d75c5a534bd Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Tue, 4 Aug 2026 13:26:54 -0500 Subject: [PATCH] most entitys working backup --- nonpacks/static/js/geo_builder.js | 6 +- .../static/js/tests/fixtures/wolf.geo.json | 172 ++++++++++++++++++ nonpacks/static/js/tests/geo_builder.test.js | 41 ++++- 3 files changed, 213 insertions(+), 6 deletions(-) create mode 100644 nonpacks/static/js/tests/fixtures/wolf.geo.json diff --git a/nonpacks/static/js/geo_builder.js b/nonpacks/static/js/geo_builder.js index 081bafa..c0029f1 100644 --- a/nonpacks/static/js/geo_builder.js +++ b/nonpacks/static/js/geo_builder.js @@ -185,7 +185,9 @@ var bone = bones[name]; if (!bone) return; var pivot = bone.pivot || [0, 0, 0]; - var rot = (bone.rotation || [0, 0, 0]).map(function (d) { return d * DEG; }); + // Bedrock/GeckoLib rotations are opposite the Three.js default + // direction, so negate the Euler angles (identity models unaffected). + var rot = (bone.rotation || [0, 0, 0]).map(function (d) { return -d * DEG; }); var local = multiply( translation(pivot[0], pivot[1], pivot[2]), multiply( @@ -199,7 +201,7 @@ (bone.cubes || []).forEach(function (c) { var origin = c.origin || [0, 0, 0]; var size = c.size || [1, 1, 1]; - var cRot = (c.rotation || [0, 0, 0]).map(function (d) { return d * DEG; }); + var cRot = (c.rotation || [0, 0, 0]).map(function (d) { return -d * DEG; }); var cubeWorld = multiply( world, multiply( diff --git a/nonpacks/static/js/tests/fixtures/wolf.geo.json b/nonpacks/static/js/tests/fixtures/wolf.geo.json new file mode 100644 index 0000000..b627be1 --- /dev/null +++ b/nonpacks/static/js/tests/fixtures/wolf.geo.json @@ -0,0 +1,172 @@ +{ + "afw_bone_textures": { + "cylinder": "needsofnature:textures/entity/wolf/wolf_features.png", + "bulb": "needsofnature:textures/entity/wolf/wolf_features.png" + }, + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.unknown", + "texture_width": 64, + "texture_height": 32, + "visible_bounds_width": 3, + "visible_bounds_height": 2.5, + "visible_bounds_offset": [0, 0.75, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [0, 0, 0] + }, + { + "name": "wolf", + "parent": "root", + "pivot": [-1, 10.5, -7] + }, + { + "name": "frontbody", + "parent": "wolf", + "pivot": [-1, 10.5, -7] + }, + { + "name": "head", + "parent": "frontbody", + "pivot": [-1, 10.5, -7], + "cubes": [ + {"origin": [-4, 7.5, -9], "size": [6, 6, 4], "uv": [0, 0]}, + {"origin": [-4, 13.5, -7], "size": [2, 2, 1], "uv": [16, 14]}, + {"origin": [0, 13.5, -7], "size": [2, 2, 1], "uv": [16, 14]}, + {"origin": [-2.5, 7.51563, -12], "size": [3, 3, 4], "uv": [0, 10]} + ] + }, + { + "name": "mane", + "parent": "frontbody", + "pivot": [-1, 10, 2], + "rotation": [90, 0, 0], + "cubes": [ + {"origin": [-5, 12, -1], "size": [8, 6, 7], "uv": [21, 0]} + ] + }, + { + "name": "leg3", + "parent": "frontbody", + "pivot": [-2.5, 8, -4], + "cubes": [ + {"origin": [-3.5, 0, -5], "size": [2, 8, 2], "uv": [0, 18]} + ] + }, + { + "name": "leg4", + "parent": "frontbody", + "pivot": [0.5, 8, -4], + "cubes": [ + {"origin": [-0.5, 0, -5], "size": [2, 8, 2], "uv": [0, 18]} + ] + }, + { + "name": "body", + "parent": "frontbody", + "pivot": [0, 10, 2], + "rotation": [90, 0, 0], + "cubes": [ + {"origin": [-4, 3, -1], "size": [6, 9, 6], "uv": [18, 14]} + ] + }, + { + "name": "d", + "parent": "body", + "pivot": [-1, 5, -0.2], + "rotation": [30.36119, -40.78947, -20.94102], + "cubes": [ + {"origin": [-2, 4, -1.2], "size": [2, 3, 2], "uv": [20, 22]} + ] + }, + { + "name": "cylinder", + "parent": "d", + "pivot": [-1.48996, 6.28701, -0.48996], + "cubes": [ + { + "origin": [-1.98996, 6.28701, -1.18996], + "size": [1, 5, 1], + "inflate": -0.1, + "uv": { + "north": {"uv": [52, 9], "uv_size": [3, 15]}, + "east": {"uv": [49, 9], "uv_size": [3, 15]}, + "south": {"uv": [58, 9], "uv_size": [3, 15]}, + "west": {"uv": [55, 9], "uv_size": [3, 15]}, + "up": {"uv": [52, 6], "uv_size": [3, 3]}, + "down": {"uv": [55, 9], "uv_size": [3, -3]} + } + }, + { + "origin": [-2.08996, 10.68701, -1.28996], + "size": [1, 1, 1], + "inflate": -0.3, + "uv": { + "north": {"uv": [54, 4], "uv_size": [2, 2]}, + "east": {"uv": [52, 4], "uv_size": [2, 2]}, + "south": {"uv": [58, 4], "uv_size": [2, 2]}, + "west": {"uv": [56, 4], "uv_size": [2, 2]}, + "up": {"uv": [54, 2], "uv_size": [2, 2]}, + "down": {"uv": [56, 4], "uv_size": [2, -2]} + } + } + ] + }, + { + "name": "bulb", + "parent": "cylinder", + "pivot": [-1.51197, 8.36355, -0.68726], + "rotation": [0, -47.5, 0], + "cubes": [ + { + "origin": [-2.26197, 7.86355, -1.73726], + "size": [1.25, 1, 2.1], + "uv": { + "north": {"uv": [52, 28], "uv_size": [3, 2]}, + "east": {"uv": [48, 28], "uv_size": [4, 2]}, + "south": {"uv": [59, 28], "uv_size": [3, 2]}, + "west": {"uv": [55, 28], "uv_size": [4, 2]}, + "up": {"uv": [52, 24], "uv_size": [3, 4]}, + "down": {"uv": [55, 28], "uv_size": [3, -4]} + } + } + ] + }, + { + "name": "backbody", + "parent": "wolf", + "pivot": [0, 10, 2] + }, + { + "name": "leg1", + "parent": "backbody", + "pivot": [-2.5, 8, 7], + "cubes": [ + {"origin": [-3.5, 0, 6], "size": [2, 8, 2], "uv": [0, 18]} + ] + }, + { + "name": "leg2", + "parent": "backbody", + "pivot": [0.5, 8, 7], + "cubes": [ + {"origin": [-0.5, 0, 6], "size": [2, 8, 2], "uv": [0, 18]} + ] + }, + { + "name": "tail", + "parent": "backbody", + "pivot": [-1, 12, 8], + "rotation": [55, 0, 0], + "cubes": [ + {"origin": [-2, 4, 7], "size": [2, 8, 2], "uv": [9, 18]} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/nonpacks/static/js/tests/geo_builder.test.js b/nonpacks/static/js/tests/geo_builder.test.js index 57e9427..58651ac 100644 --- a/nonpacks/static/js/tests/geo_builder.test.js +++ b/nonpacks/static/js/tests/geo_builder.test.js @@ -83,7 +83,8 @@ assert(allInRange, 'all UVs within [0,1]'); // Unmapped bones default to the pack's main texture. assert(headCube.texture === 'needsofnature:textures/entity/zombie/zombie.png', 'head uses default texture'); -// ---------- 2. Synthetic rotated bone: Rz(90°) around pivot (0,10,0) ---------- +// ---------- 2. Synthetic rotated bone: Rz(-90°) around pivot (0,10,0) ---------- +// Rotations are negated to match the Bedrock/GeckoLib direction. const rotated = { 'minecraft:geometry': [{ description: { identifier: 'geometry.t', texture_width: 64, texture_height: 64 }, @@ -95,10 +96,10 @@ const rotated = { }; const rotBuilt = GeoBuilder.build(rotated); assert(rotBuilt.cubes.length === 1, 'rotated model has one cube'); -// cube center = origin + size/2 = (1,11,1); Rz90 around pivot (0,10,0): -// rel (1,1,1) -> (-1,1,1) -> +pivot = (-1,11,1) +// cube center = origin + size/2 = (1,11,1); Rz(-90) around pivot (0,10,0): +// rel (1,1,1) -> (1,-1,1) -> +pivot = (1,9,1) const rotCenter = worldCenter(rotBuilt.cubes[0].matrix); -assertVec(rotCenter, [-1, 11, 1], 'rotated cube center around pivot (expect (-1,11,1))'); +assertVec(rotCenter, [1, 9, 1], 'rotated cube center around pivot (expect (1,9,1))'); // ---------- 3. Nested pivot with rotation ---------- const nested = { @@ -115,6 +116,38 @@ const nested = { const nestedBuilt = GeoBuilder.build(nested); assertVec(worldCenter(nestedBuilt.cubes[0].matrix), [0, 28, 0], 'nested pivot head center (expect (0,28,0))'); +// ---------- 4. Real wolf model: rotated bones must produce a quadruped layout ---------- +const wolf = require(path.join(__dirname, 'fixtures', 'wolf.geo.json')); +const wolfBuilt = GeoBuilder.build(wolf); +const wolfCenters = wolfBuilt.cubes.map(c => ({ c, center: worldCenter(c.matrix) })); + +const wBody = wolfCenters.find(({ c }) => c.size[0] === 8 && c.size[1] === 6 && c.size[2] === 7); +assert(!!wBody, 'wolf body cube found'); +if (wBody) { + // Body high (y~10.5) and in the front half (z<2). Under the old rotation + // sign the body landed at the back (z=7) — this catches the regression. + assertVec(wBody.center, [-1, 10.5, -3], 'wolf body center (expect (-1,10.5,-3))'); +} + +const wHead = wolfCenters.find(({ c }) => c.size[0] === 6 && c.size[1] === 6 && c.size[2] === 4); +assert(!!wHead, 'wolf head cube found'); +if (wHead) { + assertVec(wHead.center, [-1, 10.5, -7], 'wolf head center (expect (-1,10.5,-7))'); +} + +// 4 legs (size 2x8x2 cubes whose y is near the ground). +const wLegs = wolfCenters.filter(({ c, center }) => c.size[0] === 2 && c.size[1] === 8 && c.size[2] === 2 && center[1] < 6); +assert(wLegs.length === 4, `wolf has 4 legs near the ground (got ${wLegs.length})`); +wLegs.forEach(({ center }) => assert(approx(center[1], 4.0, 0.2), `leg at ground level y=${center[1].toFixed(1)}`)); + +// Tail: the cube with the largest |z|, behind the body. +let wTail = wolfCenters[0]; +for (const w of wolfCenters) { + if (Math.abs(w.center[2]) > Math.abs(wTail.center[2])) wTail = w; +} +assert(wTail.center[2] > 8, `wolf tail behind body (z=${wTail.center[2].toFixed(1)})`); +assert(wBody && wTail.center[2] > wBody.center[2], 'tail z > body z'); + // ---------- summary ---------- if (failures === 0) { console.log(`geo_builder tests OK (${checks} checks)`);