Files
tts-workshop/packages/mesh/src/shapes.test.ts
T
hypercross d554d6bde1 feat(tile): render tiles with the mesh generator
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.
2026-08-08 14:21:53 +08:00

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();
});
});