@threenative/core 0.3.0 → 0.3.2
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/README.md +10 -0
- package/capabilities.json +1881 -131
- package/dist/assets-kyoF7JlJ.d.ts +103 -0
- package/dist/{audio-Dp2mXpD3.d.ts → audio-BFiGneTL.d.ts} +62 -0
- package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
- package/dist/{game-CYIaKhgl.d.ts → game-XGrTzapq.d.ts} +350 -164
- package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
- package/dist/hot.d.ts +5 -3
- package/dist/hot.js +5 -1
- package/dist/index.d.ts +813 -143
- package/dist/index.js +4548 -941
- package/dist/net.d.ts +65 -0
- package/dist/net.js +643 -0
- package/dist/playtest.d.ts +29 -5
- package/dist/playtest.js +181 -35
- package/dist/react.d.ts +4 -2
- package/dist/{canvas-layer-CtrZHgIh.d.ts → renderer-C6hqZpoG.d.ts} +237 -75
- package/dist/world.d.ts +203 -4
- package/dist/world.js +2536 -25
- package/gpl/LICENSE.GPL +117 -0
- package/gpl/convert.py +192 -0
- package/gpl/recipes/_common.py +169 -0
- package/gpl/recipes/bake_ao.py +111 -0
- package/gpl/recipes/decimate.py +64 -0
- package/gpl/recipes/retarget.py +131 -0
- package/gpl/recipes/unwrap.py +71 -0
- package/mcp/blender-server.mjs +632 -0
- package/mcp/blender.mjs +27 -0
- package/mcp/engine-server.mjs +271 -23
- package/mcp/engine.mjs +15 -7
- package/mcp/install.d.mts +37 -0
- package/mcp/install.mjs +94 -26
- package/mcp/servers.d.mts +34 -0
- package/mcp/servers.mjs +110 -9
- package/package.json +36 -8
- package/patches/three@0.185.1.patch +249 -14
- package/scripts/ensure-mcp.mjs +20 -13
- package/scripts/bundle-engine-mcp.mjs +0 -15
- package/scripts/generate-version.mjs +0 -13
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Texture, Object3D, AnimationClip } from 'three';
|
|
2
|
+
|
|
3
|
+
interface IAssetLoaderOptions {
|
|
4
|
+
readonly basePath?: string;
|
|
5
|
+
/**
|
|
6
|
+
* URL of the manifest written by the asset compile step (`public/assets.manifest.json`).
|
|
7
|
+
* Defaults to `assets.manifest.json` resolved against `basePath`. A logical path is resolved
|
|
8
|
+
* to its compiled output through it; a manifest that is absent — 404 or unfetchable — falls
|
|
9
|
+
* back to loading every path verbatim, while one that is served malformed or with an unknown
|
|
10
|
+
* version throws.
|
|
11
|
+
*/
|
|
12
|
+
readonly manifest?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Directory the game's uncompiled assets live in, relative to `basePath`.
|
|
15
|
+
*
|
|
16
|
+
* Only consulted when there is no manifest, and only after the verbatim path has been tried. It
|
|
17
|
+
* is what makes the delete-test possible: remove everything the asset pipeline produced and the
|
|
18
|
+
* game still finds `assets/rock.png` where the author put it, just slower and uncompressed.
|
|
19
|
+
* Defaults to the compile step's own default source directory.
|
|
20
|
+
*/
|
|
21
|
+
readonly sourcePath?: string;
|
|
22
|
+
readonly model?: (url: string) => Promise<unknown>;
|
|
23
|
+
readonly texture?: (url: string) => Promise<Texture>;
|
|
24
|
+
readonly audio?: (url: string) => Promise<AudioBuffer>;
|
|
25
|
+
/**
|
|
26
|
+
* The live renderer (`IRendererLike.raw`), handed to `KTX2Loader.detectSupport()` exactly
|
|
27
|
+
* once so compiled KTX2 textures transcode to a format this machine's GPU actually supports.
|
|
28
|
+
* Required for games whose textures compile to `.ktx2`; without it such a load throws rather
|
|
29
|
+
* than silently uploading decoded RGBA.
|
|
30
|
+
*/
|
|
31
|
+
readonly renderer?: unknown;
|
|
32
|
+
}
|
|
33
|
+
/** The structural slice of three's `KTX2Loader` core depends on. */
|
|
34
|
+
interface IKTX2LoaderLike {
|
|
35
|
+
detectSupport(renderer: unknown): unknown;
|
|
36
|
+
load(url: string, onLoad: (texture: Texture) => void, onProgress?: (event: ProgressEvent<EventTarget>) => void, onError?: (error: unknown) => void): unknown;
|
|
37
|
+
setTranscoderPath(path: string): unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Present only when a renderer was handed to the asset loader. `ready` settles once support
|
|
41
|
+
* detection ran; it rejects naming the renderer and platform when no compressed format is
|
|
42
|
+
* supported, and `defineGame` awaits it during boot so such a target fails at construction.
|
|
43
|
+
*/
|
|
44
|
+
interface ICompressedTextureSupport {
|
|
45
|
+
/**
|
|
46
|
+
* The one shared instance, also handed to `GLTFLoader.setKTX2Loader()` for models. Resolves
|
|
47
|
+
* `undefined` when the renderer exposed no surface to probe (see `createKtx2Loader`).
|
|
48
|
+
*/
|
|
49
|
+
readonly loader: Promise<IKTX2LoaderLike | undefined>;
|
|
50
|
+
readonly ready: Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
interface IAssetLoader {
|
|
53
|
+
readonly compressedTextures?: ICompressedTextureSupport;
|
|
54
|
+
model<T = unknown>(path: string): Promise<T>;
|
|
55
|
+
texture(path: string): Promise<Texture>;
|
|
56
|
+
audio(path: string): Promise<AudioBuffer>;
|
|
57
|
+
release(kind: "audio" | "model" | "texture", path: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Where a logical path is served from, in the order worth trying — the manifest's
|
|
60
|
+
* content-addressed output when a manifest exists, otherwise the verbatim and source paths.
|
|
61
|
+
* For loaders this surface does not wrap (an HDR sky, a font, a data file) — a game must never
|
|
62
|
+
* hard-code a hashed output name, which changes on every rebuild.
|
|
63
|
+
*/
|
|
64
|
+
resolve(path: string): Promise<readonly string[]>;
|
|
65
|
+
/**
|
|
66
|
+
* How many loads this loader has been asked for and how many have settled (resolved or
|
|
67
|
+
* rejected). A loading view reads the ratio; the runtime folds it into `ctx.startup.progress`
|
|
68
|
+
* while the start scene loads, so the bar moves with the bytes instead of jumping 0 to 1.
|
|
69
|
+
*
|
|
70
|
+
* `requestedBytes` and `settledBytes` are the same ledger weighed by the `bytes` the compile
|
|
71
|
+
* step already records for every manifest entry, and they are what a loading bar should read.
|
|
72
|
+
* A file count treats a 710 MB model and a 4 KB icon alike: one real game's bar reached 92%
|
|
73
|
+
* on eleven small assets and then stood still for the entire download of the twelfth. Both
|
|
74
|
+
* stay 0 for a game with no manifest, where no size is knowable before the bytes arrive.
|
|
75
|
+
*/
|
|
76
|
+
readonly progress: {
|
|
77
|
+
readonly requested: number;
|
|
78
|
+
readonly requestedBytes: number;
|
|
79
|
+
readonly settled: number;
|
|
80
|
+
readonly settledBytes: number;
|
|
81
|
+
};
|
|
82
|
+
clear(): void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Repair an exported rig whose clips are Z-mirrored against its own bind pose, in place.
|
|
86
|
+
*
|
|
87
|
+
* The signature (PRD-324, the whole Wildwood animal pack): the file's bind faces its own +Z, but
|
|
88
|
+
* every animation track is expressed in a Z-mirrored frame — position tracks hold `(x, y, −z)`
|
|
89
|
+
* where the bind holds `(x, y, z)`, and quaternion tracks hold `(−x, −y, z, w)`, the conjugation
|
|
90
|
+
* of the same mirror. Played as authored, every animal faces backwards with its spine folded:
|
|
91
|
+
* head behind pelvis, healthy bone lengths, zero errors anywhere.
|
|
92
|
+
*
|
|
93
|
+
* An exporter writes exactly this when it converts a rig's bind to one convention and forgets its
|
|
94
|
+
* animation tracks. The loader repairs it before the game ever sees it: detection votes per
|
|
95
|
+
* tracked bone on whether the clip's translation Z sits negated against the bind, and only an
|
|
96
|
+
* overwhelming vote (at least `MIRROR_VOTE_MINIMUM` bones, `MIRROR_VOTE_SHARE` of them) converts
|
|
97
|
+
* every track — positions negate Z, quaternions negate X and Y, once, across all clips. A file
|
|
98
|
+
* that does not carry the signature is left byte-identical.
|
|
99
|
+
*/
|
|
100
|
+
declare function reconcileMirroredClips(root: Object3D, clips: readonly AnimationClip[]): boolean;
|
|
101
|
+
declare function createAssetLoader(options?: IAssetLoaderOptions): IAssetLoader;
|
|
102
|
+
|
|
103
|
+
export { type IAssetLoader as I, type IAssetLoaderOptions as a, createAssetLoader as c, reconcileMirroredClips as r };
|
|
@@ -53,6 +53,19 @@ interface IAudioRuntimeSnapshot {
|
|
|
53
53
|
readonly voices: number;
|
|
54
54
|
/** Retired voices held for reuse. Bounded by peak concurrency, never by session length. */
|
|
55
55
|
readonly pooled: number;
|
|
56
|
+
/**
|
|
57
|
+
* Voices stopped mid-cue by `pause` and holding their position. Nonzero here while a game
|
|
58
|
+
* claims to be running is a menu that muted the world and forgot to give it back.
|
|
59
|
+
*/
|
|
60
|
+
readonly paused: number;
|
|
61
|
+
/**
|
|
62
|
+
* Cue-shaping options this runtime accepted and could not honour, sorted and de-duplicated.
|
|
63
|
+
*
|
|
64
|
+
* The native host binds neither a biquad filter nor a schedulable `detune`, so `lowpassHz` and
|
|
65
|
+
* `detune` are silently dropped there. A mix tuned on the web and shipped to a phone is flat in
|
|
66
|
+
* a way nothing else reports, and a build can be failed on this.
|
|
67
|
+
*/
|
|
68
|
+
readonly unsupported: readonly string[];
|
|
56
69
|
}
|
|
57
70
|
declare class AudioBus {
|
|
58
71
|
#private;
|
|
@@ -65,6 +78,41 @@ declare class AudioBus {
|
|
|
65
78
|
* now bounded by how many cues have ever sounded at once, so a gate can pin it.
|
|
66
79
|
*/
|
|
67
80
|
get pooled(): number;
|
|
81
|
+
/** Voices holding their position across a `pause`. */
|
|
82
|
+
get pausedVoices(): number;
|
|
83
|
+
/** True between `pause` and `resume`. */
|
|
84
|
+
get paused(): boolean;
|
|
85
|
+
/** The master volume last asked for — the target, not a point some ramp is passing through. */
|
|
86
|
+
get volume(): number;
|
|
87
|
+
/** @see IAudioRuntimeSnapshot.unsupported */
|
|
88
|
+
get unsupported(): readonly string[];
|
|
89
|
+
/**
|
|
90
|
+
* The whole bus's level, which is what a volume slider and a duck both move.
|
|
91
|
+
*
|
|
92
|
+
* One bus per category — ambience, effects, music — makes this the mixer: ducking the wood
|
|
93
|
+
* under a discovery cue is `ambience.setVolume(0.35, 0.4)` and `setVolume(1, 0.8)` after. It
|
|
94
|
+
* rides the listener's gain, so it costs nothing per voice and applies to cues already sounding.
|
|
95
|
+
*
|
|
96
|
+
* A bus constructed with a shared `listener` shares that listener's master with every other bus
|
|
97
|
+
* on it; give each category its own bus (the default) to mix them apart.
|
|
98
|
+
*
|
|
99
|
+
* @param volume Linear gain, 0 or greater.
|
|
100
|
+
* @param fade Seconds to reach it. Defaults to a 15 ms settle, which is a level change rather
|
|
101
|
+
* than a fade — the shortest move that does not click.
|
|
102
|
+
*/
|
|
103
|
+
setVolume(volume: number, fade?: number): void;
|
|
104
|
+
/**
|
|
105
|
+
* Stop every sounding voice where it stands and hold its position.
|
|
106
|
+
*
|
|
107
|
+
* The difference from `stop` is what happens next: a paused bed resumes mid-bar, a stopped one
|
|
108
|
+
* starts over. Cues asked for while paused stay queued and sound on `resume`, so a menu opened
|
|
109
|
+
* during a burst does not fire the backlog at the player when they close it.
|
|
110
|
+
*
|
|
111
|
+
* The audio context keeps running — suspending it would silence every other bus sharing it.
|
|
112
|
+
*/
|
|
113
|
+
pause(): void;
|
|
114
|
+
/** Sound the held voices again from where they stopped, then release anything queued. */
|
|
115
|
+
resume(): void;
|
|
68
116
|
setCamera(camera: Object3D): void;
|
|
69
117
|
reparent(camera: Object3D): void;
|
|
70
118
|
unlock(): Promise<void>;
|
|
@@ -86,6 +134,20 @@ declare class AudioBus {
|
|
|
86
134
|
*/
|
|
87
135
|
playAt(buffer: AudioBuffer, source: Object3D | Vector3, options?: IAudioPlayOptions): PositionalAudio;
|
|
88
136
|
music(buffer: AudioBuffer, options?: IAudioPlayOptions): Audio;
|
|
137
|
+
/**
|
|
138
|
+
* Stop one voice and give it back to the pool.
|
|
139
|
+
*
|
|
140
|
+
* The missing primitive: `stop()` silenced every voice or none, and stopping the handle
|
|
141
|
+
* directly does not work — three's `Audio.stop()` detaches `onended` before stopping the node,
|
|
142
|
+
* so the bus's reclaim hook never fires and the voice leaks out of the pool while staying in
|
|
143
|
+
* the scene. A game that wants one entity's loop to end needs exactly this, and `playAt` with
|
|
144
|
+
* `loop` is the obvious way to reach for it.
|
|
145
|
+
*
|
|
146
|
+
* Returns false for a voice this bus is not sounding — a stale handle from a voice already
|
|
147
|
+
* recycled, or one belonging to another bus. Reported rather than thrown: a caller stopping a
|
|
148
|
+
* sound that already ended has made no mistake.
|
|
149
|
+
*/
|
|
150
|
+
stopVoice(voice: Audio<AudioNode>): boolean;
|
|
89
151
|
stop(): void;
|
|
90
152
|
dispose(): void;
|
|
91
153
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as three from 'three';
|
|
2
|
+
import { Camera, Vector2, Vector3, Scene, OrthographicCamera } from 'three';
|
|
3
|
+
import { I as IRendererLike } from './renderer-C6hqZpoG.js';
|
|
4
|
+
|
|
5
|
+
interface IViewportSize {
|
|
6
|
+
readonly aspect: number;
|
|
7
|
+
readonly height: number;
|
|
8
|
+
readonly width: number;
|
|
9
|
+
}
|
|
10
|
+
interface IViewportInsets {
|
|
11
|
+
readonly bottom: number;
|
|
12
|
+
readonly left: number;
|
|
13
|
+
readonly right: number;
|
|
14
|
+
readonly top: number;
|
|
15
|
+
}
|
|
16
|
+
interface IViewportSafeArea extends IViewportInsets {
|
|
17
|
+
/** The safe rectangle in drawable pixel coordinates, with y measured from the top edge. */
|
|
18
|
+
readonly height: number;
|
|
19
|
+
readonly source: "full-drawable-fallback" | "measured";
|
|
20
|
+
readonly width: number;
|
|
21
|
+
readonly x: number;
|
|
22
|
+
readonly y: number;
|
|
23
|
+
}
|
|
24
|
+
interface IViewportOptions {
|
|
25
|
+
readonly camera: Camera;
|
|
26
|
+
readonly renderer: IRendererLike;
|
|
27
|
+
readonly source?: IViewportPlatformSource;
|
|
28
|
+
}
|
|
29
|
+
type ViewportResizeHandler = (size: IViewportSize) => void;
|
|
30
|
+
interface IViewportPlatformSource {
|
|
31
|
+
observeResize(canvas: HTMLCanvasElement, resize: () => void): () => void;
|
|
32
|
+
readSize(canvas: HTMLCanvasElement): IViewportSize;
|
|
33
|
+
readSafeArea?(canvas: HTMLCanvasElement, size: IViewportSize): IViewportInsets | undefined;
|
|
34
|
+
}
|
|
35
|
+
declare class Viewport {
|
|
36
|
+
#private;
|
|
37
|
+
readonly camera: Camera;
|
|
38
|
+
readonly renderer: IRendererLike;
|
|
39
|
+
constructor(options: IViewportOptions);
|
|
40
|
+
get size(): IViewportSize;
|
|
41
|
+
get safeArea(): IViewportSafeArea;
|
|
42
|
+
projectPosition(screen: Vector2, z?: number, target?: Vector3): Vector3;
|
|
43
|
+
unprojectPosition(world: Vector3, target?: Vector2): Vector2;
|
|
44
|
+
onResize(handler: ViewportResizeHandler): () => void;
|
|
45
|
+
resize(): void;
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A Godot-shaped render surface that is independent of the world camera and post pipeline. */
|
|
50
|
+
declare class CanvasLayer {
|
|
51
|
+
#private;
|
|
52
|
+
readonly scene: Scene<three.Object3DEventMap>;
|
|
53
|
+
readonly camera: OrthographicCamera;
|
|
54
|
+
/** Declares that this layer covers the framebuffer, allowing the world pass to be skipped. */
|
|
55
|
+
opaque: boolean;
|
|
56
|
+
constructor(viewport: Pick<Viewport, "onResize" | "size">);
|
|
57
|
+
dispose(): void;
|
|
58
|
+
/** Subscribe to drawable-size changes after this layer has updated its camera. */
|
|
59
|
+
onResize(handler: (size: IViewportSize) => void): () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { CanvasLayer as C, type IViewportOptions as I, Viewport as V };
|