matrix-engine-wgpu 1.0.6 → 1.1.1

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.
Files changed (71) hide show
  1. package/.codesandbox/tasks.json +46 -0
  2. package/.devcontainer/devcontainer.json +22 -0
  3. package/.github/dependabot.yml +12 -0
  4. package/REFERENCE.md +3 -5
  5. package/dev.md +460 -0
  6. package/empty.js +7 -6
  7. package/examples/games/jamb/jamb.js +1133 -0
  8. package/examples/load-obj-file.js +65 -28
  9. package/examples/unlit-textures.js +26 -23
  10. package/examples.js +35 -3
  11. package/index.js +8 -2
  12. package/main.js +454 -48
  13. package/non-project-files/dev.txt +21 -0
  14. package/non-project-files/image1.png +0 -0
  15. package/non-project-files/image6.png +0 -0
  16. package/package.json +31 -13
  17. package/public/app.js +2234 -114
  18. package/public/css/style.css +371 -110
  19. package/public/empty.html +1 -1
  20. package/public/empty.js +9887 -9264
  21. package/public/examples.html +10 -8
  22. package/public/examples.js +2035 -247
  23. package/public/index.html +3 -5
  24. package/public/manifest copy.web +35 -0
  25. package/public/res/audios/block.mp3 +0 -0
  26. package/public/res/audios/dice1.mp3 +0 -0
  27. package/public/res/audios/dice2.mp3 +0 -0
  28. package/public/res/audios/start.mp3 +0 -0
  29. package/public/res/meshes/jamb/bg.blend +0 -0
  30. package/public/res/meshes/jamb/bg.blend1 +0 -0
  31. package/public/res/meshes/jamb/bg.mtl +12 -0
  32. package/public/res/meshes/jamb/bg.obj +17 -0
  33. package/public/res/meshes/jamb/bg.png +0 -0
  34. package/public/res/meshes/jamb/dice-default.png +0 -0
  35. package/public/res/meshes/jamb/dice-mark.png +0 -0
  36. package/public/res/meshes/jamb/dice.mtl +12 -0
  37. package/public/res/meshes/jamb/dice.obj +40 -0
  38. package/public/res/meshes/jamb/dice.png +0 -0
  39. package/public/res/meshes/jamb/jamb-title.mtl +12 -0
  40. package/public/res/meshes/jamb/jamb-title.obj +26008 -0
  41. package/public/res/meshes/jamb/jamb.blend +0 -0
  42. package/public/res/meshes/jamb/jamb.blend1 +0 -0
  43. package/public/res/meshes/jamb/logo.png +0 -0
  44. package/public/res/meshes/jamb/nidzaDice.blend +0 -0
  45. package/public/res/meshes/jamb/nidzaDice.blend1 +0 -0
  46. package/public/res/meshes/jamb/pile.blend +0 -0
  47. package/public/res/meshes/jamb/simpleCube.blend +0 -0
  48. package/public/res/meshes/jamb/simpleCube.blend1 +0 -0
  49. package/public/res/meshes/jamb/sounds/roll1.wav +0 -0
  50. package/public/res/meshes/jamb/text.png +0 -0
  51. package/public/res/multilang/en.json +27 -0
  52. package/public/res/multilang/sr.json +27 -0
  53. package/public/test.html +636 -0
  54. package/public/three-test.js +165 -0
  55. package/public/worker.html +1 -1
  56. package/readme.md +193 -115
  57. package/src/engine/ball.js +477 -468
  58. package/src/engine/cube.js +486 -468
  59. package/src/engine/engine.js +4 -6
  60. package/src/engine/loader-obj.js +9 -6
  61. package/src/engine/matrix-class.js +237 -204
  62. package/src/engine/mesh-obj.js +603 -515
  63. package/src/engine/raycast.js +101 -0
  64. package/src/engine/utils.js +69 -3
  65. package/src/multilang/lang.js +35 -0
  66. package/src/physics/matrix-ammo.js +168 -15
  67. package/src/shaders/fragment.wgsl.js +4 -2
  68. package/src/shaders/shaders.js +1 -1
  69. package/src/shaders/vertexShadow.wgsl.js +1 -1
  70. package/src/sounds/sounds.js +47 -0
  71. package/src/world.js +311 -248
@@ -5,472 +5,481 @@ import {createInputHandler} from "./engine";
5
5
 
