arx-convert
Version:
Converts various Arx Fatalis formats to JSON or YAML and back
212 lines • 8.81 kB
JavaScript
import { BinaryIO } from '../common/BinaryIO.js';
import { repeat, times } from '../common/helpers.js';
import { TEA_VERSION_NEW, TEA_VERSION_OLD } from './constants.js';
/**
* @see https://github.com/arx/ArxLibertatis/blob/1.2.1/src/animation/Animation.h#L131
* @see https://github.com/arx/ArxLibertatis/blob/ArxFatalis-1.21/Sources/Include/EERIETypes.h#L667
*/
export var ArxAnimationPlayingFlags;
(function (ArxAnimationPlayingFlags) {
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["None"] = 0] = "None";
/**
* Must be looped at end (indefinitely...)
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["Loop"] = 1] = "Loop";
/**
* Is played in reverse (from end to start)
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["Reverse"] = 2] = "Reverse";
/**
* Is paused
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["Paused"] = 4] = "Paused";
/**
* Has just finished
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["AnimEnd"] = 8] = "AnimEnd";
/**
* Is a static Anim (no movement offset returned)
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["StaticAnim"] = 16] = "StaticAnim";
/**
* Must be stopped at end
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["StopEnd"] = 32] = "StopEnd";
/**
* User controlled... MUST be played...
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["ForcePlay"] = 64] = "ForcePlay";
/**
* ctime externally set, no update
*/
ArxAnimationPlayingFlags[ArxAnimationPlayingFlags["ExControl"] = 128] = "ExControl";
})(ArxAnimationPlayingFlags || (ArxAnimationPlayingFlags = {}));
export class KeyFrame {
static readFrom(binary, version, numberOfGroups) {
const frame = binary.readInt32();
const flags = binary.readInt32();
const isNewerVersion = version >= TEA_VERSION_NEW;
if (isNewerVersion) {
binary.readString(256); // info_frame - always an empty string
}
const isMasterKeyFrame = binary.readInt32() !== 0;
const isKeyFrame = binary.readInt32() !== 0;
const hasTranslateData = binary.readInt32() !== 0; // key_move
const hasQuaternionData = binary.readInt32() !== 0; // key_orient
const hasMorphData = binary.readInt32() !== 0; // key_morph - 16 bytes of unknown data - ignored by Arx
const keyframe = {
frame,
flags,
isMasterKeyFrame,
isKeyFrame,
timeFrame: binary.readInt32(),
groups: [],
};
if (hasTranslateData) {
const translate = binary.readVector3();
if (translate.x !== 0 || translate.y !== 0 || translate.z !== 0) {
keyframe.translate = translate;
}
}
if (hasQuaternionData) {
binary.skipBytes(8); // theo angle - no info on the structure - not used by Arx
const quaternion = binary.readQuat();
if (quaternion.x !== 0 || quaternion.y !== 0 || quaternion.z !== 0 || quaternion.w !== 1) {
keyframe.quaternion = quaternion;
}
}
if (hasMorphData) {
binary.skipBytes(16); // thea morph - no info on the structure - not used by Arx
}
keyframe.groups = times(() => {
const isKey = binary.readInt32() !== 0;
binary.readString(8); // angle - probably not a string as it holds garbage data, like "Ò?x19" when not empty - not used by Arx
const quaternion = binary.readQuat();
const translate = binary.readVector3();
const zoom = binary.readVector3();
const group = {
isKey,
};
if (quaternion.x !== 0 || quaternion.y !== 0 || quaternion.z !== 0 || quaternion.w !== 1) {
group.quaternion = quaternion;
}
if (translate.x !== 0 || translate.y !== 0 || translate.z !== 0) {
group.translate = translate;
}
if (zoom.x !== 0 || zoom.y !== 0 || zoom.z !== 0) {
group.zoom = zoom;
}
return group;
}, numberOfGroups);
const hasSample = binary.readInt32() !== -1;
if (hasSample) {
keyframe.sample = {
name: binary.readString(256),
sizeInBytes: binary.readInt32(),
};
binary.skipBytes(keyframe.sample.sizeInBytes); // contains audio data if sizeInBytes > 0, which is already present in SFX folder of the game * - not used by Arx
// * Only 4 audios are not present in the SFX folder of the released game:
// - Bell.wav
// - porticulis_close.wav
// - porticulis_open.wav
// - wooddoorclose.wav (same as sfx/door_wood_close.wav)
// 2 audios are present in SFX, but sound different:
// - LEVER.wav
// - Bow_end.wav
}
binary.readInt32(); // num_sfx - always -1 - not used by Arx
return keyframe;
}
static accumulateFrom(keyframe) {
const hasTranslateData = keyframe.translate !== undefined;
const hasQuaternionData = keyframe.quaternion !== undefined;
const hasSample = keyframe.sample !== undefined;
const buffer = new ArrayBuffer(KeyFrame.sizeOf(TEA_VERSION_OLD, hasTranslateData, hasQuaternionData, false, keyframe.groups.length, hasSample, 0));
const binary = new BinaryIO(buffer);
binary.writeInt32(keyframe.frame);
binary.writeInt32(keyframe.flags ?? -1);
if (keyframe.isMasterKeyFrame) {
binary.writeInt32(1);
}
else {
binary.writeInt32(0);
}
if (keyframe.isKeyFrame) {
binary.writeInt32(1);
}
else {
binary.writeInt32(0);
}
// key_move
if (hasTranslateData) {
binary.writeInt32(1);
}
else {
binary.writeInt32(0);
}
// key_orient
if (hasQuaternionData) {
binary.writeInt32(1);
}
else {
binary.writeInt32(0);
}
binary.writeInt32(0); // key_morph - ignored by Arx
binary.writeInt32(keyframe.timeFrame);
if (keyframe.translate) {
binary.writeVector3(keyframe.translate);
}
if (keyframe.quaternion) {
binary.writeUint8Array(repeat(0, 8)); // theo angle - no info on the structure
binary.writeQuat(keyframe.quaternion);
}
keyframe.groups.forEach(({ isKey, quaternion = { x: 0, y: 0, z: 0, w: 1 }, translate = { x: 0, y: 0, z: 0 }, zoom = { x: 0, y: 0, z: 0 }, }) => {
if (isKey) {
binary.writeInt32(1);
}
else {
binary.writeInt32(0);
}
binary.writeString('', 8); // angle
binary.writeQuat(quaternion);
binary.writeVector3(translate);
binary.writeVector3(zoom);
});
if (keyframe.sample === undefined) {
binary.writeInt32(-1); // num_sample
}
else {
binary.writeInt32(1); // num_sample
binary.writeString(keyframe.sample.name, 256); // sample_name
binary.writeInt32(0); // sample_size
}
binary.writeInt32(-1); // num_sfx
return buffer;
}
static sizeOf(version, hasTranslateData, hasQuaternionData, hasMorphData, numberOfGroups, hasSample, sampleSize = 0) {
let numberOfBytes = BinaryIO.sizeOfInt32() * 8;
const isNewerVersion = version >= TEA_VERSION_NEW;
if (isNewerVersion) {
numberOfBytes = numberOfBytes + BinaryIO.sizeOfString(256);
}
if (hasTranslateData) {
numberOfBytes = numberOfBytes + BinaryIO.sizeOfVector3();
}
if (hasQuaternionData) {
numberOfBytes = numberOfBytes + 8 * BinaryIO.sizeOfUint8() + BinaryIO.sizeOfQuat();
}
if (hasMorphData) {
numberOfBytes = numberOfBytes + 16 * BinaryIO.sizeOfUint8();
}
const sizeOfGroup = BinaryIO.sizeOfInt32() + BinaryIO.sizeOfString(8) + BinaryIO.sizeOfQuat() + 2 * BinaryIO.sizeOfVector3();
numberOfBytes = numberOfBytes + numberOfGroups * sizeOfGroup;
numberOfBytes = numberOfBytes + BinaryIO.sizeOfInt32();
if (hasSample) {
const sizeOfSample = BinaryIO.sizeOfString(256) + BinaryIO.sizeOfInt32() + sampleSize * BinaryIO.sizeOfUint8();
numberOfBytes = numberOfBytes + sizeOfSample;
}
numberOfBytes = numberOfBytes + BinaryIO.sizeOfInt32();
return numberOfBytes;
}
}
//# sourceMappingURL=KeyFrame.js.map