teleportxr 1.0.90 → 1.0.92

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/client/client.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const fs= require("fs");
4
+ const path= require("path");
3
5
  const core= require("../core/core.js");
4
6
  const command= require("../protocol/command.js");
5
7
  const message= require("../protocol/message.js");
@@ -54,6 +56,9 @@ class Client {
54
56
  this.clientID=cid;
55
57
  this.origin_uid=0;
56
58
  this.handshakeMessage=new message.HandshakeMessage();
59
+ // The client's axes standard, learned from its Handshake. Used to pick the matching
60
+ // cubemap variant to stream (e.g. GlStyle -> *_ogl.ktx2). NotInitialized until handshake.
61
+ this.clientAxesStandard=core.AxesStandard.NotInitialized;
57
62
  this.geometryService=new gs.GeometryService(cid);
58
63
  // Per-client avatar negotiation state. The host application drives
59
64
  // when (and whether) policy is sent via this service.
@@ -564,6 +569,34 @@ class Client {
564
569
  if(sendSuccess)
565
570
  this.geometryService.EncodedResource(uid);
566
571
  }
572
+ //! For a cubemap resource, return the URL of the variant matching this client's axes
573
+ //! standard (e.g. /envCloudyCubemap.ktx2 -> /envCloudyCubemap_ogl.ktx2). Returns undefined
574
+ //! to mean "use the resource's base url" — for non-cubemaps, unknown axes standards, or
575
+ //! when the variant file is missing under the public path (so streaming never breaks).
576
+ CubemapUrlForClient(resource)
577
+ {
578
+ if(!resource || !resource.isCubemap)
579
+ return undefined;
580
+ const suffix=core.AxesStandardToCubemapSuffix(this.clientAxesStandard);
581
+ if(!suffix)
582
+ return undefined;
583
+ const candidate=resources.InsertCubemapAxesSuffix(resource.url, suffix);
584
+ if(candidate===resource.url)
585
+ return undefined;
586
+ // Only serve the variant if it actually exists; otherwise fall back to the base file.
587
+ if(this.scene&&this.scene.publicPath)
588
+ {
589
+ // URLs are server-relative (e.g. "/envCloudyCubemap.ktx2"); strip any scheme/host.
590
+ const rel=candidate.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]*/, "");
591
+ const full=path.join(this.scene.publicPath, rel);
592
+ if(!fs.existsSync(full))
593
+ {
594
+ console.warn("Cubemap variant "+full+" not found; serving base "+resource.url+" to client "+this.clientID);
595
+ return undefined;
596
+ }
597
+ }
598
+ return candidate;
599
+ }
567
600
  SendGenericResource(uid)
