@needle-tools/engine 3.2.13-alpha → 3.2.15-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.js +8177 -8108
  3. package/dist/needle-engine.min.js +274 -288
  4. package/dist/needle-engine.umd.cjs +264 -278
  5. package/lib/engine/codegen/register_types.js +2 -0
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/engine_license.js +11 -24
  8. package/lib/engine/engine_license.js.map +1 -1
  9. package/lib/engine-components/SceneSwitcher.d.ts +1 -1
  10. package/lib/engine-components/SceneSwitcher.js +25 -1
  11. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  12. package/lib/engine-components/codegen/components.d.ts +1 -0
  13. package/lib/engine-components/codegen/components.js +1 -0
  14. package/lib/engine-components/codegen/components.js.map +1 -1
  15. package/lib/engine-components/export/usdz/USDZExporter.d.ts +1 -1
  16. package/lib/engine-components/export/usdz/USDZExporter.js +13 -6
  17. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  18. package/lib/engine-components/ui/EventSystem.js +11 -4
  19. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  20. package/lib/engine-components/utils/OpenURL.d.ts +21 -0
  21. package/lib/engine-components/utils/OpenURL.js +125 -0
  22. package/lib/engine-components/utils/OpenURL.js.map +1 -0
  23. package/lib/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +2 -2
  25. package/src/engine/codegen/register_types.js +2 -0
  26. package/src/engine/engine_license.ts +12 -25
  27. package/src/engine-components/SceneSwitcher.ts +28 -3
  28. package/src/engine-components/codegen/components.ts +1 -0
  29. package/src/engine-components/export/usdz/USDZExporter.ts +10 -6
  30. package/src/engine-components/ui/EventSystem.ts +11 -5
  31. package/src/engine-components/utils/OpenURL.ts +119 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.2.13-alpha",
3
+ "version": "3.2.15-alpha",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "type": "module",
@@ -61,7 +61,7 @@
61
61
  "postprocessing": "^6.30.1",
62
62
  "simplex-noise": "^4.0.1",
63
63
  "stats.js": "^0.17.0",
64
- "three": "npm:@needle-tools/three@^0.146.6",
64
+ "three": "npm:@needle-tools/three@^0.146.8",
65
65
  "three-mesh-ui": "^6.4.5",
66
66
  "three.quarks": "^0.7.3",
67
67
  "uuid": "^9.0.0",
@@ -95,6 +95,7 @@ import { Networking } from "../../engine-components/Networking";
95
95
  import { NoiseModule } from "../../engine-components/ParticleSystemModules";
96
96
  import { ObjectRaycaster } from "../../engine-components/ui/Raycaster";
97
97
  import { OffsetConstraint } from "../../engine-components/OffsetConstraint";
98
+ import { OpenURL } from "../../engine-components/utils/OpenURL";
98
99
  import { OrbitControls } from "../../engine-components/OrbitControls";
99
100
  import { ParticleBurst } from "../../engine-components/ParticleSystemModules";
100
101
  import { ParticleSubEmitter } from "../../engine-components/ParticleSystemSubEmitter";
@@ -284,6 +285,7 @@ TypeStore.add("Networking", Networking);
284
285
  TypeStore.add("NoiseModule", NoiseModule);
285
286
  TypeStore.add("ObjectRaycaster", ObjectRaycaster);
286
287
  TypeStore.add("OffsetConstraint", OffsetConstraint);
288
+ TypeStore.add("OpenURL", OpenURL);
287
289
  TypeStore.add("OrbitControls", OrbitControls);
288
290
  TypeStore.add("ParticleBurst", ParticleBurst);
289
291
  TypeStore.add("ParticleSubEmitter", ParticleSubEmitter);
@@ -1,4 +1,4 @@
1
- import { getParam } from "./engine_utils";
1
+ import { getParam, isMobileDevice } from "./engine_utils";
2
2
  import { ContextEvent, ContextRegistry } from "./engine_context_registry";
3
3
  import { IContext } from "./engine_types";
4
4
  import { logoSVG } from "./assets";
