simulationjsv2 0.7.2 → 0.7.4

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,6 +1,9 @@
1
1
  # TODO
2
2
 
3
+ - [ ] Make materials
4
+ - [ ] Move shader group to material
3
5
  - [ ] Fix transparency
6
+ - [x] Change absolute pos function to pos function, and pos function to relative pos
4
7
  - [x] Update `updateModelMatrix2d`
5
8
  - [x] Trace line element (wireframe strip for tracing paths)
6
9
  - [x] Test new transform things on 3d stuff
@@ -0,0 +1,11 @@
1
+ /// <reference types="@webgpu/types" />
2
+ export declare class MemoBuffer {
3
+ private buffer;
4
+ private bufferSize;
5
+ private usage;
6
+ constructor(usage: GPUBufferDescriptor['usage'], size: number);
7
+ private allocBuffer;
8
+ getBuffer(): GPUBuffer;
9
+ setSize(size: number): void;
10
+ destroy(): void;
11
+ }
@@ -0,0 +1,36 @@
1
+ import { globalInfo } from './internalUtils.js';
2
+ export class MemoBuffer {
3
+ buffer;
4
+ bufferSize;
5
+ usage;
6
+ constructor(usage, size) {
7
+ this.usage = usage;
8
+ this.bufferSize = size;
9
+ this.buffer = null;
10
+ }
11
+ allocBuffer() {
12
+ const device = globalInfo.getDevice();
13
+ if (!device)
14
+ return;
15
+ this.buffer = device.createBuffer({
16
+ size: this.bufferSize,
17
+ usage: this.usage
18
+ });
19
+ }
20
+ getBuffer() {
21
+ if (!this.buffer)
22
+ this.allocBuffer();
23
+ return this.buffer;
24
+ }
25
+ setSize(size) {
26
+ if (!this.buffer)
27
+ this.allocBuffer();
28
+ if (size > this.bufferSize) {
29
+ this.bufferSize = size;
30
+ this.allocBuffer();
31
+ }
32
+ }
33
+ destroy() {
34
+ this.buffer?.destroy();
35
+ }
36
+ }
@@ -35,6 +35,7 @@ export declare abstract class SimulationElement3d {
35
35
  setRotationOffset(offset: Vector3): void;
36
36
  resetCenterOffset(): void;
37
37
  getModelMatrix(): Mat4;
38
+ isTransparent(): boolean;
38
39
  getUniformBuffer(mat: Mat4): GPUBuffer;
39
40
  protected mirrorParentTransforms3d(mat: Mat4): void;
40
41
  protected updateModelMatrix3d(): void;
@@ -44,8 +45,8 @@ export declare abstract class SimulationElement3d {
44
45
  setWireframe(wireframe: boolean): void;
45
46
  isWireframe(): boolean;
46
47
  getColor(): Color;
48
+ getRelativePos(): Vector3;
47
49
  getPos(): Vector3;
48
- getAbsolutePos(): Vector3;
49
50
  getRotation(): Vector3;
50
51
  getCenterOffset(): Vector3;
51
52
  fill(newColor: Color, t?: number, f?: LerpFunc): Promise<void>;
@@ -183,6 +184,7 @@ export declare class Spline2d extends SimulationElement2d {
183
184
  private interpolateLimit;
184
185
  private length;
185
186
  constructor(pos: Vertex, points: SplinePoint2d[], thickness?: number, detail?: number);
187
+ isTransparent(): boolean;
186
188
  private estimateLength;
187
189
  getLength(): number;
188
190
  setInterpolateStart(start: number, t?: number, f?: LerpFunc): Promise<void>;
@@ -200,11 +202,11 @@ export declare class Instance<T extends AnySimulationElement> extends Simulation
200
202
  private matrixBuffer;
201
203
  private baseMat;
202
204
  private maxInstances;
205
+ private hasMapped;
203
206
  isInstance: boolean;
204
207
  constructor(obj: T, numInstances: number);
205
- setNumInstances(numInstances: number): void;
208
+ setNumInstances(numInstances: number, forceResizeBuffer?: boolean): void;
206
209
  setInstance(instance: number, transformation: Mat4): void;
207
- private allocBuffer;
208
210
  private mapBuffer;
209
211
  getInstances(): Mat4[];
210
212
  getNumInstances(): number;
package/dist/graphics.js CHANGED
@@ -3,6 +3,7 @@ import { Vertex, cloneBuf, color, colorFromVector4, vector2, vector3, vertex, Co
3
3
  import { BlankGeometry, CircleGeometry, CubeGeometry, Line2dGeometry, Line3dGeometry, PlaneGeometry, PolygonGeometry, Spline2dGeometry, SquareGeometry, TraceLines2dGeometry as TraceLinesGeometry } from './geometry.js';
4
4
  import { SimSceneObjInfo, VertexCache, bufferGenerator, globalInfo, internalTransitionValues, logger, posTo2dScreen, vector3ToPixelRatio } from './internalUtils.js';
5
5
  import { mat4ByteLength, modelProjMatOffset } from './constants.js';
6
+ import { MemoBuffer } from './buffers.js';
6
7
  export class SimulationElement3d {
7
8
  children;
8
9
  uniformBuffer;
@@ -23,6 +24,7 @@ export class SimulationElement3d {
23
24
  * @param pos - Expected to be adjusted to devicePixelRatio before reaching constructor
24
25
  */
25
26
  constructor(pos, rotation, color = new Color()) {
27
+ const uniformBufferSize = mat4ByteLength * 2 + 4 * 2 + 8; // 4x4 matrix * 2 + vec2<f32> + 8 bc 144 is cool
26
28
  this.pos = pos;
27
29
  this.centerOffset = vector3();
28
30
  // TODO test this
@@ -31,7 +33,7 @@ export class SimulationElement3d {
31
33
  this.vertexCache = new VertexCache();
32
34
  this.wireframe = false;
33
35
  this.rotation = cloneBuf(rotation);
34
- this.uniformBuffer = null;
36
+ this.uniformBuffer = new MemoBuffer(GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, uniformBufferSize);
35
37
  this.children = [];
36
38
  this.modelMatrix = matrix4();
37
39
  this.parent = null;
@@ -78,23 +80,20 @@ export class SimulationElement3d {
78
80
  this.updateModelMatrix3d();
79
81
  return this.modelMatrix;
80
82
  }
83
+ isTransparent() {
84
+ return this.color.a < 1;
85
+ }
81
86
  getUniformBuffer(mat) {
82
87
  const device = globalInfo.errorGetDevice();
83
- if (!this.uniformBuffer) {
84
- const uniformBufferSize = 4 * 16 + 4 * 16 + 4 * 2 + 8; // 4x4 matrix + 4x4 matrix + vec2<f32> + 8 bc 144 is cool
85
- this.uniformBuffer = device.createBuffer({
86
- size: uniformBufferSize,
87
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
88
- });
89
- }
90
- device.queue.writeBuffer(this.uniformBuffer, modelProjMatOffset, mat);
91
- return this.uniformBuffer;
88
+ const buffer = this.uniformBuffer.getBuffer();
89
+ device.queue.writeBuffer(buffer, modelProjMatOffset, mat);
90
+ return buffer;
92
91
  }
93
92
  mirrorParentTransforms3d(mat) {
94
93
  if (!this.parent)
95
94
  return;
96
95
  this.parent.mirrorParentTransforms3d(mat);
97
- mat4.translate(mat, this.parent.getPos(), mat);
96
+ mat4.translate(mat, this.parent.getRelativePos(), mat);
98
97
  const parentRot = this.parent.getRotation();
99
98
  mat4.rotateZ(mat, parentRot[2], mat);
100
99
  mat4.rotateY(mat, parentRot[1], mat);
@@ -150,10 +149,10 @@ export class SimulationElement3d {
150
149
  getColor() {
151
150
  return this.color;
152
151
  }
153
- getPos() {
152
+ getRelativePos() {
154
153
  return this.pos;
155
154
  }
156
- getAbsolutePos() {
155
+ getPos() {
157
156
  const vec = vector3();
158
157
  this.updateModelMatrix3d();
159
158
  mat4.getTranslation(this.modelMatrix, vec);
@@ -884,6 +883,17 @@ export class Spline2d extends SimulationElement2d {
884
883
  this.geometry = new Spline2dGeometry(points, this.getColor(), this.thickness, this.detail);
885
884
  this.estimateLength();
886
885
  }
886
+ isTransparent() {
887
+ const curves = this.geometry.getCurves();
888
+ for (let i = 0; i < curves.length; i++) {
889
+ const colors = curves[i].getColors();
890
+ for (let j = 0; j < colors.length; j++) {
891
+ if (colors[j]?.a ?? 0 < 1)
892
+ return true;
893
+ }
894
+ }
895
+ return false;
896
+ }
887
897
  estimateLength() {
888
898
  this.length = 0;
889
899
  const curves = this.geometry.getCurves();
@@ -927,7 +937,7 @@ export class Spline2d extends SimulationElement2d {
927
937
  const clonePoint = newPoint.clone();
928
938
  const start = clonePoint.getStart()?.getPos() || vector3();
929
939
  const end = clonePoint.getEnd().getPos();
930
- const pos = this.getPos();
940
+ const pos = this.getRelativePos();
931
941
  vec3.sub(start, pos, start);
932
942
  vec3.sub(end, pos, end);
933
943
  this.geometry.updatePoint(pointIndex, clonePoint);
@@ -979,29 +989,31 @@ export class Instance extends SimulationElement3d {
979
989
  matrixBuffer;
980
990
  baseMat;
981
991
  maxInstances;
992
+ hasMapped;
982
993
  isInstance = true;
983
994
  constructor(obj, numInstances) {
984
995
  super(vector3(), vector3());
985
996
  // 32 matrices
986
997
  this.maxInstances = 32;
987
- this.matrixBuffer = null;
998
+ this.matrixBuffer = new MemoBuffer(GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, this.maxInstances * mat4ByteLength);
988
999
  obj.isInstanced = true;
989
1000
  this.obj = obj;
990
1001
  this.instanceMatrix = [];
991
1002
  this.is3d = obj.is3d;
992
1003
  this.geometry = new BlankGeometry();
1004
+ this.hasMapped = false;
993
1005
  this.baseMat = matrix4();
994
1006
  for (let i = 0; i < numInstances; i++) {
995
1007
  const clone = cloneBuf(this.baseMat);
996
1008
  this.instanceMatrix.push(clone);
997
1009
  }
998
1010
  }
999
- setNumInstances(numInstances) {
1011
+ setNumInstances(numInstances, forceResizeBuffer = false) {
1000
1012
  if (numInstances < 0)
1001
1013
  throw logger.error('Num instances is less than 0');
1002
- if (numInstances > this.maxInstances) {
1014
+ if (numInstances > this.maxInstances || forceResizeBuffer) {
1003
1015
  this.maxInstances = numInstances;
1004
- this.allocBuffer(numInstances);
1016
+ this.matrixBuffer.setSize(numInstances * mat4ByteLength);
1005
1017
  }
1006
1018
  const oldLen = this.instanceMatrix.length;
1007
1019
  if (numInstances < oldLen) {
@@ -1024,29 +1036,36 @@ export class Instance extends SimulationElement3d {
1024
1036
  if (instance >= this.instanceMatrix.length || instance < 0)
1025
1037
  return;
1026
1038
  this.instanceMatrix[instance] = transformation;
1027
- }
1028
- allocBuffer(size) {
1029
1039
  const device = globalInfo.getDevice();
1030
1040
  if (!device)
1031
1041
  return;
1032
- const byteSize = size * mat4ByteLength;
1033
- this.matrixBuffer = device.createBuffer({
1034
- size: byteSize,
1035
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
1036
- });
1037
- }
1042
+ // this.allocBuffer(size);
1043
+ const gpuBuffer = this.matrixBuffer.getBuffer();
1044
+ const buf = new Float32Array(transformation);
1045
+ device.queue.writeBuffer(gpuBuffer, instance * mat4ByteLength, buf.buffer, buf.byteOffset, buf.byteLength);
1046
+ gpuBuffer.unmap();
1047
+ }
1048
+ // private allocBuffer(size: number) {
1049
+ // const device = globalInfo.getDevice();
1050
+ // if (!device) return;
1051
+ // const byteSize = size * mat4ByteLength;
1052
+ // this.matrixBuffer = device.createBuffer({
1053
+ // size: byteSize,
1054
+ // usage:
1055
+ // });
1056
+ // }
1038
1057
  mapBuffer() {
1039
1058
  const device = globalInfo.getDevice();
1040
1059
  if (!device)
1041
1060
  return;
1042
- if (!this.matrixBuffer) {
1043
- const minSize = this.maxInstances * mat4ByteLength;
1044
- const size = Math.max(minSize, this.instanceMatrix.length);
1045
- this.allocBuffer(size);
1046
- }
1061
+ const minSize = this.maxInstances * mat4ByteLength;
1062
+ const size = Math.max(minSize, this.instanceMatrix.length);
1063
+ this.matrixBuffer.setSize(size);
1064
+ const gpuBuffer = this.matrixBuffer.getBuffer();
1047
1065
  const buf = new Float32Array(this.instanceMatrix.map((mat) => [...mat]).flat());
1048
- device.queue.writeBuffer(this.matrixBuffer, 0, buf.buffer, buf.byteOffset, buf.byteLength);
1049
- this.matrixBuffer.unmap();
1066
+ device.queue.writeBuffer(gpuBuffer, 0, buf.buffer, buf.byteOffset, buf.byteLength);
1067
+ gpuBuffer.unmap();
1068
+ this.hasMapped = true;
1050
1069
  }
1051
1070
  getInstances() {
1052
1071
  return this.instanceMatrix;
@@ -1055,8 +1074,10 @@ export class Instance extends SimulationElement3d {
1055
1074
  return this.instanceMatrix.length;
1056
1075
  }
1057
1076
  getMatrixBuffer() {
1058
- this.mapBuffer();
1059
- return this.matrixBuffer;
1077
+ if (!this.hasMapped) {
1078
+ this.mapBuffer();
1079
+ }
1080
+ return this.matrixBuffer.getBuffer();
1060
1081
  }
1061
1082
  getVertexCount() {
1062
1083
  return this.obj.getVertexCount();
@@ -2,6 +2,7 @@
2
2
  import { VertexParamGeneratorInfo, Mat4, Vector2, Vector3, VertexParamInfo } from './types.js';
3
3
  import { Color } from './utils.js';
4
4
  import { SimulationElement3d } from './graphics.js';
5
+ import { Shader } from './shaders.js';
5
6
  export declare class VertexCache {
6
7
  private vertices;
7
8
  private hasUpdated;
@@ -20,6 +21,15 @@ export declare class GlobalInfo {
20
21
  getDevice(): GPUDevice | null;
21
22
  }
22
23
  export declare const globalInfo: GlobalInfo;
24
+ export declare class CachedArray<T> {
25
+ private length;
26
+ private data;
27
+ constructor();
28
+ add(index: T): void;
29
+ reset(): void;
30
+ clearCache(): void;
31
+ toArray(): T[];
32
+ }
23
33
  export declare const updateProjectionMatrix: (mat: Mat4, aspectRatio: number, zNear?: number, zFar?: number) => any;
24
34
  export declare const updateWorldProjectionMatrix: (worldProjMat: Mat4, projMat: Mat4) => void;
25
35
  export declare const updateOrthoProjectionMatrix: (mat: Mat4, screenSize: [number, number]) => Float32Array;
@@ -65,11 +75,21 @@ declare class BufferGenerator {
65
75
  export declare const bufferGenerator: BufferGenerator;
66
76
  export declare function vector3ToPixelRatio(vec: Vector3): void;
67
77
  export declare function vector2ToPixelRatio(vec: Vector2): void;
68
- export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayouts: GPUBindGroupLayout[], presentationFormat: GPUTextureFormat, topology: GPUPrimitiveTopology, vertexParams?: VertexParamInfo[]): GPURenderPipeline;
78
+ export declare function createPipeline(device: GPUDevice, module: GPUShaderModule, bindGroupLayouts: GPUBindGroupLayout[], presentationFormat: GPUTextureFormat, topology: GPUPrimitiveTopology, transparent: boolean, vertexParams?: VertexParamInfo[]): GPURenderPipeline;
69
79
  export declare function triangulateWireFrameOrder(len: number): number[];
70
80
  export declare function getTotalVertices(scene: SimSceneObjInfo[]): number;
71
81
  export declare function vectorCompAngle(a: number, b: number): number;
72
82
  export declare function angleBetween(pos1: Vector3, pos2: Vector3): Vector3;
73
83
  export declare function internalTransitionValues(onFrame: (deltaT: number, t: number, total: number) => void, adjustment: () => void, transitionLength: number, func?: (n: number) => number): Promise<void>;
74
84
  export declare function posTo2dScreen(pos: Vector3): Vector3;
85
+ export declare function createShaderModule(shader: Shader): GPUShaderModule;
86
+ export declare function createDefaultPipelines(shader: Shader): {
87
+ triangleList: GPURenderPipeline;
88
+ triangleStrip: GPURenderPipeline;
89
+ lineStrip: GPURenderPipeline;
90
+ triangleListTransparent: GPURenderPipeline;
91
+ triangleStripTransparent: GPURenderPipeline;
92
+ lineStripTransparent: GPURenderPipeline;
93
+ };
94
+ export default function createUniformBindGroup(shader: Shader, buffers: GPUBuffer[]): GPUBindGroup;
75
95
  export {};
@@ -44,6 +44,33 @@ export class GlobalInfo {
44
44
  }
45
45
  }
46
46
  export const globalInfo = new GlobalInfo();
47
+ export class CachedArray {
48
+ length;
49
+ data;
50
+ constructor() {
51
+ this.length = 0;
52
+ this.data = [];
53
+ }
54
+ add(index) {
55
+ if (this.length < this.data.length) {
56
+ this.data[this.length - 1] = index;
57
+ }
58
+ else {
59
+ this.data.push(index);
60
+ }
61
+ this.length++;
62
+ }
63
+ reset() {
64
+ this.length = 0;
65
+ }
66
+ clearCache() {
67
+ this.reset();
68
+ this.data = [];
69
+ }
70
+ toArray() {
71
+ return this.data.slice(0, this.length);
72
+ }
73
+ }
47
74
  export const updateProjectionMatrix = (mat, aspectRatio, zNear = 1, zFar = 500) => {
48
75
  const fov = Math.PI / 4;
49
76
  return mat4.perspective(fov, aspectRatio, zNear, zFar, mat);
@@ -219,7 +246,7 @@ export function vector2ToPixelRatio(vec) {
219
246
  vec[0] *= devicePixelRatio;
220
247
  vec[1] *= devicePixelRatio;
221
248
  }
222
- export function createPipeline(device, module, bindGroupLayouts, presentationFormat, topology, vertexParams) {
249
+ export function createPipeline(device, module, bindGroupLayouts, presentationFormat, topology, transparent, vertexParams) {
223
250
  let params = [
224
251
  {
225
252
  // position
@@ -279,7 +306,17 @@ export function createPipeline(device, module, bindGroupLayouts, presentationFor
279
306
  entryPoint: 'fragment_main',
280
307
  targets: [
281
308
  {
282
- format: presentationFormat
309
+ format: presentationFormat,
310
+ blend: {
311
+ color: {
312
+ srcFactor: 'src-alpha',
313
+ dstFactor: 'one-minus-src-alpha'
314
+ },
315
+ alpha: {
316
+ srcFactor: 'src-alpha',
317
+ dstFactor: 'one-minus-src-alpha'
318
+ }
319
+ }
283
320
  }
284
321
  ]
285
322
  },
@@ -290,7 +327,7 @@ export function createPipeline(device, module, bindGroupLayouts, presentationFor
290
327
  count: 4
291
328
  },
292
329
  depthStencil: {
293
- depthWriteEnabled: true,
330
+ depthWriteEnabled: !transparent,
294
331
  depthCompare: 'less',
295
332
  format: 'depth24plus'
296
333
  }
@@ -346,3 +383,36 @@ export function posTo2dScreen(pos) {
346
383
  newPos[1] = camera.getScreenSize()[1] + newPos[1];
347
384
  return newPos;
348
385
  }
386
+ export function createShaderModule(shader) {
387
+ const device = globalInfo.errorGetDevice();
388
+ return device.createShaderModule({
389
+ code: shader.getCode()
390
+ });
391
+ }
392
+ export function createDefaultPipelines(shader) {
393
+ const device = globalInfo.errorGetDevice();
394
+ const bindGroupLayout = device.createBindGroupLayout(shader.getBindGroupLayoutDescriptor());
395
+ const shaderModule = createShaderModule(shader);
396
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
397
+ return {
398
+ triangleList: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-list', false),
399
+ triangleStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-strip', false),
400
+ lineStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'line-strip', false),
401
+ triangleListTransparent: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-list', true),
402
+ triangleStripTransparent: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-strip', true),
403
+ lineStripTransparent: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'line-strip', true)
404
+ };
405
+ }
406
+ export default function createUniformBindGroup(shader, buffers) {
407
+ const device = globalInfo.errorGetDevice();
408
+ const bindGroupLayout = shader.getBindGroupLayout();
409
+ return device.createBindGroup({
410
+ layout: bindGroupLayout,
411
+ entries: buffers.map((buffer, index) => ({
412
+ binding: index,
413
+ resource: {
414
+ buffer
415
+ }
416
+ }))
417
+ });
418
+ }
@@ -0,0 +1,3 @@
1
+ export declare class Material {
2
+ constructor();
3
+ }
@@ -0,0 +1,3 @@
1
+ export class Material {
2
+ constructor() { }
3
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ /// <reference types="@webgpu/types" />
2
+ export declare class Shader {
3
+ private bindGroupLayoutDescriptor;
4
+ private bindGroupLayout;
5
+ private code;
6
+ constructor(code: string, descriptor: GPUBindGroupLayoutDescriptor);
7
+ getCode(): string;
8
+ getBindGroupLayout(): GPUBindGroupLayout;
9
+ getBindGroupLayoutDescriptor(): GPUBindGroupLayoutDescriptor;
10
+ }
11
+ export declare const defaultShader: Shader;
@@ -0,0 +1,89 @@
1
+ import { globalInfo } from './internalUtils.js';
2
+ export class Shader {
3
+ bindGroupLayoutDescriptor;
4
+ bindGroupLayout;
5
+ code;
6
+ constructor(code, descriptor) {
7
+ this.code = code;
8
+ this.bindGroupLayoutDescriptor = descriptor;
9
+ this.bindGroupLayout = null;
10
+ }
11
+ getCode() {
12
+ return this.code;
13
+ }
14
+ getBindGroupLayout() {
15
+ const device = globalInfo.errorGetDevice();
16
+ if (!this.bindGroupLayout) {
17
+ this.bindGroupLayout = device.createBindGroupLayout(this.bindGroupLayoutDescriptor);
18
+ }
19
+ return this.bindGroupLayout;
20
+ }
21
+ getBindGroupLayoutDescriptor() {
22
+ return this.bindGroupLayoutDescriptor;
23
+ }
24
+ }
25
+ export const defaultShader = new Shader(`
26
+ struct Uniforms {
27
+ worldProjectionMatrix: mat4x4<f32>,
28
+ modelProjectionMatrix: mat4x4<f32>,
29
+ }
30
+
31
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
32
+
33
+ @group(0) @binding(1) var<storage> instanceMatrices: array<mat4x4f>;
34
+
35
+ struct VertexOutput {
36
+ @builtin(position) Position: vec4<f32>,
37
+ @location(0) fragUV: vec2<f32>,
38
+ @location(1) fragColor: vec4<f32>,
39
+ @location(2) fragPosition: vec4<f32>,
40
+ }
41
+
42
+ @vertex
43
+ fn vertex_main(
44
+ @builtin(instance_index) instanceIdx: u32,
45
+ @location(0) position: vec3<f32>,
46
+ @location(1) color: vec4<f32>,
47
+ @location(2) uv: vec2<f32>,
48
+ @location(3) drawingInstance: f32
49
+ ) -> VertexOutput {
50
+ var output: VertexOutput;
51
+
52
+ if (drawingInstance == 1) {
53
+ output.Position = uniforms.worldProjectionMatrix * uniforms.modelProjectionMatrix * instanceMatrices[instanceIdx] * vec4(position, 1.0);
54
+ } else {
55
+ output.Position = uniforms.worldProjectionMatrix * uniforms.modelProjectionMatrix * vec4(position, 1.0);
56
+ }
57
+
58
+ output.fragUV = uv;
59
+ output.fragPosition = output.Position;
60
+ output.fragColor = color;
61
+ return output;
62
+ }
63
+
64
+ @fragment
65
+ fn fragment_main(
66
+ @location(0) fragUV: vec2<f32>,
67
+ @location(1) fragColor: vec4<f32>,
68
+ @location(2) fragPosition: vec4<f32>
69
+ ) -> @location(0) vec4<f32> {
70
+ return fragColor;
71
+ }
72
+ `, {
73
+ entries: [
74
+ {
75
+ binding: 0,
76
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
77
+ buffer: {
78
+ type: 'uniform'
79
+ }
80
+ },
81
+ {
82
+ binding: 1,
83
+ visibility: GPUShaderStage.VERTEX,
84
+ buffer: {
85
+ type: 'read-only-storage'
86
+ }
87
+ }
88
+ ]
89
+ });
@@ -35,6 +35,8 @@ export declare class Simulation extends Settings {
35
35
  private renderInfo;
36
36
  private resizeEvents;
37
37
  private frameRateView;
38
+ private transparentElements;
39
+ private vertexBuffer;
38
40
  constructor(idOrCanvasRef: string | HTMLCanvasElement, sceneCamera?: Camera | null, showFrameRate?: boolean);
39
41
  private handleCanvasResize;
40
42
  onResize(cb: (width: number, height: number) => void): void;
@@ -3,56 +3,10 @@ import { EmptyElement, SimulationElement3d } from './graphics.js';
3
3
  import { BUF_LEN, worldProjMatOffset } from './constants.js';
4
4
  import { Color, matrix4, transitionValues, vector2, vector3 } from './utils.js';
5
5
  import { BlankGeometry } from './geometry.js';
6
- import { SimSceneObjInfo, buildDepthTexture, buildMultisampleTexture, updateProjectionMatrix, createPipeline, getTotalVertices, logger, removeObjectId, updateOrthoProjectionMatrix, updateWorldProjectionMatrix, globalInfo } from './internalUtils.js';
6
+ import createUniformBindGroup, { SimSceneObjInfo, buildDepthTexture, buildMultisampleTexture, updateProjectionMatrix, createPipeline, getTotalVertices, logger, removeObjectId, updateOrthoProjectionMatrix, updateWorldProjectionMatrix, globalInfo, CachedArray, createDefaultPipelines } from './internalUtils.js';
7
7
  import { Settings } from './settings.js';
8
- const shader = `
9
- struct Uniforms {
10
- worldProjectionMatrix: mat4x4<f32>,
11
- modelProjectionMatrix: mat4x4<f32>,
12
- }
13
-
14
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
15
-
16
- @group(0) @binding(1) var<storage> instanceMatrices: array<mat4x4f>;
17
-
18
- struct VertexOutput {
19
- @builtin(position) Position: vec4<f32>,
20
- @location(0) fragUV: vec2<f32>,
21
- @location(1) fragColor: vec4<f32>,
22
- @location(2) fragPosition: vec4<f32>,
23
- }
24
-
25
- @vertex
26
- fn vertex_main(
27
- @builtin(instance_index) instanceIdx: u32,
28
- @location(0) position: vec3<f32>,
29
- @location(1) color: vec4<f32>,
30
- @location(2) uv: vec2<f32>,
31
- @location(3) drawingInstance: f32
32
- ) -> VertexOutput {
33
- var output: VertexOutput;
34
-
35
- if (drawingInstance == 1) {
36
- output.Position = uniforms.worldProjectionMatrix * uniforms.modelProjectionMatrix * instanceMatrices[instanceIdx] * vec4(position, 1.0);
37
- } else {
38
- output.Position = uniforms.worldProjectionMatrix * uniforms.modelProjectionMatrix * vec4(position, 1.0);
39
- }
40
-
41
- output.fragUV = uv;
42
- output.fragPosition = output.Position;
43
- output.fragColor = color;
44
- return output;
45
- }
46
-
47
- @fragment
48
- fn fragment_main(
49
- @location(0) fragUV: vec2<f32>,
50
- @location(1) fragColor: vec4<f32>,
51
- @location(2) fragPosition: vec4<f32>
52
- ) -> @location(0) vec4<f32> {
53
- return fragColor;
54
- }
55
- `;
8
+ import { defaultShader } from './shaders.js';
9
+ import { MemoBuffer } from './buffers.js';
56
10
  const simjsFrameRateCss = `.simjs-frame-rate {
57
11
  position: absolute;
58
12
  top: 0;
@@ -69,16 +23,21 @@ class FrameRateView {
69
23
  fpsBuffer = [];
70
24
  maxFpsBufferLength = 8;
71
25
  prevAvg = 0;
26
+ showing;
72
27
  constructor(show) {
73
28
  this.el = document.createElement('div');
74
29
  this.el.classList.add('simjs-frame-rate');
30
+ this.showing = show;
75
31
  const style = document.createElement('style');
76
32
  style.innerHTML = simjsFrameRateCss;
77
- if (show) {
33
+ if (this.showing) {
78
34
  document.head.appendChild(style);
79
35
  document.body.appendChild(this.el);
80
36
  }
81
37
  }
38
+ isActive() {
39
+ return this.showing;
40
+ }
82
41
  updateFrameRate(num) {
83
42
  if (this.fpsBuffer.length < this.maxFpsBufferLength) {
84
43
  this.fpsBuffer.push(num);
@@ -94,24 +53,6 @@ class FrameRateView {
94
53
  }
95
54
  }
96
55
  }
97
- const baseBindGroupLayout = {
98
- entries: [
99
- {
100
- binding: 0,
101
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
102
- buffer: {
103
- type: 'uniform'
104
- }
105
- },
106
- {
107
- binding: 1,
108
- visibility: GPUShaderStage.VERTEX,
109
- buffer: {
110
- type: 'read-only-storage'
111
- }
112
- }
113
- ]
114
- };
115
56
  let aspectRatio = 0;
116
57
  const projMat = matrix4();
117
58
  const worldProjMat = matrix4();
@@ -215,21 +156,21 @@ export class Simulation extends Settings {
215
156
  renderInfo = null;
216
157
  resizeEvents;
217
158
  frameRateView;
159
+ transparentElements;
160
+ vertexBuffer;
218
161
  constructor(idOrCanvasRef, sceneCamera = null, showFrameRate = false) {
219
162
  super();
220
163
  if (typeof idOrCanvasRef === 'string') {
221
164
  const ref = document.getElementById(idOrCanvasRef);
222
- if (ref !== null)
223
- this.canvasRef = ref;
224
- else
165
+ if (!ref)
225
166
  throw logger.error(`Cannot find canvas with id ${idOrCanvasRef}`);
167
+ this.canvasRef = ref;
226
168
  }
227
169
  else if (idOrCanvasRef instanceof HTMLCanvasElement) {
228
170
  this.canvasRef = idOrCanvasRef;
229
171
  }
230
- else {
172
+ else
231
173
  throw logger.error(`Canvas ref/id provided is invalid`);
232
- }
233
174
  const parent = this.canvasRef.parentElement;
234
175
  if (sceneCamera) {
235
176
  camera = sceneCamera;
@@ -242,6 +183,8 @@ export class Simulation extends Settings {
242
183
  });
243
184
  this.frameRateView = new FrameRateView(showFrameRate);
244
185
  this.frameRateView.updateFrameRate(1);
186
+ this.transparentElements = new CachedArray();
187
+ this.vertexBuffer = new MemoBuffer(GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 0);
245
188
  }
246
189
  handleCanvasResize(parent) {
247
190
  if (this.fittingElement) {
@@ -320,13 +263,26 @@ export class Simulation extends Settings {
320
263
  throw logger.error('Context is null');
321
264
  const device = await adapter.requestDevice();
322
265
  globalInfo.setDevice(device);
266
+ const screenSize = vector2(this.canvasRef.width, this.canvasRef.height);
267
+ camera.setScreenSize(screenSize);
268
+ const canvas = this.canvasRef;
269
+ canvas.width = canvas.clientWidth * devicePixelRatio;
270
+ canvas.height = canvas.clientHeight * devicePixelRatio;
271
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
323
272
  ctx.configure({
324
273
  device,
325
- format: 'bgra8unorm'
274
+ format: presentationFormat,
275
+ alphaMode: 'opaque'
326
276
  });
327
- const screenSize = vector2(this.canvasRef.width, this.canvasRef.height);
328
- camera.setScreenSize(screenSize);
329
- this.render(ctx);
277
+ this.pipelines = createDefaultPipelines(defaultShader);
278
+ const instanceBuffer = device.createBuffer({
279
+ size: 10 * 4 * 16,
280
+ usage: GPUBufferUsage.STORAGE
281
+ });
282
+ this.renderInfo = {
283
+ instanceBuffer
284
+ };
285
+ this.render(device, ctx, canvas);
330
286
  })();
331
287
  }
332
288
  stop() {
@@ -341,35 +297,7 @@ export class Simulation extends Settings {
341
297
  getSceneObjects() {
342
298
  return this.scene.map((item) => item.getObj());
343
299
  }
344
- render(ctx) {
345
- const device = globalInfo.getDevice();
346
- if (this.canvasRef === null || device === null)
347
- return;
348
- const canvas = this.canvasRef;
349
- canvas.width = canvas.clientWidth * devicePixelRatio;
350
- canvas.height = canvas.clientHeight * devicePixelRatio;
351
- const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
352
- const shaderModule = device.createShaderModule({ code: shader });
353
- ctx.configure({
354
- device,
355
- format: presentationFormat,
356
- alphaMode: 'premultiplied'
357
- });
358
- const instanceBuffer = device.createBuffer({
359
- size: 10 * 4 * 16,
360
- usage: GPUBufferUsage.STORAGE
361
- });
362
- const bindGroupLayout = device.createBindGroupLayout(baseBindGroupLayout);
363
- this.renderInfo = {
364
- bindGroupLayout,
365
- instanceBuffer,
366
- vertexBuffer: null
367
- };
368
- this.pipelines = {
369
- triangleList: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-list'),
370
- triangleStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-strip'),
371
- lineStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'line-strip')
372
- };
300
+ render(device, ctx, canvas) {
373
301
  const colorAttachment = {
374
302
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
375
303
  // @ts-ignore
@@ -400,9 +328,7 @@ export class Simulation extends Settings {
400
328
  let prev = Date.now() - 10;
401
329
  let prevFps = 0;
402
330
  const frame = async () => {
403
- if (!canvas)
404
- return;
405
- if (!this.renderInfo)
331
+ if (!canvas || !this.renderInfo)
406
332
  return;
407
333
  requestAnimationFrame(frame);
408
334
  if (!this.running)
@@ -411,12 +337,10 @@ export class Simulation extends Settings {
411
337
  const diff = Math.max(now - prev, 1);
412
338
  prev = now;
413
339
  const fps = 1000 / diff;
414
- if (fps === prevFps) {
340
+ if (this.frameRateView.isActive() && fps === prevFps) {
415
341
  this.frameRateView.updateFrameRate(fps);
416
342
  }
417
343
  prevFps = fps;
418
- canvas.width = canvas.clientWidth * devicePixelRatio;
419
- canvas.height = canvas.clientHeight * devicePixelRatio;
420
344
  const screenSize = camera.getScreenSize();
421
345
  if (screenSize[0] !== canvas.width || screenSize[1] !== canvas.height) {
422
346
  camera.setScreenSize(vector2(canvas.width, canvas.height));
@@ -441,23 +365,18 @@ export class Simulation extends Settings {
441
365
  }
442
366
  const commandEncoder = device.createCommandEncoder();
443
367
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
444
- passEncoder.setPipeline(this.pipelines.triangleList);
445
368
  const totalVertices = getTotalVertices(this.scene);
446
- if (this.renderInfo.vertexBuffer === null ||
447
- this.renderInfo.vertexBuffer.size / (4 * BUF_LEN) < totalVertices) {
448
- this.renderInfo.vertexBuffer = device.createBuffer({
449
- size: totalVertices * 4 * BUF_LEN,
450
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
451
- });
452
- }
453
- this.renderScene(device, passEncoder, this.renderInfo.vertexBuffer, this.scene, 0, diff);
369
+ this.vertexBuffer.setSize(totalVertices * 4 * BUF_LEN);
370
+ this.transparentElements.reset();
371
+ const opaqueOffset = this.renderScene(device, passEncoder, this.vertexBuffer.getBuffer(), this.scene, 0, diff, false);
372
+ this.renderScene(device, passEncoder, this.vertexBuffer.getBuffer(), this.transparentElements.toArray(), opaqueOffset, diff, true);
454
373
  camera.updateConsumed();
455
374
  passEncoder.end();
456
375
  device.queue.submit([commandEncoder.finish()]);
457
376
  };
458
377
  requestAnimationFrame(frame);
459
378
  }
460
- renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff, shaderInfo) {
379
+ renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff, transparent, shaderInfo) {
461
380
  if (this.pipelines === null)
462
381
  return 0;
463
382
  let currentOffset = startOffset;
@@ -473,6 +392,10 @@ export class Simulation extends Settings {
473
392
  scene[i].traverseLife(diff);
474
393
  }
475
394
  const obj = scene[i].getObj();
395
+ if (!transparent && obj.isTransparent()) {
396
+ this.transparentElements.add(scene[i]);
397
+ continue;
398
+ }
476
399
  if (obj.hasChildren()) {
477
400
  let shaderInfo = undefined;
478
401
  if (obj instanceof ShaderGroup) {
@@ -490,7 +413,7 @@ export class Simulation extends Settings {
490
413
  };
491
414
  }
492
415
  }
493
- currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getChildrenInfos(), currentOffset, diff, shaderInfo);
416
+ currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getChildrenInfos(), currentOffset, diff, transparent, shaderInfo);
494
417
  }
495
418
  if (obj.isEmpty)
496
419
  continue;
@@ -508,15 +431,25 @@ export class Simulation extends Settings {
508
431
  passEncoder.setPipeline(shaderInfo.pipeline);
509
432
  }
510
433
  else if (obj.isWireframe()) {
511
- passEncoder.setPipeline(this.pipelines.lineStrip);
434
+ if (obj.isTransparent())
435
+ passEncoder.setPipeline(this.pipelines.lineStripTransparent);
436
+ else
437
+ passEncoder.setPipeline(this.pipelines.lineStrip);
512
438
  }
513
439
  else {
514
440
  const type = obj.getGeometryType();
441
+ // must be exhaustive
515
442
  if (type === 'strip') {
516
- passEncoder.setPipeline(this.pipelines.triangleStrip);
443
+ if (obj.isTransparent())
444
+ passEncoder.setPipeline(this.pipelines.triangleStripTransparent);
445
+ else
446
+ passEncoder.setPipeline(this.pipelines.triangleStrip);
517
447
  }
518
448
  else if (type === 'list') {
519
- passEncoder.setPipeline(this.pipelines.triangleList);
449
+ if (obj.isTransparent())
450
+ passEncoder.setPipeline(this.pipelines.triangleListTransparent);
451
+ else
452
+ passEncoder.setPipeline(this.pipelines.triangleList);
520
453
  }
521
454
  }
522
455
  let instances = 1;
@@ -530,23 +463,7 @@ export class Simulation extends Settings {
530
463
  else {
531
464
  instanceBuffer = this.renderInfo.instanceBuffer;
532
465
  }
533
- const uniformBindGroup = device.createBindGroup({
534
- layout: this.renderInfo.bindGroupLayout,
535
- entries: [
536
- {
537
- binding: 0,
538
- resource: {
539
- buffer: uniformBuffer
540
- }
541
- },
542
- {
543
- binding: 1,
544
- resource: {
545
- buffer: instanceBuffer
546
- }
547
- }
548
- ]
549
- });
466
+ const uniformBindGroup = createUniformBindGroup(defaultShader, [uniformBuffer, instanceBuffer]);
550
467
  passEncoder.setBindGroup(0, uniformBindGroup);
551
468
  }
552
469
  if (shaderInfo && shaderInfo.bufferInfo) {
@@ -620,7 +537,7 @@ export class ShaderGroup extends EmptyElement {
620
537
  const device = globalInfo.errorGetDevice();
621
538
  this.module = device.createShaderModule({ code: this.code });
622
539
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
623
- const bindGroupLayout = device.createBindGroupLayout(baseBindGroupLayout);
540
+ const bindGroupLayout = defaultShader.getBindGroupLayout();
624
541
  const bindGroups = [bindGroupLayout];
625
542
  if (this.bindGroup !== null) {
626
543
  const entryValues = this.bindGroup.bindings.map((binding, index) => ({
@@ -633,7 +550,9 @@ export class ShaderGroup extends EmptyElement {
633
550
  });
634
551
  bindGroups.push(this.bindGroupLayout);
635
552
  }
636
- this.pipeline = createPipeline(device, this.module, bindGroups, presentationFormat, this.topology, this.vertexParams);
553
+ this.pipeline = createPipeline(device, this.module, bindGroups, presentationFormat, this.topology,
554
+ // TODO possibly make transparent materials
555
+ false, this.vertexParams);
637
556
  }
638
557
  getBindGroupLayout() {
639
558
  return this.bindGroupLayout;
package/dist/types.d.ts CHANGED
@@ -73,11 +73,12 @@ export type PipelineGroup = {
73
73
  triangleList: GPURenderPipeline;
74
74
  triangleStrip: GPURenderPipeline;
75
75
  lineStrip: GPURenderPipeline;
76
+ triangleListTransparent: GPURenderPipeline;
77
+ triangleStripTransparent: GPURenderPipeline;
78
+ lineStripTransparent: GPURenderPipeline;
76
79
  };
77
80
  export type RenderInfo = {
78
81
  instanceBuffer: GPUBuffer;
79
- bindGroupLayout: GPUBindGroupLayout;
80
- vertexBuffer: GPUBuffer | null;
81
82
  };
82
83
  export type VertexParamGeneratorInfo = {
83
84
  bufferSize: number;
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.7.2",
8
+ "version": "0.7.4",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",