forked from realitymediabook/core-components
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtml-script.js
More file actions
766 lines (673 loc) · 31.4 KB
/
html-script.js
File metadata and controls
766 lines (673 loc) · 31.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
/**
* Description
* ===========
* create a HTML object by rendering a script that creates and manages it
*
*/
import { findAncestorWithComponent } from "../utils/scene-graph";
import {vueComponents as htmlComponents} from "https://resources.realitymedia.digital/vue-apps/dist/hubs.js";
import spinnerImage from "../assets/Spinner-1s-200px.png"
// load and setup all the bits of the textures for the door
const loader = new THREE.TextureLoader()
const spinnerGeometry = new THREE.PlaneGeometry( 1, 1 );
const spinnerMaterial = new THREE.MeshBasicMaterial({
transparent: true,
alphaTest: 0.1
})
loader.load(spinnerImage, (color) => {
spinnerMaterial.map = color;
spinnerMaterial.needsUpdate = true
})
// var htmlComponents;
// var scriptPromise;
// if (window.__testingVueApps) {
// scriptPromise = import(window.__testingVueApps)
// } else {
// scriptPromise = import("https://resources.realitymedia.digital/vue-apps/dist/hubs.js")
// }
// // scriptPromise = scriptPromise.then(module => {
// // return module
// // });
/**
* Modified from https://github.com/mozilla/hubs/blob/master/src/components/fader.js
* to include adjustable duration and converted from component to system
*/
AFRAME.registerSystem('html-script', {
init() {
this.systemTick = htmlComponents["systemTick"];
this.initializeEthereal = htmlComponents["initializeEthereal"]
if (!this.systemTick || !this.initializeEthereal) {
console.error("error in html-script system: htmlComponents has no systemTick and/or initializeEthereal methods")
} else {
this.initializeEthereal()
}
},
tick(t, dt) {
this.systemTick(t, dt)
},
})
const once = {
once : true
};
AFRAME.registerComponent('html-script', {
schema: {
// name must follow the pattern "*_componentName"
name: { type: "string", default: ""},
width: { type: "number", default: -1},
height: { type: "number", default: -1},
parameter1: { type: "string", default: ""},
parameter2: { type: "string", default: ""},
parameter3: { type: "string", default: ""},
parameter4: { type: "string", default: ""},
},
init: function () {
this.script = null;
this.fullName = this.data.name;
this.scriptData = {
width: this.data.width,
height: this.data.height,
parameter1: this.data.parameter1,
parameter2: this.data.parameter2,
parameter3: this.data.parameter3,
parameter4: this.data.parameter4
}
this.loading = true;
this.spinnerPlane = new THREE.Mesh( spinnerGeometry, spinnerMaterial );
this.spinnerPlane.matrixAutoUpdate = true
this.spinnerPlane.position.z = 0.05
if (!this.fullName || this.fullName.length == 0) {
this.parseNodeName();
} else {
this.componentName = this.fullName
}
let root = findAncestorWithComponent(this.el, "gltf-model-plus")
root && root.addEventListener("model-loaded", (ev) => {
this.createScript()
}, once);
//this.createScript();
},
update: function () {
if (this.data.name === "" || this.data.name === this.fullName) return
this.fullName = this.data.name;
// this.parseNodeName();
this.componentName = this.fullName;
if (this.script) {
this.destroyScript()
}
this.createScript();
},
createScript: function () {
// each time we load a script component we will possibly create
// a new networked component. This is fine, since the networked Id
// is based on the full name passed as a parameter, or assigned to the
// component in Spoke. It does mean that if we have
// multiple objects in the scene which have the same name, they will
// be in sync. It also means that if you want to drop a component on
// the scene via a .glb, it must have a valid name parameter inside it.
// A .glb in spoke will fall back to the spoke name if you use one without
// a name inside it.
let loader = () => {
this.loadScript().then( () => {
if (!this.script) return
if (this.script.isNetworked) {
// get the parent networked entity, when it's finished initializing.
// When creating this as part of a GLTF load, the
// parent a few steps up will be networked. We'll only do this
// if the HTML script wants to be networked
this.netEntity = null
// bind callbacks
this.getSharedData = this.getSharedData.bind(this);
this.takeOwnership = this.takeOwnership.bind(this);
this.setSharedData = this.setSharedData.bind(this)
this.script.setNetworkMethods(this.takeOwnership, this.setSharedData)
}
// set up the local content and hook it to the scene
const scriptEl = document.createElement('a-entity')
this.simpleContainer = scriptEl
this.simpleContainer.object3D.matrixAutoUpdate = true
this.simpleContainer.setObject3D("weblayer3d", this.script.webLayer3D)
// lets figure out the scale, but scaling to fill the a 1x1m square, that has also
// potentially been scaled by the parents parent node. If we scale the entity in spoke,
// this is where the scale is set. If we drop a node in and scale it, the scale is also
// set there.
// We used to have a fixed size passed back from the entity, but that's too restrictive:
// const width = this.script.width
// const height = this.script.height
// TODO: need to find environment-scene, go down two levels to the group above
// the nodes in the scene. Then accumulate the scales up from this node to
// that node. This will account for groups, and nesting.
var width = 1, height = 1;
if (this.el.components["media-image"]) {
// attached to an image in spoke, so the image mesh is size 1 and is scaled directly
let scaleM = this.el.object3DMap["mesh"].scale
let scaleI = this.el.object3D.scale
width = scaleM.x * scaleI.x
height = scaleM.y * scaleI.y
scaleI.x = 1
scaleI.y = 1
scaleI.z = 1
this.el.object3D.matrixNeedsUpdate = true;
} else {
// it's embedded in a simple gltf model; other models may not work
// we assume it's at the top level mesh, and that the model itself is scaled
let mesh = this.el.object3DMap["mesh"]
if (mesh) {
let box = mesh.geometry.boundingBox;
width = (box.max.x - box.min.x) * mesh.scale.x
height = (box.max.y - box.min.y) * mesh.scale.y
} else {
let meshScale = this.el.object3D.scale
width = meshScale.x
height = meshScale.y
meshScale.x = 1
meshScale.y = 1
meshScale.z = 1
this.el.object3D.matrixNeedsUpdate = true;
}
// apply the root gltf scale.
var parent2 = this.el.parentEl.parentEl.object3D
width *= parent2.scale.x
height *= parent2.scale.y
parent2.scale.x = 1
parent2.scale.y = 1
parent2.scale.z = 1
parent2.matrixNeedsUpdate = true;
}
this.actualWidth = width
this.actualHeight = height
if (width > 0 && height > 0) {
const {width: wsize, height: hsize} = this.script.getSize()
if (wsize > 0 && hsize > 0) {
var scale = Math.min(width / wsize, height / hsize)
this.simpleContainer.setAttribute("scale", { x: scale, y: scale, z: scale});
}
const spinnerScale = Math.min(width,height) * 0.25
this.spinnerPlane.scale.set(spinnerScale, spinnerScale, 1)
}
// there will be one element already, the cube we created in blender
// and attached this component to, so remove it if it is there.
// this.el.object3D.children.pop()
for (const c of this.el.object3D.children) {
c.visible = false;
}
// make sure "isStatic" is correct; can't be static if either interactive or networked
if (this.script.isStatic && (this.script.isInteractive || this.script.isNetworked)) {
this.script.isStatic = false;
}
// add in our container
this.el.appendChild(this.simpleContainer)
this.el.setObject3D("spinner", this.spinnerPlane)
// TODO: we are going to have to make sure this works if
// the script is ON an interactable (like an image)
if (this.script.isInteractive) {
if (this.el.classList.contains("interactable")) {
// this.el.classList.remove("interactable")
}
// make the html object clickable
this.simpleContainer.setAttribute('is-remote-hover-target','')
this.simpleContainer.setAttribute('tags', {
singleActionButton: true,
inspectable: true,
isStatic: true,
togglesHoveredActionSet: true
})
this.simpleContainer.setAttribute('class', "interactable")
// forward the 'interact' events to our object
this.clicked = this.clicked.bind(this)
this.simpleContainer.object3D.addEventListener('interact', this.clicked)
if (this.script.isDraggable) {
// we aren't going to really deal with this till we have a use case, but
// we can set it up for now
this.simpleContainer.setAttribute('tags', {
singleActionButton: true,
isHoldable: true,
holdableButton: true,
inspectable: true,
isStatic: true,
togglesHoveredActionSet: true
})
this.simpleContainer.object3D.addEventListener('holdable-button-down', (evt) => {
this.script.dragStart(evt)
})
this.simpleContainer.object3D.addEventListener('holdable-button-up', (evt) => {
this.script.dragEnd(evt)
})
}
//this.raycaster = new THREE.Raycaster()
this.hoverRayL = new THREE.Ray()
this.hoverRayR = new THREE.Ray()
} else {
// no interactivity, please
if (this.el.classList.contains("interactable")) {
this.el.classList.remove("interactable")
}
this.el.removeAttribute("is-remote-hover-target")
}
// TODO: this SHOULD work but make sure it works if the el we are on
// is networked, such as when attached to an image
if (this.el.hasAttribute("networked")) {
this.el.removeAttribute("networked")
}
if (this.script.isNetworked) {
// This function finds an existing copy of the Networked Entity (if we are not the
// first client in the room it will exist in other clients and be created by NAF)
// or create an entity if we are first.
this.setupNetworkedEntity = function (networkedEl) {
var persistent = true;
var netId;
if (networkedEl) {
// We will be part of a Networked GLTF if the GLTF was dropped on the scene
// or pinned and loaded when we enter the room. Use the networked parents
// networkId plus a disambiguating bit of text to create a unique Id.
netId = NAF.utils.getNetworkId(networkedEl) + "-html-script";
// if we need to create an entity, use the same persistence as our
// network entity (true if pinned, false if not)
persistent = entity.components.networked.data.persistent;
} else {
// this only happens if this component is on a scene file, since the
// elements on the scene aren't networked. So let's assume each entity in the
// scene will have a unique name. Adding a bit of text so we can find it
// in the DOM when debugging.
netId = this.fullName.replaceAll("_","-") + "-html-script"
}
// check if the networked entity we create for this component already exists.
// otherwise, create it
// - NOTE: it is created on the scene, not as a child of this entity, because
// NAF creates remote entities in the scene.
var entity;
if (NAF.entities.hasEntity(netId)) {
entity = NAF.entities.getEntity(netId);
} else {
entity = document.createElement('a-entity')
// store the method to retrieve the script data on this entity
entity.getSharedData = this.getSharedData;
// the "networked" component should have persistent=true, the template and
// networkId set, owner set to "scene" (so that it doesn't update the rest of
// the world with it's initial data, and should NOT set creator (the system will do that)
entity.setAttribute('networked', {
template: "#script-data-media",
persistent: persistent,
owner: "scene", // so that our initial value doesn't overwrite others
networkId: netId
});
this.el.sceneEl.appendChild(entity);
}
// save a pointer to the networked entity and then wait for it to be fully
// initialized before getting a pointer to the actual networked component in it
this.netEntity = entity;
NAF.utils.getNetworkedEntity(this.netEntity).then(networkedEl => {
this.stateSync = networkedEl.components["script-data"]
// if this is the first networked entity, it's sharedData will default to the
// string "{}", and we should initialize it with the initial data from the script
if (this.stateSync.sharedData.length == 2) {
let networked = networkedEl.components["networked"]
// if (networked.data.creator == NAF.clientId) {
// this.stateSync.initSharedData(this.script.getSharedData())
// }
}
})
}
this.setupNetworkedEntity = this.setupNetworkedEntity.bind(this)
this.setupNetworked = function () {
NAF.utils.getNetworkedEntity(this.el).then(networkedEl => {
this.setupNetworkedEntity(networkedEl)
}).catch(() => {
this.setupNetworkedEntity()
})
}
this.setupNetworked = this.setupNetworked.bind(this)
// This method handles the different startup cases:
// - if the GLTF was dropped on the scene, NAF will be connected and we can
// immediately initialize
// - if the GLTF is in the room scene or pinned, it will likely be created
// before NAF is started and connected, so we wait for an event that is
// fired when Hubs has started NAF
if (NAF.connection && NAF.connection.isConnected()) {
this.setupNetworked();
} else {
this.el.sceneEl.addEventListener('didConnectToNetworkedScene', this.setupNetworked)
}
}
}).catch(e => {
console.error("loadScript failed for script " + this.data.name + ": " + e)
})
}
// if attached to a node with a media-loader component, this means we attached this component
// to a media object in Spoke. We should wait till the object is fully loaded.
// Otherwise, it was attached to something inside a GLTF (probably in blender)
if (this.el.components["media-loader"]) {
this.el.addEventListener("media-loaded", () => {
loader()
},
{ once: true })
} else {
loader()
}
},
play: function () {
if (this.script) {
this.script.play()
}
},
pause: function () {
if (this.script) {
this.script.pause()
}
},
// handle "interact" events for clickable entities
clicked: function(evt) {
console.log("clicked on html: ", evt)
this.script.clicked(evt)
},
// methods that will be passed to the html object so they can update networked data
takeOwnership: function() {
if (this.stateSync) {
return this.stateSync.takeOwnership()
} else {
return true; // sure, go ahead and change it for now
}
},
setSharedData: function(dataObject) {
if (this.stateSync) {
return this.stateSync.setSharedData(dataObject)
}
return true
},
// this is called from below, to get the initial data from the script
getSharedData: function() {
if (this.script) {
return this.script.getSharedData()
}
// shouldn't happen
console.warn("script-data component called parent element but there is no script yet?")
return "{}"
},
// per frame stuff
tick: function (time) {
if (!this.script) return
if (this.loading) {
this.spinnerPlane.rotation.z += 0.03
} else {
if (this.script.isInteractive) {
// more or less copied from "hoverable-visuals.js" in hubs
const toggling = this.el.sceneEl.systems["hubs-systems"].cursorTogglingSystem;
var passthruInteractor = []
let interactorOne, interactorTwo;
const interaction = this.el.sceneEl.systems.interaction;
if (!interaction.ready) return; //DOMContentReady workaround
let hoverEl = this.simpleContainer
if (interaction.state.leftHand.hovered === hoverEl && !interaction.state.leftHand.held) {
interactorOne = interaction.options.leftHand.entity.object3D;
}
if (
interaction.state.leftRemote.hovered === hoverEl &&
!interaction.state.leftRemote.held &&
!toggling.leftToggledOff
) {
interactorOne = interaction.options.leftRemote.entity.object3D;
}
if (interactorOne) {
let pos = interactorOne.position
let dir = this.script.webLayer3D.getWorldDirection(new THREE.Vector3()).negate()
pos.addScaledVector(dir, -0.1)
this.hoverRayL.set(pos, dir)
passthruInteractor.push(this.hoverRayL)
}
if (
interaction.state.rightRemote.hovered === hoverEl &&
!interaction.state.rightRemote.held &&
!toggling.rightToggledOff
) {
interactorTwo = interaction.options.rightRemote.entity.object3D;
}
if (interaction.state.rightHand.hovered === hoverEl && !interaction.state.rightHand.held) {
interactorTwo = interaction.options.rightHand.entity.object3D;
}
if (interactorTwo) {
let pos = interactorTwo.position
let dir = this.script.webLayer3D.getWorldDirection(new THREE.Vector3()).negate()
pos.addScaledVector(dir, -0.1)
this.hoverRayR.set(pos, dir)
passthruInteractor.push(this.hoverRayR)
}
this.script.webLayer3D.interactionRays = passthruInteractor
}
if (this.script.isNetworked) {
// if we haven't finished setting up the networked entity don't do anything.
if (!this.netEntity || !this.stateSync) { return }
// if the state has changed in the networked data, update our html object
if (this.stateSync.changed) {
this.stateSync.changed = false
this.script.updateSharedData(this.stateSync.dataObject)
}
}
this.script.tick(time)
}
},
// TODO: should only be called if there is no parameter specifying the
// html script name.
parseNodeName: function () {
if (this.fullName === "") {
// TODO: switch this to find environment-root and go down to
// the node at the room of scene (one above the various nodes).
// then go up from here till we get to a node that has that node
// as it's parent
this.fullName = this.el.parentEl.parentEl.className
}
// nodes should be named anything at the beginning with
// "componentName"
// at the very end. This will fetch the component from the resource
// componentName
const params = this.fullName.match(/_([A-Za-z0-9]*)$/)
// if pattern matches, we will have length of 3, first match is the dir,
// second is the componentName name or number
if (!params || params.length < 2) {
console.warn("html-script componentName not formatted correctly: ", this.fullName)
this.componentName = null
} else {
this.componentName = params[1]
}
},
loadScript: async function () {
// if (scriptPromise) {
// try {
// htmlComponents = await scriptPromise;
// } catch(e) {
// console.error(e);
// return
// }
// scriptPromise = null
// }
var initScript = htmlComponents[this.componentName]
if (!initScript) {
console.warn("'html-script' component doesn't have script for " + this.componentName);
this.script = null
return;
}
try {
this.script = new initScript(this.scriptData);
} catch (e) {
console.error("error creating script for " + this.componentName, e);
this.script = null
}
if (this.script){
this.script.needsUpdate = true
// this.script.webLayer3D.refresh(true)
// this.script.webLayer3D.update(true)
this.script.waitForReady().then(() => {
const {width: wsize, height: hsize} = this.script.getSize()
if (wsize > 0 && hsize > 0) {
var scale = Math.min(this.actualWidth / wsize, this.actualHeight / hsize)
this.simpleContainer.setAttribute("scale", { x: scale, y: scale, z: scale});
}
// when a script finishes getting ready, tell the
// portals to update themselves
this.el.sceneEl.emit('updatePortals');
this.loading = false;
this.el.removeObject3D("spinner");
})
} else {
console.warn("'html-script' component failed to initialize script for " + this.componentName);
}
},
remove: function () {
this.destroyScript()
},
destroyScript: function () {
if (this.script.isInteractive) {
this.simpleContainer.object3D.removeEventListener('interact', this.clicked)
}
window.APP.scene.removeEventListener('didConnectToNetworkedScene', this.setupNetworked)
this.el.removeChild(this.simpleContainer)
this.simpleContainer.removeObject3D("weblayer3d")
this.simpleContainer = null
if (this.script.isNetworked && this.netEntity.parentNode) {
this.netEntity.parentNode.removeChild(this.netEntity)
}
this.script.destroy()
this.script = null
}
})
//
// Component for our networked state. This component does nothing except all us to
// change the state when appropriate. We could set this up to signal the component above when
// something has changed, instead of having the component above poll each frame.
//
AFRAME.registerComponent('script-data', {
schema: {
scriptdata: {type: "string", default: "{}"},
},
init: function () {
this.takeOwnership = this.takeOwnership.bind(this);
this.setSharedData = this.setSharedData.bind(this);
this.dataObject = this.el.getSharedData();
try {
this.sharedData = encodeURIComponent(JSON.stringify(this.dataObject))
this.el.setAttribute("script-data", "scriptdata", this.sharedData);
} catch(e) {
console.error("Couldn't encode initial script data object: ", e, this.dataObject)
this.sharedData = "{}"
this.dataObject = {}
}
this.changed = false;
},
update() {
this.changed = !(this.sharedData === this.data.scriptdata);
if (this.changed) {
try {
this.dataObject = JSON.parse(decodeURIComponent(this.data.scriptdata))
// do these after the JSON parse to make sure it has succeeded
this.sharedData = this.data.scriptdata;
this.changed = true
} catch(e) {
console.error("couldn't parse JSON received in script-sync: ", e)
this.sharedData = "{}"
this.dataObject = {}
}
}
},
// it is likely that applyPersistentSync only needs to be called for persistent
// networked entities, so we _probably_ don't need to do this. But if there is no
// persistent data saved from the network for this entity, this command does nothing.
play() {
if (this.el.components.networked) {
// not sure if this is really needed, but can't hurt
if (APP.utils) { // temporary till we ship new client
APP.utils.applyPersistentSync(this.el.components.networked.data.networkId);
}
}
},
takeOwnership() {
if (!NAF.utils.isMine(this.el) && !NAF.utils.takeOwnership(this.el)) return false;
return true;
},
// initSharedData(dataObject) {
// try {
// var htmlString = encodeURIComponent(JSON.stringify(dataObject))
// this.sharedData = htmlString
// this.dataObject = dataObject
// return true
// } catch (e) {
// console.error("can't stringify the object passed to script-sync")
// return false
// }
// },
// The key part in these methods (which are called from the component above) is to
// check if we are allowed to change the networked object. If we own it (isMine() is true)
// we can change it. If we don't own in, we can try to become the owner with
// takeOwnership(). If this succeeds, we can set the data.
//
// NOTE: takeOwnership ATTEMPTS to become the owner, by assuming it can become the
// owner and notifying the networked copies. If two or more entities try to become
// owner, only one (the last one to try) becomes the owner. Any state updates done
// by the "failed attempted owners" will not be distributed to the other clients,
// and will be overwritten (eventually) by updates from the other clients. By not
// attempting to guarantee ownership, this call is fast and synchronous. Any
// methods for guaranteeing ownership change would take a non-trivial amount of time
// because of network latencies.
setSharedData(dataObject) {
if (!NAF.utils.isMine(this.el) && !NAF.utils.takeOwnership(this.el)) return false;
try {
var htmlString = encodeURIComponent(JSON.stringify(dataObject))
this.sharedData = htmlString
this.dataObject = dataObject
this.el.setAttribute("script-data", "scriptdata", htmlString);
return true
} catch (e) {
console.error("can't stringify the object passed to script-sync")
return false
}
}
});
// Add our template for our networked object to the a-frame assets object,
// and a schema to the NAF.schemas. Both must be there to have custom components work
const assets = document.querySelector("a-assets");
assets.insertAdjacentHTML(
'beforeend',
`
<template id="script-data-media">
<a-entity
script-data
></a-entity>
</template>
`
)
const vectorRequiresUpdate = epsilon => {
return () => {
let prev = null;
return curr => {
if (prev === null) {
prev = new THREE.Vector3(curr.x, curr.y, curr.z);
return true;
} else if (!NAF.utils.almostEqualVec3(prev, curr, epsilon)) {
prev.copy(curr);
return true;
}
return false;
};
};
};
NAF.schemas.add({
template: "#script-data-media",
components: [
// {
// component: "script-data",
// property: "rotation",
// requiresNetworkUpdate: vectorRequiresUpdate(0.001)
// },
// {
// component: "script-data",
// property: "scale",
// requiresNetworkUpdate: vectorRequiresUpdate(0.001)
// },
{
component: "script-data",
property: "scriptdata"
}],
nonAuthorizedComponents: [
{
component: "script-data",
property: "scriptdata"
}
],
});