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

Beakerboy / Threejs-Geometries / 16448331444

22 Jul 2025 03:11PM UTC coverage: 60.31% (-26.5%) from 86.809%
16448331444

push

github

web-flow
Create SlicedGeometry.js

82 of 89 branches covered (92.13%)

Branch coverage included in aggregate %.

0 of 412 new or added lines in 1 file covered. (0.0%)

734 of 1264 relevant lines covered (58.07%)

396.26 hits per line

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

0.0
/src/SlicedGeometry.js
NEW
1
import {
×
NEW
2
        BufferGeometry,
×
NEW
3
        Vector2,
×
NEW
4
        Shape,
×
NEW
5
        ShapeUtils,
×
NEW
6
        BufferAttribute,
×
NEW
7
} from 'three';
×
NEW
8

×
NEW
9
/**
×
NEW
10
 * Modify ExtrudeGeometry such that z varies with x and y
×
NEW
11
 */
×
NEW
12
class SlicedGeometry extends BufferGeometry {
×
NEW
13

×
NEW
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 = {} ) {
×
NEW
15

×
NEW
16
                super();
×
NEW
17
                this.type = 'SlicedGeometry';
×
NEW
18
                this.parameters = {
×
NEW
19
                        shape: shape,
×
NEW
20
                        options: options,
×
NEW
21
                };
×
NEW
22

×
NEW
23
                // The max depth of the geometry
×
NEW
24
                var depth = options.depth;
×
NEW
25

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

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

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

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

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

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

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

×
NEW
53
                this.newShapes = WedgeGeometry.splitShape( newPoints );
×
NEW
54

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

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

×
NEW
69
                                const face = faces[ i ];
×
NEW
70
                                for ( let j = 0; j < 3; j ++ ) {
×
NEW
71

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

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

×
NEW
80
                                        } else {
×
NEW
81

×
NEW
82
                                                z = depth - depth / minY * points[ face[ j ] ].y;
×
NEW
83

×
NEW
84
                                        }
×
NEW
85

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

×
NEW
89
                                }
×
NEW
90

×
NEW
91
                        }
×
NEW
92

×
NEW
93
                }
×
NEW
94

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

×
NEW
101
                        const face = faces[ i ];
×
NEW
102
                        for ( let j = 0; j < 3; j ++ ) {
×
NEW
103

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

×
NEW
109
                        }
×
NEW
110

×
NEW
111
                }
×
NEW
112

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

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

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

×
NEW
122
                        if ( pointZ !== 0 ) {
×
NEW
123

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

×
NEW
128
                        }
×
NEW
129

×
NEW
130
                        if ( nextPointZ !== 0 ) {
×
NEW
131

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

×
NEW
136
                        }
×
NEW
137

×
NEW
138
                }
×
NEW
139

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

×
NEW
143
        }
×
NEW
144

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

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

×
NEW
159
                if ( Object.keys( crossings ).length === 0 ) {
×
NEW
160

×
NEW
161
                        return [ newOutline ];
×
NEW
162

×
NEW
163
                }
×
NEW
164

×
NEW
165
                if ( Object.keys( crossings ).length === 2 ) {
×
NEW
166

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

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

×
NEW
175
                        }
×
NEW
176

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

×
NEW
181
                }
×
NEW
182

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

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

×
NEW
189
                }
×
NEW
190

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

×
NEW
194
                        return a - b;
×
NEW
195

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

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

×
NEW
205
                }
×
NEW
206

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

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

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

×
NEW
222
                        } else {
×
NEW
223

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

×
NEW
226
                        }
×
NEW
227

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

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

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

×
NEW
235
                                }
×
NEW
236

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

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

×
NEW
245
                                } else {
×
NEW
246

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

×
NEW
256
                                }
×
NEW
257

×
NEW
258
                        }
×
NEW
259

×
NEW
260
                }
×
NEW
261

×
NEW
262
                shapes.push( currentShape );
×
NEW
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

×
NEW
271
                return shapes;
×
NEW
272

×
NEW
273
        }
×
NEW
274

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

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

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

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

×
NEW
293
                        const point = points[ i ];
×
NEW
294
                        newOutline.push( new Vector2( ...point ) );
×
NEW
295

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

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

×
NEW
308
                                        crossing = point[ 0 ];
×
NEW
309

×
NEW
310
                                } else {
×
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
                                }
×
NEW
316

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

×
NEW
320
                                        newOutline.push( new Vector2( crossing, 0 ) );
×
NEW
321

×
NEW
322
                                }
×
NEW
323

×
NEW
324
                        }
×
NEW
325

×
NEW
326
                }
×
NEW
327

×
NEW
328
                return { crossings: crossings, newOutline: new Shape( newOutline ) };
×
NEW
329

×
NEW
330
        }
×
NEW
331

×
NEW
332
        /**
×
NEW
333
        *
×
NEW
334
        */
×
NEW
335
        move( point ) {
×
NEW
336

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

×
NEW
343
        }
×
NEW
344

×
NEW
345
        /**
×
NEW
346
        *
×
NEW
347
        */
×
NEW
348
        unMove( point ) {
×
NEW
349

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

×
NEW
356
        }
×
NEW
357

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

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

×
NEW
368
                if ( points[ 0 ].equals( points[ points.length - 1 ] ) ) {
×
NEW
369

×
NEW
370
                        points.pop();
×
NEW
371

×
NEW
372
                }
×
NEW
373

×
NEW
374
                var holes = shape.extractPoints().holes;
×
NEW
375

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

×
NEW
380
                        points.reverse();
×
NEW
381

×
NEW
382
                }
×
NEW
383

×
NEW
384
                // Check that any holes are correct direction.
×
NEW
385
                for ( const hole of holes ) {
×
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
                }
×
NEW
400

×
NEW
401
                return { shape: points, holes: holes };
×
NEW
402

×
NEW
403
        }
×
NEW
404

×
NEW
405
        static fromJSON( data, shape ) {
×
NEW
406

×
NEW
407
                return new WedgeGeometry( shape, data.options );
×
NEW
408

×
NEW
409
        }
×
NEW
410

×
NEW
411
}
×
NEW
412
export { SlicedGeometry };
×
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