@@ -32,7 +32,7 @@
|
|
32
32
|
if (ext.injectImplicitBehaviours) {
|
33
33
|
// We're registering animators with implicit PlayAnimationOnClick (with hacked "start" trigger) here.
|
34
34
|
for (const animator of animators) {
|
35
|
-
if (!animator || !animator.runtimeAnimatorController) continue;
|
35
|
+
if (!animator || !animator.runtimeAnimatorController || !animator.enabled) continue;
|
36
36
|
const activeState = animator.runtimeAnimatorController.activeState;
|
37
37
|
|
38
38
|
// skip missing data, empty states or motions
|
@@ -60,7 +60,7 @@
|
|
60
60
|
}
|
61
61
|
else {
|
62
62
|
for (const animator of animators) {
|
63
|
-
if (!animator || !animator.runtimeAnimatorController) continue;
|
63
|
+
if (!animator || !animator.runtimeAnimatorController || !animator.enabled) continue;
|
64
64
|
|
65
65
|
if (debug) console.log(animator);
|
66
66
|
|
@@ -81,21 +81,43 @@
|
|
81
81
|
|
82
82
|
// TODO once PlayAnimationOnClick can use animation components as well,
|
83
83
|
// we can treat them the same as we treat Animators above.
|
84
|
-
|
85
|
-
|
86
|
-
|
84
|
+
if (ext.injectImplicitBehaviours) {
|
85
|
+
for (const animationComponent of animationComponents) {
|
86
|
+
if (!animationComponent || !animationComponent.clip ||!animationComponent.enabled) continue;
|
87
|
+
if (!animationComponent.playAutomatically) continue;
|
87
88
|
|
88
|
-
|
89
|
+
// Create a PlayAnimationOnClick component that will play the animation on start
|
90
|
+
// This is a bit hacky right now (we're using the internal "start" trigger)
|
91
|
+
const newComponent = new PlayAnimationOnClick();
|
92
|
+
newComponent.animation = animationComponent;
|
93
|
+
newComponent.stateName = animationComponent.clip.name;
|
94
|
+
newComponent.trigger = "start";
|
95
|
+
newComponent.name = "PlayAnimationOnClick_implicitAtStart_" + newComponent.stateName;
|
96
|
+
const go = new Object3D();
|
97
|
+
GameObject.addComponent(go, newComponent);
|
98
|
+
constructedObjects.push(go);
|
89
99
|
|
90
|
-
|
91
|
-
|
92
|
-
clips.push(clip);
|
100
|
+
// the behaviour can be anywhere in the hierarchy
|
101
|
+
root.add(go);
|
93
102
|
}
|
103
|
+
}
|
104
|
+
else {
|
105
|
+
for (const animationComponent of animationComponents) {
|
106
|
+
if (debug)
|
107
|
+
console.log(animationComponent);
|
94
108
|
|
95
|
-
|
109
|
+
const clips: AnimationClip[] = [];
|
110
|
+
|
111
|
+
for (const clip of animationComponent.animations) {
|
112
|
+
if (!clips.includes(clip))
|
113
|
+
clips.push(clip);
|
114
|
+
}
|
115
|
+
|
116
|
+
animationClips.push({ root: animationComponent.gameObject, clips: clips });
|
117
|
+
}
|
96
118
|
}
|
97
119
|
|
98
|
-
if (debug) console.log("USDZ Animation Clips", animationClips);
|
120
|
+
if (debug && animationClips?.length > 0) console.log("USDZ Animation Clips without behaviours", animationClips);
|
99
121
|
|
100
122
|
for (const pair of animationClips) {
|
101
123
|
for (const clip of pair.clips)
|
@@ -6,6 +6,7 @@
|
|
6
6
|
import { getParam } from "../../../../../engine/engine_utils.js";
|
7
7
|
import type { State } from "../../../../../engine/extensions/NEEDLE_animator_controller_model.js";
|
8
8
|
import { NEEDLE_progressive } from "../../../../../engine/extensions/NEEDLE_progressive.js";
|
9
|
+
import { Animation } from "../../../../Animation.js";
|
9
10
|
import { Animator } from "../../../../Animator.js";
|
10
11
|
import { AudioSource } from "../../../../AudioSource.js";
|
11
12
|
import { Behaviour, GameObject } from "../../../../Component.js";
|
@@ -155,7 +156,7 @@
|
|
155
156
|
if (this.target && this.object && this.gameObject) {
|
156
157
|
const moveForward = new BehaviorModel("Move to " + this.target?.name,
|
157
158
|
TriggerBuilder.tapTrigger(this.gameObject),
|
158
|
-
ActionBuilder.transformAction(this.object, this.target, this.duration, this.relativeMotion ?
|
159
|
+
ActionBuilder.transformAction(this.object, this.target, this.duration, this.relativeMotion ? "relative" : "absolute"),
|
159
160
|
);
|
160
161
|
ext.addBehavior(moveForward);
|
161
162
|
}
|
@@ -582,7 +583,7 @@
|
|
582
583
|
duration: number = 0.5;
|
583
584
|
|
584
585
|
@serializable()
|
585
|
-
motionType: MotionType =
|
586
|
+
motionType: MotionType = "bounce";
|
586
587
|
|
587
588
|
beforeCreateDocument() { }
|
588
589
|
|
@@ -661,7 +662,7 @@
|
|
661
662
|
const playbackTarget = this.target ? this.target.gameObject : this.gameObject;
|
662
663
|
const clipName = AudioExtension.getName(clipUrl);
|
663
664
|
const volume = this.target ? this.target.volume : 1;
|
664
|
-
const auralMode = this.target && this.target.spatialBlend == 0 ?
|
665
|
+
const auralMode = this.target && this.target.spatialBlend == 0 ? "nonSpatial" : "spatial";
|
665
666
|
|
666
667
|
// This checks if any child is clickable – if yes, the tap trigger is added; if not, we omit it.
|
667
668
|
let anyChildHasGeometry = false;
|
@@ -673,9 +674,9 @@
|
|
673
674
|
anyChildHasGeometry = true;
|
674
675
|
if (anyChildHasGeometry)
|
675
676
|
{
|
676
|
-
let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, "audio/" + clipName,
|
677
|
+
let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, "audio/" + clipName, "play", volume, auralMode);
|
677
678
|
// does not seem to work in iOS / QuickLook...
|
678
|
-
if (this.toggleOnClick) (playAction as ActionModel).multiplePerformOperation =
|
679
|
+
if (this.toggleOnClick) (playAction as ActionModel).multiplePerformOperation = "stop";
|
679
680
|
if (this.target && this.target.loop)
|
680
681
|
playAction = ActionBuilder.sequence(playAction).makeLooping();
|
681
682
|
const playClipOnTap = new BehaviorModel("playAudio " + this.name,
|
@@ -687,7 +688,7 @@
|
|
687
688
|
|
688
689
|
// automatically play audio on start too if the referenced AudioSource has playOnAwake enabled
|
689
690
|
if (this.target && this.target.playOnAwake && this.target.enabled) {
|
690
|
-
let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, "audio/" + clipName,
|
691
|
+
let playAction: IBehaviorElement = ActionBuilder.playAudioAction(playbackTarget, "audio/" + clipName, "play", volume, auralMode);
|
691
692
|
if (this.target.loop)
|
692
693
|
playAction = ActionBuilder.sequence(playAction).makeLooping();
|
693
694
|
const playClipOnStart = new BehaviorModel("playAudioOnStart" + (this.name ? "_" + this.name : ""),
|
@@ -730,8 +731,9 @@
|
|
730
731
|
// we want to expose this once we have a nice drawer for "Triggers" (e.g. shows proximity distance)
|
731
732
|
// and once we rename the component to "PlayAnimation" or "PlayAnimationOnTrigger"
|
732
733
|
trigger: "tap" | "start" = "tap"; // "proximity"
|
734
|
+
animation?: Animation;
|
733
735
|
|
734
|
-
private get target() { return this.animator?.gameObject }
|
736
|
+
private get target() { return this.animator?.gameObject || this.animation?.gameObject }
|
735
737
|
|
736
738
|
start(): void {
|
737
739
|
ensureRaycaster(this.gameObject);
|
@@ -775,7 +777,7 @@
|
|
775
777
|
}
|
776
778
|
|
777
779
|
afterCreateDocument(ext: BehaviorExtension, context: USDZExporterContext) {
|
778
|
-
if (
|
780
|
+
if ((this.animationSequence === undefined && this.animationLoopAfterSequence === undefined) || !this.stateAnimationModel) return;
|
779
781
|
if (!this.target) return;
|
780
782
|
|
781
783
|
const document = context.document;
|
@@ -842,8 +844,18 @@
|
|
842
844
|
}
|
843
845
|
|
844
846
|
createAnimation(ext: AnimationExtension, model: USDObject, _context: USDZExporterContext) {
|
845
|
-
if (!this.target || !this.animator) return;
|
847
|
+
if (!this.target || (!this.animator && !this.animation)) return;
|
846
848
|
|
849
|
+
this.stateAnimationModel = model;
|
850
|
+
|
851
|
+
if (this.animation && this.animation.clip) {
|
852
|
+
this.animationSequence = new Array<RegisteredAnimationInfo>();
|
853
|
+
this.animationLoopAfterSequence = new Array<RegisteredAnimationInfo>();
|
854
|
+
const anim = ext.registerAnimation(this.target, this.animation.clip);
|
855
|
+
if (anim) this.animationLoopAfterSequence.push(anim);
|
856
|
+
return;
|
857
|
+
}
|
858
|
+
|
847
859
|
// If there's a separate state specified to play after this one, we
|
848
860
|
// play it automatically. Theoretically an animator state machine flow could be encoded here.
|
849
861
|
|
@@ -918,7 +930,6 @@
|
|
918
930
|
console.warn("No clips found for state " + this.stateName + " on " + this.animator?.name + ", can't export animation data");
|
919
931
|
return;
|
920
932
|
}
|
921
|
-
this.stateAnimationModel = model;
|
922
933
|
|
923
934
|
const addStateToSequence = (state: State, sequence: Array<RegisteredAnimationInfo>) => {
|
924
935
|
if (!this.target) return;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Object3D } from "three";
|
2
2
|
|
3
|
-
import { getParam } from "../../../../../engine/engine_utils.js";
|
3
|
+
import { EnumToPrimitiveUnion, getParam } from "../../../../../engine/engine_utils.js";
|
4
4
|
import { makeNameSafeForUSD,USDDocument, USDObject, USDWriter } from "../../ThreeUSDZExporter.js";
|
5
5
|
import { BehaviorExtension } from "./Behaviour.js";
|
6
6
|
|
@@ -34,8 +34,6 @@
|
|
34
34
|
writeTo(_ext: BehaviorExtension, document: USDDocument, writer: USDWriter) {
|
35
35
|
if (!this.trigger || !this.action) return;
|
36
36
|
writer.beginBlock(`def Preliminary_Behavior "${this.id}"`);
|
37
|
-
writer.appendLine(`rel actions = <${this.action.id}>`);
|
38
|
-
writer.appendLine(`uniform bool exclusive = ${this.exclusive}`);
|
39
37
|
let triggerString = "";
|
40
38
|
if (Array.isArray(this.trigger)) {
|
41
39
|
triggerString = "[";
|
@@ -50,6 +48,8 @@
|
|
50
48
|
triggerString = `<${this.trigger.id}>`;
|
51
49
|
|
52
50
|
writer.appendLine(`rel triggers = ${triggerString} `);
|
51
|
+
writer.appendLine(`rel actions = <${this.action.id}>`);
|
52
|
+
writer.appendLine(`uniform bool exclusive = ${this.exclusive}`);
|
53
53
|
writer.appendLine();
|
54
54
|
if (Array.isArray(this.trigger)) {
|
55
55
|
for (const trigger of this.trigger) {
|
@@ -197,6 +197,7 @@
|
|
197
197
|
|
198
198
|
makeLooping() {
|
199
199
|
this.loops = 1;
|
200
|
+
this.performCount = 0;
|
200
201
|
return this;
|
201
202
|
}
|
202
203
|
|
@@ -217,8 +218,8 @@
|
|
217
218
|
writer.appendLine();
|
218
219
|
|
219
220
|
writer.appendLine(`token info:id = "Group"`);
|
220
|
-
writer.appendLine(`bool loops = ${this.loops} `);
|
221
|
-
writer.appendLine(`int performCount = ${this.performCount} `);
|
221
|
+
writer.appendLine(`bool loops = ${this.loops > 0 ? "true" : "false" } `);
|
222
|
+
writer.appendLine(`int performCount = ${Math.max(0, this.performCount)} `);
|
222
223
|
writer.appendLine(`token type = "${this.type}"`);
|
223
224
|
writer.appendLine();
|
224
225
|
|
@@ -232,29 +233,22 @@
|
|
232
233
|
}
|
233
234
|
}
|
234
235
|
|
235
|
-
|
236
|
-
|
237
|
-
blink = 1,
|
238
|
-
bounce = 2,
|
239
|
-
flip = 3,
|
240
|
-
float = 4,
|
241
|
-
jiggle = 5,
|
242
|
-
pulse = 6,
|
243
|
-
spin = 7,
|
244
|
-
};
|
236
|
+
/** @internal */
|
237
|
+
export type MotionType = "pop" | "blink" | "bounce" | "flip" | "float" | "jiggle" | "pulse" | "spin";
|
245
238
|
|
246
|
-
|
247
|
-
|
248
|
-
Absolute = "absolute"
|
249
|
-
};
|
239
|
+
/** @internal */
|
240
|
+
export type MotionStyle = "basic";
|
250
241
|
|
242
|
+
/** @internal */
|
243
|
+
export type Space = "relative" | "absolute";
|
244
|
+
|
251
245
|
// https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/actions_and_triggers/preliminary_action/multipleperformoperation
|
252
|
-
|
253
|
-
|
254
|
-
Ignore = "ignore",
|
255
|
-
Stop = "stop",
|
256
|
-
}
|
246
|
+
/** @internal */
|
247
|
+
export type MultiplePerformOperation = "allow" | "ignore" | "stop";
|
257
248
|
|
249
|
+
/** @internal */
|
250
|
+
export type EaseType = "none" | "in" | "out" | "inout";
|
251
|
+
|
258
252
|
export class ActionModel implements IBehaviorElement {
|
259
253
|
|
260
254
|
private static global_id: number = 0;
|
@@ -262,12 +256,12 @@
|
|
262
256
|
id: string;
|
263
257
|
tokenId?: string;
|
264
258
|
affectedObjects?: string | Target;
|
265
|
-
easeType?:
|
266
|
-
motionType?:
|
259
|
+
easeType?: EaseType;;
|
260
|
+
motionType?: MotionType;
|
267
261
|
duration?: number;
|
268
262
|
moveDistance?: number;
|
269
|
-
style?:
|
270
|
-
type?: string;
|
263
|
+
style?: MotionStyle;
|
264
|
+
type?: Space | string; // combined types of different actions
|
271
265
|
front?: Vec3;
|
272
266
|
up?: Vec3;
|
273
267
|
start?: number;
|
@@ -277,8 +271,8 @@
|
|
277
271
|
xFormTarget?: Target | string;
|
278
272
|
audio?: string;
|
279
273
|
gain?: number;
|
280
|
-
auralMode?:
|
281
|
-
multiplePerformOperation?:
|
274
|
+
auralMode?: AuralMode;
|
275
|
+
multiplePerformOperation?: MultiplePerformOperation;
|
282
276
|
|
283
277
|
clone(): ActionModel {
|
284
278
|
const copy = new ActionModel();
|
@@ -389,18 +383,9 @@
|
|
389
383
|
}
|
390
384
|
}
|
391
385
|
|
392
|
-
export
|
393
|
-
|
394
|
-
Pause = "pause",
|
395
|
-
Stop = "stop",
|
396
|
-
}
|
386
|
+
export type PlayAction = "play" | "pause" | "stop";
|
387
|
+
export type AuralMode = "spatial" | "nonSpatial" | "ambient";
|
397
388
|
|
398
|
-
export enum AuralMode {
|
399
|
-
Spatial = "spatial",
|
400
|
-
NonSpatial = "nonSpatial",
|
401
|
-
Ambient = "ambient",
|
402
|
-
}
|
403
|
-
|
404
389
|
export class ActionBuilder {
|
405
390
|
|
406
391
|
static sequence(...params: IBehaviorElement[]) {
|
@@ -420,7 +405,7 @@
|
|
420
405
|
act.duration = duration;
|
421
406
|
|
422
407
|
act.style = "basic";
|
423
|
-
act.motionType =
|
408
|
+
act.motionType = undefined;
|
424
409
|
act.moveDistance = 0;
|
425
410
|
act.easeType = "none";
|
426
411
|
return act;
|
@@ -441,7 +426,7 @@
|
|
441
426
|
act.animationSpeed = animationSpeed;
|
442
427
|
act.reversed = reversed;
|
443
428
|
act.pingPong = pingPong;
|
444
|
-
act.multiplePerformOperation =
|
429
|
+
act.multiplePerformOperation = "allow";
|
445
430
|
if (reversed) {
|
446
431
|
act.start -= duration;
|
447
432
|
//console.warn("Reversed animation does currently not work. The resulting file will most likely not playback.", act.id, targetObject);
|
@@ -479,22 +464,22 @@
|
|
479
464
|
return act;
|
480
465
|
}
|
481
466
|
|
482
|
-
static emphasize(targets: Target, duration: number, motionType: MotionType =
|
467
|
+
static emphasize(targets: Target, duration: number, motionType: MotionType = "bounce", moveDistance: number = 1, style: MotionStyle = "basic") {
|
483
468
|
const act = new ActionModel(targets);
|
484
469
|
act.tokenId = "Emphasize";
|
485
470
|
act.duration = duration;
|
486
471
|
act.style = style ?? "basic";
|
487
|
-
act.motionType =
|
472
|
+
act.motionType = motionType;
|
488
473
|
act.moveDistance = moveDistance;
|
489
474
|
return act;
|
490
475
|
}
|
491
476
|
|
492
|
-
static transformAction(targets: Target, transformTarget: Target, duration: number, transformType: Space, easeType:
|
477
|
+
static transformAction(targets: Target, transformTarget: Target, duration: number, transformType: Space, easeType: EaseType = "inout") {
|
493
478
|
const act = new ActionModel(targets);
|
494
479
|
act.tokenId = "Transform";
|
495
480
|
act.duration = duration;
|
496
481
|
act.type = transformType;
|
497
|
-
act.easeType = easeType;
|
482
|
+
act.easeType = duration > 0 ? easeType : "none";
|
498
483
|
if (Array.isArray(transformTarget)) {
|
499
484
|
console.error("Transform target must not be an array", transformTarget);
|
500
485
|
}
|
@@ -502,14 +487,14 @@
|
|
502
487
|
return act;
|
503
488
|
}
|
504
489
|
|
505
|
-
static playAudioAction(targets: Target, audio: string, type: PlayAction =
|
490
|
+
static playAudioAction(targets: Target, audio: string, type: PlayAction = "play", gain: number = 1, auralMode: AuralMode = "spatial") {
|
506
491
|
const act = new ActionModel(targets);
|
507
492
|
act.tokenId = "Audio";
|
508
493
|
act.type = type;
|
509
494
|
act.audio = audio;
|
510
495
|
act.gain = gain;
|
511
496
|
act.auralMode = auralMode;
|
512
|
-
act.multiplePerformOperation =
|
497
|
+
act.multiplePerformOperation = "allow";
|
513
498
|
return act;
|
514
499
|
}
|
515
500
|
|
@@ -58,7 +58,7 @@
|
|
58
58
|
|
59
59
|
unapply() {
|
60
60
|
// reset to the current value in the toneMappingEffect if that exists, else Linear
|
61
|
-
const currentMode: any = this.toneMappingEffect?.mode
|
61
|
+
const currentMode: any = this.toneMappingEffect?.mode?.value;
|
62
62
|
const newMode = this.toneMappingEffect?.getThreeToneMapping(currentMode)
|
63
63
|
this.context.renderer.toneMapping = newMode ?? LinearToneMapping;
|
64
64
|
}
|
@@ -83,7 +83,7 @@
|
|
83
83
|
|
84
84
|
// workaround: find the ToneMapping effect in the scene so we can apply the mode
|
85
85
|
this.toneMappingEffect = GameObject.findObjectOfType(Volume)?.sharedProfile?.components.find(c => c.typeName === "ToneMapping") as ToneMapping | null;
|
86
|
-
const currentMode: any = this.toneMappingEffect?.mode
|
86
|
+
const currentMode: any = this.toneMappingEffect?.mode?.value;
|
87
87
|
const expectedThreeMode = this.toneMappingEffect?.getThreeToneMapping(currentMode);
|
88
88
|
|
89
89
|
// We need this effect if someone uses ACES or AgX tonemapping;
|
@@ -99,7 +99,7 @@
|
|
99
99
|
this.context.renderer.toneMappingExposure = v;
|
100
100
|
|
101
101
|
// this is a workaround so that we can apply tonemapping options – no access to the ToneMappingEffect instance from the Tonemapping effect right now...
|
102
|
-
const currentMode = this.toneMappingEffect?.mode
|
102
|
+
const currentMode = this.toneMappingEffect?.mode?.value;
|
103
103
|
const threeMode = this.toneMappingEffect?.getThreeToneMapping(currentMode);
|
104
104
|
const mappedMode = this.threeToneMappingToEffectMode(threeMode);
|
105
105
|
if (mappedMode !== undefined)
|
@@ -118,6 +118,7 @@
|
|
118
118
|
export { ParticleSubEmitter } from "../ParticleSystemSubEmitter.js";
|
119
119
|
export { ParticleSystem } from "../ParticleSystem.js";
|
120
120
|
export { ParticleSystemRenderer } from "../ParticleSystem.js";
|
121
|
+
export { PhysicsExtension } from "../export/usdz/extensions/behavior/PhysicsExtension.js";
|
121
122
|
export { PixelationEffect } from "../postprocessing/Effects/Pixelation.js";
|
122
123
|
export { PlayableDirector } from "../timeline/PlayableDirector.js";
|
123
124
|
export { PlayAnimationOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
|
@@ -977,6 +977,7 @@
|
|
977
977
|
}
|
978
978
|
const file = files[i];
|
979
979
|
if (!file.includes(".glb") && !file.includes(".gltf")) {
|
980
|
+
// TODO this may not be true if the URL just forwards to a proper GLB file resource
|
980
981
|
const warning = `Needle Engine: found suspicious src "${file}"`;
|
981
982
|
console.warn(warning);
|
982
983
|
if (isLocalNetwork()) showBalloonWarning(warning);
|
@@ -55,6 +55,7 @@
|
|
55
55
|
|
56
56
|
/**
|
57
57
|
* Available attributes for the `<needle-engine>` web component
|
58
|
+
* @inheritdoc
|
58
59
|
*/
|
59
60
|
export type NeedleEngineAttributes =
|
60
61
|
MainAttributes
|
@@ -39,8 +39,17 @@
|
|
39
39
|
|
40
40
|
// https://developers.google.com/web/fundamentals/web-components/customelements
|
41
41
|
|
42
|
-
/**
|
43
|
-
*
|
42
|
+
/**
|
43
|
+
* <needle-engine> web component. See {@link NeedleEngineAttributes} attributes for supported attributes
|
44
|
+
* The needle engine web component creates and manages a needle engine context which is responsible for rendering a 3D scene using threejs.
|
45
|
+
* The needle engine context is created when the src attribute is set and disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
|
46
|
+
* The needle engine context is accessible via the context property on the needle engine element (e.g. document.querySelector("needle-engine").context).
|
47
|
+
* @link https://engine.needle.tools/docs/reference/needle-engine-attributes
|
48
|
+
*
|
49
|
+
* @example
|
50
|
+
* <needle-engine src="https://example.com/scene.glb"></needle-engine>
|
51
|
+
* @example
|
52
|
+
* <needle-engine src="https://example.com/scene.glb" camera-controls="false"></needle-engine>
|
44
53
|
*/
|
45
54
|
export class NeedleEngineHTMLElement extends HTMLElement implements INeedleEngineComponent {
|
46
55
|
|
@@ -72,6 +81,11 @@
|
|
72
81
|
return true;
|
73
82
|
}
|
74
83
|
|
84
|
+
/**
|
85
|
+
* Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
|
86
|
+
* The context is disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
|
87
|
+
* @returns {Promise<Context>} a promise that resolves to the context when the loading has finished
|
88
|
+
*/
|
75
89
|
public getContext(): Promise<Context> {
|
76
90
|
return new Promise((res, _rej) => {
|
77
91
|
if (this._context && this.loadingFinished) {
|
@@ -89,15 +103,11 @@
|
|
89
103
|
});
|
90
104
|
}
|
91
105
|
|
106
|
+
/**
|
107
|
+
* Get the context that is created when the src attribute is set and the loading has finished.
|
108
|
+
*/
|
92
109
|
public get context() { return this._context; }
|
93
110
|
|
94
|
-
/**@obsolete use context */
|
95
|
-
public get Context() { return this._context; }
|
96
|
-
/**@obsolete use Needle.GameObject */
|
97
|
-
private gameObject = GameObject;
|
98
|
-
/**@obsolete use Needle.GameObject */
|
99
|
-
private GameObject = GameObject;
|
100
|
-
|
101
111
|
private _context: Context;
|
102
112
|
private _overlay_ar: AROverlayHandler;
|
103
113
|
private _loadingProgress01: number = 0;
|
@@ -184,6 +194,9 @@
|
|
184
194
|
}
|
185
195
|
|
186
196
|
|
197
|
+
/**
|
198
|
+
* @internal
|
199
|
+
*/
|
187
200
|
async connectedCallback() {
|
188
201
|
if (debug) {
|
189
202
|
console.log("<needle-engine> connected");
|
@@ -218,6 +231,9 @@
|
|
218
231
|
}
|
219
232
|
}
|
220
233
|
|
234
|
+
/**
|
235
|
+
* @internal
|
236
|
+
*/
|
221
237
|
disconnectedCallback() {
|
222
238
|
this.removeEventListener("xr-session-started", this.onXRSessionStarted);
|
223
239
|
|
@@ -238,6 +254,9 @@
|
|
238
254
|
}
|
239
255
|
}
|
240
256
|
|
257
|
+
/**
|
258
|
+
* @internal
|
259
|
+
*/
|
241
260
|
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
|
242
261
|
if (debug) console.log("attributeChangedCallback", name, _oldValue, newValue);
|
243
262
|
switch (name) {
|
@@ -508,10 +527,16 @@
|
|
508
527
|
}
|
509
528
|
}
|
510
529
|
|
530
|
+
/**
|
531
|
+
* @internal
|
532
|
+
*/
|
511
533
|
getAROverlayContainer(): HTMLElement {
|
512
534
|
return this._overlay_ar.createOverlayContainer(this);
|
513
535
|
}
|
514
536
|
|
537
|
+
/**
|
538
|
+
* @internal
|
539
|
+
*/
|
515
540
|
getVROverlayContainer(): HTMLElement | null {
|
516
541
|
for (let i = 0; i < this.children.length; i++) {
|
517
542
|
const ch = this.children[i] as HTMLElement;
|
@@ -521,6 +546,9 @@
|
|
521
546
|
return null;
|
522
547
|
}
|
523
548
|
|
549
|
+
/**
|
550
|
+
* @internal
|
551
|
+
*/
|
524
552
|
onEnterAR(session: XRSession) {
|
525
553
|
this.onSetupAR();
|
526
554
|
const overlayContainer = this.getAROverlayContainer();
|
@@ -528,17 +556,26 @@
|
|
528
556
|
this.dispatchEvent(new CustomEvent("enter-ar", { detail: { session: session, context: this._context, htmlContainer: this._overlay_ar?.ARContainer } }));
|
529
557
|
}
|
530
558
|
|
559
|
+
/**
|
560
|
+
* @internal
|
561
|
+
*/
|
531
562
|
onExitAR(session: XRSession) {
|
532
563
|
this._overlay_ar.onEnd(this._context!);
|
533
564
|
this.onSetupDesktop();
|
534
565
|
this.dispatchEvent(new CustomEvent("exit-ar", { detail: { session: session, context: this._context, htmlContainer: this._overlay_ar?.ARContainer } }));
|
535
566
|
}
|
536
567
|
|
568
|
+
/**
|
569
|
+
* @internal
|
570
|
+
*/
|
537
571
|
onEnterVR(session: XRSession) {
|
538
572
|
this.onSetupVR();
|
539
573
|
this.dispatchEvent(new CustomEvent("enter-vr", { detail: { session: session, context: this._context } }));
|
540
574
|
}
|
541
575
|
|
576
|
+
/**
|
577
|
+
* @internal
|
578
|
+
*/
|
542
579
|
onExitVR(session: XRSession) {
|
543
580
|
this.onSetupDesktop();
|
544
581
|
this.dispatchEvent(new CustomEvent("exit-vr", { detail: { session: session, context: this._context } }));
|
@@ -628,16 +665,6 @@
|
|
628
665
|
|
629
666
|
|
630
667
|
|
631
|
-
|
632
|
-
|
633
|
-
function getHashFromString(str: string) {
|
634
|
-
let hash = 0;
|
635
|
-
for (let i = 0; i < str.length; i++) {
|
636
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
637
|
-
}
|
638
|
-
return hash;
|
639
|
-
}
|
640
|
-
|
641
668
|
function getDisplayName(str: string) {
|
642
669
|
const parts = str.split("/");
|
643
670
|
let name = parts[parts.length - 1];
|
@@ -27,11 +27,12 @@
|
|
27
27
|
|
28
28
|
const instances: Map<string, object[]> = new Map();
|
29
29
|
|
30
|
-
/** true during hot reload, can be used to modify behaviour in onEnable and onDisable */
|
30
|
+
/** @internal true during hot reload, can be used to modify behaviour in onEnable and onDisable */
|
31
31
|
export function isHotReloading() {
|
32
32
|
return isApplyingChanges;
|
33
33
|
}
|
34
34
|
|
35
|
+
/** @internal */
|
35
36
|
export function registerHotReloadType(instance: object) {
|
36
37
|
if (isApplyingChanges) return;
|
37
38
|
const type = instance.constructor;
|
@@ -44,6 +45,7 @@
|
|
44
45
|
}
|
45
46
|
}
|
46
47
|
|
48
|
+
/** @internal */
|
47
49
|
export function unregisterHotReloadType(instance: object) {
|
48
50
|
if (isApplyingChanges) return;
|
49
51
|
const type = instance.constructor;
|
@@ -223,9 +223,14 @@
|
|
223
223
|
* @param callback The callback to call when the event is triggered
|
224
224
|
* @param options The options for adding the event listener
|
225
225
|
*/
|
226
|
-
addEventListener(type: InputEvents | InputEventNames, callback:
|
226
|
+
addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): void {
|
227
227
|
if (!this._eventListeners[type]) this._eventListeners[type] = [];
|
228
228
|
|
229
|
+
if (!callback || typeof callback !== "function") {
|
230
|
+
console.error("Invalid call to addEventListener: callback is required and must be a function!");
|
231
|
+
return;
|
232
|
+
}
|
233
|
+
|
229
234
|
if (!options) options = {};
|
230
235
|
// create a copy of the options object to avoid the original object being modified
|
231
236
|
else options = { ...options };
|
@@ -248,8 +253,9 @@
|
|
248
253
|
* @param callback The callback to remove
|
249
254
|
* @param options The options for removing the event listener
|
250
255
|
*/
|
251
|
-
removeEventListener(type: InputEvents | InputEventNames, callback:
|
256
|
+
removeEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): void {
|
252
257
|
if (!this._eventListeners[type]) return;
|
258
|
+
if (!callback) return;
|
253
259
|
const listeners = this._eventListeners[type];
|
254
260
|
// if a specific queue is requested the callback should only be removed from that queue
|
255
261
|
if (options?.queue != undefined) {
|
@@ -17,10 +17,12 @@
|
|
17
17
|
// so we use this copy buffer
|
18
18
|
const new_scripts_buffer: any[] = [];
|
19
19
|
|
20
|
+
/** @internal */
|
20
21
|
export function hasNewScripts() {
|
21
22
|
return new_scripts_buffer.length > 0;
|
22
23
|
}
|
23
24
|
|
25
|
+
/** @internal */
|
24
26
|
export function processNewScripts(context: IContext) {
|
25
27
|
if (context.new_scripts.length <= 0) return;
|
26
28
|
if (debug)
|
@@ -162,12 +164,14 @@
|
|
162
164
|
context.new_scripts_post_setup_callbacks.length = 0;
|
163
165
|
}
|
164
166
|
|
167
|
+
/** @internal */
|
165
168
|
export function processRemoveFromScene(script: IComponent) {
|
166
169
|
if (!script) return;
|
167
170
|
script.__internalDisable(true);
|
168
171
|
removeScriptFromContext(script, script.context);
|
169
172
|
}
|
170
173
|
|
174
|
+
/** @internal */
|
171
175
|
export function processStart(context: IContext, object?: Object3D) {
|
172
176
|
// Call start on scripts
|
173
177
|
for (let i = 0; i < context.new_script_start.length; i++) {
|
@@ -199,6 +203,7 @@
|
|
199
203
|
}
|
200
204
|
|
201
205
|
|
206
|
+
/** @internal */
|
202
207
|
export function addScriptToArrays(script: any, context: IContext) {
|
203
208
|
// TODO: not sure if this is ideal - maybe we should add a map if we have many scripts?
|
204
209
|
const index = context.scripts.indexOf(script);
|
@@ -216,6 +221,7 @@
|
|
216
221
|
if (isNeedleXRSessionEventReceiver(script, "immersive-ar")) context.scripts_immersive_ar.push(script);
|
217
222
|
}
|
218
223
|
|
224
|
+
/** @internal */
|
219
225
|
export function removeScriptFromContext(script: any, context: IContext) {
|
220
226
|
removeFromArray(script, context.new_scripts);
|
221
227
|
removeFromArray(script, context.new_script_start);
|
@@ -237,6 +243,7 @@
|
|
237
243
|
if (index >= 0) array.splice(index, 1);
|
238
244
|
}
|
239
245
|
|
246
|
+
/** @internal */
|
240
247
|
export function isNeedleXRSessionEventReceiver(script: any, mode: XRSessionMode | null): script is INeedleXRSessionEventReceiver {
|
241
248
|
if (script) {
|
242
249
|
const i = script as Partial<INeedleXRSessionEventReceiver>;
|
@@ -257,6 +264,7 @@
|
|
257
264
|
}
|
258
265
|
|
259
266
|
|
267
|
+
/** @internal */
|
260
268
|
export function updateIsActive(obj?: Object3D) {
|
261
269
|
if (!obj) obj = ContextRegistry.Current.scene;
|
262
270
|
if (!obj) {
|
@@ -370,6 +378,7 @@
|
|
370
378
|
// temporyChildArrayBuffer.push(arr);
|
371
379
|
// }
|
372
380
|
|
381
|
+
/** @internal */
|
373
382
|
export function updateActiveInHierarchyWithoutEventCall(go: Object3D) {
|
374
383
|
let activeInHierarchy = true;
|
375
384
|
let current: Object3D | null = go;
|
@@ -404,6 +413,7 @@
|
|
404
413
|
const $waitingForPrewarm = Symbol("waitingForPrewarm");
|
405
414
|
const debugPrewarm = getParam("debugprewarm");
|
406
415
|
|
416
|
+
/** @internal */
|
407
417
|
export function registerPrewarmObject(obj: Object3D, context: IContext) {
|
408
418
|
if (!obj) return;
|
409
419
|
// allow objects to be marked as prewarmed in which case we dont need to register them again
|
@@ -421,7 +431,7 @@
|
|
421
431
|
let prewarmTarget: WebGLCubeRenderTarget | null = null;
|
422
432
|
let prewarmCamera: CubeCamera | null = null;
|
423
433
|
|
424
|
-
|
434
|
+
/** @internal called by the engine to remove scroll or animation hiccup when objects are rendered/compiled for the first time */
|
425
435
|
export function runPrewarm(context: IContext) {
|
426
436
|
if (!context) return;
|
427
437
|
const list = prewarmList.get(context);
|
@@ -447,6 +457,7 @@
|
|
447
457
|
}
|
448
458
|
}
|
449
459
|
|
460
|
+
/** @internal */
|
450
461
|
export function clearPrewarmList(context: IContext) {
|
451
462
|
const list = prewarmList.get(context);
|
452
463
|
if (list) {
|
@@ -45,7 +45,7 @@
|
|
45
45
|
export async function addFile(file: File, context: Context, backendUrl?: string): Promise<GLTF | null> {
|
46
46
|
|
47
47
|
const name = file.name;
|
48
|
-
if (name.endsWith(".gltf") || name.endsWith(".glb")) {
|
48
|
+
if (name.endsWith(".gltf") || name.endsWith(".glb") || file.type === "model/gltf+json" || file.type === "model/gltf-binary") {
|
49
49
|
return new Promise((resolve, _reject) => {
|
50
50
|
const reader = new FileReader()
|
51
51
|
reader.readAsArrayBuffer(file);
|
@@ -155,7 +155,7 @@
|
|
155
155
|
this.parentDepth = this.parentScope.parentDepth + 1;
|
156
156
|
}
|
157
157
|
this.scopeLabel = " ".repeat(this.parentDepth * 2) + scope;
|
158
|
-
this.showLogs = options?.logTimings ?? showProgressLogs
|
158
|
+
this.showLogs = options?.logTimings ?? !!showProgressLogs;
|
159
159
|
if (this.showLogs) console.time(this.scopeLabel);
|
160
160
|
this.onProgress = options?.onProgress;
|
161
161
|
}
|
@@ -3,22 +3,22 @@
|
|
3
3
|
|
4
4
|
|
5
5
|
const logoSvgString = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 160 187.74"><defs><linearGradient id="a" x1="89.64" y1="184.81" x2="90.48" y2="21.85" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#62d399"/><stop offset=".51" stop-color="#acd842"/><stop offset=".9" stop-color="#d7db0a"/></linearGradient><linearGradient id="b" x1="69.68" y1="178.9" x2="68.08" y2="16.77" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0ba398"/><stop offset=".5" stop-color="#4ca352"/><stop offset="1" stop-color="#76a30a"/></linearGradient><linearGradient id="c" x1="36.6" y1="152.17" x2="34.7" y2="84.19" gradientUnits="userSpaceOnUse"><stop offset=".19" stop-color="#36a382"/><stop offset=".54" stop-color="#49a459"/><stop offset="1" stop-color="#76a30b"/></linearGradient><linearGradient id="d" x1="15.82" y1="153.24" x2="18" y2="90.86" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#267880"/><stop offset=".51" stop-color="#457a5c"/><stop offset="1" stop-color="#717516"/></linearGradient><linearGradient id="e" x1="135.08" y1="135.43" x2="148.93" y2="63.47" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b0d939"/><stop offset="1" stop-color="#eadb04"/></linearGradient><linearGradient id="f" x1="-4163.25" y1="2285.12" x2="-4160.81" y2="2215.34" gradientTransform="rotate(20 4088.49 13316.712)" gradientUnits="userSpaceOnUse"><stop offset=".17" stop-color="#74af52"/><stop offset=".48" stop-color="#99be32"/><stop offset="1" stop-color="#c0c40a"/></linearGradient><symbol id="g" viewBox="0 0 160 187.74"><path style="fill:url(#a)" d="M79.32 36.98v150.76L95 174.54l6.59-156.31-22.27 18.75z"/><path style="fill:url(#b)" d="M79.32 36.98 57.05 18.23l6.59 156.31 15.68 13.2V36.98z"/><path style="fill:url(#c)" d="m25.19 104.83 8.63 49.04 12.5-14.95-2.46-56.42-18.67 22.33z"/><path style="fill:url(#d)" d="M25.19 104.83 0 90.24l16.97 53.86 16.85 9.77-8.63-49.04z"/><path style="fill:#9c3" d="M43.86 82.5 18.69 67.98 0 90.24l25.18 14.59L43.86 82.5z"/><path style="fill:url(#e)" d="m134.82 78.69-9.97 56.5 15.58-9.04L160 64.1l-25.18 14.59z"/><path style="fill:url(#f)" d="m134.82 78.69-18.68-22.33-2.86 65 11.57 13.83 9.97-56.5z"/><path style="fill:#ffe113" d="m160 64.1-18.69-22.26-25.17 14.52 18.67 22.33L160 64.1z"/><path style="fill:#f3e600" d="M101.59 18.23 79.32 0 57.05 18.23l22.27 18.75 22.27-18.75z"/></symbol></defs><use width="160" height="187.74" xlink:href="#g"/></svg>`;
|
6
|
-
const logoSvgBlob =
|
7
|
-
const logoSvgUrl =
|
6
|
+
const logoSvgBlob = btoa(logoSvgString);
|
7
|
+
const logoSvgUrl = "data:image/svg+xml;base64," + logoSvgBlob;
|
8
8
|
/** Logo Only */
|
9
9
|
export const needleLogoOnlySVG: string = logoSvgUrl;
|
10
10
|
|
11
11
|
|
12
12
|
const madeWithNeedleSvgString = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> <svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" version="1.1" viewBox="0 0 1014 282" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m665.95 132.73v44.88l-10.56-8.4c-0.8-0.64-1.2-1.44-1.2-2.4v-32.4c0-6.48-4.12-9.72-12.36-9.72-2.16 0-4.18 0.4-6.06 1.2s-3.54 1.8-4.98 3-2.56 2.5-3.36 3.9-1.2 2.7-1.2 3.9v40.92l-10.68-8.4c-0.72-0.64-1.08-1.44-1.08-2.4v-53.76l10.92 8.52c0.32 0.24 0.56 0.44 0.72 0.6s0.36 0.32 0.6 0.48c0.96-1.2 2.14-2.28 3.54-3.24s2.92-1.76 4.56-2.4 3.34-1.14 5.1-1.5 3.44-0.54 5.04-0.54c1.44 0 2.92 0.04 4.44 0.12s2.84 0.28 3.96 0.6c4.56 1.12 7.8 3.12 9.72 6s2.88 6.56 2.88 11.04z" fill-rule="nonzero"/> </g> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m732.38 146.05c0 0.88 0.02 1.5 0.06 1.86s-0.02 0.98-0.18 1.86h-7.08c-2.08 0-4.44-0.02-7.08-0.06s-5.36-0.06-8.16-0.06h-22.08c0 2.88 0.56 5.36 1.68 7.44s2.6 3.8 4.44 5.16 3.94 2.36 6.3 3 4.74 0.96 7.14 0.96c3.04 0 5.9-0.76 8.58-2.28s4.94-3.52 6.78-6c0.64 0.56 1.54 1.48 2.7 2.76s2.94 3.2 5.34 5.76c-2.8 3.36-6.22 6.02-10.26 7.98s-8.42 2.94-13.14 2.94-8.92-0.64-12.84-1.92-7.32-3.24-10.2-5.88-5.12-5.98-6.72-10.02-2.4-8.82-2.4-14.34c0-4.96 0.66-9.42 1.98-13.38s3.22-7.32 5.7-10.08 5.44-4.9 8.88-6.42 7.32-2.28 11.64-2.28c5.76 0 10.52 0.88 14.28 2.64s6.72 4.16 8.88 7.2 3.66 6.54 4.5 10.5 1.26 8.18 1.26 12.66zm-29.4-22.8c-2.16 0.16-4.16 0.72-6 1.68s-3.42 2.2-4.74 3.72-2.36 3.28-3.12 5.28-1.14 4.12-1.14 6.36h33.12c0-2-0.22-4.06-0.66-6.18s-1.3-4.02-2.58-5.7-3.1-3.02-5.46-4.02-5.5-1.38-9.42-1.14z" fill-rule="nonzero"/> </g> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m795.93 146.05c0 0.88 0.02 1.5 0.06 1.86s-0.02 0.98-0.18 1.86h-7.08c-2.08 0-4.44-0.02-7.08-0.06s-5.36-0.06-8.16-0.06h-22.08c0 2.88 0.56 5.36 1.68 7.44s2.6 3.8 4.44 5.16 3.94 2.36 6.3 3 4.74 0.96 7.14 0.96c3.04 0 5.9-0.76 8.58-2.28s4.94-3.52 6.78-6c0.64 0.56 1.54 1.48 2.7 2.76s2.94 3.2 5.34 5.76c-2.8 3.36-6.22 6.02-10.26 7.98s-8.42 2.94-13.14 2.94-8.92-0.64-12.84-1.92-7.32-3.24-10.2-5.88-5.12-5.98-6.72-10.02-2.4-8.82-2.4-14.34c0-4.96 0.66-9.42 1.98-13.38s3.22-7.32 5.7-10.08 5.44-4.9 8.88-6.42 7.32-2.28 11.64-2.28c5.76 0 10.52 0.88 14.28 2.64s6.72 4.16 8.88 7.2 3.66 6.54 4.5 10.5 1.26 8.18 1.26 12.66zm-29.4-22.8c-2.16 0.16-4.16 0.72-6 1.68s-3.42 2.2-4.74 3.72-2.36 3.28-3.12 5.28-1.14 4.12-1.14 6.36h33.12c0-2-0.22-4.06-0.66-6.18s-1.3-4.02-2.58-5.7-3.1-3.02-5.46-4.02-5.5-1.38-9.42-1.14z" fill-rule="nonzero"/> </g> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m858.57 97.21c0.64 0.48 0.96 1.16 0.96 2.04v74.88c-0.08 1.04-0.12 2.12-0.12 3.24-1.84-1.52-3.56-2.92-5.16-4.2-1.36-1.12-2.66-2.18-3.9-3.18s-2.06-1.66-2.46-1.98c-1.76 2.48-4.26 4.44-7.5 5.88s-7.02 2.16-11.34 2.16c-3.84 0-7.4-0.7-10.68-2.1s-6.14-3.44-8.58-6.12-4.34-5.94-5.7-9.78-2.04-8.16-2.04-12.96c0-4.32 0.78-8.34 2.34-12.06s3.6-6.92 6.12-9.6 5.38-4.78 8.58-6.3 6.48-2.28 9.84-2.28c2.56 0 4.82 0.22 6.78 0.66s3.68 1.06 5.16 1.86 2.78 1.74 3.9 2.82 2.16 2.22 3.12 3.42v-35.04l10.68 8.64zm-27.96 67.92c3.6 0 6.52-0.68 8.76-2.04s3.98-3.06 5.22-5.1 2.1-4.22 2.58-6.54 0.72-4.44 0.72-6.36v-1.2c0-1.12-0.22-2.7-0.66-4.74s-1.28-4.06-2.52-6.06-3-3.7-5.28-5.1-5.22-2.02-8.82-1.86c-3.44 0-6.26 0.74-8.46 2.22s-3.96 3.26-5.28 5.34-2.24 4.2-2.76 6.36-0.78 3.92-0.78 5.28c0 1.84 0.24 3.92 0.72 6.24s1.36 4.48 2.64 6.48 3.04 3.68 5.28 5.04 5.12 2.04 8.64 2.04z" fill-rule="nonzero"/> </g> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m882.81 97.09c0.64 0.48 0.96 1.12 0.96 1.92l-0.12 41.04v37.08l-10.56-8.4c-0.72-0.64-1.08-1.44-1.08-2.4v-77.88l10.8 8.64z" fill-rule="nonzero"/> </g> <g transform="matrix(1.008 0 0 1.008 -2.239 .61874)"> <path d="m950.36 146.05c0 0.88 0.02 1.5 0.06 1.86s-0.02 0.98-0.18 1.86h-7.08c-2.08 0-4.44-0.02-7.08-0.06s-5.36-0.06-8.16-0.06h-22.08c0 2.88 0.56 5.36 1.68 7.44s2.6 3.8 4.44 5.16 3.94 2.36 6.3 3 4.74 0.96 7.14 0.96c3.04 0 5.9-0.76 8.58-2.28s4.94-3.52 6.78-6c0.64 0.56 1.54 1.48 2.7 2.76s2.94 3.2 5.34 5.76c-2.8 3.36-6.22 6.02-10.26 7.98s-8.42 2.94-13.14 2.94-8.92-0.64-12.84-1.92-7.32-3.24-10.2-5.88-5.12-5.98-6.72-10.02-2.4-8.82-2.4-14.34c0-4.96 0.66-9.42 1.98-13.38s3.22-7.32 5.7-10.08 5.44-4.9 8.88-6.42 7.32-2.28 11.64-2.28c5.76 0 10.52 0.88 14.28 2.64s6.72 4.16 8.88 7.2 3.66 6.54 4.5 10.5 1.26 8.18 1.26 12.66zm-29.4-22.8c-2.16 0.16-4.16 0.72-6 1.68s-3.42 2.2-4.74 3.72-2.36 3.28-3.12 5.28-1.14 4.12-1.14 6.36h33.12c0-2-0.22-4.06-0.66-6.18s-1.3-4.02-2.58-5.7-3.1-3.02-5.46-4.02-5.5-1.38-9.42-1.14z" fill-rule="nonzero"/> </g> <g transform="matrix(1.8559 0 0 .7642 45.348 36.475)"> <g transform="translate(2.7114)"> <path d="m3.935 173.02c-0.331 0-0.497-0.402-0.497-1.207v-51.002c0-0.738 0.138-1.107 0.414-1.107h1.781c0.277 0 0.415 0.335 0.415 1.006v5.935c0 0.336 0.027 0.553 0.083 0.654 0.055 0.101 0.151-0.017 0.289-0.352 0.912-1.744 1.754-3.236 2.527-4.477 0.773-1.24 1.554-2.179 2.341-2.816s1.65-0.956 2.588-0.956c1.685 0 3.011 0.922 3.977 2.766 0.967 1.845 1.602 3.84 1.905 5.986 0.056 0.268 0.139 0.369 0.249 0.302s0.221-0.235 0.331-0.503c0.939-1.811 1.802-3.353 2.589-4.628 0.787-1.274 1.581-2.246 2.382-2.917s1.671-1.006 2.61-1.006c2.016 0 3.569 1.392 4.66 4.175 1.09 2.783 1.636 6.421 1.636 10.915v37.925c0 0.871-0.18 1.307-0.539 1.307h-1.739c-0.138 0-0.249-0.1-0.332-0.301-0.083-0.202-0.124-0.503-0.124-0.906v-36.315c0-3.555-0.338-6.321-1.015-8.3-0.676-1.978-1.76-2.967-3.251-2.967-0.884 0-1.726 0.386-2.527 1.157s-1.519 1.727-2.154 2.867-1.201 2.213-1.699 3.219c-0.248 0.469-0.421 0.905-0.517 1.308-0.097 0.402-0.145 0.972-0.145 1.71v37.221c0 0.871-0.166 1.307-0.497 1.307h-1.74c-0.166 0-0.29-0.1-0.373-0.301-0.083-0.202-0.124-0.503-0.124-0.906v-36.315c0-3.555-0.332-6.321-0.994-8.3-0.663-1.978-1.754-2.967-3.273-2.967-1.242 0-2.375 0.704-3.396 2.112-1.022 1.409-2.223 3.555-3.604 6.439v39.031c0 0.805-0.18 1.207-0.539 1.207h-1.698z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m53.642 166.28c-1.077 2.549-2.237 4.477-3.479 5.785-1.243 1.307-2.61 1.961-4.101 1.961-2.154 0-3.853-1.324-5.095-3.973-1.243-2.649-1.864-6.187-1.864-10.613 0-3.488 0.4-6.489 1.201-9.004s1.988-4.51 3.562-5.985c1.574-1.476 3.521-2.414 5.841-2.817l3.686-0.704c0.221-0.067 0.394-0.218 0.518-0.453 0.124-0.234 0.187-0.587 0.187-1.056v-2.917c0-3.89-0.504-6.975-1.512-9.255s-2.354-3.42-4.039-3.42c-1.298 0-2.472 0.72-3.521 2.162s-2.002 3.572-2.858 6.388c-0.083 0.268-0.159 0.453-0.228 0.554-0.069 0.1-0.172 0.083-0.311-0.051l-1.698-1.71c-0.083-0.134-0.138-0.285-0.166-0.453-0.027-0.167 0.014-0.452 0.125-0.855 0.856-3.353 2.009-6.052 3.459-8.098 1.449-2.045 3.224-3.068 5.322-3.068 1.74 0 3.211 0.687 4.412 2.062s2.112 3.37 2.734 5.986c0.621 2.615 0.932 5.7 0.932 9.255v35.712c0 0.536-0.035 0.888-0.104 1.056s-0.2 0.251-0.393 0.251h-1.533c-0.166 0-0.29-0.117-0.373-0.352-0.083-0.234-0.124-0.553-0.124-0.955l-0.083-5.231c-0.055-0.939-0.221-1.006-0.497-0.202zm0.456-19.314c0-1.14-0.194-1.643-0.58-1.509l-3.107 0.603c-1.436 0.202-2.686 0.638-3.749 1.308-1.063 0.671-1.953 1.543-2.671 2.616s-1.257 2.33-1.616 3.772-0.538 3.102-0.538 4.98c0 3.152 0.455 5.616 1.367 7.393 0.911 1.778 2.14 2.666 3.686 2.666 0.939 0 1.85-0.419 2.734-1.257s1.671-1.895 2.361-3.169c0.663-1.408 1.181-2.85 1.553-4.326 0.373-1.475 0.56-2.883 0.56-4.225v-8.852z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m79.034 173.02c-0.166 0-0.297-0.117-0.394-0.352-0.096-0.234-0.145-0.553-0.145-0.955v-4.628c0-0.536-0.041-0.838-0.124-0.905s-0.207 0.1-0.373 0.503c-0.276 0.67-0.69 1.593-1.242 2.766-0.553 1.174-1.271 2.23-2.154 3.169-0.884 0.939-1.961 1.408-3.231 1.408-1.74 0-3.314-0.989-4.722-2.967-1.409-1.979-2.534-4.963-3.376-8.953-0.843-3.991-1.264-8.937-1.264-14.838 0-5.701 0.415-10.68 1.243-14.939s1.988-7.595 3.479-10.009c1.492-2.415 3.204-3.622 5.137-3.622 1.436 0 2.616 0.57 3.541 1.71 0.926 1.14 1.719 2.381 2.382 3.722 0.249 0.47 0.414 0.637 0.497 0.503s0.125-0.536 0.125-1.207v-23.841c0-0.805 0.151-1.208 0.455-1.208h1.864c0.276 0 0.414 0.369 0.414 1.107v72.128c0 0.537-0.041 0.905-0.124 1.107-0.083 0.201-0.235 0.301-0.455 0.301h-1.533zm-0.621-42.049c-0.939-2.213-1.885-3.94-2.838-5.181s-2.009-1.861-3.169-1.861c-1.463 0-2.768 0.889-3.914 2.666s-2.044 4.376-2.693 7.796-0.973 7.578-0.973 12.474c0 5.097 0.338 9.272 1.015 12.524 0.676 3.253 1.567 5.651 2.672 7.193 1.104 1.543 2.305 2.314 3.603 2.314 1.188 0 2.258-0.704 3.211-2.113 0.952-1.408 1.705-3.118 2.257-5.13s0.829-3.957 0.829-5.835v-24.847z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m89.514 149.38c0 3.42 0.345 6.606 1.035 9.557 0.691 2.951 1.609 5.315 2.755 7.092s2.437 2.666 3.873 2.666c1.519 0 2.837-0.738 3.956-2.213 1.118-1.476 2.064-3.655 2.837-6.539 0.083-0.336 0.166-0.52 0.249-0.554 0.083-0.033 0.179 0.017 0.29 0.151l1.408 1.912c0.221 0.268 0.235 0.67 0.041 1.207-0.69 2.548-1.47 4.661-2.34 6.337-0.87 1.677-1.857 2.935-2.962 3.773-1.104 0.838-2.319 1.257-3.645 1.257-2.043 0-3.838-1.14-5.385-3.42-1.546-2.28-2.761-5.482-3.645-9.607-0.884-4.124-1.325-8.836-1.325-14.134 0-5.901 0.455-10.931 1.367-15.089 0.911-4.158 2.14-7.377 3.686-9.658 1.547-2.28 3.3-3.42 5.261-3.42 1.988 0 3.714 1.073 5.178 3.219 1.463 2.146 2.595 5.231 3.396 9.255s1.201 8.886 1.201 14.587c0 0.469-0.02 0.939-0.062 1.408-0.041 0.469-0.214 0.704-0.517 0.704h-16.362c-0.083 0-0.152 0.151-0.207 0.453-0.056 0.302-0.083 0.654-0.083 1.056zm13.752-6.237c0.304 0 0.497-0.1 0.58-0.302 0.083-0.201 0.124-0.57 0.124-1.106 0-3.219-0.283-6.187-0.849-8.903s-1.367-4.896-2.402-6.539c-1.036-1.643-2.272-2.464-3.708-2.464-1.629 0-2.996 0.955-4.101 2.867-1.104 1.911-1.94 4.342-2.506 7.293s-0.849 6.002-0.849 9.154h13.711z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m148.54 119.7c0.165 0 0.283 0.117 0.352 0.352s0.076 0.52 0.02 0.855l-6.254 50.902c-0.028 0.47-0.104 0.788-0.228 0.956s-0.297 0.251-0.518 0.251h-1.615c-0.442 0-0.718-0.402-0.829-1.207l-5.26-40.138c-0.111-0.604-0.201-0.905-0.27-0.905s-0.131 0.301-0.186 0.905l-5.012 40.138c-0.028 0.47-0.097 0.788-0.207 0.956-0.111 0.168-0.277 0.251-0.497 0.251h-1.74c-0.442 0-0.718-0.402-0.829-1.207l-6.503-50.801c-0.055-0.403-0.048-0.721 0.021-0.956s0.2-0.352 0.393-0.352h1.823c0.166 0 0.297 0.067 0.393 0.201 0.097 0.134 0.159 0.403 0.187 0.805l5.302 41.848c0.083 0.671 0.179 0.989 0.29 0.956 0.11-0.034 0.207-0.386 0.29-1.056l5.219-41.949c0.055-0.268 0.124-0.47 0.207-0.604s0.193-0.201 0.331-0.201h1.533c0.138 0 0.262 0.067 0.373 0.201 0.11 0.134 0.179 0.403 0.207 0.805l5.468 41.848c0.083 0.671 0.179 0.989 0.29 0.956 0.11-0.034 0.207-0.386 0.29-1.056l5.053-41.849c0.055-0.335 0.138-0.57 0.249-0.704 0.11-0.134 0.234-0.201 0.373-0.201h1.284z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m156.49 171.51c0 0.604-0.042 1.006-0.125 1.208-0.082 0.201-0.262 0.301-0.538 0.301h-1.533c-0.221 0-0.366-0.083-0.435-0.251s-0.103-0.486-0.103-0.956v-50.902c0-0.805 0.152-1.207 0.456-1.207h1.822c0.304 0 0.456 0.402 0.456 1.207v50.6zm0.165-63.979c0 1.207-0.207 1.811-0.621 1.811h-1.905c-0.221 0-0.366-0.135-0.435-0.403s-0.104-0.67-0.104-1.207v-7.847c0-1.006 0.18-1.509 0.539-1.509h1.988c0.359 0 0.538 0.47 0.538 1.409v7.746z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m168.3 124.83c-0.221 0-0.331 0.269-0.331 0.805v33.801c0 3.42 0.221 5.667 0.663 6.74 0.441 1.073 1.09 1.609 1.946 1.609h3.024c0.138 0 0.242 0.084 0.311 0.252 0.069 0.167 0.103 0.419 0.103 0.754v2.716c0 0.537-0.138 0.906-0.414 1.107-0.248 0.067-0.614 0.134-1.098 0.201-0.483 0.067-0.959 0.118-1.429 0.151-0.469 0.034-0.828 0.05-1.077 0.05-1.712 0-2.934-0.955-3.665-2.867-0.732-1.911-1.098-5.013-1.098-9.305v-35.108c0-0.604-0.124-0.906-0.373-0.906h-3.521c-0.248 0-0.373-0.268-0.373-0.804v-3.521c0-0.537 0.111-0.805 0.332-0.805h3.686c0.166 0 0.263-0.268 0.29-0.805l0.415-16.095c0-0.805 0.124-1.207 0.372-1.207h1.492c0.303 0 0.455 0.436 0.455 1.307v15.995c0 0.537 0.097 0.805 0.29 0.805h5.468c0.221 0 0.331 0.268 0.331 0.805v3.521c0 0.536-0.124 0.804-0.373 0.804h-5.426z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> <g transform="translate(2.7114)"> <path d="m179.4 173.02c-0.331 0-0.497-0.402-0.497-1.207v-72.329c0-0.738 0.138-1.107 0.414-1.107h1.782c0.276 0 0.414 0.336 0.414 1.006v27.162c0 0.335 0.034 0.536 0.103 0.603s0.159-0.033 0.27-0.302c0.994-1.81 1.898-3.319 2.713-4.526 0.814-1.208 1.629-2.113 2.444-2.717 0.814-0.603 1.691-0.905 2.63-0.905 2.182 0 3.839 1.375 4.971 4.125 1.132 2.749 1.698 6.404 1.698 10.965v37.925c0 0.871-0.166 1.307-0.497 1.307h-1.74c-0.165 0-0.29-0.1-0.373-0.301-0.082-0.202-0.124-0.503-0.124-0.906v-36.315c0-3.555-0.366-6.321-1.097-8.3-0.732-1.978-1.899-2.967-3.501-2.967-0.883 0-1.705 0.318-2.464 0.956-0.76 0.637-1.526 1.576-2.299 2.816-0.773 1.241-1.643 2.834-2.61 4.779v39.031c0 0.805-0.179 1.207-0.538 1.207h-1.699z" fill-rule="nonzero" stroke="#000" stroke-width=".7px"/> </g> </g> <g transform="matrix(.80638 0 0 .80638 452.53 65.421)" fill-rule="nonzero"> <path d="m79.32 36.98v150.76l15.68-13.2 6.59-156.31-22.27 18.75z" fill="url(#f)"/> <path d="m79.32 36.98-22.27-18.75 6.59 156.31 15.68 13.2v-150.76z" fill="url(#e)"/> <path d="m25.19 104.83 8.63 49.04 12.5-14.95-2.46-56.42-18.67 22.33z" fill="url(#d)"/> <path d="m25.19 104.83-25.19-14.59 16.97 53.86 16.85 9.77-8.63-49.04z" fill="url(#c)"/> <path d="M43.86,82.5L18.69,67.98L0,90.24L25.18,104.83L43.86,82.5Z" fill="#9c3"/> <path d="m134.82 78.69-9.97 56.5 15.58-9.04 19.57-62.05-25.18 14.59z" fill="url(#b)"/> <path d="m134.82 78.69-18.68-22.33-2.86 65 11.57 13.83 9.97-56.5z" fill="url(#a)"/> <path d="m160 64.1-18.69-22.26-25.17 14.52 18.67 22.33 25.19-14.59z" fill="#ffe113"/> <path d="M101.59,18.23L79.32,0L57.05,18.23L79.32,36.98L101.59,18.23Z" fill="#f3e600"/> </g> <defs> <linearGradient id="f" x2="1" gradientTransform="matrix(.84 -162.96 162.96 .84 89.64 184.81)" gradientUnits="userSpaceOnUse"><stop stop-color="#62d399" offset="0"/><stop stop-color="#acd842" offset=".51"/><stop stop-color="#d7db0a" offset=".9"/><stop stop-color="#d7db0a" offset="1"/></linearGradient> <linearGradient id="e" x2="1" gradientTransform="matrix(-1.6,-162.13,162.13,-1.6,69.68,178.9)" gradientUnits="userSpaceOnUse"><stop stop-color="#0ba398" offset="0"/><stop stop-color="#4ca352" offset=".5"/><stop stop-color="#76a30a" offset="1"/></linearGradient> <linearGradient id="d" x2="1" gradientTransform="matrix(-1.9,-67.98,67.98,-1.9,36.6,152.17)" gradientUnits="userSpaceOnUse"><stop stop-color="#36a382" offset="0"/><stop stop-color="#36a382" offset=".19"/><stop stop-color="#49a459" offset=".54"/><stop stop-color="#76a30b" offset="1"/></linearGradient> <linearGradient id="c" x2="1" gradientTransform="matrix(2.18,-62.38,62.38,2.18,15.82,153.24)" gradientUnits="userSpaceOnUse"><stop stop-color="#267880" offset="0"/><stop stop-color="#457a5c" offset=".51"/><stop stop-color="#717516" offset="1"/></linearGradient> <linearGradient id="b" x2="1" gradientTransform="matrix(13.85,-71.96,71.96,13.85,135.08,135.43)" gradientUnits="userSpaceOnUse"><stop stop-color="#b0d939" offset="0"/><stop stop-color="#eadb04" offset="1"/></linearGradient> <linearGradient id="a" x2="1" gradientTransform="matrix(26.159 -64.737 64.737 26.159 107.42 128.14)" gradientUnits="userSpaceOnUse"><stop stop-color="#74af52" offset="0"/><stop stop-color="#74af52" offset=".17"/><stop stop-color="#99be32" offset=".48"/><stop stop-color="#c0c40a" offset="1"/></linearGradient> </defs> </svg>`
|
13
|
-
const madeWithNeedleBlob =
|
14
|
-
const madeWithNeedleUrl =
|
13
|
+
const madeWithNeedleBlob = btoa(madeWithNeedleSvgString);
|
14
|
+
const madeWithNeedleUrl = "data:image/svg+xml;charset=utf-8;base64," + madeWithNeedleBlob;
|
15
15
|
/** Made With Needle Logo */
|
16
16
|
export const madeWithNeedleSVG: string = madeWithNeedleUrl;
|
17
17
|
|
18
18
|
|
19
19
|
const needleLogoSvgString = `<svg viewBox="0 0 509 154" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M665.95 132.73v44.88l-10.56-8.4c-.8-.64-1.2-1.44-1.2-2.4v-32.4c0-6.48-4.12-9.72-12.36-9.72-2.16 0-4.18.4-6.06 1.2-1.88.8-3.54 1.8-4.98 3-1.44 1.2-2.56 2.5-3.36 3.9-.8 1.4-1.2 2.7-1.2 3.9v40.92l-10.68-8.4c-.72-.64-1.08-1.44-1.08-2.4v-53.76l10.92 8.52c.32.24.56.44.72.6.16.16.36.32.6.48.96-1.2 2.14-2.28 3.54-3.24 1.4-.96 2.92-1.76 4.56-2.4 1.64-.64 3.34-1.14 5.1-1.5 1.76-.36 3.44-.54 5.04-.54 1.44 0 2.92.04 4.44.12 1.52.08 2.84.28 3.96.6 4.56 1.12 7.8 3.12 9.72 6 1.92 2.88 2.88 6.56 2.88 11.04ZM732.38 146.05c0 .88.02 1.5.06 1.86.04.36-.02.98-.18 1.86h-7.08c-2.08 0-4.44-.02-7.08-.06-2.64-.04-5.36-.06-8.16-.06h-22.08c0 2.88.56 5.36 1.68 7.44 1.12 2.08 2.6 3.8 4.44 5.16 1.84 1.36 3.94 2.36 6.3 3 2.36.64 4.74.96 7.14.96 3.04 0 5.9-.76 8.58-2.28 2.68-1.52 4.94-3.52 6.78-6 .64.56 1.54 1.48 2.7 2.76 1.16 1.28 2.94 3.2 5.34 5.76-2.8 3.36-6.22 6.02-10.26 7.98-4.04 1.96-8.42 2.94-13.14 2.94-4.72 0-8.92-.64-12.84-1.92-3.92-1.28-7.32-3.24-10.2-5.88-2.88-2.64-5.12-5.98-6.72-10.02-1.6-4.04-2.4-8.82-2.4-14.34 0-4.96.66-9.42 1.98-13.38 1.32-3.96 3.22-7.32 5.7-10.08s5.44-4.9 8.88-6.42c3.44-1.52 7.32-2.28 11.64-2.28 5.76 0 10.52.88 14.28 2.64 3.76 1.76 6.72 4.16 8.88 7.2 2.16 3.04 3.66 6.54 4.5 10.5.84 3.96 1.26 8.18 1.26 12.66Zm-29.4-22.8c-2.16.16-4.16.72-6 1.68-1.84.96-3.42 2.2-4.74 3.72-1.32 1.52-2.36 3.28-3.12 5.28-.76 2-1.14 4.12-1.14 6.36h33.12c0-2-.22-4.06-.66-6.18-.44-2.12-1.3-4.02-2.58-5.7-1.28-1.68-3.1-3.02-5.46-4.02-2.36-1-5.5-1.38-9.42-1.14ZM795.93 146.05c0 .88.02 1.5.06 1.86.04.36-.02.98-.18 1.86h-7.08c-2.08 0-4.44-.02-7.08-.06-2.64-.04-5.36-.06-8.16-.06h-22.08c0 2.88.56 5.36 1.68 7.44 1.12 2.08 2.6 3.8 4.44 5.16 1.84 1.36 3.94 2.36 6.3 3 2.36.64 4.74.96 7.14.96 3.04 0 5.9-.76 8.58-2.28 2.68-1.52 4.94-3.52 6.78-6 .64.56 1.54 1.48 2.7 2.76 1.16 1.28 2.94 3.2 5.34 5.76-2.8 3.36-6.22 6.02-10.26 7.98-4.04 1.96-8.42 2.94-13.14 2.94-4.72 0-8.92-.64-12.84-1.92-3.92-1.28-7.32-3.24-10.2-5.88-2.88-2.64-5.12-5.98-6.72-10.02-1.6-4.04-2.4-8.82-2.4-14.34 0-4.96.66-9.42 1.98-13.38 1.32-3.96 3.22-7.32 5.7-10.08s5.44-4.9 8.88-6.42c3.44-1.52 7.32-2.28 11.64-2.28 5.76 0 10.52.88 14.28 2.64 3.76 1.76 6.72 4.16 8.88 7.2 2.16 3.04 3.66 6.54 4.5 10.5.84 3.96 1.26 8.18 1.26 12.66Zm-29.4-22.8c-2.16.16-4.16.72-6 1.68-1.84.96-3.42 2.2-4.74 3.72-1.32 1.52-2.36 3.28-3.12 5.28-.76 2-1.14 4.12-1.14 6.36h33.12c0-2-.22-4.06-.66-6.18-.44-2.12-1.3-4.02-2.58-5.7-1.28-1.68-3.1-3.02-5.46-4.02-2.36-1-5.5-1.38-9.42-1.14ZM858.57 97.21c.64.48.96 1.16.96 2.04v74.88c-.08 1.04-.12 2.12-.12 3.24-1.84-1.52-3.56-2.92-5.16-4.2-1.36-1.12-2.66-2.18-3.9-3.18-1.24-1-2.06-1.66-2.46-1.98-1.76 2.48-4.26 4.44-7.5 5.88-3.24 1.44-7.02 2.16-11.34 2.16-3.84 0-7.4-.7-10.68-2.1-3.28-1.4-6.14-3.44-8.58-6.12-2.44-2.68-4.34-5.94-5.7-9.78-1.36-3.84-2.04-8.16-2.04-12.96 0-4.32.78-8.34 2.34-12.06 1.56-3.72 3.6-6.92 6.12-9.6 2.52-2.68 5.38-4.78 8.58-6.3 3.2-1.52 6.48-2.28 9.84-2.28 2.56 0 4.82.22 6.78.66 1.96.44 3.68 1.06 5.16 1.86s2.78 1.74 3.9 2.82a35.92 35.92 0 0 1 3.12 3.42V88.57l10.68 8.64Zm-27.96 67.92c3.6 0 6.52-.68 8.76-2.04 2.24-1.36 3.98-3.06 5.22-5.1a20.5 20.5 0 0 0 2.58-6.54c.48-2.32.72-4.44.72-6.36v-1.2c0-1.12-.22-2.7-.66-4.74-.44-2.04-1.28-4.06-2.52-6.06s-3-3.7-5.28-5.1c-2.28-1.4-5.22-2.02-8.82-1.86-3.44 0-6.26.74-8.46 2.22-2.2 1.48-3.96 3.26-5.28 5.34-1.32 2.08-2.24 4.2-2.76 6.36-.52 2.16-.78 3.92-.78 5.28 0 1.84.24 3.92.72 6.24.48 2.32 1.36 4.48 2.64 6.48s3.04 3.68 5.28 5.04c2.24 1.36 5.12 2.04 8.64 2.04ZM882.81 97.09c.64.48.96 1.12.96 1.92l-.12 41.04v37.08l-10.56-8.4c-.72-.64-1.08-1.44-1.08-2.4V88.45l10.8 8.64ZM950.36 146.05c0 .88.02 1.5.06 1.86.04.36-.02.98-.18 1.86h-7.08c-2.08 0-4.44-.02-7.08-.06-2.64-.04-5.36-.06-8.16-.06h-22.08c0 2.88.56 5.36 1.68 7.44 1.12 2.08 2.6 3.8 4.44 5.16 1.84 1.36 3.94 2.36 6.3 3 2.36.64 4.74.96 7.14.96 3.04 0 5.9-.76 8.58-2.28 2.68-1.52 4.94-3.52 6.78-6 .64.56 1.54 1.48 2.7 2.76 1.16 1.28 2.94 3.2 5.34 5.76-2.8 3.36-6.22 6.02-10.26 7.98-4.04 1.96-8.42 2.94-13.14 2.94-4.72 0-8.92-.64-12.84-1.92-3.92-1.28-7.32-3.24-10.2-5.88-2.88-2.64-5.12-5.98-6.72-10.02-1.6-4.04-2.4-8.82-2.4-14.34 0-4.96.66-9.42 1.98-13.38 1.32-3.96 3.22-7.32 5.7-10.08s5.44-4.9 8.88-6.42c3.44-1.52 7.32-2.28 11.64-2.28 5.76 0 10.52.88 14.28 2.64 3.76 1.76 6.72 4.16 8.88 7.2 2.16 3.04 3.66 6.54 4.5 10.5.84 3.96 1.26 8.18 1.26 12.66Zm-29.4-22.8c-2.16.16-4.16.72-6 1.68-1.84.96-3.42 2.2-4.74 3.72-1.32 1.52-2.36 3.28-3.12 5.28-.76 2-1.14 4.12-1.14 6.36h33.12c0-2-.22-4.06-.66-6.18-.44-2.12-1.3-4.02-2.58-5.7-1.28-1.68-3.1-3.02-5.46-4.02-2.36-1-5.5-1.38-9.42-1.14Z" style="fill-rule:nonzero" transform="translate(-452.406 -63.709) scale(1.00797)"/><path d="M79.32 36.98v150.76L95 174.54l6.59-156.31-22.27 18.75Z" style="fill:url(#a);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="M79.32 36.98 57.05 18.23l6.59 156.31 15.68 13.2V36.98Z" style="fill:url(#b);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="m25.19 104.83 8.63 49.04 12.5-14.95-2.46-56.42-18.67 22.33Z" style="fill:url(#c);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="M25.19 104.83 0 90.24l16.97 53.86 16.85 9.77-8.63-49.04Z" style="fill:url(#d);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="M43.86 82.5 18.69 67.98 0 90.24l25.18 14.59L43.86 82.5Z" style="fill:#9c3;fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="m134.82 78.69-9.97 56.5 15.58-9.04L160 64.1l-25.18 14.59Z" style="fill:url(#e);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="m134.82 78.69-18.68-22.33-2.86 65 11.57 13.83 9.97-56.5Z" style="fill:url(#f);fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="m160 64.1-18.69-22.26-25.17 14.52 18.67 22.33L160 64.1Z" style="fill:#ffe113;fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><path d="M101.59 18.23 79.32 0 57.05 18.23l22.27 18.75 22.27-18.75Z" style="fill:#f3e600;fill-rule:nonzero" transform="matrix(.80638 0 0 .80638 2.361 1.094)"/><defs><linearGradient id="a" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.84 -162.96 162.96 .84 89.64 184.81)"><stop offset="0" style="stop-color:#62d399;stop-opacity:1"/><stop offset=".51" style="stop-color:#acd842;stop-opacity:1"/><stop offset=".9" style="stop-color:#d7db0a;stop-opacity:1"/><stop offset="1" style="stop-color:#d7db0a;stop-opacity:1"/></linearGradient><linearGradient id="b" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-90.565 123.412 54.953) scale(162.14)"><stop offset="0" style="stop-color:#0ba398;stop-opacity:1"/><stop offset=".5" style="stop-color:#4ca352;stop-opacity:1"/><stop offset="1" style="stop-color:#76a30a;stop-opacity:1"/></linearGradient><linearGradient id="c" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="scale(-68) rotate(88.4 .881 -1.396)"><stop offset="0" style="stop-color:#36a382;stop-opacity:1"/><stop offset=".19" style="stop-color:#36a382;stop-opacity:1"/><stop offset=".54" style="stop-color:#49a459;stop-opacity:1"/><stop offset="1" style="stop-color:#76a30b;stop-opacity:1"/></linearGradient><linearGradient id="d" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-88 87.255 68.431) scale(62.42)"><stop offset="0" style="stop-color:#267880;stop-opacity:1"/><stop offset=".51" style="stop-color:#457a5c;stop-opacity:1"/><stop offset="1" style="stop-color:#717516;stop-opacity:1"/></linearGradient><linearGradient id="e" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-79.1 149.53 -14.065) scale(73.28)"><stop offset="0" style="stop-color:#b0d939;stop-opacity:1"/><stop offset="1" style="stop-color:#eadb04;stop-opacity:1"/></linearGradient><linearGradient id="f" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-67.997 148.705 -15.558) scale(69.8226)"><stop offset="0" style="stop-color:#74af52;stop-opacity:1"/><stop offset=".17" style="stop-color:#74af52;stop-opacity:1"/><stop offset=".48" style="stop-color:#99be32;stop-opacity:1"/><stop offset="1" style="stop-color:#c0c40a;stop-opacity:1"/></linearGradient></defs></svg>`;
|
20
|
-
const needleLogoBlob =
|
21
|
-
const needleLogoUrl =
|
20
|
+
const needleLogoBlob = btoa(needleLogoSvgString);
|
21
|
+
const needleLogoUrl = "data:image/svg+xml;charset=utf-8;base64," + needleLogoBlob;
|
22
22
|
/** Logo + Needle Typo */
|
23
23
|
export const needleLogoSVG: string = needleLogoUrl
|
24
24
|
|
@@ -407,8 +407,10 @@
|
|
407
407
|
// https://github.com/google/material-design-icons/issues/1165
|
408
408
|
ensureFonts();
|
409
409
|
// add to document head AND shadow dom to work
|
410
|
-
|
411
|
-
|
410
|
+
const fontUrl = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&display=block";
|
411
|
+
// const fontUrl = "./include/fonts/MaterialSymbolsOutlined.css"; // for offline support
|
412
|
+
loadFont(fontUrl, { loadedCallback: () => { this.handleSizeChange() } });
|
413
|
+
loadFont(fontUrl, { element: shadow });
|
412
414
|
|
413
415
|
const content = template.content.cloneNode(true) as DocumentFragment;
|
414
416
|
shadow?.appendChild(content);
|
@@ -160,6 +160,8 @@
|
|
160
160
|
screenpass.mainScene = scene;
|
161
161
|
composer.addPass(screenpass);
|
162
162
|
|
163
|
+
const automaticEffectsOrdering = true;
|
164
|
+
if (automaticEffectsOrdering) {
|
163
165
|
try {
|
164
166
|
this.orderEffects();
|
165
167
|
|
@@ -177,6 +179,11 @@
|
|
177
179
|
effects.length = 0;
|
178
180
|
composer.addPass(ef as Pass);
|
179
181
|
}
|
182
|
+
else {
|
183
|
+
// seems some effects are not correctly typed, but three can deal with them,
|
184
|
+
// so we might need to just pass them through
|
185
|
+
// composer.addPass(ef);
|
186
|
+
}
|
180
187
|
}
|
181
188
|
|
182
189
|
// create and apply uber pass
|
@@ -192,8 +199,20 @@
|
|
192
199
|
console.error("Error while applying postprocessing effects", e);
|
193
200
|
composer.removeAllPasses();
|
194
201
|
}
|
202
|
+
}
|
203
|
+
else {
|
204
|
+
for (const ef of effectsOrPasses) {
|
205
|
+
if (ef instanceof Effect)
|
206
|
+
composer.addPass(new EffectPass(cam, ef as Effect));
|
207
|
+
else if (ef instanceof Pass)
|
208
|
+
composer.addPass(ef as Pass);
|
209
|
+
else
|
210
|
+
// seems some effects are not correctly typed, but three can deal with them,
|
211
|
+
// so we just pass them through
|
212
|
+
composer.addPass(ef);
|
213
|
+
}
|
214
|
+
}
|
195
215
|
|
196
|
-
|
197
216
|
if (debug)
|
198
217
|
console.log("PostProcessing Passes", effectsOrPasses, "->", composer.passes);
|
199
218
|
}
|
@@ -120,6 +120,7 @@
|
|
120
120
|
import { ParticleSubEmitter } from "../../engine-components/ParticleSystemSubEmitter.js";
|
121
121
|
import { ParticleSystem } from "../../engine-components/ParticleSystem.js";
|
122
122
|
import { ParticleSystemRenderer } from "../../engine-components/ParticleSystem.js";
|
123
|
+
import { PhysicsExtension } from "../../engine-components/export/usdz/extensions/behavior/PhysicsExtension.js";
|
123
124
|
import { PixelationEffect } from "../../engine-components/postprocessing/Effects/Pixelation.js";
|
124
125
|
import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector.js";
|
125
126
|
import { PlayAnimationOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents.js";
|
@@ -339,6 +340,7 @@
|
|
339
340
|
TypeStore.add("ParticleSubEmitter", ParticleSubEmitter);
|
340
341
|
TypeStore.add("ParticleSystem", ParticleSystem);
|
341
342
|
TypeStore.add("ParticleSystemRenderer", ParticleSystemRenderer);
|
343
|
+
TypeStore.add("PhysicsExtension", PhysicsExtension);
|
342
344
|
TypeStore.add("PixelationEffect", PixelationEffect);
|
343
345
|
TypeStore.add("PlayableDirector", PlayableDirector);
|
344
346
|
TypeStore.add("PlayAnimationOnClick", PlayAnimationOnClick);
|
@@ -167,6 +167,9 @@
|
|
167
167
|
@serializable()
|
168
168
|
useGravity: boolean = true;
|
169
169
|
|
170
|
+
@serializable()
|
171
|
+
centerOfMass: Vector3 = new Vector3();
|
172
|
+
|
170
173
|
/**
|
171
174
|
* Constraints are used to lock the position or rotation of an object in a specific axis.
|
172
175
|
*/
|
@@ -15,7 +15,7 @@
|
|
15
15
|
static cache: { [key: string]: BufferGeometry } = {};
|
16
16
|
|
17
17
|
static getOrCreateGeometry(sprite: Sprite): BufferGeometry {
|
18
|
-
if (sprite.
|
18
|
+
if (sprite.__cached_geometry) return sprite.__cached_geometry;
|
19
19
|
if (sprite.guid) {
|
20
20
|
if (SpriteUtils.cache[sprite.guid]) {
|
21
21
|
if (debug) console.log("Take cached geometry for sprite", sprite.guid);
|
@@ -23,7 +23,7 @@
|
|
23
23
|
}
|
24
24
|
}
|
25
25
|
const geo = new BufferGeometry();
|
26
|
-
sprite.
|
26
|
+
sprite.__cached_geometry = geo;
|
27
27
|
const vertices = new Float32Array(sprite.triangles.length * 3);
|
28
28
|
const uvs = new Float32Array(sprite.triangles.length * 2);
|
29
29
|
for (let i = 0; i < sprite.triangles.length; i += 1) {
|
@@ -70,7 +70,11 @@
|
|
70
70
|
y!: number;
|
71
71
|
}
|
72
72
|
|
73
|
+
/**
|
74
|
+
* A sprite is a mesh that represents a 2D image
|
75
|
+
*/
|
73
76
|
export class Sprite {
|
77
|
+
|
74
78
|
@serializable()
|
75
79
|
guid?: string;
|
76
80
|
@serializable(Texture)
|
@@ -82,8 +86,48 @@
|
|
82
86
|
@serializeable()
|
83
87
|
vertices!: Array<Vec2>;
|
84
88
|
|
85
|
-
|
86
|
-
|
89
|
+
/** @internal */
|
90
|
+
__cached_geometry?: BufferGeometry;
|
91
|
+
|
92
|
+
/**
|
93
|
+
* The mesh that represents the sprite
|
94
|
+
*/
|
95
|
+
get mesh(): Mesh {
|
96
|
+
if (!this._mesh) {
|
97
|
+
this._mesh = new Mesh(SpriteUtils.getOrCreateGeometry(this), this.material);
|
98
|
+
}
|
99
|
+
return this._mesh;
|
100
|
+
}
|
101
|
+
private _mesh: Mesh | undefined;
|
102
|
+
|
103
|
+
/**
|
104
|
+
* The material used to render the sprite
|
105
|
+
*/
|
106
|
+
get material() {
|
107
|
+
if (!this._material) {
|
108
|
+
if (this.texture) {
|
109
|
+
this.texture.colorSpace = SRGBColorSpace;
|
110
|
+
if (this.texture.minFilter == NearestFilter && this.texture.magFilter == NearestFilter)
|
111
|
+
this.texture.anisotropy = 1;
|
112
|
+
this.texture.needsUpdate = true;
|
113
|
+
}
|
114
|
+
this._material = new MeshBasicMaterial({
|
115
|
+
map: this.texture,
|
116
|
+
color: 0xffffff,
|
117
|
+
side: DoubleSide,
|
118
|
+
transparent: true
|
119
|
+
});
|
120
|
+
}
|
121
|
+
return this._material;
|
122
|
+
}
|
123
|
+
private _material: MeshBasicMaterial | undefined;
|
124
|
+
|
125
|
+
/**
|
126
|
+
* The geometry of the sprite that can be used to create a mesh
|
127
|
+
*/
|
128
|
+
getGeometry() {
|
129
|
+
return SpriteUtils.getOrCreateGeometry(this);
|
130
|
+
}
|
87
131
|
}
|
88
132
|
|
89
133
|
const $spriteTexOwner = Symbol("spriteOwner");
|
@@ -107,20 +151,21 @@
|
|
107
151
|
const index = this.index;
|
108
152
|
if (index < 0 || index >= this.spriteSheet.sprites.length)
|
109
153
|
return;
|
110
|
-
|
111
|
-
const
|
154
|
+
|
155
|
+
const sprite = this.spriteSheet.sprites[index];
|
156
|
+
const tex = sprite?.texture;
|
112
157
|
if (!tex) return;
|
113
158
|
tex.colorSpace = SRGBColorSpace;
|
114
159
|
if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter)
|
115
160
|
tex.anisotropy = 1;
|
116
161
|
tex.needsUpdate = true;
|
117
162
|
|
118
|
-
if (!
|
119
|
-
|
163
|
+
if (!sprite["__hasLoadedProgressive"]) {
|
164
|
+
sprite["__hasLoadedProgressive"] = true;
|
120
165
|
const previousTexture = tex;
|
121
166
|
NEEDLE_progressive.assignTextureLOD(context, sourceId!, tex, 0).then(res => {
|
122
167
|
if (res instanceof Texture) {
|
123
|
-
|
168
|
+
sprite.texture = res;
|
124
169
|
const shouldUpdateInMaterial = material?.["map"] === previousTexture;
|
125
170
|
if (shouldUpdateInMaterial) {
|
126
171
|
material["map"] = res;
|
@@ -137,30 +182,42 @@
|
|
137
182
|
*/
|
138
183
|
export class SpriteRenderer extends Behaviour {
|
139
184
|
|
185
|
+
/** @internal The draw mode of the sprite renderer */
|
140
186
|
@serializable()
|
141
187
|
drawMode: SpriteDrawMode = SpriteDrawMode.Simple;
|
142
188
|
|
189
|
+
/** @internal Used when drawMode is set to Tiled */
|
143
190
|
size: Vec2 = { x: 1, y: 1 };
|
144
191
|
|
145
192
|
@serializable(RGBAColor)
|
146
193
|
color?: RGBAColor;
|
147
194
|
|
195
|
+
/**
|
196
|
+
* The material that is used to render the sprite
|
197
|
+
*/
|
148
198
|
@serializable(Material)
|
149
199
|
sharedMaterial?: Material;
|
150
200
|
|
201
|
+
// additional data
|
202
|
+
@serializable()
|
203
|
+
transparent: boolean = true;
|
204
|
+
@serializable()
|
205
|
+
cutoutThreshold: number = 0;
|
206
|
+
@serializable()
|
207
|
+
castShadows: boolean = false;
|
208
|
+
|
151
209
|
@serializable(SpriteData)
|
152
210
|
get sprite(): SpriteData | undefined {
|
153
211
|
return this._spriteSheet;
|
154
212
|
}
|
213
|
+
/**
|
214
|
+
* Set a new sprite sheetsheet or update the index of the sprite to be rendered in the currently assigned sprite sheet
|
215
|
+
*/
|
155
216
|
set sprite(value: SpriteData | undefined | number) {
|
156
217
|
if (value === this._spriteSheet) return;
|
157
218
|
if (typeof value === "number") {
|
158
219
|
const index = Math.floor(value);
|
159
|
-
// if (value === index)
|
160
220
|
this.spriteIndex = index;
|
161
|
-
// else if (debug) {
|
162
|
-
// console.log("Spritesheet framedrop", index, value);
|
163
|
-
// }
|
164
221
|
return;
|
165
222
|
}
|
166
223
|
else {
|
@@ -169,6 +226,9 @@
|
|
169
226
|
}
|
170
227
|
}
|
171
228
|
|
229
|
+
/**
|
230
|
+
* Set the index of the sprite to be rendered in the currently assigned sprite sheet
|
231
|
+
*/
|
172
232
|
set spriteIndex(value: number) {
|
173
233
|
if (!this._spriteSheet) return;
|
174
234
|
this._spriteSheet.index = value;
|
@@ -177,6 +237,9 @@
|
|
177
237
|
get spriteIndex(): number {
|
178
238
|
return this._spriteSheet?.index ?? 0;
|
179
239
|
}
|
240
|
+
/**
|
241
|
+
* Get the number of sprites in the currently assigned sprite sheet
|
242
|
+
*/
|
180
243
|
get spriteFrames(): number {
|
181
244
|
return this._spriteSheet?.spriteSheet?.sprites.length ?? 0;
|
182
245
|
}
|
@@ -184,14 +247,7 @@
|
|
184
247
|
private _spriteSheet?: SpriteData;
|
185
248
|
private _currentSprite?: Mesh;
|
186
249
|
|
187
|
-
|
188
|
-
@serializable()
|
189
|
-
transparent: boolean = true;
|
190
|
-
@serializable()
|
191
|
-
cutoutThreshold: number = 0;
|
192
|
-
@serializable()
|
193
|
-
castShadows: boolean = false;
|
194
|
-
|
250
|
+
/** @internal */
|
195
251
|
awake(): void {
|
196
252
|
this._currentSprite = undefined;
|
197
253
|
if (debug) {
|
@@ -199,6 +255,7 @@
|
|
199
255
|
}
|
200
256
|
}
|
201
257
|
|
258
|
+
/** @internal */
|
202
259
|
start() {
|
203
260
|
if (!this._currentSprite)
|
204
261
|
this.updateSprite();
|
@@ -206,7 +263,10 @@
|
|
206
263
|
this.gameObject.add(this._currentSprite);
|
207
264
|
}
|
208
265
|
|
209
|
-
|
266
|
+
/**
|
267
|
+
* Update the sprite. Modified properties will be applied to the sprite mesh. This method is called automatically when the sprite is changed.
|
268
|
+
*/
|
269
|
+
updateSprite() {
|
210
270
|
if (!this.__didAwake) return;
|
211
271
|
if (!this.sprite?.spriteSheet?.sprites) return;
|
212
272
|
const sprite = this.sprite.spriteSheet.sprites[this.spriteIndex];
|
@@ -20,15 +20,17 @@
|
|
20
20
|
}
|
21
21
|
|
22
22
|
@serializable(VolumeParameter)
|
23
|
-
mode
|
23
|
+
mode: VolumeParameter | undefined;
|
24
24
|
|
25
25
|
get isToneMapping() { return true; }
|
26
26
|
|
27
27
|
init(): void {
|
28
|
+
if (!this.mode) this.mode = new VolumeParameter(NoToneMapping);
|
28
29
|
this.mode.defaultValue = NoToneMapping;
|
29
30
|
}
|
30
31
|
|
31
32
|
apply() {
|
33
|
+
if (!this.mode) this.init();
|
32
34
|
this.mode!.onValueChanged = this._apply.bind(this);
|
33
35
|
this._apply(this.mode!.value)
|
34
36
|
}
|
@@ -14,6 +14,7 @@
|
|
14
14
|
import { AnimationExtension } from "./extensions/Animation.js"
|
15
15
|
import { AudioExtension } from "./extensions/behavior/AudioExtension.js";
|
16
16
|
import { BehaviorExtension } from "./extensions/behavior/Behaviour.js";
|
17
|
+
import { PhysicsExtension } from "./extensions/behavior/PhysicsExtension.js"
|
17
18
|
import { TextExtension } from "./extensions/USDZText.js";
|
18
19
|
import { USDZUIExtension } from "./extensions/USDZUI.js";
|
19
20
|
import { USDZExporter as ThreeUSDZExporter } from "./ThreeUSDZExporter.js";
|
@@ -66,7 +67,7 @@
|
|
66
67
|
* If this setting is off, Animators need to be registered by components – for example from PlayAnimationOnClick.
|
67
68
|
*/
|
68
69
|
@serializable()
|
69
|
-
autoExportAnimations: boolean =
|
70
|
+
autoExportAnimations: boolean = true;
|
70
71
|
|
71
72
|
/** Collect all AudioSources automatically on export and emit them as playing at the start.
|
72
73
|
* They will loop according to their settings.
|
@@ -145,6 +146,7 @@
|
|
145
146
|
if (this.interactive) {
|
146
147
|
this.extensions.push(new BehaviorExtension());
|
147
148
|
this.extensions.push(new AudioExtension());
|
149
|
+
this.extensions.push(new PhysicsExtension());
|
148
150
|
this.extensions.push(new TextExtension());
|
149
151
|
this.extensions.push(new USDZUIExtension());
|
150
152
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { isDevEnvironment } from "../../engine/debug/index.js";
|
1
|
+
import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
|
2
2
|
import { isMozillaXR } from "../../engine/engine_utils.js";
|
3
3
|
import { NeedleXRSession } from "../../engine/engine_xr.js";
|
4
4
|
import { ButtonsFactory } from "../../engine/webcomponents/buttons.js";
|
@@ -159,7 +159,10 @@
|
|
159
159
|
button.title = "Click to send this page to the Oculus Browser on your Quest";
|
160
160
|
button.addEventListener("click", () => {
|
161
161
|
const urlParameter = encodeURIComponent(window.location.href);
|
162
|
-
|
162
|
+
const url = baseUrl + urlParameter;
|
163
|
+
if (window.open(url) == null) {
|
164
|
+
showBalloonMessage("This page doesn't allow popups. Please paste " + url + " into your browser.");
|
165
|
+
}
|
163
166
|
});
|
164
167
|
this.listenToXRSessionState(button);
|
165
168
|
this.hideElementDuringXRSession(button);
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import { Object3D } from "three";
|
2
|
+
|
3
|
+
import { BoxCollider, CapsuleCollider, Collider, MeshCollider, SphereCollider } from "../../../../Collider.js";
|
4
|
+
import { GameObject } from "../../../../Component.js";
|
5
|
+
import { Rigidbody } from "../../../../RigidBody.js";
|
6
|
+
import type { IUSDExporterExtension } from "../../Extension.js";
|
7
|
+
import { USDObject, USDWriter, USDZExporterContext } from "../../ThreeUSDZExporter.js";
|
8
|
+
|
9
|
+
|
10
|
+
export class PhysicsExtension implements IUSDExporterExtension {
|
11
|
+
|
12
|
+
get extensionName(): string {
|
13
|
+
return "Physics";
|
14
|
+
}
|
15
|
+
|
16
|
+
onExportObject?(object: Object3D, model : USDObject, _context: USDZExporterContext) {
|
17
|
+
|
18
|
+
const rigidBodySources = GameObject.getComponents(object, Rigidbody).filter(c => c.enabled);
|
19
|
+
// no triggers supported for now; large trigger areas seem to break VisionOS since they're still interactable and block OS UI
|
20
|
+
const colliderSources = GameObject.getComponents(object, Collider).filter(c => c.enabled && !c.isTrigger);
|
21
|
+
let rigidBody = rigidBodySources.length > 0 ? rigidBodySources[0] : null;
|
22
|
+
const colliderSource = colliderSources.length > 0 ? colliderSources[0] : null;
|
23
|
+
|
24
|
+
// we still need a RigidBody component for the object to participate in physics;
|
25
|
+
// with only a Collider component it can only serve as trigger
|
26
|
+
// see https://developer.apple.com/documentation/realitykit/collisioncomponent#overview
|
27
|
+
let temporaryRigidbody: Rigidbody | undefined = undefined;
|
28
|
+
if (colliderSource && !rigidBody) {
|
29
|
+
rigidBody = GameObject.addComponent(object, Rigidbody);
|
30
|
+
rigidBody.isKinematic = true;
|
31
|
+
temporaryRigidbody = rigidBody;
|
32
|
+
}
|
33
|
+
|
34
|
+
if (rigidBody) {
|
35
|
+
model.addEventListener('serialize', (writer: USDWriter, _context: USDZExporterContext) => {
|
36
|
+
if (!rigidBody) return;
|
37
|
+
|
38
|
+
writer.appendLine();
|
39
|
+
writer.beginBlock(`def RealityKitComponent "RigidBody"`, "{", true );
|
40
|
+
|
41
|
+
// Gravity is enabled by default
|
42
|
+
if (!rigidBody.useGravity){
|
43
|
+
writer.appendLine(`bool gravityEnabled = 0`);
|
44
|
+
}
|
45
|
+
writer.appendLine(`uniform token info:id = "RealityKit.RigidBody"`);
|
46
|
+
|
47
|
+
if (rigidBody.isKinematic){
|
48
|
+
writer.appendLine(`token motionType = "Kinematic"`);
|
49
|
+
}
|
50
|
+
|
51
|
+
writer.beginBlock(`def RealityKitStruct "massFrame"`, "{", true );
|
52
|
+
writer.appendLine(`float m_mass = ${rigidBody.mass}`);
|
53
|
+
writer.beginBlock(`def RealityKitStruct "m_pose"`, "{", true );
|
54
|
+
/* TODO: Apple has a concept of center of mass orientation, not sure what that means here or what an equivalent mapping would be */
|
55
|
+
writer.appendLine(`float3 position = (${rigidBody.centerOfMass.x}, ${rigidBody.centerOfMass.y}, ${rigidBody.centerOfMass.z})`);
|
56
|
+
writer.closeBlock( "}" );
|
57
|
+
writer.closeBlock( "}" );
|
58
|
+
|
59
|
+
if (colliderSources.length > 0) {
|
60
|
+
const colliderSource = colliderSources[0];
|
61
|
+
writer.beginBlock(`def RealityKitStruct "material"`, "{", true );
|
62
|
+
const mat = colliderSource.sharedMaterial;
|
63
|
+
if (mat && mat.dynamicFriction !== undefined)
|
64
|
+
writer.appendLine(`double dynamicFriction = ${colliderSource.sharedMaterial?.dynamicFriction}`);
|
65
|
+
if (mat && mat.bounciness !== undefined)
|
66
|
+
writer.appendLine(`double restitution = ${colliderSource.sharedMaterial?.bounciness}`);
|
67
|
+
if (mat && mat.staticFriction !== undefined)
|
68
|
+
writer.appendLine(`double staticFriction = ${colliderSource.sharedMaterial?.staticFriction}`);
|
69
|
+
writer.closeBlock( "}" );
|
70
|
+
}
|
71
|
+
writer.closeBlock( "}" );
|
72
|
+
});
|
73
|
+
|
74
|
+
// we can remove the temporary component again here
|
75
|
+
if (temporaryRigidbody)
|
76
|
+
GameObject.removeComponent(temporaryRigidbody);
|
77
|
+
}
|
78
|
+
|
79
|
+
if (colliderSource) {
|
80
|
+
// TODO: Apple only allows one collider, Unity allows many, are many typically used on each object though? What can we do here? combine them? take the first?
|
81
|
+
model.addEventListener('serialize', (writer: USDWriter, _context: USDZExporterContext) => {
|
82
|
+
writer.beginBlock(`def RealityKitComponent "Collider"`, "{", true );
|
83
|
+
// TODO: a group is needed for collisions, not sure what this controls though
|
84
|
+
writer.appendLine(`uint group = 1`);
|
85
|
+
writer.appendLine(`uniform token info:id = "RealityKit.Collider"`);
|
86
|
+
|
87
|
+
// TODO: a mask is needed for collisions, this is the max value for int, possibly collision flags but i'm not sure what each bit means, or if we have that exported info from unity
|
88
|
+
// This is coming from RealityComposerPro
|
89
|
+
writer.appendLine(`uint mask = 4294967295`);
|
90
|
+
const isTrigger = colliderSource.isTrigger; // not currently used, see comment above
|
91
|
+
const typeName = isTrigger ? "Trigger" : "Default";
|
92
|
+
writer.appendLine(`token type = "${typeName}"`);
|
93
|
+
writer.beginBlock(`def RealityKitStruct "Shape"`, "{", true );
|
94
|
+
if (colliderSource instanceof SphereCollider){
|
95
|
+
const sphereCollider = colliderSource as SphereCollider;
|
96
|
+
writer.appendLine(`token shapeType = "Sphere"`);
|
97
|
+
writer.appendLine(`float radius = ${sphereCollider.radius}`);
|
98
|
+
}
|
99
|
+
else if (colliderSource instanceof BoxCollider){
|
100
|
+
const boxCollider = colliderSource as BoxCollider;
|
101
|
+
writer.appendLine(`token shapeType = "Box"`);
|
102
|
+
writer.appendLine(`float3 extent = (${boxCollider.size.x}, ${boxCollider.size.y}, ${boxCollider.size.z})`);
|
103
|
+
}
|
104
|
+
else if (colliderSource instanceof CapsuleCollider){
|
105
|
+
const capsuleCollider = colliderSource as CapsuleCollider;
|
106
|
+
writer.appendLine(`token shapeType = "Capsule"`);
|
107
|
+
writer.appendLine(`float radius = ${capsuleCollider.radius}`);
|
108
|
+
writer.appendLine(`float height = ${capsuleCollider.height}`);
|
109
|
+
}
|
110
|
+
else if (colliderSource instanceof MeshCollider && colliderSource.sharedMesh?.geometry) {
|
111
|
+
// get the bounds of the mesh
|
112
|
+
const geo = colliderSource.sharedMesh.geometry;
|
113
|
+
if (!geo.boundingBox) geo.computeBoundingBox();
|
114
|
+
const box = colliderSource.sharedMesh.geometry.boundingBox;
|
115
|
+
if (box) {
|
116
|
+
writer.appendLine(`token shapeType = "Box"`);
|
117
|
+
writer.appendLine(`float3 extent = (${box.max.x - box.min.x}, ${box.max.y - box.min.y}, ${box.max.z - box.min.z})`);
|
118
|
+
console.log("[USDZ]: Only Box, Sphere, and Capsule colliders are supported in visionOS/iOS. MeshCollider will be exported as Box", colliderSource);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
else {
|
122
|
+
console.warn("[USDZ]: Only Box, Sphere, and Capsule colliders are supported in visionOS/iOS. Ignoring collider:", colliderSource)
|
123
|
+
}
|
124
|
+
|
125
|
+
writer.beginBlock(`def RealityKitStruct "pose"`, "{", true );
|
126
|
+
// TODO: this is used for rotating the collider, I think this isn't necessary since all the changes happen in the parent transform from needle. Not positive about this though.
|
127
|
+
writer.closeBlock( "}" );
|
128
|
+
writer.closeBlock( "}" );
|
129
|
+
writer.closeBlock( "}" );
|
130
|
+
});
|
131
|
+
|
132
|
+
if (colliderSources.length > 1) {
|
133
|
+
console.log("WARNING: Multiple colliders detected. visionOS / iOS can only support objects with a single collider, only exporting the first collider: ", colliderSource)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|