6
6
  export default class MEBall {
7
7
 
8
- constructor(canvas, device, context, o) {
9
- this.context = context;
10
- this.device = device;
11
-
12
- // The input handler
13
- this.inputHandler = createInputHandler(window, canvas);
14
- this.cameras = o.cameras;
15
- this.scale = o.scale;
16
- console.log('passed : o.mainCameraParams.responseCoef ', o.mainCameraParams.responseCoef)
17
- this.mainCameraParams = {
18
- type: o.mainCameraParams.type,
19
- responseCoef: o.mainCameraParams.responseCoef
20
- } // | WASD 'arcball' };
21
-
22
- this.lastFrameMS = 0;
23
-
24
- this.entityArgPass = o.entityArgPass;
25
-
26
- this.SphereLayout = {
27
- vertexStride: 8 * 4,
28
- positionsOffset: 0,
29
- normalOffset: 3 * 4,
30
- uvOffset: 6 * 4,
31
- };
32
-
33
- this.texturesPaths = [];
34
-
35
- o.texturesPaths.forEach((t) => {
36
- this.texturesPaths.push(t)
37
- })
38
-
39
- this.position = new Position(o.position.x, o.position.y, o.position.z)
40
- this.rotation = new Rotation(o.rotation.x, o.rotation.y, o.rotation.z);
41
- this.rotation.rotationSpeed.x = o.rotationSpeed.x;
42
- this.rotation.rotationSpeed.y = o.rotationSpeed.y;
43
- this.rotation.rotationSpeed.z = o.rotationSpeed.z;
44
-
45
- this.shaderModule = device.createShaderModule({code: UNLIT_SHADER});
46
- this.presentationFormat = navigator.gpu.getPreferredCanvasFormat();
47
-
48
- this.pipeline = device.createRenderPipeline({
49
- layout: 'auto',
50
- vertex: {
51
- module: this.shaderModule,
52
- entryPoint: 'vertexMain',
53
- buffers: [
54
- {
55
- arrayStride: this.SphereLayout.vertexStride,
56
- attributes: [
57
- {
58
- // position
59
- shaderLocation: 0,
60
- offset: this.SphereLayout.positionsOffset,
61
- format: 'float32x3',
62
- },
63
- {
64
- // normal
65
- shaderLocation: 1,
66
- offset: this.SphereLayout.normalOffset,
67
- format: 'float32x3',
68
- },
69
- {
70
- // uv
71
- shaderLocation: 2,
72
- offset: this.SphereLayout.uvOffset,
73
- format: 'float32x2',
74
- },
75
- ],
76
- },
77
- ],
78
- },
79
- fragment: {
80
- module: this.shaderModule,
81
- entryPoint: 'fragmentMain',
82
- targets: [
83
- {
84
- format: this.presentationFormat,
85
- },
86
- ],
87
- },
88
- primitive: {
89
- topology: 'triangle-list',
90
-
91
- // Backface culling since the sphere is solid piece of geometry.
92
- // Faces pointing away from the camera will be occluded by faces
93
- // pointing toward the camera.
94
- cullMode: 'back',
95
- },
96
-
97
- // Enable depth testing so that the fragment closest to the camera
98
- // is rendered in front.
99
- depthStencil: {
100
- depthWriteEnabled: true,
101
- depthCompare: 'less',
102
- format: 'depth24plus',
103
- },
104
- });
105
-
106
- this.depthTexture = device.createTexture({
107
- size: [canvas.width, canvas.height],
108
- format: 'depth24plus',
109
- usage: GPUTextureUsage.RENDER_ATTACHMENT,
110
- });
111
-
112
- this.uniformBufferSize = 4 * 16; // 4x4 matrix
113
- this.uniformBuffer = device.createBuffer({
114
- size: this.uniformBufferSize,
115
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
116
- });
117
-
118
- // Fetch the images and upload them into a GPUTexture.
119
- this.texture0 = null;
120
- this.moonTexture = null;
121
-
122
- this.settings = {
123
- useRenderBundles: true,
124
- asteroidCount: 15,
125
- };
126
-
127
- this.loadTex0(this.texturesPaths, device).then(() => {
128
- this.loadTex1(device).then(() => {
129
- this.sampler = device.createSampler({
130
- magFilter: 'linear',
131
- minFilter: 'linear',
132
- });
133
-
134
- this.transform = mat4.create();
135
- mat4.identity(this.transform);
136
-
137
- // Create one large central planet surrounded by a large ring of asteroids
138
- this.planet = this.createGeometry(this.scale);
139
- this.planet.bindGroup = this.createSphereBindGroup(this.texture0, this.transform);
140
-
141
- var asteroids = [
142
- this.createGeometry(12, 8, 6, 0.15),
143
- ];
144
-
145
- this.renderables = [this.planet];
146
-
147
- // this.ensureEnoughAsteroids(asteroids, this.transform);
148
- this.renderPassDescriptor = {
149
- colorAttachments: [
150
- {
151
- view: undefined,
152
- clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
153
- loadOp: this.entityArgPass.loadOp,
154
- storeOp: this.entityArgPass.storeOp,
155
- },
156
- ],
157
- depthStencilAttachment: {
158
- view: this.depthTexture.createView(),
159
- depthClearValue: 1.0,
160
- depthLoadOp: this.entityArgPass.depthLoadOp,
161
- depthStoreOp: this.entityArgPass.depthStoreOp,
162
- },
163
- };
164
-
165
- const aspect = canvas.width / canvas.height;
166
- this.projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0);
167
- this.modelViewProjectionMatrix = mat4.create();
168
-
169
- this.frameBindGroup = device.createBindGroup({
170
- layout: this.pipeline.getBindGroupLayout(0),
171
- entries: [
172
- {
173
- binding: 0,
174
- resource: {
175
- buffer: this.uniformBuffer,
176
- },
177
- },
178
- ],
179
- });
180
-
181
-
182
- // The render bundle can be encoded once and re-used as many times as needed.
183
- // Because it encodes all of the commands needed to render at the GPU level,
184
- // those commands will not need to execute the associated JavaScript code upon
185
- // execution or be re-validated, which can represent a significant time savings.
186
- //
187
- // However, because render bundles are immutable once created, they are only
188
- // appropriate for rendering content where the same commands will be executed
189
- // every time, with the only changes being the contents of the buffers and
190
- // textures used. Cases where the executed commands differ from frame-to-frame,
191
- // such as when using frustrum or occlusion culling, will not benefit from
192
- // using render bundles as much.
193
- this.renderBundle;
194
- this.updateRenderBundle();
195
-
196
- })
197
- })
198
-
199
- }
200
-
201
- ensureEnoughAsteroids(asteroids, transform) {
202
- for(let i = this.renderables.length;i <= this.settings.asteroidCount;++i) {
203
- // Place copies of the asteroid in a ring.
204
- const radius = Math.random() * 1.7 + 1.25;
205
- const angle = Math.random() * Math.PI * 2;
206
- const x = Math.sin(angle) * radius;
207
- const y = (Math.random() - 0.5) * 0.015;
208
- const z = Math.cos(angle) * radius;
209
-
210
- mat4.identity(transform);
211
- mat4.translate(transform, [x, y, z], transform);
212
- mat4.rotateX(transform, Math.random() * Math.PI, transform);
213
- mat4.rotateY(transform, Math.random() * Math.PI, transform);
214
- this.renderables.push({
215
- ...asteroids[i % asteroids.length],
216
- bindGroup: this.createSphereBindGroup(this.moonTexture, transform),
217
- });
218
- }
219
- }
220
-
221
- updateRenderBundle() {
222
- console.log('updateRenderBundle')
223
- const renderBundleEncoder = this.device.createRenderBundleEncoder({
224
- colorFormats: [this.presentationFormat],
225
- depthStencilFormat: 'depth24plus',
226
- });
227
- this.renderScene(renderBundleEncoder);
228
- this.renderBundle = renderBundleEncoder.finish();
229
- }
230
-
231
- createGeometry(radius, widthSegments = 8, heightSegments = 4, randomness = 0) {
232
-
233
- const sphereMesh = this.createSphereMesh(radius, widthSegments, heightSegments, randomness);
234
- // Create a vertex buffer from the sphere data.
235
- const vertices = this.device.createBuffer({
236
- size: sphereMesh.vertices.byteLength,
237
- usage: GPUBufferUsage.VERTEX,
238
- mappedAtCreation: true,
239
- });
240
- new Float32Array(vertices.getMappedRange()).set(sphereMesh.vertices);
241
- vertices.unmap();
242
-
243
- const indices = this.device.createBuffer({
244
- size: sphereMesh.indices.byteLength,
245
- usage: GPUBufferUsage.INDEX,
246
- mappedAtCreation: true,
247
- });
248
- new Uint16Array(indices.getMappedRange()).set(sphereMesh.indices);
249
- indices.unmap();
250
-
251
- return {
252
- vertices,
253
- indices,
254
- indexCount: sphereMesh.indices.length,
255
- };
256
- }
257
-
258
- createSphereBindGroup(texture, transform) {
259
-
260
- const uniformBufferSize = 4 * 16; // 4x4 matrix
261
- const uniformBuffer = this.device.createBuffer({
262
- size: uniformBufferSize,
263
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
264
- mappedAtCreation: true,
265
- });
266
- new Float32Array(uniformBuffer.getMappedRange()).set(transform);
267
- uniformBuffer.unmap();
268
-
269
- const bindGroup = this.device.createBindGroup({
270
- layout: this.pipeline.getBindGroupLayout(1),
271
- entries: [
272
- {
273
- binding: 0,
274
- resource: {
275
- buffer: uniformBuffer,
276
- },
277
- },
278
- {
279
- binding: 1,
280
- resource: this.sampler,
281
- },
282
- {
283
- binding: 2,
284
- resource: texture.createView(),
285
- },
286
- ],
287
- });
288
-
289
- return bindGroup;
290
- }
291
-
292
- getTransformationMatrix(pos) {
293
- // const viewMatrix = mat4.identity();
294
- const now = Date.now();
295
- const deltaTime = (now - this.lastFrameMS) / this.mainCameraParams.responseCoef;
296
- this.lastFrameMS = now;
297
-
298
- // const viewMatrix = mat4.identity(); ORI
299
- const camera = this.cameras[this.mainCameraParams.type];
300
- const viewMatrix = camera.update(deltaTime, this.inputHandler());
301
-
302
- mat4.translate(viewMatrix, vec3.fromValues(pos.x, pos.y, pos.z), viewMatrix);
303
-
304
- mat4.rotateX(viewMatrix, Math.PI * this.rotation.getRotX(), viewMatrix);
305
- mat4.rotateY(viewMatrix, Math.PI * this.rotation.getRotY(), viewMatrix);
306
- mat4.rotateZ(viewMatrix, Math.PI * this.rotation.getRotZ(), viewMatrix);
307
-
308
- mat4.multiply(this.projectionMatrix, viewMatrix, this.modelViewProjectionMatrix);
309
- return this.modelViewProjectionMatrix;
310
- }
311
-
312
- async loadTex1(device) {
313
- return new Promise(async (resolve) => {
314
- const response = await fetch('./res/textures/tex1.jpg');
315
- const imageBitmap = await createImageBitmap(await response.blob());
316
-
317
- this.moonTexture = device.createTexture({
318
- size: [imageBitmap.width, imageBitmap.height, 1],
319
- format: 'rgba8unorm',
320
- usage:
321
- GPUTextureUsage.TEXTURE_BINDING |
322
- GPUTextureUsage.COPY_DST |
323
- GPUTextureUsage.RENDER_ATTACHMENT,
324
- });
325
- var moonTexture = this.moonTexture
326
- device.queue.copyExternalImageToTexture(
327
- {source: imageBitmap},
328
- {texture: moonTexture},
329
- [imageBitmap.width, imageBitmap.height]
330
- );
331
- resolve()
332
- })
333
- }
334
-
335
- async loadTex0(paths, device) {
336
- return new Promise(async (resolve) => {
337
- const response = await fetch(paths[0]);
338
- const imageBitmap = await createImageBitmap(await response.blob());
339
- console.log('loadTex0 WHAT IS THIS -> ', this)
340
- this.texture0 = device.createTexture({
341
- size: [imageBitmap.width, imageBitmap.height, 1],
342
- format: 'rgba8unorm',
343
- usage:
344
- GPUTextureUsage.TEXTURE_BINDING |
345
- GPUTextureUsage.COPY_DST |
346
- GPUTextureUsage.RENDER_ATTACHMENT,
347
- });
348
- var texture0 = this.texture0
349
- device.queue.copyExternalImageToTexture(
350
- {source: imageBitmap},
351
- {texture: texture0},
352
- [imageBitmap.width, imageBitmap.height]
353
- );
354
- resolve()
355
- })
356
-
357
- }
358
-
359
- createSphereMesh(radius, widthSegments = 3, heightSegments = 3, randomness = 0) {
360
- const vertices = [];
361
- const indices = [];
362
-
363
- widthSegments = Math.max(3, Math.floor(widthSegments));
364
- heightSegments = Math.max(2, Math.floor(heightSegments));
365
-
366
- const firstVertex = vec3.create();
367
- const vertex = vec3.create();
368
- const normal = vec3.create();
369
-
370
- let index = 0;
371
- const grid = [];
372
-
373
- // generate vertices, normals and uvs
374
- for(let iy = 0;iy <= heightSegments;iy++) {
375
- const verticesRow = [];
376
- const v = iy / heightSegments;
377
- // special case for the poles
378
- let uOffset = 0;
379
- if(iy === 0) {
380
- uOffset = 0.5 / widthSegments;
381
- } else if(iy === heightSegments) {
382
- uOffset = -0.5 / widthSegments;
383
- }
384
-
385
- for(let ix = 0;ix <= widthSegments;ix++) {
386
- const u = ix / widthSegments;
387
- // Poles should just use the same position all the way around.
388
- if(ix == widthSegments) {
389
- vec3.copy(firstVertex, vertex);
390
- } else if(ix == 0 || (iy != 0 && iy !== heightSegments)) {
391
- const rr = radius + (Math.random() - 0.5) * 2 * randomness * radius;
392
- // vertex
393
- vertex[0] = -rr * Math.cos(u * Math.PI * 2) * Math.sin(v * Math.PI);
394
- vertex[1] = rr * Math.cos(v * Math.PI);
395
- vertex[2] = rr * Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI);
396
- if(ix == 0) {
397
- vec3.copy(vertex, firstVertex);
398
- }
399
- }
400
- vertices.push(...vertex);
401
-
402
- // normal
403
- vec3.copy(vertex, normal);
404
- vec3.normalize(normal, normal);
405
- vertices.push(...normal);
406
- // uv
407
- vertices.push(u + uOffset, 1 - v);
408
- verticesRow.push(index++);
409
- }
410
- grid.push(verticesRow);
411
- }
412
- // indices
413
- for(let iy = 0;iy < heightSegments;iy++) {
414
- for(let ix = 0;ix < widthSegments;ix++) {
415
- const a = grid[iy][ix + 1];
416
- const b = grid[iy][ix];
417
- const c = grid[iy + 1][ix];
418
- const d = grid[iy + 1][ix + 1];
419
-
420
- if(iy !== 0) indices.push(a, b, d);
421
- if(iy !== heightSegments - 1) indices.push(b, c, d);
422
- }
423
- }
424
-
425
- return {
426
- vertices: new Float32Array(vertices),
427
- indices: new Uint16Array(indices),
428
- };
429
- }
430
-
431
- // Render bundles function as partial, limited render passes, so we can use the
432
- // same code both to render the scene normally and to build the render bundle.
433
- renderScene(passEncoder) {
434
-
435
- if(typeof this.renderables === 'undefined') return;
436
-
437
- passEncoder.setPipeline(this.pipeline);
438
- passEncoder.setBindGroup(0, this.frameBindGroup);
439
-
440
- // Loop through every renderable object and draw them individually.
441
- // (Because many of these meshes are repeated, with only the transforms
442
- // differing, instancing would be highly effective here. This sample
443
- // intentionally avoids using instancing in order to emulate a more complex
444
- // scene, which helps demonstrate the potential time savings a render bundle
445
- // can provide.)
446
- let count = 0;
447
- for(const renderable of this.renderables) {
448
- passEncoder.setBindGroup(1, renderable.bindGroup);
449
- passEncoder.setVertexBuffer(0, renderable.vertices);
450
- passEncoder.setIndexBuffer(renderable.indices, 'uint16');
451
- passEncoder.drawIndexed(renderable.indexCount);
452
-
453
- if(++count > this.settings.asteroidCount) {
454
- break;
455
- }
456
- }
457
- }
458
-
459
- draw = () => {
460
- if(this.moonTexture == null) {
461
- console.log('not ready')
462
- return;
463
- }
464
- const transformationMatrix = this.getTransformationMatrix(this.position);
465
- this.device.queue.writeBuffer(
466
- this.uniformBuffer,
467
- 0,
468
- transformationMatrix.buffer,
469
- transformationMatrix.byteOffset,
470
- transformationMatrix.byteLength
471
- );
472
- this.renderPassDescriptor.colorAttachments[0].view = this.context
473
- .getCurrentTexture()
474
- .createView();
475
- }
8
+ constructor(canvas, device, context, o) {
9
+ this.context = context;
10
+ this.device = device;
11
+
12
+ // The input handler
13
+ this.inputHandler = createInputHandler(window, canvas);
14
+ this.cameras = o.cameras;
15
+ this.scale = o.scale;
16
+ console.log('passed : o.mainCameraParams.responseCoef ', o.mainCameraParams.responseCoef)
17
+ this.mainCameraParams = {
18
+ type: o.mainCameraParams.type,
19
+ responseCoef: o.mainCameraParams.responseCoef
20
+ } // | WASD 'arcball' };
21
+
22
+ this.lastFrameMS = 0;
23
+
24
+ this.entityArgPass = o.entityArgPass;
25
+
26
+ this.SphereLayout = {
27
+ vertexStride: 8 * 4,
28
+ positionsOffset: 0,
29
+ normalOffset: 3 * 4,
30
+ uvOffset: 6 * 4,
31
+ };
32
+
33
+ if(typeof o.raycast === 'undefined') {
34
+ this.raycast = {
35
+ enabled: false,
36
+ radius: 2
37
+ };
38
+ } else {
39
+ this.raycast = o.raycast;
40
+ }
41
+
42
+ this.texturesPaths = [];
43
+
44
+ o.texturesPaths.forEach((t) => {
45
+ this.texturesPaths.push(t)
46
+ })
47
+
48
+ this.position = new Position(o.position.x, o.position.y, o.position.z)
49
+ this.rotation = new Rotation(o.rotation.x, o.rotation.y, o.rotation.z);
50
+ this.rotation.rotationSpeed.x = o.rotationSpeed.x;
51
+ this.rotation.rotationSpeed.y = o.rotationSpeed.y;
52
+ this.rotation.rotationSpeed.z = o.rotationSpeed.z;
53
+
54
+ this.shaderModule = device.createShaderModule({code: UNLIT_SHADER});
55
+ this.presentationFormat = navigator.gpu.getPreferredCanvasFormat();
56
+
57
+ this.pipeline = device.createRenderPipeline({
58
+ layout: 'auto',
59
+ vertex: {
60
+ module: this.shaderModule,
61
+ entryPoint: 'vertexMain',
62
+ buffers: [
63
+ {
64
+ arrayStride: this.SphereLayout.vertexStride,
65
+ attributes: [
66
+ {
67
+ // position
68
+ shaderLocation: 0,
69
+ offset: this.SphereLayout.positionsOffset,
70
+ format: 'float32x3',
71
+ },
72
+ {
73
+ // normal
74
+ shaderLocation: 1,
75
+ offset: this.SphereLayout.normalOffset,
76
+ format: 'float32x3',
77
+ },
78
+ {
79
+ // uv
80
+ shaderLocation: 2,
81
+ offset: this.SphereLayout.uvOffset,
82
+ format: 'float32x2',
83
+ },
84
+ ],
85
+ },
86
+ ],
87
+ },
88
+ fragment: {
89
+ module: this.shaderModule,
90
+ entryPoint: 'fragmentMain',
91
+ targets: [
92
+ {
93
+ format: this.presentationFormat,
94
+ },
95
+ ],
96
+ },
97
+ primitive: {
98
+ topology: 'triangle-list',
99
+
100
+ // Backface culling since the sphere is solid piece of geometry.
101
+ // Faces pointing away from the camera will be occluded by faces
102
+ // pointing toward the camera.
103
+ cullMode: 'back',
104
+ },
105
+
106
+ // Enable depth testing so that the fragment closest to the camera
107
+ // is rendered in front.
108
+ depthStencil: {
109
+ depthWriteEnabled: true,
110
+ depthCompare: 'less',
111
+ format: 'depth24plus',
112
+ },
113
+ });
114
+
115
+ this.depthTexture = device.createTexture({
116
+ size: [canvas.width, canvas.height],
117
+ format: 'depth24plus',
118
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
119
+ });
120
+
121
+ this.uniformBufferSize = 4 * 16; // 4x4 matrix
122
+ this.uniformBuffer = device.createBuffer({
123
+ size: this.uniformBufferSize,
124
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
125
+ });
126
+
127
+ // Fetch the images and upload them into a GPUTexture.
128
+ this.texture0 = null;
129
+ this.moonTexture = null;
130
+
131
+ this.settings = {
132
+ useRenderBundles: true,
133
+ asteroidCount: 15,
134
+ };
135
+
136
+ this.loadTex0(this.texturesPaths, device).then(() => {
137
+ this.loadTex1(this.texturesPaths, device).then(() => {
138
+ this.sampler = device.createSampler({
139
+ magFilter: 'linear',
140
+ minFilter: 'linear',
141
+ });
142
+
143
+ this.transform = mat4.create();
144
+ mat4.identity(this.transform);
145
+
146
+ // Create one large central planet surrounded by a large ring of asteroids
147
+ this.planet = this.createGeometry(this.scale);
148
+ this.planet.bindGroup = this.createSphereBindGroup(this.texture0, this.transform);
149
+
150
+ var asteroids = [
151
+ this.createGeometry(12, 8, 6, 0.15),
152
+ ];
153
+
154
+ this.renderables = [this.planet];
155
+
156
+ // this.ensureEnoughAsteroids(asteroids, this.transform);
157
+ this.renderPassDescriptor = {
158
+ colorAttachments: [
159
+ {
160
+ view: undefined,
161
+ clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
162
+ loadOp: this.entityArgPass.loadOp,
163
+ storeOp: this.entityArgPass.storeOp,
164
+ },
165
+ ],
166
+ depthStencilAttachment: {
167
+ view: this.depthTexture.createView(),
168
+ depthClearValue: 1.0,
169
+ depthLoadOp: this.entityArgPass.depthLoadOp,
170
+ depthStoreOp: this.entityArgPass.depthStoreOp,
171
+ },
172
+ };
173
+
174
+ const aspect = canvas.width / canvas.height;
175
+ this.projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0);
176
+ this.modelViewProjectionMatrix = mat4.create();
177
+
178
+ this.frameBindGroup = device.createBindGroup({
179
+ layout: this.pipeline.getBindGroupLayout(0),
180
+ entries: [
181
+ {
182
+ binding: 0,
183
+ resource: {
184
+ buffer: this.uniformBuffer,
185
+ },
186
+ },
187
+ ],
188
+ });
189
+
190
+
191
+ // The render bundle can be encoded once and re-used as many times as needed.
192
+ // Because it encodes all of the commands needed to render at the GPU level,
193
+ // those commands will not need to execute the associated JavaScript code upon
194
+ // execution or be re-validated, which can represent a significant time savings.
195
+ //
196
+ // However, because render bundles are immutable once created, they are only
197
+ // appropriate for rendering content where the same commands will be executed
198
+ // every time, with the only changes being the contents of the buffers and
199
+ // textures used. Cases where the executed commands differ from frame-to-frame,
200
+ // such as when using frustrum or occlusion culling, will not benefit from
201
+ // using render bundles as much.
202
+ this.renderBundle;
203
+ this.updateRenderBundle();
204
+
205
+ })
206
+ })
207
+
208
+ }
209
+
210
+ ensureEnoughAsteroids(asteroids, transform) {
211
+ for(let i = this.renderables.length;i <= this.settings.asteroidCount;++i) {
212
+ // Place copies of the asteroid in a ring.
213
+ const radius = Math.random() * 1.7 + 1.25;
214
+ const angle = Math.random() * Math.PI * 2;
215
+ const x = Math.sin(angle) * radius;
216
+ const y = (Math.random() - 0.5) * 0.015;
217
+ const z = Math.cos(angle) * radius;
218
+
219
+ mat4.identity(transform);
220
+ mat4.translate(transform, [x, y, z], transform);
221
+ mat4.rotateX(transform, Math.random() * Math.PI, transform);
222
+ mat4.rotateY(transform, Math.random() * Math.PI, transform);
223
+ this.renderables.push({
224
+ ...asteroids[i % asteroids.length],
225
+ bindGroup: this.createSphereBindGroup(this.moonTexture, transform),
226
+ });
227
+ }
228
+ }
229
+
230
+ updateRenderBundle() {
231
+ console.log('updateRenderBundle')
232
+ const renderBundleEncoder = this.device.createRenderBundleEncoder({
233
+ colorFormats: [this.presentationFormat],
234
+ depthStencilFormat: 'depth24plus',
235
+ });
236
+ this.renderScene(renderBundleEncoder);
237
+ this.renderBundle = renderBundleEncoder.finish();
238
+ }
239
+
240
+ createGeometry(radius, widthSegments = 8, heightSegments = 4, randomness = 0) {
241
+
242
+ const sphereMesh = this.createSphereMesh(radius, widthSegments, heightSegments, randomness);
243
+ // Create a vertex buffer from the sphere data.
244
+ const vertices = this.device.createBuffer({
245
+ size: sphereMesh.vertices.byteLength,
246
+ usage: GPUBufferUsage.VERTEX,
247
+ mappedAtCreation: true,
248
+ });
249
+ new Float32Array(vertices.getMappedRange()).set(sphereMesh.vertices);
250
+ vertices.unmap();
251
+
252
+ const indices = this.device.createBuffer({
253
+ size: sphereMesh.indices.byteLength,
254
+ usage: GPUBufferUsage.INDEX,
255
+ mappedAtCreation: true,
256
+ });
257
+ new Uint16Array(indices.getMappedRange()).set(sphereMesh.indices);
258
+ indices.unmap();
259
+
260
+ return {
261
+ vertices,
262
+ indices,
263
+ indexCount: sphereMesh.indices.length,
264
+ };
265
+ }
266
+
267
+ createSphereBindGroup(texture, transform) {
268
+
269
+ const uniformBufferSize = 4 * 16; // 4x4 matrix
270
+ const uniformBuffer = this.device.createBuffer({
271
+ size: uniformBufferSize,
272
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
273
+ mappedAtCreation: true,
274
+ });
275
+ new Float32Array(uniformBuffer.getMappedRange()).set(transform);
276
+ uniformBuffer.unmap();
277
+
278
+ const bindGroup = this.device.createBindGroup({
279
+ layout: this.pipeline.getBindGroupLayout(1),
280
+ entries: [
281
+ {
282
+ binding: 0,
283
+ resource: {
284
+ buffer: uniformBuffer,
285
+ },
286
+ },
287
+ {
288
+ binding: 1,
289
+ resource: this.sampler,
290
+ },
291
+ {
292
+ binding: 2,
293
+ resource: texture.createView(),
294
+ },
295
+ ],
296
+ });
297
+
298
+ return bindGroup;
299
+ }
300
+
301
+ getTransformationMatrix(pos) {
302
+ // const viewMatrix = mat4.identity();
303
+ const now = Date.now();
304
+ const deltaTime = (now - this.lastFrameMS) / this.mainCameraParams.responseCoef;
305
+ this.lastFrameMS = now;
306
+
307
+ // const viewMatrix = mat4.identity(); ORI
308
+ const camera = this.cameras[this.mainCameraParams.type];
309
+ const viewMatrix = camera.update(deltaTime, this.inputHandler());
310
+
311
+ mat4.translate(viewMatrix, vec3.fromValues(pos.x, pos.y, pos.z), viewMatrix);
312
+
313
+ mat4.rotateX(viewMatrix, Math.PI * this.rotation.getRotX(), viewMatrix);
314
+ mat4.rotateY(viewMatrix, Math.PI * this.rotation.getRotY(), viewMatrix);
315
+ mat4.rotateZ(viewMatrix, Math.PI * this.rotation.getRotZ(), viewMatrix);
316
+
317
+ mat4.multiply(this.projectionMatrix, viewMatrix, this.modelViewProjectionMatrix);
318
+ return this.modelViewProjectionMatrix;
319
+ }
320
+
321
+ async loadTex1(texPaths, device) {
322
+ return new Promise(async (resolve) => {
323
+ const response = await fetch(texPaths[0]);
324
+ const imageBitmap = await createImageBitmap(await response.blob());
325
+
326
+ this.moonTexture = device.createTexture({
327
+ size: [imageBitmap.width, imageBitmap.height, 1],
328
+ format: 'rgba8unorm',
329
+ usage:
330
+ GPUTextureUsage.TEXTURE_BINDING |
331
+ GPUTextureUsage.COPY_DST |
332
+ GPUTextureUsage.RENDER_ATTACHMENT,
333
+ });
334
+ var moonTexture = this.moonTexture
335
+ device.queue.copyExternalImageToTexture(
336
+ {source: imageBitmap},
337
+ {texture: moonTexture},
338
+ [imageBitmap.width, imageBitmap.height]
339
+ );
340
+ resolve()
341
+ })
342
+ }
343
+
344
+ async loadTex0(paths, device) {
345
+ return new Promise(async (resolve) => {
346
+ const response = await fetch(paths[0]);
347
+ const imageBitmap = await createImageBitmap(await response.blob());
348
+ console.log('loadTex0 WHAT IS THIS -> ', this)
349
+ this.texture0 = device.createTexture({
350
+ size: [imageBitmap.width, imageBitmap.height, 1],
351
+ format: 'rgba8unorm',
352
+ usage:
353
+ GPUTextureUsage.TEXTURE_BINDING |
354
+ GPUTextureUsage.COPY_DST |
355
+ GPUTextureUsage.RENDER_ATTACHMENT,
356
+ });
357
+ var texture0 = this.texture0
358
+ device.queue.copyExternalImageToTexture(
359
+ {source: imageBitmap},
360
+ {texture: texture0},
361
+ [imageBitmap.width, imageBitmap.height]
362
+ );
363
+ resolve()
364
+ })
365
+
366
+ }
367
+
368
+ createSphereMesh(radius, widthSegments = 3, heightSegments = 3, randomness = 0) {
369
+ const vertices = [];
370
+ const indices = [];
371
+
372
+ widthSegments = Math.max(3, Math.floor(widthSegments));
373
+ heightSegments = Math.max(2, Math.floor(heightSegments));
374
+
375
+ const firstVertex = vec3.create();
376
+ const vertex = vec3.create();
377
+ const normal = vec3.create();
378
+
379
+ let index = 0;
380
+ const grid = [];
381
+
382
+ // generate vertices, normals and uvs
383
+ for(let iy = 0;iy <= heightSegments;iy++) {
384
+ const verticesRow = [];
385
+ const v = iy / heightSegments;
386
+ // special case for the poles
387
+ let uOffset = 0;
388
+ if(iy === 0) {
389
+ uOffset = 0.5 / widthSegments;
390
+ } else if(iy === heightSegments) {
391
+ uOffset = -0.5 / widthSegments;
392
+ }
393
+
394
+ for(let ix = 0;ix <= widthSegments;ix++) {
395
+ const u = ix / widthSegments;
396
+ // Poles should just use the same position all the way around.
397
+ if(ix == widthSegments) {
398
+ vec3.copy(firstVertex, vertex);
399
+ } else if(ix == 0 || (iy != 0 && iy !== heightSegments)) {
400
+ const rr = radius + (Math.random() - 0.5) * 2 * randomness * radius;
401
+ // vertex
402
+ vertex[0] = -rr * Math.cos(u * Math.PI * 2) * Math.sin(v * Math.PI);
403
+ vertex[1] = rr * Math.cos(v * Math.PI);
404
+ vertex[2] = rr * Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI);
405
+ if(ix == 0) {
406
+ vec3.copy(vertex, firstVertex);
407
+ }
408
+ }
409
+ vertices.push(...vertex);
410
+
411
+ // normal
412
+ vec3.copy(vertex, normal);
413
+ vec3.normalize(normal, normal);
414
+ vertices.push(...normal);
415
+ // uv
416
+ vertices.push(u + uOffset, 1 - v);
417
+ verticesRow.push(index++);
418
+ }
419
+ grid.push(verticesRow);
420
+ }
421
+ // indices
422
+ for(let iy = 0;iy < heightSegments;iy++) {
423
+ for(let ix = 0;ix < widthSegments;ix++) {
424
+ const a = grid[iy][ix + 1];
425
+ const b = grid[iy][ix];
426
+ const c = grid[iy + 1][ix];
427
+ const d = grid[iy + 1][ix + 1];
428
+
429
+ if(iy !== 0) indices.push(a, b, d);
430
+ if(iy !== heightSegments - 1) indices.push(b, c, d);
431
+ }
432
+ }
433
+
434
+ return {
435
+ vertices: new Float32Array(vertices),
436
+ indices: new Uint16Array(indices),
437
+ };
438
+ }
439
+
440
+ // Render bundles function as partial, limited render passes, so we can use the
441
+ // same code both to render the scene normally and to build the render bundle.
442
+ renderScene(passEncoder) {
443
+
444
+ if(typeof this.renderables === 'undefined') return;
445
+
446
+ passEncoder.setPipeline(this.pipeline);
447
+ passEncoder.setBindGroup(0, this.frameBindGroup);
448
+
449
+ // Loop through every renderable object and draw them individually.
450
+ // (Because many of these meshes are repeated, with only the transforms
451
+ // differing, instancing would be highly effective here. This sample
452
+ // intentionally avoids using instancing in order to emulate a more complex
453
+ // scene, which helps demonstrate the potential time savings a render bundle
454
+ // can provide.)
455
+ let count = 0;
456
+ for(const renderable of this.renderables) {
457
+ passEncoder.setBindGroup(1, renderable.bindGroup);
458
+ passEncoder.setVertexBuffer(0, renderable.vertices);
459
+ passEncoder.setIndexBuffer(renderable.indices, 'uint16');
460
+ passEncoder.drawIndexed(renderable.indexCount);
461
+
462
+ if(++count > this.settings.asteroidCount) {
463
+ break;
464
+ }
465
+ }
466
+ }
467
+
468
+ draw = () => {
469
+ if(this.moonTexture == null) {
470
+ console.log('not ready')
471
+ return;
472
+ }
473
+ const transformationMatrix = this.getTransformationMatrix(this.position);
474
+ this.device.queue.writeBuffer(
475
+ this.uniformBuffer,
476
+ 0,
477
+ transformationMatrix.buffer,
478
+ transformationMatrix.byteOffset,
479
+ transformationMatrix.byteLength
480
+ );
481
+ this.renderPassDescriptor.colorAttachments[0].view = this.context
482
+ .getCurrentTexture()
483
+ .createView();
484
+ }
476
485
  }