sound library

This commit is contained in:
2026-09-29 01:07:15 +02:00
parent 6d4094e7c4
commit 55d5593804
5 changed files with 378 additions and 20 deletions
+14 -4
View File
@@ -31,7 +31,7 @@ function harness(capture = false) {
add(undo) { assert.equal(undo, false); Texture.all.push(this); }
}
const context = {
Buffer, Timeline: { animation: null },
Buffer, Blob, URL: { createObjectURL() { return 'blob:import-preview'; }, revokeObjectURL() {} }, Timeline: { animation: null },
Project: { uuid: 'original' }, Group, Cube: { all: [] }, Texture, Outliner: { elements: [] },
Plugin: { register() {} }, requireNativeModule: require,
localStorage: { getItem: (key) => storage.get(key), setItem: (key, value) => storage.set(key, value) },
@@ -49,7 +49,10 @@ function harness(capture = false) {
Animator: {
loadFile(file, names) {
context.loadedAnimation = JSON.parse(file.content);
context.Animation.all = names.map((name) => ({ name, select() {} }));
context.Animation.all = names.map((name) => ({ name, select() {}, animators: { effects: { sound:
Object.entries(context.loadedAnimation.animations[name]?.sound_effects || {}).map(([time, cues]) => ({
time: Number(time), data_points: Array.isArray(cues) ? cues : [cues],
})) } } }));
},
},
Animation: { all: [] }, Canvas: { updateAll() {}, updateView() {} },
@@ -313,7 +316,8 @@ test('complete import finds model texture in cached assets and uses its PNG dime
importing.commitAutomaticImport(h.context.importedPlan);
assert.equal(importing.context.Texture.all[0].uv_width, 32);
assert.equal(importing.context.Texture.all[0].uv_height, 64);
assert.equal(JSON.parse(importing.context.Project.animationdirector_actor_uv_basis)['1'].width, 32);
assert.equal(JSON.parse(importing.context.Project.animationdirector_actor_uv_basis)['1'].width, 64,
'The actor retains its geometry basis; each textured cube tracks its own normalization');
assert.equal(JSON.parse(importing.context.Project.multiactor_actor_textures)['1'], row.defaultTexture);
});
@@ -494,10 +498,13 @@ test('missing models can be supplied manually, and their locally supplied textur
test('batch import uses a new project, one undo entry, actor prefixes and texture override bindings', () => {
const h = harness();
h.storage.set('animationdirector_sound_source', path.resolve(__dirname, '../../NON/src/main/resources'));
const plan = { parsed: { animationId: 'example' }, blockEntry: null,
propEntries: { '["example","wolf","right",0]': { animation: 'example', item: 'minecraft:carrot', mode: 'item' } },
definition: { actors: [{ label: 'wolf', entity_types: ['minecraft:wolf'] }], animators: ['Creator'], stages: [{ stage: 1 }] },
imported: { stageNames: ['example.p1'], fileObj: { animations: { 'example.p1': { bones: { actor1_body: { rotation: [1, 2, 3] } } } } } },
imported: { stageNames: ['example.p1'], fileObj: { animations: { 'example.p1': {
bones: { actor1_body: { rotation: [1, 2, 3] } }, sound_effects: { '0.5': { effect: 'reactiveimpact' } },
} } } },
rows: [{ label: 'wolf', json: { afw_bone_textures: { body: 'test:textures/wolf.png' } },
geo: { bones: [{ name: 'body' }] }, defaultTexture: 'test:textures/wolf.png',
textures: new Map([['test:textures/wolf.png', { file: 'wolf.png' }]]) }] };
@@ -511,6 +518,9 @@ test('batch import uses a new project, one undo entry, actor prefixes and textur
assert.equal(override[h.context.Group.all[0].uuid].texture, 'test:textures/wolf.png');
assert.ok(JSON.parse(h.context.Project.animationdirector_animdef_definitions).example);
assert.equal(JSON.parse(h.context.Project.animationdirector_prop_previews)['["example","wolf","right",0]'].item,'minecraft:carrot');
const cue = h.context.Animation.all[0].animators.effects.sound[0].data_points[0];
assert.equal(cue.effect, 'reactiveimpact');
assert.equal(cue.file, 'blob:import-preview');
});
test('unexpected animation-load failures cancel the batch rather than keeping a partial import', () => {
+1 -1
View File
@@ -107,7 +107,7 @@ test('animation menu orders import and creation actions and has no deprecated sp
const menu = h.menus.find((entry) => entry.id === 'animationdirector_anims');
assert.deepEqual(Array.from(menu.children, (action) => action.name), [
'Automatically Import Animation', 'Manual Import', 'Actor Preferences',
'Replace Actor Model', 'Block Requirements', 'Item Props', 'Create Animation Definition', 'Export Animations',
'Replace Actor Model', 'Block Requirements', 'Item Props', 'Sound Library...', 'Create Animation Definition', 'Export Animations',
]);
assert.deepEqual(Array.from(menu.children[1].children, (action) => action.name), [
'Import Actor', 'Import Animation', 'Import Animation Definition', 'Pack Folders',
+26 -2
View File
@@ -8,7 +8,7 @@ const vm = require('node:vm');
function harness() {
const source = fs.readFileSync(path.join(__dirname, '..', 'AnimationDirector.js'), 'utf8').replace(
' Plugin.register(PLUGIN_ID, {',
' globalThis.uvTest = { normalizeActorUvs, expandProjectUvs, scaleCubeUvs, getActorUvBasis, tryLoadImportedActorTexture, applyActorTexturePreview, importGeometryIntoCurrentProject };\n Plugin.register(PLUGIN_ID, {'
' globalThis.uvTest = { normalizeActorUvs, expandProjectUvs, scaleCubeUvs, getActorUvBasis, tryLoadImportedActorTexture, applyActorTexturePreview, importGeometryIntoCurrentProject, applyAllActorTexturePreviews, setProjectActorTextures, setProjectBoneTextureOverrides };\n Plugin.register(PLUGIN_ID, {'
);
class Group {
static all = [];
@@ -46,7 +46,7 @@ function harness() {
setTimeout(callback) { callback(); },
Undo: { initEdit() {}, finishEdit() {}, cancelEdit() {} },
Project: { texture_width: 64, texture_height: 64, box_uv: true },
Canvas: { updateAllUVs() {} }, Plugin: { register() {} },
Canvas: { updateAllUVs() {}, updateAll() {} }, Plugin: { register() {} },
localStorage: { getItem() { return null; } },
};
vm.runInNewContext(source, context);
@@ -82,6 +82,30 @@ test('assigning a manually imported texture to an actor normalizes only that act
assert.deepEqual(Array.from(selected.faces.north.uv), [0, 0, 32, 16]);
});
test('bone texture dimensions override the base without repeated UV scaling', () => {
const h = harness();
const group = new h.context.Group({ name: 'actor1_wind', uuid: 'wind' });
group.init();
const baseCube = new h.Cube('actor1_base'), windCube = new h.Cube('actor1_wind_cube');
windCube.addTo(group);
h.context.Project.texture_width = h.context.Project.texture_height = 128;
h.normalizeActorUvs(1, 128, 128);
for (const [name, size] of [['vanilla', 32], ['wind', 128]]) {
new h.Texture({ name, uv_width: size, uv_height: size }).add();
}
h.setProjectActorTextures({ 1: 'vanilla' });
h.setProjectBoneTextureOverrides({ wind: { uuid: 'wind', name: group.name, texture: 'wind' } });
h.applyAllActorTexturePreviews();
assert.deepEqual(Array.from(baseCube.faces.north.uv), [0, 0, 64, 64]);
assert.deepEqual(Array.from(windCube.faces.north.uv), [0, 0, 16, 16]);
assert.equal(windCube.faces.north.texture, 'wind');
h.applyAllActorTexturePreviews();
assert.deepEqual(Array.from(windCube.faces.north.uv), [0, 0, 16, 16]);
h.setProjectBoneTextureOverrides({});
h.applyAllActorTexturePreviews();
assert.deepEqual(Array.from(windCube.faces.north.uv), [0, 0, 64, 64]);
});
test('importing a larger actor preserves UV mapping of the earlier model', () => {
const h = harness();
const geometry = (width) => ({ description: { texture_width: width, texture_height: width }, bones: [{ name: 'body', cubes: [