@needle-tools/engine 3.4.0-alpha → 3.5.1-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/CHANGELOG.md +14 -0
- package/dist/needle-engine.js +59388 -59097
- package/dist/needle-engine.min.js +416 -391
- package/dist/needle-engine.umd.cjs +388 -363
- package/lib/engine/api.d.ts +1 -0
- package/lib/engine/api.js +1 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_context.d.ts +9 -4
- package/lib/engine/engine_context.js +57 -32
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_registry.d.ts +5 -3
- package/lib/engine/engine_context_registry.js +10 -2
- package/lib/engine/engine_context_registry.js.map +1 -1
- package/lib/engine/engine_element.js.map +1 -1
- package/lib/engine/engine_element_loading.js +2 -3
- package/lib/engine/engine_element_loading.js.map +1 -1
- package/lib/engine/engine_input.d.ts +2 -2
- package/lib/engine/engine_physics.d.ts +20 -93
- package/lib/engine/engine_physics.js +20 -892
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_physics.types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +103 -0
- package/lib/engine/engine_physics_rapier.js +1003 -0
- package/lib/engine/engine_physics_rapier.js.map +1 -0
- package/lib/engine/engine_types.d.ts +50 -1
- package/lib/engine/engine_types.js +8 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine-components/Collider.js +6 -6
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Joints.js +2 -2
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.js +16 -7
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Renderer.js +3 -4
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +0 -1
- package/lib/engine-components/RigidBody.js +24 -30
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +52 -26
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +8 -2
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +44 -7
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/ui/Canvas.js +29 -16
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Layout.js +10 -5
- package/lib/engine-components/ui/Layout.js.map +1 -1
- package/lib/engine-components/ui/RectTransform.js +8 -3
- package/lib/engine-components/ui/RectTransform.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/vite/config.js +2 -1
- package/plugins/vite/defines.js +30 -0
- package/plugins/vite/dependency-watcher.js +173 -0
- package/plugins/vite/editor-connection.js +37 -39
- package/plugins/vite/index.js +5 -1
- package/plugins/vite/reload.js +3 -1
- package/src/engine/api.ts +1 -0
- package/src/engine/codegen/register_types.js +2 -2
- package/src/engine/engine_context.ts +75 -42
- package/src/engine/engine_context_registry.ts +13 -6
- package/src/engine/engine_element.ts +2 -1
- package/src/engine/engine_element_loading.ts +2 -3
- package/src/engine/engine_input.ts +2 -2
- package/src/engine/engine_physics.ts +25 -1020
- package/src/engine/engine_physics.types.ts +1 -3
- package/src/engine/engine_physics_rapier.ts +1127 -0
- package/src/engine/engine_types.ts +66 -4
- package/src/engine-components/Collider.ts +6 -6
- package/src/engine-components/Joints.ts +2 -2
- package/src/engine-components/ReflectionProbe.ts +17 -7
- package/src/engine-components/Renderer.ts +5 -5
- package/src/engine-components/RendererLightmap.ts +1 -1
- package/src/engine-components/RigidBody.ts +24 -31
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +58 -29
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +51 -9
- package/src/engine-components/ui/Canvas.ts +29 -16
- package/src/engine-components/ui/Layout.ts +10 -5
- package/src/engine-components/ui/RectTransform.ts +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1-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",
|
package/plugins/vite/config.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
|
|
3
|
+
/** used to pass config variables into vite.config.define
|
|
4
|
+
* for example "useRapier"
|
|
5
|
+
*/
|
|
6
|
+
export const needleDefines = (command, config, userSettings) => {
|
|
7
|
+
|
|
8
|
+
if (!userSettings) userSettings = {};
|
|
9
|
+
|
|
10
|
+
let useRapier = true;
|
|
11
|
+
if (config.useRapier === false || userSettings?.useRapier === false) useRapier = false;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name: 'needle-defines',
|
|
15
|
+
enforce: 'pre',
|
|
16
|
+
config(config) {
|
|
17
|
+
if (useRapier && userSettings?.useRapier !== true) {
|
|
18
|
+
const meta = loadConfig();
|
|
19
|
+
if (meta?.useRapier === false) {
|
|
20
|
+
useRapier = false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
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;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { exec, execSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const prefix = "[needle-dependency-watcher] ";
|
|
7
|
+
function log(...msg) {
|
|
8
|
+
console.log(prefix, ...msg)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const needleDependencyWatcher = (command, config, userSettings) => {
|
|
12
|
+
if (command === "build") return;
|
|
13
|
+
|
|
14
|
+
if (userSettings?.noDependencyWatcher === true) return;
|
|
15
|
+
|
|
16
|
+
const dir = process.cwd();
|
|
17
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
18
|
+
const viteCacheDir = path.join(dir, "node_modules", ".vite");
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
name: 'needle-dependency-watcher',
|
|
22
|
+
configureServer(server) {
|
|
23
|
+
watchPackageJson(server, dir, packageJsonPath, viteCacheDir);
|
|
24
|
+
manageClients(server);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const currentClients = new Set();
|
|
30
|
+
|
|
31
|
+
function manageClients(server) {
|
|
32
|
+
server.ws.on("connection", (socket) => {
|
|
33
|
+
currentClients.add(socket);
|
|
34
|
+
socket.on("close", () => {
|
|
35
|
+
currentClients.delete(socket);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function triggerReloadOnClients() {
|
|
41
|
+
log("Triggering reload on clients (todo)", currentClients.size)
|
|
42
|
+
// for (const client of currentClients) {
|
|
43
|
+
// client.send(JSON.stringify({ type: "full-reload" }));
|
|
44
|
+
// }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
let packageJsonStat;
|
|
49
|
+
let lastEditTime;
|
|
50
|
+
let packageJsonSize;
|
|
51
|
+
let packageJson;
|
|
52
|
+
let requireInstall = false;
|
|
53
|
+
|
|
54
|
+
function watchPackageJson(server, projectDir, packageJsonPath, cachePath) {
|
|
55
|
+
|
|
56
|
+
if (!existsSync(packageJsonPath)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log("Watching project", packageJsonPath)
|
|
61
|
+
|
|
62
|
+
lastRestartTime = 0;
|
|
63
|
+
packageJsonStat = statSync(packageJsonPath);
|
|
64
|
+
lastEditTime = packageJsonStat.mtime;
|
|
65
|
+
packageJsonSize = packageJsonStat.size;
|
|
66
|
+
packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
67
|
+
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
requireInstall = testIfInstallIsRequired(projectDir, packageJson);
|
|
70
|
+
}, 1000);
|
|
71
|
+
|
|
72
|
+
setInterval(() => {
|
|
73
|
+
packageJsonStat = statSync(packageJsonPath);
|
|
74
|
+
let modified = false;
|
|
75
|
+
if (packageJsonStat.mtime > lastEditTime) {
|
|
76
|
+
modified = true;
|
|
77
|
+
}
|
|
78
|
+
if (packageJsonStat.size !== packageJsonSize) {
|
|
79
|
+
modified = true;
|
|
80
|
+
}
|
|
81
|
+
if (modified || requireInstall) {
|
|
82
|
+
if (modified)
|
|
83
|
+
log("package.json has changed")
|
|
84
|
+
|
|
85
|
+
let requireReload = false;
|
|
86
|
+
if (!requireInstall) {
|
|
87
|
+
requireInstall = testIfInstallIsRequired(projectDir, packageJson);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// test if dependencies changed
|
|
91
|
+
let newPackageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
92
|
+
for (const key in newPackageJson.dependencies) {
|
|
93
|
+
if (packageJson.dependencies[key] !== newPackageJson.dependencies[key] && newPackageJson.dependencies[key] !== undefined) {
|
|
94
|
+
log("Dependency added", key)
|
|
95
|
+
requireReload = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
packageJsonSize = packageJsonStat.size;
|
|
101
|
+
lastEditTime = packageJsonStat.mtime;
|
|
102
|
+
|
|
103
|
+
if (requireReload || requireInstall) {
|
|
104
|
+
restart(server, projectDir, cachePath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, 1000);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function testIfInstallIsRequired(projectDir, packageJson) {
|
|
111
|
+
|
|
112
|
+
if (packageJson.dependencies) {
|
|
113
|
+
for (const key in packageJson.dependencies) {
|
|
114
|
+
// make sure the dependency is installed
|
|
115
|
+
const depPath = path.join(projectDir, "node_modules", key);
|
|
116
|
+
if (!existsSync(depPath)) {
|
|
117
|
+
log("Dependency not installed", key)
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let isRunningRestart = false;
|
|
126
|
+
let restartId = 0;
|
|
127
|
+
let lastRestartTime = 0;
|
|
128
|
+
|
|
129
|
+
async function restart(server, projectDir, cachePath) {
|
|
130
|
+
|
|
131
|
+
if (isRunningRestart) return;
|
|
132
|
+
isRunningRestart = true;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const id = ++restartId;
|
|
136
|
+
|
|
137
|
+
if (requireInstall) {
|
|
138
|
+
requireInstall = false;
|
|
139
|
+
log("Installing dependencies...")
|
|
140
|
+
execSync("npm install", { cwd: projectDir, stdio: "inherit" });
|
|
141
|
+
requireInstall = false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (id !== restartId) return;
|
|
145
|
+
if (Date.now() - lastRestartTime < 1000) return;
|
|
146
|
+
log("Restarting server...")
|
|
147
|
+
lastRestartTime = Date.now();
|
|
148
|
+
requireInstall = false;
|
|
149
|
+
if (existsSync(cachePath))
|
|
150
|
+
rmSync(cachePath, { recursive: true, force: true });
|
|
151
|
+
triggerReloadOnClients();
|
|
152
|
+
|
|
153
|
+
// touch vite config to trigger reload
|
|
154
|
+
// const viteConfigPath = path.join(projectDir, "vite.config.js");
|
|
155
|
+
// if (existsSync(viteConfigPath)) {
|
|
156
|
+
// const content = readFileSync(viteConfigPath, "utf8");
|
|
157
|
+
// writeFileSync(viteConfigPath, content, "utf8");
|
|
158
|
+
// isRunningRestart = false;
|
|
159
|
+
// return;
|
|
160
|
+
// }
|
|
161
|
+
|
|
162
|
+
// check if server is running
|
|
163
|
+
if (server.httpServer.listening)
|
|
164
|
+
server.restart();
|
|
165
|
+
isRunningRestart = false;
|
|
166
|
+
console.log("-----------------------------------------------")
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
log("Error restarting server", err);
|
|
170
|
+
isRunningRestart = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
@@ -73,48 +73,46 @@ function createPlugin(isInstalled) {
|
|
|
73
73
|
},
|
|
74
74
|
|
|
75
75
|
configureServer(server) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
76
|
+
try
|
|
77
|
+
{
|
|
78
|
+
server.ws.on('connection', (socket, _request) => {
|
|
79
|
+
|
|
80
|
+
// console.log("Send editor sync status: " + isInstalled);
|
|
81
|
+
const reply = {
|
|
82
|
+
type: "needle:editor-sync:installation-status",
|
|
83
|
+
data: isInstalled
|
|
84
|
+
}
|
|
85
|
+
socket.send(JSON.stringify(reply));
|
|
86
|
+
|
|
87
|
+
socket.on('message', async (bytes) => {
|
|
88
|
+
if (bytes?.length < 50) {
|
|
89
|
+
const message = Buffer.from(bytes).toString();
|
|
90
|
+
if (message === "needle:editor:restart") {
|
|
91
|
+
console.log("Received request for a soft restart of the vite server... ")
|
|
91
92
|
// This just restarts the vite server
|
|
92
93
|
server.restart();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
}
|
|
95
|
+
else if (message === "needle:editor:stop") {
|
|
96
|
+
process.exit();
|
|
97
|
+
}
|
|
98
|
+
else if (message === `{"type":"ping"}`) {
|
|
99
|
+
socket.send(JSON.stringify({ type: "pong" }));
|
|
100
|
+
}
|
|
101
|
+
else if (message === "needle:editor:editor-sync-enabled") {
|
|
102
|
+
console.log("Editor sync enabled")
|
|
103
|
+
editorSyncEnabled = true;
|
|
104
|
+
}
|
|
105
|
+
else if (message === "needle:editor:editor-sync-disabled") {
|
|
106
|
+
editorSyncEnabled = false;
|
|
107
|
+
}
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
});
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch(err){
|
|
113
|
+
console.error("Error in needle-editor-connection")
|
|
114
|
+
console.error(err)
|
|
115
|
+
}
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
}
|
package/plugins/vite/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { needleDefines } from "./defines.js";
|
|
1
2
|
import { needleBuild } from "./build.js";
|
|
2
3
|
import { needleMeta } from "./meta.js"
|
|
3
4
|
import { needlePoster } from "./poster.js"
|
|
@@ -9,6 +10,7 @@ import { needleViteAlias } from "./alias.js";
|
|
|
9
10
|
import { needleTransformCodegen } from "./transform-codegen.js";
|
|
10
11
|
import { needleLicense } from "./license.js";
|
|
11
12
|
import { needlePeerjs } from "./peer.js";
|
|
13
|
+
import { needleDependencyWatcher } from "./dependency-watcher.js";
|
|
12
14
|
|
|
13
15
|
export * from "./gzip.js";
|
|
14
16
|
export * from "./config.js";
|
|
@@ -22,6 +24,7 @@ export const needlePlugins = async (command, config, userSettings) => {
|
|
|
22
24
|
// ensure we have user settings initialized with defaults
|
|
23
25
|
userSettings = { ...defaultUserSettings, ...userSettings }
|
|
24
26
|
const array = [
|
|
27
|
+
needleDefines(command, config, userSettings),
|
|
25
28
|
needleLicense(command, config, userSettings),
|
|
26
29
|
needleViteAlias(command, config, userSettings),
|
|
27
30
|
needleMeta(command, config, userSettings),
|
|
@@ -31,7 +34,8 @@ export const needlePlugins = async (command, config, userSettings) => {
|
|
|
31
34
|
needleCopyFiles(command, config, userSettings),
|
|
32
35
|
needleTransformCodegen(command, config, userSettings),
|
|
33
36
|
needleDrop(command, config, userSettings),
|
|
34
|
-
needlePeerjs(command, config, userSettings)
|
|
37
|
+
needlePeerjs(command, config, userSettings),
|
|
38
|
+
needleDependencyWatcher(command, config, userSettings)
|
|
35
39
|
];
|
|
36
40
|
array.push(await editorConnection(command, config, userSettings, array));
|
|
37
41
|
return array;
|
package/plugins/vite/reload.js
CHANGED
|
@@ -14,6 +14,8 @@ let assetsDirectory = "";
|
|
|
14
14
|
export const needleReload = (command, config, userSettings) => {
|
|
15
15
|
if (command === "build") return;
|
|
16
16
|
|
|
17
|
+
if (userSettings?.noReload === true) return;
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
let isUpdatingConfig = false;
|
|
19
21
|
const updateConfig = async () => {
|
|
@@ -45,7 +47,7 @@ export const needleReload = (command, config, userSettings) => {
|
|
|
45
47
|
else if (!config.server.watch.ignored) config.server.watch.ignored = [];
|
|
46
48
|
for (const pattern of ignorePatterns)
|
|
47
49
|
config.server.watch.ignored.push(pattern);
|
|
48
|
-
if(config?.debug === true || userSettings?.debug === true)
|
|
50
|
+
if (config?.debug === true || userSettings?.debug === true)
|
|
49
51
|
setTimeout(() => console.log("Updated server ignore patterns: ", config.server.watch.ignored), 100);
|
|
50
52
|
},
|
|
51
53
|
handleHotUpdate(args) {
|
package/src/engine/api.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from "./engine_patcher"
|
|
|
27
27
|
export * from "./engine_playerview"
|
|
28
28
|
export * from "./engine_physics"
|
|
29
29
|
export * from "./engine_physics.types"
|
|
30
|
+
export * from "./engine_physics_rapier"
|
|
30
31
|
export * from "./engine_scenelighting"
|
|
31
32
|
export * from "./engine_input";
|
|
32
33
|
export * from "./engine_math";
|
|
@@ -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";
|
|
@@ -214,7 +214,7 @@ import { XRGrabModel } from "../../engine-components/webxr/WebXRGrabRendering";
|
|
|
214
214
|
import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
|
|
215
215
|
import { XRRig } from "../../engine-components/webxr/WebXRRig";
|
|
216
216
|
import { XRState } from "../../engine-components/XRFlag";
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
// Register types
|
|
219
219
|
TypeStore.add("__Ignore", __Ignore);
|
|
220
220
|
TypeStore.add("ActionBuilder", ActionBuilder);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
BufferGeometry, Camera, Clock, Color, DepthTexture, Group,
|
|
3
|
+
Material, NearestFilter, NoToneMapping, Object3D, PCFSoftShadowMap,
|
|
4
|
+
PerspectiveCamera, RGBAFormat, Scene, sRGBEncoding,
|
|
5
|
+
Texture, WebGLRenderer, WebGLRenderTarget
|
|
5
6
|
} from 'three'
|
|
6
7
|
import { Input } from './engine_input';
|
|
7
8
|
import { Physics } from './engine_physics';
|
|
@@ -27,6 +28,7 @@ import { PlayerViewManager } from './engine_playerview';
|
|
|
27
28
|
import { CoroutineData, ICamera, IComponent, IContext, ILight } from "./engine_types"
|
|
28
29
|
import { destroy, foreachComponent } from './engine_gameobject';
|
|
29
30
|
import { ContextEvent, ContextRegistry } from './engine_context_registry';
|
|
31
|
+
import { delay } from './engine_utils';
|
|
30
32
|
// import { createCameraWithOrbitControl } from '../engine-components/CameraUtils';
|
|
31
33
|
|
|
32
34
|
|
|
@@ -78,7 +80,7 @@ export enum XRSessionMode {
|
|
|
78
80
|
ImmersiveAR = "immersive-ar",
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
export declare type
|
|
83
|
+
export declare type OnRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
export function registerComponent(script: IComponent, context?: Context) {
|
|
@@ -251,8 +253,8 @@ export class Context implements IContext {
|
|
|
251
253
|
this.isManagedExternally = true;
|
|
252
254
|
}
|
|
253
255
|
else {
|
|
254
|
-
this.renderer = new WebGLRenderer({
|
|
255
|
-
antialias: true
|
|
256
|
+
this.renderer = new WebGLRenderer({
|
|
257
|
+
antialias: true
|
|
256
258
|
});
|
|
257
259
|
|
|
258
260
|
// some tonemapping other than "NONE" is required for adjusting exposure with EXR environments
|
|
@@ -346,12 +348,13 @@ export class Context implements IContext {
|
|
|
346
348
|
camera.updateProjectionMatrix();
|
|
347
349
|
}
|
|
348
350
|
|
|
349
|
-
onCreate(buildScene?: (context: Context, loadingOptions?: LoadingOptions) => Promise<void>, opts?: LoadingOptions) {
|
|
351
|
+
async onCreate(buildScene?: (context: Context, loadingOptions?: LoadingOptions) => Promise<void>, opts?: LoadingOptions) {
|
|
350
352
|
if (this._isCreated) {
|
|
351
353
|
console.warn("Context already created");
|
|
352
354
|
return null;
|
|
353
355
|
}
|
|
354
356
|
this._isCreated = true;
|
|
357
|
+
await delay(1);
|
|
355
358
|
return this.internalOnCreate(buildScene, opts);
|
|
356
359
|
}
|
|
357
360
|
|
|
@@ -437,33 +440,57 @@ export class Context implements IContext {
|
|
|
437
440
|
}
|
|
438
441
|
}
|
|
439
442
|
|
|
440
|
-
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
private _onBeforeRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
446
|
+
private _onAfterRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
441
447
|
|
|
442
448
|
/** use this to subscribe to onBeforeRender events on threejs objects */
|
|
443
|
-
addBeforeRenderListener(target: Object3D, callback:
|
|
444
|
-
if (!this._onBeforeRenderListeners
|
|
445
|
-
this._onBeforeRenderListeners
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
449
|
+
addBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
450
|
+
if (!this._onBeforeRenderListeners.has(target.uuid)) {
|
|
451
|
+
this._onBeforeRenderListeners.set(target.uuid, []);
|
|
452
|
+
target.onBeforeRender = this._createRenderCallbackWrapper(target, this._onBeforeRenderListeners);
|
|
453
|
+
}
|
|
454
|
+
this._onBeforeRenderListeners.get(target.uuid)?.push(callback);
|
|
455
|
+
}
|
|
456
|
+
removeBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
457
|
+
if (this._onBeforeRenderListeners.has(target.uuid)) {
|
|
458
|
+
const arr = this._onBeforeRenderListeners.get(target.uuid)!;
|
|
459
|
+
const idx = arr.indexOf(callback);
|
|
460
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
455
461
|
}
|
|
456
|
-
this._onBeforeRenderListeners[target.uuid].push(callback);
|
|
457
462
|
}
|
|
458
463
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
464
|
+
/** use this to subscribe to onAfterRender events on threejs objects */
|
|
465
|
+
addAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
466
|
+
if (!this._onAfterRenderListeners.has(target.uuid)) {
|
|
467
|
+
this._onAfterRenderListeners.set(target.uuid, []);
|
|
468
|
+
target.onAfterRender = this._createRenderCallbackWrapper(target, this._onAfterRenderListeners);
|
|
469
|
+
}
|
|
470
|
+
this._onAfterRenderListeners.get(target.uuid)?.push(callback);
|
|
471
|
+
}
|
|
472
|
+
removeAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
473
|
+
if (this._onAfterRenderListeners.has(target.uuid)) {
|
|
474
|
+
const arr = this._onAfterRenderListeners.get(target.uuid)!;
|
|
462
475
|
const idx = arr.indexOf(callback);
|
|
463
476
|
if (idx >= 0) arr.splice(idx, 1);
|
|
464
477
|
}
|
|
465
478
|
}
|
|
466
479
|
|
|
480
|
+
|
|
481
|
+
private _createRenderCallbackWrapper(target: Object3D, array: Map<string, OnRenderCallback[]>): OnRenderCallback {
|
|
482
|
+
return (renderer, scene, camera, geometry, material, group) => {
|
|
483
|
+
const arr = array.get(target.uuid);
|
|
484
|
+
if (!arr) return;
|
|
485
|
+
for (let i = 0; i < arr.length; i++) {
|
|
486
|
+
const fn = arr[i];
|
|
487
|
+
fn(renderer, scene, camera, geometry, material, group);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
467
494
|
private _requireDepthTexture: boolean = false;
|
|
468
495
|
private _requireColorTexture: boolean = false;
|
|
469
496
|
private _renderTarget?: WebGLRenderTarget;
|
|
@@ -498,8 +525,8 @@ export class Context implements IContext {
|
|
|
498
525
|
|
|
499
526
|
private async internalOnCreate(buildScene?: (context: Context, opts?: LoadingOptions) => Promise<void>, opts?: LoadingOptions) {
|
|
500
527
|
|
|
501
|
-
|
|
502
|
-
await
|
|
528
|
+
Context.Current = this;
|
|
529
|
+
await ContextRegistry.dispatchCallback(ContextEvent.ContextCreationStart, this);
|
|
503
530
|
|
|
504
531
|
// load and create scene
|
|
505
532
|
let prepare_succeeded = true;
|
|
@@ -512,7 +539,7 @@ export class Context implements IContext {
|
|
|
512
539
|
console.error(err);
|
|
513
540
|
prepare_succeeded = false;
|
|
514
541
|
}
|
|
515
|
-
if (!prepare_succeeded) return;
|
|
542
|
+
if (!prepare_succeeded) return false;
|
|
516
543
|
|
|
517
544
|
this.internalOnUpdateVisible();
|
|
518
545
|
|
|
@@ -523,6 +550,9 @@ export class Context implements IContext {
|
|
|
523
550
|
|
|
524
551
|
Context.Current = this;
|
|
525
552
|
|
|
553
|
+
// TODO: we could configure if we need physics
|
|
554
|
+
// await this.physics.engine?.initialize();
|
|
555
|
+
|
|
526
556
|
// Setup
|
|
527
557
|
Context.Current = this;
|
|
528
558
|
for (let i = 0; i < this.new_scripts.length; i++) {
|
|
@@ -578,7 +608,7 @@ export class Context implements IContext {
|
|
|
578
608
|
else {
|
|
579
609
|
ContextRegistry.dispatchCallback(ContextEvent.MissingCamera, this);
|
|
580
610
|
if (!this.mainCamera && !this.isManagedExternally)
|
|
581
|
-
console.
|
|
611
|
+
console.warn("Missing camera in main scene", this);
|
|
582
612
|
}
|
|
583
613
|
}
|
|
584
614
|
|
|
@@ -609,13 +639,13 @@ export class Context implements IContext {
|
|
|
609
639
|
if (debug)
|
|
610
640
|
logHierarchy(this.scene, true);
|
|
611
641
|
|
|
612
|
-
ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this);
|
|
642
|
+
return ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this);
|
|
613
643
|
}
|
|
614
644
|
|
|
615
645
|
private _accumulatedTime = 0;
|
|
616
646
|
private _framerateClock = new Clock();
|
|
617
647
|
|
|
618
|
-
private render(_, frame
|
|
648
|
+
private render(_, frame: XRFrame) {
|
|
619
649
|
this._xrFrame = frame;
|
|
620
650
|
|
|
621
651
|
this._currentFrameEvent = FrameEvent.Undefined;
|
|
@@ -627,7 +657,7 @@ export class Context implements IContext {
|
|
|
627
657
|
}
|
|
628
658
|
this._accumulatedTime = 0;
|
|
629
659
|
}
|
|
630
|
-
|
|
660
|
+
|
|
631
661
|
this._stats?.begin();
|
|
632
662
|
|
|
633
663
|
Context.Current = this;
|
|
@@ -696,16 +726,19 @@ export class Context implements IContext {
|
|
|
696
726
|
this.executeCoroutines(FrameEvent.LateUpdate);
|
|
697
727
|
if (this.onHandlePaused()) return;
|
|
698
728
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
729
|
+
if (this.physics.engine) {
|
|
730
|
+
const physicsSteps = 1;
|
|
731
|
+
const dt = this.time.deltaTime / physicsSteps;
|
|
732
|
+
for (let i = 0; i < physicsSteps; i++) {
|
|
733
|
+
this._currentFrameEvent = FrameEvent.PrePhysicsStep;
|
|
734
|
+
this.executeCoroutines(FrameEvent.PrePhysicsStep);
|
|
735
|
+
this.physics.engine.step(dt);
|
|
736
|
+
this._currentFrameEvent = FrameEvent.PostPhysicsStep;
|
|
737
|
+
this.executeCoroutines(FrameEvent.PostPhysicsStep);
|
|
738
|
+
}
|
|
739
|
+
this.physics.engine.postStep();
|
|
707
740
|
}
|
|
708
|
-
|
|
741
|
+
|
|
709
742
|
if (this.onHandlePaused()) return;
|
|
710
743
|
|
|
711
744
|
if (this.isVisibleToUser) {
|
|
@@ -781,7 +814,7 @@ export class Context implements IContext {
|
|
|
781
814
|
this.renderRequiredTextures();
|
|
782
815
|
// if (camera === this.mainCameraComponent?.cam) {
|
|
783
816
|
// if (this.mainCameraComponent.activeTexture) {
|
|
784
|
-
|
|
817
|
+
|
|
785
818
|
// }
|
|
786
819
|
// }
|
|
787
820
|
if (this.composer && !this.isInXR) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { IContext } from "./engine_types";
|
|
1
|
+
import { IComponent, IContext } from "./engine_types";
|
|
2
2
|
|
|
3
3
|
export enum ContextEvent {
|
|
4
4
|
/** called when the context is registered to the registry, the context is not fully initialized at this point */
|
|
5
5
|
ContextRegistered = "ContextRegistered",
|
|
6
|
+
/** called before the first glb is loaded, can be used to initialize physics engine, is awaited */
|
|
7
|
+
ContextCreationStart = "ContextCreationStart",
|
|
6
8
|
ContextCreated = "ContextCreated",
|
|
7
9
|
ContextDestroyed = "ContextDestroyed",
|
|
8
10
|
MissingCamera = "MissingCamera",
|
|
@@ -13,11 +15,11 @@ export type ContextEventArgs = {
|
|
|
13
15
|
context: IContext;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export type ContextCallback = (evt: ContextEventArgs) => void;
|
|
18
|
+
export type ContextCallback = (evt: ContextEventArgs) => void | Promise<any> | IComponent;
|
|
17
19
|
|
|
18
20
|
export class ContextRegistry {
|
|
19
21
|
|
|
20
|
-
static get Current(): IContext{
|
|
22
|
+
static get Current(): IContext {
|
|
21
23
|
return globalThis["NeedleEngine.Context.Current"]
|
|
22
24
|
}
|
|
23
25
|
static set Current(ctx: IContext) {
|
|
@@ -51,10 +53,15 @@ export class ContextRegistry {
|
|
|
51
53
|
this._callbacks[evt].splice(index, 1);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
static dispatchCallback(evt: ContextEvent, context:IContext) {
|
|
55
|
-
if (!this._callbacks[evt]) return;
|
|
56
|
+
static dispatchCallback(evt: ContextEvent, context: IContext) {
|
|
57
|
+
if (!this._callbacks[evt]) return true;
|
|
56
58
|
const args = { event: evt, context }
|
|
57
|
-
|
|
59
|
+
const promises = new Array<Promise<any>>();
|
|
60
|
+
this._callbacks[evt].forEach(cb => {
|
|
61
|
+
const res = cb(args)
|
|
62
|
+
if (res instanceof Promise) promises.push(res);
|
|
63
|
+
});
|
|
64
|
+
return Promise.all(promises);
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
static addContextCreatedCallback(callback: ContextCallback) {
|