@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.12 → 0.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 (34) hide show
  1. package/.cspell.json +35 -0
  2. package/dist/lib-pixelstreamingfrontend.esm.js +1 -1
  3. package/dist/lib-pixelstreamingfrontend.js +1 -1
  4. package/package.json +4 -5
  5. package/readme.md +1 -1
  6. package/src/Inputs/FakeTouchController.ts +1 -1
  7. package/src/Inputs/GamepadController.ts +2 -2
  8. package/src/Inputs/IMouseEvents.ts +1 -1
  9. package/src/Inputs/ITouchController.ts +1 -1
  10. package/src/Inputs/KeyboardController.ts +2 -2
  11. package/src/Inputs/LockedMouseEvents.ts +2 -2
  12. package/src/Inputs/MouseController.ts +1 -1
  13. package/src/Inputs/TouchController.ts +1 -1
  14. package/src/Inputs/XRGamepadController.ts +44 -26
  15. package/src/PeerConnectionController/PeerConnectionController.ts +2 -2
  16. package/src/PixelStreaming/PixelStreaming.test.ts +4 -4
  17. package/src/PixelStreaming/PixelStreaming.ts +2 -2
  18. package/src/Util/CoordinateConverter.ts +6 -5
  19. package/src/Util/RTCUtils.ts +2 -2
  20. package/src/WebRtcPlayer/WebRtcPlayerController.ts +18 -0
  21. package/src/WebXR/WebXRController.ts +368 -179
  22. package/types/Inputs/GamepadController.d.ts +1 -1
  23. package/types/Inputs/IMouseEvents.d.ts +1 -1
  24. package/types/Inputs/ITouchController.d.ts +1 -1
  25. package/types/Inputs/KeyboardController.d.ts +1 -1
  26. package/types/Inputs/LockedMouseEvents.d.ts +1 -1
  27. package/types/Inputs/XRGamepadController.d.ts +8 -1
  28. package/types/PixelStreaming/PixelStreaming.d.ts +1 -1
  29. package/types/Util/RTCUtils.d.ts +2 -2
  30. package/types/WebXR/WebXRController.d.ts +19 -3
  31. package/src/Util/WebGLUtils.ts +0 -49
  32. package/src/Util/WebXRUtils.ts +0 -25
  33. package/types/Util/WebGLUtils.d.ts +0 -4
  34. package/types/Util/WebXRUtils.d.ts +0 -9
@@ -2,8 +2,6 @@
2
2
 
3
3
  import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
4
  import { WebRtcPlayerController } from '../WebRtcPlayer/WebRtcPlayerController';
5
- import { WebGLUtils } from '../Util/WebGLUtils';
6
- import { Controller } from '../Inputs/GamepadTypes';
7
5
  import { XRGamepadController } from '../Inputs/XRGamepadController';
8
6
  import { XrFrameEvent } from '../Util/EventEmitter'
9
7
  import { Flags } from '../pixelstreamingfrontend';
