102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
import { BoxGeometry } from "three";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { createBoxBrush } from "../../src/document/brushes";
|
|
import {
|
|
applyBoxBrushFaceUvsToGeometry,
|
|
createFitToFaceBoxBrushFaceUvState,
|
|
createFitToMaterialTileBoxBrushFaceUvState,
|
|
transformProjectedFaceUv
|
|
} from "../../src/geometry/box-face-uvs";
|
|
|
|
describe("box face UV projection", () => {
|
|
it("fit-to-face produces finite UVs normalized across the target face", () => {
|
|
const brush = createBoxBrush({
|
|
size: {
|
|
x: 4,
|
|
y: 2,
|
|
z: 6
|
|
}
|
|
});
|
|
|
|
brush.faces.posZ.uv = createFitToFaceBoxBrushFaceUvState(brush, "posZ");
|
|
|
|
const geometry = new BoxGeometry(brush.size.x, brush.size.y, brush.size.z);
|
|
applyBoxBrushFaceUvsToGeometry(geometry, brush);
|
|
|
|
const uvAttribute = geometry.getAttribute("uv");
|
|
const indexAttribute = geometry.getIndex();
|
|
const posZGroup = geometry.groups.find((group) => group.materialIndex === 4);
|
|
|
|
expect(indexAttribute).not.toBeNull();
|
|
expect(posZGroup).toBeDefined();
|
|
|
|
const uniqueVertexIndices = new Set<number>();
|
|
|
|
for (let indexOffset = posZGroup!.start; indexOffset < posZGroup!.start + posZGroup!.count; indexOffset += 1) {
|
|
uniqueVertexIndices.add(indexAttribute!.getX(indexOffset));
|
|
}
|
|
|
|
const uvValues = Array.from(uniqueVertexIndices, (vertexIndex) => ({
|
|
u: uvAttribute.getX(vertexIndex),
|
|
v: uvAttribute.getY(vertexIndex)
|
|
}));
|
|
|
|
expect(uvValues).toHaveLength(4);
|
|
expect(uvValues.every((uv) => Number.isFinite(uv.u) && Number.isFinite(uv.v))).toBe(true);
|
|
expect(Math.min(...uvValues.map((uv) => uv.u))).toBeCloseTo(0);
|
|
expect(Math.max(...uvValues.map((uv) => uv.u))).toBeCloseTo(1);
|
|
expect(Math.min(...uvValues.map((uv) => uv.v))).toBeCloseTo(0);
|
|
expect(Math.max(...uvValues.map((uv) => uv.v))).toBeCloseTo(1);
|
|
});
|
|
|
|
it("applies rotation, scale, and offset deterministically to projected UVs", () => {
|
|
const transformedUv = transformProjectedFaceUv(
|
|
{
|
|
x: 4,
|
|
y: 0
|
|
},
|
|
{
|
|
x: 4,
|
|
y: 2
|
|
},
|
|
{
|
|
offset: {
|
|
x: 0.5,
|
|
y: -0.25
|
|
},
|
|
scale: {
|
|
x: 0.5,
|
|
y: 1
|
|
},
|
|
rotationQuarterTurns: 1,
|
|
flipU: true,
|
|
flipV: false
|
|
}
|
|
);
|
|
|
|
expect(transformedUv.x).toBeCloseTo(2.5);
|
|
expect(transformedUv.y).toBeCloseTo(-0.25);
|
|
});
|
|
|
|
it("fits one authored material tile across the face bounds", () => {
|
|
const brush = createBoxBrush({
|
|
size: {
|
|
x: 5,
|
|
y: 2,
|
|
z: 4
|
|
}
|
|
});
|
|
|
|
const uvState = createFitToMaterialTileBoxBrushFaceUvState(brush, "posZ", {
|
|
x: 2.5,
|
|
y: 2.5
|
|
});
|
|
|
|
expect(uvState.scale).toEqual({
|
|
x: 0.5,
|
|
y: 1.25
|
|
});
|
|
});
|
|
});
|