matrix-engine-wgpu 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) 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 +5 -2
  5. package/dev.md +460 -0
  6. package/empty.js +7 -6
  7. package/examples/games/jamb/jamb.js +1127 -0
  8. package/examples/load-obj-file.js +67 -37
  9. package/examples/unlit-textures.js +26 -23
  10. package/examples.js +35 -3
  11. package/main.js +441 -43
  12. package/non-project-files/dev.txt +21 -0
  13. package/non-project-files/image1.png +0 -0
  14. package/non-project-files/image6.png +0 -0
  15. package/package.json +50 -41
  16. package/public/app.js +11409 -8893
  17. package/public/css/style.css +376 -112
  18. package/public/empty.html +1 -1
  19. package/public/empty.js +9890 -8885
  20. package/public/examples.html +10 -8
  21. package/public/examples.js +945 -203
  22. package/public/index.html +5 -7
  23. package/public/manifest copy.web +35 -0
  24. package/public/res/audios/block.mp3 +0 -0
  25. package/public/res/audios/dice1.mp3 +0 -0
  26. package/public/res/audios/dice2.mp3 +0 -0
  27. package/public/res/audios/start.mp3 +0 -0
  28. package/public/res/fonts/Accuratist.ttf +0 -0
  29. package/public/res/fonts/Closeness.ttf +0 -0
  30. package/public/res/fonts/WARGAMES.TTF +0 -0
  31. package/public/res/fonts/readme.txt +5 -0
  32. package/public/res/fonts/stormfaze.ttf +0 -0
  33. package/public/res/meshes/blender/cube.blend +0 -0
  34. package/public/res/meshes/blender/cube.blend1 +0 -0
  35. package/public/res/meshes/blender/cube.mtl +12 -0
  36. package/public/res/meshes/blender/cube.obj +46 -0
  37. package/public/res/meshes/blender/cube.png +0 -0
  38. package/public/res/meshes/blender/cubeSmartUV.blend +0 -0
  39. package/public/res/meshes/blender/cubeSmartUV.mtl +12 -0
  40. package/public/res/meshes/blender/cubeSmartUV.obj +46 -0
  41. package/public/res/meshes/blender/sphepe.blend +0 -0
  42. package/public/res/meshes/blender/sphepe.blend1 +0 -0
  43. package/public/res/meshes/blender/sphere.mtl +10 -0
  44. package/public/res/meshes/blender/sphere.obj +3402 -0
  45. package/public/res/meshes/jamb/bg.blend +0 -0
  46. package/public/res/meshes/jamb/bg.blend1 +0 -0
  47. package/public/res/meshes/jamb/bg.mtl +12 -0
  48. package/public/res/meshes/jamb/bg.obj +17 -0
  49. package/public/res/meshes/jamb/bg.png +0 -0
  50. package/public/res/meshes/jamb/dice-default.png +0 -0
  51. package/public/res/meshes/jamb/dice-mark.png +0 -0
  52. package/public/res/meshes/jamb/dice.mtl +12 -0
  53. package/public/res/meshes/jamb/dice.obj +40 -0
  54. package/public/res/meshes/jamb/dice.png +0 -0
  55. package/public/res/meshes/jamb/jamb-title.mtl +12 -0
  56. package/public/res/meshes/jamb/jamb-title.obj +26008 -0
  57. package/public/res/meshes/jamb/jamb.blend +0 -0
  58. package/public/res/meshes/jamb/jamb.blend1 +0 -0
  59. package/public/res/meshes/jamb/logo.png +0 -0
  60. package/public/res/meshes/jamb/nidzaDice.blend +0 -0
  61. package/public/res/meshes/jamb/nidzaDice.blend1 +0 -0
  62. package/public/res/meshes/jamb/pile.blend +0 -0
  63. package/public/res/meshes/jamb/simpleCube.blend +0 -0
  64. package/public/res/meshes/jamb/simpleCube.blend1 +0 -0
  65. package/public/res/meshes/jamb/sounds/roll1.wav +0 -0
  66. package/public/res/meshes/jamb/text.png +0 -0
  67. package/public/res/multilang/en.json +27 -0
  68. package/public/res/multilang/sr.json +27 -0
  69. package/public/test.html +636 -0
  70. package/public/three-test.js +165 -0
  71. package/public/worker.html +1 -1
  72. package/readme.md +232 -116
  73. package/src/engine/cube.js +10 -5
  74. package/src/engine/engine.js +3 -9
  75. package/src/engine/loader-obj.js +9 -6
  76. package/src/engine/matrix-class.js +240 -202
  77. package/src/engine/mesh-obj.js +605 -525
  78. package/src/engine/mesh.js +476 -0
  79. package/src/engine/raycast-test.js +93 -0
  80. package/src/engine/utils.js +129 -15
  81. package/src/multilang/lang.js +35 -0
  82. package/src/physics/matrix-ammo.js +204 -30
  83. package/src/shaders/fragment.wgsl.js +4 -2
  84. package/src/shaders/shaders.js +1 -1
  85. package/src/shaders/vertexShadow.wgsl.js +1 -1
  86. package/src/sounds/sounds.js +47 -0
  87. package/src/world.js +312 -236
  88. package/src/engine/matrix-mesh.js +0 -49
