@needle-tools/engine 5.0.3 → 5.1.0-alpha.1
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 +31 -4
- package/README.md +6 -7
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-BXk8jYW3.js → needle-engine.bundle-BGyKqxBH.js} +12394 -11786
- package/dist/needle-engine.bundle-CiYtOO2O.min.js +1732 -0
- package/dist/needle-engine.bundle-DzVx9Z8D.umd.cjs +1732 -0
- package/dist/needle-engine.d.ts +660 -63
- package/dist/needle-engine.js +579 -566
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-vHLk8sXu.js → vendor-CAcsI0eU.js} +116 -115
- package/dist/{vendor-CntUvmJu.umd.cjs → vendor-CEM38hLE.umd.cjs} +2 -2
- package/dist/{vendor-DPbfJJ4d.min.js → vendor-HRlxIBga.min.js} +2 -2
- package/lib/engine/api.d.ts +2 -0
- package/lib/engine/api.js +2 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_addressables.js +5 -1
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_animation.d.ts +14 -7
- package/lib/engine/engine_animation.js +49 -9
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_components.js +33 -4
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +7 -2
- package/lib/engine/engine_context.js +10 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_gameobject.d.ts +4 -0
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_init.js +4 -0
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.js +4 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.js +0 -19
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +11 -8
- package/lib/engine/engine_networking.js +43 -26
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking_instantiate.d.ts +100 -5
- package/lib/engine/engine_networking_instantiate.js +150 -16
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_prefabs.d.ts +59 -0
- package/lib/engine/engine_networking_prefabs.js +67 -0
- package/lib/engine/engine_networking_prefabs.js.map +1 -0
- package/lib/engine/engine_physics_rapier.d.ts +3 -0
- package/lib/engine/engine_physics_rapier.js +13 -9
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/postprocessing/api.d.ts +2 -0
- package/lib/engine/postprocessing/api.js +2 -0
- package/lib/engine/postprocessing/api.js.map +1 -0
- package/lib/engine/postprocessing/index.d.ts +2 -0
- package/lib/engine/postprocessing/index.js +2 -0
- package/lib/engine/postprocessing/index.js.map +1 -0
- package/lib/engine/postprocessing/postprocessing.d.ts +83 -0
- package/lib/engine/postprocessing/postprocessing.js +280 -0
- package/lib/engine/postprocessing/postprocessing.js.map +1 -0
- package/lib/engine/postprocessing/types.d.ts +39 -0
- package/lib/engine/postprocessing/types.js +2 -0
- package/lib/engine/postprocessing/types.js.map +1 -0
- package/lib/engine/webcomponents/WebXRButtons.js +17 -3
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +2 -0
- package/lib/engine/xr/NeedleXRSession.js +47 -14
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/events.d.ts +30 -3
- package/lib/engine/xr/events.js +38 -0
- package/lib/engine/xr/events.js.map +1 -1
- package/lib/engine/xr/init.d.ts +4 -0
- package/lib/engine/xr/init.js +43 -0
- package/lib/engine/xr/init.js.map +1 -0
- package/lib/engine-components/AnimationUtils.d.ts +4 -1
- package/lib/engine-components/AnimationUtils.js +7 -19
- package/lib/engine-components/AnimationUtils.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +135 -2
- package/lib/engine-components/AnimatorController.js +216 -13
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/SeeThrough.d.ts +0 -2
- package/lib/engine-components/SeeThrough.js +0 -89
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/SyncedRoom.d.ts +4 -0
- package/lib/engine-components/SyncedRoom.js +23 -8
- package/lib/engine-components/SyncedRoom.js.map +1 -1
- package/lib/engine-components/SyncedTransform.js +5 -5
- package/lib/engine-components/SyncedTransform.js.map +1 -1
- package/lib/engine-components/Voip.d.ts +46 -0
- package/lib/engine-components/Voip.js +126 -2
- package/lib/engine-components/Voip.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +5 -2
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js +11 -18
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +3 -4
- package/lib/engine-components/postprocessing/PostProcessingEffect.js +6 -15
- package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +2 -1
- package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
- package/lib/engine-components/postprocessing/Volume.d.ts +18 -11
- package/lib/engine-components/postprocessing/Volume.js +61 -140
- package/lib/engine-components/postprocessing/Volume.js.map +1 -1
- package/lib/engine-components/postprocessing/index.d.ts +1 -0
- package/lib/engine-components/postprocessing/index.js +1 -0
- package/lib/engine-components/postprocessing/index.js.map +1 -1
- package/lib/engine-components/postprocessing/utils.d.ts +2 -0
- package/lib/engine-components/postprocessing/utils.js +2 -0
- package/lib/engine-components/postprocessing/utils.js.map +1 -1
- package/lib/engine-components/ui/Canvas.js +2 -2
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Graphic.d.ts +3 -3
- package/lib/engine-components/ui/Graphic.js +6 -2
- package/lib/engine-components/ui/Graphic.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +64 -11
- package/lib/engine-components/ui/Text.js +154 -45
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/engine-components/ui/index.d.ts +1 -0
- package/lib/engine-components/ui/index.js +1 -0
- package/lib/engine-components/ui/index.js.map +1 -1
- package/lib/engine-components-experimental/networking/PlayerSync.d.ts +25 -3
- package/lib/engine-components-experimental/networking/PlayerSync.js +60 -11
- package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
- package/package.json +5 -4
- package/plugins/common/logger.js +42 -19
- package/plugins/vite/ai.d.ts +11 -10
- package/plugins/vite/ai.js +305 -31
- package/plugins/vite/logger.client.js +4 -3
- package/src/engine/api.ts +3 -0
- package/src/engine/engine_addressables.ts +4 -1
- package/src/engine/engine_animation.ts +47 -9
- package/src/engine/engine_components.ts +36 -7
- package/src/engine/engine_context.ts +11 -2
- package/src/engine/engine_gameobject.ts +5 -0
- package/src/engine/engine_init.ts +4 -0
- package/src/engine/engine_input.ts +2 -1
- package/src/engine/engine_materialpropertyblock.ts +0 -19
- package/src/engine/engine_networking.ts +46 -23
- package/src/engine/engine_networking_instantiate.ts +160 -18
- package/src/engine/engine_networking_prefabs.ts +80 -0
- package/src/engine/engine_physics_rapier.ts +14 -9
- package/src/engine/engine_utils.ts +2 -2
- package/src/engine/postprocessing/api.ts +2 -0
- package/src/engine/postprocessing/index.ts +2 -0
- package/src/engine/postprocessing/postprocessing.ts +322 -0
- package/src/engine/postprocessing/types.ts +43 -0
- package/src/engine/webcomponents/WebXRButtons.ts +21 -4
- package/src/engine/xr/NeedleXRSession.ts +55 -20
- package/src/engine/xr/events.ts +44 -1
- package/src/engine/xr/init.ts +49 -0
- package/src/engine-components/AnimationUtils.ts +7 -17
- package/src/engine-components/AnimatorController.ts +288 -18
- package/src/engine-components/SeeThrough.ts +0 -116
- package/src/engine-components/SyncedRoom.ts +28 -9
- package/src/engine-components/SyncedTransform.ts +5 -5
- package/src/engine-components/Voip.ts +129 -2
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/postprocessing/Effects/Tonemapping.ts +16 -24
- package/src/engine-components/postprocessing/PostProcessingEffect.ts +9 -16
- package/src/engine-components/postprocessing/PostProcessingHandler.ts +2 -1
- package/src/engine-components/postprocessing/Volume.ts +72 -163
- package/src/engine-components/postprocessing/index.ts +1 -0
- package/src/engine-components/postprocessing/utils.ts +2 -0
- package/src/engine-components/ui/Canvas.ts +2 -2
- package/src/engine-components/ui/Graphic.ts +7 -3
- package/src/engine-components/ui/Text.ts +170 -52
- package/src/engine-components/ui/index.ts +2 -1
- package/src/engine-components-experimental/networking/PlayerSync.ts +64 -11
- package/dist/needle-engine.bundle-CNH61kLA.umd.cjs +0 -1730
- package/dist/needle-engine.bundle-Dvh1jROn.min.js +0 -1730
package/plugins/vite/ai.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { dirname, join } from "path";
|
|
1
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname, join, relative } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { needleLog } from "./logging.js";
|
|
5
5
|
|
|
@@ -8,20 +8,49 @@ const __dirname = dirname(__filename);
|
|
|
8
8
|
|
|
9
9
|
const pluginName = "needle-ai";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Supported AI coding agents.
|
|
13
|
+
* Each entry defines how to detect the agent and how to write its skill file.
|
|
14
|
+
* `detect` is the directory that must exist in the project root.
|
|
15
|
+
* `write(cwd, canonicalDir, content)` installs the skill in the agent's native format.
|
|
16
|
+
*
|
|
17
|
+
* The `.agents/` entry is special: it is the canonical location where SKILL.md
|
|
18
|
+
* and downloaded reference files are written directly. All other agents symlink
|
|
19
|
+
* their skill directory to `.agents/skills/needle-engine/`.
|
|
20
|
+
*/
|
|
21
|
+
const agents = [
|
|
22
|
+
{
|
|
23
|
+
name: "Claude Code",
|
|
24
|
+
detect: ".claude",
|
|
25
|
+
write: (cwd, canonicalDir, content) => symlinkSkillDir(join(cwd, ".claude"), canonicalDir),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "GitHub Copilot",
|
|
29
|
+
detect: ".github",
|
|
30
|
+
write: (cwd, canonicalDir, content) => symlinkSkillDir(join(cwd, ".github"), canonicalDir),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "Cursor",
|
|
34
|
+
detect: ".cursor",
|
|
35
|
+
write: (cwd, canonicalDir, content) => writeCursorRule(cwd, canonicalDir, content),
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
11
39
|
/**
|
|
12
40
|
* Needle Engine AI skill installer.
|
|
13
41
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
42
|
+
* Auto-detects AI coding agents by checking for their config directories
|
|
43
|
+
* in the project root, then writes the Needle Engine skill in each agent's
|
|
44
|
+
* native format.
|
|
45
|
+
*
|
|
46
|
+
* Supported agents: Claude Code, GitHub Copilot, Cursor, Codex / universal.
|
|
19
47
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
48
|
+
* `.agents/skills/needle-engine/` is always created as the canonical skill
|
|
49
|
+
* location. SKILL.md and downloaded reference files live there. Other agents
|
|
50
|
+
* symlink to it to avoid duplication.
|
|
51
|
+
*
|
|
52
|
+
* Remote reference files (API docs, templates, etc.) linked from SKILL.md are
|
|
53
|
+
* downloaded at install time and stored locally with relative paths.
|
|
25
54
|
*
|
|
26
55
|
* @param {"build" | "serve"} command
|
|
27
56
|
* @param {{} | undefined | null} config
|
|
@@ -32,34 +61,279 @@ export function needleAI(command, config, userSettings) {
|
|
|
32
61
|
return {
|
|
33
62
|
name: pluginName,
|
|
34
63
|
enforce: "pre",
|
|
35
|
-
buildStart() {
|
|
36
|
-
|
|
64
|
+
async buildStart() {
|
|
65
|
+
await installSkills();
|
|
37
66
|
},
|
|
38
|
-
configureServer() {
|
|
39
|
-
|
|
67
|
+
async configureServer() {
|
|
68
|
+
await installSkills();
|
|
40
69
|
},
|
|
41
70
|
};
|
|
42
71
|
}
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!existsSync(skillDir)) {
|
|
47
|
-
mkdirSync(skillDir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
const skillPath = join(skillDir, "SKILL.md");
|
|
73
|
+
/** Read the SKILL.md template shipped with the engine. */
|
|
74
|
+
function getSkillContent() {
|
|
50
75
|
const templatePath = join(__dirname, "../../SKILL.md");
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
return readFileSync(templatePath, "utf8");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract the body of a SKILL.md file (everything after the YAML frontmatter).
|
|
81
|
+
* Returns the full content if no frontmatter is found.
|
|
82
|
+
*/
|
|
83
|
+
function stripFrontmatter(content) {
|
|
84
|
+
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/);
|
|
85
|
+
return match ? match[1].trimStart() : content;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Remote reference downloading
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract markdown links to raw.githubusercontent.com files from content.
|
|
94
|
+
* Returns an array of { fullMatch, linkText, url, subdir, filename, localRelPath }.
|
|
95
|
+
*/
|
|
96
|
+
function extractRemoteRefs(content) {
|
|
97
|
+
const pattern = /\[([^\]]*)\]\((https:\/\/raw\.githubusercontent\.com\/[^)]+)\)/g;
|
|
98
|
+
const refs = [];
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
101
|
+
const url = match[2];
|
|
102
|
+
const urlPath = new URL(url).pathname;
|
|
103
|
+
const segments = urlPath.split("/").filter(Boolean);
|
|
104
|
+
const filename = segments[segments.length - 1];
|
|
105
|
+
const subdir = segments[segments.length - 2] || "";
|
|
106
|
+
refs.push({
|
|
107
|
+
fullMatch: match[0],
|
|
108
|
+
linkText: match[1],
|
|
109
|
+
url,
|
|
110
|
+
subdir,
|
|
111
|
+
filename,
|
|
112
|
+
localRelPath: `./${subdir}/${filename}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return refs;
|
|
54
116
|
}
|
|
55
117
|
|
|
56
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Download a single remote file. Returns text on success, null on failure.
|
|
120
|
+
* @param {{ fullMatch: string, linkText: string, url: string, subdir: string, filename: string, localRelPath: string }} ref
|
|
121
|
+
* @param {number} timeoutMs
|
|
122
|
+
* @returns {Promise<string | null>}
|
|
123
|
+
*/
|
|
124
|
+
async function downloadRef(ref, timeoutMs = 5000) {
|
|
125
|
+
try {
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
128
|
+
const response = await fetch(ref.url, { signal: controller.signal });
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
if (!response.ok) return null;
|
|
131
|
+
return await response.text();
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Download all remote references found in content. Returns rewritten content
|
|
140
|
+
* (absolute URLs → relative paths) and the downloaded file data.
|
|
141
|
+
* Failed downloads keep their original absolute URL.
|
|
142
|
+
*/
|
|
143
|
+
async function downloadAndRewriteRefs(content) {
|
|
144
|
+
const refs = extractRemoteRefs(content);
|
|
145
|
+
if (refs.length === 0) return { rewrittenContent: content, downloadedFiles: [] };
|
|
146
|
+
|
|
147
|
+
if (typeof globalThis.fetch !== "function") {
|
|
148
|
+
return { rewrittenContent: content, downloadedFiles: [] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const results = await Promise.allSettled(
|
|
152
|
+
refs.map(async ref => {
|
|
153
|
+
const data = await downloadRef(ref);
|
|
154
|
+
return { ref, data };
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
let rewrittenContent = content;
|
|
159
|
+
const downloadedFiles = [];
|
|
160
|
+
|
|
161
|
+
for (const result of results) {
|
|
162
|
+
if (result.status === "fulfilled" && result.value.data !== null) {
|
|
163
|
+
const { ref, data } = result.value;
|
|
164
|
+
const newLink = `[${ref.linkText}](${ref.localRelPath})`;
|
|
165
|
+
rewrittenContent = rewrittenContent.replace(ref.fullMatch, newLink);
|
|
166
|
+
downloadedFiles.push({
|
|
167
|
+
subdir: ref.subdir,
|
|
168
|
+
filename: ref.filename,
|
|
169
|
+
data,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { rewrittenContent, downloadedFiles };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Canonical skill directory (.agents/)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Write SKILL.md and downloaded reference files into the canonical location
|
|
183
|
+
* at `.agents/skills/needle-engine/`. This directory is always created and
|
|
184
|
+
* serves as the symlink target for all other agents.
|
|
185
|
+
* @returns {string} The absolute path to the canonical skill directory.
|
|
186
|
+
*/
|
|
187
|
+
function writeCanonicalSkillDir(cwd, content, downloadedFiles) {
|
|
188
|
+
const canonicalDir = join(cwd, ".agents", "skills", "needle-engine");
|
|
189
|
+
if (!existsSync(canonicalDir)) {
|
|
190
|
+
mkdirSync(canonicalDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
writeFileSync(join(canonicalDir, "SKILL.md"), content, "utf8");
|
|
194
|
+
|
|
195
|
+
for (const file of downloadedFiles) {
|
|
196
|
+
const fileDir = join(canonicalDir, file.subdir);
|
|
197
|
+
if (!existsSync(fileDir)) {
|
|
198
|
+
mkdirSync(fileDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
writeFileSync(join(fileDir, file.filename), file.data, "utf8");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return canonicalDir;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Agent writers
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a symlink at `<agentDir>/skills/needle-engine` → canonical skill dir.
|
|
212
|
+
* If the target already exists (file, dir, or stale symlink), it is replaced.
|
|
213
|
+
*/
|
|
214
|
+
function symlinkSkillDir(agentDir, canonicalDir) {
|
|
215
|
+
const skillsDir = join(agentDir, "skills");
|
|
216
|
+
const linkPath = join(skillsDir, "needle-engine");
|
|
217
|
+
|
|
218
|
+
if (!existsSync(skillsDir)) {
|
|
219
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const target = relative(skillsDir, canonicalDir);
|
|
223
|
+
|
|
224
|
+
// Remove existing entry (file, dir, or stale symlink)
|
|
225
|
+
try {
|
|
226
|
+
if (existsSync(linkPath) || lstatSync(linkPath).isSymbolicLink()) {
|
|
227
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch { /* nothing to remove */ }
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
symlinkSync(target, linkPath, "junction");
|
|
234
|
+
return linkPath;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Fallback: copy if symlink fails (e.g. Windows without privileges)
|
|
238
|
+
if (!existsSync(linkPath)) {
|
|
239
|
+
mkdirSync(linkPath, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
copyDirSync(canonicalDir, linkPath);
|
|
242
|
+
return linkPath;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Write a Cursor rule file at `.cursor/rules/needle-engine.mdc` and symlink
|
|
248
|
+
* the reference files directory at `.cursor/rules/needle-engine/`.
|
|
249
|
+
* Cursor uses its own frontmatter format (description, globs, alwaysApply).
|
|
250
|
+
*/
|
|
251
|
+
function writeCursorRule(cwd, canonicalDir, content) {
|
|
252
|
+
const rulesDir = join(cwd, ".cursor", "rules");
|
|
253
|
+
if (!existsSync(rulesDir)) {
|
|
254
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Symlink .cursor/rules/needle-engine/ → canonical dir (for reference files)
|
|
258
|
+
const linkPath = join(rulesDir, "needle-engine");
|
|
259
|
+
const target = relative(rulesDir, canonicalDir);
|
|
260
|
+
try {
|
|
261
|
+
if (existsSync(linkPath) || lstatSync(linkPath).isSymbolicLink()) {
|
|
262
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch { /* nothing to remove */ }
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
symlinkSync(target, linkPath, "junction");
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Fallback: copy directly
|
|
272
|
+
if (!existsSync(linkPath)) {
|
|
273
|
+
mkdirSync(linkPath, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
copyDirSync(canonicalDir, linkPath);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Write the .mdc file with adjusted relative paths
|
|
279
|
+
// SKILL.md uses ./references/api.md but the .mdc sits at .cursor/rules/needle-engine.mdc
|
|
280
|
+
// so we need ./needle-engine/references/api.md
|
|
281
|
+
let body = stripFrontmatter(content);
|
|
282
|
+
body = body.replace(/\]\(\.\/(references|templates)\//g, "](./needle-engine/$1/");
|
|
283
|
+
|
|
284
|
+
const rulePath = join(rulesDir, "needle-engine.mdc");
|
|
285
|
+
const cursorContent = `---
|
|
286
|
+
description: Needle Engine context — use when editing TypeScript components, Vite config, GLB assets, or anything related to @needle-tools/engine.
|
|
287
|
+
globs:
|
|
288
|
+
alwaysApply: false
|
|
289
|
+
---
|
|
290
|
+
${body}`;
|
|
291
|
+
writeFileSync(rulePath, cursorContent, "utf8");
|
|
292
|
+
return rulePath;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Helpers
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
/** Recursively copy a directory's contents. */
|
|
300
|
+
function copyDirSync(src, dest) {
|
|
301
|
+
for (const entry of readdirSync(src)) {
|
|
302
|
+
const srcPath = join(src, entry);
|
|
303
|
+
const destPath = join(dest, entry);
|
|
304
|
+
if (statSync(srcPath).isDirectory()) {
|
|
305
|
+
if (!existsSync(destPath)) mkdirSync(destPath, { recursive: true });
|
|
306
|
+
copyDirSync(srcPath, destPath);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
copyFileSync(srcPath, destPath);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
// Main
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
/** Detect agents and install the Needle Engine skill for each. */
|
|
319
|
+
async function installSkills() {
|
|
57
320
|
const cwd = process.cwd();
|
|
58
|
-
const dirs = [".claude", ".github", ".agents"].map(d => join(cwd, d)).filter(d => existsSync(d));
|
|
59
|
-
if (dirs.length === 0) return; // only install if developer uses an AI coding agent
|
|
60
321
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
322
|
+
const rawContent = getSkillContent();
|
|
323
|
+
const { rewrittenContent, downloadedFiles } = await downloadAndRewriteRefs(rawContent);
|
|
324
|
+
|
|
325
|
+
// Always write to .agents/ as the canonical location
|
|
326
|
+
const canonicalDir = writeCanonicalSkillDir(cwd, rewrittenContent, downloadedFiles);
|
|
327
|
+
|
|
328
|
+
// Symlink other detected agents to the canonical dir
|
|
329
|
+
const detected = agents.filter(a => existsSync(join(cwd, a.detect)));
|
|
330
|
+
const names = ["Codex / Universal"];
|
|
331
|
+
for (const agent of detected) {
|
|
332
|
+
const path = agent.write(cwd, canonicalDir, rewrittenContent);
|
|
333
|
+
if (path) names.push(agent.name);
|
|
64
334
|
}
|
|
335
|
+
|
|
336
|
+
const refCount = downloadedFiles.length;
|
|
337
|
+
const refMsg = refCount > 0 ? ` (${refCount} reference file${refCount === 1 ? "" : "s"} downloaded)` : "";
|
|
338
|
+
needleLog(pluginName, `Installed for ${names.length} agent${names.length === 1 ? "" : "s"}: ${names.join(", ")}${refMsg}`);
|
|
65
339
|
}
|
|
@@ -99,8 +99,9 @@ if (import.meta && "hot" in import.meta) {
|
|
|
99
99
|
// unpatch();
|
|
100
100
|
// }, 10_000);
|
|
101
101
|
|
|
102
|
+
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
102
103
|
const threshold = 100;
|
|
103
|
-
const devToolsArePotentiallyOpen = window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold;
|
|
104
|
+
const devToolsArePotentiallyOpen = !isMobile && (window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold);
|
|
104
105
|
if (devToolsArePotentiallyOpen) {
|
|
105
106
|
sendLogToServer("internal", "Console logging is disabled (devtools are open)");
|
|
106
107
|
}
|
|
@@ -240,8 +241,8 @@ function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth
|
|
|
240
241
|
const isServer = typeof window === "undefined";
|
|
241
242
|
const stringify_limits = {
|
|
242
243
|
string: isServer ? 100_000 : 1_000,
|
|
243
|
-
object_keys: isServer ?
|
|
244
|
-
object_depth: isServer ?
|
|
244
|
+
object_keys: isServer ? 10 : 10,
|
|
245
|
+
object_depth: isServer ? 3 : 3,
|
|
245
246
|
array_items: isServer ? 2_000 : 100,
|
|
246
247
|
}
|
|
247
248
|
|
package/src/engine/api.ts
CHANGED
|
@@ -318,6 +318,9 @@ export * from "./engine_physics.types.js";
|
|
|
318
318
|
/** Rapier physics engine integration */
|
|
319
319
|
export * from "./engine_physics_rapier.js";
|
|
320
320
|
|
|
321
|
+
/** Core postprocessing stack (accessible via context.postprocessing) */
|
|
322
|
+
export * from "./postprocessing/api.js";
|
|
323
|
+
|
|
321
324
|
|
|
322
325
|
// ============================================================================
|
|
323
326
|
// PLAYER & VIEW MANAGEMENT
|
|
@@ -463,7 +463,10 @@ export class AssetReference {
|
|
|
463
463
|
if (networked) {
|
|
464
464
|
options.context = context;
|
|
465
465
|
const prefab = this.asset;
|
|
466
|
-
|
|
466
|
+
// Only set guid from URL if we have one — runtime Object3D prefabs
|
|
467
|
+
// (via new AssetReference({ asset: obj })) have no URL and may already
|
|
468
|
+
// have a user-assigned guid. Don't overwrite with empty string.
|
|
469
|
+
if (this.url) prefab.guid = this.url;
|
|
467
470
|
const instance = syncInstantiate(prefab, options, undefined, saveOnServer);
|
|
468
471
|
if (instance) {
|
|
469
472
|
return instance;
|
|
@@ -2,9 +2,10 @@ import { AnimationAction, AnimationClip, AnimationMixer, KeyframeTrack, Object3D
|
|
|
2
2
|
|
|
3
3
|
import { isDevEnvironment } from "./debug/index.js";
|
|
4
4
|
import type { Context } from "./engine_context.js";
|
|
5
|
-
import {
|
|
5
|
+
import { IAnimationComponent, Model } from "./engine_types.js";
|
|
6
6
|
import { TypeStore } from "./engine_typestore.js";
|
|
7
7
|
|
|
8
|
+
// #region Registry
|
|
8
9
|
/**
|
|
9
10
|
* Registry for animation related data. Use {@link registerAnimationMixer} to register an animation mixer instance.
|
|
10
11
|
* Can be accessed from {@link Context.animations} and is used internally e.g. when exporting GLTF files.
|
|
@@ -52,10 +53,11 @@ export class AnimationsRegistry {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
|
|
56
|
+
// #region Animation Utils
|
|
55
57
|
/**
|
|
56
58
|
* Utility class for working with animations.
|
|
57
59
|
*/
|
|
58
|
-
export
|
|
60
|
+
export namespace AnimationUtils {
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* Tests if the root object of an AnimationAction can be animated. Objects where matrixAutoUpdate or matrixWorldAutoUpdate is set to false may not animate correctly.
|
|
@@ -63,7 +65,7 @@ export class AnimationUtils {
|
|
|
63
65
|
* @param allowLog Whether to allow logging warnings. Default is false, which only allows logging in development environments.
|
|
64
66
|
* @returns True if the root object can be animated, false otherwise
|
|
65
67
|
*/
|
|
66
|
-
|
|
68
|
+
export function testIfRootCanAnimate(action: AnimationAction, allowLog?: boolean): boolean {
|
|
67
69
|
const root = action.getRoot();
|
|
68
70
|
|
|
69
71
|
if (root && (root.userData.static || root.matrixAutoUpdate === false || root.matrixWorldAutoUpdate === false)) {
|
|
@@ -79,13 +81,15 @@ export class AnimationUtils {
|
|
|
79
81
|
* @param mixer The animation mixer to get the actions from
|
|
80
82
|
* @returns The actions or null if the mixer is invalid
|
|
81
83
|
*/
|
|
82
|
-
|
|
84
|
+
|
|
85
|
+
export function tryGetActionsFromMixer(mixer: AnimationMixer): Array<AnimationAction> | null {
|
|
83
86
|
const actions = mixer["_actions"] as Array<AnimationAction>;
|
|
84
87
|
if (!actions) return null;
|
|
85
88
|
return actions;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
|
|
92
|
+
export function tryGetAnimationClipsFromObjectHierarchy(obj: Object3D, target?: Array<AnimationClip>): Array<AnimationClip> {
|
|
89
93
|
if (!target) target = new Array<AnimationClip>();
|
|
90
94
|
|
|
91
95
|
if (!obj) {
|
|
@@ -96,18 +100,49 @@ export class AnimationUtils {
|
|
|
96
100
|
}
|
|
97
101
|
if (obj.children) {
|
|
98
102
|
for (const child of obj.children) {
|
|
99
|
-
|
|
103
|
+
tryGetAnimationClipsFromObjectHierarchy(child, target);
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
return target;
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
|
|
110
|
+
const $objectAnimationKey = Symbol("objectIsAnimatedData");
|
|
111
|
+
|
|
112
|
+
/** Internal method - This marks an object as being animated. Make sure to always call isAnimated=false if you stop animating the object
|
|
113
|
+
* @param obj The object to mark
|
|
114
|
+
* @param isAnimated Whether the object is animated or not
|
|
115
|
+
*/
|
|
116
|
+
export function setObjectAnimated(obj: Object3D, animatedBy: object, isAnimated: boolean) {
|
|
117
|
+
if (!obj) return;
|
|
118
|
+
if (obj[$objectAnimationKey] === undefined) {
|
|
119
|
+
if (!isAnimated) return;
|
|
120
|
+
obj[$objectAnimationKey] = new Set<object>();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const set = obj[$objectAnimationKey] as Set<object>;
|
|
124
|
+
if (isAnimated) {
|
|
125
|
+
set.add(animatedBy);
|
|
126
|
+
}
|
|
127
|
+
else if (set.has(animatedBy))
|
|
128
|
+
set.delete(animatedBy);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Get is the object is currently animated. Currently used by the Animator to check if a timeline animationtrack is actively animating an object */
|
|
132
|
+
export function getObjectAnimated(obj: Object3D): boolean {
|
|
133
|
+
if (!obj) return false;
|
|
134
|
+
const set = obj[$objectAnimationKey] as Set<object>;
|
|
135
|
+
return set !== undefined && set.size > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// #region Autoplay
|
|
105
139
|
/**
|
|
106
140
|
* Assigns animations from a GLTF file to the objects in the scene.
|
|
107
141
|
* This method will look for objects in the scene that have animations and assign them to the correct objects.
|
|
108
142
|
* @param file The GLTF file to assign the animations from
|
|
109
143
|
*/
|
|
110
|
-
|
|
144
|
+
|
|
145
|
+
export function autoplayAnimations(file: Object3D | Pick<Model, "animations" | "scene">): Array<IAnimationComponent> | null {
|
|
111
146
|
if (!file || !file.animations) {
|
|
112
147
|
console.debug("No animations found in file");
|
|
113
148
|
return null;
|
|
@@ -171,11 +206,14 @@ export class AnimationUtils {
|
|
|
171
206
|
}
|
|
172
207
|
|
|
173
208
|
|
|
174
|
-
|
|
209
|
+
// #region Create Clips
|
|
210
|
+
|
|
211
|
+
export function emptyClip(): AnimationClip {
|
|
175
212
|
return new AnimationClip("empty", 0, []);
|
|
176
213
|
}
|
|
177
214
|
|
|
178
|
-
|
|
215
|
+
|
|
216
|
+
export function createScaleClip(options?: ScaleClipOptions): AnimationClip {
|
|
179
217
|
const duration = options?.duration ?? 0.3;
|
|
180
218
|
|
|
181
219
|
let baseScale: Vector3Like = { x: 1, y: 1, z: 1 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Object3D, Scene } from "three";
|
|
2
|
+
import { v5 } from 'uuid';
|
|
2
3
|
|
|
3
4
|
import { ComponentLifecycleEvents } from "./engine_components_internal.js";
|
|
4
5
|
import { activeInHierarchyFieldName } from "./engine_constants.js";
|
|
@@ -6,8 +7,35 @@ import { removeScriptFromContext, updateActiveInHierarchyWithoutEventCall } from
|
|
|
6
7
|
import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
|
|
7
8
|
import { Context, registerComponent } from "./engine_setup.js";
|
|
8
9
|
import type { ComponentInit, Constructor, ConstructorConcrete, IComponent, IGameObject } from "./engine_types.js";
|
|
10
|
+
import { $componentName } from "./engine_types.js";
|
|
9
11
|
import { getParam } from "./engine_utils.js";
|
|
10
12
|
import { apply } from "./js-extensions/index.js";
|
|
13
|
+
import { TypeStore } from "./engine_typestore.js";
|
|
14
|
+
|
|
15
|
+
const COMPONENT_GUID_NAMESPACE = 'eff8ba80-635d-11ec-90d6-0242ac120003';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates a deterministic guid for a component based on the object's guid and the component type.
|
|
19
|
+
* If the object has a guid, the component guid is derived from `objectGuid:TypeName:index`
|
|
20
|
+
* using UUID v5 hashing. This ensures the same component type added to the same object on
|
|
21
|
+
* different clients gets the same guid — critical for networked components like SyncedTransform.
|
|
22
|
+
* Falls back to the shared counter-based IdProvider if the object has no guid.
|
|
23
|
+
*/
|
|
24
|
+
function generateDeterministicComponentGuid(obj: Object3D, component: IComponent): string {
|
|
25
|
+
const objectGuid = obj["guid"];
|
|
26
|
+
if (objectGuid) {
|
|
27
|
+
const typeName = component[$componentName] ?? component.constructor?.name ?? "Component";
|
|
28
|
+
// Count existing components of same type for uniqueness (e.g. multiple of the same component type)
|
|
29
|
+
const existingComponents = getComponents(obj, component.constructor as any);
|
|
30
|
+
let sameTypeCount = 0;
|
|
31
|
+
for (const c of existingComponents) {
|
|
32
|
+
if (c !== component) sameTypeCount++;
|
|
33
|
+
}
|
|
34
|
+
const key = `${objectGuid}:${typeName}:${sameTypeCount}`;
|
|
35
|
+
return v5(key, COMPONENT_GUID_NAMESPACE);
|
|
36
|
+
}
|
|
37
|
+
return getIdProvider().generateUUID();
|
|
38
|
+
}
|
|
11
39
|
|
|
12
40
|
|
|
13
41
|
const debug = getParam("debuggetcomponent");
|
|
@@ -57,9 +85,8 @@ export function addNewComponent<T extends IComponent>(obj: Object3D, componentIn
|
|
|
57
85
|
if (!obj.userData.components) obj.userData.components = [];
|
|
58
86
|
obj.userData.components.push(componentInstance);
|
|
59
87
|
componentInstance.gameObject = obj as IGameObject;
|
|
60
|
-
// TODO: currently add component does not ensure a new component instance has a guid
|
|
61
88
|
if (componentInstance.guid === undefined || componentInstance.guid === "invalid") {
|
|
62
|
-
componentInstance.guid =
|
|
89
|
+
componentInstance.guid = generateDeterministicComponentGuid(obj, componentInstance);
|
|
63
90
|
}
|
|
64
91
|
apply(obj);
|
|
65
92
|
// register the component - make sure to provide the component instance context (if assigned)
|
|
@@ -109,9 +136,9 @@ export function addComponent<T extends IComponent>(obj: Object3D, componentInsta
|
|
|
109
136
|
// componentInstance.__internalEnable();
|
|
110
137
|
// componentInstance.transform = obj;
|
|
111
138
|
if (componentInstance.guid === undefined || componentInstance.guid === "invalid") {
|
|
112
|
-
componentInstance.guid =
|
|
139
|
+
componentInstance.guid = generateDeterministicComponentGuid(obj, componentInstance);
|
|
113
140
|
}
|
|
114
|
-
if(init) componentInstance._internalInit(init);
|
|
141
|
+
if (init) componentInstance._internalInit(init);
|
|
115
142
|
// Register the component - make sure to provide the component instance context (if assigned)
|
|
116
143
|
registerComponent(componentInstance, componentInstance.context);
|
|
117
144
|
return componentInstance;
|
|
@@ -144,10 +171,12 @@ function onGetComponent<T>(obj: Object3D | null | undefined, componentType: Cons
|
|
|
144
171
|
}
|
|
145
172
|
if (!(obj?.userData?.components)) return null;
|
|
146
173
|
if (typeof componentType === "string") {
|
|
147
|
-
|
|
174
|
+
const type = TypeStore.get(componentType);
|
|
175
|
+
if (!didWarnAboutComponentAccess && !type) {
|
|
148
176
|
didWarnAboutComponentAccess = true;
|
|
149
177
|
console.warn(`Accessing components by name is not supported.\nPlease use the component type instead. This may keep working in local development but it will fail when bundling your application.\n\nYou can import other modules your main module to get access to types\nor if you use npmdefs you can make types available globally using globalThis:\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis`, componentType);
|
|
150
178
|
}
|
|
179
|
+
if (type) componentType = type as Constructor<T>;
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
if (debugEnabled())
|
|
@@ -222,7 +251,7 @@ export function getComponents<T extends IComponent>(obj: Object3D, componentType
|
|
|
222
251
|
* ```
|
|
223
252
|
*/
|
|
224
253
|
export function getComponentInChildren<T extends IComponent>(obj: Object3D, componentType: Constructor<T>, includeInactive: boolean = false): T | null {
|
|
225
|
-
if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
|
|
254
|
+
if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
|
|
226
255
|
const res = getComponent(obj, componentType) as IComponent | null;
|
|
227
256
|
if (includeInactive === false && (res?.enabled === false || res?.activeAndEnabled === false)) return null;
|
|
228
257
|
if (res) return res as T;
|
|
@@ -335,7 +364,7 @@ export function findObjectOfType<T extends IComponent>(type: Constructor<T>, con
|
|
|
335
364
|
if (!scene) return null;
|
|
336
365
|
|
|
337
366
|
const res = getComponentInChildren(scene, type, includeInactive);
|
|
338
|
-
if(res) return res;
|
|
367
|
+
if (res) return res;
|
|
339
368
|
return null;
|
|
340
369
|
}
|
|
341
370
|
|
|
@@ -43,6 +43,7 @@ import { patchTonemapping } from './engine_tonemapping.js';
|
|
|
43
43
|
import type { CoroutineData, ICamera, IComponent, IContext, ILight, LoadedModel, Model, SourceIdentifier, Vec2 } from "./engine_types.js";
|
|
44
44
|
import { deepClone, delay, DeviceUtilities, getParam } from './engine_utils.js';
|
|
45
45
|
import type { INeedleXRSessionEventReceiver, NeedleXRSession } from './engine_xr.js';
|
|
46
|
+
import { PostProcessing } from './postprocessing/index.js';
|
|
46
47
|
import { NeedleMenu } from './webcomponents/needle menu/needle-menu.js';
|
|
47
48
|
import type { NeedleEngineWebComponent } from './webcomponents/needle-engine.js';
|
|
48
49
|
|
|
@@ -387,9 +388,11 @@ export class Context implements IContext {
|
|
|
387
388
|
*/
|
|
388
389
|
renderer!: WebGLRenderer;
|
|
389
390
|
/**
|
|
390
|
-
* The effect composer
|
|
391
|
+
* The effect composer used for rendering postprocessing effects.
|
|
392
|
+
* @deprecated Use `context.postprocessing.composer` instead.
|
|
391
393
|
*/
|
|
392
|
-
composer: EffectComposer | ThreeEffectComposer | null
|
|
394
|
+
get composer(): EffectComposer | ThreeEffectComposer | null { return this.postprocessing.composer; }
|
|
395
|
+
set composer(value: EffectComposer | ThreeEffectComposer | null) { this.postprocessing.composer = value; }
|
|
393
396
|
|
|
394
397
|
// #region internal script lists
|
|
395
398
|
/**
|
|
@@ -504,6 +507,8 @@ export class Context implements IContext {
|
|
|
504
507
|
input: Input;
|
|
505
508
|
/** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
|
|
506
509
|
physics: Physics;
|
|
510
|
+
/** access postprocessing effects stack. Add/remove effects and configure adaptive performance settings */
|
|
511
|
+
postprocessing: PostProcessing;
|
|
507
512
|
/** access networking methods (use it to send or listen to messages or join a networking backend) */
|
|
508
513
|
connection: NetworkConnection;
|
|
509
514
|
/** @deprecated AssetDatabase is deprecated */
|
|
@@ -564,6 +569,7 @@ export class Context implements IContext {
|
|
|
564
569
|
this.time = new Time();
|
|
565
570
|
this.input = new Input(this);
|
|
566
571
|
this.physics = new Physics(this);
|
|
572
|
+
this.postprocessing = new PostProcessing(this);
|
|
567
573
|
this.connection = new NetworkConnection(this);
|
|
568
574
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
569
575
|
this.assets = new AssetDatabase();
|
|
@@ -1778,6 +1784,9 @@ export class Context implements IContext {
|
|
|
1778
1784
|
if (this.renderer.toneMapping !== NoToneMapping)
|
|
1779
1785
|
patchTonemapping(this);
|
|
1780
1786
|
|
|
1787
|
+
// Update postprocessing stack (applies dirty effects, adaptive multisampling/pixel ratio)
|
|
1788
|
+
this.postprocessing.update();
|
|
1789
|
+
|
|
1781
1790
|
if (this.composer && !this.isInXR) {
|
|
1782
1791
|
// if a camera is passed in we need to check if we need to update the composer's camera
|
|
1783
1792
|
if (camera && "setMainCamera" in this.composer) {
|