poly-extrude 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/polygon.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import earcut from 'earcut';
3
- import { generateNormal, generateSideWallUV, isClockwise, merge } from './util';
3
+ import { generateNormal, generateSideWallUV, isClockwise, merge, isClosedRing, validateRing, calPolygonPointsCount, validatePolygon } from './util';
4
4
  import { PolylineType, PolygonType, ResultType } from './type';
5
5
 
6
6
  type PolygonsOptions = {
@@ -16,20 +16,7 @@ type PolygonsResult = ResultType & {
16
16
  export function extrudePolygons(polygons: Array<PolygonType>, options?: PolygonsOptions): PolygonsResult {
17
17
  options = Object.assign({}, { depth: 2, top: true }, options);
18
18
  const results = polygons.map(polygon => {
19
- for (let i = 0, len = polygon.length; i < len; i++) {
20
- const ring = polygon[i];
21
- validateRing(ring);
22
- if (i === 0) {
23
- if (!isClockwise(ring)) {
24
- polygon[i] = ring.reverse();
25
- }
26
- } else if (isClockwise(ring)) {
27
- polygon[i] = ring.reverse();
28
- }
29
- if (isClosedRing(ring)) {
30
- ring.splice(ring.length - 1, 1);
31
- }
32
- }
19
+ validatePolygon(polygon);
33
20
  const result = flatVertices(polygon, options) as Record<string, any>;
34
21
  result.polygon = polygon;
35
22
  const triangles = earcut(result.flatVertices, result.holes, 2);
@@ -119,16 +106,6 @@ function generateSides(result, options) {
119
106
  }
120
107
  }
121
108
 
122
- function calPolygonPointsCount(polygon) {
123
- let count = 0;
124
- let i = 0;
125
- const len = polygon.length;
126
- while (i < len) {
127
- count += (polygon[i].length);
128
- i++;
129
- }
130
- return count;
131
- }
132
109
 
133
110
  function flatVertices(polygon, options) {
134
111
  const count = calPolygonPointsCount(polygon);
@@ -183,14 +160,55 @@ function flatVertices(polygon, options) {
183
160
 
184
161
  }
185
162
 
186
- function validateRing(ring: PolylineType) {
187
- if (!isClosedRing(ring)) {
188
- ring.push(ring[0]);
163
+
164
+ function simplePolygon(polygon, options = {}) {
165
+ const flatVertices = [], holes = [];
166
+ let pIndex = -1, aIndex = -1, uvIndex = -1;
167
+ const points = [], uv = [];
168
+ for (let i = 0, len = polygon.length; i < len; i++) {
169
+ const ring = polygon[i];
170
+ if (i > 0) {
171
+ holes.push(flatVertices.length / 2);
172
+ }
173
+ for (let j = 0, len1 = ring.length; j < len1; j++) {
174
+ const c = ring[j];
175
+ flatVertices[++pIndex] = c[0];
176
+ flatVertices[++pIndex] = c[1];
177
+
178
+ points[++aIndex] = c[0];
179
+ points[++aIndex] = c[1];
180
+ points[++aIndex] = c[2] || 0;
181
+
182
+ uv[++uvIndex] = c[0];
183
+ uv[++uvIndex] = c[1];
184
+ }
185
+ }
186
+ const triangles = earcut(flatVertices, holes, 2);
187
+ const normal = generateNormal(triangles, points);
188
+ return {
189
+ normal,
190
+ uv,
191
+ points,
192
+ indices: triangles
189
193
  }
190
194
  }
191
195
 
192
- function isClosedRing(ring: PolylineType) {
193
- const len = ring.length;
194
- const [x1, y1] = ring[0], [x2, y2] = ring[len - 1];
195
- return (x1 === x2 && y1 === y2);
196
+ export function polygons(polygons, options = {}): PolygonsResult {
197
+ const results = polygons.map(polygon => {
198
+ validatePolygon(polygon);
199
+
200
+ const result = simplePolygon(polygon, options) as Record<string, any>;
201
+ result.polygon = polygon;
202
+ result.position = new Float32Array(result.points);
203
+ result.indices = new Uint32Array(result.indices);
204
+ result.uv = new Float32Array(result.uv);
205
+ result.normal = new Float32Array(result.normal);
206
+ return result;
207
+ });
208
+ const result = merge(results as Array<ResultType>) as PolygonsResult;
209
+ result.polygons = polygons;
210
+ return result;
211
+
212
+
213
+
196
214
  }
@@ -0,0 +1,315 @@
1
+ import { Vector3 } from './math/Vector3';
2
+ import { PathPoint } from './path/PathPoint';
3
+ import { PathPointList } from './path/PathPointList';
4
+ import { PolygonType, PolylineType, ResultType } from './type';
5
+ import { generateNormal, isClockwise, line2Vectors, merge, validateRing, isClosedRing, calPolygonPointsCount, getPolygonsBBOX, mergeArray } from './util';
6
+ import earcut from 'earcut';
7
+ const UP = new Vector3(0, 0, 1);
8
+ const normalDir = new Vector3();
9
+
10
+ type PolygonsOnPathOptions = {
11
+ extrudePath: PolylineType;
12
+ openEnd?: boolean;
13
+ openEndUV?: boolean;
14
+
15
+ }
16
+
17
+ type PolygonsOnPathResult = ResultType & {
18
+ polygons: Array<PolygonType>;
19
+ }
20
+
21
+
22
+ type Point = [number, number];
23
+
24
+ export function extrudePolygonsOnPath(polygons: Array<PolygonType>, options?: PolygonsOnPathOptions) {
25
+ options = Object.assign({}, { openEnd: false, openEndUV: true }, options);
26
+ const { extrudePath, openEnd } = options;
27
+ if (!extrudePath || !Array.isArray(extrudePath) || extrudePath.length < 2) {
28
+ console.error('extrudePath is error:', extrudePath);
29
+ return null;
30
+ }
31
+ const bbox = getPolygonsBBOX(polygons);
32
+ const [minx, miny, maxx, maxy] = bbox;
33
+ const center = [(minx + maxx) / 2, (miny + maxy) / 2] as Point;
34
+
35
+ const points = line2Vectors(extrudePath);
36
+ const pathPointList = new PathPointList();
37
+ //@ts-ignore
38
+ pathPointList.set(points, 0, options.cornerSplit, UP);
39
+
40
+ const results = polygons.map(polygon => {
41
+ for (let i = 0, len = polygon.length; i < len; i++) {
42
+ const ring = polygon[i];
43
+ validateRing(ring);
44
+ if (i === 0) {
45
+ if (isClockwise(ring)) {
46
+ polygon[i] = ring.reverse();
47
+ }
48
+ } else if (!isClockwise(ring)) {
49
+ polygon[i] = ring.reverse();
50
+ }
51
+ }
52
+
53
+ const result = generatePolygonOnPathVertexData(pathPointList, polygon, center) as Record<string, any>;
54
+ if (!openEnd) {
55
+ generateStartAndEnd(result, polygon, options);
56
+ }
57
+ result.polygon = polygon;
58
+ result.position = new Float32Array(result.points);
59
+ result.indices = new Uint32Array(result.indices);
60
+ result.uv = new Float32Array(result.uv);
61
+ result.normal = new Float32Array(result.normal);
62
+ return result;
63
+ });
64
+ const result = merge(results as Array<ResultType>) as PolygonsOnPathResult;
65
+ result.polygons = polygons;
66
+ return result;
67
+ }
68
+
69
+
70
+ function getAngle(c1: Point, c2: Point) {
71
+ const [x1, y1] = c1;
72
+ const [x2, y2] = c2;
73
+ const dy = y2 - y1;
74
+ const dx = x2 - x1;
75
+ return Math.atan2(dy, dx);
76
+ }
77
+
78
+
79
+ function transformPolygon(polygon: PolygonType, center: Point) {
80
+ const [cx, cy] = center;
81
+ const list = [];
82
+ polygon.forEach((ring, rIndex) => {
83
+ const data = [];
84
+ let totalDistance = 0;
85
+ let tempPoint;
86
+ for (let i = 0, len = ring.length; i < len; i++) {
87
+ const p = ring[i];
88
+ const x1 = p[0], y1 = p[1];
89
+ const offsetx = x1 - cx, offsety = y1 - cy;
90
+ let distance = 0;
91
+ if (i > 0) {
92
+ const x2 = tempPoint[0], y2 = tempPoint[1];
93
+ const dx = x2 - x1, dy = y2 - y1;
94
+ distance = Math.sqrt(dx * dx + dy * dy) + totalDistance;
95
+ totalDistance = distance;
96
+ }
97
+ data[i] = {
98
+ // dx,
99
+ // dy,
100
+ // dz: 0,
101
+ distance,
102
+ radius: Math.sqrt(offsetx * offsetx + offsety * offsety),
103
+ angle: getAngle(center, p as Point)
104
+ }
105
+ tempPoint = p;
106
+ }
107
+ list[rIndex] = {
108
+ ring: data,
109
+ ringLen: totalDistance
110
+ };
111
+ });
112
+ return list;
113
+ }
114
+
115
+ const TEMP_VECTOR3 = new Vector3(0, 0, 0);
116
+ // Vertex Data Generate Functions
117
+ // code copy from https://github.com/shawn0326/three.path/blob/master/src/PathGeometry.js
118
+ function generatePolygonOnPathVertexData(pathPointList, polygon: PolygonType, center: Point) {
119
+ const tpolygon = transformPolygon(polygon, center);
120
+ // let count = 0;
121
+ // modify data
122
+ const points: number[] = [];
123
+ const normal: number[] = [];
124
+ const uv: number[] = [];
125
+ // const uv2 = [];
126
+ const indices: number[] = [];
127
+ let verticesCount = 0;
128
+
129
+ let pIndex = -1;
130
+ let nIndex = -1;
131
+ let uIndex = -1;
132
+ let iIndex = -1;
133
+
134
+ const startPoints = [], endPoints = [];
135
+
136
+ function addVertices(pathPoint, ring, ringLen, first, end) {
137
+ const uvDist = pathPoint.dist / ringLen;
138
+ const radialSegments = ring.length;
139
+ // const startRad = ring[0].angle;
140
+
141
+ for (let i = 0; i < radialSegments; i++) {
142
+ const item = ring[i];
143
+ if (!item) {
144
+ continue;
145
+ }
146
+ const isLast = i === radialSegments - 1;
147
+ const angle = item.angle;
148
+ const radius = item.radius;
149
+ const distance = item.distance;
150
+ normalDir.copy(pathPoint.up).applyAxisAngle(pathPoint.dir, angle).normalize();
151
+ const v = TEMP_VECTOR3.copy(pathPoint.up);
152
+ v.applyAxisAngle(pathPoint.dir, angle);
153
+ v.x *= radius;
154
+ v.y *= radius;
155
+ v.z *= radius;
156
+
157
+ points[++pIndex] = pathPoint.pos.x + v.x;
158
+ points[++pIndex] = pathPoint.pos.y + v.y;
159
+ points[++pIndex] = pathPoint.pos.z + v.z;
160
+
161
+ // if (i === 0 || i === radialSegments - 1) {
162
+ // console.log(i, radialSegments, v.x, v.y, v.z);
163
+ // }
164
+
165
+
166
+ normal[++nIndex] = normalDir.x;
167
+ normal[++nIndex] = normalDir.y;
168
+ normal[++nIndex] = normalDir.z;
169
+
170
+ uv[++uIndex] = uvDist;
171
+ uv[++uIndex] = distance / ringLen;
172
+
173
+ verticesCount++;
174
+
175
+ if (first && !isLast) {
176
+ let index = startPoints.length - 1;
177
+ startPoints[++index] = pathPoint.pos.x + v.x;
178
+ startPoints[++index] = pathPoint.pos.y + v.y;
179
+ startPoints[++index] = pathPoint.pos.z + v.z;
180
+ }
181
+ if (end && !isLast) {
182
+ let index = endPoints.length - 1;
183
+ endPoints[++index] = pathPoint.pos.x + v.x;
184
+ endPoints[++index] = pathPoint.pos.y + v.y;
185
+ endPoints[++index] = pathPoint.pos.z + v.z;
186
+ }
187
+ }
188
+
189
+ if (!first) {
190
+ const begin1 = verticesCount - (radialSegments) * 2;
191
+ const begin2 = verticesCount - (radialSegments);
192
+
193
+ for (let i = 0; i < radialSegments; i++) {
194
+ indices[++iIndex] = begin2 + i;
195
+ indices[++iIndex] = begin1 + i;
196
+ indices[++iIndex] = begin1 + i + 1;
197
+ indices[++iIndex] = begin2 + i;
198
+ indices[++iIndex] = begin1 + i + 1;
199
+ indices[++iIndex] = begin2 + i + 1;
200
+ }
201
+ }
202
+ }
203
+
204
+ const polygonLen = tpolygon[0].ringLen;
205
+ tpolygon.forEach(item => {
206
+ for (let i = 0; i < pathPointList.count; i++) {
207
+ const pathPoint = pathPointList.array[i];
208
+ const { ring, ringLen } = item;
209
+ addVertices(pathPoint, ring, ringLen, i === 0, i === pathPointList.count - 1);
210
+
211
+ }
212
+ });
213
+
214
+
215
+ return {
216
+ points,
217
+ normal,
218
+ uv,
219
+ // uv2,
220
+ indices,
221
+ startPoints,
222
+ endPoints,
223
+ polygonLen
224
+ // count
225
+ };
226
+ }
227
+
228
+
229
+ function generateStartAndEnd(result, polygon, options) {
230
+ const { openEndUV } = options;
231
+ for (let i = 0, len = polygon.length; i < len; i++) {
232
+ const ring = polygon[i];
233
+ if (isClosedRing(ring)) {
234
+ ring.splice(ring.length - 1, 1);
235
+ }
236
+ }
237
+ const pointCount = calPolygonPointsCount(polygon);
238
+
239
+ const flatVertices = [], holes = [];
240
+ let pIndex = -1;
241
+ for (let i = 0, len = polygon.length; i < len; i++) {
242
+ const ring = polygon[i];
243
+ if (i > 0) {
244
+ holes.push(flatVertices.length / 2);
245
+ }
246
+ for (let j = 0, len1 = ring.length; j < len1; j++) {
247
+ const c = ring[j];
248
+ flatVertices[++pIndex] = c[0];
249
+ flatVertices[++pIndex] = c[1];
250
+ }
251
+ }
252
+ const triangles = earcut(flatVertices, holes, 2);
253
+ const { points, normal, uv, indices, startPoints, endPoints, polygonLen } = result;
254
+
255
+ pIndex = 0;
256
+ let uIndex = 0;
257
+ const aPoints1 = [], auv1 = [], aPoints2 = [], auv2 = [];
258
+
259
+
260
+ for (let i = 0; i < pointCount; i++) {
261
+ const idx = i * 3;
262
+ const x = startPoints[idx];
263
+ const y = startPoints[idx + 1];
264
+ const z = startPoints[idx + 2];
265
+
266
+ aPoints1[pIndex] = x;
267
+ aPoints1[pIndex + 1] = y;
268
+ aPoints1[pIndex + 2] = z;
269
+ if (openEndUV) {
270
+ auv1[uIndex] = y / polygonLen;
271
+ auv1[uIndex + 1] = z / polygonLen;
272
+ } else {
273
+ auv1[uIndex] = 0;
274
+ auv1[uIndex + 1] = 0;
275
+ }
276
+
277
+ const x1 = endPoints[idx];
278
+ const y1 = endPoints[idx + 1];
279
+ const z1 = endPoints[idx + 2];
280
+
281
+ aPoints2[pIndex] = x1;
282
+ aPoints2[pIndex + 1] = y1;
283
+ aPoints2[pIndex + 2] = z1;
284
+ if (openEndUV) {
285
+ auv2[uIndex] = y1 / polygonLen;
286
+ auv2[uIndex + 1] = z1 / polygonLen;
287
+ } else {
288
+ auv2[uIndex] = 0;
289
+ auv2[uIndex + 1] = 0;
290
+ }
291
+
292
+ pIndex += 3;
293
+ uIndex += 2;
294
+
295
+ }
296
+
297
+ const indexOffset = points.length / 3;
298
+ const indexs = [];
299
+ for (let i = 0, len = triangles.length; i < len; i++) {
300
+ indexs[i] = triangles[i] + indexOffset;
301
+ indexs[i + len] = triangles[i] + indexOffset + pointCount;
302
+ }
303
+
304
+ const anormal1 = generateNormal(triangles, aPoints1) as any;
305
+ const anormal2 = generateNormal(triangles, aPoints2) as any;
306
+ mergeArray(points, aPoints1);
307
+ mergeArray(points, aPoints2);
308
+ mergeArray(uv, auv1);
309
+ mergeArray(uv, auv2);
310
+ mergeArray(normal, anormal1);
311
+ mergeArray(normal, anormal2);
312
+ mergeArray(indices, indexs);
313
+ }
314
+
315
+
package/src/util.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { Vector3 } from './math/Vector3';
2
- import { PolylineType, ResultType } from './type';
2
+ import { PolygonType, PolylineType, ResultType } from './type';
3
3
 
4
+ export function mergeArray(array1, array2) {
5
+ let index = array1.length - 1;
6
+ for (let i = 0, len = array2.length; i < len; i++) {
7
+ array1[++index] = array2[i];
8
+ }
9
+ }
4
10
  /**
5
11
  * https://github.com/Turfjs/turf/blob/master/packages/turf-boolean-clockwise/index.ts
6
12
  * @param {*} ring
@@ -22,6 +28,67 @@ export function isClockwise(ring: PolylineType) {
22
28
  return sum > 0;
23
29
  }
24
30
 
31
+
32
+ export function validateRing(ring: PolylineType) {
33
+ if (!isClosedRing(ring)) {
34
+ ring.push(ring[0]);
35
+ }
36
+ }
37
+
38
+ export function isClosedRing(ring: PolylineType) {
39
+ const len = ring.length;
40
+ const [x1, y1] = ring[0], [x2, y2] = ring[len - 1];
41
+ return (x1 === x2 && y1 === y2);
42
+ }
43
+
44
+ export function calPolygonPointsCount(polygon: PolygonType) {
45
+ let count = 0;
46
+ let i = 0;
47
+ const len = polygon.length;
48
+ while (i < len) {
49
+ count += (polygon[i].length);
50
+ i++;
51
+ }
52
+ return count;
53
+ }
54
+
55
+ export function getPolygonsBBOX(polygons, bbox?) {
56
+ bbox = bbox || [Infinity, Infinity, -Infinity, -Infinity];
57
+ for (let i = 0, len = polygons.length; i < len; i++) {
58
+ const p = polygons[i];
59
+ if (Array.isArray(p[0][0])) {
60
+ getPolygonsBBOX(p, bbox);
61
+ } else {
62
+ for (let j = 0, len1 = p.length; j < len1; j++) {
63
+ const c = p[j];
64
+ const [x, y] = c;
65
+ bbox[0] = Math.min(bbox[0], x);
66
+ bbox[1] = Math.min(bbox[1], y);
67
+ bbox[2] = Math.max(bbox[2], x);
68
+ bbox[3] = Math.max(bbox[3], y);
69
+ }
70
+ }
71
+ }
72
+ return bbox;
73
+ }
74
+
75
+ export function validatePolygon(polygon: PolygonType) {
76
+ for (let i = 0, len = polygon.length; i < len; i++) {
77
+ const ring = polygon[i];
78
+ validateRing(ring);
79
+ if (i === 0) {
80
+ if (!isClockwise(ring)) {
81
+ polygon[i] = ring.reverse();
82
+ }
83
+ } else if (isClockwise(ring)) {
84
+ polygon[i] = ring.reverse();
85
+ }
86
+ if (isClosedRing(ring)) {
87
+ ring.splice(ring.length - 1, 1);
88
+ }
89
+ }
90
+ }
91
+
25
92
  function v3Sub(out, v1, v2) {
26
93
  out[0] = v1[0] - v2[0];
27
94
  out[1] = v1[1] - v2[1];