Replace the per-shape tile geometries with @tts/mesh extrusion, honoring the tile type and stretch aspect ratio. Add extrudeShapeParts so the caps carry the texture while the walls are solid white, matching TTS tinting. Fix bottom-face UVs to mirror horizontally and make wall UVs inherit the front mapping independent of z.
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
circleShape,
|
|
frameShape,
|
|
hexShape,
|
|
polygonShape,
|
|
rectShape,
|
|
roundedRectShape,
|
|
scaleShape,
|
|
shapeFromThree,
|
|
signedArea,
|
|
} from './shapes.js';
|
|
|
|
describe('shape generators', () => {
|
|
it('rectShape is a CCW unit-ish rectangle', () => {
|
|
const s = rectShape(2, 4);
|
|
expect(s.outline).toEqual([
|
|
[-1, -2],
|
|
[1, -2],
|
|
[1, 2],
|
|
[-1, 2],
|
|
]);
|
|
expect(signedArea(s.outline)).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('polygonShape produces a regular polygon with the right vertex count', () => {
|
|
const s = polygonShape(6, 2);
|
|
expect(s.outline).toHaveLength(6);
|
|
// All vertices on the circle of radius 2.
|
|
for (const [x, y] of s.outline) {
|
|
expect(Math.hypot(x!, y!)).toBeCloseTo(2, 5);
|
|
}
|
|
expect(signedArea(s.outline)).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('hexShape is a pointy-top hexagon', () => {
|
|
const s = hexShape(1);
|
|
expect(s.outline).toHaveLength(6);
|
|
// Pointy-top: a vertex sits at the top (angle 90deg, max y).
|
|
const top = s.outline.reduce((a, b) => (b[1]! > a[1]! ? b : a));
|
|
expect(top[0]).toBeCloseTo(0, 5);
|
|
expect(top[1]).toBeCloseTo(1, 5);
|
|
});
|
|
|
|
it('circleShape approximates a circle', () => {
|
|
const s = circleShape(3, 64);
|
|
expect(s.outline).toHaveLength(64);
|
|
for (const [x, y] of s.outline) {
|
|
expect(Math.hypot(x!, y!)).toBeCloseTo(3, 5);
|
|
}
|
|
});
|
|
|
|
it('roundedRectShape clamps the corner radius', () => {
|
|
const s = roundedRectShape(2, 2, 5);
|
|
// Radius clamped to half the smaller dimension (1).
|
|
const minX = Math.min(...s.outline.map((p) => p[0]!));
|
|
const maxX = Math.max(...s.outline.map((p) => p[0]!));
|
|
expect(minX).toBeCloseTo(-1, 5);
|
|
expect(maxX).toBeCloseTo(1, 5);
|
|
expect(s.outline.length).toBe(4 * 4); // 4 corners * 4 segments
|
|
});
|
|
|
|
it('frameShape has a hole wound CW', () => {
|
|
const s = frameShape(4, 4, 2);
|
|
expect(s.outline).toHaveLength(4);
|
|
expect(s.holes).toHaveLength(1);
|
|
expect(signedArea(s.outline)).toBeGreaterThan(0);
|
|
expect(signedArea(s.holes![0]!)).toBeLessThan(0);
|
|
});
|
|
|
|
it('scaleShape scales outline and holes', () => {
|
|
const s = scaleShape(rectShape(2, 2), 2, 3);
|
|
expect(s.outline).toEqual([
|
|
[-2, -3],
|
|
[2, -3],
|
|
[2, 3],
|
|
[-2, 3],
|
|
]);
|
|
const framed = scaleShape(frameShape(4, 4, 2), 2, 2);
|
|
expect(framed.holes).toHaveLength(1);
|
|
expect(framed.holes![0]![0]).toEqual([-2, -2]);
|
|
});
|
|
|
|
it('shapeFromThree converts three.js vectors', () => {
|
|
const fake = {
|
|
getPoints: () => [
|
|
{ x: 0, y: 0 },
|
|
{ x: 1, y: 0 },
|
|
{ x: 1, y: 1 },
|
|
],
|
|
getPointsHoles: () => [],
|
|
};
|
|
const s = shapeFromThree(fake);
|
|
expect(s.outline).toEqual([
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 1],
|
|
]);
|
|
expect(s.holes).toBeUndefined();
|
|
});
|
|
}); |