pts 0.12.9 → 1.0.1
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 +92 -80
- package/dist/index.d.mts +6488 -1245
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +6488 -1245
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10733 -10668
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10684 -10600
- package/dist/index.mjs.map +1 -0
- package/dist/pts.js +10868 -10768
- package/dist/pts.js.map +1 -0
- package/dist/pts.min.js +3 -5
- package/dist/pts.min.js.map +1 -0
- package/package.json +92 -31
- package/src/Canvas.ts +1737 -0
- package/src/Color.ts +1109 -0
- package/src/Create.ts +1834 -0
- package/src/Dom.ts +940 -0
- package/src/Form.ts +312 -0
- package/src/Image.ts +722 -0
- package/src/LinearAlgebra.ts +530 -0
- package/src/Num.ts +1093 -0
- package/src/Op.ts +2442 -0
- package/src/Physics.ts +1233 -0
- package/src/Play.ts +861 -0
- package/src/Pt.ts +1310 -0
- package/src/Space.ts +898 -0
- package/src/Svg.ts +1652 -0
- package/src/Types.ts +318 -0
- package/src/Typography.ts +228 -0
- package/src/UI.ts +757 -0
- package/src/Util.ts +454 -0
- package/src/_module.ts +18 -0
- package/src/_path.ts +1402 -0
- package/src/_script.ts +94 -0
- package/src/_triangulate.ts +925 -0
- package/src/uheprng.ts +153 -0
package/src/Pt.ts
ADDED
|
@@ -0,0 +1,1310 @@
|
|
|
1
|
+
/*! Pts.js is licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
|
|
2
|
+
|
|
3
|
+
import { Util, Const } from "./Util";
|
|
4
|
+
import { Geom, Num } from "./Num";
|
|
5
|
+
import { Vec, Mat } from "./LinearAlgebra";
|
|
6
|
+
import {
|
|
7
|
+
type IPt,
|
|
8
|
+
type GroupLike,
|
|
9
|
+
type PtLike,
|
|
10
|
+
type PtIterable,
|
|
11
|
+
type PtLikeIterable,
|
|
12
|
+
} from "./Types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pt is a subclass of standard [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) with additional properties and functions to support vector and geometric calculations.
|
|
16
|
+
* See [Pt guide](../guide/Pt-0200.html) for details.
|
|
17
|
+
*/
|
|
18
|
+
export class Pt extends Float32Array implements IPt, Iterable<number> {
|
|
19
|
+
protected _id!: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a Pt. If no parameter is provided, this will instantiate a Pt with 2 dimensions [0, 0].
|
|
23
|
+
* Note that `new Pt(3)` will only instantiate Pt with length of 3 (ie, same as `new Float32Array(3)` ). If you need a Pt with 1 dimension of value 3, use `new Pt([3])`.
|
|
24
|
+
* @example `new Pt()`, `new Pt(1,2,3,4,5)`, `new Pt([1,2])`, `new Pt({x:0, y:1})`, `new Pt(pt)`
|
|
25
|
+
* @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties
|
|
26
|
+
*/
|
|
27
|
+
constructor(...args: Array<number | number[] | IPt | Float32Array>) {
|
|
28
|
+
// each branch below produces a valid Float32Array constructor argument
|
|
29
|
+
let params: any;
|
|
30
|
+
const a0 = args[0];
|
|
31
|
+
if (args.length === 1 && typeof a0 == "number") {
|
|
32
|
+
params = a0; // init with the TypedArray's length. Needed this in order to make ".map", ".slice" etc work.
|
|
33
|
+
} else if (args.length === 0) {
|
|
34
|
+
params = 2; // default is [0, 0]
|
|
35
|
+
} else if (
|
|
36
|
+
args.length === 1 &&
|
|
37
|
+
(Array.isArray(a0) || ArrayBuffer.isView(a0))
|
|
38
|
+
) {
|
|
39
|
+
params = a0; // the Float32Array constructor copies array-likes natively
|
|
40
|
+
} else if (typeof a0 === "number") {
|
|
41
|
+
params = args; // a list of numbers; rest args are already a fresh array
|
|
42
|
+
} else {
|
|
43
|
+
params = Util.getArgs(args);
|
|
44
|
+
}
|
|
45
|
+
super(params);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create an n-dimensional Pt with either default value or random values.
|
|
50
|
+
* @param dimensions number of dimensions
|
|
51
|
+
* @param defaultValue optional default value to fill the dimensions
|
|
52
|
+
* @param randomize if `true`, randomize the value between 0 to default value
|
|
53
|
+
*/
|
|
54
|
+
static make(
|
|
55
|
+
dimensions: number,
|
|
56
|
+
defaultValue: number = 0,
|
|
57
|
+
randomize: boolean = false,
|
|
58
|
+
): Pt {
|
|
59
|
+
const p = new Pt(dimensions);
|
|
60
|
+
if (defaultValue) p.fill(defaultValue);
|
|
61
|
+
if (randomize) {
|
|
62
|
+
for (let i = 0, len = p.length; i < len; i++) {
|
|
63
|
+
p[i] = p[i] * Num.random();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* ID string of this Pt
|
|
71
|
+
*/
|
|
72
|
+
get id(): string {
|
|
73
|
+
return this._id;
|
|
74
|
+
}
|
|
75
|
+
set id(s: string) {
|
|
76
|
+
this._id = s;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Value in the first dimensional of this Pt
|
|
81
|
+
*/
|
|
82
|
+
get x(): number {
|
|
83
|
+
return this[0];
|
|
84
|
+
}
|
|
85
|
+
set x(n: number) {
|
|
86
|
+
this[0] = n;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Value in the second dimension of this Pt
|
|
91
|
+
*/
|
|
92
|
+
get y(): number {
|
|
93
|
+
return this[1];
|
|
94
|
+
}
|
|
95
|
+
set y(n: number) {
|
|
96
|
+
this[1] = n;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Value in the third dimension of this Pt
|
|
101
|
+
*/
|
|
102
|
+
get z(): number {
|
|
103
|
+
return this[2];
|
|
104
|
+
}
|
|
105
|
+
set z(n: number) {
|
|
106
|
+
this[2] = n;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Value in the forth dimension of this Pt
|
|
111
|
+
*/
|
|
112
|
+
get w(): number {
|
|
113
|
+
return this[3];
|
|
114
|
+
}
|
|
115
|
+
set w(n: number) {
|
|
116
|
+
this[3] = n;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clone this Pt and return it as a new Pt.
|
|
121
|
+
*/
|
|
122
|
+
clone(): Pt {
|
|
123
|
+
return new Pt(this);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if another Pt is equal to this Pt, within a threshold. Every dimension of this Pt must be matched: a shorter Pt, or one with a NaN dimension, is not equal (following IEEE semantics, NaN never equals NaN).
|
|
128
|
+
* @param p another Pt to compare with
|
|
129
|
+
* @param threshold a threshold value within which the two Pts are considered equal. Default is 0.000001.
|
|
130
|
+
*/
|
|
131
|
+
equals(p: PtLike, threshold = 0.000001): boolean {
|
|
132
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
133
|
+
// written so that a missing or NaN dimension fails the comparison
|
|
134
|
+
if (!(Math.abs(this[i] - p[i]) <= threshold)) return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Update the values of this Pt.
|
|
141
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
142
|
+
*/
|
|
143
|
+
to(...args: any[]): this {
|
|
144
|
+
const p = Util.getPtLike(args);
|
|
145
|
+
for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
|
|
146
|
+
this[i] = p[i];
|
|
147
|
+
}
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Like [`Pt.to`](#link) but returns a new Pt.
|
|
153
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
154
|
+
*/
|
|
155
|
+
$to(...args: any[]): Pt {
|
|
156
|
+
return this.clone().to(...args);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Update the values of this Pt to point at a specific angle.
|
|
161
|
+
* @param radian target angle in radian
|
|
162
|
+
* @param magnitude Optional magnitude if known. If not provided, it'll calculate and use this Pt's magnitude.
|
|
163
|
+
* @param anchorFromPt If `true`, add it from this Pt's current position. Default is `false` which update the position from origin (0,0). See also [`Geom.rotate2D`](#link) for rotating a point from another anchor point.
|
|
164
|
+
*/
|
|
165
|
+
toAngle(
|
|
166
|
+
radian: number,
|
|
167
|
+
magnitude?: number,
|
|
168
|
+
anchorFromPt: boolean = false,
|
|
169
|
+
): this {
|
|
170
|
+
const m = magnitude != undefined ? magnitude : this.magnitude();
|
|
171
|
+
const change = [Math.cos(radian) * m, Math.sin(radian) * m];
|
|
172
|
+
return anchorFromPt ? this.add(change) : this.to(change);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create an operation using this Pt, passing this Pt into a custom function's first parameter. See the [Op guide](../guide/Op-0400.html) for details.
|
|
177
|
+
* @param fn any function that takes a Pt as its first parameter
|
|
178
|
+
* @example `let myOp = pt.op( fn ); let result = myOp( [1,2,3] );`
|
|
179
|
+
* @returns a resulting function that takes other parameters required in `fn`
|
|
180
|
+
*/
|
|
181
|
+
op(fn: (p1: PtLike, ...rest: any[]) => any): (...rest: any[]) => any {
|
|
182
|
+
const self = this;
|
|
183
|
+
return (...params: any[]) => {
|
|
184
|
+
return fn(self, ...params);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* This combines a series of operations into an array. See the [Op guide](../guide/Op-0400.html) for details.
|
|
190
|
+
* @param fns an array of functions for `op`
|
|
191
|
+
* @example `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );`
|
|
192
|
+
* @returns an array of resulting functions
|
|
193
|
+
*/
|
|
194
|
+
ops(
|
|
195
|
+
fns: ((p1: PtLike, ...rest: any[]) => any)[],
|
|
196
|
+
): ((...rest: any[]) => any)[] {
|
|
197
|
+
const _ops = [];
|
|
198
|
+
for (let i = 0, len = fns.length; i < len; i++) {
|
|
199
|
+
_ops.push(this.op(fns[i]));
|
|
200
|
+
}
|
|
201
|
+
return _ops;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Take specific dimensional values from this Pt and create a new Pt.
|
|
206
|
+
* @param axis a string such as "xy" (use Const.xy) or an array to specify indices
|
|
207
|
+
*/
|
|
208
|
+
$take(axis: string | number[]): Pt {
|
|
209
|
+
const p = [];
|
|
210
|
+
for (let i = 0, len = axis.length; i < len; i++) {
|
|
211
|
+
p.push((this as any)[axis[i]] || 0);
|
|
212
|
+
}
|
|
213
|
+
return new Pt(p);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Concatenate this Pt with addition dimensional values and return as a new Pt.
|
|
218
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
219
|
+
*/
|
|
220
|
+
$concat(...args: any[]): Pt {
|
|
221
|
+
return new Pt(this.toArray().concat(Util.getArgs(args)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Add scalar or vector values to this Pt.
|
|
226
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
227
|
+
*/
|
|
228
|
+
add(...args: any[]): this {
|
|
229
|
+
args.length === 1 && typeof args[0] == "number"
|
|
230
|
+
? Vec.add(this, args[0])
|
|
231
|
+
: Vec.add(this, Util.getPtLike(args));
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Like [`Pt.add`](#link), but returns result as a new Pt.
|
|
237
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
238
|
+
*/
|
|
239
|
+
$add(...args: any[]): Pt {
|
|
240
|
+
return this.clone().add(...args);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Subtract scalar or vector values from this Pt.
|
|
245
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
246
|
+
*/
|
|
247
|
+
subtract(...args: any[]): this {
|
|
248
|
+
args.length === 1 && typeof args[0] == "number"
|
|
249
|
+
? Vec.subtract(this, args[0])
|
|
250
|
+
: Vec.subtract(this, Util.getPtLike(args));
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Like [`Pt.subtract`](#link), but returns result as a new Pt.
|
|
256
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
257
|
+
*/
|
|
258
|
+
$subtract(...args: any[]): Pt {
|
|
259
|
+
return this.clone().subtract(...args);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Multiply scalar or vector values (as element-wise) with this Pt.
|
|
264
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
265
|
+
*/
|
|
266
|
+
multiply(...args: any[]): this {
|
|
267
|
+
args.length === 1 && typeof args[0] == "number"
|
|
268
|
+
? Vec.multiply(this, args[0])
|
|
269
|
+
: Vec.multiply(this, Util.getPtLike(args));
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Like [`Pt.multiply`](#link), but returns result as a new Pt.
|
|
275
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
276
|
+
*/
|
|
277
|
+
$multiply(...args: any[]): Pt {
|
|
278
|
+
return this.clone().multiply(...args);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Divide this Pt over scalar or vector values (as element-wise).
|
|
283
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
284
|
+
*/
|
|
285
|
+
divide(...args: any[]): this {
|
|
286
|
+
args.length === 1 && typeof args[0] == "number"
|
|
287
|
+
? Vec.divide(this, args[0])
|
|
288
|
+
: Vec.divide(this, Util.getPtLike(args));
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Like [`Pt.divide`](#link), but returns result as a new Pt.
|
|
294
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
295
|
+
*/
|
|
296
|
+
$divide(...args: any[]): Pt {
|
|
297
|
+
return this.clone().divide(...args);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get the squared distance (magnitude) of this Pt from origin.
|
|
302
|
+
*/
|
|
303
|
+
magnitudeSq(): number {
|
|
304
|
+
return Vec.dot(this, this);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get the distance (magnitude) of this Pt from origin.
|
|
309
|
+
*/
|
|
310
|
+
magnitude(): number {
|
|
311
|
+
return Vec.magnitude(this);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Convert to a unit vector, which is a normalized vector whose magnitude equals to 1.
|
|
316
|
+
* @param magnitude Optional: if the magnitude is known, pass it as a parameter to avoid duplicate calculation.
|
|
317
|
+
*/
|
|
318
|
+
unit(magnitude: number | undefined = undefined): Pt {
|
|
319
|
+
Vec.unit(this, magnitude);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get a new unit vector from this Pt.
|
|
325
|
+
*/
|
|
326
|
+
$unit(magnitude: number | undefined = undefined): Pt {
|
|
327
|
+
return this.clone().unit(magnitude);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Dot product of this Pt and another Pt.
|
|
332
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
333
|
+
*/
|
|
334
|
+
dot(...args: any[]): number {
|
|
335
|
+
return Vec.dot(this, Util.getPtLike(args));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 2D Cross product of this Pt and another Pt. Return results as a new Pt.
|
|
340
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
341
|
+
*/
|
|
342
|
+
$cross2D(...args: any[]): number {
|
|
343
|
+
return Vec.cross2D(this, Util.getPtLike(args));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 3D Cross product of this Pt and another Pt. Return results as a new Pt.
|
|
348
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
349
|
+
*/
|
|
350
|
+
$cross(...args: any[]): Pt {
|
|
351
|
+
return Vec.cross(this, Util.getPtLike(args));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Calculate the vector projection of another Pt onto this Pt — ie, the component of the other Pt along this Pt's direction. Note that this Pt must be non-zero.
|
|
356
|
+
* @param args the other Pt, as either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
357
|
+
* @returns the projection vector as a Pt
|
|
358
|
+
*/
|
|
359
|
+
$project(...args: any[]): Pt {
|
|
360
|
+
return this.$multiply(this.dot(...args) / this.magnitudeSq());
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Calculate the scalar projection of another Pt onto this Pt — the signed length of the other Pt's component along this Pt's direction. Note that this Pt must be non-zero.
|
|
365
|
+
* @param args the other Pt, as either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
366
|
+
*/
|
|
367
|
+
projectScalar(...args: any[]): number {
|
|
368
|
+
return this.dot(...args) / this.magnitude();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Absolute values for all values in this pt.
|
|
373
|
+
*/
|
|
374
|
+
abs(): Pt {
|
|
375
|
+
Vec.abs(this);
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get a new Pt with absolute values of this Pt.
|
|
381
|
+
*/
|
|
382
|
+
$abs(): Pt {
|
|
383
|
+
return this.clone().abs();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Floor values for all values in this Pt.
|
|
388
|
+
*/
|
|
389
|
+
floor(): Pt {
|
|
390
|
+
Vec.floor(this);
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get a new Pt with floor values of this Pt.
|
|
396
|
+
*/
|
|
397
|
+
$floor(): Pt {
|
|
398
|
+
return this.clone().floor();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Ceiling values for all values in this Pt.
|
|
403
|
+
*/
|
|
404
|
+
ceil(): Pt {
|
|
405
|
+
Vec.ceil(this);
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get a new Pt with ceiling values of this Pt.
|
|
411
|
+
*/
|
|
412
|
+
$ceil(): Pt {
|
|
413
|
+
return this.clone().ceil();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Rounded values for all values in this Pt.
|
|
418
|
+
*/
|
|
419
|
+
round(): Pt {
|
|
420
|
+
Vec.round(this);
|
|
421
|
+
return this;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get a new Pt with rounded values of this Pt.
|
|
426
|
+
*/
|
|
427
|
+
$round(): Pt {
|
|
428
|
+
return this.clone().round();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Find the minimum value across all dimensions in this Pt.
|
|
433
|
+
* @returns an object with `value` and `index` which returns the minimum value and its dimensional index
|
|
434
|
+
*/
|
|
435
|
+
minValue(): { value: number; index: number } {
|
|
436
|
+
return Vec.min(this);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Find the maximum value across all dimensions in this Pt.
|
|
441
|
+
* @returns an object with `value` and `index` which returns the maximum value and its dimensional index
|
|
442
|
+
*/
|
|
443
|
+
maxValue(): { value: number; index: number } {
|
|
444
|
+
return Vec.max(this);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get a new Pt that has the minimum dimensional values of this Pt and another Pt.
|
|
449
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
450
|
+
*/
|
|
451
|
+
$min(...args: any[]): Pt {
|
|
452
|
+
const p = Util.getPtLike(args);
|
|
453
|
+
const m = this.clone();
|
|
454
|
+
for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
|
|
455
|
+
m[i] = Math.min(this[i], p[i]);
|
|
456
|
+
}
|
|
457
|
+
return m;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get a new Pt that has the maximum dimensional values of this Pt and another Pt.
|
|
462
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
463
|
+
*/
|
|
464
|
+
$max(...args: any[]): Pt {
|
|
465
|
+
const p = Util.getPtLike(args);
|
|
466
|
+
const m = this.clone();
|
|
467
|
+
for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
|
|
468
|
+
m[i] = Math.max(this[i], p[i]);
|
|
469
|
+
}
|
|
470
|
+
return m;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Get angle of this Pt from origin.
|
|
475
|
+
* @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions
|
|
476
|
+
*/
|
|
477
|
+
angle(axis: string | number[] = Const.xy): number {
|
|
478
|
+
return Math.atan2((this as any)[axis[1]], (this as any)[axis[0]]);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get the signed angle between this and another Pt, normalized to [-π, π).
|
|
483
|
+
* @param p the other Pt
|
|
484
|
+
* @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions
|
|
485
|
+
*/
|
|
486
|
+
angleBetween(p: Pt, axis: string | number[] = Const.xy): number {
|
|
487
|
+
// normalize the difference so results don't jump across the ±π wrap
|
|
488
|
+
return (
|
|
489
|
+
Geom.boundRadian(this.angle(axis) - p.angle(axis) + Math.PI) - Math.PI
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Scale this Pt from origin or from an anchor point.
|
|
495
|
+
* @param scale scale ratio
|
|
496
|
+
* @param anchor optional anchor point to scale from
|
|
497
|
+
*/
|
|
498
|
+
scale(scale: number | number[] | PtLike, anchor?: PtLike) {
|
|
499
|
+
Geom.scale(this, scale, anchor || Pt.make(this.length, 0));
|
|
500
|
+
return this;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Rotate this Pt from origin or from an anchor point in 2D.
|
|
505
|
+
* @param angle rotate angle
|
|
506
|
+
* @param anchor optional anchor point to scale from
|
|
507
|
+
* @param axis optional string such as "yz" to specify a 2D plane
|
|
508
|
+
*/
|
|
509
|
+
rotate2D(angle: number, anchor?: PtLike, axis?: string) {
|
|
510
|
+
Geom.rotate2D(this, angle, anchor || Pt.make(this.length, 0), axis);
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Shear this Pt from origin or from an anchor point in 2D.
|
|
516
|
+
* @param scale shearing value which can be a number or an array of 2 numbers
|
|
517
|
+
* @param anchor optional anchor point to scale from
|
|
518
|
+
* @param axis optional string such as "yz" to specify a 2D plane
|
|
519
|
+
*/
|
|
520
|
+
shear2D(scale: number | number[] | PtLike, anchor?: PtLike, axis?: string) {
|
|
521
|
+
Geom.shear2D(this, scale, anchor || Pt.make(this.length, 0), axis);
|
|
522
|
+
return this;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Reflect this Pt along a 2D line.
|
|
527
|
+
* @param line a Group of 2 Pts that defines a line for reflection
|
|
528
|
+
* @param axis optional axis such as "yz" to define a 2D plane of reflection
|
|
529
|
+
*/
|
|
530
|
+
reflect2D(line: GroupLike, axis?: string): this {
|
|
531
|
+
Geom.reflect2D(this, line, axis);
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* A string representation of this Pt. Eg, "Pt(1, 2, 3)".
|
|
537
|
+
*/
|
|
538
|
+
toString(): string {
|
|
539
|
+
return `Pt(${this.join(", ")})`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Convert this Pt to a javascript Array.
|
|
544
|
+
*/
|
|
545
|
+
toArray(): number[] {
|
|
546
|
+
const a = [];
|
|
547
|
+
for (let i = 0, len = this.length; i < len; i++) a.push(this[i]);
|
|
548
|
+
return a;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Convert this Pt to a Group as new Group([0,...], pt)
|
|
553
|
+
*/
|
|
554
|
+
toGroup(): Group {
|
|
555
|
+
return new Group(Pt.make(this.length), this.clone());
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Convert this Pt to a Bound as new Group([0,...], pt)
|
|
560
|
+
*/
|
|
561
|
+
toBound(): Bound {
|
|
562
|
+
return new Bound(Pt.make(this.length), this.clone());
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* A Group is a subclass of standard javascript [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). It should only contain Pt instances. You can think of it as an array of Float32Arrays.
|
|
568
|
+
* See [Group guide](../guide/Group-0300.html) for details.
|
|
569
|
+
*/
|
|
570
|
+
export class Group extends Array<Pt> {
|
|
571
|
+
protected _id!: string;
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Create a Group by passing an array of [`Pt`](#link). You may also create a Group using [`Group.fromArray`](#link) or [`Group.fromPtArray`](#link).
|
|
575
|
+
* @param args an array of Pts
|
|
576
|
+
*/
|
|
577
|
+
constructor(...args: Pt[]) {
|
|
578
|
+
super(...args);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* ID string of this Group
|
|
583
|
+
*/
|
|
584
|
+
get id(): string {
|
|
585
|
+
return this._id;
|
|
586
|
+
}
|
|
587
|
+
set id(s: string) {
|
|
588
|
+
this._id = s;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* The first Pt in this Group
|
|
593
|
+
*/
|
|
594
|
+
get p1(): Pt {
|
|
595
|
+
return this[0];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* The second Pt in this Group
|
|
600
|
+
*/
|
|
601
|
+
get p2(): Pt {
|
|
602
|
+
return this[1];
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* The third Pt in this Group
|
|
607
|
+
*/
|
|
608
|
+
get p3(): Pt {
|
|
609
|
+
return this[2];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* The forth Pt in this Group
|
|
614
|
+
*/
|
|
615
|
+
get p4(): Pt {
|
|
616
|
+
return this[3];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The last Pt in this Group
|
|
621
|
+
*/
|
|
622
|
+
get q1(): Pt {
|
|
623
|
+
return this[this.length - 1];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* The second-last Pt in this Group
|
|
628
|
+
*/
|
|
629
|
+
get q2(): Pt {
|
|
630
|
+
return this[this.length - 2];
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* The third-last Pt in this Group
|
|
635
|
+
*/
|
|
636
|
+
get q3(): Pt {
|
|
637
|
+
return this[this.length - 3];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* The forth-last Pt in this Group
|
|
642
|
+
*/
|
|
643
|
+
get q4(): Pt {
|
|
644
|
+
return this[this.length - 4];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Depp clone this group and its Pts.
|
|
649
|
+
*/
|
|
650
|
+
clone(): Group {
|
|
651
|
+
const group = new Group();
|
|
652
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
653
|
+
group.push(this[i].clone());
|
|
654
|
+
}
|
|
655
|
+
return group;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Convert an array of numeric arrays into a Group.
|
|
660
|
+
* @param list an Iterable<PtLike> such as an array or a generator (of PtLike numeric arrays)
|
|
661
|
+
* @example `Group.fromArray( [[1,2], [3,4], [5,6]] )`
|
|
662
|
+
*/
|
|
663
|
+
static fromArray(list: PtLikeIterable): Group {
|
|
664
|
+
const g = new Group();
|
|
665
|
+
for (const li of list) {
|
|
666
|
+
const p = li instanceof Pt ? (li as Pt) : new Pt(li);
|
|
667
|
+
g.push(p);
|
|
668
|
+
}
|
|
669
|
+
return g;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Convert an Array/Iterable of Pt into a Group.
|
|
674
|
+
* @param list an Iterable<Pt>
|
|
675
|
+
*/
|
|
676
|
+
static fromPtArray(list: PtIterable): Group {
|
|
677
|
+
return Group.from(list) as Group;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Split this Group into an array of sub-groups.
|
|
682
|
+
* @param chunkSize number of items per sub-group
|
|
683
|
+
* @param stride forward-steps after each sub-group
|
|
684
|
+
* @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed
|
|
685
|
+
*/
|
|
686
|
+
split(
|
|
687
|
+
chunkSize: number,
|
|
688
|
+
stride?: number,
|
|
689
|
+
loopBack: boolean = false,
|
|
690
|
+
): Group[] {
|
|
691
|
+
// build real Groups (Util.split returns plain arrays); indexed assignment
|
|
692
|
+
// avoids the slow spread through the Array subclass constructor
|
|
693
|
+
const st = stride || chunkSize;
|
|
694
|
+
const chunks: Group[] = [];
|
|
695
|
+
if (this.length <= 0 || st <= 0) return chunks;
|
|
696
|
+
|
|
697
|
+
let index = 0;
|
|
698
|
+
while (index < this.length) {
|
|
699
|
+
const g = new Group();
|
|
700
|
+
let size = 0;
|
|
701
|
+
for (let k = 0; k < chunkSize; k++) {
|
|
702
|
+
if (loopBack) {
|
|
703
|
+
g[size++] = this[(index + k) % this.length];
|
|
704
|
+
} else {
|
|
705
|
+
if (index + k >= this.length) break;
|
|
706
|
+
g[size++] = this[index + k];
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
index += st;
|
|
710
|
+
if (size === chunkSize) chunks.push(g);
|
|
711
|
+
}
|
|
712
|
+
return chunks;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Insert more Pt into this group.
|
|
717
|
+
* @param pts a Group or an Iterable<Pt>
|
|
718
|
+
* @param index the index position to insert into
|
|
719
|
+
*/
|
|
720
|
+
insert(pts: PtIterable, index = 0): this {
|
|
721
|
+
// iterToArray returns an array input as-is; inserting a group into itself
|
|
722
|
+
// must read a snapshot, not the tail it is about to shift
|
|
723
|
+
let _pts = Util.iterToArray(pts);
|
|
724
|
+
if ((_pts as unknown) === this) _pts = _pts.slice();
|
|
725
|
+
const len = this.length;
|
|
726
|
+
const n = _pts.length;
|
|
727
|
+
if (n === 0) return this;
|
|
728
|
+
// normalize like Array.prototype.splice, including negative index
|
|
729
|
+
let start = Math.trunc(index) || 0;
|
|
730
|
+
start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
|
|
731
|
+
// shift the tail and copy in place: splice's argument-spread overflows
|
|
732
|
+
// the call stack for very large inputs
|
|
733
|
+
this.length = len + n;
|
|
734
|
+
for (let i = len - 1; i >= start; i--) this[i + n] = this[i];
|
|
735
|
+
for (let i = 0; i < n; i++) this[start + i] = _pts[i];
|
|
736
|
+
return this;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Like Array's splice function, with support for negative index and a friendlier name.
|
|
741
|
+
* @param index start index, which can be negative (where -1 is at index 0, -2 at index 1, etc)
|
|
742
|
+
* @param count number of items to remove
|
|
743
|
+
* @returns The items that are removed.
|
|
744
|
+
*/
|
|
745
|
+
remove(index = 0, count: number = 1): Group {
|
|
746
|
+
const param: [number, number] =
|
|
747
|
+
index < 0 ? [index * -1 - 1, count] : [index, count];
|
|
748
|
+
return Group.prototype.splice.apply(this, param) as Group;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Split this group into an array of sub-group segments.
|
|
753
|
+
* @param pts_per_segment number of Pts in each segment
|
|
754
|
+
* @param stride forward-step to take
|
|
755
|
+
* @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed
|
|
756
|
+
*/
|
|
757
|
+
segments(
|
|
758
|
+
pts_per_segment: number = 2,
|
|
759
|
+
stride: number = 1,
|
|
760
|
+
loopBack: boolean = false,
|
|
761
|
+
): Group[] {
|
|
762
|
+
return this.split(pts_per_segment, stride, loopBack);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Get all the line segments (ie, edges in a graph) of this group.
|
|
767
|
+
*/
|
|
768
|
+
lines(): Group[] {
|
|
769
|
+
return this.segments(2, 1);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Find the centroid of this group's Pts, which is the average middle point.
|
|
774
|
+
*/
|
|
775
|
+
centroid(): Pt {
|
|
776
|
+
return Geom.centroid(this);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Find the rectangular bounding box of this group's Pts.
|
|
781
|
+
* @returns a Group of 2 Pts representing the top-left and bottom-right of the rectangle
|
|
782
|
+
*/
|
|
783
|
+
boundingBox(): Group {
|
|
784
|
+
return Geom.boundingBox(this);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Anchor all the Pts in this Group using a target Pt as origin. (ie, subtract all Pt with the target anchor to get a relative position). All the Pts' values will be updated.
|
|
789
|
+
* @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group
|
|
790
|
+
*/
|
|
791
|
+
anchorTo(ptOrIndex: PtLike | number = 0) {
|
|
792
|
+
Geom.anchor(this, ptOrIndex, "to");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Anchor all the Pts in this Group by its absolute position from a target Pt. (ie, add all Pt with the target anchor to get an absolute position). All the Pts' values will be updated.
|
|
797
|
+
* @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group
|
|
798
|
+
*/
|
|
799
|
+
anchorFrom(ptOrIndex: PtLike | number = 0) {
|
|
800
|
+
Geom.anchor(this, ptOrIndex, "from");
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Create an operation using this Group, passing this Group into a custom function's first parameter. See the [Op guide](../guide/Op-0400.html) for details.
|
|
805
|
+
* @param fn any function that takes a Group as its first parameter
|
|
806
|
+
* @example `let myOp = group.op( fn ); let result = myOp( [1,2,3] );`
|
|
807
|
+
* @returns a resulting function that takes other parameters required in `fn`
|
|
808
|
+
*/
|
|
809
|
+
op(fn: (g1: PtIterable, ...rest: any[]) => any): (...rest: any[]) => any {
|
|
810
|
+
const self = this;
|
|
811
|
+
return (...params: any[]) => {
|
|
812
|
+
return fn(self, ...params);
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* This combines a series of operations into an array. See the [Op guide](../guide/Op-0400.html) for details.
|
|
818
|
+
* @param fns an array of functions for `op`
|
|
819
|
+
* @example `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );`
|
|
820
|
+
* @returns an array of resulting functions
|
|
821
|
+
*/
|
|
822
|
+
ops(
|
|
823
|
+
fns: ((g1: PtIterable, ...rest: any[]) => any)[],
|
|
824
|
+
): ((...rest: any[]) => any)[] {
|
|
825
|
+
const _ops = [];
|
|
826
|
+
for (let i = 0, len = fns.length; i < len; i++) {
|
|
827
|
+
_ops.push(this.op(fns[i]));
|
|
828
|
+
}
|
|
829
|
+
return _ops;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Get an interpolated point on the line segments defined by this Group.
|
|
834
|
+
* @param t a value between 0 to 1 usually
|
|
835
|
+
*/
|
|
836
|
+
interpolate(t: number): Pt {
|
|
837
|
+
t = Num.clamp(t, 0, 1);
|
|
838
|
+
const chunk = this.length - 1;
|
|
839
|
+
const tc = 1 / (this.length - 1);
|
|
840
|
+
const idx = Math.floor(t / tc);
|
|
841
|
+
return Geom.interpolate(
|
|
842
|
+
this[idx],
|
|
843
|
+
this[Math.min(this.length - 1, idx + 1)],
|
|
844
|
+
(t - idx * tc) * chunk,
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Move every Pt's position by a specific amount. Same as [`Group.add`](#link).
|
|
850
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
851
|
+
*/
|
|
852
|
+
moveBy(...args: any[]): this {
|
|
853
|
+
return this.add(...args);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Move the first Pt in this group to a specific position, and move all the other Pts correspondingly.
|
|
858
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
859
|
+
*/
|
|
860
|
+
moveTo(...args: any[]): this {
|
|
861
|
+
const d = new Pt(...args).subtract(this[0]);
|
|
862
|
+
this.moveBy(d);
|
|
863
|
+
return this;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Scale this group's Pts from an anchor point. Default anchor point is the first Pt in this group.
|
|
868
|
+
* @param scale scale ratio
|
|
869
|
+
* @param anchor optional anchor point to scale from
|
|
870
|
+
*/
|
|
871
|
+
scale(scale: number | number[] | PtLike, anchor?: PtLike): this {
|
|
872
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
873
|
+
Geom.scale(this[i], scale, anchor || this[0]);
|
|
874
|
+
}
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Rotate this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group.
|
|
880
|
+
* @param angle rotate angle
|
|
881
|
+
* @param anchor optional anchor point to scale from
|
|
882
|
+
* @param axis optional string such as "yz" to specify a 2D plane
|
|
883
|
+
*/
|
|
884
|
+
rotate2D(angle: number, anchor?: PtLike, axis?: string): this {
|
|
885
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
886
|
+
Geom.rotate2D(this[i], angle, anchor || this[0], axis);
|
|
887
|
+
}
|
|
888
|
+
return this;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Shear this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group.
|
|
893
|
+
* @param scale shearing value which can be a number or an array of 2 numbers
|
|
894
|
+
* @param anchor optional anchor point to scale from
|
|
895
|
+
* @param axis optional string such as "yz" to specify a 2D plane
|
|
896
|
+
*/
|
|
897
|
+
shear2D(
|
|
898
|
+
scale: number | number[] | PtLike,
|
|
899
|
+
anchor?: PtLike,
|
|
900
|
+
axis?: string,
|
|
901
|
+
): this {
|
|
902
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
903
|
+
Geom.shear2D(this[i], scale, anchor || this[0], axis);
|
|
904
|
+
}
|
|
905
|
+
return this;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Reflect this group's Pts along a 2D line. Default anchor point is the first Pt in this group.
|
|
910
|
+
* @param line a Group or an Iterable<PtLike> with 2 Pt that defines a line for reflection
|
|
911
|
+
* @param axis optional axis such as "yz" to define a 2D plane of reflection
|
|
912
|
+
*/
|
|
913
|
+
reflect2D(line: PtLikeIterable, axis?: string): this {
|
|
914
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
915
|
+
Geom.reflect2D(this[i], line, axis);
|
|
916
|
+
}
|
|
917
|
+
return this;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Sort this group's Pts by values in a specific dimension.
|
|
922
|
+
* @param dim dimensional index
|
|
923
|
+
* @param desc if true, sort descending. Default is false (ascending)
|
|
924
|
+
*/
|
|
925
|
+
sortByDimension(dim: number, desc: boolean = false): this {
|
|
926
|
+
return this.sort((a, b) => (desc ? b[dim] - a[dim] : a[dim] - b[dim]));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Update each Pt in this Group with an existing Pt function.
|
|
931
|
+
* @param ptFn string name of an existing Pt function. Note that the function must return Pt.
|
|
932
|
+
* @param args arguments for the function specified in ptFn
|
|
933
|
+
*/
|
|
934
|
+
forEachPt(ptFn: string, ...args: any[]): this {
|
|
935
|
+
if (this.length === 0) return this;
|
|
936
|
+
if (!(this[0] as any)[ptFn]) {
|
|
937
|
+
Util.warn(`${ptFn} is not a function of Pt`);
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
941
|
+
this[i] = (this[i] as any)[ptFn](...args);
|
|
942
|
+
}
|
|
943
|
+
return this;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Apply a Vec operation to every Pt, parsing the arguments once for the whole
|
|
948
|
+
* group rather than once per Pt.
|
|
949
|
+
*/
|
|
950
|
+
protected _vecOp(
|
|
951
|
+
fn: (a: PtLike, b: PtLike | number) => PtLike,
|
|
952
|
+
args: any[],
|
|
953
|
+
): this {
|
|
954
|
+
const b =
|
|
955
|
+
args.length === 1 && typeof args[0] == "number"
|
|
956
|
+
? args[0]
|
|
957
|
+
: Util.getPtLike(args);
|
|
958
|
+
for (let i = 0, len = this.length; i < len; i++) {
|
|
959
|
+
fn(this[i], b);
|
|
960
|
+
}
|
|
961
|
+
return this;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Add scalar or vector values to this group's Pts.
|
|
966
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
967
|
+
*/
|
|
968
|
+
add(...args: any[]): this {
|
|
969
|
+
return this._vecOp(Vec.add, args);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Subtract scalar or vector values from this group's Pts.
|
|
974
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
975
|
+
*/
|
|
976
|
+
subtract(...args: any[]): this {
|
|
977
|
+
return this._vecOp(Vec.subtract, args);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Multiply scalar or vector values (as element-wise) with this group's Pts.
|
|
982
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
983
|
+
*/
|
|
984
|
+
multiply(...args: any[]): this {
|
|
985
|
+
return this._vecOp(Vec.multiply, args);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Divide this group's Pts over scalar or vector values (as element-wise).
|
|
990
|
+
* @param args can be either a list of numbers, an array, a Pt, or an object with {x,y,z,w} properties
|
|
991
|
+
*/
|
|
992
|
+
divide(...args: any[]): this {
|
|
993
|
+
return this._vecOp(Vec.divide, args);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Apply this group as a matrix and calculate matrix addition.
|
|
998
|
+
* @param g a scalar number, an array of numeric arrays, or a group of Pt
|
|
999
|
+
* @returns a new Group
|
|
1000
|
+
*/
|
|
1001
|
+
$matrixAdd(g: GroupLike | number[][] | number): Group {
|
|
1002
|
+
return Mat.add(this, g);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Apply this group as a matrix and calculate matrix multiplication.
|
|
1007
|
+
* @param g a scalar number, an array of numeric arrays, or a Group of K Pts, each with N dimensions (K-rows, N-columns) -- or if transposed is true, then N Pts with K dimensions
|
|
1008
|
+
* @param transposed (Only applicable if it's not elementwise multiplication) If true, then a and b's columns should match (ie, each Pt should have the same dimensions). Default is `false`.
|
|
1009
|
+
* @param elementwise if true, then the multiplication is done element-wise. Default is `false`.
|
|
1010
|
+
* @returns If not elementwise, this will return a new Group with M Pt, each with N dimensions (M-rows, N-columns).
|
|
1011
|
+
*/
|
|
1012
|
+
$matrixMultiply(
|
|
1013
|
+
g: GroupLike | number,
|
|
1014
|
+
transposed: boolean = false,
|
|
1015
|
+
elementwise: boolean = false,
|
|
1016
|
+
): Group {
|
|
1017
|
+
return Mat.multiply(this, g, transposed, elementwise);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Zip one slice of an array of Pt. Imagine the Pts are organized in rows, then this function will take the values in a specific column.
|
|
1022
|
+
* @param index index to zip at
|
|
1023
|
+
* @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead.
|
|
1024
|
+
*/
|
|
1025
|
+
zipSlice(index: number, defaultValue: number | boolean = false): Pt {
|
|
1026
|
+
return Mat.zipSlice(this, index, defaultValue);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Zip a group of Pt. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]].
|
|
1031
|
+
* @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead.
|
|
1032
|
+
* @param useLongest If true, find the longest list of values in a Pt and use its length for zipping. Default is false, which uses the first item's length for zipping.
|
|
1033
|
+
*/
|
|
1034
|
+
$zip(
|
|
1035
|
+
defaultValue: number | boolean | undefined = undefined,
|
|
1036
|
+
useLongest = false,
|
|
1037
|
+
): Group {
|
|
1038
|
+
return Mat.zip(this, defaultValue, useLongest);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Get a Bound instance of this group
|
|
1043
|
+
*/
|
|
1044
|
+
toBound(): Bound {
|
|
1045
|
+
return Bound.fromGroup(this);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Get a string representation of this group.
|
|
1050
|
+
*/
|
|
1051
|
+
toString(): string {
|
|
1052
|
+
return "Group[ " + this.reduce((p, c) => p + c.toString() + " ", "") + " ]";
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Bound is a subclass of [`Group`](#link) that represents a rectangular boundary.
|
|
1058
|
+
* It includes some convenient accessors (eg, bottomRight, center) for bounding box calculations.
|
|
1059
|
+
*/
|
|
1060
|
+
export class Bound extends Group implements IPt {
|
|
1061
|
+
protected _center: Pt = new Pt();
|
|
1062
|
+
protected _size: Pt = new Pt();
|
|
1063
|
+
protected _inited = false;
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Create a Bound. This is similar to the Group constructor. You can also create a Bound via the static function [`Bound.fromGroup`](#link), or alternatively via the [Group.toBound](#link) function.
|
|
1067
|
+
* @param args a list of Pt as parameters
|
|
1068
|
+
* @see Bound.fromGroup
|
|
1069
|
+
*/
|
|
1070
|
+
constructor(...args: Pt[]) {
|
|
1071
|
+
super(...args);
|
|
1072
|
+
this.init();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Create a Bound from a [`ClientRect`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) object.
|
|
1077
|
+
* @param rect an object that has {top, left, bottom, right, width, height} properties
|
|
1078
|
+
* @returns a Bound object
|
|
1079
|
+
*/
|
|
1080
|
+
static fromBoundingRect(rect: ClientRect): Bound {
|
|
1081
|
+
const b = new Bound(
|
|
1082
|
+
new Pt(rect.left || 0, rect.top || 0),
|
|
1083
|
+
new Pt(rect.right || 0, rect.bottom || 0),
|
|
1084
|
+
);
|
|
1085
|
+
if (rect.width && rect.height) b.size = new Pt(rect.width, rect.height);
|
|
1086
|
+
return b;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Create a Bound from a Group or an array of Pts
|
|
1091
|
+
* @param g a Group or an Iterable<PtLike>
|
|
1092
|
+
*/
|
|
1093
|
+
static fromGroup(g: PtLikeIterable): Bound {
|
|
1094
|
+
const _g = Util.iterToArray(g);
|
|
1095
|
+
if (_g.length < 2)
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
"Cannot create a Bound from a group that has less than 2 Pt",
|
|
1098
|
+
);
|
|
1099
|
+
const first = _g[0];
|
|
1100
|
+
const last = _g[_g.length - 1];
|
|
1101
|
+
return new Bound(
|
|
1102
|
+
first instanceof Pt ? first : new Pt(first),
|
|
1103
|
+
last instanceof Pt ? last : new Pt(last),
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Initiate the bound's properties.
|
|
1109
|
+
*/
|
|
1110
|
+
protected init() {
|
|
1111
|
+
if (this.p1) {
|
|
1112
|
+
this._size = this.p1.clone();
|
|
1113
|
+
this._inited = true;
|
|
1114
|
+
}
|
|
1115
|
+
if (this.p1 && this.p2) {
|
|
1116
|
+
this._updateSize();
|
|
1117
|
+
this._inited = true;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Clone this bound and return a new one.
|
|
1123
|
+
*/
|
|
1124
|
+
clone(): Bound {
|
|
1125
|
+
// the topLeft and bottomRight getters already return fresh Pts
|
|
1126
|
+
return new Bound(this.topLeft, this.bottomRight);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Recalculte size and center.
|
|
1131
|
+
*/
|
|
1132
|
+
protected _updateSize() {
|
|
1133
|
+
// In-place equivalent of `bottomRight.$subtract(topLeft).abs()`: dimensions
|
|
1134
|
+
// follow this[1], with missing/NaN topLeft values treated as 0.
|
|
1135
|
+
const a = this[0];
|
|
1136
|
+
const b = this[1];
|
|
1137
|
+
const n = b ? b.length : 0;
|
|
1138
|
+
if (this._size.length !== n) this._size = new Pt(n);
|
|
1139
|
+
for (let i = 0; i < n; i++) {
|
|
1140
|
+
let lo = a ? a[i] || 0 : 0;
|
|
1141
|
+
if (a && b[i] < lo) {
|
|
1142
|
+
// corners given in the other order: keep top-left the smaller one
|
|
1143
|
+
a[i] = b[i];
|
|
1144
|
+
b[i] = lo;
|
|
1145
|
+
lo = a[i];
|
|
1146
|
+
}
|
|
1147
|
+
this._size[i] = Math.abs(b[i] - lo);
|
|
1148
|
+
}
|
|
1149
|
+
this._updateCenter();
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Recalculate center.
|
|
1154
|
+
*/
|
|
1155
|
+
protected _updateCenter() {
|
|
1156
|
+
// In-place equivalent of `size.$multiply(0.5).add(topLeft)`
|
|
1157
|
+
const a = this[0];
|
|
1158
|
+
const n = this._size.length;
|
|
1159
|
+
if (this._center.length !== n) this._center = new Pt(n);
|
|
1160
|
+
for (let i = 0; i < n; i++) {
|
|
1161
|
+
this._center[i] = this._size[i] * 0.5 + (a ? a[i] || 0 : 0);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Recalculate based on top-left position and size.
|
|
1167
|
+
*/
|
|
1168
|
+
protected _updatePosFromTop() {
|
|
1169
|
+
this.bottomRight = this.topLeft.$add(this._size);
|
|
1170
|
+
this._updateCenter();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Recalculate based on bottom-right position and size.
|
|
1175
|
+
*/
|
|
1176
|
+
protected _updatePosFromBottom() {
|
|
1177
|
+
this.topLeft = this.bottomRight.$subtract(this._size);
|
|
1178
|
+
this._updateCenter();
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Recalculate based on center position and size.
|
|
1183
|
+
*/
|
|
1184
|
+
protected _updatePosFromCenter() {
|
|
1185
|
+
const half = this._size.$multiply(0.5);
|
|
1186
|
+
// Assign the corner Pts directly. Going through the `topLeft` and
|
|
1187
|
+
// `bottomRight` setters would call `_updateSize` after the first of the two,
|
|
1188
|
+
// recomputing size and center from a half-updated pair — so the second line
|
|
1189
|
+
// would read a `_center` that no longer holds the value being applied.
|
|
1190
|
+
const center = this._center;
|
|
1191
|
+
this[0] = center.$subtract(half);
|
|
1192
|
+
this[1] = center.$add(half);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Size of this Bound
|
|
1197
|
+
*/
|
|
1198
|
+
get size(): Pt {
|
|
1199
|
+
return new Pt(this._size);
|
|
1200
|
+
}
|
|
1201
|
+
set size(p: Pt) {
|
|
1202
|
+
this._size = new Pt(p);
|
|
1203
|
+
this._updatePosFromTop();
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Center position of this Bound
|
|
1208
|
+
*/
|
|
1209
|
+
get center(): Pt {
|
|
1210
|
+
return new Pt(this._center);
|
|
1211
|
+
}
|
|
1212
|
+
set center(p: Pt) {
|
|
1213
|
+
this._center = new Pt(p);
|
|
1214
|
+
this._updatePosFromCenter();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Top-left position of this Bound
|
|
1219
|
+
*/
|
|
1220
|
+
get topLeft(): Pt {
|
|
1221
|
+
return new Pt(this[0]);
|
|
1222
|
+
}
|
|
1223
|
+
set topLeft(p: Pt) {
|
|
1224
|
+
this[0] = new Pt(p);
|
|
1225
|
+
this._updateSize();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Bottom-right position of this Bound
|
|
1230
|
+
*/
|
|
1231
|
+
get bottomRight(): Pt {
|
|
1232
|
+
return new Pt(this[1]);
|
|
1233
|
+
}
|
|
1234
|
+
set bottomRight(p: Pt) {
|
|
1235
|
+
this[1] = new Pt(p);
|
|
1236
|
+
this._updateSize();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Width of this Bound
|
|
1241
|
+
*/
|
|
1242
|
+
get width(): number {
|
|
1243
|
+
return this._size.length > 0 ? this._size.x : 0;
|
|
1244
|
+
}
|
|
1245
|
+
set width(w: number) {
|
|
1246
|
+
this._size.x = w;
|
|
1247
|
+
this._updatePosFromTop();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Height of this Bound
|
|
1252
|
+
*/
|
|
1253
|
+
get height(): number {
|
|
1254
|
+
return this._size.length > 1 ? this._size.y : 0;
|
|
1255
|
+
}
|
|
1256
|
+
set height(h: number) {
|
|
1257
|
+
this._size.y = h;
|
|
1258
|
+
this._updatePosFromTop();
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Depth of this Bound
|
|
1263
|
+
*/
|
|
1264
|
+
get depth(): number {
|
|
1265
|
+
return this._size.length > 2 ? this._size.z : 0;
|
|
1266
|
+
}
|
|
1267
|
+
set depth(d: number) {
|
|
1268
|
+
this._size.z = d;
|
|
1269
|
+
this._updatePosFromTop();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* First value of the Bound's top-left position
|
|
1274
|
+
*/
|
|
1275
|
+
get x(): number | undefined {
|
|
1276
|
+
// direct read: going through the `topLeft` getter clones a Pt per access
|
|
1277
|
+
return this[0] ? this[0][0] : undefined;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Second value of the Bound's top-left position
|
|
1282
|
+
*/
|
|
1283
|
+
get y(): number | undefined {
|
|
1284
|
+
return this[0] ? this[0][1] : undefined;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Third value of the Bound's top-left position
|
|
1289
|
+
*/
|
|
1290
|
+
get z(): number | undefined {
|
|
1291
|
+
return this[0] ? this[0][2] : undefined;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Whether this Bound has been initiated
|
|
1296
|
+
*/
|
|
1297
|
+
get inited(): boolean {
|
|
1298
|
+
return this._inited;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* If the Bound's Pts are changed, call this function to update the Bound's properties.
|
|
1303
|
+
* It's simpler and preferable to change the Bound's properties (eg, topLeft, bottomRight) instead of updating the Bound's Pts.
|
|
1304
|
+
* Note that this recomputes from the current corner Pts in place; it does not replace them with fresh instances.
|
|
1305
|
+
*/
|
|
1306
|
+
update() {
|
|
1307
|
+
this._updateSize();
|
|
1308
|
+
return this;
|
|
1309
|
+
}
|
|
1310
|
+
}
|