@@ -0,0 +1,476 @@
1
+ import {mat4, vec3} from 'wgpu-matrix';
2
+ import {Position, Rotation} from "./matrix-class";
3
+ import {createInputHandler} from "./engine";
4
+ import {vertexShadowWGSL} from '../shaders/vertexShadow.wgsl';
5
+ import {fragmentWGSL} from '../shaders/fragment.wgsl';
6
+ import {vertexWGSL} from '../shaders/vertex.wgsl';
7
+ import {downloadMeshes} from './loader-obj';
8
+
9
+ export default class MEMesh {
10
+
11
+ constructor(canvas, device, context, o) {
12
+
13
+ this.done = false;
14
+ this.device = device;
15
+ this.context = context;
16
+ this.entityArgPass = o.entityArgPass;
17
+
18
+ this.mesh = o.mesh;
19
+ this.inputHandler = createInputHandler(window, canvas);
20
+ this.cameras = o.cameras;
21
+ this.mainCameraParams = {
22
+ type: o.mainCameraParams.type,
23
+ responseCoef: o.mainCameraParams.responseCoef
24
+ }
25
+ this.lastFrameMS = 0;
26
+ this.texturesPaths = [];
27
+ o.texturesPaths.forEach((t) => {this.texturesPaths.push(t)})
28
+ this.presentationFormat = navigator.gpu.getPreferredCanvasFormat();
29
+ this.position = new Position(o.position.x, o.position.y, o.position.z);
30
+ // console.log('cube added on pos : ', this.position)
31
+ this.rotation = new Rotation(o.rotation.x, o.rotation.y, o.rotation.z);
32
+ this.rotation.rotationSpeed.x = o.rotationSpeed.x;
33
+ this.rotation.rotationSpeed.y = o.rotationSpeed.y;
34
+ this.rotation.rotationSpeed.z = o.rotationSpeed.z;
35
+ this.scale = o.scale;
36
+
37
+ // new
38
+ this.runProgram = () => {
39
+ return new Promise(async (resolve) => {
40
+ this.shadowDepthTextureSize = 1024;
41
+ const aspect = canvas.width / canvas.height;
42
+ this.projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 2000.0);
43
+ this.modelViewProjectionMatrix = mat4.create();
44
+
45
+ this.loadTex0(this.texturesPaths, device).then(() => {
46
+ resolve()
47
+ console.log('load tex for mesh', this.texture0)
48
+ })
49
+ })
50
+ }
51
+
52
+ this.runProgram().then(() => {
53
+ const aspect = canvas.width / canvas.height;
54
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
55
+ this.context.configure({
56
+ device: this.device,
57
+ format: presentationFormat,
58
+ alphaMode: 'premultiplied',
59
+ });
60
+
61
+ // Create the model vertex buffer.
62
+ this.vertexBuffer = this.device.createBuffer({
63
+ size: this.mesh.positions.length * 3 * 2 * Float32Array.BYTES_PER_ELEMENT,
64
+ usage: GPUBufferUsage.VERTEX,
65
+ mappedAtCreation: true,
66
+ });
67
+ {
68
+ const mapping = new Float32Array(this.vertexBuffer.getMappedRange());
69
+ for(let i = 0;i < this.mesh.positions.length;++i) {
70
+ mapping.set(this.mesh.positions[i], 6 * i);
71
+ mapping.set(this.mesh.normals[i], 6 * i + 3);
72
+ }
73
+ this.vertexBuffer.unmap();
74
+ }
75
+
76
+ // Create the model index buffer.
77
+ this.indexCount = this.mesh.triangles.length * 3;
78
+ this.indexBuffer = this.device.createBuffer({
79
+ size: this.indexCount * Uint16Array.BYTES_PER_ELEMENT,
80
+ usage: GPUBufferUsage.INDEX,
81
+ mappedAtCreation: true,
82
+ });
83
+ {
84
+ const mapping = new Uint16Array(this.indexBuffer.getMappedRange());
85
+ for(let i = 0;i < this.mesh.triangles.length;++i) {
86
+ mapping.set(this.mesh.triangles[i], 3 * i);
87
+ }
88
+ this.indexBuffer.unmap();
89
+ }
90
+
91
+ // Create the depth texture for rendering/sampling the shadow map.
92
+ this.shadowDepthTexture = this.device.createTexture({
93
+ size: [this.shadowDepthTextureSize, this.shadowDepthTextureSize, 1],
94
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
95
+ format: 'depth32float',
96
+ });
97
+ this.shadowDepthTextureView = this.shadowDepthTexture.createView();
98
+
99
+ // Create some common descriptors used for both the shadow pipeline
100
+ // and the color rendering pipeline.
101
+ this.vertexBuffers = [
102
+ {
103
+ arrayStride: Float32Array.BYTES_PER_ELEMENT * 6,
104
+ attributes: [
105
+ {
106
+ // position
107
+ shaderLocation: 0,
108
+ offset: 0,
109
+ format: 'float32x3',
110
+ },
111
+ {
112
+ // normal
113
+ shaderLocation: 1,
114
+ offset: Float32Array.BYTES_PER_ELEMENT * 3,
115
+ format: 'float32x3',
116
+ },
117
+ ],
118
+ },
119
+ ];
120
+
121
+ const primitive = {
122
+ topology: 'triangle-list',
123
+ cullMode: 'back',
124
+ };
125
+
126
+ this.uniformBufferBindGroupLayout = this.device.createBindGroupLayout({
127
+ entries: [
128
+ {
129
+ binding: 0,
130
+ visibility: GPUShaderStage.VERTEX,
131
+ buffer: {
132
+ type: 'uniform',
133
+ },
134
+ },
135
+ ],
136
+ });
137
+
138
+ this.shadowPipeline = this.device.createRenderPipeline({
139
+ layout: this.device.createPipelineLayout({
140
+ bindGroupLayouts: [
141
+ this.uniformBufferBindGroupLayout,
142
+ this.uniformBufferBindGroupLayout,
143
+ ],
144
+ }),
145
+ vertex: {
146
+ module: this.device.createShaderModule({
147
+ code: vertexShadowWGSL,
148
+ }),
149
+ buffers: this.vertexBuffers,
150
+ },
151
+ depthStencil: {
152
+ depthWriteEnabled: true,
153
+ depthCompare: 'less',
154
+ format: 'depth32float',
155
+ },
156
+ primitive,
157
+ });
158
+
159
+ // Create a bind group layout which holds the scene uniforms and
160
+ // the texture+sampler for depth. We create it manually because the WebPU
161
+ // implementation doesn't infer this from the shader (yet).
162
+ this.bglForRender = this.device.createBindGroupLayout({
163
+ entries: [
164
+ {
165
+ binding: 0,
166
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
167
+ buffer: {
168
+ type: 'uniform',
169
+ },
170
+ },
171
+ {
172
+ binding: 1,
173
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
174
+ texture: {
175
+ sampleType: 'depth',
176
+ },
177
+ },
178
+ {
179
+ binding: 2,
180
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
181
+ sampler: {
182
+ type: 'comparison',
183
+ },
184
+ },
185
+ {
186
+ binding: 3,
187
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
188
+ texture: {
189
+ sampleType: 'float',
190
+ },
191
+ },
192
+ {
193
+ binding: 4,
194
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
195
+ sampler: {
196
+ type: 'filtering',
197
+ },
198
+ }
199
+ ],
200
+ });
201
+
202
+ this.pipeline = this.device.createRenderPipeline({
203
+ layout: this.device.createPipelineLayout({
204
+ bindGroupLayouts: [this.bglForRender, this.uniformBufferBindGroupLayout],
205
+ }),
206
+ vertex: {
207
+ module: this.device.createShaderModule({
208
+ code: vertexWGSL,
209
+ }),
210
+ buffers: this.vertexBuffers,
211
+ },
212
+ fragment: {
213
+ module: this.device.createShaderModule({
214
+ code: fragmentWGSL,
215
+ }),
216
+ targets: [
217
+ {
218
+ format: presentationFormat,
219
+ },
220
+ ],
221
+ constants: {
222
+ shadowDepthTextureSize: this.shadowDepthTextureSize,
223
+ },
224
+ },
225
+ depthStencil: {
226
+ depthWriteEnabled: true,
227
+ depthCompare: 'less',
228
+ format: 'depth24plus-stencil8',
229
+ },
230
+ primitive,
231
+ });
232
+
233
+ const depthTexture = this.device.createTexture({
234
+ size: [canvas.width, canvas.height],
235
+ format: 'depth24plus-stencil8',
236
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
237
+ });
238
+
239
+ this.renderPassDescriptor = {
240
+ colorAttachments: [
241
+ {
242
+ // view is acquired and set in render loop.
243
+ view: undefined,
244
+
245
+ clearValue: {r: 0.5, g: 0.5, b: 0.5, a: 1.0},
246
+ loadOp: 'load',
247
+ storeOp: 'store',
248
+ },
249
+ ],
250
+ depthStencilAttachment: {
251
+ view: depthTexture.createView(),
252
+
253
+ depthClearValue: 1.0,
254
+ depthLoadOp: 'clear',
255
+ depthStoreOp: 'store',
256
+ stencilClearValue: 0,
257
+ stencilLoadOp: 'clear',
258
+ stencilStoreOp: 'store',
259
+ },
260
+ };
261
+
262
+ this.modelUniformBuffer = this.device.createBuffer({
263
+ size: 4 * 16, // 4x4 matrix
264
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
265
+ });
266
+
267
+ this.sceneUniformBuffer = this.device.createBuffer({
268
+ // Two 4x4 viewProj matrices,
269
+ // one for the camera and one for the light.
270
+ // Then a vec3 for the light position.
271
+ // Rounded to the nearest multiple of 16.
272
+ size: 2 * 4 * 16 + 4 * 4,
273
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
274
+ });
275
+
276
+ this.sceneBindGroupForShadow = this.device.createBindGroup({
277
+ layout: this.uniformBufferBindGroupLayout,
278
+ entries: [
279
+ {
280
+ binding: 0,
281
+ resource: {
282
+ buffer: this.sceneUniformBuffer,
283
+ },
284
+ },
285
+ ],
286
+ });
287
+
288
+ this.sceneBindGroupForRender = this.device.createBindGroup({
289
+ layout: this.bglForRender,
290
+ entries: [
291
+ {
292
+ binding: 0,
293
+ resource: {
294
+ buffer: this.sceneUniformBuffer,
295
+ },
296
+ },
297
+ {
298
+ binding: 1,
299
+ resource: this.shadowDepthTextureView,
300
+ },
301
+ {
302
+ binding: 2,
303
+ resource: this.device.createSampler({
304
+ compare: 'less',
305
+ }),
306
+ },
307
+ {
308
+ binding: 3,
309
+ resource: this.texture0.createView(),
310
+ },
311
+ {
312
+ binding: 4,
313
+ resource: this.sampler,
314
+ },
315
+ ],
316
+ });
317
+
318
+ this.modelBindGroup = this.device.createBindGroup({
319
+ layout: this.uniformBufferBindGroupLayout,
320
+ entries: [
321
+ {
322
+ binding: 0,
323
+ resource: {
324
+ buffer: this.modelUniformBuffer,
325
+ },
326
+ },
327
+ ],
328
+ });
329
+
330
+ // Rotates the camera around the origin based on time.
331
+ this.getTransformationMatrix = (pos) => {
332
+ const now = Date.now();
333
+ const deltaTime = (now - this.lastFrameMS) / this.mainCameraParams.responseCoef;
334
+ this.lastFrameMS = now;
335
+
336
+ const camera = this.cameras[this.mainCameraParams.type];
337
+ this.viewMatrix = camera.update(deltaTime, this.inputHandler());
338
+
339
+ mat4.translate(this.viewMatrix, vec3.fromValues(pos.x, pos.y, pos.z), this.viewMatrix);
340
+ mat4.rotateX(this.viewMatrix, Math.PI * this.rotation.getRotX(), this.viewMatrix);
341
+ mat4.rotateY(this.viewMatrix, Math.PI * this.rotation.getRotY(), this.viewMatrix);
342
+ mat4.rotateZ(this.viewMatrix, Math.PI * this.rotation.getRotZ(), this.viewMatrix);
343
+ mat4.multiply(this.projectionMatrix, this.viewMatrix, this.modelViewProjectionMatrix);
344
+
345
+ return this.modelViewProjectionMatrix;
346
+ }
347
+
348
+ this.upVector = vec3.fromValues(0, 1, 0);
349
+ this.origin = vec3.fromValues(0, 0, 0);
350
+
351
+ const lightPosition = vec3.fromValues(50, 100, -100);
352
+ const lightViewMatrix = mat4.lookAt(lightPosition, this.origin, this.upVector);
353
+ const lightProjectionMatrix = mat4.create();
354
+ {
355
+ const left = -80;
356
+ const right = 80;
357
+ const bottom = -80;
358
+ const top = 80;
359
+ const near = -200;
360
+ const far = 300;
361
+ mat4.ortho(left, right, bottom, top, near, far, lightProjectionMatrix);
362
+ }
363
+
364
+ const lightViewProjMatrix = mat4.multiply(
365
+ lightProjectionMatrix,
366
+ lightViewMatrix
367
+ );
368
+
369
+ // looks like affect on transformations for now const 0
370
+ const modelMatrix = mat4.translation([0, 0, 0]);
371
+ // The camera/light aren't moving, so write them into buffers now.
372
+ {
373
+ const lightMatrixData = lightViewProjMatrix; // as Float32Array;
374
+ this.device.queue.writeBuffer(
375
+ this.sceneUniformBuffer,
376
+ 0,
377
+ lightMatrixData.buffer,
378
+ lightMatrixData.byteOffset,
379
+ lightMatrixData.byteLength
380
+ );
381
+
382
+ const lightData = lightPosition;
383
+ this.device.queue.writeBuffer(
384
+ this.sceneUniformBuffer,
385
+ 128,
386
+ lightData.buffer,
387
+ lightData.byteOffset,
388
+ lightData.byteLength
389
+ );
390
+
391
+ const modelData = modelMatrix;
392
+ this.device.queue.writeBuffer(
393
+ this.modelUniformBuffer,
394
+ 0,
395
+ modelData.buffer,
396
+ modelData.byteOffset,
397
+ modelData.byteLength
398
+ );
399
+ }
400
+
401
+ this.shadowPassDescriptor = {
402
+ colorAttachments: [],
403
+ depthStencilAttachment: {
404
+ view: this.shadowDepthTextureView,
405
+ depthClearValue: 1.0,
406
+ depthLoadOp: 'clear',
407
+ depthStoreOp: 'store',
408
+ },
409
+ };
410
+
411
+ this.done = true;
412
+ })
413
+ }
414
+
415
+ async loadTex0(texturesPaths, device) {
416
+ this.sampler = device.createSampler({
417
+ magFilter: 'linear',
418
+ minFilter: 'linear',
419
+ });
420
+
421
+ return new Promise(async (resolve) => {
422
+ const response = await fetch(texturesPaths[0]);
423
+ const imageBitmap = await createImageBitmap(await response.blob());
424
+ this.texture0 = device.createTexture({
425
+ size: [imageBitmap.width, imageBitmap.height, 1],
426
+ format: 'rgba8unorm',
427
+ usage:
428
+ GPUTextureUsage.TEXTURE_BINDING |
429
+ GPUTextureUsage.COPY_DST |
430
+ GPUTextureUsage.RENDER_ATTACHMENT,
431
+ });
432
+
433
+ device.queue.copyExternalImageToTexture(
434
+ {source: imageBitmap},
435
+ {texture: this.texture0},
436
+ [imageBitmap.width, imageBitmap.height]
437
+ );
438
+ resolve()
439
+ })
440
+ }
441
+
442
+ draw = (commandEncoder) => {
443
+ if(this.done == false) return;
444
+ const transformationMatrix = this.getTransformationMatrix(this.position);
445
+ this.device.queue.writeBuffer(
446
+ this.sceneUniformBuffer,
447
+ 64,
448
+ transformationMatrix.buffer,
449
+ transformationMatrix.byteOffset,
450
+ transformationMatrix.byteLength
451
+ );
452
+ this.renderPassDescriptor.colorAttachments[0].view = this.context
453
+ .getCurrentTexture()
454
+ .createView();
455
+ {
456
+ const shadowPass = commandEncoder.beginRenderPass(this.shadowPassDescriptor);
457
+ shadowPass.setPipeline(this.shadowPipeline);
458
+ shadowPass.setBindGroup(0, this.sceneBindGroupForShadow);
459
+ shadowPass.setBindGroup(1, this.modelBindGroup);
460
+ shadowPass.setVertexBuffer(0, this.vertexBuffer);
461
+ shadowPass.setIndexBuffer(this.indexBuffer, 'uint16');
462
+ shadowPass.drawIndexed(this.indexCount);
463
+ shadowPass.end();
464
+ }
465
+ {
466
+ const renderPass = commandEncoder.beginRenderPass(this.renderPassDescriptor);
467
+ renderPass.setPipeline(this.pipeline);
468
+ renderPass.setBindGroup(0, this.sceneBindGroupForRender);
469
+ renderPass.setBindGroup(1, this.modelBindGroup);
470
+ renderPass.setVertexBuffer(0, this.vertexBuffer);
471
+ renderPass.setIndexBuffer(this.indexBuffer, 'uint16');
472
+ renderPass.drawIndexed(this.indexCount);
473
+ renderPass.end();
474
+ }
475
+ }
476
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @author Nikola Lukic
3
+ * @email zlatnaspirala@gmail.com
4
+ * @site https://maximumroulette.com
5
+ * @Licence GPL v3
6
+ * @credits chatgpt used for this script adaptation.
7
+ * @Note matrix-engine-wgpu adaptation test
8
+ * default for now:
9
+ * app.cameras['WASD']
10
+ * Only tested for WASD type of camera.
11
+ * app is global - will be fixed in future
12
+ */
13
+ import {mat4, vec3, vec4} from "wgpu-matrix";
14
+
15
+ let rayHitEvent;
16
+
17
+ export let touchCoordinate = {
18
+ enabled: false,
19
+ x: 0,
20
+ y: 0,
21
+ stopOnFirstDetectedHit: false
22
+ };
23
+
24
+ function multiplyMatrixVector(matrix, vector) {
25
+ return vec4.transformMat4(vector, matrix);
26
+ }
27
+
28
+ export function getRayFromMouse(event, canvas, camera) {
29
+ const rect = canvas.getBoundingClientRect();
30
+ let x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
31
+ let y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
32
+ // simple invert
33
+ x = -x;
34
+ const fov = Math.PI / 4;
35
+ const aspect = canvas.width / canvas.height;
36
+ const near = 0.1;
37
+ const far = 100;
38
+ camera.projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 1000.0);
39
+ const invProjection = mat4.inverse(camera.projectionMatrix);
40
+ const correctedView = mat4.clone(camera.view_);
41
+ correctedView[2] *= -1;
42
+ correctedView[6] *= -1;
43
+ correctedView[10] *= -1;
44
+ const invView = mat4.inverse(correctedView);
45
+ const ndc = [x, y, 1, 1];
46
+ let worldPos = multiplyMatrixVector(invProjection, ndc);
47
+ worldPos = multiplyMatrixVector(invView, worldPos);
48
+ let world;
49
+ if (worldPos[3] !== 0) {
50
+ world = [
51
+ worldPos[0] / worldPos[3],
52
+ worldPos[2] / worldPos[3],
53
+ worldPos[1] / worldPos[3]
54
+ ];
55
+ } else {
56
+ console.log("[raycaster]special case 0.")
57
+ world = [
58
+ worldPos[0],
59
+ worldPos[1],
60
+ worldPos[2]
61
+ ];
62
+ }
63
+ const rayOrigin = [camera.position[0], camera.position[1], camera.position[2]];
64
+ const rayDirection = vec3.normalize(vec3.subtract(world, rayOrigin));
65
+ return {rayOrigin, rayDirection};
66
+ }
67
+
68
+ export function rayIntersectsSphere(rayOrigin, rayDirection, sphereCenter, sphereRadius) {
69
+ const pos = [sphereCenter.x, sphereCenter.y, sphereCenter.z];
70
+ const oc = vec3.subtract(rayOrigin, pos);
71
+ const a = vec3.dot(rayDirection, rayDirection);
72
+ const b = 2.0 * vec3.dot(oc, rayDirection);
73
+ const c = vec3.dot(oc, oc) - sphereRadius * sphereRadius;
74
+ const discriminant = b * b - 4 * a * c;
75
+ return discriminant > 0;
76
+ }
77
+
78
+ export function addRaycastListener () {
79
+ window.addEventListener('click', (event) => {
80
+ let canvas = document.getElementsByTagName('canvas')[0];
81
+ let camera = app.cameras.WASD;
82
+ const { rayOrigin, rayDirection } = getRayFromMouse(event, canvas, camera);
83
+ for (const object of app.mainRenderBundle) {
84
+ if (rayIntersectsSphere(rayOrigin, rayDirection, object.position, 2)) {
85
+ console.log('Object clicked:', object.name);
86
+ // Just like in matrix-engine webGL version "ray.hit.event"
87
+ dispatchEvent(new CustomEvent('ray.hit.event', {detail: {
88
+ hitObject: object
89
+ }}))
90
+ }
91
+ }
92
+ });
93
+ }