ris-framework 0.2.0-dev.14 → 0.2.0-dev.16

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.
Files changed (160) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/README.md +8 -6
  3. package/dist/api/IFramework.d.ts +1 -1
  4. package/dist/api/IFramework.d.ts.map +1 -1
  5. package/dist/api/_export.d.ts +3 -0
  6. package/dist/api/_export.d.ts.map +1 -1
  7. package/dist/api/camera/CameraTransformUtilities.d.ts +65 -0
  8. package/dist/api/camera/CameraTransformUtilities.d.ts.map +1 -0
  9. package/dist/api/camera/ICamera.d.ts +36 -2
  10. package/dist/api/camera/ICamera.d.ts.map +1 -1
  11. package/dist/api/camera/OrbitCamera.d.ts +22 -14
  12. package/dist/api/camera/OrbitCamera.d.ts.map +1 -1
  13. package/dist/api/camera/OrthographicCamera.d.ts +33 -11
  14. package/dist/api/camera/OrthographicCamera.d.ts.map +1 -1
  15. package/dist/api/camera/PerspectiveCamera.d.ts +32 -18
  16. package/dist/api/camera/PerspectiveCamera.d.ts.map +1 -1
  17. package/dist/api/content/ContentManager.d.ts.map +1 -1
  18. package/dist/api/lights/AmbientLight.d.ts +7 -2
  19. package/dist/api/lights/AmbientLight.d.ts.map +1 -1
  20. package/dist/api/lights/DirectionalLight.d.ts +18 -4
  21. package/dist/api/lights/DirectionalLight.d.ts.map +1 -1
  22. package/dist/api/lights/ILight.d.ts +18 -0
  23. package/dist/api/lights/ILight.d.ts.map +1 -0
  24. package/dist/api/lights/PointLight.d.ts +17 -4
  25. package/dist/api/lights/PointLight.d.ts.map +1 -1
  26. package/dist/api/lights/_export.d.ts +1 -0
  27. package/dist/api/lights/_export.d.ts.map +1 -1
  28. package/dist/api/material/ForwardBlinnPhongMaterial.d.ts +2 -2
  29. package/dist/api/material/ForwardBlinnPhongMaterial.d.ts.map +1 -1
  30. package/dist/api/material/IMaterial.d.ts +7 -0
  31. package/dist/api/material/IMaterial.d.ts.map +1 -1
  32. package/dist/api/material/IMaterialFactory.d.ts +3 -2
  33. package/dist/api/material/IMaterialFactory.d.ts.map +1 -1
  34. package/dist/api/material/IUnlitMaterialDescriptor.d.ts +16 -0
  35. package/dist/api/material/IUnlitMaterialDescriptor.d.ts.map +1 -0
  36. package/dist/api/material/MaterialFactory.d.ts +3 -2
  37. package/dist/api/material/MaterialFactory.d.ts.map +1 -1
  38. package/dist/api/material/SpriteMaterial.d.ts +3 -0
  39. package/dist/api/material/SpriteMaterial.d.ts.map +1 -1
  40. package/dist/api/material/UnlitMaterial.d.ts +5 -12
  41. package/dist/api/material/UnlitMaterial.d.ts.map +1 -1
  42. package/dist/api/material/_export.d.ts +1 -1
  43. package/dist/api/material/_export.d.ts.map +1 -1
  44. package/dist/api/meshes/IRenderableMesh.d.ts +3 -14
  45. package/dist/api/meshes/IRenderableMesh.d.ts.map +1 -1
  46. package/dist/api/meshes/RenderableMesh.d.ts +5 -20
  47. package/dist/api/meshes/RenderableMesh.d.ts.map +1 -1
  48. package/dist/api/rendering/IGraphicsDevice.d.ts +2 -2
  49. package/dist/api/rendering/IGraphicsDevice.d.ts.map +1 -1
  50. package/dist/api/rendering/IRenderer.d.ts +6 -4
  51. package/dist/api/rendering/IRenderer.d.ts.map +1 -1
  52. package/dist/api/rendering/_export-rendering.d.ts +1 -0
  53. package/dist/api/rendering/_export-rendering.d.ts.map +1 -1
  54. package/dist/api/rendering/primitive/IPrimitiveStateDescriptor.d.ts +24 -0
  55. package/dist/api/rendering/primitive/IPrimitiveStateDescriptor.d.ts.map +1 -0
  56. package/dist/api/rendering/primitive/_export.d.ts +1 -1
  57. package/dist/api/rendering/primitive/_export.d.ts.map +1 -1
  58. package/dist/api/rendering/render-pipelines/IMainRenderTargetRenderPipeline.d.ts +5 -0
  59. package/dist/api/rendering/render-pipelines/IMainRenderTargetRenderPipeline.d.ts.map +1 -1
  60. package/dist/api/rendering/texture/ITextureDescriptor.d.ts +5 -1
  61. package/dist/api/rendering/texture/ITextureDescriptor.d.ts.map +1 -1
  62. package/dist/api/rendering/texture/ITextureFactory.d.ts +3 -1
  63. package/dist/api/rendering/texture/ITextureFactory.d.ts.map +1 -1
  64. package/dist/api/rendering/texture/TextureUtilities.d.ts +7 -0
  65. package/dist/api/rendering/texture/TextureUtilities.d.ts.map +1 -1
  66. package/dist/api/scene/ISceneObject.d.ts +12 -0
  67. package/dist/api/scene/ISceneObject.d.ts.map +1 -0
  68. package/dist/api/scene/Scene.d.ts +17 -6
  69. package/dist/api/scene/Scene.d.ts.map +1 -1
  70. package/dist/api/scene/node/Node.d.ts +26 -0
  71. package/dist/api/scene/node/Node.d.ts.map +1 -0
  72. package/dist/api/scene/node/TransformNode.d.ts +46 -0
  73. package/dist/api/scene/node/TransformNode.d.ts.map +1 -0
  74. package/dist/core/material/UnlitMaterialDescriptorUtilities.d.ts +17 -0
  75. package/dist/core/material/UnlitMaterialDescriptorUtilities.d.ts.map +1 -0
  76. package/dist/core/rendering/AGraphicsDevice.d.ts +2 -2
  77. package/dist/core/rendering/AGraphicsDevice.d.ts.map +1 -1
  78. package/dist/core/rendering/ARenderer.d.ts +5 -2
  79. package/dist/core/rendering/ARenderer.d.ts.map +1 -1
  80. package/dist/core/rendering/primitive/PrimitiveStateDescriptorUtilities.d.ts +15 -0
  81. package/dist/core/rendering/primitive/PrimitiveStateDescriptorUtilities.d.ts.map +1 -0
  82. package/dist/core/rendering/texture/TextureDescriptorUtilities.d.ts +2 -2
  83. package/dist/core/rendering/texture/TextureDescriptorUtilities.d.ts.map +1 -1
  84. package/dist/index.js +704 -272
  85. package/dist/webgl/WebGlGraphicsDevice.d.ts +2 -2
  86. package/dist/webgl/WebGlGraphicsDevice.d.ts.map +1 -1
  87. package/dist/webgl/lights/WebGlLightsBuffer.d.ts.map +1 -1
  88. package/dist/webgl/primitive/WebGlPrimitiveState.d.ts +3 -3
  89. package/dist/webgl/primitive/WebGlPrimitiveState.d.ts.map +1 -1
  90. package/dist/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.d.ts +8 -0
  91. package/dist/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.d.ts.map +1 -1
  92. package/dist/webgl/texture/WebGlTextureFactory.d.ts +1 -1
  93. package/dist/webgl/texture/WebGlTextureFactory.d.ts.map +1 -1
  94. package/dist/webgl/utilities/WebGlTextureUtilities.d.ts.map +1 -1
  95. package/dist/webgpu/WebGpuGraphicsDevice.d.ts +2 -2
  96. package/dist/webgpu/WebGpuGraphicsDevice.d.ts.map +1 -1
  97. package/dist/webgpu/primitive/WebGpuPrimitiveState.d.ts +2 -2
  98. package/dist/webgpu/primitive/WebGpuPrimitiveState.d.ts.map +1 -1
  99. package/dist/webgpu/render-pipelines/WebGpuMainRenderTargetRenderPipeline.d.ts +5 -0
  100. package/dist/webgpu/render-pipelines/WebGpuMainRenderTargetRenderPipeline.d.ts.map +1 -1
  101. package/dist/webgpu/texture/WebGpuTextureFactory.d.ts.map +1 -1
  102. package/package.json +2 -2
  103. package/src/Framework.ts +1 -1
  104. package/src/api/IFramework.ts +1 -1
  105. package/src/api/_export.ts +3 -0
  106. package/src/api/camera/CameraTransformUtilities.ts +182 -0
  107. package/src/api/camera/ICamera.ts +42 -2
  108. package/src/api/camera/OrbitCamera.ts +71 -38
  109. package/src/api/camera/OrthographicCamera.ts +71 -28
  110. package/src/api/camera/PerspectiveCamera.ts +69 -33
  111. package/src/api/content/ContentManager.ts +9 -5
  112. package/src/api/lights/AmbientLight.ts +8 -2
  113. package/src/api/lights/DirectionalLight.ts +30 -5
  114. package/src/api/lights/ILight.ts +22 -0
  115. package/src/api/lights/PointLight.ts +26 -5
  116. package/src/api/lights/_export.ts +1 -0
  117. package/src/api/material/ForwardBlinnPhongMaterial.ts +2 -2
  118. package/src/api/material/IMaterial.ts +8 -0
  119. package/src/api/material/IMaterialFactory.ts +3 -2
  120. package/src/api/material/IUnlitMaterialDescriptor.ts +18 -0
  121. package/src/api/material/MaterialFactory.ts +4 -3
  122. package/src/api/material/SpriteMaterial.ts +4 -0
  123. package/src/api/material/UnlitMaterial.ts +11 -18
  124. package/src/api/material/_export.ts +1 -1
  125. package/src/api/meshes/IRenderableMesh.ts +3 -18
  126. package/src/api/meshes/RenderableMesh.ts +15 -45
  127. package/src/api/rendering/IGraphicsDevice.ts +2 -2
  128. package/src/api/rendering/IRenderer.ts +6 -4
  129. package/src/api/rendering/_export-rendering.ts +2 -1
  130. package/src/api/rendering/primitive/IPrimitiveStateDescriptor.ts +27 -0
  131. package/src/api/rendering/primitive/_export.ts +1 -1
  132. package/src/api/rendering/render-pipelines/IMainRenderTargetRenderPipeline.ts +6 -0
  133. package/src/api/rendering/texture/ITextureDescriptor.ts +6 -1
  134. package/src/api/rendering/texture/ITextureFactory.ts +3 -1
  135. package/src/api/rendering/texture/TextureUtilities.ts +16 -0
  136. package/src/api/scene/ISceneObject.ts +13 -0
  137. package/src/api/scene/Scene.ts +52 -11
  138. package/src/api/scene/node/Node.ts +54 -0
  139. package/src/api/scene/node/TransformNode.ts +88 -0
  140. package/src/core/material/UnlitMaterialDescriptorUtilities.ts +21 -0
  141. package/src/core/rendering/AGraphicsDevice.ts +2 -2
  142. package/src/core/rendering/ARenderer.ts +23 -5
  143. package/src/core/rendering/primitive/PrimitiveStateDescriptorUtilities.ts +22 -0
  144. package/src/core/rendering/texture/TextureDescriptorUtilities.ts +3 -2
  145. package/src/webgl/WebGlGraphicsDevice.ts +2 -3
  146. package/src/webgl/lights/WebGlLightsBuffer.ts +21 -8
  147. package/src/webgl/primitive/WebGlPrimitiveState.ts +8 -6
  148. package/src/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.ts +14 -0
  149. package/src/webgl/texture/WebGlTextureFactory.ts +27 -16
  150. package/src/webgl/utilities/WebGlTextureUtilities.ts +10 -4
  151. package/src/webgpu/WebGpuGraphicsDevice.ts +3 -3
  152. package/src/webgpu/primitive/WebGpuPrimitiveState.ts +7 -5
  153. package/src/webgpu/render-pipelines/WebGpuMainRenderTargetRenderPipeline.ts +6 -0
  154. package/src/webgpu/texture/WebGpuTextureFactory.ts +1 -1
  155. package/dist/api/material/ICameraMaterial.d.ts +0 -18
  156. package/dist/api/material/ICameraMaterial.d.ts.map +0 -1
  157. package/dist/api/rendering/primitive/PrimitiveStateDescriptor.d.ts +0 -24
  158. package/dist/api/rendering/primitive/PrimitiveStateDescriptor.d.ts.map +0 -1
  159. package/src/api/material/ICameraMaterial.ts +0 -22
  160. package/src/api/rendering/primitive/PrimitiveStateDescriptor.ts +0 -27
