simulationjsv2 0.7.3 → 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
@@ -3,6 +3,7 @@
3
3
  - [ ] Make materials
4
4
  - [ ] Move shader group to material
5
5
  - [ ] Fix transparency
6
+ - [x] Change absolute pos function to pos function, and pos function to relative pos
6
7
  - [x] Update `updateModelMatrix2d`
7
8
  - [x] Trace line element (wireframe strip for tracing paths)
8
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>;
@@ -203,9 +205,8 @@ export declare class Instance<T extends AnySimulationElement> extends Simulation
203
205
  private hasMapped;
204
206
  isInstance: boolean;
205
207
  constructor(obj: T, numInstances: number);
206
- setNumInstances(numInstances: number): void;
208
+ setNumInstances(numInstances: number, forceResizeBuffer?: boolean): void;
207
209
  setInstance(instance: number, transformation: Mat4): void;
208
- private allocBuffer;
209
210
  private mapBuffer;
210
211
  getInstances(): Mat4[];
211
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);
@@ -985,7 +995,7 @@ export class Instance extends SimulationElement3d {
985
995
  super(vector3(), vector3());
986
996
  // 32 matrices
987
997
  this.maxInstances = 32;
988
- this.matrixBuffer = null;
998
+ this.matrixBuffer = new MemoBuffer(GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, this.maxInstances * mat4ByteLength);
989
999
  obj.isInstanced = true;
990
1000
  this.obj = obj;
991
1001
  this.instanceMatrix = [];
@@ -998,12 +1008,12 @@ export class Instance extends SimulationElement3d {
998
1008
  this.instanceMatrix.push(clone);
999
1009
  }
1000
1010
  }
