working Player-like geometry renderer
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
// Pure geometry math for GeckoLib/Blockbench geo.json models.
|
||||
// No Three.js dependency — shared by model_viewer.js (browser) and unit tests (Node).
|
||||
//
|
||||
// Transforms follow the GeckoLib convention: each bone contributes
|
||||
// local = T(pivot) · R(bone.rotation) · T(−pivot)
|
||||
// and cubes are authored in model space (origin at feet, Y up).
|
||||
// `build()` flattens the bone tree into world-space cubes, avoiding the
|
||||
// nested-pivot double-counting that broke naive group-based renderers.
|
||||
(function (global, factory) {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
global.GeoBuilder = factory();
|
||||
}
|
||||
})(typeof self !== 'undefined' ? self : this, function () {
|
||||
'use strict';
|
||||
|
||||
var DEG = Math.PI / 180;
|
||||
|
||||
// ---------- minimal 4x4 matrix helpers (column-major, Three.js order) ----------
|
||||
function identity() {
|
||||
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
}
|
||||
|
||||
function multiply(a, b) {
|
||||
var out = new Array(16);
|
||||
for (var c = 0; c < 4; c++) {
|
||||
for (var r = 0; r < 4; r++) {
|
||||
out[c * 4 + r] =
|
||||
a[0 * 4 + r] * b[c * 4 + 0] +
|
||||
a[1 * 4 + r] * b[c * 4 + 1] +
|
||||
a[2 * 4 + r] * b[c * 4 + 2] +
|
||||
a[3 * 4 + r] * b[c * 4 + 3];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function translation(x, y, z) {
|
||||
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1];
|
||||
}
|
||||
|
||||
function rotationXYZ(rx, ry, rz) {
|
||||
var cx = Math.cos(rx), sx = Math.sin(rx);
|
||||
var cy = Math.cos(ry), sy = Math.sin(ry);
|
||||
var cz = Math.cos(rz), sz = Math.sin(rz);
|
||||
// R = Rz * Ry * Rx (matches Three.js Euler 'XYZ' default order)
|
||||
var r = identity();
|
||||
r[0] = cy * cz;
|
||||
r[1] = cy * sz;
|
||||
r[2] = -sy;
|
||||
r[4] = sx * sy * cz - cx * sz;
|
||||
r[5] = sx * sy * sz + cx * cz;
|
||||
r[6] = sx * cy;
|
||||
r[8] = cx * sy * cz + sx * sz;
|
||||
r[9] = cx * sy * sz - sx * cz;
|
||||
r[10] = cx * cy;
|
||||
return r;
|
||||
}
|
||||
|
||||
function transformPoint(m, p) {
|
||||
var x = p[0], y = p[1], z = p[2];
|
||||
var w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
||||
return [
|
||||
(m[0] * x + m[4] * y + m[8] * z + m[12]) / w,
|
||||
(m[1] * x + m[5] * y + m[9] * z + m[13]) / w,
|
||||
(m[2] * x + m[6] * y + m[10] * z + m[14]) / w,
|
||||
];
|
||||
}
|
||||
|
||||
// ---------- box UV ----------
|
||||
// Minecraft "box UV" face rects (u, v, w, h) in texture pixels.
|
||||
function boxUVRects(uv, size) {
|
||||
var u = uv[0], v = uv[1];
|
||||
var x = size[0], y = size[1], z = size[2];
|
||||
return {
|
||||
top: [u + z, v, x, z],
|
||||
bottom: [u + z + x, v, x, z],
|
||||
north: [u + z, v + z, x, y],
|
||||
south: [u + z + x, v + z, x, y],
|
||||
east: [u, v + z, z, y],
|
||||
west: [u + z + x + z, v + z, z, y],
|
||||
};
|
||||
}
|
||||
|
||||
// Build one cube as a BufferGeometry-compatible mesh:
|
||||
// 24 positions (6 faces x 4 corners), 24 UVs, 36 indices, outward winding.
|
||||
function cubeGeometry(size, uv, tw, th) {
|
||||
var hx = size[0] / 2, hy = size[1] / 2, hz = size[2] / 2;
|
||||
var rects = boxUVRects(uv, size);
|
||||
var order = ['east', 'west', 'top', 'bottom', 'south', 'north'];
|
||||
var outward = {
|
||||
east: [1, 0, 0], west: [-1, 0, 0],
|
||||
top: [0, 1, 0], bottom: [0, -1, 0],
|
||||
south: [0, 0, 1], north: [0, 0, -1],
|
||||
};
|
||||
|
||||
var positions = [];
|
||||
var uvs = [];
|
||||
var indices = [];
|
||||
var v = 0;
|
||||
|
||||
function pushFace(face, corners) {
|
||||
var a = corners[0], b = corners[1], c = corners[2];
|
||||
var abx = b[0] - a[0], aby = b[1] - a[1], abz = b[2] - a[2];
|
||||
var acx = c[0] - a[0], acy = c[1] - a[1], acz = c[2] - a[2];
|
||||
var nx = aby * acz - abz * acy;
|
||||
var ny = abz * acx - abx * acz;
|
||||
var nz = abx * acy - aby * acx;
|
||||
var out = outward[face];
|
||||
if (nx * out[0] + ny * out[1] + nz * out[2] < 0) {
|
||||
var tmp = corners[1];
|
||||
corners[1] = corners[2];
|
||||
corners[2] = tmp;
|
||||
}
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var cor = corners[i];
|
||||
positions.push(cor[0], cor[1], cor[2]);
|
||||
uvs.push(cor[3], cor[4]);
|
||||
}
|
||||
indices.push(v, v + 1, v + 2, v, v + 2, v + 3);
|
||||
v += 4;
|
||||
}
|
||||
|
||||
for (var i = 0; i < order.length; i++) {
|
||||
var face = order[i];
|
||||
var rect = rects[face];
|
||||
var u0 = rect[0] / tw, u1 = (rect[0] + rect[2]) / tw;
|
||||
var vTop = 1 - (rect[1] + rect[3]) / th;
|
||||
var vBot = 1 - rect[1] / th;
|
||||
var cs;
|
||||
if (face === 'east') {
|
||||
cs = [
|
||||
[hx, hy, -hz, u1, vTop], [hx, hy, hz, u0, vTop],
|
||||
[hx, -hy, hz, u0, vBot], [hx, -hy, -hz, u1, vBot],
|
||||
];
|
||||
} else if (face === 'west') {
|
||||
cs = [
|
||||
[-hx, hy, hz, u1, vTop], [-hx, hy, -hz, u0, vTop],
|
||||
[-hx, -hy, -hz, u0, vBot], [-hx, -hy, hz, u1, vBot],
|
||||
];
|
||||
} else if (face === 'top') {
|
||||
cs = [
|
||||
[-hx, hy, hz, u0, vTop], [hx, hy, hz, u1, vTop],
|
||||
[hx, hy, -hz, u1, vBot], [-hx, hy, -hz, u0, vBot],
|
||||
];
|
||||
} else if (face === 'bottom') {
|
||||
cs = [
|
||||
[-hx, -hy, -hz, u0, vTop], [hx, -hy, -hz, u1, vTop],
|
||||
[hx, -hy, hz, u1, vBot], [-hx, -hy, hz, u0, vBot],
|
||||
];
|
||||
} else if (face === 'south') {
|
||||
cs = [
|
||||
[hx, hy, hz, u0, vTop], [-hx, hy, hz, u1, vTop],
|
||||
[-hx, -hy, hz, u1, vBot], [hx, -hy, hz, u0, vBot],
|
||||
];
|
||||
} else { // north
|
||||
cs = [
|
||||
[-hx, hy, -hz, u0, vTop], [hx, hy, -hz, u1, vTop],
|
||||
[hx, -hy, -hz, u1, vBot], [-hx, -hy, -hz, u0, vBot],
|
||||
];
|
||||
}
|
||||
pushFace(face, cs);
|
||||
}
|
||||
|
||||
return { positions: positions, uvs: uvs, indices: indices };
|
||||
}
|
||||
|
||||
// ---------- model build ----------
|
||||
// Returns { cubes: [{matrix, size, uv, texture}], texture_width, texture_height }
|
||||
function build(geo) {
|
||||
var geometry = ((geo['minecraft:geometry'] || [])[0]) || null;
|
||||
if (!geometry) return { cubes: [], texture_width: 64, texture_height: 64 };
|
||||
var tw = (geometry.description && geometry.description.texture_width) || 64;
|
||||
var th = (geometry.description && geometry.description.texture_height) || 64;
|
||||
var textures = geo['afw_bone_textures'] || {};
|
||||
var defaultTex = null;
|
||||
for (var k in textures) { defaultTex = textures[k]; break; }
|
||||
|
||||
var bones = {};
|
||||
(geometry.bones || []).forEach(function (b) { bones[b.name] = b; });
|
||||
|
||||
var cubes = [];
|
||||
function walk(name, parentMatrix) {
|
||||
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; });
|
||||
var local = multiply(
|
||||
translation(pivot[0], pivot[1], pivot[2]),
|
||||
multiply(
|
||||
rotationXYZ(rot[0], rot[1], rot[2]),
|
||||
translation(-pivot[0], -pivot[1], -pivot[2])
|
||||
)
|
||||
);
|
||||
var world = multiply(parentMatrix, local);
|
||||
var tex = textures[name] || defaultTex;
|
||||
|
||||
(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 cubeWorld = multiply(
|
||||
world,
|
||||
multiply(
|
||||
translation(origin[0], origin[1], origin[2]),
|
||||
multiply(
|
||||
rotationXYZ(cRot[0], cRot[1], cRot[2]),
|
||||
translation(size[0] / 2, size[1] / 2, size[2] / 2)
|
||||
)
|
||||
)
|
||||
);
|
||||
cubes.push({
|
||||
matrix: cubeWorld,
|
||||
size: size,
|
||||
uv: c.uv || [0, 0],
|
||||
texture: tex,
|
||||
});
|
||||
});
|
||||
|
||||
for (var child in bones) {
|
||||
if (bones[child].parent === name) walk(child, world);
|
||||
}
|
||||
}
|
||||
|
||||
for (var root in bones) {
|
||||
if (!bones[root].parent) walk(root, identity());
|
||||
}
|
||||
|
||||
return { cubes: cubes, texture_width: tw, texture_height: th };
|
||||
}
|
||||
|
||||
return {
|
||||
build: build,
|
||||
cubeGeometry: cubeGeometry,
|
||||
boxUVRects: boxUVRects,
|
||||
transformPoint: transformPoint,
|
||||
identity: identity,
|
||||
translation: translation,
|
||||
rotationXYZ: rotationXYZ,
|
||||
multiply: multiply,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user