77 lines
3.9 KiB
JavaScript
77 lines
3.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { BoxGeometry, Group, Mesh, MeshStandardMaterial, Vector3 } from 'three';
|
|
import {
|
|
applyCharacterAppearance, captureAppearanceEditor, getAppearanceRegion,
|
|
getAppearanceStats, paintAppearanceRegion, prepareAppearanceEditor, restoreAppearanceEditor,
|
|
} from '../src/utils/characterAppearance.js';
|
|
|
|
function fixture(material = new MeshStandardMaterial()) {
|
|
const model = new Group();
|
|
const mesh = new Mesh(new BoxGeometry(), material);
|
|
model.add(mesh);
|
|
model.updateMatrixWorld(true);
|
|
return { model, mesh };
|
|
}
|
|
|
|
test('painting separates material regions while preserving the original mesh and UVs', () => {
|
|
const { model, mesh } = fixture();
|
|
const original = mesh.geometry;
|
|
const originalUV = original.getAttribute('uv').array.slice();
|
|
prepareAppearanceEditor(model);
|
|
assert.notEqual(mesh.geometry, original);
|
|
assert.deepEqual(getAppearanceStats(model), { body: 12, hair: 0, iris: 0 });
|
|
const snapshot = captureAppearanceEditor(model);
|
|
const indexBuffer = mesh.geometry.index;
|
|
assert.equal(paintAppearanceRegion({ object: mesh, faceIndex: 0, point: new Vector3(.5, 0, 0) }, 0, 'hair', new Vector3(-1, 0, 0)), true);
|
|
assert.deepEqual(getAppearanceStats(model), { body: 11, hair: 1, iris: 0 });
|
|
assert.deepEqual(mesh.geometry.getAttribute('uv').array, originalUV);
|
|
assert.equal(mesh.geometry.index, indexBuffer);
|
|
assert.equal(original.groups.length, 6);
|
|
restoreAppearanceEditor(snapshot);
|
|
assert.deepEqual(getAppearanceStats(model), { body: 12, hair: 0, iris: 0 });
|
|
assert.equal(paintAppearanceRegion({ object: mesh, faceIndex: 0, point: new Vector3() }, 1, 'unknown', new Vector3()), false);
|
|
});
|
|
|
|
test('hair and iris colors remain independent of other instances and can return to the original', () => {
|
|
const originalMaterial = new MeshStandardMaterial();
|
|
const first = fixture(originalMaterial);
|
|
const second = fixture(originalMaterial);
|
|
prepareAppearanceEditor(first.model);
|
|
prepareAppearanceEditor(second.model);
|
|
applyCharacterAppearance(first.model, { hairColor: '#ff0000', eyeColor: '#0000ff' });
|
|
applyCharacterAppearance(second.model, { hairColor: '#00ff00' });
|
|
const shader = () => ({ uniforms: {}, fragmentShader: '#include <map_fragment>\n#include <emissivemap_fragment>' });
|
|
const hairShader = shader(), irisShader = shader(), otherShader = shader();
|
|
first.mesh.material[1].onBeforeCompile(hairShader);
|
|
first.mesh.material[2].onBeforeCompile(irisShader);
|
|
second.mesh.material[1].onBeforeCompile(otherShader);
|
|
assert.equal(hairShader.uniforms.falukantTargetColor.value.getHexString(), 'ff0000');
|
|
assert.equal(irisShader.uniforms.falukantTargetColor.value.getHexString(), '0000ff');
|
|
assert.equal(otherShader.uniforms.falukantTargetColor.value.getHexString(), '00ff00');
|
|
applyCharacterAppearance(first.model, { hairColor: '#cccccc', eyeColor: null });
|
|
assert.equal(hairShader.uniforms.falukantTargetColor.value.getHexString(), 'cccccc');
|
|
assert.equal(irisShader.uniforms.falukantColorEnabled.value, 0);
|
|
assert.equal(otherShader.uniforms.falukantTargetColor.value.getHexString(), '00ff00');
|
|
applyCharacterAppearance(first.model, { hairColor: 'invalid' });
|
|
assert.equal(hairShader.uniforms.falukantColorEnabled.value, 0);
|
|
});
|
|
|
|
test('saved glTF material metadata can be prepared again without multiplying materials', () => {
|
|
const first = fixture();
|
|
prepareAppearanceEditor(first.model);
|
|
const model = new Group();
|
|
const mesh = new Mesh(first.mesh.geometry.clone(), first.mesh.material.map(material => {
|
|
const copy = material.clone();
|
|
copy.userData = JSON.parse(JSON.stringify(material.userData));
|
|
return copy;
|
|
}));
|
|
model.add(mesh);
|
|
prepareAppearanceEditor(model);
|
|
assert.equal(mesh.material.length, 3);
|
|
assert.equal(getAppearanceRegion(mesh.material[1]), 'hair');
|
|
assert.equal(getAppearanceRegion(mesh.material[2]), 'iris');
|
|
prepareAppearanceEditor(model);
|
|
assert.equal(mesh.material.length, 3);
|
|
});
|