568
601
  {
569
602
  var resource=resources.GetResourceFromUid(uid);
@@ -572,9 +605,10 @@ class Client {
572
605
  console.warn("No resource of uid ",uid," was found.")
573
606
  return;
574
607
  }
608
+ const urlOverride=this.CubemapUrlForClient(resource);
575
609
  const MAX_BUFFER_SIZE=resource.encodedSize();;
576
610
  const buffer = new ArrayBuffer(MAX_BUFFER_SIZE);
577
- const resourceSize=resource_encoder.EncodeResource(resource,buffer);
611
+ const resourceSize=resource_encoder.EncodeResource(resource,buffer,urlOverride);
578
612
  const view2 = new DataView(buffer, 0, resourceSize);
579
613
  if(!this.webRtcConnection)
580
614
  {
@@ -588,7 +622,7 @@ class Client {
588
622
  return;
589
623
  }
590
624
  const sendSuccess = this.webRtcConnection.sendGeometry(view2);
591
- console.log("[T+"+this.elapsedMsSinceStart()+"ms, conn+"+this.elapsedMsSinceConnected()+"ms] Sending resource "+uid+" "+resource.url+" to Client "+this.clientID+", size: "+resourceSize+" bytes — "+
625
+ console.log("[T+"+this.elapsedMsSinceStart()+"ms, conn+"+this.elapsedMsSinceConnected()+"ms] Sending resource "+uid+" "+(urlOverride||resource.url)+" to Client "+this.clientID+", size: "+resourceSize+" bytes — "+
592
626
  (sendSuccess ? "OK" : "FAILED"));
593
627
  if(sendSuccess)
594
628
  this.geometryService.EncodedResource(uid);
@@ -619,6 +653,10 @@ class Client {
619
653
  }
620
654
  var handshakeMessage=new message.HandshakeMessage();
621
655
  core.decodeFromUint8Array(handshakeMessage,data);
656
+ // Remember the client's axes standard so cubemaps are streamed in the matching variant.
657
+ this.clientAxesStandard=handshakeMessage.AxesStandard_axesStandard;
658
+ console.log("Client "+this.clientID+" handshake axesStandard="+this.clientAxesStandard
659
+ +" (cubemap suffix '"+core.AxesStandardToCubemapSuffix(this.clientAxesStandard)+"')");
622
660
  const excess =data.length-message.HandshakeMessage.sizeof();
623
661
  const numReceived =excess/core.UID_SIZE;
624
662
  if(numReceived!=handshakeMessage.uint64_resourceCount) {
package/core/core.js CHANGED
@@ -175,12 +175,31 @@ const AxesStandard =
175
175
  LeftHanded: 2,
176
176
  YVertical: 4,
177
177
  ZVertical: 8,
178
- EngineeringStyle: this.ZVertical | this.RightHanded,
179
- GlStyle: 16 | this.YVertical | this.RightHanded,
180
- UnrealStyle: 32 | this.ZVertical | this.LeftHanded,
181
- UnityStyle: 64 | this.YVertical | this.LeftHanded,
178
+ // NB: these must be explicit literals, not `this.ZVertical | ...`. Inside an object
179
+ // literal `this` is the module scope, not the object, so the bitwise expressions would
180
+ // silently evaluate to the wrong values (e.g. EngineeringStyle=0, GlStyle=16). The wire
181
+ // protocol and clients use 9 and 21, so hard-code the resolved values to match.
182
+ EngineeringStyle: 8 | 1, // ZVertical | RightHanded = 9
183
+ GlStyle: 16 | 4 | 1, // 16 | YVertical | RightHanded = 21
184
+ UnrealStyle: 32 | 8 | 2, // 32 | ZVertical | LeftHanded = 42
185
+ UnityStyle: 64 | 4 | 2, // 64 | YVertical | LeftHanded = 70
182
186
  };
183
187
 
188
+ //! Map a client's axes standard to the filename suffix used for the matching cubemap
189
+ //! variant. e.g. GlStyle -> "ogl", so /envCloudyCubemap.ktx2 -> /envCloudyCubemap_ogl.ktx2.
190
+ //! Returns "" for an unknown/uninitialised standard, meaning "serve the original file".
191
+ function AxesStandardToCubemapSuffix(axesStandard)
192
+ {
193
+ switch (axesStandard)
194
+ {
195
+ case AxesStandard.GlStyle: return "ogl";
196
+ case AxesStandard.EngineeringStyle: return "eng";
197
+ case AxesStandard.UnrealStyle: return "unreal";
198
+ case AxesStandard.UnityStyle: return "unity";
199
+ default: return "";
200
+ }
201
+ }
202
+
184
203
  const GeometryPayloadType =
185
204
  {
186
205
  Invalid: 0,
@@ -441,7 +460,7 @@ function put_string(dataView, byteOffset, name) {
441
460
 
442
461
  module.exports = {
443
462
  UID_SIZE, endian, SizeOfType, encodeIntoDataView, decodeFromDataView
444
- , vec4, BackgroundMode, AxesStandard, GeometryPayloadType, DisplayInfo, RenderingFeatures, LightingMode, VideoCodec
463
+ , vec4, BackgroundMode, AxesStandard, AxesStandardToCubemapSuffix, GeometryPayloadType, DisplayInfo, RenderingFeatures, LightingMode, VideoCodec
445
464
  , VideoConfig, ClientDynamicLighting, encodeToUint8Array, decodeFromUint8Array
446
465
  , generateUid, getStartTimeUnixUs, getTimestampUs,
447
466
  unixTimeToUTCString, put_float32, put_uint16, put_int32, put_uint32
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "name": "teleportxr",
18
18
  "description": "Teleport Spatial Server on node.js",
19
- "version": "1.0.90",
19
+ "version": "1.0.92",
20
20
  "repository": {
21
21
  "type": "git",
22
22
  "url": "git+https://github.com/simul/teleport-nodejs.git"
@@ -13,12 +13,14 @@ function putPlaceholderSize(dataView)
13
13
  }
14
14
 
15
15
  // return the size of the encoded resource.
16
- function EncodeResource(res,buffer)
16
+ // urlOverride, if supplied, is passed to the resource so it can encode a client-specific
17
+ // url (e.g. an axes-standard cubemap variant) without mutating the shared resource.
18
+ function EncodeResource(res,buffer,urlOverride)
17
19
  {
18
20
  var byteOffset=0;
19
- const dataView = new DataView(buffer);
21
+ const dataView = new DataView(buffer);
20
22
  byteOffset=putPlaceholderSize(dataView);
21
- var t=res.encodeIntoDataView(dataView,byteOffset);
23
+ var t=res.encodeIntoDataView(dataView,byteOffset,urlOverride);
22
24
  byteOffset=t;
23
25
  t-=8;
24
26
  // Actual size is now known: write the count of bytes that follow the
@@ -16,16 +16,21 @@ class Resource {
16
16
  this.uid = uid;
17
17
  this.url = url;
18
18
  this.type = type;
19
+ // True for environment cubemaps (background/diffuse/specular). Such resources are
20
+ // served per-client in the variant matching the client's axes standard.
21
+ this.isCubemap = false;
19
22
  }
20
23
  encodedSize(){
21
24
  return 500;
22
25
  }
23
- encodeIntoDataView(dataView, byteOffset) {
26
+ //! urlOverride, if supplied, replaces this.url for this encoding only (e.g. to send a
27
+ //! client-specific cubemap variant). The stored this.url is left untouched.
28
+ encodeIntoDataView(dataView, byteOffset, urlOverride) {
24
29
  byteOffset = core.put_uint8(dataView, byteOffset, this.type);
25
30
  byteOffset = core.put_uint64(dataView, byteOffset, this.uid);
26
- var url = this.url;
27
- if (this.url.search("://") == -1)
28
- url = Resource.defaultPathRoot + this.url;
31
+ var url = urlOverride || this.url;
32
+ if (url.search("://") == -1)
33
+ url = Resource.defaultPathRoot + url;
29
34
  byteOffset = core.put_string(dataView, byteOffset, url);
30
35
  return byteOffset;
31
36
  }
@@ -211,6 +216,28 @@ function GetOrAddTexture(url) {
211
216
  return GetOrAddResourceFromUrl(core.GeometryPayloadType.TexturePointer, url);
212
217
  }
213
218
 
219
+ //! Get or add a cubemap texture, flagging the resource so it is served per-client in the
220
+ //! variant matching the client's axes standard. Returns the resource uid.
221
+ function GetOrAddCubemap(url) {
222
+ const uid = GetOrAddTexture(url);
223
+ const res = GetResourceFromUid(uid);
224
+ if (res)
225
+ res.isCubemap = true;
226
+ return uid;
227
+ }
228
+
229
+ //! Insert an axes suffix before a cubemap URL's extension:
230
+ //! InsertCubemapAxesSuffix("/envCloudyCubemap.ktx2", "ogl") -> "/envCloudyCubemap_ogl.ktx2"
231
+ //! Returns the url unchanged when the suffix is empty.
232
+ function InsertCubemapAxesSuffix(url, suffix) {
233
+ if (!suffix || !url)
234
+ return url;
235
+ const dot = url.lastIndexOf(".");
236
+ if (dot < 0)
237
+ return url + "_" + suffix;
238
+ return url.substring(0, dot) + "_" + suffix + url.substring(dot);
239
+ }
240
+
214
241
  //! Get or add the mesh url as a resource.
215
242
  function GetOrAddMesh(url) {
216
243
  return GetOrAddResourceFromUrl(core.GeometryPayloadType.MeshPointer, url);
@@ -238,6 +265,8 @@ module.exports = {
238
265
  GetOrAddResourceUidFromUrl,
239
266
  GetResourceFromUid,
240
267
  GetOrAddTexture,
268
+ GetOrAddCubemap,
269
+ InsertCubemapAxesSuffix,
241
270
  GetOrAddMesh,
242
271
  AddFontAtlas,
243
272
  AddTextCanvas,
package/scene/scene.js CHANGED
@@ -191,17 +191,19 @@ class Scene {
191
191
  if(j.environment.background_texture)
192
192
  {
193
193
  this.backgroundTexturePath=j.environment.background_texture;
194
- resources.GetOrAddTexture(this.backgroundTexturePath);
194
+ // The environment background is a cubemap; flag it so each client is served the
195
+ // variant matching its axes standard (falls back to the base file if absent).
196
+ resources.GetOrAddCubemap(this.backgroundTexturePath);
195
197
  }
196
198
  if(j.environment.diffuse_cubemap)
197
199
  {
198
200
  this.diffuseCubemapPath=j.environment.diffuse_cubemap;
199
- resources.GetOrAddTexture(this.diffuseCubemapPath);
201
+ resources.GetOrAddCubemap(this.diffuseCubemapPath);
200
202
  }
201
203
  if(j.environment.specular_cubemap)
202
204
  {
203
205
  this.specularCubemapPath=j.environment.specular_cubemap;
204
- resources.GetOrAddTexture(this.specularCubemapPath);
206
+ resources.GetOrAddCubemap(this.specularCubemapPath);
205
207
  }
206
208
  }
207
209
  if(j.font_atlases)