@@ -195,6 +195,7 @@ export class TextureUtilities {
195
195
  [TextureFormat.BC7_RGBA_UNORM]: KtxTranscodeFormat.BC7_RGBA,
196
196
  [TextureFormat.ETC2_RGBA8_UNORM]: KtxTranscodeFormat.ETC2_RGBA,
197
197
  [TextureFormat.PVRTC_4BPPV1_RGBA] : KtxTranscodeFormat.PVRTC1_4_RGBA,
198
+ [TextureFormat.PVRTC_4BPPV1_RGB] : KtxTranscodeFormat.PVRTC1_4_RGB,
198
199
  // Basis transcodes to the same blocks for sRGB; only the GPU format differs.
199
200
  [TextureFormat.RGBA_8_UNORM_SRGB]: KtxTranscodeFormat.RGBA32,
200
201
  [TextureFormat.ASTC_4X4_RGBA_SRGB]: KtxTranscodeFormat.ASTC_4X4_RGBA,
@@ -343,4 +344,19 @@ export class TextureUtilities {
343
344
  public static isSrgbFormat(textureFormat: TextureFormat) : boolean {
344
345
  return this._srgbFormats.includes(textureFormat);
345
346
  }
347
+
348
+ /**
349
+ * Returns the requested format if it is the file's format or its sRGB/linear variant.
350
+ * @param fileFormat The format the data is stored in.
351
+ * @param requested The explicitly requested format.
352
+ * @throws If the requested format cannot reinterpret the data.
353
+ */
354
+ public static resolveExplicitFormat(fileFormat: TextureFormat, requested: TextureFormat): TextureFormat {
355
+ if (requested == fileFormat
356
+ || requested == TextureUtilities.convertToSrgbVariant(fileFormat)
357
+ || requested == TextureUtilities.convertToLinearVariant(fileFormat)) {
358
+ return requested;
359
+ }
360
+ throw new Error(`Texture format ${requested} is not compatible with the file's format ${fileFormat}; only its sRGB or linear variant can be used.`);
361
+ }
346
362
  }
@@ -0,0 +1,13 @@
1
+ import type {TransformNode} from "./node/TransformNode.ts";
2
+
3
+ /**
4
+ * An object that can be placed in a scene: it has a transform.
5
+ * @public
6
+ */
7
+ export interface ISceneObject {
8
+
9
+ /**
10
+ * The transform node.
11
+ */
12
+ transform: TransformNode;
13
+ }
@@ -5,13 +5,14 @@ import type {IDisposable} from "../core/IDisposable.ts";
5
5
  import type {IFramework} from "../IFramework.ts";
6
6
  import type {IRenderableMesh} from "../meshes/IRenderableMesh.ts";
7
7
  import {SceneMesh} from "./SceneMesh.ts";
8
- import {isCameraMaterial} from "../material/ICameraMaterial.ts";
9
8
  import {isLightsMaterial} from "../material/ILightsMaterial.ts";
10
9
  import type {GameTime} from "../time/GameTime.ts";
11
10
  import type {ICamera} from "../camera/ICamera.ts";
11
+ import type {ISceneObject} from "./ISceneObject.ts";
12
+ import {TransformNode} from "./node/TransformNode.ts";
12
13
 
13
- /** Anything a scene can hold: a mesh, a light (ambient, directional, point) or a camera. */
14
- export type SceneItem = IRenderableMesh | SceneMesh | AmbientLight | DirectionalLight | PointLight | ICamera;
14
+ /** Anything a scene can hold: a mesh, a scene object, a light (ambient, directional, point) or a camera. */
15
+ export type SceneItem = IRenderableMesh | SceneMesh | ISceneObject | AmbientLight | DirectionalLight | PointLight | ICamera;
15
16
 
16
17
  /**
17
18
  * The scene. Lights and cameras given a scene in their constructor add themselves through {@link add};
@@ -22,6 +23,7 @@ export type SceneItem = IRenderableMesh | SceneMesh | AmbientLight | Directional
22
23
  export class Scene implements IDisposable {
23
24
 
24
25
  private readonly _sceneMeshes: SceneMesh[] = [];
26
+ private readonly _nodes: ISceneObject[] = [];
25
27
  private readonly _directionalLights: DirectionalLight[] = [];
26
28
  private readonly _pointLights: PointLight[] = [];
27
29
 
@@ -71,6 +73,11 @@ export class Scene implements IDisposable {
71
73
  return this._sceneMeshes;
72
74
  }
73
75
 
76
+ /** The scene objects (anything with a transform, including root meshes), in the order they were added. */
77
+ public get nodes(): readonly ISceneObject[] {
78
+ return this._nodes;
79
+ }
80
+
74
81
  /** The root meshes in this scene, in the order they were added. */
75
82
  public get meshes(): readonly IRenderableMesh[] {
76
83
  return this._sceneMeshes.map(s => s.mesh);
@@ -103,9 +110,13 @@ export class Scene implements IDisposable {
103
110
  * Adds a mesh, a light or a camera. Adding an item that is already in the scene does nothing.
104
111
  * - Renderable mesh or {@link SceneMesh}: drawn by {@link render}; its materials get the scene camera and lights.
105
112
  * A mesh is wrapped in a `SceneMesh`, so {@link dispose} also disposes its materials.
106
- * - Ambient light: added to {@link ambientLights} (use {@link activeAmbientLight} to make it the lit one).
113
+ * The mesh is also added to {@link nodes}.
114
+ * - {@link ISceneObject} (anything with a `transform`): added to {@link nodes}; {@link update} updates its transform.
115
+ * - Lights are also added to {@link nodes} (they are scene objects).
116
+ * - Ambient light: added to {@link ambientLights} (use {@link activeAmbientLight} to make it the lit one).
107
117
  * - Directional / point light: added to {@link directionalLights} / {@link pointLights}.
108
- * - Camera: becomes the scene {@link camera}.
118
+ * - Camera: becomes the scene {@link camera} and is added to {@link nodes} (cameras are scene objects).
119
+ * Checked before the generic scene object case.
109
120
  * Constructors given a scene (lights, the perspective camera) call this themselves.
110
121
  * @param item The item to add.
111
122
  * @throws Error when all slots for a light kind are in use (3 directional, 5 point), or for an unsupported item.
@@ -113,10 +124,15 @@ export class Scene implements IDisposable {
113
124
  public add(item: SceneItem): void {
114
125
  if (item instanceof AmbientLight) {
115
126
  this._addAmbientLight(item);
127
+ this._addNode(item);
116
128
  } else if (item instanceof DirectionalLight || item instanceof PointLight) {
117
129
  this._addLight(item);
130
+ this._addNode(item);
118
131
  } else if (Scene._isCamera(item)) {
119
132
  this.camera = item;
133
+ if (Scene._isSceneObject(item)) {
134
+ this._addNode(item);
135
+ }
120
136
  } else if (item instanceof SceneMesh || Scene._isMesh(item)) {
121
137
  const mesh = item instanceof SceneMesh ? item.mesh : item;
122
138
  if (!this._sceneMeshes.some(s => s.mesh === mesh)) {
@@ -124,8 +140,11 @@ export class Scene implements IDisposable {
124
140
  this._sceneMeshes.push(sceneMesh);
125
141
  this._bind(sceneMesh);
126
142
  }
143
+ this._addNode(mesh);
144
+ } else if (Scene._isSceneObject(item)) {
145
+ this._addNode(item);
127
146
  } else {
128
- throw new Error("Scene.add: expected a mesh, an AmbientLight, DirectionalLight, PointLight or a camera.");
147
+ throw new Error("Scene.add: expected a mesh, a scene object, an AmbientLight, DirectionalLight, PointLight or a camera.");
129
148
  }
130
149
  }
131
150
 
@@ -133,7 +152,7 @@ export class Scene implements IDisposable {
133
152
  * Removes a mesh (or scene mesh), a light or a camera. The mesh and its materials are not disposed.
134
153
  * Removing the active camera clears {@link camera}.
135
154
  * @param item The item to remove.
136
- * @returns True when the item was in the scene (for a camera: was the active camera).
155
+ * @returns True when the item was in the scene.
137
156
  * @throws Error when removing the active ambient light (set another {@link activeAmbientLight} first), or for an unsupported item.
138
157
  */
139
158
  public remove(item: SceneItem): boolean {
@@ -141,16 +160,19 @@ export class Scene implements IDisposable {
141
160
  if (item === this._activeAmbientLight) {
142
161
  throw new Error("Scene.remove: cannot remove the active ambient light; set scene.activeAmbientLight to another light first.");
143
162
  }
163
+ Scene._removeFrom(this._nodes, item);
144
164
  return Scene._removeFrom(this.ambientLights, item);
145
165
  }
146
166
  if (item instanceof DirectionalLight || item instanceof PointLight) {
167
+ Scene._removeFrom(this._nodes, item);
147
168
  return item instanceof DirectionalLight
148
169
  ? Scene._removeFrom(this._directionalLights, item)
149
170
  : Scene._removeFrom(this._pointLights, item);
150
171
  }
151
172
  if (Scene._isCamera(item)) {
173
+ const wasNode = Scene._removeFrom(this._nodes, item);
152
174
  if (item !== this._activeCamera) {
153
- return false;
175
+ return wasNode;
154
176
  }
155
177
  this._activeCamera = undefined;
156
178
  return true;
@@ -158,13 +180,17 @@ export class Scene implements IDisposable {
158
180
  if (item instanceof SceneMesh || Scene._isMesh(item)) {
159
181
  const mesh = item instanceof SceneMesh ? item.mesh : item;
160
182
  const index = this._sceneMeshes.findIndex(s => s.mesh === mesh);
183
+ Scene._removeFrom(this._nodes, mesh);
161
184
  if (index < 0) {
162
185
  return false;
163
186
  }
164
187
  this._sceneMeshes.splice(index, 1);
165
188
  return true;
166
189
  }
167
- throw new Error("Scene.remove: expected a mesh, an AmbientLight, DirectionalLight, PointLight or a camera.");
190
+ if (Scene._isSceneObject(item)) {
191
+ return Scene._removeFrom(this._nodes, item);
192
+ }
193
+ throw new Error("Scene.remove: expected a mesh, a scene object, an AmbientLight, DirectionalLight, PointLight or a camera.");
168
194
  }
169
195
 
170
196
  /**
@@ -220,6 +246,17 @@ export class Scene implements IDisposable {
220
246
  && "projectionViewBuffer" in camera && "viewMatrix" in camera && typeof camera.updateBuffers === "function";
221
247
  }
222
248
 
249
+ /** Scene objects have a TransformNode as transform. */
250
+ private static _isSceneObject(item: unknown): item is ISceneObject {
251
+ return typeof item === "object" && item !== null && (item as Partial<ISceneObject>).transform instanceof TransformNode;
252
+ }
253
+
254
+ private _addNode(node: ISceneObject): void {
255
+ if (!this._nodes.includes(node)) {
256
+ this._nodes.push(node);
257
+ }
258
+ }
259
+
223
260
  /** Renderable meshes have render() and sub meshes. */
224
261
  private static _isMesh(item: unknown): item is IRenderableMesh {
225
262
  const mesh = item as Partial<IRenderableMesh> | null;
@@ -227,12 +264,15 @@ export class Scene implements IDisposable {
227
264
  }
228
265
 
229
266
  /**
230
- * Updates the scene: assigns {@link camera}, the active ambient light and the light lists to the mesh materials.
267
+ * Updates the scene: updates the transforms of {@link nodes}, assigns {@link camera}, the active ambient light and the light lists to the mesh materials.
231
268
  * Each lights material uploads its lights buffer when drawn, only if the lights changed.
232
269
  * @param _time The game time.
233
270
  * @public
234
271
  */
235
272
  update(_time: GameTime): void {
273
+ for (const node of this._nodes) {
274
+ node.transform.update(_time);
275
+ }
236
276
  for (const sceneMesh of this._sceneMeshes) {
237
277
  this._bind(sceneMesh);
238
278
  }
@@ -266,7 +306,7 @@ export class Scene implements IDisposable {
266
306
  }
267
307
  for (const material of sceneMesh.materials) {
268
308
  // The setter rebinds buffers, so skip when unchanged.
269
- if (isCameraMaterial(material) && material.camera !== camera) {
309
+ if (material.camera !== camera) {
270
310
  material.camera = camera;
271
311
  }
272
312
  }
@@ -293,5 +333,6 @@ export class Scene implements IDisposable {
293
333
  sceneMesh.dispose();
294
334
  }
295
335
  this._sceneMeshes.length = 0;
336
+ this._nodes.length = 0;
296
337
  }
297
338
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A node in a hierarchy. A node has at most one parent; use {@link addChild} and {@link removeChild}
3
+ * to change the hierarchy (they keep `parent` and `children` in sync).
4
+ * @public
5
+ */
6
+ export class Node {
7
+
8
+ private readonly _children: Node[] = [];
9
+ private _parent?: Node;
10
+
11
+ /** The child nodes of this node. */
12
+ public get children(): readonly Node[] {
13
+ return this._children;
14
+ }
15
+
16
+ /** The parent node of this node, if any. */
17
+ public get parent(): Node | undefined {
18
+ return this._parent;
19
+ }
20
+
21
+ /**
22
+ * Adds a child. It is removed from its previous parent first. Adding an existing child does nothing.
23
+ * @param child The child to add.
24
+ * @throws Error when adding the node to itself or to one of its descendants.
25
+ */
26
+ public addChild(child: Node): void {
27
+ if (child._parent === this) {
28
+ return;
29
+ }
30
+ for (let n: Node | undefined = this; n; n = n._parent) {
31
+ if (n === child) {
32
+ throw new Error("Node.addChild: a node cannot be added to itself or to one of its descendants.");
33
+ }
34
+ }
35
+ child._parent?.removeChild(child);
36
+ child._parent = this;
37
+ this._children.push(child);
38
+ }
39
+
40
+ /**
41
+ * Removes a child.
42
+ * @param child The child to remove.
43
+ * @returns True when it was a child of this node.
44
+ */
45
+ public removeChild(child: Node): boolean {
46
+ const index = this._children.indexOf(child);
47
+ if (index < 0) {
48
+ return false;
49
+ }
50
+ this._children.splice(index, 1);
51
+ child._parent = undefined;
52
+ return true;
53
+ }
54
+ }
@@ -0,0 +1,88 @@
1
+ import {mat4, quat, vec3} from "gl-matrix";
2
+ import type {GameTime} from "../../time/GameTime.ts";
3
+ import {Node} from "./Node.ts";
4
+
5
+ /**
6
+ * The node responsible for transformations in the world.
7
+ *
8
+ * The matrices are computed lazily: reading {@link modelMatrix} recomputes them when position, rotation or scale
9
+ * changed (also through in-place edits), so {@link update} does not have to be called before the first render.
10
+ * When the parent is a `TransformNode`, {@link modelMatrix} is the world matrix `parent.modelMatrix * localMatrix`.
11
+ * @public
12
+ */
13
+ export class TransformNode extends Node {
14
+
15
+ private readonly _rotationQuat = quat.create();
16
+ private readonly _localMatrix = mat4.create();
17
+ private readonly _worldMatrix = mat4.create();
18
+ // Cached copies to detect in-place edits; NaN forces the first computation.
19
+ private readonly _lastPosition = vec3.fromValues(NaN, NaN, NaN);
20
+ private readonly _lastRotation = vec3.create();
21
+ private readonly _lastScale = vec3.create();
22
+
23
+ /**
24
+ * The constructor.
25
+ */
26
+ public constructor() {
27
+ super();
28
+
29
+ this.position = vec3.fromValues(0, 0, 0);
30
+ this.rotation = vec3.fromValues(0, 0, 0);
31
+ this.scale = vec3.fromValues(1, 1, 1);
32
+ }
33
+
34
+ /** Position relative to the parent (the world, without one). Defaults to the origin. In-place edits are detected. */
35
+ public position: vec3;
36
+
37
+ /**
38
+ * Euler rotation in radians, applied in Y, X, Z order (yaw, pitch, roll): `R = Ry * Rx * Rz`.
39
+ * Defaults to (0, 0, 0). In-place edits are detected.
40
+ */
41
+ public rotation: vec3;
42
+
43
+ /** Scale. Defaults to (1, 1, 1). In-place edits are detected. */
44
+ public scale: vec3;
45
+
46
+ /** The local matrix `T * R * S`, recomputed only when position, rotation or scale changed. */
47
+ public get localMatrix(): mat4 {
48
+ if (!vec3.exactEquals(this._lastPosition, this.position)
49
+ || !vec3.exactEquals(this._lastRotation, this.rotation)
50
+ || !vec3.exactEquals(this._lastScale, this.scale)) {
51
+ vec3.copy(this._lastPosition, this.position);
52
+ vec3.copy(this._lastRotation, this.rotation);
53
+ vec3.copy(this._lastScale, this.scale);
54
+ const q = quat.identity(this._rotationQuat);
55
+ quat.rotateY(q, q, this.rotation[1]);
56
+ quat.rotateX(q, q, this.rotation[0]);
57
+ quat.rotateZ(q, q, this.rotation[2]);
58
+ mat4.fromRotationTranslationScale(this._localMatrix, q, this.position, this.scale);
59
+ }
60
+ return this._localMatrix;
61
+ }
62
+
63
+ /**
64
+ * The model (world) matrix: {@link localMatrix}, or `parent.modelMatrix * localMatrix` when the parent is a
65
+ * `TransformNode`. Always the same matrix object for a node without a parent transform.
66
+ */
67
+ public get modelMatrix(): mat4 {
68
+ const parent = this.parent;
69
+ if (parent instanceof TransformNode) {
70
+ return mat4.multiply(this._worldMatrix, parent.modelMatrix, this.localMatrix);
71
+ }
72
+ return this.localMatrix;
73
+ }
74
+
75
+ /**
76
+ * Updates the node and its transform children, recomputing the matrices if needed.
77
+ * Called by `Scene.update` for scene nodes; optional otherwise.
78
+ * @param gameTime The {@link GameTime}
79
+ */
80
+ public update(gameTime: GameTime): void {
81
+ void this.modelMatrix;
82
+ for (const child of this.children) {
83
+ if (child instanceof TransformNode) {
84
+ child.update(gameTime);
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,21 @@
1
+ import type {IUnlitMaterialDescriptor} from "../../api/material/IUnlitMaterialDescriptor.ts";
2
+
3
+ /**
4
+ * An unlit material descriptor with every field present. `undefined` means "use the default":
5
+ * the pipeline's default primitive state, and no camera (a placeholder projection view buffer).
6
+ * @internal
7
+ */
8
+ export type ResolvedUnlitMaterialDescriptor = { [K in keyof Required<IUnlitMaterialDescriptor>]: IUnlitMaterialDescriptor[K] };
9
+
10
+ /**
11
+ * Fills in the defaults for every missing field of an unlit material descriptor.
12
+ * @param desc The descriptor, possibly partial or missing.
13
+ * @returns A new descriptor with the defaults applied.
14
+ * @internal
15
+ */
16
+ export function resolveUnlitMaterialDescriptor(desc?: IUnlitMaterialDescriptor): ResolvedUnlitMaterialDescriptor {
17
+ return {
18
+ primitiveState: desc?.primitiveState,
19
+ camera: desc?.camera,
20
+ };
21
+ }
@@ -8,7 +8,7 @@ import {
8
8
  type IBlendState,
9
9
  type IPrimitiveState,
10
10
  type IGraphicsDevice, SwapChainDescriptor, type RenderPassDescriptor, SamplerFilter, MipMapSamplerFilter,
11
- type ISwapChain, type IRenderPass, type PrimitiveStateDescriptor
11
+ type ISwapChain, type IRenderPass, type IPrimitiveStateDescriptor
12
12
  } from "../../api/index.ts";
13
13
  import {PowerPreferenceType} from "../../api/rendering/PowerPreference.ts";
14
14
 
@@ -103,7 +103,7 @@ export abstract class AGraphicsDevice implements IGraphicsDevice {
103
103
  public abstract createBlendState(descriptor: BlendStateDescriptor): IBlendState;
104
104
 
105
105
  /** @inheritdoc */
106
- public abstract createPrimitiveState(descriptor?: PrimitiveStateDescriptor ): IPrimitiveState;
106
+ public abstract createPrimitiveState(descriptor?: IPrimitiveStateDescriptor): IPrimitiveState;
107
107
 
108
108
  /** @inheritDoc */
109
109
  public abstract setupDebugCallback(): void;
@@ -36,6 +36,7 @@ export abstract class ARenderer implements ITempRenderer {
36
36
  private _swapChainRequiresResize: boolean = false;
37
37
  private _mainRenderPassDirty = true;
38
38
  private _clearColor: Color = Color.lightPink();
39
+ private _colorSpace: ColorSpace;
39
40
 
40
41
  protected _mainRenderTargetRequiresResize: boolean = false;
41
42
  protected _swapChain: ISwapChain = null!;
@@ -46,6 +47,7 @@ export abstract class ARenderer implements ITempRenderer {
46
47
  protected _mainRenderTargetPipeline?: IMainRenderTargetRenderPipeline;
47
48
  protected _renderConfiguration: IRendererConfiguration;
48
49
 
50
+
49
51
  /** @inheritdoc */
50
52
  public get graphicsDevice(): IGraphicsDevice {
51
53
  return this._graphicsDevice;
@@ -67,10 +69,27 @@ export abstract class ARenderer implements ITempRenderer {
67
69
  }
68
70
 
69
71
  /** @inheritdoc */
70
- public readonly preferredTextureFormat: TextureFormat;
72
+ public get preferredTextureFormat(): TextureFormat {
73
+ return this._colorSpace == ColorSpace.SRGB ? TextureFormat.RGBA_8_UNORM_SRGB : TextureFormat.RGBA_8_UNORM;
74
+ }
75
+
76
+ /** @inheritDoc */
77
+ public get colorSpace(): ColorSpace {
78
+ return this._colorSpace;
79
+ }
71
80
 
72
81
  /** @inheritDoc */
73
- public readonly preferredColorSpace: ColorSpace;
82
+ public set colorSpace(colorSpace: ColorSpace) {
83
+ if (colorSpace == this._colorSpace) {
84
+ return;
85
+ }
86
+ this._colorSpace = colorSpace;
87
+ if (this._mainRenderTargetPipeline) {
88
+ this._mainRenderTargetPipeline.colorSpace = colorSpace;
89
+ // The main render target's format follows the color space, so recreate it on the next frame.
90
+ this._mainRenderTargetRequiresResize = true;
91
+ }
92
+ }
74
93
 
75
94
  /** @inheritdoc */
76
95
  public get preferredDepthStencilFormat(): TextureFormat {
@@ -111,9 +130,7 @@ export abstract class ARenderer implements ITempRenderer {
111
130
  this._windowManager = framework.windowManager;
112
131
  this._currentBackBufferSize[0] = this._windowManager.canvas.width;
113
132
  this._currentBackBufferSize[1] = this._windowManager.canvas.height;
114
- this.preferredColorSpace = configuration.textureColorSpace;
115
- this.preferredTextureFormat = configuration.textureColorSpace == ColorSpace.SRGB
116
- ? TextureFormat.RGBA_8_UNORM_SRGB : TextureFormat.RGBA_8_UNORM;
133
+ this._colorSpace = configuration.textureColorSpace;
117
134
  }
118
135
 
119
136
  backend: RenderingBackend = RenderingBackend.WEB_GL;
@@ -192,6 +209,7 @@ export abstract class ARenderer implements ITempRenderer {
192
209
  this._mainRenderTargetPipeline = this._framework
193
210
  .renderPipelineFactory
194
211
  .createMainFrameBufferPipeline(this._mainRenderTarget);
212
+ this._mainRenderTargetPipeline.colorSpace = this._colorSpace;
195
213
 
196
214
  this._createMainRenderPass();
197
215
  this._mainRenderTargetRequiresResize = false;
@@ -0,0 +1,22 @@
1
+ import {CullMode, FrontFace, type IPrimitiveStateDescriptor, PrimitiveTopology} from "../../../api/index.ts";
2
+
3
+ /**
4
+ * A primitive state descriptor with all defaults applied.
5
+ * @internal
6
+ */
7
+ export type ResolvedPrimitiveStateDescriptor = Required<IPrimitiveStateDescriptor>;
8
+
9
+ /**
10
+ * Fills in the defaults for every missing field of a primitive state descriptor:
11
+ * TRIANGLE_LIST topology, BACK culling and CCW front face.
12
+ * @param desc The descriptor, possibly partial or missing.
13
+ * @returns A new descriptor with the defaults applied.
14
+ * @internal
15
+ */
16
+ export function resolvePrimitiveStateDescriptor(desc?: IPrimitiveStateDescriptor): ResolvedPrimitiveStateDescriptor {
17
+ return {
18
+ topology: desc?.topology ?? PrimitiveTopology.TRIANGLE_LIST,
19
+ cullFace: desc?.cullFace ?? CullMode.BACK,
20
+ frontFace: desc?.frontFace ?? FrontFace.CCW,
21
+ };
22
+ }
@@ -1,11 +1,11 @@
1
1
  import {type ITextureDescriptor, TextureFormat, TextureUsage} from "../../../api/index.ts";
2
2
 
3
3
  /**
4
- * A texture descriptor with all defaults applied. Only `data` and `blockSize` stay optional.
4
+ * A texture descriptor with all defaults applied. Only `data`, `blockSize` and `preferredColorSpace` stay optional.
5
5
  * @internal
6
6
  */
7
7
  export type ResolvedTextureDescriptor =
8
- Required<Omit<ITextureDescriptor, "data" | "blockSize">> & Pick<ITextureDescriptor, "data" | "blockSize">;
8
+ Required<Omit<ITextureDescriptor, "data" | "blockSize" | "preferredColorSpace">> & Pick<ITextureDescriptor, "data" | "blockSize" | "preferredColorSpace">;
9
9
 
10
10
  /**
11
11
  * Fills in the defaults for every missing field of a texture descriptor.
@@ -23,5 +23,6 @@ export function resolveTextureDescriptor(desc?: ITextureDescriptor): ResolvedTex
23
23
  data: desc?.data,
24
24
  generateMipmaps: desc?.generateMipmaps ?? false,
25
25
  blockSize: desc?.blockSize,
26
+ preferredColorSpace: desc?.preferredColorSpace,
26
27
  };
27
28
  }
@@ -15,7 +15,7 @@ import {
15
15
  type ISampler,
16
16
  type ISwapChain,
17
17
  type IWindowManager,
18
- PrimitiveStateDescriptor,
18
+ type IPrimitiveStateDescriptor,
19
19
  RenderPassDescriptor,
20
20
  SwapChainDescriptor
21
21
  } from "../api/index.ts";
@@ -125,8 +125,7 @@ export class WebGlGraphicsDevice extends AGraphicsDevice {
125
125
  }
126
126
 
127
127
  /** @inheritdoc */
128
- public createPrimitiveState(descriptor?: PrimitiveStateDescriptor): IPrimitiveState {
129
- descriptor = descriptor ?? new PrimitiveStateDescriptor();
128
+ public createPrimitiveState(descriptor?: IPrimitiveStateDescriptor): IPrimitiveState {
130
129
  return new WebGlPrimitiveState(this._gl, descriptor);
131
130
  }
132
131
  }
@@ -11,6 +11,9 @@ import {
11
11
  type LightsBufferDescriptor,
12
12
  DEFAULT_DIRECTIONAL_LIGHTS_COUNT,
13
13
  } from "../../api/index.ts";
14
+ import {vec3} from "gl-matrix";
15
+
16
+ const _v = vec3.create();
14
17
 
15
18
  /**
16
19
  * The WebGL lights buffer. Owns a std140 uniform buffer for the `LightsBuffer` block.
@@ -191,10 +194,15 @@ export class WebGlLightsBuffer implements LightsBuffer {
191
194
  this._writeColor(offset, light?.color, light?.intensity ?? 0);
192
195
  this._data[offset + 3] = 0;
193
196
 
194
- // vec3 direction
195
- this._data[offset + 4] = light ? light.direction[0] : 0;
196
- this._data[offset + 5] = light ? light.direction[1] : 0;
197
- this._data[offset + 6] = light ? light.direction[2] : 0;
197
+ // vec3 direction (world space, from the transform)
198
+ if (light) {
199
+ light.getDirection(_v);
200
+ } else {
201
+ vec3.zero(_v);
202
+ }
203
+ this._data[offset + 4] = _v[0];
204
+ this._data[offset + 5] = _v[1];
205
+ this._data[offset + 6] = _v[2];
198
206
  this._data[offset + 7] = 0;
199
207
  }
200
208
 
@@ -209,10 +217,15 @@ export class WebGlLightsBuffer implements LightsBuffer {
209
217
  // An empty slot keeps constant at 1 so the shader never divides by zero.
210
218
  this._data[offset + 3] = light ? light.constant : 1;
211
219
 
212
- // vec3 position, float linear
213
- this._data[offset + 4] = light ? light.position[0] : 0;
214
- this._data[offset + 5] = light ? light.position[1] : 0;
215
- this._data[offset + 6] = light ? light.position[2] : 0;
220
+ // vec3 position (world space, from the transform), float linear
221
+ if (light) {
222
+ light.getPosition(_v);
223
+ } else {
224
+ vec3.zero(_v);
225
+ }
226
+ this._data[offset + 4] = _v[0];
227
+ this._data[offset + 5] = _v[1];
228
+ this._data[offset + 6] = _v[2];
216
229
  this._data[offset + 7] = light ? light.linear : 0;
217
230
 
218
231
  // float quadratic, then padding to the 16-byte struct size
@@ -2,9 +2,10 @@ import {
2
2
  CullMode,
3
3
  type FrontFace,
4
4
  type IPrimitiveState,
5
- type PrimitiveStateDescriptor,
5
+ type IPrimitiveStateDescriptor,
6
6
  type PrimitiveTopology
7
7
  } from "../../api/index.ts";
8
+ import {resolvePrimitiveStateDescriptor} from "../../core/rendering/primitive/PrimitiveStateDescriptorUtilities.ts";
8
9
  import { WebGlConverter } from "../utilities/WebGlConverter.ts";
9
10
 
10
11
  /**
@@ -19,12 +20,13 @@ export class WebGlPrimitiveState implements IPrimitiveState {
19
20
  /**
20
21
  * The constructor.
21
22
  * @param _gl The WebGL2 Rendering Context.
22
- * @param descriptor The descriptor of the primitive state.
23
+ * @param descriptor The descriptor of the primitive state; missing fields use the defaults.
23
24
  */
24
- public constructor(_gl: WebGL2RenderingContext, descriptor: PrimitiveStateDescriptor) {
25
- this.topology = descriptor.topology;
26
- this.cullFace = descriptor.cullFace;
27
- this.frontFace = descriptor.frontFace;
25
+ public constructor(_gl: WebGL2RenderingContext, descriptor?: IPrimitiveStateDescriptor) {
26
+ const resolved = resolvePrimitiveStateDescriptor(descriptor);
27
+ this.topology = resolved.topology;
28
+ this.cullFace = resolved.cullFace;
29
+ this.frontFace = resolved.frontFace;
28
30
 
29
31
  this._cullingEnabled = this.cullFace != CullMode.NONE;
30
32
 
@@ -13,6 +13,7 @@ import {
13
13
  type ITexture2D,
14
14
  TextureFormat
15
15
  } from "../../api/index.ts";
16
+ import {ColorSpace} from "../../api/rendering/enums.ts";
16
17
 
17
18
  /**
18
19
  * The WebGL implementation of the main render target render pipeline.
@@ -24,6 +25,7 @@ export class WebGlMainRenderTargetRenderPipeline extends AWebGlRenderPipeline im
24
25
  private _mainRenderTarget: WebGlTexture2D = null!;
25
26
 
26
27
  private _colorConversionUniformLocation: WebGLUniformLocation = null!;
28
+ /** The shader's `u_colorConversion`: 1 encodes linear to sRGB on output, 0 passes through. */
27
29
  private _colorSpaceValue = 0;
28
30
 
29
31
  /**
@@ -40,6 +42,18 @@ export class WebGlMainRenderTargetRenderPipeline extends AWebGlRenderPipeline im
40
42
  }
41
43
  }
42
44
 
45
+ /**
46
+ * The color space. With SRGB the main render target is sRGB (sampled back as linear) and the shader
47
+ * encodes to sRGB for the screen; with LINEAR the values are passed through. Takes effect on the next frame.
48
+ */
49
+ public get colorSpace(): ColorSpace {
50
+ return this._colorSpaceValue == 1 ? ColorSpace.SRGB : ColorSpace.LINEAR;
51
+ }
52
+
53
+ public set colorSpace(value: ColorSpace) {
54
+ this._colorSpaceValue = value == ColorSpace.SRGB ? 1 : 0;
55
+ }
56
+
43
57
  /** @inheritDoc */
44
58
  public readonly geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2;
45
59