1001
- setNumInstances(numInstances) {
1011
+ setNumInstances(numInstances, forceResizeBuffer = false) {
1002
1012
  if (numInstances < 0)
1003
1013
  throw logger.error('Num instances is less than 0');
1004
- if (numInstances > this.maxInstances) {
1014
+ if (numInstances > this.maxInstances || forceResizeBuffer) {
1005
1015
  this.maxInstances = numInstances;
1006
- this.allocBuffer(numInstances);
1016
+ this.matrixBuffer.setSize(numInstances * mat4ByteLength);
1007
1017
  }
1008
1018
  const oldLen = this.instanceMatrix.length;
1009
1019
  if (numInstances < oldLen) {
@@ -1029,37 +1039,32 @@ export class Instance extends SimulationElement3d {
1029
1039
  const device = globalInfo.getDevice();
1030
1040
  if (!device)
1031
1041
  return;
1032
- if (!this.matrixBuffer) {
1033
- const minSize = this.maxInstances * mat4ByteLength;
1034
- const size = Math.max(minSize, this.instanceMatrix.length);
1035
- this.allocBuffer(size);
1036
- }
1042
+ // this.allocBuffer(size);
1043
+ const gpuBuffer = this.matrixBuffer.getBuffer();
1037
1044
  const buf = new Float32Array(transformation);
1038
- device.queue.writeBuffer(this.matrixBuffer, instance * mat4ByteLength, buf.buffer, buf.byteOffset, buf.byteLength);
1039
- this.matrixBuffer.unmap();
1040
- }
1041
- allocBuffer(size) {
1042
- const device = globalInfo.getDevice();
1043
- if (!device)
1044
- return;
1045
- const byteSize = size * mat4ByteLength;
1046
- this.matrixBuffer = device.createBuffer({
1047
- size: byteSize,
1048
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
1049
- });
1050
- }
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
+ // }
1051
1057
  mapBuffer() {
1052
1058
  const device = globalInfo.getDevice();
1053
1059
  if (!device)
1054
1060
  return;
1055
- if (!this.matrixBuffer) {
1056
- const minSize = this.maxInstances * mat4ByteLength;
1057
- const size = Math.max(minSize, this.instanceMatrix.length);
1058
- this.allocBuffer(size);
1059
- }
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();
1060
1065
  const buf = new Float32Array(this.instanceMatrix.map((mat) => [...mat]).flat());
1061
- device.queue.writeBuffer(this.matrixBuffer, 0, buf.buffer, buf.byteOffset, buf.byteLength);
1062
- this.matrixBuffer.unmap();
1066
+ device.queue.writeBuffer(gpuBuffer, 0, buf.buffer, buf.byteOffset, buf.byteLength);
1067
+ gpuBuffer.unmap();
1063
1068
  this.hasMapped = true;
1064
1069
  }
1065
1070
  getInstances() {
@@ -1072,7 +1077,7 @@ export class Instance extends SimulationElement3d {
1072
1077
  if (!this.hasMapped) {
1073
1078
  this.mapBuffer();
1074
1079
  }
1075
- return this.matrixBuffer;
1080
+ return this.matrixBuffer.getBuffer();
1076
1081
  }
1077
1082
  getVertexCount() {
1078
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;
@@ -99,24 +53,6 @@ class FrameRateView {
99
53
  }
100
54
  }
101
55
  }
102
- const baseBindGroupLayout = {
103
- entries: [
104
- {
105
- binding: 0,
106
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
107
- buffer: {
108
- type: 'uniform'
109
- }
110
- },
111
- {
112
- binding: 1,
113
- visibility: GPUShaderStage.VERTEX,
114
- buffer: {
115
- type: 'read-only-storage'
116
- }
117
- }
118
- ]
119
- };
120
56
  let aspectRatio = 0;
121
57
  const projMat = matrix4();
122
58
  const worldProjMat = matrix4();
@@ -220,21 +156,21 @@ export class Simulation extends Settings {
220
156
  renderInfo = null;
221
157
  resizeEvents;
222
158
  frameRateView;
159
+ transparentElements;
160
+ vertexBuffer;
223
161
  constructor(idOrCanvasRef, sceneCamera = null, showFrameRate = false) {
224
162
  super();
225
163
  if (typeof idOrCanvasRef === 'string') {
226
164
  const ref = document.getElementById(idOrCanvasRef);
227
- if (ref !== null)
228
- this.canvasRef = ref;
229
- else
165
+ if (!ref)
230
166
  throw logger.error(`Cannot find canvas with id ${idOrCanvasRef}`);
167
+ this.canvasRef = ref;
231
168
  }
232
169
  else if (idOrCanvasRef instanceof HTMLCanvasElement) {
233
170
  this.canvasRef = idOrCanvasRef;
234
171
  }
235
- else {
172
+ else
236
173
  throw logger.error(`Canvas ref/id provided is invalid`);
237
- }
238
174
  const parent = this.canvasRef.parentElement;
239
175
  if (sceneCamera) {
240
176
  camera = sceneCamera;
@@ -247,6 +183,8 @@ export class Simulation extends Settings {
247
183
  });
248
184
  this.frameRateView = new FrameRateView(showFrameRate);
249
185
  this.frameRateView.updateFrameRate(1);
186
+ this.transparentElements = new CachedArray();
187
+ this.vertexBuffer = new MemoBuffer(GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 0);
250
188
  }
251
189
  handleCanvasResize(parent) {
252
190
  if (this.fittingElement) {
@@ -325,13 +263,26 @@ export class Simulation extends Settings {
325
263
  throw logger.error('Context is null');
326
264
  const device = await adapter.requestDevice();
327
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();
328
272
  ctx.configure({
329
273
  device,
330
- format: 'bgra8unorm'
274
+ format: presentationFormat,
275
+ alphaMode: 'opaque'
331
276
  });
332
- const screenSize = vector2(this.canvasRef.width, this.canvasRef.height);
333
- camera.setScreenSize(screenSize);
334
- 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);
335
286
  })();
336
287
  }
337
288
  stop() {
@@ -346,35 +297,7 @@ export class Simulation extends Settings {
346
297
  getSceneObjects() {
347
298
  return this.scene.map((item) => item.getObj());
348
299
  }
349
- render(ctx) {
350
- const device = globalInfo.getDevice();
351
- if (this.canvasRef === null || device === null)
352
- return;
353
- const canvas = this.canvasRef;
354
- canvas.width = canvas.clientWidth * devicePixelRatio;
355
- canvas.height = canvas.clientHeight * devicePixelRatio;
356
- const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
357
- const shaderModule = device.createShaderModule({ code: shader });
358
- ctx.configure({
359
- device,
360
- format: presentationFormat,
361
- alphaMode: 'premultiplied'
362
- });
363
- const instanceBuffer = device.createBuffer({
364
- size: 10 * 4 * 16,
365
- usage: GPUBufferUsage.STORAGE
366
- });
367
- const bindGroupLayout = device.createBindGroupLayout(baseBindGroupLayout);
368
- this.renderInfo = {
369
- bindGroupLayout,
370
- instanceBuffer,
371
- vertexBuffer: null
372
- };
373
- this.pipelines = {
374
- triangleList: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-list'),
375
- triangleStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'triangle-strip'),
376
- lineStrip: createPipeline(device, shaderModule, [bindGroupLayout], presentationFormat, 'line-strip')
377
- };
300
+ render(device, ctx, canvas) {
378
301
  const colorAttachment = {
379
302
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
380
303
  // @ts-ignore
@@ -414,12 +337,10 @@ export class Simulation extends Settings {
414
337
  const diff = Math.max(now - prev, 1);
415
338
  prev = now;
416
339
  const fps = 1000 / diff;
417
- if (fps === prevFps && this.frameRateView.isActive()) {
340
+ if (this.frameRateView.isActive() && fps === prevFps) {
418
341
  this.frameRateView.updateFrameRate(fps);
419
342
  }
420
343
  prevFps = fps;
421
- canvas.width = canvas.clientWidth * devicePixelRatio;
422
- canvas.height = canvas.clientHeight * devicePixelRatio;
423
344
  const screenSize = camera.getScreenSize();
424
345
  if (screenSize[0] !== canvas.width || screenSize[1] !== canvas.height) {
425
346
  camera.setScreenSize(vector2(canvas.width, canvas.height));
@@ -444,23 +365,18 @@ export class Simulation extends Settings {
444
365
  }
445
366
  const commandEncoder = device.createCommandEncoder();
446
367
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
447
- passEncoder.setPipeline(this.pipelines.triangleList);
448
368
  const totalVertices = getTotalVertices(this.scene);
449
- if (this.renderInfo.vertexBuffer === null ||
450
- this.renderInfo.vertexBuffer.size / (4 * BUF_LEN) < totalVertices) {
451
- this.renderInfo.vertexBuffer = device.createBuffer({
452
- size: totalVertices * 4 * BUF_LEN,
453
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
454
- });
455
- }
456
- 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);
457
373
  camera.updateConsumed();
458
374
  passEncoder.end();
459
375
  device.queue.submit([commandEncoder.finish()]);
460
376
  };
461
377
  requestAnimationFrame(frame);
462
378
  }
463
- renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff, shaderInfo) {
379
+ renderScene(device, passEncoder, vertexBuffer, scene, startOffset, diff, transparent, shaderInfo) {
464
380
  if (this.pipelines === null)
465
381
  return 0;
466
382
  let currentOffset = startOffset;
@@ -476,6 +392,10 @@ export class Simulation extends Settings {
476
392
  scene[i].traverseLife(diff);
477
393
  }
478
394
  const obj = scene[i].getObj();
395
+ if (!transparent && obj.isTransparent()) {
396
+ this.transparentElements.add(scene[i]);
397
+ continue;
398
+ }
479
399
  if (obj.hasChildren()) {
480
400
  let shaderInfo = undefined;
481
401
  if (obj instanceof ShaderGroup) {
@@ -493,7 +413,7 @@ export class Simulation extends Settings {
493
413
  };
494
414
  }
495
415
  }
496
- currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getChildrenInfos(), currentOffset, diff, shaderInfo);
416
+ currentOffset += this.renderScene(device, passEncoder, vertexBuffer, obj.getChildrenInfos(), currentOffset, diff, transparent, shaderInfo);
497
417
  }
498
418
  if (obj.isEmpty)
499
419
  continue;
@@ -511,15 +431,25 @@ export class Simulation extends Settings {
511
431
  passEncoder.setPipeline(shaderInfo.pipeline);
512
432
  }
513
433
  else if (obj.isWireframe()) {
514
- passEncoder.setPipeline(this.pipelines.lineStrip);
434
+ if (obj.isTransparent())
435
+ passEncoder.setPipeline(this.pipelines.lineStripTransparent);
436
+ else
437
+ passEncoder.setPipeline(this.pipelines.lineStrip);
515
438
  }
516
439
  else {
517
440
  const type = obj.getGeometryType();
441
+ // must be exhaustive
518
442
  if (type === 'strip') {
519
- passEncoder.setPipeline(this.pipelines.triangleStrip);
443
+ if (obj.isTransparent())
444
+ passEncoder.setPipeline(this.pipelines.triangleStripTransparent);
445
+ else
446
+ passEncoder.setPipeline(this.pipelines.triangleStrip);
520
447
  }
521
448
  else if (type === 'list') {
522
- passEncoder.setPipeline(this.pipelines.triangleList);
449
+ if (obj.isTransparent())
450
+ passEncoder.setPipeline(this.pipelines.triangleListTransparent);
451
+ else
452
+ passEncoder.setPipeline(this.pipelines.triangleList);
523
453
  }
524
454
  }
525
455
  let instances = 1;
@@ -533,23 +463,7 @@ export class Simulation extends Settings {
533
463
  else {
534
464
  instanceBuffer = this.renderInfo.instanceBuffer;
535
465
  }
536
- const uniformBindGroup = device.createBindGroup({
537
- layout: this.renderInfo.bindGroupLayout,
538
- entries: [
539
- {
540
- binding: 0,
541
- resource: {
542
- buffer: uniformBuffer
543
- }
544
- },
545
- {
546
- binding: 1,
547
- resource: {
548
- buffer: instanceBuffer
549
- }
550
- }
551
- ]
552
- });
466
+ const uniformBindGroup = createUniformBindGroup(defaultShader, [uniformBuffer, instanceBuffer]);
553
467
  passEncoder.setBindGroup(0, uniformBindGroup);
554
468
  }
555
469
  if (shaderInfo && shaderInfo.bufferInfo) {
@@ -623,7 +537,7 @@ export class ShaderGroup extends EmptyElement {
623
537
  const device = globalInfo.errorGetDevice();
624
538
  this.module = device.createShaderModule({ code: this.code });
625
539
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
626
- const bindGroupLayout = device.createBindGroupLayout(baseBindGroupLayout);
540
+ const bindGroupLayout = defaultShader.getBindGroupLayout();
627
541
  const bindGroups = [bindGroupLayout];
628
542
  if (this.bindGroup !== null) {
629
543
  const entryValues = this.bindGroup.bindings.map((binding, index) => ({
@@ -636,7 +550,9 @@ export class ShaderGroup extends EmptyElement {
636
550
  });
637
551
  bindGroups.push(this.bindGroupLayout);
638
552
  }
639
- 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);
640
556
  }
641
557
  getBindGroupLayout() {
642
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.3",
8
+ "version": "0.7.4",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",