@@ -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
|
});
|
@@ -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
|
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
|
-
|
18
|
-
|
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 @@
|
|
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);
|
@@ -408,7 +408,7 @@
|
|
408
408
|
}
|
409
409
|
}
|
410
410
|
}
|
411
|
-
else if (
|
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);
|
@@ -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() {
|
@@ -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
|
59
|
-
const quat = target ??
|
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 (!
|
87
|
-
|
88
|
-
obj.
|
89
|
-
return vec
|
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 =
|
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
|
-
|
123
|
-
|
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
|
-
|
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 @@
|
|
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
|
}
|