@@ -31,8 +31,8 @@ async function showLicenseInfo(ctx: IContext) {
31
31
 
32
32
 
33
33
  const licenseElementIdentifier = "needle-license-element";
34
- const licenseDuration = 15000;
35
- const licenseDelay = 500;
34
+ const licenseDuration = 5000;
35
+ const licenseDelay = 200;
36
36
 
37
37
  function onNonCommercialVersionDetected(ctx: IContext) {
38
38
  setTimeout(() => insertNonCommercialUseHint(ctx), 2000);
@@ -62,7 +62,8 @@ function insertNonCommercialUseHint(ctx: IContext) {
62
62
 
63
63
  const textElement = document.createElement("div");
64
64
  textElement.classList.add("text");
65
- textElement.innerHTML = "Needle Engine<br/><span class=\"non-commercial\">Non Commercial</span>";
65
+ // if (!isMobileDevice())
66
+ // textElement.innerHTML = "Needle Engine<br/><span class=\"non-commercial\">Non Commercial</span>";
66
67
  licenseElement.appendChild(textElement);
67
68
 
68
69
  licenseElement.title = "Needle Engine — non commercial version";
@@ -157,11 +158,11 @@ function createLicenseStyle() {
157
158
  animation-delay: ${licenseDelay / 1000}s;
158
159
  animation-easing: ease-in-out;
159
160
  mix-blend-mode: difference;
160
- color: rgb(40, 40, 40);
161
+ color: rgb(0, 0, 0);
161
162
  mix-blend-mode: difference;
162
163
  line-height: 1em;
163
164
  margin-left: -3px;
164
- text-shadow: 0 0 2px rgba(200,200,200, .3);
165
+ text-shadow: 0 0 2px rgba(200,200,200, .5);
165
166
  }
166
167
 
167
168
  ${selector} .text .non-commercial {
@@ -175,30 +176,16 @@ function createLicenseStyle() {
175
176
  transform: translate(0px, 10px);
176
177
  pointer-events: none;
177
178
  }
178
- 1% {
179
- transform: translate(0, -5px);
180
- opacity: 1;
181
- }
182
- 2% {
183
- transform: translate(0, 2.5px);
184
- }
185
- 3% {
179
+ 8% {
186
180
  transform: translate(0, 0px);
187
181
  pointer-events: all;
182
+ opacity: 1;
183
+ transform: scale(1.1)
188
184
  }
189
- 4% {
190
- transform: scale(1)
191
- }
192
- 4.5% {
193
- transform: scale(1.3)
194
- }
195
- 6% {
196
- transform: scale(1.32)
197
- }
198
- 7% {
185
+ 20% {
199
186
  transform: scale(1)
200
187
  }
201
- 98% {
188
+ 90% {
202
189
  opacity: 1;
203
190
  pointer-events: all;
204
191
  transform: scale(1)
@@ -186,8 +186,33 @@ export class SceneSwitcher extends Behaviour {
186
186
  return this.select(this._currentIndex - 1);
187
187
  }
188
188
 
189
- select(index: number): Promise<boolean> {
190
- if (debug) console.log("select", index)
189
+ select(index: number | string): Promise<boolean> {
190
+ if (debug) console.log("select", index);
191
+
192
+ if(typeof index === "object"){
193
+ // If a user tries to reference a scene object in a UnityEvent and invoke select(obj)
194
+ // Then the object will be serialized as a object { guid : ... } or with the index json pointer
195
+ // This case is not supported right now. Object references in the editor must not be scene references
196
+ console.warn("Switching to \"" + index + "\" might not work. Please either use an index or a AssetReference (not a scene reference)");
197
+ }
198
+
199
+ if (typeof index === "string") {
200
+ // If the parameter is a string we try to resolve the scene by its uri
201
+ // it's either already known in the scenes array
202
+ // or we create/get a new AssetReference and try to switch to that
203
+ const scene = this.scenes?.find(s => s.uri === index);
204
+ if (!scene) {
205
+ // Ok the scene is unknown to the scene switcher
206
+ // we create a new asset reference (or get an existing one)
207
+ // And switch to that. With this we can not modify the history
208
+ // Until the scene switcher can store the uri in the history instead of the index
209
+ const reference = AssetReference.getOrCreate(this.sourceId ?? "", index, this.context);
210
+ return this.switchScene(reference);
211
+ }
212
+ if (scene) index = this.scenes.indexOf(scene);
213
+ else return couldNotLoadScenePromise;
214
+ }
215
+
191
216
  if (!this.scenes?.length) return couldNotLoadScenePromise;
192
217
  if (index < 0) {
193
218
  if (this.clamp) return couldNotLoadScenePromise;
@@ -225,7 +250,7 @@ export class SceneSwitcher extends Behaviour {
225
250
  GameObject.add(scene.asset, this.gameObject);
226
251
  if (this.useSceneLighting)
227
252
  this.context.sceneLighting.enable(scene)
228
- if (this.useHistory) {
253
+ if (this.useHistory && index >= 0) {
229
254
  // save the loaded scene as an url parameter
230
255
  if (this.queryParameterName?.length)
231
256
  setParamWithoutReload(this.queryParameterName, index.toString(), this.useHistory);
@@ -93,6 +93,7 @@ export { Networking } from "../Networking";
93
93
  export { NoiseModule } from "../ParticleSystemModules";
94
94
  export { ObjectRaycaster } from "../ui/Raycaster";
95
95
  export { OffsetConstraint } from "../OffsetConstraint";
96
+ export { OpenURL } from "../utils/OpenURL";
96
97
  export { OrbitControls } from "../OrbitControls";
97
98
  export { ParticleBurst } from "../ParticleSystemModules";
98
99
  export { ParticleSubEmitter } from "../ParticleSystemSubEmitter";
@@ -1,6 +1,5 @@
1
1
  import { delay, getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils";
2
- import { Object3D, Color } from "three";
3
- import * as THREE from "three";
2
+ import { Object3D, Color, Mesh, Matrix4 } from "three";
4
3
  import { USDZExporter as ThreeUSDZExporter } from "three/examples/jsm/exporters/USDZExporter";
5
4
  import { AnimationExtension } from "./extensions/Animation"
6
5
  import { ensureQuicklookLinkIsCreated } from "./utils/quicklook";
@@ -13,6 +12,7 @@ import { serializable } from "../../../engine/engine_serialization";
13
12
  import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/debug";
14
13
  import { Context } from "../../../engine/engine_setup";
15
14
  import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
15
+ import { hasProLicense } from "../../../engine/engine_license";
16
16
 
17
17
  const debug = getParam("debugusdz");
18
18
 
@@ -37,6 +37,9 @@ export class USDZExporter extends Behaviour {
37
37
  @serializable()
38
38
  autoExportAnimations: boolean = false;
39
39
 
40
+ @serializable()
41
+ exportFileName?: string;
42
+
40
43
  @serializable(QuickLookOverlay)
41
44
  overlay?: QuickLookOverlay;
42
45
 
@@ -72,7 +75,7 @@ export class USDZExporter extends Behaviour {
72
75
  if (!this.objectToExport) this.objectToExport = this.gameObject;
73
76
 
74
77
 
75
- if (isDevEnvironment() && (!this.objectToExport || this.objectToExport.children.length <= 0)) {
78
+ if (isDevEnvironment() && (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)) {
76
79
  showBalloonWarning("USDZ Exporter has nothing to export");
77
80
  console.warn("USDZExporter has no objects to export assigned:", this)
78
81
  }
@@ -114,7 +117,7 @@ export class USDZExporter extends Behaviour {
114
117
  const scale = 1 / this.webARSessionRoot!.arScale;
115
118
  scene.matrix.makeScale(scale, scale, scale);
116
119
  if (this.webARSessionRoot.invertForward) {
117
- scene.matrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI));
120
+ scene.matrix.multiply(new Matrix4().makeRotationY(Math.PI));
118
121
  }
119
122
  }
120
123
 
@@ -131,8 +134,9 @@ export class USDZExporter extends Behaviour {
131
134
  const eventArgs = { self: this, exporter: exporter, extensions: extensions, object: this.objectToExport };
132
135
  this.dispatchEvent(new CustomEvent("before-export", { detail: eventArgs }))
133
136
 
134
- let name = "needle";
137
+ let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
135
138
  if (debug) name += "-" + getFormattedDate();
139
+ else if (!hasProLicense()) name = name + " - Made with Needle";
136
140
 
137
141
  //@ts-ignore
138
142
  exporter.debug = debug;
@@ -152,7 +156,7 @@ export class USDZExporter extends Behaviour {
152
156
 
153
157
  // see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
154
158
  const overlay = this.buildQuicklookOverlay();
155
- console.log(overlay);
159
+ if(debug) console.log(overlay);
156
160
  const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
157
161
  const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
158
162
  const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
@@ -6,14 +6,14 @@ import * as ThreeMeshUI from 'three-mesh-ui'
6
6
  import { Context } from "../../engine/engine_setup";
7
7
  import { OrbitControls } from "../OrbitControls";
8
8
  import { IPointerEventHandler, PointerEventData } from "./PointerEvents";
9
- import { Raycaster } from "./Raycaster";
9
+ import { ObjectRaycaster, Raycaster } from "./Raycaster";
10
10
  import { InputEvents } from "../../engine/engine_input";
11
11
  import { Object3D } from "three";
12
12
  import { ICanvasGroup, IGraphic } from "./Interfaces";
13
13
  import { getParam } from "../../engine/engine_utils";
14
14
  import { UIRaycastUtils } from "./RaycastUtils";
15
15
  import { $shadowDomOwner } from "./BaseUIComponent";
16
- import { showBalloonMessage, showBalloonWarning } from "../../engine/debug";
16
+ import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../engine/debug";
17
17
 
18
18
  const debug = getParam("debugeventsystem");
19
19
 
@@ -86,9 +86,15 @@ export class EventSystem extends Behaviour {
86
86
  }
87
87
 
88
88
  start() {
89
- // const res = GameObject.findObjectsOfType(Raycaster, this.context);
90
- // if (res)
91
- // this.raycaster = [...res];
89
+ if (this.raycaster.length <= 0) {
90
+ const res = GameObject.findObjectOfType(Raycaster, this.context);
91
+ if (!res) {
92
+ const rc = GameObject.addNewComponent(this.context.scene, ObjectRaycaster);
93
+ this.raycaster.push(rc);
94
+ if (isDevEnvironment())
95
+ console.warn("Added an ObjectRaycaster to the scene because no raycaster was found", this);
96
+ }
97
+ }
92
98
  }
93
99
 
94
100
  register(rc: Raycaster) {
@@ -0,0 +1,119 @@
1
+
2
+ import { IPointerClickHandler, PointerEventData } from "../ui";
3
+ import { Behaviour } from "../Component";
4
+ import { serializable } from "../../engine/engine_serialization";
5
+ import { isDevEnvironment, showBalloonMessage } from "../../engine/debug";
6
+ import { isSafari } from "../../engine/engine_utils";
7
+ import { ObjectRaycaster, Raycaster } from "../ui/Raycaster";
8
+ import { tryGetUIComponent } from "../ui/Utils";
9
+
10
+ export enum OpenURLMode {
11
+ NewTab = 0,
12
+ SameTab = 1,
13
+ NewWindow = 2
14
+ }
15
+
16
+ export class OpenURL extends Behaviour implements IPointerClickHandler {
17
+
18
+ @serializable()
19
+ clickable: boolean = true;
20
+
21
+ @serializable()
22
+ url?: string;
23
+
24
+ @serializable()
25
+ mode: OpenURLMode = OpenURLMode.NewTab;
26
+
27
+ async open() {
28
+ if (!this.url) {
29
+ console.error("URL is not set", this);
30
+ return;
31
+ }
32
+
33
+ this._validateUrl();
34
+
35
+ if (isDevEnvironment()) showBalloonMessage("Open URL: " + this.url)
36
+
37
+
38
+ switch (this.mode) {
39
+ case OpenURLMode.NewTab:
40
+ if (isSafari()) {
41
+ globalThis.open(this.url, "_blank");
42
+ }
43
+ else
44
+ globalThis.open(this.url, "_blank");
45
+ break;
46
+ case OpenURLMode.SameTab:
47
+ if (isSafari()) {
48
+ globalThis.open(this.url, "_top");
49
+ }
50
+ else globalThis.open(this.url, "_self");
51
+ break;
52
+ case OpenURLMode.NewWindow:
53
+ if (isSafari()) {
54
+ globalThis.open(this.url, "_top");
55
+ }
56
+ else globalThis.open(this.url, "_new");
57
+ break;
58
+
59
+ }
60
+ }
61
+
62
+ start(): void {
63
+ const raycaster = this.gameObject.getComponentInParent(ObjectRaycaster);
64
+ if (!raycaster) this.gameObject.addNewComponent(ObjectRaycaster);
65
+ }
66
+
67
+ onEnable(): void {
68
+ if (isSafari()) window.addEventListener("touchend", this._safariNewTabWorkaround);
69
+ }
70
+ onDisable(): void {
71
+ if (isSafari()) window.removeEventListener("touchend", this._safariNewTabWorkaround);
72
+ }
73
+
74
+ onPointerEnter(args) {
75
+ if (!args.used && this.clickable)
76
+ this.context.input.setCursorPointer();
77
+ }
78
+ onPointerExit() {
79
+ if (this.clickable)
80
+ this.context.input.setCursorNormal();
81
+ }
82
+ onPointerClick(args: PointerEventData) {
83
+ if (this.clickable && !args.used && this.url?.length)
84
+ this.open();
85
+ }
86
+
87
+ private _safariNewTabWorkaround = () => {
88
+ if (!this.clickable || !this.url?.length) return;
89
+ // we only need this workaround for opening a new tab
90
+ if (this.mode === OpenURLMode.SameTab) return;
91
+ // When we process the click directly in the browser event we can open a new tab
92
+ // by emitting a link attribute and calling onClick
93
+ const raycaster = this.gameObject.getComponentInParent(Raycaster);
94
+ if (raycaster) {
95
+ const hits = raycaster.performRaycast();
96
+ if (!hits) return;
97
+ for (const hit of hits) {
98
+ if (hit.object === this.gameObject || tryGetUIComponent(hit.object)?.gameObject === this.gameObject) {
99
+ this._validateUrl();
100
+ var a = document.createElement('a') as HTMLAnchorElement;
101
+ a.setAttribute("target", "_blank");
102
+ a.setAttribute("href", this.url);
103
+ a.click();
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ private _validateUrl() {
111
+ if (!this.url) return;
112
+ if (this.url.startsWith("www.")) {
113
+ if (isDevEnvironment()) {
114
+ console.warn("URL is not valid, adding https:// to the start of the URL", this.url);
115
+ }
116
+ this.url = "https://" + this.url;
117
+ }
118
+ }
119
+ }