Needle Engine

Changes between version 3.5.3-alpha.1 and 3.5.4-alpha
Files changed (13) hide show
  1. plugins/vite/defines.js +12 -6
  2. plugins/vite/meta.js +16 -3
  3. src/engine/codegen/register_types.js +4 -4
  4. src/engine-components/codegen/components.ts +1 -1
  5. src/engine/engine_addressables.ts +2 -1
  6. src/engine/engine_constants.ts +3 -0
  7. src/engine/engine_context.ts +6 -0
  8. src/engine/engine_serialization_core.ts +1 -1
  9. src/engine-components/ui/Graphic.ts +3 -1
  10. src/needle-engine.ts +10 -1
  11. src/engine-components/ui/Text.ts +5 -4
  12. src/engine-components/export/usdz/USDZExporter.ts +19 -7
  13. plugins/vite/utils.js +11 -0
plugins/vite/defines.js CHANGED
@@ -1,19 +1,26 @@
1
1
  import { loadConfig } from "./config.js";
2
+ import { tryGetNeedleEngineVersion } from "./utils.js";
2
3
 
3
4
  /** used to pass config variables into vite.config.define
4
5
  * for example "useRapier"
5
6
  */
6
- export const needleDefines = (command, config, userSettings) => {
7
+ export const needleDefines = (command, needleEngineConfig, userSettings) => {
7
8
 
8
9
  if (!userSettings) userSettings = {};
9
10
 
10
11
  let useRapier = true;
11
- if (config.useRapier === false || userSettings?.useRapier === false) useRapier = false;
12
+ if (needleEngineConfig.useRapier === false || userSettings?.useRapier === false) useRapier = false;
12
13
 
13
14
  return {
14
15
  name: 'needle-defines',
15
16
  enforce: 'pre',
16
- config(config) {
17
+ config(viteConfig) {
18
+ if (!viteConfig.define) viteConfig.define = {};
19
+ viteConfig.define.NEEDLE_ENGINE_META = {
20
+ version: tryGetNeedleEngineVersion(),
21
+ generator: needleEngineConfig.generator,
22
+ }
23
+
17
24
  if (useRapier && userSettings?.useRapier !== true) {
18
25
  const meta = loadConfig();
19
26
  if (meta?.useRapier === false) {
@@ -21,9 +28,8 @@
21
28
  }
22
29
  }
23
30
  console.log("UseRapier?", useRapier);
24
- if (!config.define) config.define = {};
25
- if (config.define.NEEDLE_USE_RAPIER === undefined) {
26
- config.define.NEEDLE_USE_RAPIER = useRapier;
31
+ if (viteConfig.define.NEEDLE_USE_RAPIER === undefined) {
32
+ viteConfig.define.NEEDLE_USE_RAPIER = useRapier;
27
33
  }
28
34
  }
29
35
  }
plugins/vite/meta.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { loadConfig } from './config.js';
2
2
  import fs from 'fs';
3
3
  import { getPosterPath } from './poster.js';
4
+ import { tryGetNeedleEngineVersion } from './utils.js';
4
5
 
5
6
  export const needleMeta = (command, config, userSettings) => {
6
7
 
@@ -15,7 +16,7 @@
15
16
 
16
17
  return {
17
18
  // replace meta tags
18
- name: 'needle-meta-tags',
19
+ name: 'needle-meta',
19
20
  transformIndexHtml: {
20
21
  enforce: 'pre',
21
22
  transform(html, _ctx) {
@@ -91,15 +92,27 @@
91
92
  }
92
93
  }
93
94
 
94
- // if(!tags.filter(t => t.attrs?.name === "generator"))
95
- tags.push({ tag: 'meta', attrs: { name: 'generator', content: 'Needle' } });
95
+ let generator = "Needle";
96
+ if (config.generator?.length > 5) {
97
+ generator = config.generator;
98
+ }
99
+ tags.push({ tag: 'meta', attrs: { name: 'generator', content: generator } });
96
100
 
101
+ const needleEngineVersion = tryGetNeedleEngineVersion();
102
+ if (needleEngineVersion) {
103
+ if (command === "build")
104
+ console.log("Needle Engine version: " + needleEngineVersion);
105
+ tags.push({ tag: 'meta', attrs: { name: 'needle-engine', content: needleEngineVersion } });
106
+ }
107
+ else console.log("WARN: could not find needle engine package.json")
108
+
97
109
  return { html, tags }
98
110
  },
99
111
  }
100
112
  }
101
113
  }
102
114
 
115
+
103
116
  function updateUrlMetaTag(html, url) {
104
117
  html = html.replace(`<meta name="url" content="http://needle.tools">`, `<meta name="url" content="${url}">`);
105
118
  return html;
src/engine/codegen/register_types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
@@ -54,6 +54,7 @@
54
54
  import { ColorOverLifetimeModule } from "../../engine-components/ParticleSystemModules";
55
55
  import { Component } from "../../engine-components/Component";
56
56
  import { ControlTrackHandler } from "../../engine-components/timeline/TimelineTracks";
57
+ import { CustomBranding } from "../../engine-components/export/usdz/USDZExporter";
57
58
  import { Deletable } from "../../engine-components/DeleteBox";
58
59
  import { DeleteBox } from "../../engine-components/DeleteBox";
59
60
  import { DepthOfField } from "../../engine-components/postprocessing/Effects/DepthOfField";
@@ -126,7 +127,6 @@
126
127
  import { PreliminaryAction } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
127
128
  import { PreliminaryTrigger } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
128
129
  import { PresentationMode } from "../../engine-components-experimental/Presentation";
129
- import { QuickLookOverlay } from "../../engine-components/export/usdz/USDZExporter";
130
130
  import { RawImage } from "../../engine-components/ui/Image";
131
131
  import { Raycaster } from "../../engine-components/ui/Raycaster";
132
132
  import { Rect } from "../../engine-components/ui/RectTransform";
@@ -215,7 +215,7 @@
215
215
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
216
216
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
217
217
  import { XRState } from "../../engine-components/XRFlag";
218
-
218
+
219
219
  // Register types
220
220
  TypeStore.add("__Ignore", __Ignore);
221
221
  TypeStore.add("ActionBuilder", ActionBuilder);
@@ -270,6 +270,7 @@
270
270
  TypeStore.add("ColorOverLifetimeModule", ColorOverLifetimeModule);
271
271
  TypeStore.add("Component", Component);
272
272
  TypeStore.add("ControlTrackHandler", ControlTrackHandler);
273
+ TypeStore.add("CustomBranding", CustomBranding);
273
274
  TypeStore.add("Deletable", Deletable);
274
275
  TypeStore.add("DeleteBox", DeleteBox);
275
276
  TypeStore.add("DepthOfField", DepthOfField);
@@ -342,7 +343,6 @@
342
343
  TypeStore.add("PreliminaryAction", PreliminaryAction);
343
344
  TypeStore.add("PreliminaryTrigger", PreliminaryTrigger);
344
345
  TypeStore.add("PresentationMode", PresentationMode);
345
- TypeStore.add("QuickLookOverlay", QuickLookOverlay);
346
346
  TypeStore.add("RawImage", RawImage);
347
347
  TypeStore.add("Raycaster", Raycaster);
348
348
  TypeStore.add("Rect", Rect);
src/engine-components/codegen/components.ts CHANGED
@@ -52,6 +52,7 @@
52
52
  export { ColorOverLifetimeModule } from "../ParticleSystemModules";
53
53
  export { Component } from "../Component";
54
54
  export { ControlTrackHandler } from "../timeline/TimelineTracks";
55
+ export { CustomBranding } from "../export/usdz/USDZExporter";
55
56
  export { Deletable } from "../DeleteBox";
56
57
  export { DeleteBox } from "../DeleteBox";
57
58
  export { DepthOfField } from "../postprocessing/Effects/DepthOfField";
@@ -121,7 +122,6 @@
121
122
  export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
122
123
  export { PreliminaryAction } from "../export/usdz/extensions/behavior/BehaviourComponents";
123
124
  export { PreliminaryTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents";
124
- export { QuickLookOverlay } from "../export/usdz/USDZExporter";
125
125
  export { RawImage } from "../ui/Image";
126
126
  export { Raycaster } from "../ui/Raycaster";
127
127
  export { Rect } from "../ui/RectTransform";
src/engine/engine_addressables.ts CHANGED
@@ -353,8 +353,8 @@
353
353
 
354
354
 
355
355
 
356
+ const failedTexturePromise = Promise.resolve(null);
356
357
 
357
-
358
358
  export class ImageReference {
359
359
 
360
360
  private static imageReferences = new Map<string, ImageReference>();
@@ -392,6 +392,7 @@
392
392
 
393
393
  private loader: TextureLoader | null = null;
394
394
  createTexture(): Promise<Texture | null> {
395
+ if (!this.url) return failedTexturePromise;
395
396
  if (!this.loader) this.loader = new TextureLoader();
396
397
  this.loader.setCrossOrigin("anonymous");
397
398
  return this.loader.loadAsync(this.url);
src/engine/engine_constants.ts CHANGED
@@ -1,3 +1,6 @@
1
+ declare const NEEDLE_ENGINE_META: { version: string, generator: string };
2
+ export const NEEDLE_ENGINE_VERSION = NEEDLE_ENGINE_META.version;
3
+ export const NEEDLE_ENGINE_GENERATOR = NEEDLE_ENGINE_META.generator;
1
4
 
2
5
  export const activeInHierarchyFieldName = "needle_isActiveInHierarchy";
3
6
  export const builtinComponentKeyName = "builtin_components";
src/engine/engine_context.ts CHANGED
@@ -29,6 +29,7 @@
29
29
  import { destroy, foreachComponent } from './engine_gameobject';
30
30
  import { ContextEvent, ContextRegistry } from './engine_context_registry';
31
31
  import { delay } from './engine_utils';
32
+ import { NEEDLE_ENGINE_VERSION } from './engine_constants';
32
33
  // import { createCameraWithOrbitControl } from '../engine-components/CameraUtils';
33
34
 
34
35
 
@@ -93,6 +94,11 @@
93
94
 
94
95
  export class Context implements IContext {
95
96
 
97
+ /** the needle engine version */
98
+ get version() {
99
+ return NEEDLE_ENGINE_VERSION;
100
+ }
101
+
96
102
  static get Current(): Context {
97
103
  return ContextRegistry.Current as Context;
98
104
  }
src/engine/engine_serialization_core.ts CHANGED
@@ -436,7 +436,7 @@
436
436
  if (isPrimitiveType(data[key]) && !isPrimitiveType(member)) {
437
437
 
438
438
  const prop = tryFindPropertyDescriptor(member, key);
439
- if (prop?.writable === false && (prop && prop.set === undefined)) {
439
+ if (prop && (prop?.writable === undefined || prop?.writable === false) && (prop.set === undefined)) {
440
440
  if (debug)
441
441
  console.warn("Property is not writable \"" + key + "\"", member, prop, data[key], member[key]);
442
442
  continue;
src/engine-components/ui/Graphic.ts CHANGED
@@ -174,7 +174,6 @@
174
174
  static textureCache: Map<Texture, Texture> = new Map();
175
175
 
176
176
  protected async setTexture(tex: Texture | null | undefined) {
177
- if (!tex) return;
178
177
  this.setOptions({ backgroundOpacity: 0 });
179
178
  if (tex) {
180
179
  // workaround for https://github.com/needle-tools/needle-engine-support/issues/109
@@ -190,6 +189,9 @@
190
189
  }
191
190
  this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
192
191
  }
192
+ else {
193
+ this.setOptions({ backgroundImage: null, borderRadius: 0, backgroundOpacity: this.color.alpha });
194
+ }
193
195
  }
194
196
 
195
197
  protected onAfterAddedToScene(): void {
src/needle-engine.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { makeErrorsVisibleForDevelopment } from "./engine/debug/debug_overlay";
2
2
  makeErrorsVisibleForDevelopment();
3
3
 
4
+ import { NEEDLE_ENGINE_GENERATOR, NEEDLE_ENGINE_VERSION } from "./engine/engine_constants";
5
+
4
6
  import "./engine/engine_element";
5
7
  import "./engine/engine_setup";
6
8
  import "./engine-components/CameraUtils"
@@ -9,6 +11,7 @@
9
11
  export * from "./engine-components/api";
10
12
  export * from "./engine-components-experimental/api";
11
13
 
14
+
12
15
  // make accessible for external javascript
13
16
  import { Context } from "./engine/engine_setup";
14
17
  const Needle = { Context: Context };
@@ -27,6 +30,12 @@
27
30
  import * as Components from "./engine-components/codegen/components";
28
31
  registerGlobal(Components);
29
32
 
33
+ Needle["$meta"] = {
34
+ version: NEEDLE_ENGINE_VERSION,
35
+ generator: NEEDLE_ENGINE_GENERATOR
36
+ };
37
+
38
+
30
39
  import { GameObject } from "./engine-components/Component";
31
40
  for (const method of Object.getOwnPropertyNames(GameObject)) {
32
41
  switch (method) {
@@ -50,4 +59,4 @@
50
59
 
51
60
 
52
61
 
53
- import "./engine/engine_license";
62
+ import "./engine/engine_license";
src/engine-components/ui/Text.ts CHANGED
@@ -61,10 +61,11 @@
61
61
  }
62
62
 
63
63
  set text(val: string) {
64
-
65
-
66
- this._text = val;
67
- this.feedText(this.text, this.supportRichText);
64
+ if (val !== this._text) {
65
+ this._text = val;
66
+ this.feedText(this.text, this.supportRichText);
67
+ this.markDirty();
68
+ }
68
69
  }
69
70
 
70
71
  private set_text(val: string) {
src/engine-components/export/usdz/USDZExporter.ts CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  const debug = getParam("debugusdz");
20
20
 
21
- export class QuickLookOverlay {
21
+ export class CustomBranding {
22
22
  @serializable()
23
23
  callToAction?: string;
24
24
  @serializable()
@@ -45,8 +45,8 @@
45
45
  @serializable(URL)
46
46
  customUsdzFile?: string;
47
47
 
48
- @serializable(QuickLookOverlay)
49
- overlay?: QuickLookOverlay;
48
+ @serializable(CustomBranding)
49
+ customBranding?: CustomBranding;
50
50
 
51
51
  // Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
52
52
  @serializable()
@@ -66,6 +66,7 @@
66
66
  private webARSessionRoot: WebARSessionRoot | undefined;
67
67
 
68
68
  start() {
69
+ console.log(this.customUsdzFile);
69
70
  if (debug) {
70
71
  console.log(this);
71
72
  console.log("Debug USDZ, press 't' to export")
@@ -249,16 +250,27 @@
249
250
  if (debug)
250
251
  showBalloonMessage("Quicklook url: " + callToActionURL);
251
252
  if (callToActionURL) {
252
- globalThis.open(callToActionURL, "_blank");
253
+ if (!hasProLicense()) {
254
+ console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
255
+ }
256
+ else {
257
+ globalThis.open(callToActionURL, "_blank");
258
+ }
253
259
  }
254
260
  }
255
261
  }
256
262
  }
257
263
  }
258
264
 
259
- private buildQuicklookOverlay(): QuickLookOverlay {
260
- const obj: QuickLookOverlay = {};
261
- if (this.overlay) Object.assign(obj, this.overlay);
265
+ private buildQuicklookOverlay(): CustomBranding {
266
+ const obj: CustomBranding = {};
267
+ if (this.customBranding) Object.assign(obj, this.customBranding);
268
+ if (!hasProLicense()) {
269
+ console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
270
+ obj.callToAction = "Close";
271
+ obj.checkoutTitle = "🌵 Made with Needle";
272
+ obj.checkoutSubtitle = "_";
273
+ }
262
274
  if (!obj.callToAction?.length)
263
275
  obj.callToAction = "Close";
264
276
  if (!obj.checkoutTitle?.length)
plugins/vite/utils.js ADDED
@@ -0,0 +1,11 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+
3
+ export function tryGetNeedleEngineVersion() {
4
+ const needleEnginePackageJsonPath = process.cwd() + "/node_modules/@needle-tools/engine/package.json";
5
+ if (existsSync(needleEnginePackageJsonPath)) {
6
+ const json = JSON.parse(readFileSync(needleEnginePackageJsonPath));
7
+ const version = json.version;
8
+ return version;
9
+ }
10
+ return null;
11
+ }