@@ -12,18 +10,31 @@ export class WebXRController {
12
10
  private xrSession: XRSession;
13
11
  private xrRefSpace: XRReferenceSpace;
14
12
  private gl: WebGL2RenderingContext;
13
+ private xrViewerPose : XRViewerPose = null;
14
+ // Used for comparisons to ensure two numbers are close enough.
15
+ private EPSILON = 0.0000001;
15
16
 
16
17
  private positionLocation: number;
17
18
  private texcoordLocation: number;
18
- private resolutionLocation: WebGLUniformLocation;
19
- private offsetLocation: WebGLUniformLocation;
20
19
 
21
20
  private positionBuffer: WebGLBuffer;
22
21
  private texcoordBuffer: WebGLBuffer;
23
22
 
23
+ private videoTexture: WebGLTexture = null;
24
+ private prevVideoWidth: number = 0;
25
+ private prevVideoHeight: number = 0;
26
+
24
27
  private webRtcController: WebRtcPlayerController;
25
28
  private xrGamepadController: XRGamepadController;
26
- private xrControllers: Array<Controller>;
29
+
30
+ private leftView: XRView = null;
31
+ private rightView: XRView = null;
32
+
33
+ // Store the HMD data we have last sent (not all of it is needed every frame unless it changes)
34
+ private lastSentLeftEyeProj: Float32Array = null;
35
+ private lastSentRightEyeProj: Float32Array = null;
36
+ private lastSentRelativeLeftEyePos: DOMPointReadOnly = null;
37
+ private lastSentRelativeRightEyePos: DOMPointReadOnly = null;
27
38
 
28
39
  onSessionStarted: EventTarget;
29
40
  onSessionEnded: EventTarget;
@@ -32,7 +43,6 @@ export class WebXRController {
32
43
  constructor(webRtcPlayerController: WebRtcPlayerController) {
33
44
  this.xrSession = null;
34
45
  this.webRtcController = webRtcPlayerController;
35
- this.xrControllers = [];
36
46
  this.xrGamepadController = new XRGamepadController(
37
47
  this.webRtcController.streamMessageController
38
48
  );
@@ -43,8 +53,15 @@ export class WebXRController {
43
53
 
44
54
  public xrClicked() {
45
55
  if (!this.xrSession) {
56
+
57
+ if(!navigator.xr){
58
+ Logger.Error(Logger.GetStackTrace(), "This browser does not support XR.");
59
+ return;
60
+ }
61
+
46
62
  navigator.xr
47
- .requestSession('immersive-vr')
63
+ /* Request immersive-vr session without any optional features. */
64
+ .requestSession('immersive-vr', { optionalFeatures: [] })
48
65
  .then((session: XRSession) => {
49
66
  this.onXrSessionStarted(session);
50
67
  });
@@ -59,31 +76,59 @@ export class WebXRController {
59
76
  this.onSessionEnded.dispatchEvent(new Event('xrSessionEnded'));
60
77
  }
61
78
 
62
- onXrSessionStarted(session: XRSession) {
63
- Logger.Log(Logger.GetStackTrace(), 'XR Session started');
64
-
65
- this.xrSession = session;
66
- this.xrSession.addEventListener('end', () => {
67
- this.onXrSessionEnded();
68
- });
69
-
79
+ initGL() {
80
+ if (this.gl) { return; }
70
81
  const canvas = document.createElement('canvas');
71
82
  this.gl = canvas.getContext('webgl2', {
72
83
  xrCompatible: true
73
84
  });
74
85
 
75
- this.xrSession.updateRenderState({
76
- baseLayer: new XRWebGLLayer(this.xrSession, this.gl)
77
- });
86
+ // Set our clear color
87
+ this.gl.clearColor(0.0, 0.0, 0.0, 1);
88
+ }
89
+
90
+ initShaders() {
91
+
92
+ // shader source code
93
+ const vertexShaderSource: string =
94
+ `
95
+ attribute vec2 a_position;
96
+ attribute vec2 a_texCoord;
97
+
98
+ // varyings
99
+ varying vec2 v_texCoord;
100
+
101
+ void main() {
102
+ gl_Position = vec4(a_position.x, a_position.y, 0, 1);
103
+ // pass the texCoord to the fragment shader
104
+ // The GPU will interpolate this value between points.
105
+ v_texCoord = a_texCoord;
106
+ }
107
+ `;
108
+
109
+ const fragmentShaderSource: string =
110
+ `
111
+ precision mediump float;
112
+
113
+ // our texture
114
+ uniform sampler2D u_image;
115
+
116
+ // the texCoords passed in from the vertex shader.
117
+ varying vec2 v_texCoord;
118
+
119
+ void main() {
120
+ gl_FragColor = texture2D(u_image, v_texCoord);
121
+ }
122
+ `;
78
123
 
79
124
  // setup vertex shader
80
125
  const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
81
- this.gl.shaderSource(vertexShader, WebGLUtils.vertexShader());
126
+ this.gl.shaderSource(vertexShader, vertexShaderSource);
82
127
  this.gl.compileShader(vertexShader);
83
128
 
84
129
  // setup fragment shader
85
130
  const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
86
- this.gl.shaderSource(fragmentShader, WebGLUtils.fragmentShader());
131
+ this.gl.shaderSource(fragmentShader, fragmentShaderSource);
87
132
  this.gl.compileShader(fragmentShader);
88
133
 
89
134
  // setup GLSL program
@@ -102,93 +147,298 @@ export class WebXRController {
102
147
  shaderProgram,
103
148
  'a_texCoord'
104
149
  );
105
- // Create a buffer to put three 2d clip space points in
106
- this.positionBuffer = this.gl.createBuffer();
107
- // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
108
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
109
-
110
- // Turn on the position attribute
111
- this.gl.enableVertexAttribArray(this.positionLocation);
112
- // Create a texture.
113
- const texture = this.gl.createTexture();
114
- this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
115
- // Set the parameters so we can render any size image.
116
- this.gl.texParameteri(
117
- this.gl.TEXTURE_2D,
118
- this.gl.TEXTURE_WRAP_S,
119
- this.gl.CLAMP_TO_EDGE
120
- );
121
- this.gl.texParameteri(
122
- this.gl.TEXTURE_2D,
123
- this.gl.TEXTURE_WRAP_T,
124
- this.gl.CLAMP_TO_EDGE
125
- );
126
- this.gl.texParameteri(
127
- this.gl.TEXTURE_2D,
128
- this.gl.TEXTURE_MIN_FILTER,
129
- this.gl.NEAREST
130
- );
131
- this.gl.texParameteri(
132
- this.gl.TEXTURE_2D,
133
- this.gl.TEXTURE_MAG_FILTER,
134
- this.gl.NEAREST
135
- );
150
+ }
136
151
 
137
- this.texcoordBuffer = this.gl.createBuffer();
138
- // lookup uniforms
139
- this.resolutionLocation = this.gl.getUniformLocation(
140
- shaderProgram,
141
- 'u_resolution'
142
- );
143
- this.offsetLocation = this.gl.getUniformLocation(
144
- shaderProgram,
145
- 'u_offset'
146
- );
152
+ updateVideoTexture(){
153
+
154
+ if(!this.videoTexture){
155
+ // Create our texture that we use in our shader
156
+ // and bind it once because we never use any other texture.
157
+ this.videoTexture = this.gl.createTexture();
158
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.videoTexture);
159
+
160
+ // Set the parameters so we can render any size image.
161
+ this.gl.texParameteri(
162
+ this.gl.TEXTURE_2D,
163
+ this.gl.TEXTURE_WRAP_S,
164
+ this.gl.CLAMP_TO_EDGE
165
+ );
166
+ this.gl.texParameteri(
167
+ this.gl.TEXTURE_2D,
168
+ this.gl.TEXTURE_WRAP_T,
169
+ this.gl.CLAMP_TO_EDGE
170
+ );
171
+ this.gl.texParameteri(
172
+ this.gl.TEXTURE_2D,
173
+ this.gl.TEXTURE_MIN_FILTER,
174
+ this.gl.LINEAR
175
+ );
176
+ this.gl.texParameteri(
177
+ this.gl.TEXTURE_2D,
178
+ this.gl.TEXTURE_MAG_FILTER,
179
+ this.gl.LINEAR
180
+ );
181
+ }
182
+
183
+ const videoHeight = this.webRtcController.videoPlayer.getVideoElement().videoHeight;
184
+ const videoWidth = this.webRtcController.videoPlayer.getVideoElement().videoWidth;
185
+
186
+ if(this.prevVideoHeight != videoHeight || this.prevVideoWidth != videoWidth){
187
+ // Do full update of texture if dimensions do not match
188
+ this.gl.texImage2D(
189
+ this.gl.TEXTURE_2D,
190
+ 0,
191
+ this.gl.RGBA,
192
+ videoWidth,
193
+ videoHeight,
194
+ 0,
195
+ this.gl.RGBA,
196
+ this.gl.UNSIGNED_BYTE,
197
+ this.webRtcController.videoPlayer.getVideoElement()
198
+ );
199
+ } else {
200
+ // If dimensions match just update the sub region
201
+ this.gl.texSubImage2D(
202
+ this.gl.TEXTURE_2D,
203
+ 0,
204
+ 0,
205
+ 0,
206
+ videoWidth,
207
+ videoHeight,
208
+ this.gl.RGBA,
209
+ this.gl.UNSIGNED_BYTE,
210
+ this.webRtcController.videoPlayer.getVideoElement()
211
+ );
212
+ }
213
+
214
+ // Update prev video width/height
215
+ this.prevVideoHeight = videoHeight;
216
+ this.prevVideoWidth = videoWidth;
217
+ }
218
+
219
+ initBuffers(){
220
+ // Create out position buffer and its vertex shader attribute
221
+ {
222
+ // Create a buffer to put the the vertices of the plane we will draw the video stream onto
223
+ this.positionBuffer = this.gl.createBuffer();
224
+ // Bind the position buffer
225
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
226
+ // Enable `positionLocation` to be used as vertex shader attribute
227
+ this.gl.enableVertexAttribArray(this.positionLocation);
228
+
229
+ // Note: positions are passed in clip-space coordinates [-1..1] so no need to convert in-shader
230
+ // prettier-ignore
231
+ this.gl.bufferData(
232
+ this.gl.ARRAY_BUFFER,
233
+ new Float32Array([
234
+ -1.0, 1.0,
235
+ 1.0, 1.0,
236
+ -1.0, -1.0,
237
+ -1.0, -1.0,
238
+ 1.0, 1.0,
239
+ 1.0, -1.0
240
+ ]),
241
+ this.gl.STATIC_DRAW
242
+ );
243
+
244
+ // Tell position attribute of the vertex shader how to get data out of the bound buffer (the positionBuffer)
245
+ this.gl.vertexAttribPointer(
246
+ this.positionLocation,
247
+ 2 /*size*/,
248
+ this.gl.FLOAT /*type*/,
249
+ false /*normalize*/,
250
+ 0 /*stride*/,
251
+ 0 /*offset*/
252
+ );
253
+ }
254
+
255
+ // Create our texture coordinate buffers for accessing our texture
256
+ {
257
+ this.texcoordBuffer = this.gl.createBuffer();
258
+ // Bind the texture coordinate buffer
259
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texcoordBuffer);
260
+ // Enable `texcoordLocation` to be used as a vertex shader attribute
261
+ this.gl.enableVertexAttribArray(this.texcoordLocation);
262
+
263
+ // The texture coordinates to apply for rectangle we are drawing
264
+ this.gl.bufferData(
265
+ this.gl.ARRAY_BUFFER,
266
+ new Float32Array([
267
+ 0.0, 0.0,
268
+ 1.0, 0.0,
269
+ 0.0, 1.0,
270
+ 0.0, 1.0,
271
+ 1.0, 0.0,
272
+ 1.0, 1.0
273
+ ]),
274
+ this.gl.STATIC_DRAW
275
+ );
276
+
277
+ // Tell texture coordinate attribute of the vertex shader how to get data out of the bound buffer (the texcoordBuffer)
278
+ this.gl.vertexAttribPointer(
279
+ this.texcoordLocation,
280
+ 2 /*size*/,
281
+ this.gl.FLOAT /*type*/,
282
+ false /*normalize*/,
283
+ 0 /*stride*/,
284
+ 0 /*offset*/
285
+ );
286
+ }
287
+ }
288
+
289
+ onXrSessionStarted(session: XRSession) {
290
+ Logger.Log(Logger.GetStackTrace(), 'XR Session started');
291
+
292
+ this.xrSession = session;
293
+ this.xrSession.addEventListener('end', () => {
294
+ this.onXrSessionEnded();
295
+ });
296
+
297
+ // Initialization
298
+ this.initGL();
299
+ this.initShaders();
300
+ this.initBuffers();
147
301
 
148
302
  session.requestReferenceSpace('local').then((refSpace) => {
149
303
  this.xrRefSpace = refSpace;
150
- this.xrSession.requestAnimationFrame(
151
- (time: DOMHighResTimeStamp, frame: XRFrame) =>
152
- this.onXrFrame(time, frame)
153
- );
304
+
305
+ // Set up our base layer (i.e. a projection layer that fills the entire XR viewport).
306
+ this.xrSession.updateRenderState({
307
+ baseLayer: new XRWebGLLayer(this.xrSession, this.gl)
308
+ });
309
+
310
+ // Update target framerate to 90 fps if 90 fps is supported in this XR device
311
+ if(this.xrSession.supportedFrameRates) {
312
+ for (const frameRate of this.xrSession.supportedFrameRates) {
313
+ if(frameRate == 90){
314
+ session.updateTargetFrameRate(90);
315
+ }
316
+ }
317
+ }
318
+
319
+ // Binding to each new frame to get latest XR updates
320
+ this.xrSession.requestAnimationFrame(this.onXrFrame.bind(this));
154
321
  });
155
322
 
156
323
  this.onSessionStarted.dispatchEvent(new Event('xrSessionStarted'));
157
324
  }
158
325
 
159
- onXrFrame(time: DOMHighResTimeStamp, frame: XRFrame) {
160
- const pose = frame.getViewerPose(this.xrRefSpace);
161
- if (pose) {
162
- const matrix = pose.transform.matrix;
163
- const mat = [];
164
- for (let i = 0; i < 16; i++) {
165
- mat[i] = new Float32Array([matrix[i]])[0];
166
- }
326
+ areArraysEqual(a: Float32Array, b: Float32Array) : boolean {
327
+ return a.length === b.length && a.every((element, index) => Math.abs(element - b[index]) <= this.EPSILON);
328
+ }
329
+
330
+ arePointsEqual(a: DOMPointReadOnly, b: DOMPointReadOnly) : boolean {
331
+ return Math.abs(a.x - b.x) >= this.EPSILON && Math.abs(a.y - b.y) >= this.EPSILON && Math.abs(a.z - b.z) >= this.EPSILON;
332
+ }
333
+
334
+ sendXRDataToUE() {
335
+ if(this.leftView == null || this.rightView == null) {
336
+ return;
337
+ }
338
+
339
+ // We selectively send either the `XREyeViews` or `XRHMDTransform`
340
+ // messages over the datachannel. The reason for this selective sending is that
341
+ // the `XREyeViews` is a much larger message and changes infrequently (e.g. only when user changes headset IPD).
342
+ // Therefore, we only need to send it once on startup and then any time it changes.
343
+ // The rest of the time we can send the `XRHMDTransform` message.
344
+ let shouldSendEyeViews = this.lastSentLeftEyeProj == null ||
345
+ this.lastSentRightEyeProj == null ||
346
+ this.lastSentRelativeLeftEyePos == null ||
347
+ this.lastSentRelativeRightEyePos == null;
348
+
349
+ const leftEyeTrans = this.leftView.transform.matrix;
350
+ const leftEyeProj = this.leftView.projectionMatrix;
351
+ const rightEyeTrans = this.rightView.transform.matrix;
352
+ const rightEyeProj = this.rightView.projectionMatrix;
353
+ const hmdTrans = this.xrViewerPose.transform.matrix;
354
+
355
+ // Check if projection matrices have changed
356
+ if(!shouldSendEyeViews && this.lastSentLeftEyeProj != null && this.lastSentRightEyeProj != null) {
357
+ const leftEyeProjUnchanged = this.areArraysEqual(leftEyeProj, this.lastSentLeftEyeProj);
358
+ const rightEyeProjUnchanged = this.areArraysEqual(rightEyeProj, this.lastSentRightEyeProj);
359
+ shouldSendEyeViews = leftEyeProjUnchanged == false || rightEyeProjUnchanged == false;
360
+ }
167
361
 
362
+ const leftEyeRelativePos = new DOMPointReadOnly(
363
+ this.leftView.transform.position.x - this.xrViewerPose.transform.position.x,
364
+ this.leftView.transform.position.y - this.xrViewerPose.transform.position.y,
365
+ this.leftView.transform.position.z - this.xrViewerPose.transform.position.z,
366
+ 1.0
367
+ );
368
+
369
+ const rightEyeRelativePos = new DOMPointReadOnly(
370
+ this.leftView.transform.position.x - this.xrViewerPose.transform.position.x,
371
+ this.leftView.transform.position.y - this.xrViewerPose.transform.position.y,
372
+ this.leftView.transform.position.z - this.xrViewerPose.transform.position.z,
373
+ 1.0
374
+ );
375
+
376
+ // Check if relative eye pos has changed (e.g IPD changed)
377
+ if(!shouldSendEyeViews && this.lastSentRelativeLeftEyePos != null && this.lastSentRelativeRightEyePos != null) {
378
+ const leftEyePosUnchanged = this.arePointsEqual(leftEyeRelativePos, this.lastSentRelativeLeftEyePos);
379
+ const rightEyePosUnchanged = this.arePointsEqual(rightEyeRelativePos, this.lastSentRelativeRightEyePos);
380
+ shouldSendEyeViews = leftEyePosUnchanged == false || rightEyePosUnchanged == false;
381
+ // Note: We are not checking if EyeView rotation changes (as far as I know no HMD supports changing this value at runtime).
382
+ }
383
+
384
+ if(shouldSendEyeViews) {
385
+ // send transform (4x4) and projection matrix (4x4) data for each eye (left first, then right)
168
386
  // prettier-ignore
387
+ this.webRtcController.streamMessageController.toStreamerHandlers.get('XREyeViews')([
388
+ // Left eye 4x4 transform matrix
389
+ leftEyeTrans[0], leftEyeTrans[4], leftEyeTrans[8], leftEyeTrans[12],
390
+ leftEyeTrans[1], leftEyeTrans[5], leftEyeTrans[9], leftEyeTrans[13],
391
+ leftEyeTrans[2], leftEyeTrans[6], leftEyeTrans[10], leftEyeTrans[14],
392
+ leftEyeTrans[3], leftEyeTrans[7], leftEyeTrans[11], leftEyeTrans[15],
393
+ // Left eye 4x4 projection matrix
394
+ leftEyeProj[0], leftEyeProj[4], leftEyeProj[8], leftEyeProj[12],
395
+ leftEyeProj[1], leftEyeProj[5], leftEyeProj[9], leftEyeProj[13],
396
+ leftEyeProj[2], leftEyeProj[6], leftEyeProj[10], leftEyeProj[14],
397
+ leftEyeProj[3], leftEyeProj[7], leftEyeProj[11], leftEyeProj[15],
398
+ // Right eye 4x4 transform matrix
399
+ rightEyeTrans[0], rightEyeTrans[4], rightEyeTrans[8], rightEyeTrans[12],
400
+ rightEyeTrans[1], rightEyeTrans[5], rightEyeTrans[9], rightEyeTrans[13],
401
+ rightEyeTrans[2], rightEyeTrans[6], rightEyeTrans[10], rightEyeTrans[14],
402
+ rightEyeTrans[3], rightEyeTrans[7], rightEyeTrans[11], rightEyeTrans[15],
403
+ // right eye 4x4 projection matrix
404
+ rightEyeProj[0], rightEyeProj[4], rightEyeProj[8], rightEyeProj[12],
405
+ rightEyeProj[1], rightEyeProj[5], rightEyeProj[9], rightEyeProj[13],
406
+ rightEyeProj[2], rightEyeProj[6], rightEyeProj[10], rightEyeProj[14],
407
+ rightEyeProj[3], rightEyeProj[7], rightEyeProj[11], rightEyeProj[15],
408
+ // HMD 4x4 transform
409
+ hmdTrans[0], hmdTrans[4], hmdTrans[8], hmdTrans[12],
410
+ hmdTrans[1], hmdTrans[5], hmdTrans[9], hmdTrans[13],
411
+ hmdTrans[2], hmdTrans[6], hmdTrans[10], hmdTrans[14],
412
+ hmdTrans[3], hmdTrans[7], hmdTrans[11], hmdTrans[15],
413
+ ]);
414
+ this.lastSentLeftEyeProj = leftEyeProj;
415
+ this.lastSentRightEyeProj = rightEyeProj;
416
+ this.lastSentRelativeLeftEyePos = leftEyeRelativePos;
417
+ this.lastSentRelativeRightEyePos = rightEyeRelativePos;
418
+ }
419
+ else {
420
+ // If we don't need to the entire eye views being sent just send the HMD transform
169
421
  this.webRtcController.streamMessageController.toStreamerHandlers.get('XRHMDTransform')([
170
- mat[0], mat[4], mat[8], mat[12],
171
- mat[1], mat[5], mat[9], mat[13],
172
- mat[2], mat[6], mat[10], mat[14],
173
- mat[3], mat[7], mat[11], mat[15]
422
+ // HMD 4x4 transform
423
+ hmdTrans[0], hmdTrans[4], hmdTrans[8], hmdTrans[12],
424
+ hmdTrans[1], hmdTrans[5], hmdTrans[9], hmdTrans[13],
425
+ hmdTrans[2], hmdTrans[6], hmdTrans[10], hmdTrans[14],
426
+ hmdTrans[3], hmdTrans[7], hmdTrans[11], hmdTrans[15],
174
427
  ]);
428
+ }
429
+ }
175
430
 
176
- const glLayer = this.xrSession.renderState.baseLayer;
177
- // If we do have a valid pose, bind the WebGL layer's framebuffer,
178
- // which is where any content to be displayed on the XRDevice must be
179
- // rendered.
180
- this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, glLayer.framebuffer);
431
+ onXrFrame(time: DOMHighResTimeStamp, frame: XRFrame) {
432
+ this.xrViewerPose = frame.getViewerPose(this.xrRefSpace);
433
+ if (this.xrViewerPose) {
434
+ this.updateViews();
435
+ if(this.leftView == null || this.rightView == null) {
436
+ return;
437
+ }
181
438
 
182
- // Upload the image into the texture. WebGL knows how to extract the current frame from the video element
183
- this.gl.texImage2D(
184
- this.gl.TEXTURE_2D,
185
- 0,
186
- this.gl.RGBA,
187
- this.gl.RGBA,
188
- this.gl.UNSIGNED_BYTE,
189
- this.webRtcController.videoPlayer.getVideoElement()
190
- );
191
- this.render(this.webRtcController.videoPlayer.getVideoElement());
439
+ this.sendXRDataToUE();
440
+ this.updateVideoTexture();
441
+ this.render();
192
442
  }
193
443
 
194
444
  if (this.webRtcController.config.isFlagEnabled(Flags.XRControllerInput)) {
@@ -209,105 +459,44 @@ export class WebXRController {
209
459
  this.onXrFrame(time, frame)
210
460
  );
211
461
 
212
- this.onFrame.dispatchEvent(new XrFrameEvent({
213
- time,
214
- frame
215
- }));
462
+ this.onFrame.dispatchEvent(new XrFrameEvent({ time, frame }));
463
+ }
464
+
465
+ private updateViews() {
466
+ if(!this.xrViewerPose) {
467
+ return;
468
+ }
469
+ for (const view of this.xrViewerPose.views) {
470
+ if (view.eye === "left") {
471
+ this.leftView = view;
472
+ }
473
+ else if(view.eye === "right") {
474
+ this.rightView = view;
475
+ }
476
+ }
216
477
  }
217
478
 
218
- private render(videoElement: HTMLVideoElement) {
479
+ private render() {
219
480
  if (!this.gl) {
220
481
  return;
221
482
  }
222
483
 
484
+ // Bind the framebuffer to the base layer's framebuffer
223
485
  const glLayer = this.xrSession.renderState.baseLayer;
224
- this.gl.viewport(
225
- 0,
226
- 0,
227
- glLayer.framebufferWidth,
228
- glLayer.framebufferHeight
229
- );
230
- this.gl.uniform4f(this.offsetLocation, 1.0, 1.0, 0.0, 0.0);
231
-
232
- // Set rectangle
233
- // prettier-ignore
234
- this.gl.bufferData(
235
- this.gl.ARRAY_BUFFER,
236
- new Float32Array([
237
- 0, 0,
238
- videoElement.videoWidth, 0,
239
- 0, videoElement.videoHeight,
240
- 0, videoElement.videoHeight,
241
- videoElement.videoWidth, 0,
242
- videoElement.videoWidth, videoElement.videoHeight
243
- ]),
244
- this.gl.STATIC_DRAW
245
- );
486
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, glLayer.framebuffer);
246
487
 
247
- // Provide texture coordinates for the rectangle
248
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texcoordBuffer);
249
- this.gl.bufferData(
250
- this.gl.ARRAY_BUFFER,
251
- new Float32Array([
252
- 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0
253
- ]),
254
- this.gl.STATIC_DRAW
255
- );
488
+ // Set the relevant portion of clip space
489
+ this.gl.viewport(0, 0, glLayer.framebufferWidth, glLayer.framebufferHeight);
256
490
 
257
- let size; // components per iteration
258
- let type; // the data type
259
- let normalize; // normalize the data
260
- let stride; // 0 = move forward size * sizeof(type) each iteration to get the next position
261
- let offset; // start position of the buffer
262
-
263
- // Bind the position buffer.
264
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
265
- // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
266
- size = 2; // 2 components per iteration
267
- type = this.gl.FLOAT; // the data is 32bit floats
268
- normalize = false; // don't normalize the data
269
- stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
270
- offset = 0; // start at the beginning of the buffer
271
- this.gl.vertexAttribPointer(
272
- this.positionLocation,
273
- size,
274
- type,
275
- normalize,
276
- stride,
277
- offset
278
- );
279
- // Turn on the texcoord attribute
280
- this.gl.enableVertexAttribArray(this.texcoordLocation);
281
- // bind the texcoord buffer.
282
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texcoordBuffer);
283
- // Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
284
- size = 2; // 2 components per iteration
285
- type = this.gl.FLOAT; // the data is 32bit floats
286
- normalize = false; // don't normalize the data
287
- stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
288
- offset = 0; // start at the beginning of the buffer
289
- this.gl.vertexAttribPointer(
290
- this.texcoordLocation,
291
- size,
292
- type,
293
- normalize,
294
- stride,
295
- offset
296
- );
297
- // set the resolution
298
- this.gl.uniform2f(
299
- this.resolutionLocation,
300
- videoElement.videoWidth,
301
- videoElement.videoHeight
302
- );
303
- // draw the rectangle.
304
- const primitiveType = this.gl.TRIANGLES;
305
- const count = 6;
306
- offset = 0;
307
- this.gl.drawArrays(primitiveType, offset, count);
491
+ // Draw the rectangle we will show the video stream texture on
492
+ this.gl.drawArrays(this.gl.TRIANGLES /*primitiveType*/, 0 /*offset*/, 6 /*count*/);
308
493
  }
309
494
 
310
495
  static isSessionSupported(mode: XRSessionMode): Promise<boolean> {
496
+ if (location.protocol !== "https:") {
497
+ Logger.Info(null, "WebXR requires https, if you want WebXR use https.");
498
+ }
499
+
311
500
  if (navigator.xr) {
312
501
  return navigator.xr.isSessionSupported(mode);
313
502
  } else {
@@ -13,7 +13,7 @@ export declare class GamePadController {
13
13
  */
14
14
  constructor(toStreamerMessagesProvider: StreamMessageController);
15
15
  /**
16
- * Unregisters all event handlers
16
+ * Unregister all event handlers.
17
17
  */
18
18
  unregisterGamePadEvents(): void;
19
19
  /**
@@ -47,7 +47,7 @@ export interface IMouseEvents {
47
47
  */
48
48
  handleContextMenu?(mouseEvent: MouseEvent): void;
49
49
  /**
50
- * Unregisters any registered mouse event handlers
50
+ * Unregister any registered mouse event handlers
51
51
  */
52
52
  unregisterMouseEvents(): void;
53
53
  }
@@ -18,7 +18,7 @@ export interface ITouchController {
18
18
  */
19
19
  onTouchMove(touchEvent: TouchEvent): void;
20
20
  /**
21
- * Unregisters all touch event handlers
21
+ * Unregister all touch event handlers.
22
22
  */
23
23
  unregisterTouchEvents(): void;
24
24
  }
@@ -24,7 +24,7 @@ export declare class KeyboardController {
24
24
  */
25
25
  registerKeyBoardEvents(): void;
26
26
  /**
27
- * Unregisters document keyboard events
27
+ * Unregister document keyboard events.
28
28
  */
29
29
  unregisterKeyBoardEvents(): void;
30
30
  /**