• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Beakerboy / Threejs-Geometries / 15406610193

03 Jun 2025 01:44AM UTC coverage: 87.5% (-11.7%) from 99.215%
15406610193

push

github

web-flow
Understand wedge (#18)

* Update WedgeGeometry.tests.js

83 of 87 branches covered (95.4%)

Branch coverage included in aggregate %.

137 of 165 new or added lines in 1 file covered. (83.03%)

76 existing lines in 1 file now uncovered.

715 of 825 relevant lines covered (86.67%)

657.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.95
/src/WedgeGeometry.js
1
import {
1✔
2
        BufferGeometry,
1✔
3
        Vector2,
1✔
4
        Shape,
1✔
5
        ShapeUtils,
1✔
6
        BufferAttribute,
1✔
7
} from 'three';
1✔
8

1✔
9
/**
1✔
10
 * Modify ExtrudeGeometry such that z varies with x and y
1✔
11
 */
1✔
12
class WedgeGeometry extends BufferGeometry {
1✔
13

1✔
14
        constructor( shape = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) {
1✔
15

72✔
16
                super();
72✔
17
                this.type = 'WedgeGeometry';
72✔
18
                this.parameters = {
72✔
19
                        shape: shape,
72✔
20
                        options: options,
72✔
21
                };
72✔
22

72✔
23
                // The max depth of the geometry
72✔
24
                var depth = options.depth;
72✔
25

72✔
26
                // a point on which the peak will pass through
72✔
27
                const center = options.center !== undefined ? options.center : [ 0, 0 ];
72✔
28
                this.parameters.options.center = center;
72✔
29

72✔
30
                // The direction that the downward slope faces,
72✔
31
                const angle = options.angle;
72✔
32

72✔
33
                // The outer shape is the original shape plus any crossing points.
72✔
34
                const outerShape = new Shape();
72✔
35

72✔
36
                // A straight array of vertices for the outer shape
72✔
37
                const outerVertices = [];
72✔
38

72✔
39
                this.points = WedgeGeometry.cleanInputs( shape );
72✔
40
                // Get the cleaned outer shape and holes.
72✔
41
                var shapePoints = this.points.shape;
72✔
42
                var shapeHoles = this.points.holes;
72✔
43
                this.parameters.shape = new Shape( shapePoints.map( point => new Vector2( ...point ) ) );
72✔
44
                // this.parameters.shape.holes.push( ...shapeHoles );
72✔
45

72✔
46
                // The original shape's point, but rotated and centered.
72✔
47
                /** @type {[number, number][]} */
72✔
48
                const newPoints = shapePoints.map( ( point ) => this.move( point ) );
72✔
49

72✔
50
                var minY = Math.min( ...newPoints.map( ( point ) => point[ 1 ] ) );
72✔
51
                var maxY = Math.max( ...newPoints.map( ( point ) => point[ 1 ] ) );
72✔
52

72✔
53
                this.newShapes = WedgeGeometry.splitShape( newPoints );
72✔
54

72✔
55
                const positions = [];
72✔
56
                // positions.push( ...this.buildRoof() );
72✔
57
                // positions.push( ...this.buildWalls() );
72✔
58
                // positions.push( ...this.buildFloor() );
72✔
59
                // If the line does not intersect, display the outline.
72✔
60
                // otherwise just the parts.
72✔
61
                const startK = this.newShapes.length > 1 ? 1 : 0;
72✔
62
                for ( let k = startK; k < this.newShapes.length; k ++ ) {
72✔
63

105✔
64
                        const points = this.newShapes[ k ].extractPoints().shape;
105✔
65
                        // Add top of roof
105✔
66
                        const faces = ShapeUtils.triangulateShape( points, [] );
105✔
67
                        for ( let i = 0; i < faces.length; i ++ ) {
105✔
68

2,192✔
69
                                const face = faces[ i ];
2,192✔
70
                                for ( let j = 0; j < 3; j ++ ) {
2,192✔
71

6,576✔
72
                                        const unmoved = this.unMove( [ points[ face[ j ] ].x, points[ face[ j ] ].y ] );
6,576✔
73
                                        const x = unmoved[ 0 ];
6,576✔
74
                                        const y = unmoved[ 1 ];
6,576✔
75
                                        var z;
6,576✔
76
                                        if ( points[ face[ j ] ].y >= 0 ) {
6,576✔
77

297✔
78
                                                z = depth - depth / maxY * points[ face[ j ] ].y;
297✔
79

297✔
80
                                        } else {
6,576✔
81

6,279✔
82
                                                z = depth - depth / minY * points[ face[ j ] ].y;
6,279✔
83

6,279✔
84
                                        }
6,279✔
85

6,576✔
86
                                        //const z = ( x * Math.sin( angle ) - y * Math.cos( angle ) - minDepth ) * scale;
6,576✔
87
                                        positions.push( x, y, z );
6,576✔
88

6,576✔
89
                                }
6,576✔
90

2,192✔
91
                        }
2,192✔
92

105✔
93
                }
105✔
94

72✔
95
                // Build the floor
72✔
96
                const floorPoints = this.newShapes[ 0 ].extractPoints().shape;
72✔
97
                const floorHoles = this.newShapes[ 0 ].extractPoints().holes;
72✔
98
                const faces = ShapeUtils.triangulateShape( floorPoints, [] );
72✔
99
                for ( let i = 0; i < faces.length; i ++ ) {
72✔
100

2,192✔
101
                        const face = faces[ i ];
2,192✔
102
                        for ( let j = 0; j < 3; j ++ ) {
2,192✔
103

6,576✔
104
                                const unmoved = this.unMove( [ floorPoints[ face[ j ] ].x, floorPoints[ face[ j ] ].y ] );
6,576✔
105
                                const x = unmoved[ 0 ];
6,576✔
106
                                const y = unmoved[ 1 ];
6,576✔
107
                                positions.push( x, y, 0 );
6,576✔
108

6,576✔
109
                        }
6,576✔
110

2,192✔
111
                }
2,192✔
112

72✔
113
                // Make walls by iterating the outline.
72✔
114
                for ( let i = 0; i < floorPoints.length; i ++ ) {
72✔
115

2,336✔
116
                        var point = floorPoints[ i ];
2,336✔
117
                        var pointZ = depth * ( 1 - point.y / ( point.y >= 0 ? maxY : minY ) );
2,336✔
118

2,336✔
119
                        var nextPoint = floorPoints[ ( i + 1 ) % floorPoints.length ];
2,336✔
120
                        var nextPointZ = depth * ( 1 - nextPoint.y / ( nextPoint.y >= 0 ? maxY : minY ) );
2,336✔
121

2,336✔
122
                        if ( pointZ !== 0 ) {
2,336✔
123

2,204✔
124
                                positions.push( ...this.unMove( [ point.x, point.y ] ), 0 );
2,204✔
125
                                positions.push( ...this.unMove( [ point.x, point.y ] ), pointZ );
2,204✔
126
                                positions.push( ...this.unMove( [ nextPoint.x, nextPoint.y ] ), 0 );
2,204✔
127

2,204✔
128
                        }
2,204✔
129

2,336✔
130
                        if ( nextPointZ !== 0 ) {
2,336✔
131

2,204✔
132
                                positions.push( ...this.unMove( [ point.x, point.y ] ), pointZ );
2,204✔
133
                                positions.push( ...this.unMove( [ nextPoint.x, nextPoint.y ] ), nextPointZ );
2,204✔
134
                                positions.push( ...this.unMove( [ nextPoint.x, nextPoint.y ] ), 0 );
2,204✔
135

2,204✔
136
                        }
2,204✔
137

2,336✔
138
                }
2,336✔
139

72✔
140
                this.setAttribute( 'position', new BufferAttribute( new Float32Array( positions ), 3 ) );
72✔
141
                this.computeVertexNormals();
72✔
142

72✔
143
        }
72✔
144

1✔
145
        /**
1✔
146
        * Split a shape using the x-axis as the line. Shape is clockwise.
1✔
147
        * Not tested on self-intersecting shapes.
1✔
148
        *
1✔
149
        * @param {[[number, number]]} points - an array of x, y pairs.
1✔
150
        * @return {Three.Shape[]} an array of shapes. Element 0 is the original shape with
1✔
151
        *                   the addition of new vertices for the crossing points.
1✔
152
        */
1✔
153
        static splitShape( points ) {
1✔
154

74✔
155
                const crossingResults = WedgeGeometry.getCrossings( points );
74✔
156
                const crossings = crossingResults.crossings;
74✔
157
                const newOutline = crossingResults.newOutline;
74✔
158

74✔
159
                if ( Object.keys( crossings ).length === 0 ) {
74✔
160

40✔
161
                        return [ newOutline ];
40✔
162

40✔
163
                }
40✔
164

34✔
165
                if ( Object.keys( crossings ).length === 2 ) {
34✔
166

34✔
167
                        const points = newOutline.extractPoints().shape;
34✔
168
                        const topShapePoints = [];
34✔
169
                        const bottomShapePoints = [];
34✔
170
                        for ( const point of points ) {
34✔
171

204✔
172
                                if ( point.y >= 0 ) topShapePoints.push( point );
204✔
173
                                if ( point.y <= 0 ) bottomShapePoints.push( point );
204✔
174

204✔
175
                        }
204✔
176

34✔
177
                        const topShape = new Shape( topShapePoints );
34✔
178
                        const bottomShape = new Shape( bottomShapePoints );
34✔
179
                        return [ newOutline, topShape, bottomShape ];
34✔
180

34✔
181
                }
34✔
UNCOV
182

×
UNCOV
183
                // Sort crossings and save the crossing number.
×
UNCOV
184
                var sortedCrossings = [];
×
UNCOV
185
                for ( const key in crossings ) {
×
UNCOV
186

×
UNCOV
187
                        sortedCrossings.push( crossings[ key ] );
×
UNCOV
188

×
UNCOV
189
                }
×
UNCOV
190

×
UNCOV
191
                // Sort numerically.
×
UNCOV
192
                sortedCrossings.sort( function ( a, b ) {
×
UNCOV
193

×
UNCOV
194
                        return a - b;
×
UNCOV
195

×
UNCOV
196
                } );
×
UNCOV
197
                for ( var key in crossings ) {
×
UNCOV
198

×
UNCOV
199
                        const value = crossings[ key ];
×
UNCOV
200
                        crossings[ key ] = {
×
UNCOV
201
                                value: value,
×
UNCOV
202
                                number: sortedCrossings.indexOf( value ),
×
UNCOV
203
                        };
×
UNCOV
204

×
UNCOV
205
                }
×
UNCOV
206

×
UNCOV
207
                // Walk the shape and assemble pieces from matched crossings.
×
UNCOV
208
                const shapes = [];
×
UNCOV
209
                // A list of crossing numbers that will close each shape in activeShapes.
×
UNCOV
210
                const pendingCrossbacks = [];
×
UNCOV
211
                const activeShapes = [];
×
UNCOV
212
                // The crossing number that will close the current shape.
×
UNCOV
213
                var activeCrossing = - 1;
×
UNCOV
214
                var currentShape = new Shape();
×
UNCOV
215
                for ( let i = 0; i < points.length; i ++ ) {
×
UNCOV
216

×
NEW
217
                        const point = points[ i ];
×
UNCOV
218
                        if ( i === 0 ) {
×
UNCOV
219

×
UNCOV
220
                                currentShape.moveTo( point[ 0 ], point[ 1 ] );
×
UNCOV
221

×
UNCOV
222
                        } else {
×
UNCOV
223

×
UNCOV
224
                                currentShape.lineTo( point[ 0 ], point[ 1 ] );
×
UNCOV
225

×
UNCOV
226
                        }
×
UNCOV
227

×
UNCOV
228
                        if ( i in crossings ) {
×
UNCOV
229

×
NEW
230
                                const crossing = crossings[ i ];
×
UNCOV
231
                                if ( crossing.value !== point[ 0 ] ) {
×
UNCOV
232

×
UNCOV
233
                                        currentShape.lineTo( crossing.value, 0 );
×
UNCOV
234

×
UNCOV
235
                                }
×
UNCOV
236

×
UNCOV
237
                                // If we can finalize the current shape.
×
UNCOV
238
                                if ( crossing.number === activeCrossing ) {
×
UNCOV
239

×
UNCOV
240
                                        shapes.push( currentShape );
×
UNCOV
241
                                        currentShape = activeShapes.pop();
×
UNCOV
242
                                        activeCrossing = pendingCrossbacks.pop();
×
UNCOV
243
                                        currentShape.lineTo( crossing.value, 0 );
×
UNCOV
244

×
UNCOV
245
                                } else {
×
UNCOV
246

×
UNCOV
247
                                        activeShapes.push( currentShape );
×
UNCOV
248
                                        pendingCrossbacks.push( activeCrossing );
×
UNCOV
249
                                        currentShape = new Shape();
×
UNCOV
250
                                        // crossing number is zero indexed.
×
UNCOV
251
                                        // If it is even, it closes at the next nuber, odd closes at the previous value.
×
UNCOV
252
                                        // 0=>1, 1=>0, 5=>4
×
UNCOV
253
                                        activeCrossing = crossing.number + 2 * ( ( crossing.number + 1 ) % 2 ) - 1;
×
UNCOV
254
                                        currentShape.moveTo( crossing.value, 0 );
×
UNCOV
255

×
UNCOV
256
                                }
×
UNCOV
257

×
258
                        }
×
259

×
260
                }
×
261

×
262
                shapes.push( currentShape );
×
263
                shapes.unshift( newOutline );
×
NEW
264

×
NEW
265
                for ( const i in shapes ) {
×
NEW
266

×
NEW
267
                        shapes[ i ] = new Shape( WedgeGeometry.cleanInputs( shapes[ i ] ).shape );
×
NEW
268

×
NEW
269
                }
×
NEW
270

×
UNCOV
271
                return shapes;
×
UNCOV
272

×
273
        }
74✔
274

1✔
275
        /**
1✔
276
         * Given a set of points, add points for each time it crosses the x axis
1✔
277
         * and return the new shape along with a list of crossing indicies
1✔
278
         * @param {} points - The points
1✔
279
         * @returns {}
1✔
280
         */
1✔
281
        static getCrossings( points ) {
1✔
282

76✔
283
                // An associative array of all the values where the shape crosses the x axis, keyed by segment number.
76✔
284
                const crossings = [];
76✔
285

76✔
286
                // The new outline point, original with the addition of any crossing points.
76✔
287
                /** @type {Vector2[]} */
76✔
288
                const newOutline = [];
76✔
289

76✔
290
                // Walk the shape and find all crossings.
76✔
291
                for ( let i = 0; i < points.length; i ++ ) {
76✔
292

1,256✔
293
                        const point = points[ i ];
1,256✔
294
                        newOutline.push( new Vector2( ...point ) );
1,256✔
295

1,256✔
296
                        const prevPoint = points[ ( i - 1 + points.length ) % points.length ];
1,256✔
297
                        const nextPoint = points[ ( i + 1 ) % points.length ];
1,256✔
298
                        const pointOnLine = point[ 1 ] === 0;
1,256✔
299
                        const sameSides = ( prevPoint[ 1 ] > 0 ) === ( nextPoint[ 1 ] > 0 );
1,256✔
300
                        const switchesSides = ( point[ 1 ] > 0 ) !== ( nextPoint[ 1 ] > 0 );
1,256✔
301
                        // if this point is the crossing point between the previous and next points
1,256✔
302
                        // or the line is crossed between this point and the next
1,256✔
303
                        if ( ( pointOnLine && ! sameSides ) || switchesSides ) {
1,256!
304

70✔
305
                                var crossing;
70✔
306
                                if ( pointOnLine || nextPoint[ 0 ] === point[ 0 ] ) {
70✔
307

70✔
308
                                        crossing = point[ 0 ];
70✔
309

70✔
310
                                } else {
70!
NEW
311

×
NEW
312
                                        const m = ( nextPoint[ 1 ] - point[ 1 ] ) / ( nextPoint[ 0 ] - point[ 0 ] );
×
NEW
313
                                        crossing = point[ 0 ] - point[ 1 ] / m;
×
NEW
314

×
NEW
315
                                }
×
316

70✔
317
                                crossings[ i ] = crossing;
70✔
318
                                if ( ! pointOnLine ) {
70✔
319

70✔
320
                                        newOutline.push( new Vector2( crossing, 0 ) );
70✔
321

70✔
322
                                }
70✔
323

70✔
324
                        }
70✔
325

1,256✔
326
                }
1,256✔
327

76✔
328
                return { crossings: crossings, newOutline: new Shape( newOutline ) };
76✔
329

76✔
330
        }
76✔
331

1✔
332
        /**
1✔
333
        *
1✔
334
        */
1✔
335
        move( point ) {
1✔
336

1,240✔
337
                const angle = this.parameters.options.angle;
1,240✔
338
                const center = this.parameters.options.center;
1,240✔
339
                const pointX = ( point.x - center[ 0 ] ) * Math.cos( angle ) - ( point.y - center[ 1 ] ) * Math.sin( angle );
1,240✔
340
                const pointY = ( point.x - center[ 0 ] ) * Math.sin( angle ) + ( point.y - center[ 1 ] ) * Math.cos( angle );
1,240✔
341
                return [ pointX, pointY ];
1,240✔
342

1,240✔
343
        }
1,240✔
344

1✔
345
        /**
1✔
346
        *
1✔
347
        */
1✔
348
        unMove( point ) {
1✔
349

26,376✔
350
                const angle = this.parameters.options.angle;
26,376✔
351
                const center = this.parameters.options.center;
26,376✔
352
                const pointX = point[ 0 ] * Math.cos( angle ) + point[ 1 ] * Math.sin( angle ) + center[ 0 ];
26,376✔
353
                const pointY = - 1 * point[ 0 ] * Math.sin( angle ) + point[ 1 ] * Math.cos( angle ) + center[ 1 ];
26,376✔
354
                return [ pointX, pointY ];
26,376✔
355

26,376✔
356
        }
26,376✔
357

1✔
358
        /**
1✔
359
         * Ensure start end duplicates are removed fron shape and holes, and that the shares are oriented correctly.
1✔
360
         * modifies this.parameters.shape
1✔
361
         * @returns {Vector2[], Vector2[][]}
1✔
362
         */
1✔
363
        static cleanInputs( shape ) {
1✔
364

75✔
365
                // Get the outer shape and holes.
75✔
366
                const points = shape.extractPoints().shape;
75✔
367

75✔
368
                if ( points[ 0 ].equals( points[ points.length - 1 ] ) ) {
75✔
369

29✔
370
                        points.pop();
29✔
371

29✔
372
                }
29✔
373

75✔
374
                var holes = shape.extractPoints().holes;
75✔
375

75✔
376
                // Ensuse all paths are in the correct direction for the normals
75✔
377
                const reverse = ! ShapeUtils.isClockWise( points );
75✔
378
                if ( reverse ) {
75✔
379

44✔
380
                        points.reverse();
44✔
381

44✔
382
                }
44✔
383

75✔
384
                // Check that any holes are correct direction.
75✔
385
                for ( const hole of holes ) {
75!
NEW
386

×
NEW
387
                        if ( hole[ 0 ].equals( hole[ hole.length - 1 ] ) ) {
×
NEW
388

×
NEW
389
                                hole.pop();
×
NEW
390

×
NEW
391
                        }
×
NEW
392

×
NEW
393
                        if ( ShapeUtils.isClockWise( hole ) ) {
×
NEW
394

×
NEW
395
                                hole.reverse();
×
NEW
396

×
NEW
397
                        }
×
NEW
398

×
NEW
399
                }
×
400

75✔
401
                return { shape: points, holes: holes };
75✔
402

75✔
403
        }
75✔
404

1✔
405
        static fromJSON( data, shape ) {
1✔
406

8✔
407
                return new WedgeGeometry( shape, data.options );
8✔
408

8✔
409
        }
8✔
410

1✔
411
}
1✔
412
export { WedgeGeometry };
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc