simulationjsv2 0.3.2 → 0.4.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
@@ -1,9 +1,9 @@
1
1
  # SimulationJS v2
2
2
 
3
- SimulationJS is a graphics library with simple easy to use APIs. Version 2 hopes to boost performance by using webgpu under the hood.
3
+ SimulationJS is a graphics library with simple easy to use APIs. Version 2 hopes to improve 3d graphics and boost performance by using webgpu under the hood.
4
4
 
5
- The APIs for v2 will be different however semantically, using them should be nearly identical.
5
+ The APIs for v2 will differ however semantically, using them should be nearly identical.
6
6
 
7
- **Documentation coming soon**
7
+ **Documentation eventually**
8
8
 
9
9
  By _Jackson Otto_
@@ -74,6 +74,7 @@ export declare class Spline2dGeometry extends Geometry {
74
74
  constructor(points: SplinePoint2d[], color: Color, thickness: number, detail: number);
75
75
  updateInterpolationStart(start: number): void;
76
76
  updateInterpolationLimit(limit: number): void;
77
+ updatePoint(pointIndex: number, newPoint: SplinePoint2d): void;
77
78
  updateThickness(thickness: number): void;
78
79
  private getVertexCount;
79
80
  getWireframeVertexCount(): number;
package/dist/geometry.js CHANGED
@@ -239,6 +239,20 @@ export class Spline2dGeometry extends Geometry {
239
239
  updateInterpolationLimit(limit) {
240
240
  this.params.interpolateLimit = Math.min(1, Math.max(0, limit));
241
241
  }
242
+ updatePoint(pointIndex, newPoint) {
243
+ if (pointIndex < 0 && pointIndex >= this.params.points.length)
244
+ return;
245
+ const start = newPoint.getStart();
246
+ const end = newPoint.getEnd();
247
+ const [startControl, endControl] = newPoint.getControls();
248
+ const rawControls = newPoint.getRawControls();
249
+ vec3.add(end.getPos(), rawControls[1], endControl);
250
+ if (start && startControl) {
251
+ vec3.add(start.getPos(), rawControls[0], startControl);
252
+ }
253
+ this.params.points[pointIndex] = newPoint;
254
+ this.computeCurves();
255
+ }
242
256
  updateThickness(thickness) {
243
257
  this.params.thickness = thickness;
244
258
  }
@@ -255,6 +269,8 @@ export class Spline2dGeometry extends Geometry {
255
269
  return this.params.curves;
256
270
  }
257
271
  computeCurves() {
272
+ this.params.curves = [];
273
+ this.params.distance = 0;
258
274
  for (let i = 0; i < this.params.points.length; i++) {
259
275
  let prevControl = null;
260
276
  let prevColor = null;
@@ -1,4 +1,4 @@
1
- /// <reference types="dist" />
1
+ /// <reference types="@webgpu/types" />
2
2
  import { Camera } from './simulation.js';
3
3
  import type { Vector2, Vector3, LerpFunc, VertexColorMap, ElementRotation, Mat4 } from './types.js';
4
4
  import { Vertex, Color } from './utils.js';
@@ -156,6 +156,7 @@ export declare class SplinePoint2d {
156
156
  getDetail(): number | undefined;
157
157
  getColors(prevColor?: Color | null): (Color | null)[];
158
158
  getVectorArray(prevEnd: Vector2 | null, prevControl: Vector2 | null): readonly [Vector2, Vector2, Vector2, Vector2];
159
+ clone(): SplinePoint2d;
159
160
  }
160
161
  export declare class Spline2d extends SimulationElement2d {
161
162
  protected geometry: Spline2dGeometry;
@@ -169,8 +170,10 @@ export declare class Spline2d extends SimulationElement2d {
169
170
  getLength(): number;
170
171
  setInterpolateStart(start: number, t?: number, f?: LerpFunc): Promise<void>;
171
172
  setInterpolateLimit(limit: number, t?: number, f?: LerpFunc): Promise<void>;
173
+ updatePoint(pointIndex: number, newPoint: SplinePoint2d): void;
174
+ updatePointAbsolute(pointIndex: number, newPoint: SplinePoint2d): void;
172
175
  setThickness(thickness: number, t?: number, f?: LerpFunc): Promise<void>;
173
- interpolateSlope(t: number): readonly [Vector2, Vector2];
176
+ interpolateSlope(t: number): Vector2[] | readonly [Vector2, Vector2];
174
177
  interpolate(t: number): Vector2;
175
178
  protected updateMatrix(camera: Camera): void;
176
179
  }
package/dist/graphics.js CHANGED
@@ -773,6 +773,9 @@ export class SplinePoint2d {
773
773
  vector2FromVector3(this.end.getPos())
774
774
  ];
775
775
  }
776
+ clone() {
777
+ return new SplinePoint2d(this.start, this.end, this.control1, this.control2, this.rawControls, this.detail);
778
+ }
776
779
  }
777
780
  export class Spline2d extends SimulationElement2d {
778
781
  geometry;
@@ -794,6 +797,7 @@ export class Spline2d extends SimulationElement2d {
794
797
  this.estimateLength();
795
798
  }
796
799
  estimateLength() {
800
+ this.length = 0;
797
801
  const curves = this.geometry.getCurves();
798
802
  for (let i = 0; i < curves.length; i++) {
799
803
  this.length += curves[i].getLength();
@@ -826,6 +830,22 @@ export class Spline2d extends SimulationElement2d {
826
830
  this.vertexCache.updated();
827
831
  }, t, f);
828
832
  }
833
+ updatePoint(pointIndex, newPoint) {
834
+ this.geometry.updatePoint(pointIndex, newPoint);
835
+ this.estimateLength();
836
+ this.vertexCache.updated();
837
+ }
838
+ updatePointAbsolute(pointIndex, newPoint) {
839
+ const clonePoint = newPoint.clone();
840
+ const start = clonePoint.getStart()?.getPos() || vector3();
841
+ const end = clonePoint.getEnd().getPos();
842
+ const pos = vector3FromVector2(this.getPos());
843
+ vec3.sub(start, pos, start);
844
+ vec3.sub(end, pos, end);
845
+ this.geometry.updatePoint(pointIndex, clonePoint);
846
+ this.estimateLength();
847
+ this.vertexCache.updated();
848
+ }
829
849
  setThickness(thickness, t = 0, f) {
830
850
  thickness *= devicePixelRatio;
831
851
  const diff = thickness - this.thickness;
@@ -855,6 +875,8 @@ export class Spline2d extends SimulationElement2d {
855
875
  }
856
876
  currentLength += curves[i].getLength();
857
877
  }
878
+ if (curves.length === 0)
879
+ return [vector2(), vector2()];
858
880
  return curves[index].interpolateSlope(diff);
859
881
  }
860
882
  interpolate(t) {
@@ -898,7 +920,6 @@ export class Instance extends SimulationElement3d {
898
920
  return;
899
921
  const minSize = 640;
900
922
  const size = Math.max(minSize, this.instanceMatrix[0].byteLength * this.instanceMatrix.length);
901
- console.log(size, this.instanceMatrix);
902
923
  this.matrixBuffer = this.device.createBuffer({
903
924
  size,
904
925
  usage: GPUBufferUsage.STORAGE,
@@ -1,4 +1,4 @@
1
- /// <reference types="dist" />
1
+ /// <reference types="@webgpu/types" />
2
2
  import { Mat4, Vector2, Vector3 } from './types.js';
3
3
  import { Color } from './utils.js';
4
4
  import { SimulationElement } from './graphics.js';
@@ -17,7 +17,28 @@ export declare const getTransformationMatrix: (pos: Vector3, rotation: Vector3,
17
17
  export declare const getOrthoMatrix: (screenSize: [number, number]) => Float32Array;
18
18
  export declare const buildDepthTexture: (device: GPUDevice, width: number, height: number) => GPUTexture;
19
19
  export declare const buildMultisampleTexture: (device: GPUDevice, ctx: GPUCanvasContext, width: number, height: number) => GPUTexture;
20
- export declare const applyElementToScene: (scene: SimulationElement[], el: SimulationElement) => void;
20
+ export declare const addObject: (scene: SimSceneObjInfo[], el: SimulationElement<any>, id?: string) => void;
21
+ export declare const removeObject: (scene: SimSceneObjInfo[], el: SimulationElement<any>) => void;
22
+ export declare const removeObjectId: (scene: SimSceneObjInfo[], id: string) => void;
23
+ export declare class SimSceneObjInfo {
24
+ private obj;
25
+ private id;
26
+ private lifetime;
27
+ private currentLife;
28
+ constructor(obj: SimulationElement<any>, id?: string);
29
+ /**
30
+ * @param lifetime - ms
31
+ */
32
+ setLifetime(lifetime: number): void;
33
+ getLifetime(): number | null;
34
+ lifetimeComplete(): boolean;
35
+ /**
36
+ * @param amount - ms
37
+ */
38
+ traverseLife(amount: number): void;
39
+ getObj(): SimulationElement<Vector3 | Vector2>;
40
+ getId(): string | null;
41
+ }
21
42
  declare class Logger {
22
43
  constructor();
23
44
  private fmt;
@@ -42,5 +63,5 @@ export declare function matrixFromRotation(rotation: Vector3): Mat4;
42
63
  export declare function rotateMat4(mat: Mat4, rotation: Vector3): void;
43
64
  export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayout: GPUBindGroupLayout, presentationFormat: GPUTextureFormat, entryPoint: string, topology: GPUPrimitiveTopology): GPURenderPipeline;
44
65
  export declare function triangulateWireFrameOrder(len: number): number[];
45
- export declare function getTotalVertices(scene: SimulationElement[]): number;
66
+ export declare function getTotalVertices(scene: SimSceneObjInfo[]): number;
46
67
  export {};
@@ -59,14 +59,71 @@ export const buildMultisampleTexture = (device, ctx, width, height) => {
59
59
  sampleCount: 4
60
60
  });
61
61
  };
62
- export const applyElementToScene = (scene, el) => {
62
+ export const addObject = (scene, el, id) => {
63
63
  if (el instanceof SimulationElement) {
64
- scene.unshift(el);
64
+ const obj = new SimSceneObjInfo(el, id);
65
+ scene.unshift(obj);
65
66
  }
66
67
  else {
67
68
  throw logger.error('Cannot add invalid SimulationElement');
68
69
  }
69
70
  };
71
+ export const removeObject = (scene, el) => {
72
+ if (!(el instanceof SimulationElement))
73
+ return;
74
+ for (let i = 0; i < scene.length; i++) {
75
+ if (scene[i].getObj() === el) {
76
+ scene.splice(i, 1);
77
+ break;
78
+ }
79
+ }
80
+ };
81
+ export const removeObjectId = (scene, id) => {
82
+ for (let i = 0; i < scene.length; i++) {
83
+ if (scene[i].getId() === id) {
84
+ scene.splice(i, 1);
85
+ break;
86
+ }
87
+ }
88
+ };
89
+ export class SimSceneObjInfo {
90
+ obj;
91
+ id;
92
+ lifetime; // ms
93
+ currentLife;
94
+ constructor(obj, id) {
95
+ this.obj = obj;
96
+ this.id = id || null;
97
+ this.lifetime = null;
98
+ this.currentLife = 0;
99
+ }
100
+ /**
101
+ * @param lifetime - ms
102
+ */
103
+ setLifetime(lifetime) {
104
+ this.lifetime = lifetime;
105
+ }
106
+ getLifetime() {
107
+ return this.lifetime;
108
+ }
109
+ lifetimeComplete() {
110
+ if (this.lifetime === null)
111
+ return false;
112
+ return this.currentLife >= this.lifetime;
113
+ }
114
+ /**
115
+ * @param amount - ms
116
+ */
117
+ traverseLife(amount) {
118
+ this.currentLife += amount;
119
+ }
120
+ getObj() {
121
+ return this.obj;
122
+ }
123
+ getId() {
124
+ return this.id;
125
+ }
126
+ }
70
127
  class Logger {
71
128
  constructor() { }
72
129
  fmt(msg) {
@@ -250,9 +307,10 @@ export function triangulateWireFrameOrder(len) {
250
307
  export function getTotalVertices(scene) {
251
308
  let total = 0;
252
309
  for (let i = 0; i < scene.length; i++) {
253
- if (scene[i].isCollection)
310
+ const obj = scene[i].getObj();
311
+ if (obj.isCollection)
254
312
  continue;
255
- total += scene[i].getVertexCount();
313
+ total += obj.getVertexCount();
256
314
  }
257
315
  return total;
258
316
  }
@@ -1,20 +1,28 @@
1
- /// <reference types="dist" />
1
+ /// <reference types="@webgpu/types" />
2
2
  import { SimulationElement, SimulationElement3d } from './graphics.js';
3
3
  import type { Vector2, Vector3, LerpFunc } from './types.js';
4
4
  import { Color } from './utils.js';
5
5
  import { BlankGeometry } from './geometry.js';
6
+ import { SimSceneObjInfo } from './internalUtils.js';
6
7
  export declare class Simulation {
7
8
  canvasRef: HTMLCanvasElement | null;
8
9
  private bgColor;
9
10
  private scene;
10
11
  private fittingElement;
11
12
  private running;
13
+ private initialized;
12
14
  private frameRateView;
13
15
  private camera;
14
16
  private pipelines;
15
17
  private renderInfo;
16
18
  constructor(idOrCanvasRef: string | HTMLCanvasElement, camera?: Camera | null, showFrameRate?: boolean);
17
- add(el: SimulationElement<any>): void;
19
+ add(el: SimulationElement<any>, id?: string): void;
20
+ remove(el: SimulationElement<any>): void;
21
+ removeId(id: string): void;
22
+ /**
23
+ * @param lifetime - ms
24
+ */
25
+ setLifetime(el: SimulationElement<any>, lifetime: number): void;
18
26
  setCanvasSize(width: number, height: number): void;
19
27
  start(): void;
20
28
  stop(): void;
@@ -33,8 +41,15 @@ export declare class SceneCollection extends SimulationElement3d {
33
41
  constructor(name: string);
34
42
  setWireframe(_: boolean): void;
35
43
  getName(): string;
36
- getScene(): SimulationElement<Vector3>[];
44
+ getScene(): SimSceneObjInfo[];
45
+ setSceneObjects(newScene: SimulationElement<any>[]): void;
46
+ setScene(newScene: SimSceneObjInfo[]): void;
37
47
  add(el: SimulationElement<any>): void;
48
+ remove(el: SimulationElement<any>): void;
49
+ /**
50
+ * @param lifetime - ms
51
+ */
52
+ setLifetime(el: SimulationElement<any>, lifetime: number): void;
38
53
  empty(): void;
39
54
  getSceneBuffer(camera: Camera): number[];
40
55
  getWireframe(camera: Camera): number[];
@@ -3,7 +3,7 @@ import { SimulationElement3d } from './graphics.js';
3
3
  import { BUF_LEN } from './constants.js';
4
4
  import { Color, transitionValues, vector2, vector3 } from './utils.js';
5
5
  import { BlankGeometry } from './geometry.js';
6
- import { applyElementToScene, buildDepthTexture, buildMultisampleTexture, buildProjectionMatrix, createPipeline, getOrthoMatrix, getTotalVertices, getTransformationMatrix, logger } from './internalUtils.js';
6
+ import { SimSceneObjInfo, addObject, buildDepthTexture, buildMultisampleTexture, buildProjectionMatrix, createPipeline, getOrthoMatrix, getTotalVertices, getTransformationMatrix, logger, removeObject, removeObjectId } from './internalUtils.js';
7
7
  const shader = `
8
8
  struct Uniforms {
9
9
  modelViewProjectionMatrix : mat4x4<f32>,
@@ -115,6 +115,7 @@ export class Simulation {
115
115
  scene = [];
116
116
  fittingElement = false;
117
117
  running = true;
118
+ initialized = false;
118
119
  frameRateView;
119
120
  camera;
120
121
  pipelines;
@@ -152,8 +153,23 @@ export class Simulation {
152
153
  this.frameRateView = new FrameRateView(showFrameRate);
153
154
  this.frameRateView.updateFrameRate(1);
154
155
  }
155
- add(el) {
156
- applyElementToScene(this.scene, el);
156
+ add(el, id) {
157
+ addObject(this.scene, el, id);
158
+ }
159
+ remove(el) {
160
+ removeObject(this.scene, el);
161
+ }
162
+ removeId(id) {
163
+ removeObjectId(this.scene, id);
164
+ }
165
+ /**
166
+ * @param lifetime - ms
167
+ */
168
+ setLifetime(el, lifetime) {
169
+ for (let i = 0; i < this.scene.length; i++) {
170
+ if (this.scene[i].getObj() === el)
171
+ this.scene[i].setLifetime(lifetime);
172
+ }
157
173
  }
158
174
  setCanvasSize(width, height) {
159
175
  this.assertHasCanvas();
@@ -163,8 +179,13 @@ export class Simulation {
163
179
  this.canvasRef.style.height = height + 'px';
164
180
  }
165
181
  start() {
182
+ if (this.initialized) {
183
+ this.running = true;
184
+ return;
185
+ }
166
186
  (async () => {
167
187
  this.assertHasCanvas();
188
+ this.initialized = true;
168
189
  this.running = true;
169
190
  const adapter = await navigator.gpu.requestAdapter();
170
191
  if (!adapter)
@@ -191,8 +212,9 @@ export class Simulation {
191
212
  }
192
213
  propagateDevice(device) {
193
214
  for (let i = 0; i < this.scene.length; i++) {
194
- if (this.scene[i].isInstance) {
195
- this.scene[i].setDevice(device);
215
+ const obj = this.scene[i].getObj();
216
+ if (obj.isInstance) {
217
+ obj.setDevice(device);
196
218
  }
197
219
  }
198
220
  }
@@ -299,9 +321,11 @@ export class Simulation {
299
321
  let prev = Date.now() - 10;
300
322
  let prevFps = 0;
301
323
  const frame = async () => {
302
- if (!this.running || !canvas)
324
+ if (!canvas)
303
325
  return;
304
326
  requestAnimationFrame(frame);
327
+ if (!this.running)
328
+ return;
305
329
  const now = Date.now();
306
330
  const diff = Math.max(now - prev, 1);
307
331
  prev = now;
@@ -339,14 +363,14 @@ export class Simulation {
339
363
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
340
364
  passEncoder.setPipeline(this.pipelines.triangleList3d);
341
365
  passEncoder.setBindGroup(0, uniformBindGroup);
342
- this.renderScene(device, passEncoder, this.scene);
366
+ this.renderScene(device, passEncoder, this.scene, diff);
343
367
  this.camera.updateConsumed();
344
368
  passEncoder.end();
345
369
  device.queue.submit([commandEncoder.finish()]);
346
370
  };
347
371
  requestAnimationFrame(frame);
348
372
  }
349
- async renderScene(device, passEncoder, scene) {
373
+ async renderScene(device, passEncoder, scene, diff) {
350
374
  if (this.pipelines === null)
351
375
  return;
352
376
  let totalVertices = getTotalVertices(scene);
@@ -355,17 +379,28 @@ export class Simulation {
355
379
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
356
380
  });
357
381
  let currentOffset = 0;
382
+ let toRemove = [];
358
383
  for (let i = 0; i < scene.length; i++) {
359
- if (scene[i].isCollection) {
360
- this.renderScene(device, passEncoder, scene[i].getScene());
384
+ const lifetime = scene[i].getLifetime();
385
+ if (lifetime !== null) {
386
+ const complete = scene[i].lifetimeComplete();
387
+ if (complete) {
388
+ toRemove.push(i);
389
+ continue;
390
+ }
391
+ scene[i].traverseLife(diff);
392
+ }
393
+ const obj = scene[i].getObj();
394
+ if (obj.isCollection) {
395
+ this.renderScene(device, passEncoder, obj.getScene(), diff);
361
396
  continue;
362
397
  }
363
- const buffer = new Float32Array(scene[i].getBuffer(this.camera));
398
+ const buffer = new Float32Array(obj.getBuffer(this.camera));
364
399
  const vertexCount = buffer.length / BUF_LEN;
365
400
  device.queue.writeBuffer(vertexBuffer, currentOffset, buffer);
366
401
  vertexBuffer.unmap();
367
- const is3d = Boolean(scene[i].is3d);
368
- if (scene[i].isWireframe()) {
402
+ const is3d = Boolean(obj.is3d);
403
+ if (obj.isWireframe()) {
369
404
  if (is3d) {
370
405
  passEncoder.setPipeline(this.pipelines.lineStrip3d);
371
406
  }
@@ -374,7 +409,7 @@ export class Simulation {
374
409
  }
375
410
  }
376
411
  else {
377
- const type = scene[i].getGeometryType();
412
+ const type = obj.getGeometryType();
378
413
  if (type === 'strip') {
379
414
  if (is3d) {
380
415
  passEncoder.setPipeline(this.pipelines.triangleStrip3d);
@@ -393,9 +428,9 @@ export class Simulation {
393
428
  }
394
429
  }
395
430
  let instances = 1;
396
- if (scene[i].isInstance) {
397
- instances = scene[i].getNumInstances();
398
- const buf = scene[i].getMatrixBuffer();
431
+ if (obj.isInstance) {
432
+ instances = obj.getNumInstances();
433
+ const buf = obj.getMatrixBuffer();
399
434
  if (buf && this.renderInfo) {
400
435
  const uniformBindGroup = device.createBindGroup({
401
436
  layout: this.renderInfo.bindGroupLayout,
@@ -421,6 +456,9 @@ export class Simulation {
421
456
  passEncoder.draw(vertexCount, instances, 0, 0);
422
457
  currentOffset += buffer.byteLength;
423
458
  }
459
+ for (let i = toRemove.length - 1; i >= 0; i--) {
460
+ removeObject(scene, scene[i].getObj());
461
+ }
424
462
  }
425
463
  fitElement() {
426
464
  this.assertHasCanvas();
@@ -457,14 +495,32 @@ export class SceneCollection extends SimulationElement3d {
457
495
  getScene() {
458
496
  return this.scene;
459
497
  }
498
+ setSceneObjects(newScene) {
499
+ this.scene = newScene.map((item) => new SimSceneObjInfo(item));
500
+ }
501
+ setScene(newScene) {
502
+ this.scene = newScene;
503
+ }
460
504
  add(el) {
461
- applyElementToScene(this.scene, el);
505
+ addObject(this.scene, el);
506
+ }
507
+ remove(el) {
508
+ removeObject(this.scene, el);
509
+ }
510
+ /**
511
+ * @param lifetime - ms
512
+ */
513
+ setLifetime(el, lifetime) {
514
+ for (let i = 0; i < this.scene.length; i++) {
515
+ if (this.scene[i].getObj() === el)
516
+ this.scene[i].setLifetime(lifetime);
517
+ }
462
518
  }
463
519
  empty() {
464
520
  this.scene = [];
465
521
  }
466
522
  getSceneBuffer(camera) {
467
- return this.scene.map((item) => item.getBuffer(camera)).flat();
523
+ return this.scene.map((item) => item.getObj().getBuffer(camera)).flat();
468
524
  }
469
525
  getWireframe(camera) {
470
526
  return this.getSceneBuffer(camera);
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /// <reference types="dist" />
1
+ /// <reference types="@webgpu/types" />
2
2
  import { CubicBezierCurve2d, SplinePoint2d } from './graphics.js';
3
3
  import { Color, Vertex } from './utils.js';
4
4
  export type Shift<T extends any[]> = ((...args: T) => any) extends (arg1: any, ...rest: infer R) => any ? R : never;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "types": "./dist/index.d.ts",
6
6
  "author": "Jackson Otto",
7
7
  "description": "A simple graphics library using WebGPU",
8
- "version": "0.3.2",
8
+ "version": "0.4.0",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",