forked from phenom/healer-man
Release Healer Man 0.1.13
This commit is contained in:
1 parent
65e24f644e
commit
67d7a8ebe7
324 files changed
+917
-48
No files matched your search
@@ -11,8 +11,10 @@ const VALUE_TYPES = {
|
||||
byteLength: 4,
|
||||
read: (buffer, offset) => Array.from(buffer.subarray(offset, offset + 4)),
|
||||
},
|
||||
uint32: { byteLength: 4, read: (buffer, offset) => buffer.readUInt32LE(offset) },
|
||||
uint16: { byteLength: 2, read: (buffer, offset) => buffer.readUInt16LE(offset) },
|
||||
int16: { byteLength: 2, read: (buffer, offset) => buffer.readInt16LE(offset) },
|
||||
vector3: { byteLength: 12, read: (buffer, offset) => vector3(buffer, offset) },
|
||||
};
|
||||
|
||||
function asBuffer(value) {
|
||||
@@ -44,6 +46,54 @@ function decodeArray(buffer, descriptorOffset, valueType, label) {
|
||||
};
|
||||
}
|
||||
|
||||
function decodeNestedArray(buffer, descriptorOffset, valueType, label) {
|
||||
const descriptor = readArrayHeader(buffer, descriptorOffset, label);
|
||||
const byteLength = descriptor.count * 8;
|
||||
if (!Number.isSafeInteger(byteLength)) throw new Error(`${label} is too large to decode.`);
|
||||
if (descriptor.count) assertRange(buffer, descriptor.offset, byteLength, `${label} sequence descriptors`);
|
||||
return {
|
||||
...descriptor,
|
||||
sequences: Array.from({ length: descriptor.count }, (_, index) => (
|
||||
decodeArray(buffer, descriptor.offset + index * 8, valueType, `${label} sequence ${index}`)
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeTrack(buffer, offset, valueType, label) {
|
||||
assertRange(buffer, offset, 20, `${label} track`);
|
||||
const interpolation = buffer.readUInt16LE(offset);
|
||||
if (interpolation > 3) throw new Error(`${label} uses unsupported interpolation ${interpolation}.`);
|
||||
const spline = interpolation === 2 || interpolation === 3;
|
||||
const trackValueType = spline ? {
|
||||
byteLength: valueType.byteLength * 3,
|
||||
read: (source, valueOffset) => ({
|
||||
value: valueType.read(source, valueOffset),
|
||||
inTangent: valueType.read(source, valueOffset + valueType.byteLength),
|
||||
outTangent: valueType.read(source, valueOffset + valueType.byteLength * 2),
|
||||
}),
|
||||
} : valueType;
|
||||
const timestamps = decodeNestedArray(buffer, offset + 4, VALUE_TYPES.uint32, `${label} timestamps`);
|
||||
const values = decodeNestedArray(buffer, offset + 12, trackValueType, `${label} values`);
|
||||
if (timestamps.sequences.length !== values.sequences.length) {
|
||||
throw new Error(`${label} timestamp and value sequence counts do not match.`);
|
||||
}
|
||||
const sequences = timestamps.sequences.map((timestampSequence, index) => {
|
||||
const valueSequence = values.sequences[index];
|
||||
if (timestampSequence.values.length !== valueSequence.values.length) {
|
||||
throw new Error(`${label} sequence ${index} timestamp and value counts do not match.`);
|
||||
}
|
||||
return {
|
||||
timestamps: timestampSequence.values,
|
||||
values: valueSequence.values,
|
||||
};
|
||||
});
|
||||
return {
|
||||
interpolation,
|
||||
globalSequence: buffer.readInt16LE(offset + 2),
|
||||
sequences,
|
||||
};
|
||||
}
|
||||
|
||||
function decodeString(buffer, descriptorOffset, label) {
|
||||
const descriptor = readArrayHeader(buffer, descriptorOffset, label);
|
||||
if (!descriptor.count) return "";
|
||||
@@ -113,6 +163,54 @@ function decodeMaterials(buffer, name) {
|
||||
});
|
||||
}
|
||||
|
||||
function decodeAnimations(buffer, name) {
|
||||
const count = buffer.readUInt32LE(28);
|
||||
const offset = buffer.readUInt32LE(32);
|
||||
const recordSize = 64;
|
||||
const byteLength = count * recordSize;
|
||||
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} animations are too large to decode.`);
|
||||
if (count) assertRange(buffer, offset, byteLength, `${name} animation records`);
|
||||
return Array.from({ length: count }, (_, index) => {
|
||||
const animationOffset = offset + index * recordSize;
|
||||
return {
|
||||
index,
|
||||
id: buffer.readUInt16LE(animationOffset),
|
||||
variationIndex: buffer.readUInt16LE(animationOffset + 2),
|
||||
durationMs: buffer.readUInt32LE(animationOffset + 4),
|
||||
moveSpeed: buffer.readFloatLE(animationOffset + 8),
|
||||
flags: buffer.readUInt32LE(animationOffset + 12),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function decodeColors(buffer, name) {
|
||||
const count = buffer.readUInt32LE(72);
|
||||
const offset = buffer.readUInt32LE(76);
|
||||
const recordSize = 40;
|
||||
const byteLength = count * recordSize;
|
||||
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} colors are too large to decode.`);
|
||||
if (count) assertRange(buffer, offset, byteLength, `${name} color records`);
|
||||
return Array.from({ length: count }, (_, index) => {
|
||||
const colorOffset = offset + index * recordSize;
|
||||
return {
|
||||
color: decodeTrack(buffer, colorOffset, VALUE_TYPES.vector3, `${name} color ${index} RGB`),
|
||||
alpha: decodeTrack(buffer, colorOffset + 20, VALUE_TYPES.int16, `${name} color ${index} alpha`),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function decodeTextureWeights(buffer, name) {
|
||||
const count = buffer.readUInt32LE(88);
|
||||
const offset = buffer.readUInt32LE(92);
|
||||
const recordSize = 20;
|
||||
const byteLength = count * recordSize;
|
||||
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} texture weights are too large to decode.`);
|
||||
if (count) assertRange(buffer, offset, byteLength, `${name} texture-weight records`);
|
||||
return Array.from({ length: count }, (_, index) => (
|
||||
decodeTrack(buffer, offset + index * recordSize, VALUE_TYPES.int16, `${name} texture weight ${index}`)
|
||||
));
|
||||
}
|
||||
|
||||
function decodeSkinSubmeshes(buffer, count, offset, triangleCount, vertexLookupCount, name) {
|
||||
const byteLength = count * SKIN_SUBMESH_SIZE;
|
||||
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} submeshes are too large to decode.`);
|
||||
@@ -231,7 +329,11 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
|
||||
}
|
||||
|
||||
const vertices = decodeVertices(m2, name);
|
||||
const globalSequences = decodeArray(m2, 20, VALUE_TYPES.uint32, `${name} global sequences`).values;
|
||||
const animations = decodeAnimations(m2, name);
|
||||
const colors = decodeColors(m2, name);
|
||||
const textures = decodeTextures(m2, name);
|
||||
const textureWeights = decodeTextureWeights(m2, name);
|
||||
const materials = decodeMaterials(m2, name);
|
||||
const lookups = {
|
||||
textures: decodeArray(m2, 128, VALUE_TYPES.uint16, `${name} texture lookup`).values,
|
||||
@@ -274,6 +376,8 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
|
||||
magic: "MD20",
|
||||
version: 264,
|
||||
modelName: decodeString(m2, 8, `${name} model name`),
|
||||
globalSequences,
|
||||
animations,
|
||||
bounds: {
|
||||
minimum: vector3(m2, 160),
|
||||
maximum: vector3(m2, 172),
|
||||
@@ -281,7 +385,9 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
|
||||
},
|
||||
vertices,
|
||||
indices,
|
||||
colors,
|
||||
textures,
|
||||
textureWeights,
|
||||
materials,
|
||||
lookups,
|
||||
skin: {
|
||||
|
||||
Reference in new issue
Block a user