[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft.
flo-bit.dev/shapecraft/
3.2 kB
88 lines
1import { describe, it, expect } from 'vitest'
2import { box, sphere, cylinder } from '../src/primitives'
3import { weld, mirror, array, radialArray, subtract, union, intersect } from '../src/ops'
4
5describe('weld', () => {
6 it('merges coincident vertices and indexes the mesh', () => {
7 const b = box({ size: 1 }) // non-indexed, 24 verts (4 per face × 6)
8 const w = weld(b)
9 expect(w.geometry.getIndex()).not.toBeNull()
10 expect(w.vertexCount).toBe(8) // a cube has 8 unique corners
11 expect(w.faceCount).toBe(b.faceCount) // same triangles
12 })
13})
14
15describe('mirror', () => {
16 it('doubles geometry and spans both sides of the plane', () => {
17 const b = box({ size: 1 }).translate(1, 0, 0) // sits at x∈[0.5,1.5]
18 const m = mirror(b, { axis: 'x', position: 0 })
19 expect(m.faceCount).toBe(b.faceCount * 2)
20 const bb = m.boundingBox
21 expect(bb.min.x).toBeLessThan(-0.4) // reflected copy on the −x side
22 expect(bb.max.x).toBeGreaterThan(1.4)
23 })
24
25 it('keepOriginal:false returns just the reflection', () => {
26 const b = box({ size: 1 }).translate(1, 0, 0)
27 const m = mirror(b, { axis: 'x', keepOriginal: false })
28 expect(m.faceCount).toBe(b.faceCount)
29 expect(m.boundingBox.max.x).toBeLessThan(-0.4)
30 })
31})
32
33describe('array', () => {
34 it('repeats count times with an offset', () => {
35 const b = box({ size: 1 })
36 const a = array(b, { count: 4, offset: [2, 0, 0] })
37 expect(a.vertexCount).toBe(b.vertexCount * 4)
38 // 4 boxes spaced by 2 along x → spans ~[-0.5, 6.5]
39 expect(a.boundingBox.max.x).toBeGreaterThan(6)
40 })
41
42 it('count 1 is just the original', () => {
43 const b = box({ size: 1 })
44 expect(array(b, { count: 1 }).vertexCount).toBe(b.vertexCount)
45 })
46})
47
48describe('radialArray', () => {
49 it('places copies around an axis', () => {
50 const s = sphere({ radius: 0.3 })
51 const r = radialArray(s, { count: 6, radius: 1, axis: 'y' })
52 expect(r.vertexCount).toBe(s.vertexCount * 6)
53 const bb = r.boundingBox
54 // a ring of radius 1 → spans roughly ±1.3 in x and z
55 expect(bb.max.x).toBeGreaterThan(1)
56 expect(bb.max.z).toBeGreaterThan(1)
57 })
58})
59
60describe('boolean (CSG)', () => {
61 it('subtract carves a hole, keeping the outer bounds', () => {
62 const block = box({ size: 2 })
63 const drill = cylinder({ radius: 0.4, height: 3, segments: 16 })
64 const result = subtract(block, drill)
65 expect(result.vertexCount).toBeGreaterThan(0)
66 const p = result.positions
67 for (let i = 0; i < p.length; i++) expect(Number.isFinite(p[i])).toBe(true)
68 const bb = result.boundingBox
69 expect(bb.max.x).toBeCloseTo(1, 1)
70 expect(bb.min.x).toBeCloseTo(-1, 1)
71 })
72
73 it('union spans both volumes', () => {
74 const a = box({ size: 1 })
75 const b = box({ size: 1 }).translate(1.4, 0, 0)
76 const bb = union(a, b).boundingBox
77 expect(bb.min.x).toBeCloseTo(-0.5, 1)
78 expect(bb.max.x).toBeCloseTo(1.9, 1)
79 })
80
81 it('intersect keeps only the overlap', () => {
82 const a = box({ size: 2 }) // x∈[-1,1]
83 const b = box({ size: 2 }).translate(1, 0, 0) // x∈[0,2]
84 const bb = intersect(a, b).boundingBox
85 expect(bb.min.x).toBeCloseTo(0, 1)
86 expect(bb.max.x).toBeCloseTo(1, 1)
87 })
88})