teleportxr 1.0.91 → 1.0.93
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 +46 -3
- package/core/core.js +131 -5
- package/package.json +1 -1
- package/protocol/encoders/node_encoder.js +5 -3
- package/protocol/encoders/resource_encoder.js +5 -3
- package/scene/node.js +13 -2
- package/scene/resources.js +33 -4
- package/scene/scene.js +9 -3
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.
|
|
@@ -223,6 +228,9 @@ class Client {
|
|
|
223
228
|
this.setupCommand.int64_startTimestamp_utc_unix_us = BigInt(core.getStartTimeUnixUs());
|
|
224
229
|
if(this.scene)
|
|
225
230
|
{
|
|
231
|
+
// Tell the client which axes standard the server authored the scene in. Node transforms
|
|
232
|
+
// are converted to the client's own standard at encode time (see SendNode).
|
|
233
|
+
this.setupCommand.AxesStandard_axesStandard=this.scene.serverAxesStandard;
|
|
226
234
|
if(this.scene.backgroundTexturePath&&this.scene.backgroundTexturePath!="")
|
|
227
235
|
{
|
|
228
236
|
this.setupCommand.BackgroundMode_backgroundMode=BackgroundMode.TEXTURE;
|
|
@@ -545,7 +553,9 @@ class Client {
|
|
|
545
553
|
var node=this.scene.GetNode(uid);
|
|
546
554
|
const MAX_NODE_SIZE=500;
|
|
547
555
|
const buffer = new ArrayBuffer(MAX_NODE_SIZE);
|
|
548
|
-
|
|
556
|
+
// Convert the node's transform from the server's axes standard to this client's, exactly as
|
|
557
|
+
// the C++ server does in GeometryEncoder::encodeNodes (ConvertTransform server->client).
|
|
558
|
+
const nodeSize=node_encoder.encodeNode(node,buffer,this.scene.serverAxesStandard,this.clientAxesStandard);
|
|
549
559
|
const view2 = new DataView(buffer, 0, nodeSize);
|
|
550
560
|
if(!this.webRtcConnection)
|
|
551
561
|
{
|
|
@@ -564,6 +574,34 @@ class Client {
|
|
|
564
574
|
if(sendSuccess)
|
|
565
575
|
this.geometryService.EncodedResource(uid);
|
|
566
576
|
}
|
|
577
|
+
//! For a cubemap resource, return the URL of the variant matching this client's axes
|
|
578
|
+
//! standard (e.g. /envCloudyCubemap.ktx2 -> /envCloudyCubemap_ogl.ktx2). Returns undefined
|
|
579
|
+
//! to mean "use the resource's base url" — for non-cubemaps, unknown axes standards, or
|
|
580
|
+
//! when the variant file is missing under the public path (so streaming never breaks).
|
|
581
|
+
CubemapUrlForClient(resource)
|
|
582
|
+
{
|
|
583
|
+
if(!resource || !resource.isCubemap)
|
|
584
|
+
return undefined;
|
|
585
|
+
const suffix=core.AxesStandardToCubemapSuffix(this.clientAxesStandard);
|
|
586
|
+
if(!suffix)
|
|
587
|
+
return undefined;
|
|
588
|
+
const candidate=resources.InsertCubemapAxesSuffix(resource.url, suffix);
|
|
589
|
+
if(candidate===resource.url)
|
|
590
|
+
return undefined;
|
|
591
|
+
// Only serve the variant if it actually exists; otherwise fall back to the base file.
|
|
592
|
+
if(this.scene&&this.scene.publicPath)
|
|
593
|
+
{
|
|
594
|
+
// URLs are server-relative (e.g. "/envCloudyCubemap.ktx2"); strip any scheme/host.
|
|
595
|
+
const rel=candidate.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]*/, "");
|
|
596
|
+
const full=path.join(this.scene.publicPath, rel);
|
|
597
|
+
if(!fs.existsSync(full))
|
|
598
|
+
{
|
|
599
|
+
console.warn("Cubemap variant "+full+" not found; serving base "+resource.url+" to client "+this.clientID);
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return candidate;
|
|
604
|
+
}
|
|
567
605
|
SendGenericResource(uid)
|
|
568
606
|
{
|
|
569
607
|
var resource=resources.GetResourceFromUid(uid);
|
|
@@ -572,9 +610,10 @@ class Client {
|
|
|
572
610
|
console.warn("No resource of uid ",uid," was found.")
|
|
573
611
|
return;
|
|
574
612
|
}
|
|
613
|
+
const urlOverride=this.CubemapUrlForClient(resource);
|
|
575
614
|
const MAX_BUFFER_SIZE=resource.encodedSize();;
|
|
576
615
|
const buffer = new ArrayBuffer(MAX_BUFFER_SIZE);
|
|
577
|
-
const resourceSize=resource_encoder.EncodeResource(resource,buffer);
|
|
616
|
+
const resourceSize=resource_encoder.EncodeResource(resource,buffer,urlOverride);
|
|
578
617
|
const view2 = new DataView(buffer, 0, resourceSize);
|
|
579
618
|
if(!this.webRtcConnection)
|
|
580
619
|
{
|
|
@@ -588,7 +627,7 @@ class Client {
|
|
|
588
627
|
return;
|
|
589
628
|
}
|
|
590
629
|
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 — "+
|
|
630
|
+
console.log("[T+"+this.elapsedMsSinceStart()+"ms, conn+"+this.elapsedMsSinceConnected()+"ms] Sending resource "+uid+" "+(urlOverride||resource.url)+" to Client "+this.clientID+", size: "+resourceSize+" bytes — "+
|
|
592
631
|
(sendSuccess ? "OK" : "FAILED"));
|
|
593
632
|
if(sendSuccess)
|
|
594
633
|
this.geometryService.EncodedResource(uid);
|
|
@@ -619,6 +658,10 @@ class Client {
|
|
|
619
658
|
}
|
|
620
659
|
var handshakeMessage=new message.HandshakeMessage();
|
|
621
660
|
core.decodeFromUint8Array(handshakeMessage,data);
|
|
661
|
+
// Remember the client's axes standard so cubemaps are streamed in the matching variant.
|
|
662
|
+
this.clientAxesStandard=handshakeMessage.AxesStandard_axesStandard;
|
|
663
|
+
console.log("Client "+this.clientID+" handshake axesStandard="+this.clientAxesStandard
|
|
664
|
+
+" (cubemap suffix '"+core.AxesStandardToCubemapSuffix(this.clientAxesStandard)+"')");
|
|
622
665
|
const excess =data.length-message.HandshakeMessage.sizeof();
|
|
623
666
|
const numReceived =excess/core.UID_SIZE;
|
|
624
667
|
if(numReceived!=handshakeMessage.uint64_resourceCount) {
|
package/core/core.js
CHANGED
|
@@ -175,12 +175,136 @@ const AxesStandard =
|
|
|
175
175
|
LeftHanded: 2,
|
|
176
176
|
YVertical: 4,
|
|
177
177
|
ZVertical: 8,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
|
|
203
|
+
// ---- Axes-standard conversions for object transforms ----
|
|
204
|
+
// Ported from the C++ server (libavstream common_maths.h ConvertPosition/Rotation/Scale), which
|
|
205
|
+
// converts each node's transform from the server's axes standard to the client's during encoding.
|
|
206
|
+
// The C++ table only covers Unreal/Unity <-> Gl/Engineering (its server is engine-driven), so the
|
|
207
|
+
// Engineering <-> GlStyle cases below are added here (both right-handed: position/quat vector part
|
|
208
|
+
// maps as (x, z, -y); its inverse as (x, -z, y); scale permutes y/z).
|
|
209
|
+
function ConvertPosition(from, to, p)
|
|
210
|
+
{
|
|
211
|
+
const A = AxesStandard;
|
|
212
|
+
if (from === to) return { x: p.x, y: p.y, z: p.z };
|
|
213
|
+
if (from === A.UnrealStyle)
|
|
214
|
+
{
|
|
215
|
+
if (to === A.GlStyle) return { x: +p.y, y: +p.z, z: -p.x };
|
|
216
|
+
if (to === A.EngineeringStyle) return { x: p.y, y: p.x, z: p.z };
|
|
217
|
+
}
|
|
218
|
+
else if (from === A.UnityStyle)
|
|
219
|
+
{
|
|
220
|
+
if (to === A.GlStyle) return { x: p.x, y: p.y, z: -p.z };
|
|
221
|
+
if (to === A.EngineeringStyle) return { x: p.x, y: p.z, z: p.y };
|
|
222
|
+
}
|
|
223
|
+
else if (from === A.EngineeringStyle)
|
|
224
|
+
{
|
|
225
|
+
if (to === A.UnrealStyle) return { x: p.y, y: p.x, z: p.z };
|
|
226
|
+
if (to === A.UnityStyle) return { x: p.x, y: p.z, z: p.y };
|
|
227
|
+
if (to === A.GlStyle) return { x: p.x, y: p.z, z: -p.y }; // added
|
|
228
|
+
}
|
|
229
|
+
else if (from === A.GlStyle)
|
|
230
|
+
{
|
|
231
|
+
if (to === A.UnrealStyle) return { x: -p.z, y: +p.x, z: +p.y };
|
|
232
|
+
if (to === A.UnityStyle) return { x: p.x, y: p.y, z: -p.z };
|
|
233
|
+
if (to === A.EngineeringStyle) return { x: p.x, y: -p.z, z: p.y }; // added
|
|
234
|
+
}
|
|
235
|
+
console.warn("ConvertPosition: unsupported axes "+from+"->"+to+"; leaving unchanged");
|
|
236
|
+
return { x: p.x, y: p.y, z: p.z };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ConvertRotation(from, to, q)
|
|
240
|
+
{
|
|
241
|
+
const A = AxesStandard;
|
|
242
|
+
if (from === to) return { x: q.x, y: q.y, z: q.z, w: q.w };
|
|
243
|
+
if (from === A.UnrealStyle)
|
|
244
|
+
{
|
|
245
|
+
if (to === A.GlStyle) return { x: -q.y, y: -q.z, z: +q.x, w: q.w };
|
|
246
|
+
if (to === A.EngineeringStyle) return { x: -q.y, y: -q.x, z: -q.z, w: q.w };
|
|
247
|
+
}
|
|
248
|
+
else if (from === A.EngineeringStyle)
|
|
249
|
+
{
|
|
250
|
+
if (to === A.UnrealStyle) return { x: -q.y, y: -q.x, z: -q.z, w: q.w };
|
|
251
|
+
if (to === A.UnityStyle) return { x: -q.x, y: -q.z, z: -q.y, w: q.w };
|
|
252
|
+
if (to === A.GlStyle) return { x: q.x, y: q.z, z: -q.y, w: q.w }; // added
|
|
253
|
+
}
|
|
254
|
+
else if (from === A.GlStyle)
|
|
255
|
+
{
|
|
256
|
+
if (to === A.UnrealStyle) return { x: +q.z, y: -q.x, z: -q.y, w: q.w };
|
|
257
|
+
if (to === A.UnityStyle) return { x: -q.x, y: -q.y, z: q.z, w: q.w };
|
|
258
|
+
if (to === A.EngineeringStyle) return { x: q.x, y: -q.z, z: q.y, w: q.w }; // added
|
|
259
|
+
}
|
|
260
|
+
else if (from === A.UnityStyle)
|
|
261
|
+
{
|
|
262
|
+
if (to === A.GlStyle) return { x: -q.x, y: -q.y, z: q.z, w: q.w };
|
|
263
|
+
if (to === A.EngineeringStyle) return { x: -q.x, y: -q.z, z: -q.y, w: q.w };
|
|
264
|
+
}
|
|
265
|
+
console.warn("ConvertRotation: unsupported axes "+from+"->"+to+"; leaving unchanged");
|
|
266
|
+
return { x: q.x, y: q.y, z: q.z, w: q.w };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function ConvertScale(from, to, s)
|
|
270
|
+
{
|
|
271
|
+
const A = AxesStandard;
|
|
272
|
+
if (from === to) return { x: s.x, y: s.y, z: s.z };
|
|
273
|
+
if (from === A.UnrealStyle)
|
|
274
|
+
{
|
|
275
|
+
if (to === A.GlStyle) return { x: +s.y, y: +s.z, z: s.x };
|
|
276
|
+
if (to === A.EngineeringStyle) return { x: s.y, y: s.x, z: s.z };
|
|
277
|
+
}
|
|
278
|
+
else if (from === A.UnityStyle)
|
|
279
|
+
{
|
|
280
|
+
if (to === A.GlStyle) return { x: s.x, y: s.y, z: s.z };
|
|
281
|
+
if (to === A.EngineeringStyle) return { x: s.x, y: s.z, z: s.y };
|
|
282
|
+
}
|
|
283
|
+
else if (from === A.EngineeringStyle)
|
|
284
|
+
{
|
|
285
|
+
if (to === A.UnrealStyle) return { x: s.y, y: s.x, z: s.z };
|
|
286
|
+
if (to === A.UnityStyle) return { x: s.x, y: s.z, z: s.y };
|
|
287
|
+
if (to === A.GlStyle) return { x: s.x, y: s.z, z: s.y }; // added (abs of position map)
|
|
288
|
+
}
|
|
289
|
+
else if (from === A.GlStyle)
|
|
290
|
+
{
|
|
291
|
+
if (to === A.UnrealStyle) return { x: s.z, y: +s.x, z: +s.y };
|
|
292
|
+
if (to === A.UnityStyle) return { x: s.x, y: s.y, z: s.z };
|
|
293
|
+
if (to === A.EngineeringStyle) return { x: s.x, y: s.z, z: s.y }; // added
|
|
294
|
+
}
|
|
295
|
+
return { x: s.x, y: s.y, z: s.z };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
//! Convert a {position, orientation, scale} pose from one axes standard to another.
|
|
299
|
+
function ConvertPose(from, to, pose)
|
|
300
|
+
{
|
|
301
|
+
return {
|
|
302
|
+
position: ConvertPosition(from, to, pose.position),
|
|
303
|
+
orientation: ConvertRotation(from, to, pose.orientation),
|
|
304
|
+
scale: ConvertScale(from, to, pose.scale || { x: 1, y: 1, z: 1 }),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
184
308
|
const GeometryPayloadType =
|
|
185
309
|
{
|
|
186
310
|
Invalid: 0,
|
|
@@ -441,7 +565,9 @@ function put_string(dataView, byteOffset, name) {
|
|
|
441
565
|
|
|
442
566
|
module.exports = {
|
|
443
567
|
UID_SIZE, endian, SizeOfType, encodeIntoDataView, decodeFromDataView
|
|
444
|
-
, vec4, BackgroundMode, AxesStandard,
|
|
568
|
+
, vec4, BackgroundMode, AxesStandard, AxesStandardToCubemapSuffix
|
|
569
|
+
, ConvertPosition, ConvertRotation, ConvertScale, ConvertPose
|
|
570
|
+
, GeometryPayloadType, DisplayInfo, RenderingFeatures, LightingMode, VideoCodec
|
|
445
571
|
, VideoConfig, ClientDynamicLighting, encodeToUint8Array, decodeFromUint8Array
|
|
446
572
|
, generateUid, getStartTimeUnixUs, getTimestampUs,
|
|
447
573
|
unixTimeToUTCString, put_float32, put_uint16, put_int32, put_uint32
|
package/package.json
CHANGED
|
@@ -13,13 +13,15 @@ function putPlaceholderSize(dataView)
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// return the size of the encoded node.
|
|
16
|
-
|
|
16
|
+
// fromAxes/toAxes, when supplied, convert the node's transform from the server's axes standard to
|
|
17
|
+
// the client's during encoding (matches the C++ server). Omit them to encode the pose unchanged.
|
|
18
|
+
function encodeNode(node,buffer,fromAxes,toAxes)
|
|
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
23
|
|
|
22
|
-
var t=node.encodeIntoDataView(dataView,byteOffset);
|
|
24
|
+
var t=node.encodeIntoDataView(dataView,byteOffset,fromAxes,toAxes);
|
|
23
25
|
byteOffset=t;
|
|
24
26
|
// Actual size is now known: write the count of bytes that follow the
|
|
25
27
|
// size field, in little-endian (the protocol convention). Without the
|
|
@@ -13,12 +13,14 @@ function putPlaceholderSize(dataView)
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// return the size of the encoded resource.
|
|
16
|
-
|
|
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
|
package/scene/node.js
CHANGED
|
@@ -308,7 +308,7 @@ class Node {
|
|
|
308
308
|
);
|
|
309
309
|
this.components.push(tc);
|
|
310
310
|
}
|
|
311
|
-
encodeIntoDataView(dataView, byteOffset) {
|
|
311
|
+
encodeIntoDataView(dataView, byteOffset, fromAxes, toAxes) {
|
|
312
312
|
byteOffset = core.put_uint8(
|
|
313
313
|
dataView,
|
|
314
314
|
byteOffset,
|
|
@@ -317,8 +317,19 @@ class Node {
|
|
|
317
317
|
|
|
318
318
|
byteOffset = core.put_uint64(dataView, byteOffset, this.uid);
|
|
319
319
|
byteOffset = core.put_string(dataView, byteOffset, this.name);
|
|
320
|
+
// Convert the node's transform into the client's axes standard (server -> client). When no
|
|
321
|
+
// conversion is requested, or the standards match, the pose is encoded unchanged.
|
|
320
322
|
var clientsidePose = this.pose;
|
|
321
|
-
|
|
323
|
+
if (fromAxes !== undefined && toAxes !== undefined && fromAxes !== toAxes &&
|
|
324
|
+
toAxes !== core.AxesStandard.NotInitialized)
|
|
325
|
+
{
|
|
326
|
+
const c = core.ConvertPose(fromAxes, toAxes, this.pose);
|
|
327
|
+
clientsidePose = new Pose();
|
|
328
|
+
clientsidePose.position = c.position;
|
|
329
|
+
clientsidePose.orientation = c.orientation;
|
|
330
|
+
clientsidePose.scale = c.scale;
|
|
331
|
+
}
|
|
332
|
+
|
|
322
333
|
byteOffset = clientsidePose.encodeIntoDataView(dataView, byteOffset);
|
|
323
334
|
|
|
324
335
|
byteOffset = core.put_uint8(dataView, byteOffset, this.stationary);
|
package/scene/resources.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
28
|
-
url = Resource.defaultPathRoot +
|
|
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
|
@@ -15,6 +15,10 @@ class Scene {
|
|
|
15
15
|
this.specularCubemapPath="";
|
|
16
16
|
this.assetsPath="assets";
|
|
17
17
|
this.publicPath="http_resources";
|
|
18
|
+
// The axes standard the scene (node poses, cubemaps) is authored in. Node transforms are
|
|
19
|
+
// converted from this to each client's axes standard when streamed. Z-up Engineering by
|
|
20
|
+
// default, matching the C++ client's native frame and the *_eng cubemap source.
|
|
21
|
+
this.serverAxesStandard=core.AxesStandard.EngineeringStyle;
|
|
18
22
|
}
|
|
19
23
|
GetOrCreateNode(uid) {
|
|
20
24
|
if (!this.nodes.has(uid)) {
|
|
@@ -191,17 +195,19 @@ class Scene {
|
|
|
191
195
|
if(j.environment.background_texture)
|
|
192
196
|
{
|
|
193
197
|
this.backgroundTexturePath=j.environment.background_texture;
|
|
194
|
-
|
|
198
|
+
// The environment background is a cubemap; flag it so each client is served the
|
|
199
|
+
// variant matching its axes standard (falls back to the base file if absent).
|
|
200
|
+
resources.GetOrAddCubemap(this.backgroundTexturePath);
|
|
195
201
|
}
|
|
196
202
|
if(j.environment.diffuse_cubemap)
|
|
197
203
|
{
|
|
198
204
|
this.diffuseCubemapPath=j.environment.diffuse_cubemap;
|
|
199
|
-
resources.
|
|
205
|
+
resources.GetOrAddCubemap(this.diffuseCubemapPath);
|
|
200
206
|
}
|
|
201
207
|
if(j.environment.specular_cubemap)
|
|
202
208
|
{
|
|
203
209
|
this.specularCubemapPath=j.environment.specular_cubemap;
|
|
204
|
-
resources.
|
|
210
|
+
resources.GetOrAddCubemap(this.specularCubemapPath);
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
if(j.font_atlases)
|