simulationjsv2 0.4.9 → 0.5.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/TODO.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # TODO
2
2
 
3
- - [ ] Memoize render scene buffer
4
- - Should only be reset to resize to a new larger object then reused on all frames
3
+ - [ ] Make input position vec3 not vec4
4
+ - [ ] Change position/rotation to be matrix transform on gpu
5
+ - [ ] Make getBuffer return cached Float32Array
@@ -1,4 +1,4 @@
1
- import { CircleGeometryParams, CubeGeometryParams, EmptyParams, Line2dGeometryParams, Line3dGeometryParams, Mat4, PolygonGeometryParams, Spline2dGeometryParams, SquareGeometryParams, Vector2, Vector3, VertexColorMap } from './types.js';
1
+ import { VertexParamGeneratorInfo, CircleGeometryParams, CubeGeometryParams, EmptyParams, Line2dGeometryParams, Line3dGeometryParams, Mat4, PolygonGeometryParams, Spline2dGeometryParams, SquareGeometryParams, Vector2, Vector3, VertexColorMap } from './types.js';
2
2
  import { Color, Vertex } from './utils.js';
3
3
  import { CubicBezierCurve2d, SplinePoint2d } from './graphics.js';
4
4
  export declare abstract class Geometry<T extends EmptyParams> {
@@ -14,9 +14,9 @@ export declare abstract class Geometry<T extends EmptyParams> {
14
14
  abstract recompute(): void;
15
15
  getTriangleVertexCount(): number;
16
16
  getWireframeVertexCount(): number;
17
- protected bufferFromOrder(order: number[], color: Color): number[];
18
- getWireframeBuffer(color: Color): number[];
19
- getTriangleBuffer(color: Color): number[];
17
+ protected bufferFromOrder(order: number[], color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
18
+ getWireframeBuffer(color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
19
+ getTriangleBuffer(color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
20
20
  }
21
21
  export declare class PlaneGeometry extends Geometry<EmptyParams> {
22
22
  protected params: {};
@@ -26,7 +26,7 @@ export declare class PlaneGeometry extends Geometry<EmptyParams> {
26
26
  constructor(vertices: Vertex[]);
27
27
  recompute(): void;
28
28
  updateVertices(vertices: Vertex[]): void;
29
- getTriangleBuffer(color: Color): number[];
29
+ getTriangleBuffer(color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
30
30
  }
31
31
  export declare class CubeGeometry extends Geometry<CubeGeometryParams> {
32
32
  protected params: CubeGeometryParams;
@@ -48,7 +48,7 @@ export declare class SquareGeometry extends Geometry<SquareGeometryParams> {
48
48
  setWidth(width: number): void;
49
49
  setHeight(height: number): void;
50
50
  recompute(): void;
51
- getTriangleBuffer(color: Color): number[];
51
+ getTriangleBuffer(color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
52
52
  }
53
53
  export declare class BlankGeometry extends Geometry<EmptyParams> {
54
54
  protected wireframeOrder: never[];
@@ -83,8 +83,8 @@ export declare class Spline2dGeometry extends Geometry<Spline2dGeometryParams> {
83
83
  private computeCurves;
84
84
  private updateWireframeOrder;
85
85
  recompute(): void;
86
- getWireframeBuffer(color: Color): number[];
87
- getTriangleBuffer(_: Color): number[];
86
+ getWireframeBuffer(color: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
87
+ getTriangleBuffer(_: Color, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
88
88
  }
89
89
  export declare class Line2dGeometry extends Geometry<Line2dGeometryParams> {
90
90
  protected wireframeOrder: number[];
package/dist/geometry.js CHANGED
@@ -24,20 +24,20 @@ export class Geometry {
24
24
  getWireframeVertexCount() {
25
25
  return this.wireframeOrder.length;
26
26
  }
27
- bufferFromOrder(order, color) {
27
+ bufferFromOrder(order, color, vertexParamGenerator) {
28
28
  return order
29
29
  .map((vertexIndex) => {
30
30
  const pos = cloneBuf(this.vertices[vertexIndex]);
31
31
  vec3.transformMat4(pos, this.matrix, pos);
32
- return bufferGenerator.generate(pos[0], pos[1], pos[2], color);
32
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], color, vector2(), vertexParamGenerator);
33
33
  })
34
34
  .flat();
35
35
  }
36
- getWireframeBuffer(color) {
37
- return this.bufferFromOrder(this.wireframeOrder, color);
36
+ getWireframeBuffer(color, vertexParamGenerator) {
37
+ return this.bufferFromOrder(this.wireframeOrder, color, vertexParamGenerator);
38
38
  }
39
- getTriangleBuffer(color) {
40
- return this.bufferFromOrder(this.triangleOrder, color);
39
+ getTriangleBuffer(color, vertexParamGenerator) {
40
+ return this.bufferFromOrder(this.triangleOrder, color, vertexParamGenerator);
41
41
  }
42
42
  }
43
43
  export class PlaneGeometry extends Geometry {
@@ -61,13 +61,13 @@ export class PlaneGeometry extends Geometry {
61
61
  .fill(0)
62
62
  .map((_, index) => index)).flat();
63
63
  }
64
- getTriangleBuffer(color) {
64
+ getTriangleBuffer(color, vertexParamGenerator) {
65
65
  return this.triangleOrder
66
66
  .map((index) => {
67
67
  const vertex = this.rawVertices[index];
68
68
  const pos = cloneBuf(vertex.getPos());
69
69
  vec3.transformMat4(pos, this.matrix, pos);
70
- return bufferGenerator.generate(pos[0], pos[1], pos[2], vertex.getColor() || color);
70
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], vertex.getColor() || color, vector2(), vertexParamGenerator);
71
71
  })
72
72
  .flat();
73
73
  }
@@ -155,12 +155,12 @@ export class SquareGeometry extends Geometry {
155
155
  vector3(-this.params.width * centerOffset[0], -this.params.height * (1 - centerOffset[1]))
156
156
  ];
157
157
  }
158
- getTriangleBuffer(color) {
158
+ getTriangleBuffer(color, vertexParamGenerator) {
159
159
  return this.triangleOrder
160
160
  .map((vertexIndex) => {
161
161
  const pos = cloneBuf(this.vertices[vertexIndex]);
162
162
  vec3.transformMat4(pos, this.matrix, pos);
163
- return bufferGenerator.generate(pos[0], pos[1], pos[2], this.params.colorMap[vertexIndex] || color);
163
+ return bufferGenerator.generate(pos[0], pos[1], pos[2], this.params.colorMap[vertexIndex] || color, vector2(), vertexParamGenerator);
164
164
  })
165
165
  .flat();
166
166
  }
@@ -349,22 +349,21 @@ export class Spline2dGeometry extends Geometry {
349
349
  .map((_, index) => index)).flat();
350
350
  this.updateWireframeOrder();
351
351
  }
352
- getWireframeBuffer(color) {
352
+ getWireframeBuffer(color, vertexParamGenerator) {
353
353
  return this.wireframeOrder
354
354
  .map((vertexIndex) => {
355
355
  const vertex = cloneBuf(this.vertices[vertexIndex]);
356
356
  vec3.transformMat4(vertex, this.matrix, vertex);
357
- return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], color);
357
+ return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], color, vector2(), vertexParamGenerator);
358
358
  })
359
359
  .flat();
360
360
  }
361
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
362
- getTriangleBuffer(_) {
361
+ getTriangleBuffer(_, vertexParamGenerator) {
363
362
  return this.triangleOrder
364
363
  .map((vertexIndex) => {
365
364
  const vertex = cloneBuf(this.vertices[vertexIndex]);
366
365
  vec3.transformMat4(vertex, this.matrix, vertex);
367
- return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], this.params.vertexColors[vertexIndex]);
366
+ return bufferGenerator.generate(vertex[0], vertex[1], vertex[2], this.params.vertexColors[vertexIndex], vector2(), vertexParamGenerator);
368
367
  })
369
368
  .flat();
370
369
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="@webgpu/types" />
2
2
  import { Camera } from './simulation.js';
3
- import type { Vector2, Vector3, LerpFunc, VertexColorMap, ElementRotation, Mat4, AnySimulationElement } from './types.js';
3
+ import type { Vector2, Vector3, LerpFunc, VertexColorMap, ElementRotation, Mat4, AnySimulationElement, VertexParamGeneratorInfo } from './types.js';
4
4
  import { Vertex, Color } from './utils.js';
5
5
  import { BlankGeometry, CircleGeometry, CubeGeometry, Geometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, Spline2dGeometry, SquareGeometry } from './geometry.js';
6
6
  import { VertexCache } from './internalUtils.js';
@@ -30,7 +30,7 @@ export declare abstract class SimulationElement<T extends Vector2 | Vector3 = Ve
30
30
  protected abstract updateMatrix(camera: Camera): void;
31
31
  getVertexCount(): number;
32
32
  protected defaultUpdateMatrix(camera: Camera): void;
33
- getBuffer(camera: Camera): number[];
33
+ getBuffer(camera: Camera, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
34
34
  }
35
35
  export declare abstract class SimulationElement3d extends SimulationElement {
36
36
  protected pos: Vector3;
@@ -49,7 +49,7 @@ export declare abstract class SimulationElement2d extends SimulationElement<Vect
49
49
  rotate(rotation: number, t?: number, f?: LerpFunc): Promise<void>;
50
50
  rotateTo(newRotation: number, t?: number, f?: LerpFunc): Promise<void>;
51
51
  move(amount: Vector2, t?: number, f?: LerpFunc): Promise<void>;
52
- moveTo(pos: Vector2, t?: number, f?: LerpFunc): Promise<void>;
52
+ moveTo(newPos: Vector2, t?: number, f?: LerpFunc): Promise<void>;
53
53
  }
54
54
  export declare class Plane extends SimulationElement3d {
55
55
  protected geometry: PlaneGeometry;
package/dist/graphics.js CHANGED
@@ -73,19 +73,21 @@ export class SimulationElement {
73
73
  }
74
74
  this.geometry.updateMatrix(matrix);
75
75
  }
76
- getBuffer(camera) {
77
- if (this.vertexCache.shouldUpdate() || camera.hasUpdated()) {
76
+ getBuffer(camera, vertexParamGenerator) {
77
+ const shouldEvalExtender = vertexParamGenerator?.shouldEvaluate?.();
78
+ const reEvalExtender = shouldEvalExtender === undefined ? true : shouldEvalExtender;
79
+ if (this.vertexCache.shouldUpdate() || camera.hasUpdated() || reEvalExtender) {
78
80
  this.updateMatrix(camera);
79
81
  this.geometry.recompute();
80
82
  if (this.isInstanced) {
81
83
  bufferGenerator.setInstancing(true);
82
84
  }
83
- let resBuffer = [];
85
+ let resBuffer;
84
86
  if (this.isWireframe()) {
85
- resBuffer = this.geometry.getWireframeBuffer(this.color);
87
+ resBuffer = this.geometry.getWireframeBuffer(this.color, vertexParamGenerator);
86
88
  }
87
89
  else {
88
- resBuffer = this.geometry.getTriangleBuffer(this.color);
90
+ resBuffer = this.geometry.getTriangleBuffer(this.color, vertexParamGenerator);
89
91
  }
90
92
  bufferGenerator.setInstancing(false);
91
93
  this.vertexCache.setCache(resBuffer);
@@ -186,18 +188,22 @@ export class SimulationElement2d extends SimulationElement {
186
188
  }, t, f);
187
189
  }
188
190
  move(amount, t = 0, f) {
191
+ const tempAmount = cloneBuf(amount);
192
+ vector2ToPixelRatio(tempAmount);
189
193
  const finalPos = vector2();
190
- vec3.add(amount, this.pos, finalPos);
194
+ vec3.add(tempAmount, this.pos, finalPos);
191
195
  return transitionValues((p) => {
192
- this.pos[0] += amount[0] * p;
193
- this.pos[1] += amount[1] * p;
196
+ this.pos[0] += tempAmount[0] * p;
197
+ this.pos[1] += tempAmount[1] * p;
194
198
  this.vertexCache.updated();
195
199
  }, () => {
196
200
  this.pos = finalPos;
197
201
  this.vertexCache.updated();
198
202
  }, t, f);
199
203
  }
200
- moveTo(pos, t = 0, f) {
204
+ moveTo(newPos, t = 0, f) {
205
+ const pos = cloneBuf(newPos);
206
+ vector2ToPixelRatio(pos);
201
207
  const diff = vector2();
202
208
  vec2.sub(pos, this.pos, diff);
203
209
  return transitionValues((p) => {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@webgpu/types" />
2
- import { AnySimulationElement, Mat4, Vector2, Vector3 } from './types.js';
2
+ import { AnySimulationElement, VertexParamGeneratorInfo, Mat4, Vector2, Vector3, VertexParamInfo } from './types.js';
3
3
  import { Color } from './utils.js';
4
4
  export declare class VertexCache {
5
5
  private vertices;
@@ -52,14 +52,14 @@ declare class BufferGenerator {
52
52
  private instancing;
53
53
  constructor();
54
54
  setInstancing(state: boolean): void;
55
- generate(x: number, y: number, z: number, color: Color, uv?: Vector2): number[];
55
+ generate(x: number, y: number, z: number, color: Color, uv?: Vector2, vertexParamGenerator?: VertexParamGeneratorInfo): number[];
56
56
  }
57
57
  export declare const bufferGenerator: BufferGenerator;
58
58
  export declare function vector3ToPixelRatio(vec: Vector3): void;
59
59
  export declare function vector2ToPixelRatio(vec: Vector2): void;
60
60
  export declare function matrixFromRotation(rotation: Vector3): Mat4;
61
61
  export declare function rotateMat4(mat: Mat4, rotation: Vector3): void;
62
- export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayout: GPUBindGroupLayout, presentationFormat: GPUTextureFormat, entryPoint: string, topology: GPUPrimitiveTopology): GPURenderPipeline;
62
+ export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayouts: GPUBindGroupLayout[], presentationFormat: GPUTextureFormat, entryPoint: string, topology: GPUPrimitiveTopology, vertexParams?: VertexParamInfo[]): GPURenderPipeline;
63
63
  export declare function triangulateWireFrameOrder(len: number): number[];
64
64
  export declare function getTotalVertices(scene: SimSceneObjInfo[]): number;
65
65
  export {};
@@ -182,7 +182,15 @@ class BufferGenerator {
182
182
  setInstancing(state) {
183
183
  this.instancing = state;
184
184
  }
185
- generate(x, y, z, color, uv = vector2()) {
185
+ generate(x, y, z, color, uv = vector2(), vertexParamGenerator) {
186
+ if (vertexParamGenerator) {
187
+ const buf = vertexParamGenerator.createBuffer(x, y, z, color);
188
+ if (buf.length !== vertexParamGenerator.bufferSize) {
189
+ logger.log_error(`Vertex size for shader group does not match buffer extension size (${buf.length} to expected ${vertexParamGenerator.bufferSize})`);
190
+ return [];
191
+ }
192
+ return buf;
193
+ }
186
194
  return [x, y, z, 1, ...color.toBuffer(), ...uv, this.instancing ? 1 : 0];
187
195
  }
188
196
  }
@@ -208,43 +216,58 @@ export function rotateMat4(mat, rotation) {
208
216
  mat4.rotateY(mat, rotation[1], mat);
209
217
  mat4.rotateX(mat, rotation[0], mat);
210
218
  }
211
- export function createPipeline(device, module, bindGroupLayout, presentationFormat, entryPoint, topology) {
219
+ export function createPipeline(device, module, bindGroupLayouts, presentationFormat, entryPoint, topology, vertexParams) {
220
+ let params = [
221
+ {
222
+ // position
223
+ shaderLocation: 0,
224
+ offset: 0,
225
+ format: 'float32x4'
226
+ },
227
+ {
228
+ // color
229
+ shaderLocation: 1,
230
+ offset: colorOffset,
231
+ format: 'float32x4'
232
+ },
233
+ {
234
+ // size
235
+ shaderLocation: 2,
236
+ offset: uvOffset,
237
+ format: 'float32x2'
238
+ },
239
+ {
240
+ // drawing instances
241
+ shaderLocation: 3,
242
+ offset: drawingInstancesOffset,
243
+ format: 'float32'
244
+ }
245
+ ];
246
+ let stride = vertexSize;
247
+ if (vertexParams) {
248
+ params = [];
249
+ let offset = 0;
250
+ for (let i = 0; i < vertexParams.length; i++) {
251
+ params.push({
252
+ shaderLocation: i,
253
+ offset,
254
+ format: vertexParams[i].format
255
+ });
256
+ offset += vertexParams[i].size;
257
+ }
258
+ stride = offset;
259
+ }
212
260
  return device.createRenderPipeline({
213
261
  layout: device.createPipelineLayout({
214
- bindGroupLayouts: [bindGroupLayout]
262
+ bindGroupLayouts: bindGroupLayouts
215
263
  }),
216
264
  vertex: {
217
265
  module,
218
266
  entryPoint,
219
267
  buffers: [
220
268
  {
221
- arrayStride: vertexSize,
222
- attributes: [
223
- {
224
- // position
225
- shaderLocation: 0,
226
- offset: 0,
227
- format: 'float32x4'
228
- },
229
- {
230
- // color
231
- shaderLocation: 1,
232
- offset: colorOffset,
233
- format: 'float32x4'
234
- },
235
- {
236
- // size
237
- shaderLocation: 2,
238
- offset: uvOffset,
239
- format: 'float32x2'
240
- },
241
- {
242
- // drawing instances
243
- shaderLocation: 3,
244
- offset: drawingInstancesOffset,
245
- format: 'float32'
246
- }
247
- ]
269
+ arrayStride: stride,
270
+ attributes: params
248
271
  }
249
272
  ]
250
273
  },
@@ -1,6 +1,6 @@
1
1
  /// <reference types="@webgpu/types" />
2
2
  import { SimulationElement3d } from './graphics.js';
3
- import type { Vector2, Vector3, LerpFunc, AnySimulationElement } from './types.js';
3
+ import type { Vector2, Vector3, LerpFunc, AnySimulationElement, VertexParamGeneratorInfo, VertexParamInfo, BindGroupInfo } from './types.js';
4
4
  import { Color } from './utils.js';
5
5
  import { BlankGeometry } from './geometry.js';
6
6
  import { SimSceneObjInfo } from './internalUtils.js';
@@ -16,7 +16,12 @@ export declare class Simulation {
16
16
  private device;
17
17
  private pipelines;
18
18
  private renderInfo;
19
+ private resizeEvents;
19
20
  constructor(idOrCanvasRef: string | HTMLCanvasElement, camera?: Camera | null, showFrameRate?: boolean);
21
+ private handleCanvasResize;
22
+ onResize(cb: (width: number, height: number) => void): void;
23
+ getWidth(): number;
24
+ getHeight(): number;
20
25
  add(el: AnySimulationElement, id?: string): void;
21
26
  remove(el: AnySimulationElement): void;
22
27
  removeId(id: string): void;
@@ -24,6 +29,7 @@ export declare class Simulation {
24
29
  * @param lifetime - ms
25
30
  */
26
31
  setLifetime(el: AnySimulationElement, lifetime: number): void;
32
+ private applyCanvasSize;
27
33
  setCanvasSize(width: number, height: number): void;
28
34
  start(): void;
29
35
  private propagateDevice;
@@ -39,13 +45,13 @@ export declare class SceneCollection extends SimulationElement3d {
39
45
  protected geometry: BlankGeometry;
40
46
  private name;
41
47
  private scene;
42
- private device;
43
- constructor(name: string);
48
+ protected device: GPUDevice | null;
49
+ constructor(name?: string);
44
50
  setWireframe(_: boolean): void;
45
- getName(): string;
51
+ getName(): string | null;
46
52
  getScene(): SimSceneObjInfo[];
47
53
  setDevice(device: GPUDevice): void;
48
- private propagateDevice;
54
+ protected propagateDevice(device: GPUDevice): void;
49
55
  getVertexCount(): number;
50
56
  getSceneObjects(): AnySimulationElement[];
51
57
  setSceneObjects(newScene: AnySimulationElement[]): void;
@@ -82,3 +88,24 @@ export declare class Camera {
82
88
  getPos(): Vector3;
83
89
  getAspectRatio(): number;
84
90
  }
91
+ export declare class ShaderGroup extends SceneCollection {
92
+ protected geometry: BlankGeometry;
93
+ private code;
94
+ private module;
95
+ private pipeline;
96
+ private bindGroupLayout;
97
+ private topology;
98
+ private paramGenerator;
99
+ private vertexParams;
100
+ private bindGroup;
101
+ private valueBuffers;
102
+ constructor(shaderCode: string, topology: GPUPrimitiveTopology | undefined, vertexParams: VertexParamInfo[], paramGenerator: VertexParamGeneratorInfo, bindGroup?: BindGroupInfo);
103
+ protected propagateDevice(device: GPUDevice): void;
104
+ getBindGroupLayout(): GPUBindGroupLayout | null;
105
+ getPipeline(): GPURenderPipeline | null;
106
+ getBindGroupBuffers(): GPUBuffer[] | null;
107
+ private createBuffer;
108
+ protected updateMatrix(camera: Camera): void;
109
+ getVertexParamGenerator(): VertexParamGeneratorInfo;
110
+ hasBindGroup(): boolean;
111
+ }
@@ -12,7 +12,7 @@ struct Uniforms {
12
12
 
13
13
  @group(0) @binding(0) var<uniform> uniforms : Uniforms;
14
14
 
15
- @group(0) @binding(1) var<storage, read> instanceMatrices : array<mat4x4f>;
15
+ @group(0) @binding(1) var<storage> instanceMatrices : array<mat4x4f>;
16
16
 
17
17
  struct VertexOutput {
18
18
  @builtin(position) Position : vec4<f32>,
@@ -31,9 +31,15 @@ fn vertex_main_3d(
31
31
  ) -> VertexOutput {
32
32
  var output : VertexOutput;
33
33
 
34
- output.Position = uniforms.modelViewProjectionMatrix * position;
34
+ if (drawingInstance == 1) {
35
+ let transformedPos = instanceMatrices[instanceIdx] * position;
36
+ output.Position = uniforms.modelViewProjectionMatrix * transformedPos;
37
+ } else {
38
+ output.Position = uniforms.modelViewProjectionMatrix * position;
39
+ }
40
+
35
41
  output.fragUV = uv;
36
- output.fragPosition = position;
42
+ output.fragPosition = output.Position;
37
43
  output.fragColor = color;
38
44
  return output;
39
45
  }
@@ -56,7 +62,7 @@ fn vertex_main_2d(
56
62
  }
57
63
 
58
64
  output.fragUV = uv;
59
- output.fragPosition = position;
65
+ output.fragPosition = output.Position;
60
66
  output.fragColor = color;
61
67
  return output;
62
68
  }
@@ -70,9 +76,7 @@ fn fragment_main(
70
76
  return fragColor;
71
77
  }
72
78
  `;
73
- const simjsFrameRateCss = `@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@100&display=swap');
74
-
75
- .simjs-frame-rate {
79
+ const simjsFrameRateCss = `.simjs-frame-rate {
76
80
  position: absolute;
77
81
  top: 0;
78
82
  left: 0;
@@ -80,13 +84,14 @@ const simjsFrameRateCss = `@import url('https://fonts.googleapis.com/css2?family
80
84
  color: white;
81
85
  padding: 8px 12px;
82
86
  z-index: 1000;
83
- font-family: Roboto Mono;
87
+ font-family: monospace;
84
88
  font-size: 16px;
85
89
  }`;
86
90
  class FrameRateView {
87
91
  el;
88
92
  fpsBuffer = [];
89
93
  maxFpsBufferLength = 8;
94
+ prevAvg = 0;
90
95
  constructor(show) {
91
96
  this.el = document.createElement('div');
92
97
  this.el.classList.add('simjs-frame-rate');
@@ -106,7 +111,10 @@ class FrameRateView {
106
111
  this.fpsBuffer.push(num);
107
112
  }
108
113
  const fps = Math.round(this.fpsBuffer.reduce((acc, curr) => acc + curr, 0) / this.fpsBuffer.length);
109
- this.el.innerHTML = `${fps} FPS`;
114
+ if (fps !== this.prevAvg) {
115
+ this.el.innerHTML = `${fps} FPS`;
116
+ this.prevAvg = fps;
117
+ }
110
118
  }
111
119
  }
112
120
  export class Simulation {
@@ -121,6 +129,7 @@ export class Simulation {
121
129
  device = null;
122
130
  pipelines = null;
123
131
  renderInfo = null;
132
+ resizeEvents;
124
133
  constructor(idOrCanvasRef, camera = null, showFrameRate = false) {
125
134
  if (typeof idOrCanvasRef === 'string') {
126
135
  const ref = document.getElementById(idOrCanvasRef);
@@ -142,16 +151,29 @@ export class Simulation {
142
151
  this.camera = camera;
143
152
  if (parent === null)
144
153
  throw logger.error('Canvas parent is null');
154
+ this.resizeEvents = [];
145
155
  addEventListener('resize', () => {
146
- if (this.fittingElement) {
147
- const width = parent.clientWidth;
148
- const height = parent.clientHeight;
149
- this.setCanvasSize(width, height);
150
- }
156
+ this.handleCanvasResize(parent);
151
157
  });
152
158
  this.frameRateView = new FrameRateView(showFrameRate);
153
159
  this.frameRateView.updateFrameRate(1);
154
160
  }
161
+ handleCanvasResize(parent) {
162
+ if (this.fittingElement) {
163
+ const width = parent.clientWidth;
164
+ const height = parent.clientHeight;
165
+ this.setCanvasSize(width, height);
166
+ }
167
+ }
168
+ onResize(cb) {
169
+ this.resizeEvents.push(cb);
170
+ }
171
+ getWidth() {
172
+ return (this.canvasRef?.width || 0) / devicePixelRatio;
173
+ }
174
+ getHeight() {
175
+ return (this.canvasRef?.height || 0) / devicePixelRatio;
176
+ }
155
177
  add(el, id) {
156
178
  addObject(this.scene, el, this.device, id);
157
179
  }
@@ -170,7 +192,7 @@ export class Simulation {
170
192
  this.scene[i].setLifetime(lifetime);
171
193
  }
172
194
  }
173
- setCanvasSize(width, height) {
195
+ applyCanvasSize(width, height) {
174
196
  if (this.canvasRef === null)
175
197
  return;
176
198
  this.canvasRef.width = width * devicePixelRatio;
@@ -178,6 +200,12 @@ export class Simulation {
178
200
  this.canvasRef.style.width = width + 'px';
179
201
  this.canvasRef.style.height = height + 'px';
180
202
  }
203
+ setCanvasSize(width, height) {
204
+ this.applyCanvasSize(width, height);
205
+ for (let i = 0; i < this.resizeEvents.length; i++) {
206
+ this.resizeEvents[i](width, height);
207
+ }
208
+ }
181
209
  start() {
182
210
  if (this.initialized) {
183
211
  this.running = true;
@@ -274,12 +302,12 @@ export class Simulation {
274
302
  vertexBuffer: null
275
303
  };
276
304
  this.pipelines = {
277
- triangleList2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'triangle-list'),
278
- triangleStrip2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'triangle-strip'),
279
- lineStrip2d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_2d', 'line-strip'),
280
- triangleList3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'triangle-list'),
281
- triangleStrip3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'triangle-strip'),
282
- lineStrip3d: createPipeline(device, shaderModule, bindGroupLayout, presentationFormat, 'vertex_main_3d', 'line-strip')
305
+ triangleList2d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_2d', 'triangle-list'),
306
+ triangleStrip2d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_2d', 'triangle-strip'),
307
+ lineStrip2d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_2d', 'line-strip'),
308
+ triangleList3d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_3d', 'triangle-list'),
309
+ triangleStrip3d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_3d', 'triangle-strip'),
310
+ lineStrip3d: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'vertex_main_3d', 'line-strip')
283
311
  };
284
312
  const uniformBindGroup = device.createBindGroup({
285
313
  layout: bindGroupLayout,
@@ -394,7 +422,7 @@ export class Simulation {
394
422
  };
395
423
  requestAnimationFrame(frame);
396
424
  }
397
- renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff) {
425
+ renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff, shaderInfo) {
398
426
  if (this.pipelines === null)
399
427
  return 0;
400
428
  let currentOffset = startOffset;
@@ -411,15 +439,35 @@ export class Simulation {
411
439
  }
412
440
  const obj = scene[i].getObj();
413
441
  if (obj instanceof SceneCollection) {
414
- currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getScene(), currentOffset, diff);
442
+ let shaderInfo = undefined;
443
+ if (obj instanceof ShaderGroup) {
444
+ const pipeline = obj.getPipeline();
445
+ if (pipeline !== null) {
446
+ shaderInfo = {
447
+ pipeline,
448
+ paramGenerator: obj.getVertexParamGenerator(),
449
+ bufferInfo: obj.hasBindGroup()
450
+ ? {
451
+ buffers: obj.getBindGroupBuffers(),
452
+ layout: obj.getBindGroupLayout()
453
+ }
454
+ : null
455
+ };
456
+ }
457
+ }
458
+ currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getScene(), currentOffset, diff, shaderInfo || undefined);
415
459
  continue;
416
460
  }
417
- const buffer = new Float32Array(obj.getBuffer(this.camera));
418
- const vertexCount = buffer.length / BUF_LEN;
461
+ const buffer = new Float32Array(obj.getBuffer(this.camera, shaderInfo?.paramGenerator));
462
+ const bufLen = shaderInfo?.paramGenerator?.bufferSize || BUF_LEN;
463
+ const vertexCount = buffer.length / bufLen;
419
464
  device.queue.writeBuffer(vertexBuffer, currentOffset, buffer);
420
465
  vertexBuffer.unmap();
421
466
  const is3d = Boolean(obj.is3d);
422
- if (obj.isWireframe()) {
467
+ if (shaderInfo) {
468
+ passEncoder.setPipeline(shaderInfo.pipeline);
469
+ }
470
+ else if (obj.isWireframe()) {
423
471
  if (is3d) {
424
472
  passEncoder.setPipeline(this.pipelines.lineStrip3d);
425
473
  }
@@ -471,6 +519,19 @@ export class Simulation {
471
519
  passEncoder.setBindGroup(0, uniformBindGroup);
472
520
  }
473
521
  }
522
+ if (shaderInfo && shaderInfo.bufferInfo) {
523
+ const bindGroupEntries = shaderInfo.bufferInfo.buffers.map((buffer, index) => ({
524
+ binding: index,
525
+ resource: {
526
+ buffer
527
+ }
528
+ }));
529
+ const bindGroup = device.createBindGroup({
530
+ layout: shaderInfo.bufferInfo.layout,
531
+ entries: bindGroupEntries
532
+ });
533
+ passEncoder.setBindGroup(1, bindGroup);
534
+ }
474
535
  passEncoder.setVertexBuffer(0, vertexBuffer, currentOffset, buffer.byteLength);
475
536
  passEncoder.draw(vertexCount, instances, 0, 0);
476
537
  currentOffset += buffer.byteLength;
@@ -500,7 +561,7 @@ export class SceneCollection extends SimulationElement3d {
500
561
  constructor(name) {
501
562
  super(vector3());
502
563
  this.wireframe = false;
503
- this.name = name;
564
+ this.name = name || null;
504
565
  this.scene = [];
505
566
  this.geometry = new BlankGeometry();
506
567
  }
@@ -564,9 +625,11 @@ export class SceneCollection extends SimulationElement3d {
564
625
  getSceneBuffer(camera) {
565
626
  return this.scene.map((item) => item.getObj().getBuffer(camera)).flat();
566
627
  }
628
+ // TODO - improve
567
629
  getWireframe(camera) {
568
630
  return this.getSceneBuffer(camera);
569
631
  }
632
+ // TODO - improve
570
633
  getTriangles(camera) {
571
634
  return this.getSceneBuffer(camera);
572
635
  }
@@ -661,3 +724,132 @@ export class Camera {
661
724
  return this.aspectRatio;
662
725
  }
663
726
  }
727
+ const defaultShaderCode = `
728
+ struct Uniforms {
729
+ modelViewProjectionMatrix : mat4x4<f32>,
730
+ orthoProjectionMatrix : mat4x4<f32>
731
+ }
732
+
733
+ @group(0) @binding(0) var<uniform> uniforms : Uniforms;
734
+
735
+ @group(0) @binding(1) var<storage, read> instanceMatrices : array<mat4x4f>;
736
+ `;
737
+ export class ShaderGroup extends SceneCollection {
738
+ geometry;
739
+ code;
740
+ module;
741
+ pipeline;
742
+ bindGroupLayout;
743
+ topology;
744
+ paramGenerator;
745
+ vertexParams;
746
+ bindGroup;
747
+ valueBuffers;
748
+ constructor(shaderCode, topology = 'triangle-list', vertexParams, paramGenerator, bindGroup) {
749
+ super();
750
+ this.geometry = new BlankGeometry();
751
+ this.code = defaultShaderCode + shaderCode;
752
+ this.module = null;
753
+ this.pipeline = null;
754
+ this.bindGroupLayout = null;
755
+ this.bindGroup = bindGroup || null;
756
+ this.topology = topology;
757
+ this.paramGenerator = paramGenerator;
758
+ this.vertexParams = vertexParams;
759
+ this.valueBuffers = null;
760
+ }
761
+ propagateDevice(device) {
762
+ super.propagateDevice(device);
763
+ this.module = device.createShaderModule({ code: this.code });
764
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
765
+ const bindGroupLayout = device.createBindGroupLayout({
766
+ entries: [
767
+ {
768
+ binding: 0,
769
+ visibility: GPUShaderStage.VERTEX,
770
+ buffer: {
771
+ type: 'uniform'
772
+ }
773
+ },
774
+ {
775
+ binding: 1,
776
+ visibility: GPUShaderStage.VERTEX,
777
+ buffer: {
778
+ type: 'read-only-storage'
779
+ }
780
+ }
781
+ ]
782
+ });
783
+ const bindGroups = [bindGroupLayout];
784
+ if (this.bindGroup !== null) {
785
+ const entryValues = this.bindGroup.bindings.map((binding, index) => ({
786
+ binding: index,
787
+ visibility: binding.visibility,
788
+ buffer: binding.buffer
789
+ }));
790
+ this.bindGroupLayout = device.createBindGroupLayout({
791
+ entries: entryValues
792
+ });
793
+ bindGroups.push(this.bindGroupLayout);
794
+ }
795
+ this.pipeline = createPipeline(device, this.module, bindGroups, presentationFormat, 'vertex_main_2d', this.topology, this.vertexParams);
796
+ }
797
+ getBindGroupLayout() {
798
+ return this.bindGroupLayout;
799
+ }
800
+ getPipeline() {
801
+ return this.pipeline;
802
+ }
803
+ getBindGroupBuffers() {
804
+ if (this.bindGroup === null)
805
+ return null;
806
+ if (this.device === null)
807
+ return null;
808
+ const values = this.bindGroup.values();
809
+ if (this.valueBuffers === null) {
810
+ const buffers = [];
811
+ for (let i = 0; i < values.length; i++) {
812
+ const buffer = this.createBuffer(this.device, values[i]);
813
+ buffers.push(buffer);
814
+ }
815
+ this.valueBuffers = buffers;
816
+ }
817
+ else {
818
+ for (let i = 0; i < values.length; i++) {
819
+ const arrayConstructor = values[i].array;
820
+ const array = new arrayConstructor(values[i].value);
821
+ if (array.byteLength > this.valueBuffers[i].size) {
822
+ this.valueBuffers[i].destroy();
823
+ const newBuffer = this.createBuffer(this.device, values[i]);
824
+ this.valueBuffers[i] = newBuffer;
825
+ }
826
+ else {
827
+ this.device.queue.writeBuffer(this.valueBuffers[i], 0, array.buffer, array.byteOffset, array.byteLength);
828
+ }
829
+ }
830
+ }
831
+ return this.valueBuffers;
832
+ }
833
+ createBuffer(device, value) {
834
+ const arrayConstructor = value.array;
835
+ const array = new arrayConstructor(value.value);
836
+ const buffer = device.createBuffer({
837
+ mappedAtCreation: true,
838
+ size: array.byteLength,
839
+ usage: value.usage
840
+ });
841
+ const bufferArr = new arrayConstructor(buffer.getMappedRange());
842
+ bufferArr.set(array);
843
+ buffer.unmap();
844
+ return buffer;
845
+ }
846
+ updateMatrix(camera) {
847
+ this.defaultUpdateMatrix(camera);
848
+ }
849
+ getVertexParamGenerator() {
850
+ return this.paramGenerator;
851
+ }
852
+ hasBindGroup() {
853
+ return !!this.bindGroup;
854
+ }
855
+ }
package/dist/types.d.ts CHANGED
@@ -78,3 +78,34 @@ export type RenderInfo = {
78
78
  bindGroupLayout: GPUBindGroupLayout;
79
79
  vertexBuffer: GPUBuffer | null;
80
80
  };
81
+ export type VertexParamGeneratorInfo = {
82
+ bufferSize: number;
83
+ createBuffer: (x: number, y: number, z: number, color: Color) => number[];
84
+ shouldEvaluate?: () => boolean;
85
+ };
86
+ export type ShaderInfo = {
87
+ pipeline: GPURenderPipeline;
88
+ paramGenerator: VertexParamGeneratorInfo;
89
+ bufferInfo: {
90
+ buffers: GPUBuffer[];
91
+ layout: GPUBindGroupLayout;
92
+ } | null;
93
+ };
94
+ export type VertexParamInfo = {
95
+ format: GPUVertexFormat;
96
+ size: number;
97
+ };
98
+ export type BindGroupEntry = {
99
+ visibility: GPUBindGroupLayoutEntry['visibility'];
100
+ buffer: GPUBindGroupLayoutEntry['buffer'];
101
+ };
102
+ export type ArrayConstructors = Float32ArrayConstructor | Float64ArrayConstructor | Int8ArrayConstructor | Int16ArrayConstructor | Int32ArrayConstructor;
103
+ export type BindGroupValue = {
104
+ value: number[];
105
+ usage: GPUBufferDescriptor['usage'];
106
+ array: ArrayConstructors;
107
+ };
108
+ export type BindGroupInfo = {
109
+ bindings: BindGroupEntry[];
110
+ values: () => BindGroupValue[];
111
+ };
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.4.9",
8
+ "version": "0.5.0",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",