@needle-tools/engine 3.5.4-alpha → 3.5.5-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.5.4-alpha",
3
+ "version": "3.5.5-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",
@@ -1,5 +1,5 @@
1
1
 
2
- import { resolve, join } from 'path'
2
+ import { resolve, join, isAbsolute } from 'path'
3
3
  import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
4
4
  import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
5
5
 
@@ -51,6 +51,30 @@ async function run(isBuild, config) {
51
51
  if (!existsSync(outDir)) {
52
52
  mkdirSync(outDir);
53
53
  }
54
+
55
+ // copy a list of files or directories declared in build.copy = [] in the needle.config.json
56
+ /*
57
+ "build": {
58
+ "copy": ["myFolder", "myFile.txt"]
59
+ }
60
+ */
61
+ if (needleConfig?.build?.copy) {
62
+ const arr = needleConfig.build.copy;
63
+ for (let i = 0; i < arr.length; i++) {
64
+ const entry = arr[i];
65
+ if (Array.isArray(entry)) {
66
+ console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
67
+ continue;
68
+ }
69
+ const src = resolve(baseDir, entry);
70
+ const dest = resolvePath(outDir, entry);
71
+ if (existsSync(src)) {
72
+ console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
73
+ copyRecursiveSync(src, dest);
74
+ }
75
+ }
76
+ }
77
+
54
78
  // copy assets dir
55
79
  const assetsDir = resolve(baseDir, assetsDirName);
56
80
  if (existsSync(assetsDir)) {
@@ -68,13 +92,36 @@ async function run(isBuild, config) {
68
92
  }
69
93
  }
70
94
 
95
+ /** resolves relative or absolute paths to a path inside the out directory
96
+ * for example D:/myFile.txt would resolve to outDir/myFile.txt
97
+ * wherereas "some/relative/path" would become outDir/some/relative/path
98
+ */
99
+ function resolvePath(outDir, pathValue) {
100
+ if (isAbsolute(pathValue)) {
101
+ var exists = existsSync(pathValue);
102
+ if (!exists) return null;
103
+ var stats = exists && statSync(pathValue);
104
+ if (stats.isDirectory()) {
105
+ const dirName = pathValue.replaceAll('\\', '/').split('/').pop();
106
+ return resolve(outDir, dirName);
107
+ }
108
+ const fileName = pathValue.replaceAll('\\', '/').split('/').pop();
109
+ return resolve(outDir, fileName);
110
+ }
111
+ return resolve(outDir, pathValue);
112
+ }
113
+
71
114
  function copyRecursiveSync(src, dest) {
115
+ if (dest === null) {
116
+ console.log(`[${pluginName}] - Copy ${src} to ${dest} - dest is null`)
117
+ return;
118
+ }
72
119
  var exists = existsSync(src);
73
120
  var stats = exists && statSync(src);
74
121
  var isDirectory = exists && stats.isDirectory();
75
122
  if (isDirectory) {
76
123
  if (!existsSync(dest))
77
- mkdirSync(dest);
124
+ mkdirSync(dest, { recursive: true });
78
125
  readdirSync(src).forEach(function (childItemName) {
79
126
  copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
80
127
  });
@@ -1,5 +1,6 @@
1
1
  import { loadConfig } from './config.js';
2
2
 
3
+ let didLog = false;
3
4
 
4
5
  export const needleLicense = (command, config, userSettings) => {
5
6
 
@@ -7,15 +8,27 @@ export const needleLicense = (command, config, userSettings) => {
7
8
  name: "needle-license",
8
9
  enforce: 'pre',
9
10
  async transform(src, id) {
10
- const isNeedleEngineFile = id.includes("engine/engine_license") || id.includes("needle-tools_engine.js");
11
+ const isNeedleEngineFile = id.includes("engine/engine_license") || id.includes("needle-tools_engine");
11
12
  // sometimes the actual license parameter is in a unnamed chunk file
12
13
  const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
13
14
  if (isNeedleEngineFile || isViteChunkFile) {
14
15
  const needleConfig = await loadConfig();
15
16
  if (needleConfig) {
16
17
  if (typeof needleConfig.license === "string") {
17
- src = src.replace("const NEEDLE_ENGINE_LICENSE_TYPE: string = \"\";", "const NEEDLE_ENGINE_LICENSE_TYPE: string = \"" + needleConfig.license + "\";");
18
- return { code: src, map: null }
18
+ if (!didLog) {
19
+ didLog = true;
20
+ console.log("Applying license: " + needleConfig.license);
21
+ }
22
+ const index = src.indexOf("NEEDLE_ENGINE_LICENSE_TYPE");
23
+ if (index >= 0) {
24
+ const end = src.indexOf(";", index);
25
+ if (end >= 0) {
26
+ const line = src.substring(index, end);
27
+ const replaced = "NEEDLE_ENGINE_LICENSE_TYPE = \"" + needleConfig.license + "\"";
28
+ src = src.replace(line, replaced);
29
+ return { code: src, map: null }
30
+ }
31
+ }
19
32
  }
20
33
  }
21
34
  else {
@@ -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";
@@ -215,7 +215,7 @@ import { XRGrabModel } from "../../engine-components/webxr/WebXRGrabRendering";
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);
@@ -7,7 +7,7 @@ const debug = getParam("debuglicense");
7
7
 
8
8
  // This is modified by a bundler (e.g. vite)
9
9
  // Do not edit manually
10
- const NEEDLE_ENGINE_LICENSE_TYPE: string = "";
10
+ const NEEDLE_ENGINE_LICENSE_TYPE: string = "basic";
11
11
  if (debug) console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
12
12
 
13
13
  export function hasProLicense() {
@@ -50,13 +50,13 @@ export function setWorldPositionXYZ(obj: Object3D, x: number, y: number, z: numb
50
50
  }
51
51
 
52
52
 
53
+ const _worldQuaternions = new CircularBuffer(() => new Quaternion(), 100);
53
54
  const _worldQuaternionBuffer: Quaternion = new Quaternion();
54
- const _worldQuaternion: Quaternion = new Quaternion();
55
55
  const _tempQuaternionBuffer2: Quaternion = new Quaternion();
56
56
 
57
57
  export function getWorldQuaternion(obj: Object3D, target: Quaternion | null = null): Quaternion {
58
- if (!obj) return _worldQuaternion.set(0, 0, 0, 1);
59
- const quat = target ?? _worldQuaternion;
58
+ if (!obj) return _worldQuaternions.get().identity();
59
+ const quat = target ?? _worldQuaternions.get();
60
60
  if (!obj.parent) return quat.copy(obj.quaternion);
61
61
  obj.getWorldQuaternion(quat);
62
62
  return quat;
@@ -79,14 +79,16 @@ export function setWorldQuaternionXYZW(obj: Object3D, x: number, y: number, z: n
79
79
  setWorldQuaternion(obj, _worldQuaternionBuffer);
80
80
  }
81
81
 
82
+ const _worldScaleBuffer = new CircularBuffer(() => new Vector3(), 100);
82
83
  const _worldScale: Vector3 = new Vector3();
83
- const _worldScale2: Vector3 = new Vector3();
84
84
 
85
85
  export function getWorldScale(obj: Object3D, vec: Vector3 | null = null): Vector3 {
86
- if (!obj) return _worldScale.set(0, 0, 0);
87
- if (!obj.parent) return _worldScale.copy(obj.scale);
88
- obj.getWorldScale(vec ?? _worldScale);
89
- return vec ?? _worldScale;
86
+ if (!vec)
87
+ vec = _worldScaleBuffer.get();
88
+ if (!obj) return vec.set(0, 0, 0);
89
+ if (!obj.parent) return vec.copy(obj.scale);
90
+ obj.getWorldScale(vec);
91
+ return vec;
90
92
  }
91
93
 
92
94
  export function setWorldScale(obj: Object3D, vec: Vector3) {
@@ -95,7 +97,7 @@ export function setWorldScale(obj: Object3D, vec: Vector3) {
95
97
  obj.scale.copy(vec);
96
98
  return;
97
99
  }
98
- const tempVec = _worldScale2;
100
+ const tempVec = _worldScale;
99
101
  const obj2 = obj.parent;
100
102
  obj2.getWorldScale(tempVec);
101
103
  obj.scale.copy(vec);
@@ -109,6 +111,18 @@ export function forward(obj: Object3D): Vector3 {
109
111
  return _forward.set(0, 0, 1).applyQuaternion(_forwardQuat);
110
112
  }
111
113
 
114
+ const _worldDirectionBuffer = new CircularBuffer(() => new Vector3(), 100);
115
+ const _worldDirectionQuat = new Quaternion();
116
+ /** Get the world direction. Returns world forward if nothing is passed in.
117
+ * Pass in a relative direction to get it converted to world space (e.g. dir = new Vector3(0, 1, 1))
118
+ * The returned vector will not be normalized
119
+ */
120
+ export function getWorldDirection(obj: Object3D, dir?: Vector3) {
121
+ // If no direction is passed in set the direction to the forward vector
122
+ if (!dir) dir = _worldDirectionBuffer.get().set(0, 0, 1);
123
+ getWorldQuaternion(obj, _worldDirectionQuat);
124
+ return dir.applyQuaternion(_worldDirectionQuat);
125
+ }
112
126
 
113
127
 
114
128
  const _worldEulerBuffer: Euler = new Euler();
@@ -119,14 +133,16 @@ const _worldRotation: Vector3 = new Vector3();
119
133
 
120
134
  // world euler (in radians)
121
135
  export function getWorldEuler(obj: Object3D): Euler {
122
- obj.getWorldQuaternion(_worldQuaternion);
123
- _worldEuler.setFromQuaternion(_worldQuaternion);
136
+ const quat = _worldQuaternions.get();
137
+ obj.getWorldQuaternion(quat);
138
+ _worldEuler.setFromQuaternion(quat);
124
139
  return _worldEuler;
125
140
  }
126
141
 
127
142
  // world euler (in radians)
128
143
  export function setWorldEuler(obj: Object3D, val: Euler) {
129
- setWorldQuaternion(obj, _worldQuaternion.setFromEuler(val));;
144
+ const quat = _worldQuaternions.get();
145
+ setWorldQuaternion(obj, quat.setFromEuler(val));;
130
146
  }
131
147
 
132
148
  // returns rotation in degrees
@@ -408,4 +408,38 @@ export function isSafari() {
408
408
 
409
409
  export function isQuest() {
410
410
  return navigator.userAgent.includes("OculusBrowser");
411
+ }
412
+
413
+
414
+
415
+ const cloudflareIPRegex = /ip=(?<ip>.+?)\n/s;
416
+ export async function getIpCloudflare() {
417
+ const data = await fetch('https://www.cloudflare.com/cdn-cgi/trace');
418
+ const body = await data.text();
419
+ // we are only interested in the ip= part:
420
+ const match = cloudflareIPRegex.exec(body);
421
+ if (match)
422
+ return match[1];
423
+ return null;
424
+ }
425
+
426
+ export async function getIp() {
427
+ const res = await fetch("https://api.db-ip.com/v2/free/self");
428
+ const json = await res.json();
429
+ return json.ipAddress;
430
+ }
431
+
432
+ export type IpAndLocation = {
433
+ ipAddress: string;
434
+ continentCode: string;
435
+ continentName: string;
436
+ countryCode: string;
437
+ countryName: string;
438
+ stateProv: string;
439
+ city: string;
440
+ }
441
+ export async function getIpAndLocation(): Promise<IpAndLocation> {
442
+ const res = (await fetch("https://api.db-ip.com/v2/free/self").catch(() => null))!;
443
+ const json = await res.json() as IpAndLocation;
444
+ return json;
411
445
  }
@@ -408,7 +408,7 @@ export class AnimatorController {
408
408
  }
409
409
  }
410
410
  }
411
- else if (isDevEnvironment()) {
411
+ else if (debug) {
412
412
  if (!state["__warned_no_motion"]) {
413
413
  state["__warned_no_motion"] = true;
414
414
  console.warn("No action", state.motion, this);