simulationjsv2 0.2.7 → 0.2.9

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/TODO.md ADDED
@@ -0,0 +1,4 @@
1
+ # TODO
2
+
3
+ - [ ] Memoize render scene buffer
4
+ - Should only be reset to resize to a new larger object then reused on all frames
@@ -1,4 +1,6 @@
1
- export declare const BUF_LEN = 10;
2
- export declare const vertexSize = 40;
1
+ export declare const vertexSize = 44;
2
+ export declare const positionOffset = 0;
3
3
  export declare const colorOffset = 16;
4
4
  export declare const uvOffset = 32;
5
+ export declare const drawingInstancesOffset = 40;
6
+ export declare const BUF_LEN: number;
package/dist/constants.js CHANGED
@@ -1,4 +1,6 @@
1
- export const BUF_LEN = 10;
2
- export const vertexSize = 40; // 4 * 10
1
+ export const vertexSize = 44; // 4 * 10
2
+ export const positionOffset = 0;
3
3
  export const colorOffset = 16; // 4 * 4
4
4
  export const uvOffset = 32; // 4 * 8
5
+ export const drawingInstancesOffset = 40;
6
+ export const BUF_LEN = vertexSize / 4;
@@ -12,6 +12,8 @@ export declare abstract class Geometry {
12
12
  updateMatrix(matrix: Mat4): void;
13
13
  getType(): "list" | "strip";
14
14
  abstract recompute(): void;
15
+ getTriangleVertexCount(): number;
16
+ getWireframeVertexCount(): number;
15
17
  protected bufferFromOrder(order: number[], color: Color): number[];
16
18
  getWireframeBuffer(color: Color): number[];
17
19
  getTriangleBuffer(color: Color): number[];
@@ -22,7 +24,6 @@ export declare class PlaneGeometry extends Geometry {
22
24
  protected triangleOrder: number[];
23
25
  private rawVertices;
24
26
  constructor(vertices: Vertex[]);
25
- private updateWireframeOrder;
26
27
  recompute(): void;
27
28
  updateVertices(vertices: Vertex[]): void;
28
29
  getTriangleBuffer(color: Color): number[];
@@ -73,6 +74,9 @@ export declare class SplineGeometry extends Geometry {
73
74
  constructor(points: SplinePoint2d[], color: Color, thickness: number, detail: number);
74
75
  updateInterpolationStart(start: number): void;
75
76
  updateInterpolationLimit(limit: number): void;
77
+ private getVertexCount;
78
+ getWireframeVertexCount(): number;
79
+ getTriangleVertexCount(): number;
76
80
  private computeCurves;
77
81
  private updateWireframeOrder;
78
82
  recompute(): void;
package/dist/geometry.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { mat4, vec2, vec3 } from 'wgpu-matrix';
2
- import { cloneBuf, interpolateColors, lossyTriangulate, matrix4, triangulateWireFrameOrder, vector2, vector2FromVector3, vector3, vector3FromVector2, vertex, vertexBuffer } from './utils.js';
2
+ import { cloneBuf, interpolateColors, lossyTriangulate, matrix4, triangulateWireFrameOrder, vector2, vector2FromVector3, vector3, vector3FromVector2, vertex, bufferGenerator } from './utils.js';
3
3
  import { CubicBezierCurve2d } from './graphics.js';
4
+ import { BUF_LEN } from './constants.js';
4
5
  export class Geometry {
5
6
  vertices;
6
7
  matrix;
@@ -16,12 +17,18 @@ export class Geometry {
16
17
  getType() {
17
18
  return this.geometryType;
18
19
  }
20
+ getTriangleVertexCount() {
21
+ return this.triangleOrder.length;
22
+ }
23
+ getWireframeVertexCount() {
24
+ return this.wireframeOrder.length;
25
+ }
19
26
  bufferFromOrder(order, color) {
20
27
  return order
21
28
  .map((vertexIndex) => {
22
29
  const pos = cloneBuf(this.vertices[vertexIndex]);
23
30
  vec3.transformMat4(pos, this.matrix, pos);
24
- return vertexBuffer(pos[0], pos[1], pos[2], color);
31
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], color);
25
32
  })
26
33
  .flat();
27
34
  }
@@ -44,22 +51,22 @@ export class PlaneGeometry extends Geometry {
44
51
  this.rawVertices = vertices;
45
52
  this.updateVertices(vertices);
46
53
  }
47
- updateWireframeOrder() {
48
- this.wireframeOrder = triangulateWireFrameOrder(this.vertices.length);
49
- }
50
54
  recompute() { }
51
55
  updateVertices(vertices) {
52
56
  this.rawVertices = vertices;
53
57
  this.vertices = vertices.map((vertex) => vertex.getPos());
54
- this.updateWireframeOrder();
58
+ this.wireframeOrder = triangulateWireFrameOrder(this.vertices.length);
59
+ this.triangleOrder = lossyTriangulate(Array(this.rawVertices.length)
60
+ .fill(0)
61
+ .map((_, index) => index)).flat();
55
62
  }
56
63
  getTriangleBuffer(color) {
57
- return lossyTriangulate(this.rawVertices)
58
- .flat()
59
- .map((vertex) => {
64
+ return this.triangleOrder
65
+ .map((index) => {
66
+ const vertex = this.rawVertices[index];
60
67
  const pos = cloneBuf(vertex.getPos());
61
68
  vec3.transformMat4(pos, this.matrix, pos);
62
- return vertexBuffer(pos[0], pos[1], pos[2], vertex.getColor() || color);
69
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], vertex.getColor() || color);
63
70
  })
64
71
  .flat();
65
72
  }
@@ -150,7 +157,7 @@ export class SquareGeometry extends Geometry {
150
157
  .map((vertexIndex) => {
151
158
  const pos = cloneBuf(this.vertices[vertexIndex]);
152
159
  vec3.transformMat4(pos, this.matrix, pos);
153
- return vertexBuffer(pos[0], pos[1], pos[2], this.params.colorMap[vertexIndex] || color);
160
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], this.params.colorMap[vertexIndex] || color);
154
161
  })
155
162
  .flat();
156
163
  }
@@ -206,7 +213,7 @@ export class SplineGeometry extends Geometry {
206
213
  triangleOrder;
207
214
  params;
208
215
  constructor(points, color, thickness, detail) {
209
- super([], 'list');
216
+ super();
210
217
  this.wireframeOrder = [];
211
218
  this.triangleOrder = [];
212
219
  this.params = {
@@ -229,6 +236,15 @@ export class SplineGeometry extends Geometry {
229
236
  updateInterpolationLimit(limit) {
230
237
  this.params.interpolateLimit = Math.min(1, Math.max(0, limit));
231
238
  }
239
+ getVertexCount() {
240
+ return this.triangleOrder.length * BUF_LEN;
241
+ }
242
+ getWireframeVertexCount() {
243
+ return this.getVertexCount();
244
+ }
245
+ getTriangleVertexCount() {
246
+ return this.getVertexCount();
247
+ }
232
248
  computeCurves() {
233
249
  for (let i = 0; i < this.params.points.length; i++) {
234
250
  let prevControl = null;
@@ -313,7 +329,7 @@ export class SplineGeometry extends Geometry {
313
329
  .map((vertexIndex) => {
314
330
  const vertex = cloneBuf(this.vertices[vertexIndex]);
315
331
  vec3.transformMat4(vertex, this.matrix, vertex);
316
- return vertexBuffer(vertex[0], vertex[1], vertex[2], color);
332
+ return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], color);
317
333
  })
318
334
  .flat();
319
335
  }
@@ -322,7 +338,7 @@ export class SplineGeometry extends Geometry {
322
338
  .map((vertexIndex) => {
323
339
  const vertex = cloneBuf(this.vertices[vertexIndex]);
324
340
  vec3.transformMat4(vertex, this.matrix, vertex);
325
- return vertexBuffer(vertex[0], vertex[1], vertex[2], this.params.vertexColors[vertexIndex]);
341
+ return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], this.params.vertexColors[vertexIndex]);
326
342
  })
327
343
  .flat();
328
344
  }
@@ -400,7 +416,7 @@ export class PolygonGeometry extends Geometry {
400
416
  .map((vertexIndex) => {
401
417
  const vertex = cloneBuf(this.vertices[vertexIndex]);
402
418
  vec3.transformMat4(vertex, this.matrix, vertex);
403
- return vertexBuffer(vertex[0], vertex[1], 0, this.params.points[vertexIndex].getColor() || color);
419
+ return bufferGenerator.generate(vertex[0], vertex[1], 0, this.params.points[vertexIndex].getColor() || color);
404
420
  })
405
421
  .flat();
406
422
  }
@@ -1,7 +1,8 @@
1
+ /// <reference types="dist" />
1
2
  import { Camera } from './simulation.js';
2
- import type { Vector2, Vector3, LerpFunc, VertexColorMap, ElementRotation } from './types.js';
3
+ import type { Vector2, Vector3, LerpFunc, VertexColorMap, ElementRotation, Mat4 } from './types.js';
3
4
  import { Vertex, VertexCache, Color } from './utils.js';
4
- import { CircleGeometry, CubeGeometry, Geometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, SplineGeometry, SquareGeometry } from './geometry.js';
5
+ import { BlankGeometry, CircleGeometry, CubeGeometry, Geometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, SplineGeometry, SquareGeometry } from './geometry.js';
5
6
  export declare abstract class SimulationElement<T extends Vector2 | Vector3 = Vector3> {
6
7
  protected abstract pos: T;
7
8
  protected abstract geometry: Geometry;
@@ -9,28 +10,31 @@ export declare abstract class SimulationElement<T extends Vector2 | Vector3 = Ve
9
10
  protected wireframe: boolean;
10
11
  protected vertexCache: VertexCache;
11
12
  protected rotation: ElementRotation<T>;
12
- readonly is3d: boolean;
13
+ isInstanced: boolean;
13
14
  /**
14
15
  * @param pos - Expected to be adjusted to devicePixelRatio before reaching constructor
15
16
  */
16
- constructor(color: Color | undefined, rotation: ElementRotation<T>, is3d?: boolean);
17
+ constructor(color: Color | undefined, rotation: ElementRotation<T>);
17
18
  getGeometryType(): "list" | "strip";
18
19
  setWireframe(wireframe: boolean): void;
19
20
  isWireframe(): boolean;
20
21
  getColor(): Color;
21
22
  getPos(): T;
23
+ getRotation(): ElementRotation<T>;
22
24
  fill(newColor: Color, t?: number, f?: LerpFunc): Promise<void>;
23
25
  abstract move(amount: T, t?: number, f?: LerpFunc): Promise<void>;
24
26
  abstract moveTo(pos: T, t?: number, f?: LerpFunc): Promise<void>;
25
27
  abstract rotate(amount: ElementRotation<T>, t?: number, f?: LerpFunc): Promise<void>;
26
28
  abstract rotateTo(rotation: ElementRotation<T>, t?: number, f?: LerpFunc): Promise<void>;
27
29
  protected abstract updateMatrix(camera: Camera): void;
30
+ getVertexCount(): number;
28
31
  protected defaultUpdateMatrix(camera: Camera): void;
29
32
  getBuffer(camera: Camera): number[];
30
33
  }
31
34
  export declare abstract class SimulationElement3d extends SimulationElement {
32
35
  protected pos: Vector3;
33
- rotation: Vector3;
36
+ protected rotation: Vector3;
37
+ is3d: boolean;
34
38
  constructor(pos: Vector3, rotation?: Vector3, color?: Color);
35
39
  rotate(amount: Vector3, t?: number, f?: LerpFunc): Promise<void>;
36
40
  rotateTo(rot: Vector3, t?: number, f?: LerpFunc): Promise<void>;
@@ -163,3 +167,21 @@ export declare class Spline2d extends SimulationElement2d {
163
167
  interpolate(t: number): Vector2;
164
168
  protected updateMatrix(camera: Camera): void;
165
169
  }
170
+ export declare class Instance<T extends SimulationElement2d | SimulationElement3d> extends SimulationElement3d {
171
+ protected geometry: BlankGeometry;
172
+ private obj;
173
+ private instanceMatrix;
174
+ private matrixBuffer;
175
+ private device;
176
+ readonly isInstance = true;
177
+ constructor(obj: T, numInstances: number);
178
+ private setMatrixBuffer;
179
+ getInstances(): Mat4[];
180
+ getNumInstances(): number;
181
+ setDevice(device: GPUDevice): void;
182
+ getMatrixBuffer(): GPUBuffer | null;
183
+ getVertexCount(): number;
184
+ getGeometryType(): "list" | "strip";
185
+ protected updateMatrix(_: Camera): void;
186
+ getBuffer(camera: Camera): number[];
187
+ }
package/dist/graphics.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import { vec3, mat4, vec2, vec4 } from 'wgpu-matrix';
2
- import { Vertex, VertexCache, cloneBuf, color, colorFromVector4, vector3ToPixelRatio, vector2, vector3, vertex, Color, transitionValues, logger, vector2FromVector3, matrix4, rotateMat4, vector3FromVector2 } from './utils.js';
3
- import { CircleGeometry, CubeGeometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, SplineGeometry, SquareGeometry } from './geometry.js';
2
+ import { Vertex, VertexCache, cloneBuf, color, colorFromVector4, vector3ToPixelRatio, vector2, vector3, vertex, Color, transitionValues, logger, vector2FromVector3, matrix4, rotateMat4, vector3FromVector2, vector2ToPixelRatio, bufferGenerator } from './utils.js';
3
+ import { BlankGeometry, CircleGeometry, CubeGeometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, SplineGeometry, SquareGeometry } from './geometry.js';
4
4
  export class SimulationElement {
5
5
  color;
6
6
  wireframe;
7
7
  vertexCache;
8
8
  rotation;
9
- is3d;
9
+ isInstanced;
10
10
  /**
11
11
  * @param pos - Expected to be adjusted to devicePixelRatio before reaching constructor
12
12
  */
13
- constructor(color = new Color(), rotation, is3d = true) {
13
+ constructor(color = new Color(), rotation) {
14
14
  this.color = color;
15
15
  this.vertexCache = new VertexCache();
16
- this.is3d = is3d;
17
16
  this.wireframe = false;
18
17
  this.rotation = rotation;
18
+ this.isInstanced = false;
19
19
  }
20
20
  getGeometryType() {
21
21
  return this.geometry.getType();
@@ -32,6 +32,9 @@ export class SimulationElement {
32
32
  getPos() {
33
33
  return this.pos;
34
34
  }
35
+ getRotation() {
36
+ return this.rotation;
37
+ }
35
38
  fill(newColor, t = 0, f) {
36
39
  const diff = newColor.diff(this.color);
37
40
  const finalColor = newColor.clone();
@@ -46,6 +49,15 @@ export class SimulationElement {
46
49
  this.vertexCache.updated();
47
50
  }, t, f);
48
51
  }
52
+ getVertexCount() {
53
+ if (this.vertexCache.shouldUpdate()) {
54
+ this.geometry.recompute();
55
+ }
56
+ if (this.isWireframe()) {
57
+ return this.geometry.getWireframeVertexCount();
58
+ }
59
+ return this.geometry.getTriangleVertexCount();
60
+ }
49
61
  defaultUpdateMatrix(camera) {
50
62
  const matrix = matrix4();
51
63
  if (typeof this.rotation === 'number') {
@@ -64,6 +76,9 @@ export class SimulationElement {
64
76
  if (this.vertexCache.shouldUpdate() || camera.hasUpdated()) {
65
77
  this.updateMatrix(camera);
66
78
  this.geometry.recompute();
79
+ if (this.isInstanced) {
80
+ bufferGenerator.setInstancing(true);
81
+ }
67
82
  let resBuffer = [];
68
83
  if (this.isWireframe()) {
69
84
  resBuffer = this.geometry.getWireframeBuffer(this.color);
@@ -71,6 +86,7 @@ export class SimulationElement {
71
86
  else {
72
87
  resBuffer = this.geometry.getTriangleBuffer(this.color);
73
88
  }
89
+ bufferGenerator.setInstancing(false);
74
90
  this.vertexCache.setCache(resBuffer);
75
91
  return resBuffer;
76
92
  }
@@ -80,6 +96,7 @@ export class SimulationElement {
80
96
  export class SimulationElement3d extends SimulationElement {
81
97
  pos;
82
98
  rotation;
99
+ is3d = true;
83
100
  constructor(pos, rotation = vector3(), color) {
84
101
  super(color, rotation);
85
102
  this.pos = pos;
@@ -143,7 +160,7 @@ export class SimulationElement2d extends SimulationElement {
143
160
  pos;
144
161
  rotation;
145
162
  constructor(pos, rotation = 0, color) {
146
- super(color, rotation, false);
163
+ super(color, rotation);
147
164
  this.pos = pos;
148
165
  this.rotation = rotation;
149
166
  }
@@ -219,8 +236,9 @@ export class Square extends SimulationElement2d {
219
236
  */
220
237
  constructor(pos, width, height, color, rotation, vertexColors) {
221
238
  super(pos, rotation, color);
222
- this.width = width;
223
- this.height = height;
239
+ vector2ToPixelRatio(this.pos);
240
+ this.width = width * devicePixelRatio;
241
+ this.height = height * devicePixelRatio;
224
242
  this.vertexColors = this.cloneColorMap(vertexColors || {});
225
243
  this.geometry = new SquareGeometry(this.width, this.height);
226
244
  this.geometry.setVertexColorMap(this.vertexColors);
@@ -389,7 +407,6 @@ export class Circle extends SimulationElement2d {
389
407
  this.defaultUpdateMatrix(camera);
390
408
  }
391
409
  }
392
- // TODO: litterally this whole thing
393
410
  export class Polygon extends SimulationElement2d {
394
411
  geometry;
395
412
  vertices;
@@ -800,3 +817,69 @@ export class Spline2d extends SimulationElement2d {
800
817
  this.defaultUpdateMatrix(camera);
801
818
  }
802
819
  }
820
+ export class Instance extends SimulationElement3d {
821
+ geometry;
822
+ obj;
823
+ instanceMatrix;
824
+ matrixBuffer;
825
+ device;
826
+ isInstance = true;
827
+ constructor(obj, numInstances) {
828
+ super(vector3());
829
+ this.device = null;
830
+ this.matrixBuffer = null;
831
+ obj.isInstanced = true;
832
+ this.obj = obj;
833
+ this.instanceMatrix = [];
834
+ this.is3d = Boolean(obj.is3d);
835
+ this.geometry = new BlankGeometry();
836
+ const mat = matrix4();
837
+ if (typeof obj.getRotation() === 'number') {
838
+ mat4.rotateZ(mat, obj.getRotation(), mat);
839
+ }
840
+ else {
841
+ rotateMat4(mat, obj.getRotation());
842
+ }
843
+ for (let i = 0; i < numInstances; i++) {
844
+ const clone = cloneBuf(mat);
845
+ this.instanceMatrix.push(clone);
846
+ }
847
+ }
848
+ setMatrixBuffer() {
849
+ if (!this.device || this.instanceMatrix.length === 0)
850
+ return;
851
+ this.matrixBuffer = this.device.createBuffer({
852
+ size: this.instanceMatrix[0].length * 4 * this.instanceMatrix.length,
853
+ usage: GPUBufferUsage.STORAGE,
854
+ mappedAtCreation: true
855
+ });
856
+ const buf = this.instanceMatrix.map((mat) => [...mat]).flat();
857
+ new Float32Array(this.matrixBuffer.getMappedRange()).set(buf);
858
+ this.matrixBuffer.unmap();
859
+ }
860
+ getInstances() {
861
+ return this.instanceMatrix;
862
+ }
863
+ getNumInstances() {
864
+ return this.instanceMatrix.length;
865
+ }
866
+ setDevice(device) {
867
+ this.device = device;
868
+ if (this.matrixBuffer === null) {
869
+ this.setMatrixBuffer();
870
+ }
871
+ }
872
+ getMatrixBuffer() {
873
+ return this.matrixBuffer;
874
+ }
875
+ getVertexCount() {
876
+ return this.obj.getVertexCount();
877
+ }
878
+ getGeometryType() {
879
+ return this.obj.getGeometryType();
880
+ }
881
+ updateMatrix(_) { }
882
+ getBuffer(camera) {
883
+ return this.obj.getBuffer(camera);
884
+ }
885
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './simulation.js';
2
2
  export * from './graphics.js';
3
3
  export * from './types.js';
4
- export { Vertex, Color, cloneBuf, vector4, vector3, vector2, vector3FromVector2, colorFromVector4, randomInt, randomColor, vertex, color, colorf, transitionValues, lerp, smoothStep, linearStep, splinePoint2d, continuousSplinePoint2d, easeInOutQuad, easeInOutExpo, easeInOutQuart, waitFor } from './utils.js';
4
+ export { Vertex, Color, cloneBuf, vector4, vector3, vector2, vector3FromVector2, colorFromVector4, randomInt, randomColor, vertex, color, colorf, transitionValues, lerp, smoothStep, linearStep, splinePoint2d, continuousSplinePoint2d, easeInOutQuad, easeInOutExpo, easeInOutQuart, waitFor, matrix4 } from './utils.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './simulation.js';
2
2
  export * from './graphics.js';
3
3
  export * from './types.js';
4
- export { Vertex, Color, cloneBuf, vector4, vector3, vector2, vector3FromVector2, colorFromVector4, randomInt, randomColor, vertex, color, colorf, transitionValues, lerp, smoothStep, linearStep, splinePoint2d, continuousSplinePoint2d, easeInOutQuad, easeInOutExpo, easeInOutQuart, waitFor } from './utils.js';
4
+ export { Vertex, Color, cloneBuf, vector4, vector3, vector2, vector3FromVector2, colorFromVector4, randomInt, randomColor, vertex, color, colorf, transitionValues, lerp, smoothStep, linearStep, splinePoint2d, continuousSplinePoint2d, easeInOutQuad, easeInOutExpo, easeInOutQuart, waitFor, matrix4 } from './utils.js';
@@ -12,12 +12,14 @@ export declare class Simulation {
12
12
  private frameRateView;
13
13
  private camera;
14
14
  private pipelines;
15
+ private renderInfo;
15
16
  constructor(idOrCanvasRef: string | HTMLCanvasElement, camera?: Camera | null, showFrameRate?: boolean);
16
17
  add(el: SimulationElement<any>): void;
17
18
  setCanvasSize(width: number, height: number): void;
18
19
  start(): void;
19
20
  stop(): void;
20
21
  setBackground(color: Color): void;
22
+ private propagateDevice;
21
23
  render(device: GPUDevice, ctx: GPUCanvasContext): void;
22
24
  private renderScene;
23
25
  fitElement(): void;
@@ -27,6 +29,7 @@ export declare class SceneCollection extends SimulationElement3d {
27
29
  protected geometry: BlankGeometry;
28
30
  private name;
29
31
  private scene;
32
+ readonly isCollection = true;
30
33
  constructor(name: string);
31
34
  setWireframe(_: boolean): void;
32
35
  getName(): string;
@@ -1,16 +1,17 @@
1
1
  import { vec3 } from 'wgpu-matrix';
2
2
  import { SimulationElement3d } from './graphics.js';
3
3
  import { BUF_LEN } from './constants.js';
4
- import { Color, applyElementToScene, buildDepthTexture, buildMultisampleTexture, buildProjectionMatrix, createPipeline, getOrthoMatrix, getTransformationMatrix, logger, transitionValues, vector2, vector3 } from './utils.js';
4
+ import { Color, applyElementToScene, buildDepthTexture, buildMultisampleTexture, buildProjectionMatrix, createPipeline, getOrthoMatrix, getTotalVertices, getTransformationMatrix, logger, transitionValues, vector2, vector3 } from './utils.js';
5
5
  import { BlankGeometry } from './geometry.js';
6
6
  const shader = `
7
7
  struct Uniforms {
8
8
  modelViewProjectionMatrix : mat4x4<f32>,
9
- orthoProjectionMatrix : mat4x4<f32>,
10
- screenSize : vec2<f32>,
9
+ orthoProjectionMatrix : mat4x4<f32>
11
10
  }
12
11
 
13
- @binding(0) @group(0) var<uniform> uniforms : Uniforms;
12
+ @group(0) @binding(0) var<uniform> uniforms : Uniforms;
13
+
14
+ @group(0) @binding(1) var<storage, read> instanceMatrices : array<mat4x4f, 10>;
14
15
 
15
16
  struct VertexOutput {
16
17
  @builtin(position) Position : vec4<f32>,
@@ -21,9 +22,11 @@ struct VertexOutput {
21
22
 
22
23
  @vertex
23
24
  fn vertex_main_3d(
25
+ @builtin(instance_index) instanceIdx : u32,
24
26
  @location(0) position : vec4<f32>,
25
27
  @location(1) color : vec4<f32>,
26
28
  @location(2) uv : vec2<f32>,
29
+ @location(3) drawingInstance: f32
27
30
  ) -> VertexOutput {
28
31
  var output : VertexOutput;
29
32
 
@@ -36,13 +39,21 @@ fn vertex_main_3d(
36
39
 
37
40
  @vertex
38
41
  fn vertex_main_2d(
42
+ @builtin(instance_index) instanceIdx : u32,
39
43
  @location(0) position : vec4<f32>,
40
44
  @location(1) color : vec4<f32>,
41
45
  @location(2) uv : vec2<f32>,
46
+ @location(3) drawingInstance: f32
42
47
  ) -> VertexOutput {
43
- var output : VertexOutput;
48
+ var output: VertexOutput;
49
+
50
+ if (drawingInstance == 1) {
51
+ let transformedPos = instanceMatrices[instanceIdx] * position;
52
+ output.Position = uniforms.orthoProjectionMatrix * transformedPos;
53
+ } else {
54
+ output.Position = uniforms.orthoProjectionMatrix * position;
55
+ }
44
56
 
45
- output.Position = uniforms.orthoProjectionMatrix * position;
46
57
  output.fragUV = uv;
47
58
  output.fragPosition = position;
48
59
  output.fragColor = color;
@@ -56,7 +67,6 @@ fn fragment_main(
56
67
  @location(2) fragPosition: vec4<f32>
57
68
  ) -> @location(0) vec4<f32> {
58
69
  return fragColor;
59
- // return fragPosition;
60
70
  }
61
71
  `;
62
72
  const simjsFrameRateCss = `@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@100&display=swap');
@@ -107,6 +117,7 @@ export class Simulation {
107
117
  frameRateView;
108
118
  camera;
109
119
  pipelines;
120
+ renderInfo;
110
121
  constructor(idOrCanvasRef, camera = null, showFrameRate = false) {
111
122
  if (typeof idOrCanvasRef === 'string') {
112
123
  const ref = document.getElementById(idOrCanvasRef);
@@ -135,6 +146,7 @@ export class Simulation {
135
146
  this.setCanvasSize(width, height);
136
147
  }
137
148
  });
149
+ this.renderInfo = null;
138
150
  this.pipelines = null;
139
151
  this.frameRateView = new FrameRateView(showFrameRate);
140
152
  this.frameRateView.updateFrameRate(1);
@@ -160,6 +172,7 @@ export class Simulation {
160
172
  if (!ctx)
161
173
  throw logger.error('Context is null');
162
174
  const device = await adapter.requestDevice();
175
+ this.propagateDevice(device);
163
176
  ctx.configure({
164
177
  device,
165
178
  format: 'bgra8unorm'
@@ -175,6 +188,13 @@ export class Simulation {
175
188
  setBackground(color) {
176
189
  this.bgColor = color;
177
190
  }
191
+ propagateDevice(device) {
192
+ for (let i = 0; i < this.scene.length; i++) {
193
+ if (this.scene[i].isInstance) {
194
+ this.scene[i].setDevice(device);
195
+ }
196
+ }
197
+ }
178
198
  render(device, ctx) {
179
199
  this.assertHasCanvas();
180
200
  const canvas = this.canvasRef;
@@ -187,27 +207,60 @@ export class Simulation {
187
207
  format: presentationFormat,
188
208
  alphaMode: 'premultiplied'
189
209
  });
190
- this.pipelines = {
191
- triangleList2d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_2d', 'triangle-list'),
192
- triangleStrip2d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_2d', 'triangle-strip'),
193
- lineStrip2d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_2d', 'line-strip'),
194
- triangleList3d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_3d', 'triangle-list'),
195
- triangleStrip3d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_3d', 'triangle-strip'),
196
- lineStrip3d: createPipeline(device, shaderModule, presentationFormat, 'vertex_main_3d', 'line-strip')
197
- };
198
210
  const uniformBufferSize = 4 * 16 + 4 * 16 + 4 * 2 + 8; // 4x4 matrix + 4x4 matrix + vec2<f32> + 8 bc 144 is cool
199
211
  const uniformBuffer = device.createBuffer({
200
212
  size: uniformBufferSize,
201
213
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
202
214
  });
215
+ const instanceBuffer = device.createBuffer({
216
+ size: 16 * 10 * 4,
217
+ usage: GPUBufferUsage.STORAGE
218
+ });
219
+ const bindGroupLayout = device.createBindGroupLayout({
220
+ entries: [
221
+ {
222
+ binding: 0,
223
+ visibility: GPUShaderStage.VERTEX,
224
+ buffer: {
225
+ type: 'uniform'
226
+ }
227
+ },
228
+ {
229
+ binding: 1,
230
+ visibility: GPUShaderStage.VERTEX,
231
+ buffer: {
232
+ type: 'read-only-storage'
233
+ }
234
+ }
235
+ ]
236
+ });
237
+ this.renderInfo = {
238
+ uniformBuffer,
239
+ bindGroupLayout,
240
+ instanceBuffer
241
+ };
242
+ this.pipelines = {
243
+ triangleList2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'triangle-list'),
244
+ triangleStrip2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'triangle-strip'),
245
+ lineStrip2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'line-strip'),
246
+ triangleList3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'triangle-list'),
247
+ triangleStrip3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'triangle-strip'),
248
+ lineStrip3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'line-strip')
249
+ };
203
250
  const uniformBindGroup = device.createBindGroup({
204
- layout: this.pipelines.triangleList3d.getBindGroupLayout(0),
251
+ layout: bindGroupLayout,
205
252
  entries: [
206
253
  {
207
254
  binding: 0,
208
255
  resource: {
209
256
  buffer: uniformBuffer
210
257
  }
258
+ },
259
+ {
260
+ binding: 1,
261
+ resource: {
262
+ buffer: instanceBuffer
263
+ }
211
264
  }
212
265
  ]
213
266
  });
@@ -281,8 +334,6 @@ export class Simulation {
281
334
  device.queue.writeBuffer(uniformBuffer, 0, modelViewProjectionMatrix.buffer, modelViewProjectionMatrix.byteOffset, modelViewProjectionMatrix.byteLength);
282
335
  device.queue.writeBuffer(uniformBuffer, 4 * 16, // 4x4 matrix
283
336
  orthoMatrix.buffer, orthoMatrix.byteOffset, orthoMatrix.byteLength);
284
- device.queue.writeBuffer(uniformBuffer, 4 * 16 + 4 * 16, // 4x4 matrix + 4x4 matrix
285
- screenSize.buffer, screenSize.byteOffset, screenSize.byteLength);
286
337
  const commandEncoder = device.createCommandEncoder();
287
338
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
288
339
  passEncoder.setPipeline(this.pipelines.triangleList3d);
@@ -294,26 +345,27 @@ export class Simulation {
294
345
  };
295
346
  requestAnimationFrame(frame);
296
347
  }
297
- renderScene(device, passEncoder, scene) {
348
+ async renderScene(device, passEncoder, scene) {
298
349
  if (this.pipelines === null)
299
350
  return;
351
+ let totalVertices = getTotalVertices(scene);
352
+ const vertexBuffer = device.createBuffer({
353
+ size: totalVertices * 4 * BUF_LEN,
354
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
355
+ });
356
+ let currentOffset = 0;
300
357
  for (let i = 0; i < scene.length; i++) {
301
- if (scene[i] instanceof SceneCollection) {
358
+ if (scene[i].isCollection) {
302
359
  this.renderScene(device, passEncoder, scene[i].getScene());
303
360
  continue;
304
361
  }
305
- const buffer = scene[i].getBuffer(this.camera);
306
- const vertexF32Array = new Float32Array(buffer);
307
- const vertexBuffer = device.createBuffer({
308
- size: vertexF32Array.byteLength,
309
- usage: GPUBufferUsage.VERTEX,
310
- mappedAtCreation: true
311
- });
312
- new Float32Array(vertexBuffer.getMappedRange()).set(vertexF32Array);
362
+ const buffer = new Float32Array(scene[i].getBuffer(this.camera));
363
+ const vertexCount = buffer.length / BUF_LEN;
364
+ device.queue.writeBuffer(vertexBuffer, currentOffset, buffer);
313
365
  vertexBuffer.unmap();
314
- const vertexCount = vertexF32Array.length / BUF_LEN;
366
+ const is3d = Boolean(scene[i].is3d);
315
367
  if (scene[i].isWireframe()) {
316
- if (scene[i].is3d) {
368
+ if (is3d) {
317
369
  passEncoder.setPipeline(this.pipelines.lineStrip3d);
318
370
  }
319
371
  else {
@@ -323,7 +375,7 @@ export class Simulation {
323
375
  else {
324
376
  const type = scene[i].getGeometryType();
325
377
  if (type === 'strip') {
326
- if (scene[i].is3d) {
378
+ if (is3d) {
327
379
  passEncoder.setPipeline(this.pipelines.triangleStrip3d);
328
380
  }
329
381
  else {
@@ -331,7 +383,7 @@ export class Simulation {
331
383
  }
332
384
  }
333
385
  else if (type === 'list') {
334
- if (scene[i].is3d) {
386
+ if (is3d) {
335
387
  passEncoder.setPipeline(this.pipelines.triangleList3d);
336
388
  }
337
389
  else {
@@ -339,8 +391,34 @@ export class Simulation {
339
391
  }
340
392
  }
341
393
  }
342
- passEncoder.setVertexBuffer(0, vertexBuffer);
343
- passEncoder.draw(vertexCount);
394
+ let instances = 1;
395
+ if (scene[i].isInstance) {
396
+ instances = scene[i].getNumInstances();
397
+ const buf = scene[i].getMatrixBuffer();
398
+ if (buf && this.renderInfo) {
399
+ const uniformBindGroup = device.createBindGroup({
400
+ layout: this.renderInfo.bindGroupLayout,
401
+ entries: [
402
+ {
403
+ binding: 0,
404
+ resource: {
405
+ buffer: this.renderInfo.uniformBuffer
406
+ }
407
+ },
408
+ {
409
+ binding: 1,
410
+ resource: {
411
+ buffer: buf
412
+ }
413
+ }
414
+ ]
415
+ });
416
+ passEncoder.setBindGroup(0, uniformBindGroup);
417
+ }
418
+ }
419
+ passEncoder.setVertexBuffer(0, vertexBuffer, currentOffset, buffer.byteLength);
420
+ passEncoder.draw(vertexCount, instances, 0, 0);
421
+ currentOffset += buffer.byteLength;
344
422
  }
345
423
  }
346
424
  fitElement() {
@@ -363,6 +441,7 @@ export class SceneCollection extends SimulationElement3d {
363
441
  geometry;
364
442
  name;
365
443
  scene;
444
+ isCollection = true;
366
445
  constructor(name) {
367
446
  super(vector3());
368
447
  this.wireframe = false;
@@ -390,7 +469,6 @@ export class SceneCollection extends SimulationElement3d {
390
469
  return this.getSceneBuffer(camera);
391
470
  }
392
471
  getTriangles(camera) {
393
- console.log('here');
394
472
  return this.getSceneBuffer(camera);
395
473
  }
396
474
  updateMatrix(camera) {
package/dist/types.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  /// <reference types="dist" />
2
2
  import { CubicBezierCurve2d, SplinePoint2d } from './graphics.js';
3
3
  import { Color, Vertex } from './utils.js';
4
- export type Vector4 = Float32Array & [number, number, number, number];
5
- export type Vector3 = Float32Array & [number, number, number];
6
- export type Vector2 = Float32Array & [number, number];
7
- export type Mat4 = Float32Array & [
4
+ export type FloatArray = Float32Array | Float64Array;
5
+ export type Vector4 = FloatArray & [number, number, number, number];
6
+ export type Vector3 = FloatArray & [number, number, number];
7
+ export type Vector2 = FloatArray & [number, number];
8
+ export type Mat4 = FloatArray & [
8
9
  number,
9
10
  number,
10
11
  number,
@@ -68,3 +69,8 @@ export type PipelineGroup = {
68
69
  triangleStrip3d: GPURenderPipeline;
69
70
  lineStrip3d: GPURenderPipeline;
70
71
  };
72
+ export type RenderInfo = {
73
+ uniformBuffer: GPUBuffer;
74
+ instanceBuffer: GPUBuffer;
75
+ bindGroupLayout: GPUBindGroupLayout;
76
+ };
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="dist" />
2
2
  import { SimulationElement, SplinePoint2d } from './graphics.js';
3
- import { Mat4, Vector2, Vector3, Vector4 } from './types.js';
3
+ import { FloatArray, Mat4, Vector2, Vector3, Vector4 } from './types.js';
4
4
  export declare class Color {
5
5
  r: number;
6
6
  g: number;
@@ -76,10 +76,16 @@ export declare function linearStep(t: number): number;
76
76
  export declare function easeInOutExpo(t: number): number;
77
77
  export declare function easeInOutQuart(t: number): number;
78
78
  export declare function easeInOutQuad(t: number): number;
79
- export declare function vertexBuffer(x: number, y: number, z: number, color: Color, uv?: Vector2): number[];
79
+ declare class BufferGenerator {
80
+ private instancing;
81
+ constructor();
82
+ setInstancing(state: boolean): void;
83
+ generate(x: number, y: number, z: number, color: Color, uv?: Vector2): number[];
84
+ }
85
+ export declare const bufferGenerator: BufferGenerator;
80
86
  export declare function vector3ToPixelRatio(vec: Vector3): void;
81
87
  export declare function vector2ToPixelRatio(vec: Vector2): void;
82
- export declare function cloneBuf<T extends Float32Array>(buf: T): T;
88
+ export declare function cloneBuf<T extends FloatArray>(buf: T): T;
83
89
  export declare function vector4(x?: number, y?: number, z?: number, w?: number): Vector4;
84
90
  export declare function vector3(x?: number, y?: number, z?: number): Vector3;
85
91
  export declare function vector2(x?: number, y?: number): Vector2;
@@ -101,6 +107,7 @@ export declare function interpolateColors(colors: Color[], t: number): Color;
101
107
  export declare function waitFor(t: number): Promise<unknown>;
102
108
  export declare function matrixFromRotation(rotation: Vector3): Mat4;
103
109
  export declare function rotateMat4(mat: Mat4, rotation: Vector3): void;
104
- export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, presentationFormat: GPUTextureFormat, entryPoint: string, topology: GPUPrimitiveTopology): GPURenderPipeline;
110
+ export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayout: GPUBindGroupLayout, presentationFormat: GPUTextureFormat, entryPoint: string, topology: GPUPrimitiveTopology): GPURenderPipeline;
105
111
  export declare function triangulateWireFrameOrder(len: number): number[];
112
+ export declare function getTotalVertices(scene: SimulationElement[]): number;
106
113
  export {};
package/dist/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mat4, vec2, vec3, vec4 } from 'wgpu-matrix';
2
2
  import { SimulationElement, SplinePoint2d } from './graphics.js';
3
- import { BUF_LEN, colorOffset, uvOffset, vertexSize } from './constants.js';
3
+ import { BUF_LEN, colorOffset, drawingInstancesOffset, uvOffset, vertexSize } from './constants.js';
4
4
  export class Color {
5
5
  r; // 0 - 255
6
6
  g; // 0 - 255
@@ -100,9 +100,9 @@ export class Vertex {
100
100
  }
101
101
  toBuffer(defaultColor) {
102
102
  if (this.is3d)
103
- return vertexBuffer(this.pos[0], this.pos[1], this.pos[2], this.color || defaultColor, this.uv);
103
+ return bufferGenerator.generate(this.pos[0], this.pos[1], this.pos[2], this.color || defaultColor, this.uv);
104
104
  else
105
- return vertexBuffer(this.pos[0], this.pos[1], 0, this.color || defaultColor, this.uv);
105
+ return bufferGenerator.generate(this.pos[0], this.pos[1], 0, this.color || defaultColor, this.uv);
106
106
  }
107
107
  }
108
108
  export const buildProjectionMatrix = (aspectRatio, zNear = 1, zFar = 500) => {
@@ -260,9 +260,17 @@ export function easeInOutQuart(t) {
260
260
  export function easeInOutQuad(t) {
261
261
  return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
262
262
  }
263
- export function vertexBuffer(x, y, z, color, uv = vector2()) {
264
- return [x, y, z, 1, ...color.toBuffer(), ...uv];
263
+ class BufferGenerator {
264
+ instancing = false;
265
+ constructor() { }
266
+ setInstancing(state) {
267
+ this.instancing = state;
268
+ }
269
+ generate(x, y, z, color, uv = vector2()) {
270
+ return [x, y, z, 1, ...color.toBuffer(), ...uv, this.instancing ? 1 : 0];
271
+ }
265
272
  }
273
+ export const bufferGenerator = new BufferGenerator();
266
274
  export function vector3ToPixelRatio(vec) {
267
275
  vec[0] *= devicePixelRatio;
268
276
  vec[1] *= devicePixelRatio;
@@ -371,9 +379,11 @@ export function rotateMat4(mat, rotation) {
371
379
  mat4.rotateY(mat, rotation[1], mat);
372
380
  mat4.rotateX(mat, rotation[0], mat);
373
381
  }
374
- export function createPipeline(device, module, presentationFormat, entryPoint, topology) {
382
+ export function createPipeline(device, module, bindGroupLayout, presentationFormat, entryPoint, topology) {
375
383
  return device.createRenderPipeline({
376
- layout: 'auto',
384
+ layout: device.createPipelineLayout({
385
+ bindGroupLayouts: [bindGroupLayout]
386
+ }),
377
387
  vertex: {
378
388
  module,
379
389
  entryPoint,
@@ -398,6 +408,12 @@ export function createPipeline(device, module, presentationFormat, entryPoint, t
398
408
  shaderLocation: 2,
399
409
  offset: uvOffset,
400
410
  format: 'float32x2'
411
+ },
412
+ {
413
+ // drawing instances
414
+ shaderLocation: 3,
415
+ offset: drawingInstancesOffset,
416
+ format: 'float32'
401
417
  }
402
418
  ]
403
419
  }
@@ -438,3 +454,12 @@ export function triangulateWireFrameOrder(len) {
438
454
  }
439
455
  return order;
440
456
  }
457
+ export function getTotalVertices(scene) {
458
+ let total = 0;
459
+ for (let i = 0; i < scene.length; i++) {
460
+ if (scene[i].isCollection)
461
+ continue;
462
+ total += scene[i].getVertexCount();
463
+ }
464
+ return total;
465
+ }
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.2.7",
8
+ "version": "0.2.9",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",