|
| 1 | +/** |
| 2 | + * @author TristanVALCKE / https://github.com/Itee |
| 3 | + * @author Joe Pea / http://github.com/trusktr |
| 4 | + */ |
| 5 | + |
| 6 | +import { runStdGeometryTests } from '../test-utils' |
| 7 | +import { PlaneGeometry } from './PlaneGeometry' |
| 8 | +import { BufferGeometry } from '../core/BufferGeometry' |
| 9 | +import { Float32BufferAttribute } from '../core/BufferAttribute' |
| 10 | + |
| 11 | +let geometries: BufferGeometry[] = [] |
| 12 | + |
| 13 | +describe('Geometries', (): void => { |
| 14 | + describe('PlaneGeometry', (): void => { |
| 15 | + beforeEach((): void => { |
| 16 | + geometries = [ |
| 17 | + new PlaneGeometry(), |
| 18 | + new PlaneGeometry(10, 20), |
| 19 | + new PlaneGeometry(10, 20, 2, 3), |
| 20 | + ] |
| 21 | + }) |
| 22 | + |
| 23 | + // Standard geometry tests |
| 24 | + test('Standard geometry tests', (): void => { |
| 25 | + runStdGeometryTests(geometries) |
| 26 | + }) |
| 27 | + |
| 28 | + test('default parameters create 1x1 plane', (): void => { |
| 29 | + const geom = new PlaneGeometry() |
| 30 | + |
| 31 | + expect(geom.parameters.width).toBe(1, 'default width should be 1') |
| 32 | + expect(geom.parameters.height).toBe(1, 'default height should be 1') |
| 33 | + expect(geom.parameters.widthSegments).toBe(1, 'default widthSegments should be 1') |
| 34 | + expect(geom.parameters.heightSegments).toBe(1, 'default heightSegments should be 1') |
| 35 | + }) |
| 36 | + |
| 37 | + test('custom parameters are stored correctly', (): void => { |
| 38 | + const geom = new PlaneGeometry(10, 20, 4, 5) |
| 39 | + |
| 40 | + expect(geom.parameters.width).toBe(10, 'width should be 10') |
| 41 | + expect(geom.parameters.height).toBe(20, 'height should be 20') |
| 42 | + expect(geom.parameters.widthSegments).toBe(4, 'widthSegments should be 4') |
| 43 | + expect(geom.parameters.heightSegments).toBe(5, 'heightSegments should be 5') |
| 44 | + }) |
| 45 | + |
| 46 | + test('plane has required attributes', (): void => { |
| 47 | + const geom = new PlaneGeometry(4, 4) |
| 48 | + |
| 49 | + expect(geom.attributes.has('position')).toBe(true, 'should have position attribute') |
| 50 | + expect(geom.attributes.has('normal')).toBe(true, 'should have normal attribute') |
| 51 | + expect(geom.attributes.has('uv')).toBe(true, 'should have uv attribute') |
| 52 | + }) |
| 53 | + |
| 54 | + test('plane with 1x1 segments has correct vertex count', (): void => { |
| 55 | + const geom = new PlaneGeometry(2, 2, 1, 1) |
| 56 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 57 | + |
| 58 | + // 1x1 segments = (1+1) * (1+1) = 4 vertices |
| 59 | + expect(positionAttr.count).toBe(4, '1x1 segments should have 4 vertices') |
| 60 | + }) |
| 61 | + |
| 62 | + test('plane with 2x3 segments has correct vertex count', (): void => { |
| 63 | + const geom = new PlaneGeometry(4, 6, 2, 3) |
| 64 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 65 | + |
| 66 | + // 2x3 segments = (2+1) * (3+1) = 3 * 4 = 12 vertices |
| 67 | + expect(positionAttr.count).toBe(12, '2x3 segments should have 12 vertices') |
| 68 | + }) |
| 69 | + |
| 70 | + test('plane vertices are within expected bounds', (): void => { |
| 71 | + const width: f32 = 10.0 |
| 72 | + const height: f32 = 20.0 |
| 73 | + const geom = new PlaneGeometry(width, height, 2, 2) |
| 74 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 75 | + |
| 76 | + for (let i = 0; i < positionAttr.count; i++) { |
| 77 | + const x = positionAttr.getX(i) |
| 78 | + const y = positionAttr.getY(i) |
| 79 | + const z = positionAttr.getZ(i) |
| 80 | + |
| 81 | + // X coordinates should be within [-width/2, width/2] |
| 82 | + expect(Math.abs(x)).toBeLessThanOrEqual(width / 2 + 0.01, 'x should be within bounds') |
| 83 | + |
| 84 | + // Y coordinates should be within [-height/2, height/2] |
| 85 | + expect(Math.abs(y)).toBeLessThanOrEqual(height / 2 + 0.01, 'y should be within bounds') |
| 86 | + |
| 87 | + // Z coordinates should be 0 for a plane |
| 88 | + expect(z).toBe(0.0, 'z should be 0 for plane') |
| 89 | + } |
| 90 | + }) |
| 91 | + |
| 92 | + test('plane normals point in +Z direction', (): void => { |
| 93 | + const geom = new PlaneGeometry(4, 4, 2, 2) |
| 94 | + const normalAttr = geom.attributes.get('normal') as Float32BufferAttribute |
| 95 | + |
| 96 | + for (let i = 0; i < normalAttr.count; i++) { |
| 97 | + const nx = normalAttr.getX(i) |
| 98 | + const ny = normalAttr.getY(i) |
| 99 | + const nz = normalAttr.getZ(i) |
| 100 | + |
| 101 | + expect(nx).toBe(0.0, 'normal x component should be 0') |
| 102 | + expect(ny).toBe(0.0, 'normal y component should be 0') |
| 103 | + expect(nz).toBe(1.0, 'normal z component should be 1') |
| 104 | + } |
| 105 | + }) |
| 106 | + |
| 107 | + test('plane UVs are in [0, 1] range', (): void => { |
| 108 | + const geom = new PlaneGeometry(4, 4, 2, 3) |
| 109 | + const uvAttr = geom.attributes.get('uv') as Float32BufferAttribute |
| 110 | + |
| 111 | + for (let i = 0; i < uvAttr.count; i++) { |
| 112 | + const u = uvAttr.getX(i) |
| 113 | + const v = uvAttr.getY(i) |
| 114 | + |
| 115 | + expect(u).toBeGreaterThanOrEqual(0.0, 'u should be >= 0') |
| 116 | + expect(u).toBeLessThanOrEqual(1.0, 'u should be <= 1') |
| 117 | + expect(v).toBeGreaterThanOrEqual(0.0, 'v should be >= 0') |
| 118 | + expect(v).toBeLessThanOrEqual(1.0, 'v should be <= 1') |
| 119 | + } |
| 120 | + }) |
| 121 | + |
| 122 | + test('plane has correct index count for 1x1 segments', (): void => { |
| 123 | + const geom = new PlaneGeometry(2, 2, 1, 1) |
| 124 | + |
| 125 | + // 1x1 segments = 1 * 1 = 1 quad = 2 triangles = 6 indices |
| 126 | + expect(geom.index!.count).toBe(6, '1x1 segments should have 6 indices') |
| 127 | + }) |
| 128 | + |
| 129 | + test('plane has correct index count for 2x3 segments', (): void => { |
| 130 | + const geom = new PlaneGeometry(4, 6, 2, 3) |
| 131 | + |
| 132 | + // 2x3 segments = 2 * 3 = 6 quads = 12 triangles = 36 indices |
| 133 | + expect(geom.index!.count).toBe(36, '2x3 segments should have 36 indices') |
| 134 | + }) |
| 135 | + |
| 136 | + test('plane indices reference valid vertices', (): void => { |
| 137 | + const geom = new PlaneGeometry(4, 4, 2, 2) |
| 138 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 139 | + const vertexCount = positionAttr.count |
| 140 | + |
| 141 | + for (let i = 0; i < geom.index!.count; i++) { |
| 142 | + const index = geom.index!.getX(i) |
| 143 | + expect(index).toBeGreaterThanOrEqual(0, 'index should be >= 0') |
| 144 | + expect(index).toBeLessThan(vertexCount, 'index should be < vertex count') |
| 145 | + } |
| 146 | + }) |
| 147 | + |
| 148 | + test('plane with 0 segments defaults to 1 segment', (): void => { |
| 149 | + // Test edge case - segments should be at least 1 |
| 150 | + const geom = new PlaneGeometry(2, 2, 0, 0) |
| 151 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 152 | + |
| 153 | + // Should behave like 1x1 segments (though the implementation may vary) |
| 154 | + // At minimum, should have 4 vertices |
| 155 | + expect(positionAttr.count).toBeGreaterThanOrEqual(4, 'should have at least 4 vertices') |
| 156 | + }) |
| 157 | + |
| 158 | + test('plane corners are at expected positions', (): void => { |
| 159 | + const width: f32 = 10.0 |
| 160 | + const height: f32 = 20.0 |
| 161 | + const geom = new PlaneGeometry(width, height, 1, 1) |
| 162 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 163 | + |
| 164 | + // For a 1x1 segment plane, we have 4 corners |
| 165 | + const corners: f32[][] = [] |
| 166 | + for (let i = 0; i < 4; i++) { |
| 167 | + corners.push([ |
| 168 | + positionAttr.getX(i), |
| 169 | + positionAttr.getY(i), |
| 170 | + positionAttr.getZ(i) |
| 171 | + ]) |
| 172 | + } |
| 173 | + |
| 174 | + // Check that we have corners at the extremes |
| 175 | + let hasTopLeft = false |
| 176 | + let hasTopRight = false |
| 177 | + let hasBottomLeft = false |
| 178 | + let hasBottomRight = false |
| 179 | + |
| 180 | + for (let i = 0; i < 4; i++) { |
| 181 | + const x = corners[i][0] |
| 182 | + const y = corners[i][1] |
| 183 | + |
| 184 | + if (Math.abs(x - (-width / 2)) < 0.01 && Math.abs(y - (height / 2)) < 0.01) hasTopLeft = true |
| 185 | + if (Math.abs(x - (width / 2)) < 0.01 && Math.abs(y - (height / 2)) < 0.01) hasTopRight = true |
| 186 | + if (Math.abs(x - (-width / 2)) < 0.01 && Math.abs(y - (-height / 2)) < 0.01) hasBottomLeft = true |
| 187 | + if (Math.abs(x - (width / 2)) < 0.01 && Math.abs(y - (-height / 2)) < 0.01) hasBottomRight = true |
| 188 | + } |
| 189 | + |
| 190 | + expect(hasTopLeft || hasTopRight || hasBottomLeft || hasBottomRight).toBe( |
| 191 | + true, |
| 192 | + 'should have at least one corner at expected position' |
| 193 | + ) |
| 194 | + }) |
| 195 | + |
| 196 | + test('plane is centered on origin', (): void => { |
| 197 | + const geom = new PlaneGeometry(10, 20, 3, 3) |
| 198 | + const positionAttr = geom.attributes.get('position') as Float32BufferAttribute |
| 199 | + |
| 200 | + let sumX: f32 = 0.0 |
| 201 | + let sumY: f32 = 0.0 |
| 202 | + let sumZ: f32 = 0.0 |
| 203 | + |
| 204 | + for (let i = 0; i < positionAttr.count; i++) { |
| 205 | + sumX += positionAttr.getX(i) |
| 206 | + sumY += positionAttr.getY(i) |
| 207 | + sumZ += positionAttr.getZ(i) |
| 208 | + } |
| 209 | + |
| 210 | + const avgX = sumX / positionAttr.count |
| 211 | + const avgY = sumY / positionAttr.count |
| 212 | + const avgZ = sumZ / positionAttr.count |
| 213 | + |
| 214 | + // Average position should be close to origin |
| 215 | + expect(Math.abs(avgX)).toBeLessThan(0.01, 'average x should be near 0') |
| 216 | + expect(Math.abs(avgY)).toBeLessThan(0.01, 'average y should be near 0') |
| 217 | + expect(Math.abs(avgZ)).toBeLessThan(0.01, 'average z should be near 0') |
| 218 | + }) |
| 219 | + }) |
| 220 | +}) |
0 commit comments