poly-extrude 0.15.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/readme.md CHANGED
@@ -21,6 +21,8 @@ Extrude polygons/polylines. Born in [maptalks.three](https://github.com/maptalks
21
21
  [![](./gallery/tube.png)](https://deyihu.github.io/poly-extrude/test/tube.html)<br>
22
22
  [![](./gallery/terrain.png)](https://deyihu.github.io/poly-extrude/test/terrain.html)<br>
23
23
 
24
+ [shape demo ](https://deyihu.github.io/poly-extrude/test/shape.html)<br>
25
+
24
26
  ## Install
25
27
 
26
28
  ### NPM
@@ -111,6 +113,7 @@ npm i poly-extrude
111
113
 
112
114
  * `polygons`
113
115
  * `options.depth`
116
+ * `options.top` Whether to display the top
114
117
 
115
118
  ```js
116
119
  const result = extrudePolygons(polygons, {
@@ -216,7 +219,6 @@ const {
216
219
  * `options.bottomStickGround` Is the bottom attached to the ground
217
220
  * `options.pathUV` generate Path UV
218
221
 
219
-
220
222
  [extrudeSlopes pathUV demo](https://deyihu.github.io/poly-extrude/test/slope-pathuv.html)
221
223
 
222
224
  ```js
package/src/index.ts CHANGED
@@ -1,13 +1,16 @@
1
- import { extrudePolygons } from './polygon';
1
+ import { extrudePolygons, polygons } from './polygon';
2
2
  import { extrudePolylines, expandLine, leftOnLine, extrudeSlopes } from './polyline';
3
3
  import { cylinder } from './cylinder';
4
4
  import { expandPaths } from './path';
5
5
  import { expandTubes } from './tube';
6
6
  import { plane } from './plane';
7
+ import { extrudePolygonsOnPath } from './polygonpath';
7
8
  import { isClockwise, merge } from './util';
8
9
  export {
9
10
  isClockwise, merge,
10
11
  extrudePolygons, extrudePolylines,
11
12
  extrudeSlopes, expandLine, leftOnLine,
12
- cylinder, expandPaths, expandTubes, plane
13
+ cylinder, expandPaths, expandTubes, plane,
14
+ extrudePolygonsOnPath,
15
+ polygons
13
16
  };
package/src/polygon.ts CHANGED
@@ -1,10 +1,11 @@
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 = {
7
- depth?: number
7
+ depth?: number,
8
+ top?: boolean
8
9
  }
9
10
 
10
11
  type PolygonsResult = ResultType & {
@@ -13,26 +14,13 @@ type PolygonsResult = ResultType & {
13
14
 
14
15
 
15
16
  export function extrudePolygons(polygons: Array<PolygonType>, options?: PolygonsOptions): PolygonsResult {
16
- options = Object.assign({}, { depth: 2 }, options);
17
+ options = Object.assign({}, { depth: 2, top: true }, options);
17
18
  const results = polygons.map(polygon => {
18
- for (let i = 0, len = polygon.length; i < len; i++) {
19
- const ring = polygon[i];
20
- validateRing(ring);
21
- if (i === 0) {
22
- if (!isClockwise(ring)) {
23
- polygon[i] = ring.reverse();
24
- }
25
- } else if (isClockwise(ring)) {
26
- polygon[i] = ring.reverse();
27
- }
28
- if (isClosedRing(ring)) {
29
- ring.splice(ring.length - 1, 1);
30
- }
31
- }
19
+ validatePolygon(polygon);
32
20
  const result = flatVertices(polygon, options) as Record<string, any>;
33
21
  result.polygon = polygon;
34
22
  const triangles = earcut(result.flatVertices, result.holes, 2);
35
- generateTopAndBottom(result, triangles);
23
+ generateTopAndBottom(result, triangles, options);
36
24
  generateSides(result, options);
37
25
  result.position = new Float32Array(result.points);
38
26
  result.indices = new Uint32Array(result.indices);
@@ -46,18 +34,24 @@ export function extrudePolygons(polygons: Array<PolygonType>, options?: Polygons
46
34
 
47
35
  }
48
36
 
49
- function generateTopAndBottom(result, triangles) {
37
+ function generateTopAndBottom(result, triangles, options: PolygonsOptions) {
50
38
  const indices: number[] = [];
51
39
  const { count } = result;
40
+ const top = options.top;
52
41
  for (let i = 0, len = triangles.length; i < len; i += 3) {
53
42
  // top
54
43
  const a = triangles[i], b = triangles[i + 1], c = triangles[i + 2];
55
- indices[i] = a;
56
- indices[i + 1] = b;
57
- indices[i + 2] = c;
44
+ if (top) {
45
+ indices[i] = a;
46
+ indices[i + 1] = b;
47
+ indices[i + 2] = c;
48
+ }
58
49
  // bottom
59
- const idx = len + i;
50
+ let idx = len + i;
60
51
  const a1 = count + a, b1 = count + b, c1 = count + c;
52
+ if (!top) {
53
+ idx = i;
54
+ }
61
55
  indices[idx] = a1;
62
56
  indices[idx + 1] = b1;
63
57
  indices[idx + 2] = c1;
@@ -112,16 +106,6 @@ function generateSides(result, options) {
112
106
  }
113
107
  }
114
108
 
115
- function calPolygonPointsCount(polygon) {
116
- let count = 0;
117
- let i = 0;
118
- const len = polygon.length;
119
- while (i < len) {
120
- count += (polygon[i].length);
121
- i++;
122
- }
123
- return count;
124
- }
125
109
 
126
110
  function flatVertices(polygon, options) {
127
111
  const count = calPolygonPointsCount(polygon);
@@ -176,14 +160,55 @@ function flatVertices(polygon, options) {
176
160
 
177
161
  }
178
162
 
179
- function validateRing(ring: PolylineType) {
180
- if (!isClosedRing(ring)) {
181
- 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
182
193
  }
183
194
  }
184
195
 
185
- function isClosedRing(ring: PolylineType) {
186
- const len = ring.length;
187
- const [x1, y1] = ring[0], [x2, y2] = ring[len - 1];
188
- 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
+
189
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];