Needle Engine

Changes between version 3.5.4-alpha and 3.5.5-alpha
Files changed (7) hide show
  1. plugins/vite/copyfiles.js +49 -2
  2. plugins/vite/license.js +16 -3
  3. src/engine/codegen/register_types.js +2 -2
  4. src/engine-components/AnimatorController.ts +1 -1
  5. src/engine/engine_license.ts +1 -1
  6. src/engine/engine_three_utils.ts +28 -12
  7. src/engine/engine_utils.ts +34 -0
plugins/vite/copyfiles.js CHANGED
@@ -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 @@
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 @@
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
  });
plugins/vite/license.js CHANGED
@@ -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 @@
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 {
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";
@@ -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);
src/engine-components/AnimatorController.ts CHANGED
@@ -408,7 +408,7 @@
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);
src/engine/engine_license.ts CHANGED
@@ -7,7 +7,7 @@
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() {
src/engine/engine_three_utils.ts CHANGED
@@ -50,13 +50,13 @@
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 @@
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 @@
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 @@
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 @@
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
src/engine/engine_utils.ts CHANGED
@@ -408,4 +408,38 @@
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
  }