@skewedaspect/sage 0.9.0-beta.9 → 0.9.0

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.
@@ -101,9 +101,10 @@ export declare class ToggleBinding implements Binding {
101
101
  */
102
102
  process(state: InputState, eventBus: GameEventBus): void;
103
103
  /**
104
- * Reset edge-detection state so the next input is evaluated from a clean baseline.
104
+ * Reset edge-detection state for a context activation. When a state is provided, primes from the current physical
105
+ * input so that already-held inputs don't produce phantom edges in the newly activated context.
105
106
  */
106
- resetEdgeState(): void;
107
+ resetEdgeState(state?: InputState): void;
107
108
  /**
108
109
  * Reset toggle to its initial state
109
110
  */
@@ -70,9 +70,10 @@ export declare class TriggerBinding implements Binding {
70
70
  */
71
71
  process(state: InputState, eventBus: GameEventBus): void;
72
72
  /**
73
- * Reset edge-detection state so the next input is evaluated from a clean baseline.
73
+ * Reset edge-detection state for a context activation. When a state is provided, primes from the current physical
74
+ * input so that already-held inputs don't produce phantom rising edges in the newly activated context.
74
75
  */
75
- resetEdgeState(): void;
76
+ resetEdgeState(state?: InputState): void;
76
77
  /**
77
78
  * Returns a JSON-serializable representation of this trigger binding.
78
79
  */
@@ -96,7 +96,7 @@ export declare class ValueBinding implements Binding {
96
96
  /**
97
97
  * No-op for value bindings — they have no edge-detection state to reset.
98
98
  */
99
- resetEdgeState(): void;
99
+ resetEdgeState(_state?: InputState): void;
100
100
  /**
101
101
  * Returns a JSON-serializable representation of this value binding.
102
102
  */
@@ -188,6 +188,19 @@ export declare class GameEntity<EntityState extends object = object> implements
188
188
  * @internal
189
189
  */
190
190
  $detachFromNode(): void;
191
+ /**
192
+ * Add this entity to a named outline layer for visual highlighting.
193
+ * Requires the entity to be attached to a node in a level with outlines configured.
194
+ *
195
+ * @param layerName - The name of the outline layer (must exist on the current level)
196
+ */
197
+ highlight(layerName: string): void;
198
+ /**
199
+ * Remove this entity from an outline layer, or from all layers if no name given.
200
+ *
201
+ * @param layerName - Specific layer to remove from, or omit to remove from all
202
+ */
203
+ unhighlight(layerName?: string): void;
191
204
  /**
192
205
  * Adds a child entity to this entity's hierarchy.
193
206
  * Sets the child's parent to this entity. If both entities have nodes, parents the child's node.
@@ -57,6 +57,7 @@ export declare class GameEngine {
57
57
  logger: LoggingUtility;
58
58
  raycast: RaycastHelper;
59
59
  timer: GameTimer;
60
+ largeWorldRendering: boolean;
60
61
  started: boolean;
61
62
  private _log;
62
63
  private _beforeStartHook;
@@ -72,7 +73,7 @@ export declare class GameEngine {
72
73
  * @param engines
73
74
  * @param managers
74
75
  */
75
- constructor(canvas: GameCanvas, renderEngine: AbstractEngine, physics: HavokPlugin, eventBus: GameEventBus, logger: LoggingUtility, raycast: RaycastHelper, timer: GameTimer, engines: Engines, managers: Managers);
76
+ constructor(canvas: GameCanvas, renderEngine: AbstractEngine, physics: HavokPlugin, eventBus: GameEventBus, logger: LoggingUtility, raycast: RaycastHelper, timer: GameTimer, engines: Engines, managers: Managers, largeWorldRendering?: boolean);
76
77
  /**
77
78
  * Register a function to be called before the game engine starts
78
79
  * @param hook
@@ -1,5 +1,4 @@
1
- import { Scene, TransformNode, Vector3 } from '@babylonjs/core';
2
- import { StaticSound } from '@babylonjs/core/AudioV2/abstractAudio/staticSound';
1
+ import { Scene, StaticSound, TransformNode, Vector3 } from '@babylonjs/core';
3
2
  import { LevelConfig, LevelContext } from "../interfaces/level.d.ts";
4
3
  import { GameEntity } from "./entity.d.ts";
5
4
  import { Level } from "./level.d.ts";
@@ -54,6 +53,14 @@ export declare class GameLevel extends Level {
54
53
  * Import all meshes from the configured scene file (GLB/GLTF/Babylon) into the scene.
55
54
  */
56
55
  private _loadSceneFile;
56
+ /**
57
+ * Extract the scene file path from the config, which can be a string or SceneConfig object.
58
+ */
59
+ private _getScenePath;
60
+ /**
61
+ * Check if the scene config specifies a right-handed coordinate system.
62
+ */
63
+ private _isRightHanded;
57
64
  /**
58
65
  * Set up the scene environment: IBL (image-based lighting) for PBR reflections and/or a visible skybox.
59
66
  */
@@ -89,6 +96,11 @@ export declare class GameLevel extends Level {
89
96
  * Apply shared light properties from a config to an existing light.
90
97
  */
91
98
  private _applyLightConfig;
99
+ /**
100
+ * Set up clustered lighting: create a container, move eligible lights into it,
101
+ * and fix PBR material falloff for compatibility.
102
+ */
103
+ private _setupClustering;
92
104
  private _toVector3;
93
105
  private _toColor3;
94
106
  /**
@@ -50,21 +50,23 @@ export declare class MouseInputPlugin {
50
50
  */
51
51
  $teardown(): Promise<void>;
52
52
  /**
53
- * Set up mouse event listeners
53
+ * Set up pointer and wheel event listeners. We use pointer events instead of mouse events
54
+ * because BabylonJS calls preventDefault() on pointerdown, which suppresses the
55
+ * corresponding mousedown/mouseup per the Pointer Events spec.
54
56
  */
55
57
  private _setupMouseEvents;
56
58
  /**
57
- * Handle mouse button down events
59
+ * Handle pointer down events (mouse buttons)
58
60
  */
59
- private _handleMouseDown;
61
+ private _handlePointerDown;
60
62
  /**
61
- * Handle mouse button up events
63
+ * Handle pointer up events (mouse buttons)
62
64
  */
63
- private _handleMouseUp;
65
+ private _handlePointerUp;
64
66
  /**
65
- * Handle mouse move events
67
+ * Handle pointer move events (mouse position)
66
68
  */
67
- private _handleMouseMove;
69
+ private _handlePointerMove;
68
70
  /**
69
71
  * Handle mouse wheel events
70
72
  */
@@ -1,7 +1,9 @@
1
1
  import { Scene } from '@babylonjs/core';
2
+ import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';
2
3
  import { LoggerInterface } from "../interfaces/logger.d.ts";
3
4
  import { LevelConfig, LevelContext, LevelInstance, PropertyHandler } from "../interfaces/level.d.ts";
4
5
  import { GameEngine } from "./gameEngine.d.ts";
6
+ import { OutlineManager } from "../managers/outline.d.ts";
5
7
  /**
6
8
  * Abstract base class for game levels.
7
9
  *
@@ -25,6 +27,8 @@ export declare abstract class Level implements LevelInstance {
25
27
  protected readonly _log: LoggerInterface;
26
28
  protected readonly _context: LevelContext;
27
29
  protected _scene: Scene | null;
30
+ clusteredLights: ClusteredLightContainer | null;
31
+ outlines: OutlineManager | null;
28
32
  /**
29
33
  * Creates a new Level instance.
30
34
  *
@@ -1,5 +1,4 @@
1
- import { AudioBus } from '@babylonjs/core/AudioV2/abstractAudio/audioBus';
2
- import { IStaticSoundOptions, StaticSound } from '@babylonjs/core/AudioV2/abstractAudio/staticSound';
1
+ import { AudioBus, IStaticSoundOptions, StaticSound } from '@babylonjs/core';
3
2
  import { Disposable } from "../interfaces/lifecycle.d.ts";
4
3
  import { LoggingUtility } from "../utils/logger.d.ts";
5
4
  export declare class AudioEngine implements Disposable {
@@ -1,4 +1,4 @@
1
- import { AbstractEngine, AbstractMesh, AssetContainer, DirectionalLight, FreeCamera, HemisphericLight, Light, Mesh, PhysicsAggregate, PhysicsShapeType, PointLight, Scene, SpotLight, Vector3 } from '@babylonjs/core';
1
+ import { AbstractEngine, AbstractMesh, AssetContainer, DirectionalLight, FreeCamera, HemisphericLight, Light, Mesh, PhysicsAggregate, PhysicsShapeType, PointLight, RectAreaLight, Scene, SpotLight, Vector3 } from '@babylonjs/core';
2
2
  import { HavokPhysicsWithBindings } from '@babylonjs/havok';
3
3
  import { GameCanvas } from "../interfaces/game.d.ts";
4
4
  import { Disposable } from "../interfaces/lifecycle.d.ts";
@@ -13,7 +13,7 @@ export declare class SceneEngine implements Disposable {
13
13
  * Creates a new scene with physics enabled
14
14
  */
15
15
  createScene(): Scene;
16
- enablePhysics(scene: Scene, gravityVector?: Vector3): void;
16
+ enablePhysics(scene: Scene, gravityVector?: Vector3, floatingOriginWorldRadius?: number): void;
17
17
  /**
18
18
  * Creates a free camera at the specified position, targeting the origin. Attaches controls to the provided
19
19
  * canvas, or falls back to the engine's default canvas.
@@ -58,6 +58,16 @@ export declare class SceneEngine implements Disposable {
58
58
  * @param intensity - Default: 1.0
59
59
  */
60
60
  createSpotLight(name: string, position: Vector3, direction: Vector3, angle: number, exponent: number, scene: Scene, intensity?: number): SpotLight;
61
+ /**
62
+ * Creates a rectangular area light (emits from a flat rectangle in the -Z direction)
63
+ * @param name
64
+ * @param position
65
+ * @param width
66
+ * @param height
67
+ * @param scene
68
+ * @param intensity - Default: 1.0
69
+ */
70
+ createRectAreaLight(name: string, position: Vector3, width: number, height: number, scene: Scene, intensity?: number): RectAreaLight;
61
71
  /**
62
72
  * Creates a sphere mesh. Defaults: diameter 1, 32 segments.
63
73
  * @param name
@@ -5,6 +5,9 @@ import { LevelManager } from "../managers/level.d.ts";
5
5
  * Supports the following properties on scene nodes:
6
6
  * - `collider` (string): Collider type - "box", "sphere", "mesh", or "none"
7
7
  * - `collider_mesh` (boolean): On a child node, marks it as the collision source for parent's "mesh" collider
8
+ * - `collider_mass` (number): Mass for the physics body. Defaults to 0 (static). Set > 0 for dynamic bodies.
9
+ * - `collider_kinematic` (boolean): When true, sets `disablePreStep = false` so the physics body
10
+ * tracks mesh transform each frame. Required for animated colliders (doors, elevators, platforms).
8
11
  *
9
12
  * For "mesh" colliders, if a child node has `collider_mesh: true`, that child's geometry
10
13
  * is used for collision and the child is made invisible.
@@ -1,9 +1,3 @@
1
1
  import { Scene } from '@babylonjs/core';
2
2
  import { PostProcessingConfig } from "../interfaces/level.d.ts";
3
- /**
4
- * Apply post-processing effects to a scene based on a declarative config.
5
- * Creates BabylonJS rendering pipelines and configures them according to the provided settings.
6
- *
7
- * This is a level-config handler, not a node property handler.
8
- */
9
- export declare function applyPostProcessing(scene: Scene, config: PostProcessingConfig): void;
3
+ export declare function applyPostProcessing(scene: Scene, config: PostProcessingConfig): Promise<void>;
@@ -0,0 +1,3 @@
1
+ import { Scene } from '@babylonjs/core';
2
+ import { PostProcessingConfig } from "../interfaces/level.d.ts";
3
+ export declare function applyFrameGraphPostProcessing(scene: Scene, config: PostProcessingConfig): Promise<void>;
@@ -0,0 +1,9 @@
1
+ import { Scene } from '@babylonjs/core';
2
+ import { PostProcessingConfig } from "../interfaces/level.d.ts";
3
+ /**
4
+ * Apply post-processing effects to a scene based on a declarative config.
5
+ * Creates BabylonJS rendering pipelines and configures them according to the provided settings.
6
+ *
7
+ * This is a level-config handler, not a node property handler.
8
+ */
9
+ export declare function applyPipelinePostProcessing(scene: Scene, config: PostProcessingConfig): void;
@@ -109,10 +109,13 @@ export interface Binding {
109
109
  */
110
110
  process(state: InputState, eventBus: GameEventBus): void;
111
111
  /**
112
- * Reset any internal edge-detection state. Called when a binding's context is reactivated so that stale state
113
- * from a previous activation doesn't swallow the first input event.
112
+ * Reset edge-detection state for a context activation. When an input state is provided, the binding primes its
113
+ * internal state from the current physical input so that already-held inputs don't produce phantom edges. Without
114
+ * a state, resets to a clean baseline (false).
115
+ *
116
+ * @param state - Last known input state for this binding's device, if available
114
117
  */
115
- resetEdgeState(): void;
118
+ resetEdgeState(state?: InputState): void;
116
119
  /**
117
120
  * Convert the binding to a JSON-serializable object.
118
121
  */
@@ -25,4 +25,10 @@ export interface SageOptions {
25
25
  logLevel?: LogLevel;
26
26
  /** Audio channel names. If provided, SAGE initializes AudioV2 with one bus per channel. */
27
27
  audioChannels?: string[];
28
+ /**
29
+ * Enable Large World Rendering for scenes with coordinates far from origin.
30
+ * Uses float64 CPU matrices and floating origin to prevent GPU precision loss.
31
+ * Also enables multi-region Havok physics when physics are active.
32
+ */
33
+ largeWorldRendering?: boolean;
28
34
  }
@@ -36,6 +36,10 @@ export interface LevelContext {
36
36
  export interface SpawnDefinition {
37
37
  /** The entity type name to spawn */
38
38
  entity: string;
39
+ /** Optional human-readable name for the spawned entity instance. */
40
+ name?: string;
41
+ /** Tags to apply to the spawned entity (merged with definition tags). */
42
+ tags?: string[];
39
43
  /** Additional configuration passed to the entity's initial state */
40
44
  config?: Record<string, unknown>;
41
45
  }
@@ -44,17 +48,36 @@ export interface SpawnDefinition {
44
48
  * Used to configure entities that are marked with the 'entity' property in scene files.
45
49
  */
46
50
  export interface EntityDefinition {
51
+ /** Optional name override for the entity (overrides the scene node name). */
52
+ name?: string;
53
+ /** Tags to apply to the entity (merged with definition tags). */
54
+ tags?: string[];
47
55
  /** Additional configuration passed to the entity's initial state */
48
56
  config?: Record<string, unknown>;
49
57
  }
58
+ /**
59
+ * Configuration for loading a scene file.
60
+ * Can be a simple string path (backward compatible) or an object with options.
61
+ */
62
+ export interface SceneConfig {
63
+ /** Path to the scene file (.glb, .gltf, .babylon, etc.) */
64
+ path: string;
65
+ /**
66
+ * Whether the scene data is in a right-handed coordinate system.
67
+ * When true, sets `scene.useRightHandedSystem = true` before loading,
68
+ * which tells BabylonJS to interpret all coordinates as right-handed.
69
+ * Typically needed for .babylon files exported from Blender.
70
+ */
71
+ rightHanded?: boolean;
72
+ }
50
73
  /**
51
74
  * Configuration for a game level loaded from YAML
52
75
  */
53
76
  export interface LevelConfig {
54
77
  /** The name of the level */
55
78
  name: string;
56
- /** Path to the scene file (.glb, .babylon) */
57
- scene?: string;
79
+ /** Scene file path, or configuration object with path and options */
80
+ scene?: string | SceneConfig;
58
81
  /** Name of a registered custom Level class to use instead of GameLevel */
59
82
  class?: string;
60
83
  /** Configuration passed to custom Level class */
@@ -89,6 +112,10 @@ export interface LevelConfig {
89
112
  sounds?: Record<string, LevelSoundConfig>;
90
113
  /** Post-processing effects configuration */
91
114
  postProcessing?: PostProcessingConfig;
115
+ /** Clustered lighting configuration for many-light scenes */
116
+ clustering?: ClusteringConfig;
117
+ /** Named outline layers for entity highlighting */
118
+ outlines?: Record<string, OutlineLayerConfig>;
92
119
  }
93
120
  /**
94
121
  * Reusable 3D vector config for positions, directions, and rotations.
@@ -112,7 +139,7 @@ export interface ColorConfig {
112
139
  */
113
140
  export interface CameraDefinition {
114
141
  /** Camera type (required for create mode, omit for override mode) */
115
- type?: 'free' | 'arcRotate' | 'universal';
142
+ type?: 'free' | 'arcRotate' | 'universal' | 'geospatial';
116
143
  /** Mark as the active camera for this level */
117
144
  active?: boolean;
118
145
  /** Attach canvas input controls */
@@ -135,6 +162,17 @@ export interface CameraDefinition {
135
162
  lowerBetaLimit?: number;
136
163
  upperBetaLimit?: number;
137
164
  wheelPrecision?: number;
165
+ planetRadius?: number;
166
+ yaw?: number;
167
+ pitch?: number;
168
+ center?: Vec3Config;
169
+ checkCollisions?: boolean;
170
+ radiusMin?: number;
171
+ radiusMax?: number;
172
+ pitchMin?: number;
173
+ pitchMax?: number;
174
+ yawMin?: number;
175
+ yawMax?: number;
138
176
  }
139
177
  /**
140
178
  * Configuration for a single light. If `type` is present, a new light is created.
@@ -142,7 +180,7 @@ export interface CameraDefinition {
142
180
  */
143
181
  export interface LightDefinition {
144
182
  /** Light type (required for create mode, omit for override mode) */
145
- type?: 'hemispheric' | 'directional' | 'point' | 'spot';
183
+ type?: 'hemispheric' | 'directional' | 'point' | 'spot' | 'rectarea';
146
184
  intensity?: number;
147
185
  diffuse?: ColorConfig;
148
186
  specular?: ColorConfig;
@@ -151,6 +189,8 @@ export interface LightDefinition {
151
189
  groundColor?: ColorConfig;
152
190
  angle?: number;
153
191
  exponent?: number;
192
+ width?: number;
193
+ height?: number;
154
194
  }
155
195
  /**
156
196
  * Configuration for the scene environment: skybox visuals and IBL (image-based lighting) for PBR reflections.
@@ -192,6 +232,11 @@ export interface LevelSoundConfig {
192
232
  * Configuration for post-processing effects applied to a level's scene.
193
233
  */
194
234
  export interface PostProcessingConfig {
235
+ /**
236
+ * Rendering backend. 'pipeline' uses DefaultRenderingPipeline, 'frameGraph' uses Frame Graph.
237
+ * Default: 'pipeline'.
238
+ */
239
+ renderer?: 'pipeline' | 'frameGraph';
195
240
  bloom?: {
196
241
  weight?: number;
197
242
  threshold?: number;
@@ -226,6 +271,32 @@ export interface PostProcessingConfig {
226
271
  edge?: number;
227
272
  color?: number;
228
273
  };
274
+ /** Volumetric lighting (Frame Graph only). Warns if used with pipeline renderer */
275
+ volumetric?: {
276
+ extinction?: number;
277
+ scattering?: number;
278
+ };
279
+ }
280
+ /**
281
+ * Configuration for clustered lighting. When enabled, all eligible point and spot lights
282
+ * are rendered via a screen-space clustering system for improved multi-light performance.
283
+ * Hemispheric, directional, and rect area lights are unaffected.
284
+ */
285
+ export interface ClusteringConfig {
286
+ enabled: boolean;
287
+ horizontalTiles?: number;
288
+ verticalTiles?: number;
289
+ depthSlices?: number;
290
+ maxRange?: number;
291
+ }
292
+ /**
293
+ * Configuration for a named selection outline layer.
294
+ * Defines the visual style for screen-space entity outlines.
295
+ */
296
+ export interface OutlineLayerConfig {
297
+ color?: ColorConfig;
298
+ thickness?: number;
299
+ occlusionStrength?: number;
229
300
  }
230
301
  /**
231
302
  * Constructor signature for Level classes.
@@ -1,4 +1,4 @@
1
- import { IStaticSoundOptions, StaticSound } from '@babylonjs/core/AudioV2/abstractAudio/staticSound';
1
+ import { IStaticSoundOptions, StaticSound } from '@babylonjs/core';
2
2
  import { AudioEngine } from "../engines/audio.d.ts";
3
3
  import { Disposable } from "../interfaces/lifecycle.d.ts";
4
4
  import { LoggingUtility } from "../utils/logger.d.ts";
@@ -35,6 +35,8 @@ export declare class BindingManager implements Disposable {
35
35
  private _activeContexts;
36
36
  /** The most recent device type that produced intentional input */
37
37
  private _lastActiveDeviceType;
38
+ /** Last input state per device, used to prime edge state on context activation */
39
+ private _lastInputState;
38
40
  /** Active captureInput() state, or null when not capturing */
39
41
  private _captureState;
40
42
  /** Event bus for handling game events */
@@ -0,0 +1,16 @@
1
+ import { Scene, SelectionOutlineLayer } from '@babylonjs/core';
2
+ import { GameEntity } from "../classes/entity.d.ts";
3
+ import { OutlineLayerConfig } from "../interfaces/level.d.ts";
4
+ export declare class OutlineManager {
5
+ private _scene;
6
+ private _layers;
7
+ constructor(scene: Scene, configs?: Record<string, OutlineLayerConfig>);
8
+ get(name: string): SelectionOutlineLayer | undefined;
9
+ create(name: string, config?: OutlineLayerConfig): SelectionOutlineLayer;
10
+ highlightEntity(layerName: string, entity: GameEntity): void;
11
+ unhighlightEntity(layerName: string, entity: GameEntity): void;
12
+ unhighlightEntityAll(entity: GameEntity): void;
13
+ clear(layerName: string): void;
14
+ dispose(): void;
15
+ private _rebuildSelection;
16
+ }
@@ -0,0 +1,70 @@
1
+ import { Constants as e, FrameGraph as t, FrameGraphBloomTask as n, FrameGraphChromaticAberrationTask as r, FrameGraphClearTextureTask as i, FrameGraphCopyToBackbufferColorTask as a, FrameGraphGeometryRendererTask as o, FrameGraphGrainTask as s, FrameGraphImageProcessingTask as c, FrameGraphObjectRendererTask as l, FrameGraphSSAO2RenderingPipelineTask as u, FrameGraphSharpenTask as d, ImageProcessingConfiguration as f } from "@babylonjs/core";
2
+ //#region src/handlers/postProcessingFrameGraph.ts
3
+ var p = {
4
+ hable: f.TONEMAPPING_STANDARD,
5
+ reinhard: f.TONEMAPPING_STANDARD,
6
+ hejidawson: f.TONEMAPPING_STANDARD,
7
+ photographic: f.TONEMAPPING_STANDARD,
8
+ aces: f.TONEMAPPING_ACES
9
+ };
10
+ async function m(m, h) {
11
+ let g = m.activeCamera;
12
+ if (!g) return;
13
+ let _ = m.getEngine(), v = new t(m), y = v.textureManager.createRenderTargetTexture("sage-fg-color", {
14
+ size: {
15
+ width: 100,
16
+ height: 100
17
+ },
18
+ sizeIsPercentage: !0,
19
+ options: {
20
+ formats: [e.TEXTUREFORMAT_RGBA],
21
+ samples: 4
22
+ }
23
+ }), b = new i("sage-clear", v);
24
+ b.targetTexture = y, v.addTask(b);
25
+ let x = new l("sage-render", v, m);
26
+ x.targetTexture = b.outputTexture, x.depthTexture = b.outputDepthTexture, x.camera = g, x.objectList = {
27
+ meshes: m.meshes,
28
+ particleSystems: m.particleSystems
29
+ }, x.isMainObjectRenderer = !0, v.addTask(x);
30
+ let S = x.outputTexture;
31
+ if (h.ssao) {
32
+ let e = new o("sage-geometry", v, m);
33
+ v.addTask(e);
34
+ let t = new u("sage-ssao", v, .5, 1);
35
+ t.sourceTexture = S, t.depthTexture = e.geometryViewDepthTexture, t.normalTexture = e.geometryViewNormalTexture, t.camera = g, v.addTask(t), h.ssao.radius !== void 0 && (t.ssao.radius = h.ssao.radius), h.ssao.samples !== void 0 && (t.ssao.samples = h.ssao.samples), h.ssao.totalStrength !== void 0 && (t.ssao.totalStrength = h.ssao.totalStrength), S = t.outputTexture;
36
+ }
37
+ if (h.bloom) {
38
+ let e = new n("sage-bloom", v, h.bloom.weight ?? .5, h.bloom.kernel ?? 128, h.bloom.threshold ?? .1, !0, h.bloom.scale ?? .5);
39
+ e.sourceTexture = S, v.addTask(e), S = e.outputTexture;
40
+ }
41
+ if (h.grain) {
42
+ let e = new s("sage-grain", v);
43
+ e.sourceTexture = S, v.addTask(e), h.grain.intensity !== void 0 && (e.postProcess.intensity = h.grain.intensity), h.grain.animated !== void 0 && (e.postProcess.animated = h.grain.animated), S = e.outputTexture;
44
+ }
45
+ if (h.sharpen) {
46
+ let e = new d("sage-sharpen", v);
47
+ e.sourceTexture = S, v.addTask(e), h.sharpen.edge !== void 0 && (e.postProcess.edgeAmount = h.sharpen.edge), h.sharpen.color !== void 0 && (e.postProcess.colorAmount = h.sharpen.color), S = e.outputTexture;
48
+ }
49
+ if (h.chromaticAberration) {
50
+ let e = new r("sage-chromab", v);
51
+ e.sourceTexture = S, v.addTask(e), h.chromaticAberration.amount !== void 0 && (e.postProcess.aberrationAmount = h.chromaticAberration.amount), S = e.outputTexture;
52
+ }
53
+ if (h.tonemap || h.vignette) {
54
+ let e = new c("sage-imgproc", v);
55
+ if (e.sourceTexture = S, v.addTask(e), h.tonemap && (e.postProcess.toneMappingEnabled = !0, h.tonemap.operator && (e.postProcess.toneMappingType = p[h.tonemap.operator] ?? f.TONEMAPPING_STANDARD)), h.vignette && (e.postProcess.vignetteEnabled = !0, h.vignette.weight !== void 0 && (e.postProcess.vignetteWeight = h.vignette.weight), h.vignette.stretch !== void 0 && (e.postProcess.vignetteStretch = h.vignette.stretch), h.vignette.color)) {
56
+ let t = h.vignette.color;
57
+ e.postProcess.vignetteColor.set(t.r, t.g, t.b, 1);
58
+ }
59
+ S = e.outputTexture;
60
+ }
61
+ h.volumetric && console.warn("[SAGE] Volumetric lighting config is recognized but automatic light detection is not yet supported. Use scene.frameGraph to add FrameGraphVolumetricLightingTask manually.");
62
+ let C = new a("sage-output", v);
63
+ C.sourceTexture = S, v.addTask(C), m.cameraToUseForPointers = g, m.frameGraph = v, _.onResizeObservable.add(async () => {
64
+ await v.buildAsync();
65
+ }), await v.buildAsync();
66
+ }
67
+ //#endregion
68
+ export { m as applyFrameGraphPostProcessing };
69
+
70
+ //# sourceMappingURL=postProcessingFrameGraph-CaMgQzR-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postProcessingFrameGraph-CaMgQzR-.js","names":[],"sources":["../src/handlers/postProcessingFrameGraph.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Frame Graph Post-Processing Backend\n//\n// Builds a directed task chain using BabylonJS Frame Graph system.\n// Each post-processing effect is a separate task chained via texture handles.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Constants,\n FrameGraph,\n FrameGraphBloomTask,\n FrameGraphChromaticAberrationTask,\n FrameGraphClearTextureTask,\n FrameGraphCopyToBackbufferColorTask,\n FrameGraphGeometryRendererTask,\n FrameGraphGrainTask,\n FrameGraphImageProcessingTask,\n FrameGraphObjectRendererTask,\n FrameGraphSSAO2RenderingPipelineTask,\n FrameGraphSharpenTask,\n type FrameGraphTextureHandle,\n ImageProcessingConfiguration,\n type Scene,\n} from '@babylonjs/core';\n\nimport type { PostProcessingConfig } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Map from user-facing tonemap operator names to BabylonJS constants.\n * BabylonJS only has STANDARD (0) and ACES (1) — the other names all map to STANDARD.\n */\nconst TONEMAP_OPERATORS : Record<string, number> = {\n hable: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n reinhard: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n hejidawson: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n photographic: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n aces: ImageProcessingConfiguration.TONEMAPPING_ACES,\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport async function applyFrameGraphPostProcessing(scene : Scene, config : PostProcessingConfig) : Promise<void>\n{\n const camera = scene.activeCamera;\n if(!camera)\n {\n return;\n }\n\n const engine = scene.getEngine();\n const fg = new FrameGraph(scene);\n\n // Create the main render target texture\n const colorTexture = fg.textureManager.createRenderTargetTexture('sage-fg-color', {\n size: { width: 100, height: 100 },\n sizeIsPercentage: true,\n options: { formats: [ Constants.TEXTUREFORMAT_RGBA ], samples: 4 },\n });\n\n // 1. Clear\n const clearTask = new FrameGraphClearTextureTask('sage-clear', fg);\n clearTask.targetTexture = colorTexture;\n fg.addTask(clearTask);\n\n // 2. Render objects\n const renderTask = new FrameGraphObjectRendererTask('sage-render', fg, scene);\n renderTask.targetTexture = clearTask.outputTexture;\n renderTask.depthTexture = clearTask.outputDepthTexture;\n renderTask.camera = camera;\n renderTask.objectList = { meshes: scene.meshes, particleSystems: scene.particleSystems };\n renderTask.isMainObjectRenderer = true;\n fg.addTask(renderTask);\n\n // Track the current output texture through the chain\n let currentTexture : FrameGraphTextureHandle = renderTask.outputTexture;\n\n // 3. SSAO (needs geometry renderer for depth/normals)\n if(config.ssao)\n {\n const geoTask = new FrameGraphGeometryRendererTask('sage-geometry', fg, scene);\n fg.addTask(geoTask);\n\n const ssaoTask = new FrameGraphSSAO2RenderingPipelineTask('sage-ssao', fg, 0.5, 1.0);\n ssaoTask.sourceTexture = currentTexture;\n ssaoTask.depthTexture = geoTask.geometryViewDepthTexture;\n ssaoTask.normalTexture = geoTask.geometryViewNormalTexture;\n ssaoTask.camera = camera;\n fg.addTask(ssaoTask);\n\n if(config.ssao.radius !== undefined) { ssaoTask.ssao.radius = config.ssao.radius; }\n if(config.ssao.samples !== undefined) { ssaoTask.ssao.samples = config.ssao.samples; }\n if(config.ssao.totalStrength !== undefined) { ssaoTask.ssao.totalStrength = config.ssao.totalStrength; }\n\n currentTexture = ssaoTask.outputTexture;\n }\n\n // 4. Bloom\n if(config.bloom)\n {\n const bloomTask = new FrameGraphBloomTask(\n 'sage-bloom',\n fg,\n config.bloom.weight ?? 0.5,\n config.bloom.kernel ?? 128,\n config.bloom.threshold ?? 0.1,\n true,\n config.bloom.scale ?? 0.5\n );\n bloomTask.sourceTexture = currentTexture;\n fg.addTask(bloomTask);\n\n currentTexture = bloomTask.outputTexture;\n }\n\n // 5. Grain\n if(config.grain)\n {\n const grainTask = new FrameGraphGrainTask('sage-grain', fg);\n grainTask.sourceTexture = currentTexture;\n fg.addTask(grainTask);\n\n if(config.grain.intensity !== undefined) { grainTask.postProcess.intensity = config.grain.intensity; }\n if(config.grain.animated !== undefined) { grainTask.postProcess.animated = config.grain.animated; }\n\n currentTexture = grainTask.outputTexture;\n }\n\n // 6. Sharpen\n if(config.sharpen)\n {\n const sharpenTask = new FrameGraphSharpenTask('sage-sharpen', fg);\n sharpenTask.sourceTexture = currentTexture;\n fg.addTask(sharpenTask);\n\n if(config.sharpen.edge !== undefined) { sharpenTask.postProcess.edgeAmount = config.sharpen.edge; }\n if(config.sharpen.color !== undefined) { sharpenTask.postProcess.colorAmount = config.sharpen.color; }\n\n currentTexture = sharpenTask.outputTexture;\n }\n\n // 7. Chromatic aberration\n if(config.chromaticAberration)\n {\n const chromTask = new FrameGraphChromaticAberrationTask('sage-chromab', fg);\n chromTask.sourceTexture = currentTexture;\n fg.addTask(chromTask);\n\n if(config.chromaticAberration.amount !== undefined)\n {\n chromTask.postProcess.aberrationAmount = config.chromaticAberration.amount;\n }\n\n currentTexture = chromTask.outputTexture;\n }\n\n // 8. Image processing (tonemap + vignette)\n if(config.tonemap || config.vignette)\n {\n const imgTask = new FrameGraphImageProcessingTask('sage-imgproc', fg);\n imgTask.sourceTexture = currentTexture;\n fg.addTask(imgTask);\n\n if(config.tonemap)\n {\n imgTask.postProcess.toneMappingEnabled = true;\n\n if(config.tonemap.operator)\n {\n imgTask.postProcess.toneMappingType\n = TONEMAP_OPERATORS[config.tonemap.operator]\n ?? ImageProcessingConfiguration.TONEMAPPING_STANDARD;\n }\n }\n\n if(config.vignette)\n {\n imgTask.postProcess.vignetteEnabled = true;\n\n if(config.vignette.weight !== undefined) { imgTask.postProcess.vignetteWeight = config.vignette.weight; }\n if(config.vignette.stretch !== undefined)\n {\n imgTask.postProcess.vignetteStretch = config.vignette.stretch;\n }\n if(config.vignette.color)\n {\n const vc = config.vignette.color;\n imgTask.postProcess.vignetteColor.set(vc.r, vc.g, vc.b, 1);\n }\n }\n\n currentTexture = imgTask.outputTexture;\n }\n\n // 9. Volumetric lighting — not yet wired up for automatic light detection\n if(config.volumetric)\n {\n console.warn(\n '[SAGE] Volumetric lighting config is recognized but automatic light detection is not yet supported. '\n + 'Use scene.frameGraph to add FrameGraphVolumetricLightingTask manually.'\n );\n }\n\n // 10. Output to screen\n const copyTask = new FrameGraphCopyToBackbufferColorTask('sage-output', fg);\n copyTask.sourceTexture = currentTexture;\n fg.addTask(copyTask);\n\n // Wire up the scene — cameraToUseForPointers is needed because activeCamera becomes null between tasks\n scene.cameraToUseForPointers = camera;\n scene.frameGraph = fg;\n\n // Rebuild on resize\n engine.onResizeObservable.add(async () =>\n {\n await fg.buildAsync();\n });\n\n // Initial build\n await fg.buildAsync();\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
package/dist/sage.d.ts CHANGED
@@ -38,6 +38,7 @@ export type { CreateEntityOptions } from "./managers/entity.d.ts";
38
38
  export type { TransitionOptions } from "./managers/level.d.ts";
39
39
  export { AudioManager } from "./managers/audio.d.ts";
40
40
  export type { ChannelInfo } from "./managers/audio.d.ts";
41
+ export { OutlineManager } from "./managers/outline.d.ts";
41
42
  export { SaveManager } from "./managers/save.d.ts";
42
43
  export type { SaveData, SerializedEntity } from "./managers/save.d.ts";
43
44
  export { generateId } from "./utils/id.d.ts";