ris-framework 0.1.0-dev.9 → 0.2.0-dev.11

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 (217) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +15 -4
  3. package/dist/Framework.d.ts +9 -3
  4. package/dist/Framework.d.ts.map +1 -1
  5. package/dist/api/IFramework.d.ts +7 -6
  6. package/dist/api/IFramework.d.ts.map +1 -1
  7. package/dist/api/_export.d.ts +2 -0
  8. package/dist/api/_export.d.ts.map +1 -1
  9. package/dist/api/camera/CameraFactory.d.ts +3 -2
  10. package/dist/api/camera/CameraFactory.d.ts.map +1 -1
  11. package/dist/api/camera/ICamera.d.ts +5 -0
  12. package/dist/api/camera/ICamera.d.ts.map +1 -1
  13. package/dist/api/camera/ICameraFactory.d.ts +5 -6
  14. package/dist/api/camera/ICameraFactory.d.ts.map +1 -1
  15. package/dist/api/camera/OrthographicCamera.d.ts +7 -0
  16. package/dist/api/camera/OrthographicCamera.d.ts.map +1 -1
  17. package/dist/api/camera/PerspectiveCamera.d.ts +30 -6
  18. package/dist/api/camera/PerspectiveCamera.d.ts.map +1 -1
  19. package/dist/api/content/ContentManager.d.ts +67 -2
  20. package/dist/api/content/ContentManager.d.ts.map +1 -1
  21. package/dist/api/content/IContentManager.d.ts +32 -1
  22. package/dist/api/content/IContentManager.d.ts.map +1 -1
  23. package/dist/api/content/ShaderModuleContent.d.ts.map +1 -1
  24. package/dist/api/data/Color.d.ts +5 -0
  25. package/dist/api/data/Color.d.ts.map +1 -1
  26. package/dist/api/geometry/BaseGeometry.d.ts +9 -0
  27. package/dist/api/geometry/BaseGeometry.d.ts.map +1 -1
  28. package/dist/api/geometry/GeometryBuilder.d.ts +3 -1
  29. package/dist/api/geometry/GeometryBuilder.d.ts.map +1 -1
  30. package/dist/api/geometry/IGeometryBuilder.d.ts +24 -0
  31. package/dist/api/geometry/IGeometryBuilder.d.ts.map +1 -1
  32. package/dist/api/geometry/_export-geometry.d.ts +1 -0
  33. package/dist/api/geometry/_export-geometry.d.ts.map +1 -1
  34. package/dist/api/input/InputManager.d.ts +4 -0
  35. package/dist/api/input/InputManager.d.ts.map +1 -1
  36. package/dist/api/lights/AmbientLight.d.ts +35 -0
  37. package/dist/api/lights/AmbientLight.d.ts.map +1 -0
  38. package/dist/api/lights/DirectionalLight.d.ts +40 -0
  39. package/dist/api/lights/DirectionalLight.d.ts.map +1 -0
  40. package/dist/api/lights/LightFactory.d.ts +18 -0
  41. package/dist/api/lights/LightFactory.d.ts.map +1 -0
  42. package/dist/api/lights/LightId.d.ts +6 -0
  43. package/dist/api/lights/LightId.d.ts.map +1 -0
  44. package/dist/api/lights/LightsBuffer.d.ts +70 -0
  45. package/dist/api/lights/LightsBuffer.d.ts.map +1 -0
  46. package/dist/api/lights/PointLight.d.ts +59 -0
  47. package/dist/api/lights/PointLight.d.ts.map +1 -0
  48. package/dist/api/lights/_export.d.ts +6 -0
  49. package/dist/api/lights/_export.d.ts.map +1 -0
  50. package/dist/api/loaders/ObjGeometryMapper.d.ts +19 -0
  51. package/dist/api/loaders/ObjGeometryMapper.d.ts.map +1 -0
  52. package/dist/api/loaders/_export.d.ts +1 -0
  53. package/dist/api/loaders/_export.d.ts.map +1 -1
  54. package/dist/api/loaders/obj/ObjLoader.d.ts +29 -0
  55. package/dist/api/loaders/obj/ObjLoader.d.ts.map +1 -0
  56. package/dist/api/loaders/obj/ObjTypes.d.ts +103 -0
  57. package/dist/api/loaders/obj/ObjTypes.d.ts.map +1 -0
  58. package/dist/api/loaders/obj/index.d.ts +3 -0
  59. package/dist/api/loaders/obj/index.d.ts.map +1 -0
  60. package/dist/api/material/ForwardBlinnPhongMaterial.d.ts +127 -0
  61. package/dist/api/material/ForwardBlinnPhongMaterial.d.ts.map +1 -0
  62. package/dist/api/material/IMaterial.d.ts +15 -6
  63. package/dist/api/material/IMaterial.d.ts.map +1 -1
  64. package/dist/api/material/IMaterialFactory.d.ts +17 -2
  65. package/dist/api/material/IMaterialFactory.d.ts.map +1 -1
  66. package/dist/api/material/InspectTextureMipsMaterial.d.ts +16 -7
  67. package/dist/api/material/InspectTextureMipsMaterial.d.ts.map +1 -1
  68. package/dist/api/material/MaterialFactory.d.ts +6 -0
  69. package/dist/api/material/MaterialFactory.d.ts.map +1 -1
  70. package/dist/api/material/SpriteMaterial.d.ts +51 -0
  71. package/dist/api/material/SpriteMaterial.d.ts.map +1 -0
  72. package/dist/api/material/UnlitMaterial.d.ts +28 -10
  73. package/dist/api/material/UnlitMaterial.d.ts.map +1 -1
  74. package/dist/api/material/_export.d.ts +2 -0
  75. package/dist/api/material/_export.d.ts.map +1 -1
  76. package/dist/api/meshes/IMesh.d.ts +35 -5
  77. package/dist/api/meshes/IMesh.d.ts.map +1 -1
  78. package/dist/api/meshes/IMeshFactory.d.ts +25 -4
  79. package/dist/api/meshes/IMeshFactory.d.ts.map +1 -1
  80. package/dist/api/meshes/Mesh.d.ts +58 -6
  81. package/dist/api/meshes/Mesh.d.ts.map +1 -1
  82. package/dist/api/meshes/MeshFactory.d.ts +7 -3
  83. package/dist/api/meshes/MeshFactory.d.ts.map +1 -1
  84. package/dist/api/meshes/QuadMesh.d.ts +5 -1
  85. package/dist/api/meshes/QuadMesh.d.ts.map +1 -1
  86. package/dist/api/meshes/SubMesh.d.ts +47 -0
  87. package/dist/api/meshes/SubMesh.d.ts.map +1 -0
  88. package/dist/api/meshes/_export.d.ts +1 -0
  89. package/dist/api/meshes/_export.d.ts.map +1 -1
  90. package/dist/api/rendering/render-pipelines/IForwardBlinnPhongRenderPipeline.d.ts +56 -0
  91. package/dist/api/rendering/render-pipelines/IForwardBlinnPhongRenderPipeline.d.ts.map +1 -0
  92. package/dist/api/rendering/render-pipelines/IRenderPipeline.d.ts +3 -0
  93. package/dist/api/rendering/render-pipelines/IRenderPipeline.d.ts.map +1 -1
  94. package/dist/api/rendering/render-pipelines/IRenderPipelineFactory.d.ts +29 -0
  95. package/dist/api/rendering/render-pipelines/IRenderPipelineFactory.d.ts.map +1 -1
  96. package/dist/api/rendering/render-pipelines/_export-render-pipelines.d.ts +1 -0
  97. package/dist/api/rendering/render-pipelines/_export-render-pipelines.d.ts.map +1 -1
  98. package/dist/api/scene/Scene.d.ts +104 -0
  99. package/dist/api/scene/Scene.d.ts.map +1 -0
  100. package/dist/api/shader/IShaderModuleLoader.d.ts +4 -1
  101. package/dist/api/shader/IShaderModuleLoader.d.ts.map +1 -1
  102. package/dist/api/shader/{shader-loader.d.ts → ShaderLoader.d.ts} +1 -1
  103. package/dist/api/shader/ShaderLoader.d.ts.map +1 -0
  104. package/dist/api/sprites/SpriteBatchDrawable.d.ts +1 -1
  105. package/dist/api/sprites/SpriteBatchDrawable.d.ts.map +1 -1
  106. package/dist/api/sprites/SpriteBatchMesh.d.ts +14 -1
  107. package/dist/api/sprites/SpriteBatchMesh.d.ts.map +1 -1
  108. package/dist/api/utilities/GeometryUtilities.d.ts.map +1 -1
  109. package/dist/api/window/WindowManager.d.ts +4 -0
  110. package/dist/api/window/WindowManager.d.ts.map +1 -1
  111. package/dist/core/rendering/vertex-buffer-layout.d.ts +1 -0
  112. package/dist/core/rendering/vertex-buffer-layout.d.ts.map +1 -1
  113. package/dist/index.js +2252 -260
  114. package/dist/webgl/WebGlGraphicsDevice.d.ts.map +1 -1
  115. package/dist/webgl/lights/WebGlAmbientLightFactory.d.ts +17 -0
  116. package/dist/webgl/lights/WebGlAmbientLightFactory.d.ts.map +1 -0
  117. package/dist/webgl/lights/WebGlLightsBuffer.d.ts +55 -0
  118. package/dist/webgl/lights/WebGlLightsBuffer.d.ts.map +1 -0
  119. package/dist/webgl/render-pipelines/AWebGlRenderPipeline.d.ts +3 -1
  120. package/dist/webgl/render-pipelines/AWebGlRenderPipeline.d.ts.map +1 -1
  121. package/dist/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.d.ts +3 -0
  122. package/dist/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.d.ts.map +1 -1
  123. package/dist/webgl/render-pipelines/WebGlRenderPipelineFactory.d.ts +3 -1
  124. package/dist/webgl/render-pipelines/WebGlRenderPipelineFactory.d.ts.map +1 -1
  125. package/dist/webgl/render-pipelines/inspect/WebGlInspectTextureMipsRenderPipeline.d.ts +3 -1
  126. package/dist/webgl/render-pipelines/inspect/WebGlInspectTextureMipsRenderPipeline.d.ts.map +1 -1
  127. package/dist/webgl/render-pipelines/material/WebGlBlinnPhongRenderPipeline.d.ts +88 -0
  128. package/dist/webgl/render-pipelines/material/WebGlBlinnPhongRenderPipeline.d.ts.map +1 -0
  129. package/dist/webgl/render-pipelines/material/WebGlUnlitRenderPipeline.d.ts +3 -1
  130. package/dist/webgl/render-pipelines/material/WebGlUnlitRenderPipeline.d.ts.map +1 -1
  131. package/dist/webgl/render-pipelines/sprite/WebGlSpriteRenderPipeline.d.ts +3 -1
  132. package/dist/webgl/render-pipelines/sprite/WebGlSpriteRenderPipeline.d.ts.map +1 -1
  133. package/dist/webgl/shader/WebGlShaderModule.d.ts +4 -1
  134. package/dist/webgl/shader/WebGlShaderModule.d.ts.map +1 -1
  135. package/dist/webgl/shader/WebGlShaderModuleLoader.d.ts +16 -5
  136. package/dist/webgl/shader/WebGlShaderModuleLoader.d.ts.map +1 -1
  137. package/dist/webgl/texture/WebGlTexture2D.d.ts +8 -1
  138. package/dist/webgl/texture/WebGlTexture2D.d.ts.map +1 -1
  139. package/dist/webgl/utilities/webgl-filter-anisotropic-utilities.d.ts +0 -1
  140. package/dist/webgl/utilities/webgl-filter-anisotropic-utilities.d.ts.map +1 -1
  141. package/package.json +4 -4
  142. package/shaders/glsl/forward_blinn_phong_material_fs.glsl +110 -0
  143. package/shaders/glsl/forward_blinn_phong_material_vs.glsl +32 -0
  144. package/shaders/glsl/sprite_fs.glsl +1 -1
  145. package/shaders/glsl/unlit_material_fs.glsl +1 -1
  146. package/src/Framework.ts +37 -13
  147. package/src/api/IFramework.ts +8 -6
  148. package/src/api/_export.ts +2 -0
  149. package/src/api/camera/CameraFactory.ts +6 -6
  150. package/src/api/camera/ICamera.ts +6 -0
  151. package/src/api/camera/ICameraFactory.ts +5 -10
  152. package/src/api/camera/OrthographicCamera.ts +23 -1
  153. package/src/api/camera/PerspectiveCamera.ts +60 -15
  154. package/src/api/content/ContentManager.ts +201 -5
  155. package/src/api/content/IContentManager.ts +33 -1
  156. package/src/api/content/ShaderModuleContent.ts +9 -0
  157. package/src/api/data/Color.ts +7 -1
  158. package/src/api/geometry/BaseGeometry.ts +23 -0
  159. package/src/api/geometry/GeometryBuilder.ts +14 -2
  160. package/src/api/geometry/IGeometryBuilder.ts +31 -1
  161. package/src/api/geometry/_export-geometry.ts +2 -1
  162. package/src/api/input/InputManager.ts +14 -5
  163. package/src/api/lights/AmbientLight.ts +53 -0
  164. package/src/api/lights/DirectionalLight.ts +60 -0
  165. package/src/api/lights/LightFactory.ts +21 -0
  166. package/src/api/lights/LightId.ts +9 -0
  167. package/src/api/lights/LightsBuffer.ts +87 -0
  168. package/src/api/lights/PointLight.ts +91 -0
  169. package/src/api/lights/_export.ts +5 -0
  170. package/src/api/loaders/ObjGeometryMapper.ts +28 -0
  171. package/src/api/loaders/_export.ts +2 -1
  172. package/src/api/loaders/obj/ObjLoader.ts +270 -0
  173. package/src/api/loaders/obj/ObjTypes.ts +124 -0
  174. package/src/api/loaders/obj/index.ts +5 -0
  175. package/src/api/material/ForwardBlinnPhongMaterial.ts +302 -0
  176. package/src/api/material/IMaterial.ts +16 -6
  177. package/src/api/material/IMaterialFactory.ts +19 -2
  178. package/src/api/material/InspectTextureMipsMaterial.ts +63 -24
  179. package/src/api/material/MaterialFactory.ts +12 -0
  180. package/src/api/material/SpriteMaterial.ts +83 -0
  181. package/src/api/material/UnlitMaterial.ts +101 -29
  182. package/src/api/material/_export.ts +3 -1
  183. package/src/api/meshes/IMesh.ts +45 -6
  184. package/src/api/meshes/IMeshFactory.ts +27 -4
  185. package/src/api/meshes/Mesh.ts +163 -41
  186. package/src/api/meshes/MeshFactory.ts +30 -6
  187. package/src/api/meshes/QuadMesh.ts +6 -2
  188. package/src/api/meshes/SubMesh.ts +71 -0
  189. package/src/api/meshes/_export.ts +1 -0
  190. package/src/api/rendering/render-pipelines/IForwardBlinnPhongRenderPipeline.ts +66 -0
  191. package/src/api/rendering/render-pipelines/IRenderPipeline.ts +4 -0
  192. package/src/api/rendering/render-pipelines/IRenderPipelineFactory.ts +39 -0
  193. package/src/api/rendering/render-pipelines/_export-render-pipelines.ts +2 -1
  194. package/src/api/scene/Scene.ts +264 -0
  195. package/src/api/shader/IShaderModuleLoader.ts +2 -1
  196. package/src/api/sprites/SpriteBatchDrawable.ts +12 -16
  197. package/src/api/sprites/SpriteBatchMesh.ts +23 -4
  198. package/src/api/utilities/GeometryUtilities.ts +2 -0
  199. package/src/api/window/WindowManager.ts +22 -2
  200. package/src/core/rendering/vertex-buffer-layout.ts +32 -1
  201. package/src/main.ts +0 -1
  202. package/src/webgl/WebGlGraphicsDevice.ts +0 -1
  203. package/src/webgl/lights/WebGlAmbientLightFactory.ts +35 -0
  204. package/src/webgl/lights/WebGlLightsBuffer.ts +215 -0
  205. package/src/webgl/render-pipelines/AWebGlRenderPipeline.ts +7 -1
  206. package/src/webgl/render-pipelines/WebGlMainRenderTargetRenderPipeline.ts +3 -0
  207. package/src/webgl/render-pipelines/WebGlRenderPipelineFactory.ts +24 -2
  208. package/src/webgl/render-pipelines/inspect/WebGlInspectTextureMipsRenderPipeline.ts +16 -6
  209. package/src/webgl/render-pipelines/material/WebGlBlinnPhongRenderPipeline.ts +300 -0
  210. package/src/webgl/render-pipelines/material/WebGlUnlitRenderPipeline.ts +16 -6
  211. package/src/webgl/render-pipelines/sprite/WebGlSpriteRenderPipeline.ts +11 -7
  212. package/src/webgl/shader/WebGlShaderModule.ts +11 -4
  213. package/src/webgl/shader/WebGlShaderModuleLoader.ts +29 -10
  214. package/src/webgl/texture/WebGlTexture2D.ts +19 -5
  215. package/src/webgl/utilities/webgl-filter-anisotropic-utilities.ts +9 -6
  216. package/dist/api/shader/shader-loader.d.ts.map +0 -1
  217. /package/src/api/shader/{shader-loader.ts → ShaderLoader.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { container } from "tsyringe";
2
- import { vec4, vec3, mat4, vec2 } from "gl-matrix";
1
+ import { vec4, vec3, mat4, vec2, quat } from "gl-matrix";
3
2
  var RenderingBackend = /* @__PURE__ */ ((RenderingBackend2) => {
4
3
  RenderingBackend2[RenderingBackend2["DEFAULT"] = 0] = "DEFAULT";
5
4
  RenderingBackend2[RenderingBackend2["WEB_GPU"] = 1] = "WEB_GPU";
@@ -284,6 +283,13 @@ class Color {
284
283
  */
285
284
  lerp(a2, b2, t2, o2) {
286
285
  }
286
+ /**
287
+ * Copy self and return new instance with same values.
288
+ * @return Color
289
+ * */
290
+ copy() {
291
+ return new Color(this.r, this.g, this.b, this.a);
292
+ }
287
293
  }
288
294
  class TextureDescriptor {
289
295
  /**
@@ -953,6 +959,142 @@ var GeometryFormat = /* @__PURE__ */ ((GeometryFormat2) => {
953
959
  GeometryFormat2[GeometryFormat2["POS3_TEXTURECOORDS2"] = 4] = "POS3_TEXTURECOORDS2";
954
960
  return GeometryFormat2;
955
961
  })(GeometryFormat || {});
962
+ class BaseGeometry {
963
+ /**
964
+ * Initializes a new instance of the BaseGeometry class.
965
+ * @param positions The optional positions of the geometry.
966
+ * @param indices The optional indices of the geometry.
967
+ * @param normals The optional normals of the geometry.
968
+ * @param textureCoords The optional texture coordinates of the geometry.
969
+ * @param colors The optional colors of the geometry.
970
+ */
971
+ constructor(positions, indices, normals, textureCoords, colors) {
972
+ this.positions = positions;
973
+ this.indices = indices;
974
+ this.normals = normals;
975
+ this.textureCoords = textureCoords;
976
+ this.colors = colors;
977
+ if (this.indices && this.indices.length > 0) {
978
+ this.vertexCount = this.indices.length / 3;
979
+ } else if (this.positions && this.positions.length > 0) {
980
+ this.vertexCount = this.positions.length / 3;
981
+ }
982
+ }
983
+ /** @inheritdoc */
984
+ vertexCount = 0;
985
+ /** @inheritdoc */
986
+ positions;
987
+ /** @inheritdoc */
988
+ positionsFormat = VertexFormat.FLOAT_32X3;
989
+ /** @inheritdoc */
990
+ colors;
991
+ /** @inheritdoc */
992
+ colorFormat = VertexFormat.FLOAT_32X4;
993
+ /** @inheritdoc */
994
+ textureCoords;
995
+ /** @inheritdoc */
996
+ textureFormat = VertexFormat.FLOAT_32X2;
997
+ /** @inheritdoc */
998
+ normals;
999
+ /** @inheritdoc */
1000
+ normalsFormat = VertexFormat.FLOAT_32X3;
1001
+ /** The indices of geometry. */
1002
+ indices;
1003
+ /**
1004
+ * Pushes vertex data from source format to destination format.
1005
+ * @param index The index of the vertex to push.
1006
+ * @param source The source vertex data.
1007
+ * @param sourceFormat The vertex format of the source vertex data.
1008
+ * @param destination The destination array to push the vertex data to.
1009
+ * @param destinationFormat The vertex format of the destination vertex data.
1010
+ */
1011
+ pushFormat(index, source, sourceFormat, destination, destinationFormat) {
1012
+ let _0 = 0;
1013
+ let _1 = 0;
1014
+ let _2 = 0;
1015
+ let _3 = 0;
1016
+ if (sourceFormat == VertexFormat.FLOAT_32) {
1017
+ _0 = source[index];
1018
+ } else if (sourceFormat == VertexFormat.FLOAT_32X2) {
1019
+ _0 = source[index * 2];
1020
+ _1 = source[index * 2 + 1];
1021
+ } else if (sourceFormat == VertexFormat.FLOAT_32X3) {
1022
+ _0 = source[index * 3];
1023
+ _1 = source[index * 3 + 1];
1024
+ _2 = source[index * 3 + 2];
1025
+ } else if (sourceFormat == VertexFormat.FLOAT_32X4) {
1026
+ _0 = source[index * 4];
1027
+ _1 = source[index * 4 + 1];
1028
+ _2 = source[index * 4 + 2];
1029
+ _3 = source[index * 4 + 3];
1030
+ }
1031
+ destination.push(_0);
1032
+ if (destinationFormat >= VertexFormat.FLOAT_32X2) {
1033
+ destination.push(_1);
1034
+ }
1035
+ if (destinationFormat >= VertexFormat.FLOAT_32X3) {
1036
+ destination.push(_2);
1037
+ }
1038
+ if (destinationFormat >= VertexFormat.FLOAT_32X4) {
1039
+ destination.push(_3);
1040
+ }
1041
+ }
1042
+ /**
1043
+ * Converts the geometry data to interleaved vertex data based on the specified geometry format.
1044
+ * @param format The geometry format to convert the geometry data to.
1045
+ * @returns The interleaved vertex data as an array of numbers.
1046
+ */
1047
+ toInterleaved(format) {
1048
+ let interleavedData = [];
1049
+ switch (format) {
1050
+ case GeometryFormat.POS3_COLOR4: {
1051
+ let count = this.positions.length / 3;
1052
+ for (let i2 = 0; i2 < count; i2++) {
1053
+ this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
1054
+ this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
1055
+ }
1056
+ break;
1057
+ }
1058
+ case GeometryFormat.POS3_TEXTURECOORDS2: {
1059
+ let count = this.positions.length / 3;
1060
+ for (let i2 = 0; i2 < count; i2++) {
1061
+ this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
1062
+ this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
1063
+ }
1064
+ break;
1065
+ }
1066
+ case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2: {
1067
+ let count = this.positions.length / 3;
1068
+ for (let i2 = 0; i2 < count; i2++) {
1069
+ this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
1070
+ this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
1071
+ this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
1072
+ }
1073
+ break;
1074
+ }
1075
+ case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_NORMAL3: {
1076
+ let count = this.positions.length / 3;
1077
+ for (let i2 = 0; i2 < count; i2++) {
1078
+ this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
1079
+ this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
1080
+ this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
1081
+ this.pushFormat(i2, this.normals, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
1082
+ }
1083
+ break;
1084
+ }
1085
+ case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_ALIGNED: {
1086
+ let count = this.positions.length / 3;
1087
+ for (let i2 = 0; i2 < count; i2++) {
1088
+ this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X4);
1089
+ this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
1090
+ this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X4);
1091
+ }
1092
+ break;
1093
+ }
1094
+ }
1095
+ return interleavedData;
1096
+ }
1097
+ }
956
1098
  var LoadAction = /* @__PURE__ */ ((LoadAction2) => {
957
1099
  LoadAction2[LoadAction2["CLEAR"] = 0] = "CLEAR";
958
1100
  LoadAction2[LoadAction2["LOAD"] = 1] = "LOAD";
@@ -1398,19 +1540,30 @@ class PerspectiveCamera {
1398
1540
  _viewBuffer = null;
1399
1541
  _projectionBuffer = null;
1400
1542
  /**
1401
- * The constructor.
1543
+ * The constructor. Call `initialize()` before use (the camera factory does).
1402
1544
  * @param framework The framework.
1403
- * @param fov The field of view in radians.
1404
- * @param aspectRatio The aspect ratio.
1405
- * @param nearPlane The near plane.
1406
- * @param farPlane The far plane.
1545
+ * @param descriptor The optional descriptor; omitted fields use the defaults documented on {@link PerspectiveCameraDescriptor}.
1546
+ * `fov` is in degrees; {@link fieldOfView} holds it in radians. Vectors are copied.
1547
+ * @param scene The optional scene; the camera assigns itself as `scene.camera`.
1407
1548
  */
1408
- constructor(framework, fov, aspectRatio, nearPlane, farPlane) {
1549
+ constructor(framework, descriptor, scene) {
1409
1550
  this._framework = framework;
1410
- this.fieldOfView = fov;
1411
- this.aspectRatio = aspectRatio;
1412
- this.nearPlane = nearPlane;
1413
- this.farPlane = farPlane;
1551
+ this.fieldOfView = MathHelper.toRadians(descriptor?.fov ?? 60);
1552
+ this.aspectRatio = descriptor?.aspectRatio ?? 1;
1553
+ this.nearPlane = descriptor?.near ?? 0.01;
1554
+ this.farPlane = descriptor?.far ?? 100;
1555
+ if (descriptor?.eye) {
1556
+ vec3.copy(this.eye, descriptor.eye);
1557
+ }
1558
+ if (descriptor?.target) {
1559
+ vec3.copy(this.target, descriptor.target);
1560
+ }
1561
+ if (descriptor?.up) {
1562
+ vec3.copy(this.up, descriptor.up);
1563
+ }
1564
+ if (scene) {
1565
+ scene.add(this);
1566
+ }
1414
1567
  this._updateMatrices();
1415
1568
  }
1416
1569
  /**
@@ -1505,11 +1658,18 @@ class PerspectiveCamera {
1505
1658
  */
1506
1659
  initialize() {
1507
1660
  const bufferUsage = BufferUsage.UNIFORM | BufferUsage.COPY_DST;
1508
- this._positionBuffer = this._framework.bufferFactory.createUniformBuffer(this.eye, bufferUsage);
1661
+ this._positionBuffer = this._framework.bufferFactory.createUniformBuffer(this._worldEye(), bufferUsage);
1509
1662
  this._projectionViewBuffer = this._framework.bufferFactory.createUniformBuffer(this.projectionViewMatrix, bufferUsage);
1510
1663
  this._projectionBuffer = this._framework.bufferFactory.createUniformBuffer(this.projectionMatrix, bufferUsage);
1511
1664
  this._viewBuffer = this._framework.bufferFactory.createUniformBuffer(this.viewMatrix, bufferUsage);
1512
1665
  }
1666
+ /**
1667
+ * The eye in world space, as used by the view matrix: z is flipped for the right-handed look-at.
1668
+ * This is what {@link positionBuffer} holds, std140 `vec3` (padded to 16 bytes).
1669
+ */
1670
+ _worldEye() {
1671
+ return [this.eye[0], this.eye[1], -this.eye[2], 0];
1672
+ }
1513
1673
  /**
1514
1674
  * Updates the camera matrices.
1515
1675
  */
@@ -1528,7 +1688,7 @@ class PerspectiveCamera {
1528
1688
  }
1529
1689
  /** @inheritDoc */
1530
1690
  updateBuffers() {
1531
- this._positionBuffer.update(this.eye);
1691
+ this._positionBuffer.update(this._worldEye());
1532
1692
  this._projectionBuffer.update(this.projectionMatrix);
1533
1693
  this._viewBuffer.update(this.viewMatrix);
1534
1694
  this._projectionViewBuffer.update(this.projectionViewMatrix);
@@ -2192,6 +2352,9 @@ class OrthographicCamera {
2192
2352
  _projectionBuffer = null;
2193
2353
  _viewBuffer = null;
2194
2354
  _projectionViewBuffer = null;
2355
+ _positionBuffer = null;
2356
+ /** std140 vec3 world-space eye, padded to 4 floats. */
2357
+ _positionData = [0, 0, 0, 0];
2195
2358
  /**
2196
2359
  *
2197
2360
  * @param framework
@@ -2210,7 +2373,7 @@ class OrthographicCamera {
2210
2373
  this._bottom = bottom;
2211
2374
  this._near = near;
2212
2375
  this._far = far;
2213
- this._eye = vec3.fromValues(0, 0, -1);
2376
+ this._eye = vec3.fromValues(0, 0, 1);
2214
2377
  this._target = vec3.fromValues(0, 0, 0);
2215
2378
  this._zSpaceLength = 2;
2216
2379
  this._updateMatrices();
@@ -2316,6 +2479,17 @@ class OrthographicCamera {
2316
2479
  get viewBuffer() {
2317
2480
  return this._viewBuffer;
2318
2481
  }
2482
+ /** @inheritDoc */
2483
+ get positionBuffer() {
2484
+ return this._positionBuffer;
2485
+ }
2486
+ /** Writes the world-space eye into the std140 position data (no z flip: the look-at uses the eye as is). */
2487
+ _updatePositionData() {
2488
+ this._positionData[0] = this._eye[0];
2489
+ this._positionData[1] = this._eye[1];
2490
+ this._positionData[2] = this._eye[2];
2491
+ return this._positionData;
2492
+ }
2319
2493
  /**
2320
2494
  * Updates the camera matrices.
2321
2495
  */
@@ -2341,6 +2515,11 @@ class OrthographicCamera {
2341
2515
  BufferUsage.UNIFORM,
2342
2516
  "OrthographicCamera.projectionViewBuffer"
2343
2517
  );
2518
+ this._positionBuffer = this._framework.bufferFactory.createUniformBuffer(
2519
+ this._updatePositionData(),
2520
+ BufferUsage.UNIFORM,
2521
+ "OrthographicCamera.positionBuffer"
2522
+ );
2344
2523
  }
2345
2524
  /** @inheritDoc */
2346
2525
  updateBuffers() {
@@ -2348,6 +2527,7 @@ class OrthographicCamera {
2348
2527
  this.projectionBuffer.update(this.projectionMatrix);
2349
2528
  this.viewBuffer.update(this.viewMatrix);
2350
2529
  this.projectionViewBuffer.update(this.projectionViewMatrix);
2530
+ this.positionBuffer.update(this._updatePositionData());
2351
2531
  }
2352
2532
  /** @inheritDoc */
2353
2533
  update(_2) {
@@ -2361,6 +2541,7 @@ class OrthographicCamera {
2361
2541
  this.projectionBuffer.dispose();
2362
2542
  this.projectionViewBuffer.dispose();
2363
2543
  this.viewBuffer.dispose();
2544
+ this.positionBuffer.dispose();
2364
2545
  }
2365
2546
  }
2366
2547
  class CameraFactory {
@@ -2391,12 +2572,11 @@ class CameraFactory {
2391
2572
  }
2392
2573
  /** @inheritDoc */
2393
2574
  createOrbitCamera(fov = 60, aspectRatio = 1, near = 0.01, far = 100) {
2394
- return new OrbitCamera(this.createPerspectiveCamera(fov, aspectRatio, near, far), this._framework.input);
2575
+ return new OrbitCamera(this.createPerspectiveCamera({ fov, aspectRatio, near, far }), this._framework.input);
2395
2576
  }
2396
2577
  /** @inheritDoc */
2397
- createPerspectiveCamera(fov = 60, aspectRatio = 1, near = 0.01, far = 100) {
2398
- fov = MathHelper.toRadians(fov);
2399
- const camera = new PerspectiveCamera(this._framework, fov, aspectRatio, near, far);
2578
+ createPerspectiveCamera(descriptor, scene) {
2579
+ const camera = new PerspectiveCamera(this._framework, descriptor, scene);
2400
2580
  camera.initialize();
2401
2581
  return camera;
2402
2582
  }
@@ -2534,6 +2714,268 @@ class ImageLoader {
2534
2714
  );
2535
2715
  }
2536
2716
  }
2717
+ class ObjMaterialDefinition {
2718
+ /**
2719
+ * The constructor.
2720
+ * @param name The material name from `newmtl`.
2721
+ */
2722
+ constructor(name) {
2723
+ this.name = name;
2724
+ }
2725
+ name;
2726
+ /** Ambient colour (`Ka`). */
2727
+ ambientColor;
2728
+ /** Diffuse colour (`Kd`). */
2729
+ diffuseColor;
2730
+ /** Specular colour (`Ks`). */
2731
+ specularColor;
2732
+ /** Emissive colour (`Ke`). */
2733
+ emissiveColor;
2734
+ /** Specular exponent (`Ns`). */
2735
+ specularExponent;
2736
+ /** Opacity, from `d`, or `1 - Tr` when only `Tr` is given. */
2737
+ opacity;
2738
+ /** Optical density / index of refraction (`Ni`). */
2739
+ opticalDensity;
2740
+ /** Illumination model (`illum`). */
2741
+ illuminationModel;
2742
+ /**
2743
+ * Texture maps by statement name, e.g. `map_Kd`, `map_Ks`, `map_Bump`, `bump`, `disp`, `decal`, `refl`.
2744
+ * The value is the file name (last token); map options such as `-bm 1` are kept in {@link mapOptions}.
2745
+ */
2746
+ maps = {};
2747
+ /** Options written before the file name of a map statement, e.g. `{ map_Bump: "-bm 0.5" }`. */
2748
+ mapOptions = {};
2749
+ }
2750
+ function tokens(line) {
2751
+ return line.trim().split(/\s+/);
2752
+ }
2753
+ function readColor(parts) {
2754
+ if (parts.length < 2 || isNaN(parseFloat(parts[1]))) {
2755
+ return void 0;
2756
+ }
2757
+ const r3 = parseFloat(parts[1]);
2758
+ const g2 = parts.length > 2 ? parseFloat(parts[2]) : r3;
2759
+ const b2 = parts.length > 3 ? parseFloat(parts[3]) : r3;
2760
+ return [r3, g2, b2];
2761
+ }
2762
+ class ObjLoader {
2763
+ /**
2764
+ * Parses .mtl text.
2765
+ * @param text The .mtl contents.
2766
+ * @returns The material definitions, in file order.
2767
+ */
2768
+ parseMtl(text) {
2769
+ const materials = [];
2770
+ let current;
2771
+ for (const rawLine of text.split(/\r?\n/)) {
2772
+ const line = rawLine.trim();
2773
+ if (line.length == 0 || line.startsWith("#")) {
2774
+ continue;
2775
+ }
2776
+ const parts = tokens(line);
2777
+ const keyword = parts[0];
2778
+ if (keyword == "newmtl") {
2779
+ current = new ObjMaterialDefinition(line.substring(keyword.length).trim());
2780
+ materials.push(current);
2781
+ continue;
2782
+ }
2783
+ if (!current) {
2784
+ continue;
2785
+ }
2786
+ switch (keyword) {
2787
+ case "Ka":
2788
+ current.ambientColor = readColor(parts);
2789
+ break;
2790
+ case "Kd":
2791
+ current.diffuseColor = readColor(parts);
2792
+ break;
2793
+ case "Ks":
2794
+ current.specularColor = readColor(parts);
2795
+ break;
2796
+ case "Ke":
2797
+ current.emissiveColor = readColor(parts);
2798
+ break;
2799
+ case "Ns":
2800
+ current.specularExponent = parseFloat(parts[1]);
2801
+ break;
2802
+ case "Ni":
2803
+ current.opticalDensity = parseFloat(parts[1]);
2804
+ break;
2805
+ case "illum":
2806
+ current.illuminationModel = parseInt(parts[1]);
2807
+ break;
2808
+ case "d":
2809
+ current.opacity = parseFloat(parts[parts.length - 1]);
2810
+ break;
2811
+ case "Tr":
2812
+ if (current.opacity === void 0) {
2813
+ current.opacity = 1 - parseFloat(parts[1]);
2814
+ }
2815
+ break;
2816
+ default:
2817
+ if (keyword.startsWith("map_") || keyword == "bump" || keyword == "disp" || keyword == "decal" || keyword == "refl" || keyword == "norm") {
2818
+ if (parts.length >= 2) {
2819
+ current.maps[keyword] = parts[parts.length - 1];
2820
+ if (parts.length > 2) {
2821
+ current.mapOptions[keyword] = parts.slice(1, -1).join(" ");
2822
+ }
2823
+ }
2824
+ }
2825
+ break;
2826
+ }
2827
+ }
2828
+ return materials;
2829
+ }
2830
+ /**
2831
+ * Parses .obj text.
2832
+ * @param text The .obj contents.
2833
+ * @param materials Material definitions to use for `usemtl`, e.g. from {@link parseMtl}. Default none.
2834
+ * @returns The geometry, materials and raw data.
2835
+ */
2836
+ fromText(text, materials = []) {
2837
+ const rawPositions = [];
2838
+ const rawTextureCoords = [];
2839
+ const rawNormals = [];
2840
+ const faceIndices = [];
2841
+ const materialLibraries = [];
2842
+ const materialRanges = [];
2843
+ const positions = [];
2844
+ const textureCoords = [];
2845
+ const normals = [];
2846
+ const colors = [];
2847
+ const indices = [];
2848
+ const vertexLookup = /* @__PURE__ */ new Map();
2849
+ const materialsByName = new Map(materials.map((m2) => [m2.name, m2]));
2850
+ let materialName;
2851
+ let rangeStart = 0;
2852
+ const closeRange = () => {
2853
+ if (indices.length > rangeStart) {
2854
+ materialRanges.push({ materialName, indexStart: rangeStart, indexCount: indices.length - rangeStart });
2855
+ }
2856
+ rangeStart = indices.length;
2857
+ };
2858
+ const resolve = (value, count) => {
2859
+ if (value === void 0 || value.length == 0) {
2860
+ return -1;
2861
+ }
2862
+ const index = parseInt(value);
2863
+ return index < 0 ? count + index : index - 1;
2864
+ };
2865
+ const addCorner = (p2, t2, n2) => {
2866
+ faceIndices.push(p2, t2, n2);
2867
+ const key = `${p2}/${t2}/${n2}/${materialName ?? ""}`;
2868
+ let vertex = vertexLookup.get(key);
2869
+ if (vertex === void 0) {
2870
+ vertex = positions.length / 3;
2871
+ vertexLookup.set(key, vertex);
2872
+ positions.push(rawPositions[p2 * 3], rawPositions[p2 * 3 + 1], rawPositions[p2 * 3 + 2]);
2873
+ textureCoords.push(t2 >= 0 ? rawTextureCoords[t2 * 2] : 0, t2 >= 0 ? rawTextureCoords[t2 * 2 + 1] : 0);
2874
+ normals.push(n2 >= 0 ? rawNormals[n2 * 3] : 0, n2 >= 0 ? rawNormals[n2 * 3 + 1] : 0, n2 >= 0 ? rawNormals[n2 * 3 + 2] : 0);
2875
+ const material = materialName !== void 0 ? materialsByName.get(materialName) : void 0;
2876
+ const kd = material?.diffuseColor ?? [1, 1, 1];
2877
+ colors.push(kd[0], kd[1], kd[2], material?.opacity ?? 1);
2878
+ }
2879
+ indices.push(vertex);
2880
+ };
2881
+ for (const rawLine of text.split(/\r?\n/)) {
2882
+ const line = rawLine.trim();
2883
+ if (line.length == 0 || line.startsWith("#")) {
2884
+ continue;
2885
+ }
2886
+ const parts = tokens(line);
2887
+ switch (parts[0]) {
2888
+ case "v":
2889
+ rawPositions.push(parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3]));
2890
+ break;
2891
+ case "vt":
2892
+ rawTextureCoords.push(parseFloat(parts[1]), parts.length > 2 ? parseFloat(parts[2]) : 0);
2893
+ break;
2894
+ case "vn":
2895
+ rawNormals.push(parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3]));
2896
+ break;
2897
+ case "f": {
2898
+ const corners = parts.slice(1).map((corner) => {
2899
+ const [p2, t2, n2] = corner.split("/");
2900
+ return [
2901
+ resolve(p2, rawPositions.length / 3),
2902
+ resolve(t2, rawTextureCoords.length / 2),
2903
+ resolve(n2, rawNormals.length / 3)
2904
+ ];
2905
+ });
2906
+ for (let i2 = 1; i2 + 1 < corners.length; i2++) {
2907
+ addCorner(corners[0][0], corners[0][1], corners[0][2]);
2908
+ addCorner(corners[i2][0], corners[i2][1], corners[i2][2]);
2909
+ addCorner(corners[i2 + 1][0], corners[i2 + 1][1], corners[i2 + 1][2]);
2910
+ }
2911
+ break;
2912
+ }
2913
+ case "mtllib":
2914
+ materialLibraries.push(...parts.slice(1));
2915
+ break;
2916
+ case "usemtl":
2917
+ closeRange();
2918
+ materialName = line.substring(parts[0].length).trim();
2919
+ break;
2920
+ }
2921
+ }
2922
+ closeRange();
2923
+ if (positions.length == 0) {
2924
+ throw new Error("Invalid obj file.");
2925
+ }
2926
+ return {
2927
+ geometry: {
2928
+ positions,
2929
+ textureCoords,
2930
+ normals,
2931
+ colors,
2932
+ indices,
2933
+ vertexCount: positions.length / 3,
2934
+ materialRanges
2935
+ },
2936
+ materials,
2937
+ materialLibraries,
2938
+ raw: { positions: rawPositions, textureCoords: rawTextureCoords, normals: rawNormals, faceIndices }
2939
+ };
2940
+ }
2941
+ /**
2942
+ * Loads an .obj file and the .mtl files it references with `mtllib`.
2943
+ * A missing or failing .mtl is skipped (logged as a warning); the model still loads with white vertex colours.
2944
+ * @param filePath The .obj URL.
2945
+ * @param options Optional: how to resolve `mtllib` names, or skip materials.
2946
+ * @returns The geometry, materials and raw data.
2947
+ */
2948
+ async loadAsync(filePath, options) {
2949
+ const response = await fetch(filePath);
2950
+ if (!response.ok) {
2951
+ throw new Error(`Failed to load OBJ '${filePath}': ${response.status} ${response.statusText}`);
2952
+ }
2953
+ const text = await response.text();
2954
+ const materials = [];
2955
+ const libraries = options?.ignoreMaterials ? [] : text.split(/\r?\n/).map((line) => tokens(line)).filter((parts) => parts[0] == "mtllib").flatMap((parts) => parts.slice(1));
2956
+ const opaqueUrl = /^(data|blob):/i.test(filePath);
2957
+ for (const library of libraries) {
2958
+ try {
2959
+ let mtlUrl;
2960
+ if (options?.resolveMaterialUrl) {
2961
+ mtlUrl = options.resolveMaterialUrl(library);
2962
+ } else if (opaqueUrl) {
2963
+ throw new Error("the OBJ was loaded from a data: or blob: URL (e.g. a bundler-inlined asset), so the name cannot be resolved; pass options.resolveMaterialUrl");
2964
+ } else {
2965
+ mtlUrl = filePath.substring(0, filePath.lastIndexOf("/") + 1) + library;
2966
+ }
2967
+ const mtlResponse = await fetch(mtlUrl);
2968
+ if (!mtlResponse.ok) {
2969
+ throw new Error(`${mtlResponse.status} ${mtlResponse.statusText}`);
2970
+ }
2971
+ materials.push(...this.parseMtl(await mtlResponse.text()));
2972
+ } catch (error) {
2973
+ console.warn(`ObjLoader: could not load material library '${library}'.`, error);
2974
+ }
2975
+ }
2976
+ return this.fromText(text, materials);
2977
+ }
2978
+ }
2537
2979
  class WindowBounds {
2538
2980
  /**
2539
2981
  * Creates window bounds with the given width and height.
@@ -2557,6 +2999,8 @@ class GeometryUtilities {
2557
2999
  switch (geometryFormat) {
2558
3000
  case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2:
2559
3001
  return (3 + 4 + 2) * Float32Array.BYTES_PER_ELEMENT;
3002
+ case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_NORMAL3:
3003
+ return (3 + 4 + 2 + 3) * Float32Array.BYTES_PER_ELEMENT;
2560
3004
  default:
2561
3005
  throw new Error("Unknown geometryFormat");
2562
3006
  }
@@ -2580,8 +3024,88 @@ class MeshParameters {
2580
3024
  */
2581
3025
  colors;
2582
3026
  }
3027
+ class SubMesh {
3028
+ /**
3029
+ * The constructor.
3030
+ * @param parent The mesh that owns the buffers.
3031
+ * @param material The material of this submesh.
3032
+ * @param indexStart The first index of the range.
3033
+ * @param indexCount The number of indices in the range.
3034
+ */
3035
+ constructor(parent, material, indexStart, indexCount) {
3036
+ this.parent = parent;
3037
+ this.material = material;
3038
+ this.indexStart = indexStart;
3039
+ this.indexCount = indexCount;
3040
+ }
3041
+ parent;
3042
+ material;
3043
+ indexStart;
3044
+ indexCount;
3045
+ /** The parent's vertex buffer. */
3046
+ get vertexBuffer() {
3047
+ return this.parent.vertexBuffer;
3048
+ }
3049
+ /** The parent's index buffer. */
3050
+ get indexBuffer() {
3051
+ return this.parent.indexBuffer;
3052
+ }
3053
+ /** The parent's position; setting it sets the parent's. */
3054
+ get position() {
3055
+ return this.parent.position;
3056
+ }
3057
+ set position(value) {
3058
+ this.parent.position = value;
3059
+ }
3060
+ /** The parent's rotation; setting it sets the parent's. */
3061
+ get rotation() {
3062
+ return this.parent.rotation;
3063
+ }
3064
+ set rotation(value) {
3065
+ this.parent.rotation = value;
3066
+ }
3067
+ /** The parent's scale; setting it sets the parent's. */
3068
+ get scale() {
3069
+ return this.parent.scale;
3070
+ }
3071
+ set scale(value) {
3072
+ this.parent.scale = value;
3073
+ }
3074
+ /** The parent's model matrix. */
3075
+ get modelMatrix() {
3076
+ return this.parent.modelMatrix;
3077
+ }
3078
+ /** The parent's model buffer. */
3079
+ get modelBuffer() {
3080
+ return this.parent.modelBuffer;
3081
+ }
3082
+ /** Draws this submesh with its material, using the parent's transform (model buffer). */
3083
+ render() {
3084
+ this.parent.__assignModelBuffer(this.material);
3085
+ this.material.beforeRender();
3086
+ this.material.renderMesh(this);
3087
+ }
3088
+ /** Does nothing: the buffers belong to the parent and the material to the caller. */
3089
+ dispose() {
3090
+ }
3091
+ }
2583
3092
  class Mesh {
3093
+ /**
3094
+ * The constructor.
3095
+ * @param _framework The framework.
3096
+ * @param scene The scene. The mesh adds itself to it and removes itself on dispose.
3097
+ * `null` only for internal meshes drawn outside a scene (e.g. the sprite batch).
3098
+ * @param _material The material. Optional for a mesh that only holds submeshes.
3099
+ */
3100
+ constructor(_framework, scene, _material) {
3101
+ this._framework = _framework;
3102
+ this.scene = scene;
3103
+ this._material = _material;
3104
+ scene?.add(this);
3105
+ }
2584
3106
  _framework;
3107
+ scene;
3108
+ _material;
2585
3109
  _bufferUsage = BufferUsage.VERTEX;
2586
3110
  _state = State.CREATED;
2587
3111
  _vertexData;
@@ -2592,12 +3116,95 @@ class Mesh {
2592
3116
  _normals;
2593
3117
  _vertexBuffer;
2594
3118
  _indexBuffer;
3119
+ _subMeshes = [];
3120
+ _modelMatrix = mat4.create();
3121
+ _rotationQuat = quat.create();
3122
+ // Cached copies to detect in-place edits; NaN forces the first computation.
3123
+ _lastPosition = vec3.fromValues(NaN, NaN, NaN);
3124
+ _lastRotation = vec3.create();
3125
+ _lastScale = vec3.create();
3126
+ _modelBuffer;
3127
+ /** The matrix last uploaded to the model buffer. */
3128
+ _uploadedMatrix = mat4.create();
3129
+ /** @inheritDoc */
3130
+ position = vec3.create();
3131
+ /** @inheritDoc */
3132
+ rotation = vec3.create();
3133
+ /** @inheritDoc */
3134
+ scale = vec3.fromValues(1, 1, 1);
3135
+ /** @inheritDoc */
3136
+ get modelMatrix() {
3137
+ if (!vec3.exactEquals(this._lastPosition, this.position) || !vec3.exactEquals(this._lastRotation, this.rotation) || !vec3.exactEquals(this._lastScale, this.scale)) {
3138
+ vec3.copy(this._lastPosition, this.position);
3139
+ vec3.copy(this._lastRotation, this.rotation);
3140
+ vec3.copy(this._lastScale, this.scale);
3141
+ const q = quat.identity(this._rotationQuat);
3142
+ quat.rotateY(q, q, this.rotation[1]);
3143
+ quat.rotateX(q, q, this.rotation[0]);
3144
+ quat.rotateZ(q, q, this.rotation[2]);
3145
+ mat4.fromRotationTranslationScale(this._modelMatrix, q, this.position, this.scale);
3146
+ }
3147
+ return this._modelMatrix;
3148
+ }
3149
+ /** @inheritDoc Created on first access and disposed with the mesh. */
3150
+ get modelBuffer() {
3151
+ if (!this._modelBuffer) {
3152
+ mat4.copy(this._uploadedMatrix, this.modelMatrix);
3153
+ this._modelBuffer = this._framework.bufferFactory.createUniformBuffer(
3154
+ this._uploadedMatrix,
3155
+ BufferUsage.UNIFORM | BufferUsage.COPY_DST,
3156
+ "Mesh.modelBuffer"
3157
+ );
3158
+ }
3159
+ return this._modelBuffer;
3160
+ }
3161
+ /** Uploads the model matrix when the transform changed since the last upload. */
3162
+ _updateModelBuffer() {
3163
+ const buffer = this.modelBuffer;
3164
+ const matrix = this.modelMatrix;
3165
+ if (!mat4.exactEquals(this._uploadedMatrix, matrix)) {
3166
+ mat4.copy(this._uploadedMatrix, matrix);
3167
+ buffer.update(this._uploadedMatrix);
3168
+ }
3169
+ return buffer;
3170
+ }
2595
3171
  /**
2596
- * The constructor.
2597
- * @param framework The framework.
3172
+ * Assigns this mesh's model buffer to a material that uses one (has a `modelBuffer` property),
3173
+ * only when it differs. Materials without one (e.g. sprites) are skipped and no buffer is created for them.
3174
+ * @internal
2598
3175
  */
2599
- constructor(framework) {
2600
- this._framework = framework;
3176
+ __assignModelBuffer(material) {
3177
+ if (!("modelBuffer" in material)) {
3178
+ return;
3179
+ }
3180
+ const buffer = this._updateModelBuffer();
3181
+ if (material.modelBuffer !== buffer) {
3182
+ material.modelBuffer = buffer;
3183
+ }
3184
+ }
3185
+ /** The material. Undefined for a mesh drawn through its submeshes. */
3186
+ get material() {
3187
+ return this._material;
3188
+ }
3189
+ /** Sets the material. */
3190
+ set material(material) {
3191
+ this._material = material;
3192
+ }
3193
+ /** The submeshes. They share this mesh's vertex and index buffers. */
3194
+ get subMeshes() {
3195
+ return this._subMeshes;
3196
+ }
3197
+ /**
3198
+ * Adds a submesh that draws a range of this mesh's index buffer with its own material.
3199
+ * @param material The material of the submesh.
3200
+ * @param indexStart The first index of the range.
3201
+ * @param indexCount The number of indices in the range.
3202
+ * @returns The submesh.
3203
+ */
3204
+ addSubMesh(material, indexStart, indexCount) {
3205
+ const subMesh = new SubMesh(this, material, indexStart, indexCount);
3206
+ this._subMeshes.push(subMesh);
3207
+ return subMesh;
2601
3208
  }
2602
3209
  /**
2603
3210
  * The state of a mesh.
@@ -2726,11 +3333,14 @@ class Mesh {
2726
3333
  * Sets the mesh from a geometry.
2727
3334
  *
2728
3335
  * This will create vertex buffer and index buffer from geometry.
2729
- * Positions, Colors, TextureCoordinates and Normals will be ignored.
2730
3336
  * @param geometry - The geometry.
2731
- * @param format - The format for geometry.
3337
+ * @param geometryFormat - The vertex layout. Defaults to the material's geometry format.
2732
3338
  */
2733
- setGeometry(geometry, format) {
3339
+ setGeometry(geometry, geometryFormat) {
3340
+ const format = geometryFormat ?? this._material?.geometryFormat;
3341
+ if (format === void 0) {
3342
+ throw new Error("Cannot create buffers for a Mesh without a material or geometry format.");
3343
+ }
2734
3344
  if (geometry.vertexCount == 0) {
2735
3345
  const message = "Cannot create buffers for a Mesh as Mesh has no positions. Check that geometry.vertexCount is set.";
2736
3346
  throw new Error(message);
@@ -2759,25 +3369,44 @@ class Mesh {
2759
3369
  this._colors = meshParameters.colors;
2760
3370
  this.applyChanges();
2761
3371
  }
2762
- /**
2763
- * Dispose of the mesh.
2764
- */
3372
+ /** @inheritDoc */
3373
+ render() {
3374
+ if (this.subMeshes?.length > 0) {
3375
+ for (let i2 = 0; i2 < this.subMeshes.length; ++i2) {
3376
+ this.subMeshes[i2].render();
3377
+ }
3378
+ }
3379
+ if (this.material) {
3380
+ this.__assignModelBuffer(this.material);
3381
+ this.material.beforeRender();
3382
+ this.material.renderMesh(this);
3383
+ }
3384
+ }
3385
+ /**
3386
+ * Dispose of the mesh.
3387
+ */
2765
3388
  dispose() {
3389
+ this.scene?.remove(this);
3390
+ this._subMeshes.length = 0;
2766
3391
  this._vertexBuffer?.dispose();
2767
3392
  this._vertexBuffer = void 0;
2768
3393
  this._indexBuffer?.dispose();
2769
3394
  this._indexBuffer = void 0;
3395
+ this._modelBuffer?.dispose();
3396
+ this._modelBuffer = void 0;
2770
3397
  }
2771
3398
  }
2772
3399
  class QuadMesh extends Mesh {
2773
3400
  /**
2774
3401
  * The constructor.
2775
3402
  * @param framework The framework.
3403
+ * @param scene The scene the quad adds itself to.
3404
+ * @param material The material.
2776
3405
  * @param _isUpdatable Makes mesh updatable.
2777
3406
  * @param scale The scale factor for geometry. If not set, it is set to (1,1).
2778
3407
  */
2779
- constructor(framework, _isUpdatable, scale) {
2780
- super(framework);
3408
+ constructor(framework, scene, material, _isUpdatable, scale) {
3409
+ super(framework, scene, material);
2781
3410
  this._isUpdatable = _isUpdatable;
2782
3411
  if (_isUpdatable) {
2783
3412
  this._bufferUsage = BufferUsage.VERTEX | BufferUsage.COPY_DST;
@@ -2887,38 +3516,65 @@ class MeshFactory {
2887
3516
  }
2888
3517
  _framework;
2889
3518
  /** @inheritDoc */
2890
- create(geometry, geometryFormat) {
2891
- const mesh = new Mesh(this._framework);
2892
- mesh.setGeometry(geometry, geometryFormat);
3519
+ create(scene, geometry, material) {
3520
+ const mesh = new Mesh(this._framework, scene, material);
3521
+ mesh.setGeometry(geometry);
3522
+ return mesh;
3523
+ }
3524
+ /** @inheritDoc */
3525
+ createWithSubMeshes(scene, geometry, subMeshes) {
3526
+ if (subMeshes.length == 0) {
3527
+ throw new Error("createWithSubMeshes needs at least one submesh.");
3528
+ }
3529
+ const format = subMeshes[0].material.geometryFormat;
3530
+ if (subMeshes.some((s2) => s2.material.geometryFormat != format)) {
3531
+ throw new Error("All submesh materials must use the same geometry format, as they share one vertex buffer.");
3532
+ }
3533
+ const mesh = new Mesh(this._framework, scene);
3534
+ mesh.setGeometry(geometry, format);
3535
+ for (const subMesh of subMeshes) {
3536
+ mesh.addSubMesh(subMesh.material, subMesh.indexStart, subMesh.indexCount);
3537
+ }
2893
3538
  return mesh;
2894
3539
  }
2895
3540
  /** @inheritDoc */
2896
- createQuadMesh(isUpdatable, scale) {
2897
- const quadMesh = new QuadMesh(this._framework, isUpdatable, scale);
2898
- quadMesh.initialize();
2899
- return quadMesh;
3541
+ createQuadMesh(_isUpdatable, _scale) {
3542
+ throw new Error("Meshes not implemented.");
2900
3543
  }
2901
3544
  }
2902
3545
  class InspectTextureMipsMaterial {
2903
3546
  _framework;
2904
3547
  _mipLevel = [0];
3548
+ /** Created when the model buffer is assigned (the pipeline requires one). */
2905
3549
  _pipeline;
3550
+ /** Texture and sampler set before the pipeline exists; applied when it is created. */
3551
+ _pendingTexture;
3552
+ _pendingSampler;
3553
+ _samplerSet = false;
2906
3554
  _projectionViewBuffer;
2907
3555
  _fragmentDataBuffer;
2908
3556
  _modelBuffer;
2909
- _previousModelMatrix = mat4.create();
2910
3557
  constructor(framework) {
2911
3558
  this._framework = framework;
2912
- void this._framework;
2913
3559
  const usage = BufferUsage.UNIFORM | BufferUsage.COPY_DST;
2914
- this._projectionViewBuffer = framework.bufferFactory.createUniformBuffer(this._previousModelMatrix, usage);
2915
- this._modelBuffer = framework.bufferFactory.createUniformBuffer(this._previousModelMatrix, usage);
3560
+ this._projectionViewBuffer = framework.bufferFactory.createUniformBuffer(mat4.create(), usage);
2916
3561
  this._fragmentDataBuffer = framework.bufferFactory.createUniformBuffer(this._mipLevel, usage);
2917
- this._pipeline = framework.renderPipelineFactory.createInspectTextureMipsRenderPipeline(
3562
+ }
3563
+ /** Creates the pipeline with the current bindings. */
3564
+ _createPipeline(modelBuffer) {
3565
+ const pipeline = this._framework.renderPipelineFactory.createInspectTextureMipsRenderPipeline(
2918
3566
  this._projectionViewBuffer,
2919
- this._modelBuffer,
3567
+ modelBuffer,
2920
3568
  this._fragmentDataBuffer
2921
3569
  );
3570
+ if (this._pendingTexture) {
3571
+ pipeline.spriteTexture = this._pendingTexture;
3572
+ }
3573
+ if (this._samplerSet) {
3574
+ pipeline.textureSampler = this._pendingSampler;
3575
+ }
3576
+ this._pipeline = pipeline;
3577
+ return pipeline;
2922
3578
  }
2923
3579
  /**
2924
3580
  * The mip level to inspect.
@@ -2928,30 +3584,55 @@ class InspectTextureMipsMaterial {
2928
3584
  * The texture to inspect.
2929
3585
  */
2930
3586
  get texture() {
2931
- return this._pipeline.spriteTexture;
3587
+ return this._pipeline?.spriteTexture ?? this._pendingTexture;
2932
3588
  }
2933
3589
  /**
2934
3590
  * The texture to inspect.
2935
3591
  */
2936
3592
  set texture(value) {
2937
- this._pipeline.spriteTexture = value;
3593
+ this._pendingTexture = value;
3594
+ if (this._pipeline) {
3595
+ this._pipeline.spriteTexture = value;
3596
+ }
2938
3597
  }
2939
3598
  /**
2940
3599
  * The texture sampler.
2941
3600
  */
2942
3601
  get textureSampler() {
2943
- return this._pipeline.textureSampler;
3602
+ return this._pipeline ? this._pipeline.textureSampler : this._pendingSampler;
2944
3603
  }
2945
3604
  /**
2946
3605
  * The texture sampler.
2947
3606
  */
2948
3607
  set textureSampler(value) {
2949
- this._pipeline.textureSampler = value;
3608
+ this._pendingSampler = value;
3609
+ this._samplerSet = true;
3610
+ if (this._pipeline) {
3611
+ this._pipeline.textureSampler = value;
3612
+ }
2950
3613
  }
2951
3614
  /**
2952
- * The model matrix.
3615
+ * The model buffer (std140 `mat4`). Owned by the mesh, which assigns it before drawing; never disposed by this material.
3616
+ * Undefined until assigned: rendering without one throws.
2953
3617
  */
2954
- modelMatrix = mat4.create();
3618
+ get modelBuffer() {
3619
+ return this._modelBuffer;
3620
+ }
3621
+ /** Binds the model buffer to the pipeline. */
3622
+ set modelBuffer(value) {
3623
+ if (value === this._modelBuffer) {
3624
+ return;
3625
+ }
3626
+ this._modelBuffer = value;
3627
+ if (!value) {
3628
+ return;
3629
+ }
3630
+ if (this._pipeline) {
3631
+ this._pipeline.modelBuffer = value;
3632
+ } else {
3633
+ this._createPipeline(value);
3634
+ }
3635
+ }
2955
3636
  /**
2956
3637
  * The geometry format.
2957
3638
  */
@@ -2962,44 +3643,49 @@ class InspectTextureMipsMaterial {
2962
3643
  this._mipLevel[0] = this.mipLevel;
2963
3644
  this._fragmentDataBuffer.update(this._mipLevel);
2964
3645
  }
2965
- if (!mat4.equals(this._previousModelMatrix, this.modelMatrix)) {
2966
- mat4.copy(this._previousModelMatrix, this.modelMatrix);
2967
- this._modelBuffer.update(this._previousModelMatrix);
2968
- }
2969
3646
  }
2970
3647
  /** @inheritDoc */
2971
3648
  renderMesh(mesh) {
2972
- this.render(mesh.vertexBuffer, mesh.indexBuffer);
3649
+ this.render(mesh.vertexBuffer, mesh.indexBuffer, mesh.indexCount ?? -1, mesh.indexStart ?? 0);
2973
3650
  }
2974
3651
  /** @inheritDoc */
2975
- render(vertexBuffer, indexBuffer) {
2976
- this._pipeline.render(vertexBuffer, indexBuffer);
3652
+ render(vertexBuffer, indexBuffer, indicesCount, indicesOffset) {
3653
+ if (!this._modelBuffer) {
3654
+ throw new Error("InspectTextureMipsMaterial: no modelBuffer. Render it through a mesh (the mesh assigns its model buffer) or set material.modelBuffer.");
3655
+ }
3656
+ (this._pipeline ?? this._createPipeline(this._modelBuffer)).render(vertexBuffer, indexBuffer, indicesCount, indicesOffset);
2977
3657
  }
2978
3658
  /** @inheritDoc */
2979
3659
  dispose() {
2980
- this._pipeline.dispose();
3660
+ this._pipeline?.dispose();
3661
+ this._pipeline = void 0;
2981
3662
  this._projectionViewBuffer.dispose();
2982
- this._modelBuffer.dispose();
2983
3663
  this._fragmentDataBuffer.dispose();
2984
3664
  }
2985
3665
  }
2986
3666
  class UnlitMaterialDescriptor {
2987
3667
  /** The primitive state. */
2988
3668
  primitiveState;
2989
- /** The projection view buffer to use */
2990
- projectionViewBuffer;
3669
+ /** The camera used to render this material. */
3670
+ camera;
2991
3671
  }
2992
3672
  class UnlitMaterial {
2993
3673
  _framework;
2994
3674
  _color = Color.white();
2995
3675
  _materialData = [1, 1, 1, 1];
3676
+ /** Created when the model buffer is assigned (the pipeline requires one). */
2996
3677
  _pipeline;
3678
+ _primitiveState;
3679
+ /** Texture and sampler set before the pipeline exists; applied when it is created. */
3680
+ _pendingTexture;
3681
+ _pendingSampler;
3682
+ _samplerSet = false;
2997
3683
  _projectionViewBuffer;
2998
3684
  /** False when the projection view buffer came from the descriptor. The caller owns that one. */
2999
3685
  _ownsProjectionViewBuffer;
3686
+ _camera;
3000
3687
  _materialBuffer;
3001
3688
  _modelBuffer;
3002
- _previousModelMatrix = mat4.create();
3003
3689
  /**
3004
3690
  * The constructor.
3005
3691
  * @param framework The framework.
@@ -3007,18 +3693,54 @@ class UnlitMaterial {
3007
3693
  */
3008
3694
  constructor(framework, config) {
3009
3695
  this._framework = framework;
3010
- void this._framework;
3011
3696
  const usage = BufferUsage.UNIFORM | BufferUsage.COPY_DST;
3012
- this._ownsProjectionViewBuffer = !config?.projectionViewBuffer;
3013
- this._projectionViewBuffer = config?.projectionViewBuffer ?? framework.bufferFactory.createUniformBuffer(this._previousModelMatrix, usage);
3014
- this._modelBuffer = framework.bufferFactory.createUniformBuffer(this._previousModelMatrix, usage);
3697
+ this._camera = config?.camera;
3698
+ this._ownsProjectionViewBuffer = !config?.camera?.projectionViewBuffer;
3699
+ this._projectionViewBuffer = config?.camera?.projectionViewBuffer ?? framework.bufferFactory.createUniformBuffer(mat4.create(), usage);
3015
3700
  this._materialBuffer = framework.bufferFactory.createUniformBuffer(this._materialData, usage);
3016
- this._pipeline = framework.renderPipelineFactory.createUnlitRenderPipeline(
3701
+ this._primitiveState = config?.primitiveState;
3702
+ }
3703
+ /** Creates the pipeline with the current bindings. */
3704
+ _createPipeline(modelBuffer) {
3705
+ const pipeline = this._framework.renderPipelineFactory.createUnlitRenderPipeline(
3017
3706
  this._projectionViewBuffer,
3018
- this._modelBuffer,
3707
+ modelBuffer,
3019
3708
  this._materialBuffer,
3020
- config?.primitiveState
3709
+ this._primitiveState
3021
3710
  );
3711
+ if (this._pendingTexture) {
3712
+ pipeline.diffuseTexture = this._pendingTexture;
3713
+ }
3714
+ if (this._samplerSet) {
3715
+ pipeline.diffuseTextureSampler = this._pendingSampler;
3716
+ }
3717
+ this._pipeline = pipeline;
3718
+ return pipeline;
3719
+ }
3720
+ /**
3721
+ * The camera. Setting it binds the camera's projection view buffer to the pipeline
3722
+ * (the material's own placeholder buffer is disposed). Setting undefined keeps the last bound buffer.
3723
+ */
3724
+ get camera() {
3725
+ return this._camera;
3726
+ }
3727
+ /** @inheritDoc */
3728
+ set camera(camera) {
3729
+ if (camera === this._camera) {
3730
+ return;
3731
+ }
3732
+ this._camera = camera;
3733
+ if (!camera) {
3734
+ return;
3735
+ }
3736
+ if (this._ownsProjectionViewBuffer) {
3737
+ this._projectionViewBuffer.dispose();
3738
+ this._ownsProjectionViewBuffer = false;
3739
+ }
3740
+ this._projectionViewBuffer = camera.projectionViewBuffer;
3741
+ if (this._pipeline) {
3742
+ this._pipeline.projectionViewBuffer = this._projectionViewBuffer;
3743
+ }
3022
3744
  }
3023
3745
  /**
3024
3746
  * The diffuse color.
@@ -3028,30 +3750,55 @@ class UnlitMaterial {
3028
3750
  * The texture to inspect.
3029
3751
  */
3030
3752
  get diffuseTexture() {
3031
- return this._pipeline.diffuseTexture;
3753
+ return this._pipeline?.diffuseTexture ?? this._pendingTexture;
3032
3754
  }
3033
3755
  /**
3034
3756
  * The texture to inspect.
3035
3757
  */
3036
3758
  set diffuseTexture(value) {
3037
- this._pipeline.diffuseTexture = value;
3759
+ this._pendingTexture = value;
3760
+ if (this._pipeline) {
3761
+ this._pipeline.diffuseTexture = value;
3762
+ }
3038
3763
  }
3039
3764
  /**
3040
3765
  * The texture sampler.
3041
3766
  */
3042
3767
  get diffuseTextureSampler() {
3043
- return this._pipeline.diffuseTextureSampler;
3768
+ return this._pipeline ? this._pipeline.diffuseTextureSampler : this._pendingSampler;
3044
3769
  }
3045
3770
  /**
3046
3771
  * The texture sampler.
3047
3772
  */
3048
3773
  set diffuseTextureSampler(value) {
3049
- this._pipeline.diffuseTextureSampler = value;
3774
+ this._pendingSampler = value;
3775
+ this._samplerSet = true;
3776
+ if (this._pipeline) {
3777
+ this._pipeline.diffuseTextureSampler = value;
3778
+ }
3050
3779
  }
3051
3780
  /**
3052
- * The model matrix.
3781
+ * The model buffer (std140 `mat4`). Owned by the mesh, which assigns it before drawing; never disposed by this material.
3782
+ * Undefined until assigned: rendering without one throws.
3053
3783
  */
3054
- modelMatrix = mat4.create();
3784
+ get modelBuffer() {
3785
+ return this._modelBuffer;
3786
+ }
3787
+ /** Binds the model buffer to the pipeline. */
3788
+ set modelBuffer(value) {
3789
+ if (value === this._modelBuffer) {
3790
+ return;
3791
+ }
3792
+ this._modelBuffer = value;
3793
+ if (!value) {
3794
+ return;
3795
+ }
3796
+ if (this._pipeline) {
3797
+ this._pipeline.modelBuffer = value;
3798
+ } else {
3799
+ this._createPipeline(value);
3800
+ }
3801
+ }
3055
3802
  /**
3056
3803
  * The geometry format.
3057
3804
  */
@@ -3066,26 +3813,321 @@ class UnlitMaterial {
3066
3813
  this._materialData[3] = this._color.a;
3067
3814
  this._materialBuffer.update(this._materialData);
3068
3815
  }
3069
- if (!mat4.equals(this._previousModelMatrix, this.modelMatrix)) {
3070
- mat4.copy(this._previousModelMatrix, this.modelMatrix);
3071
- this._modelBuffer.update(this._previousModelMatrix);
3072
- }
3073
3816
  }
3074
3817
  /** @inheritDoc */
3075
3818
  renderMesh(mesh) {
3076
- this.render(mesh.vertexBuffer, mesh.indexBuffer);
3819
+ this.render(mesh.vertexBuffer, mesh.indexBuffer, mesh.indexCount ?? -1, mesh.indexStart ?? 0);
3077
3820
  }
3078
3821
  /** @inheritDoc */
3079
- render(vertexBuffer, indexBuffer) {
3080
- this._pipeline.render(vertexBuffer, indexBuffer);
3822
+ render(vertexBuffer, indexBuffer, indicesCount, indicesOffset) {
3823
+ if (!this._modelBuffer) {
3824
+ throw new Error("UnlitMaterial: no modelBuffer. Render it through a mesh (the mesh assigns its model buffer) or set material.modelBuffer.");
3825
+ }
3826
+ (this._pipeline ?? this._createPipeline(this._modelBuffer)).render(vertexBuffer, indexBuffer, indicesCount, indicesOffset);
3081
3827
  }
3082
3828
  /** @inheritDoc */
3083
3829
  dispose() {
3084
- this._pipeline.dispose();
3830
+ this._pipeline?.dispose();
3831
+ this._pipeline = void 0;
3085
3832
  if (this._ownsProjectionViewBuffer) {
3086
3833
  this._projectionViewBuffer.dispose();
3087
3834
  }
3088
- this._modelBuffer.dispose();
3835
+ this._materialBuffer.dispose();
3836
+ }
3837
+ }
3838
+ class SpriteMaterial {
3839
+ _renderPipeline;
3840
+ /**
3841
+ * The constructor.
3842
+ * @param framework - The framework.
3843
+ * @param descriptor - The sprite material descriptor.
3844
+ */
3845
+ constructor(framework, descriptor) {
3846
+ this._renderPipeline = framework.renderPipelineFactory.createSpriteRenderPipeline(descriptor.projectionViewBuffer);
3847
+ this._renderPipeline.textureSampler = descriptor.sampler;
3848
+ this._renderPipeline.spriteTexture = descriptor.texture;
3849
+ }
3850
+ /** @inheritDoc */
3851
+ get geometryFormat() {
3852
+ return this._renderPipeline.geometryFormat;
3853
+ }
3854
+ /** The sprite texture. */
3855
+ get texture() {
3856
+ return this._renderPipeline.spriteTexture;
3857
+ }
3858
+ /** The sprite texture. */
3859
+ set texture(value) {
3860
+ this._renderPipeline.spriteTexture = value;
3861
+ }
3862
+ /** @inheritDoc */
3863
+ beforeRender() {
3864
+ }
3865
+ /** @inheritDoc */
3866
+ renderMesh(mesh) {
3867
+ this.render(mesh.vertexBuffer, mesh.indexBuffer, mesh.indexCount ?? -1, mesh.indexStart ?? 0);
3868
+ }
3869
+ /**
3870
+ * Renders the material.
3871
+ * @param vertexBuffer - The vertex buffer.
3872
+ * @param indexBuffer - The index buffer.
3873
+ * @param indicesCount - The number of indices to draw. If -1 draws all indices.
3874
+ * @param indicesOffset - Indices offset. Defaults to 0.
3875
+ */
3876
+ render(vertexBuffer, indexBuffer, indicesCount, indicesOffset) {
3877
+ this._renderPipeline.render(vertexBuffer, indexBuffer, indicesCount, indicesOffset);
3878
+ }
3879
+ /** @inheritDoc */
3880
+ dispose() {
3881
+ this._renderPipeline.dispose();
3882
+ }
3883
+ }
3884
+ class ForwardBlinnPhongMaterialDescriptor {
3885
+ /** The primitive state. */
3886
+ primitiveState;
3887
+ /** The projection view buffer. */
3888
+ projectionViewBuffer;
3889
+ /** The camera position buffer. */
3890
+ cameraPositionBuffer;
3891
+ /** The lights buffer. The shader is compiled for its light counts. Can also be set later through `material.lightsBuffer`. */
3892
+ lightsBuffer;
3893
+ }
3894
+ class ForwardBlinnPhongMaterial {
3895
+ _framework;
3896
+ _lightsBuffer;
3897
+ _diffuseColorCache;
3898
+ _ambientColorCache;
3899
+ _specularColorCache;
3900
+ _shininessCache;
3901
+ // std140: each vec3 starts on a 16-byte boundary, so pad each colour to 4 floats.
3902
+ _materialData = [
3903
+ 0.1,
3904
+ 0.1,
3905
+ 0.1,
3906
+ 0,
3907
+ // Ambient
3908
+ 0.8,
3909
+ 0.8,
3910
+ 0.8,
3911
+ 0,
3912
+ // Diffuse
3913
+ 0.5,
3914
+ 0.5,
3915
+ 0.5,
3916
+ 32
3917
+ // Specular, shininess (the float fills the vec3's padding)
3918
+ ];
3919
+ /** Created when the model buffer is assigned (the pipeline requires one). */
3920
+ _pipeline;
3921
+ _primitiveState;
3922
+ /** Texture and sampler set before the pipeline exists; applied when it is created. */
3923
+ _pendingTexture;
3924
+ _pendingSampler;
3925
+ _samplerSet = false;
3926
+ _projectionViewBuffer;
3927
+ _camera;
3928
+ _cameraPositionBuffer;
3929
+ _materialBuffer;
3930
+ _modelBuffer;
3931
+ /**
3932
+ * The constructor.
3933
+ * @param framework The framework.
3934
+ * @param descriptor The material descriptor. Its projection view and camera position buffers are shared and are not disposed by this material.
3935
+ * @throws Error when `descriptor`, `descriptor.projectionViewBuffer` or `descriptor.cameraPositionBuffer` is missing.
3936
+ */
3937
+ constructor(framework, descriptor) {
3938
+ const missing = [
3939
+ descriptor?.projectionViewBuffer ? void 0 : "descriptor.projectionViewBuffer",
3940
+ descriptor?.cameraPositionBuffer ? void 0 : "descriptor.cameraPositionBuffer"
3941
+ ].filter((x2) => x2 !== void 0);
3942
+ if (missing.length > 0) {
3943
+ throw new Error(`ForwardBlinnPhongMaterial: ${missing.join(" and ")} ${missing.length > 1 ? "are" : "is"} required (e.g. camera.projectionViewBuffer and camera.positionBuffer).`);
3944
+ }
3945
+ this._framework = framework;
3946
+ const usage = BufferUsage.UNIFORM | BufferUsage.COPY_DST;
3947
+ this._diffuseColorCache = this.diffuseColor.copy();
3948
+ this._ambientColorCache = this.ambientColor.copy();
3949
+ this._specularColorCache = this.specularColor.copy();
3950
+ this._shininessCache = this.shininess;
3951
+ this._projectionViewBuffer = descriptor.projectionViewBuffer;
3952
+ this._cameraPositionBuffer = descriptor.cameraPositionBuffer;
3953
+ this._materialBuffer = framework.bufferFactory.createUniformBuffer(this._materialData, usage);
3954
+ this._primitiveState = descriptor.primitiveState;
3955
+ this._lightsBuffer = descriptor.lightsBuffer;
3956
+ }
3957
+ /** Creates the pipeline with the current bindings. */
3958
+ _createPipeline(modelBuffer) {
3959
+ const pipeline = this._framework.renderPipelineFactory.createForwardBlinnPhongRenderPipeline({
3960
+ projectionViewBuffer: this._projectionViewBuffer,
3961
+ modelBuffer,
3962
+ materialBuffer: this._materialBuffer,
3963
+ cameraPositionBuffer: this._cameraPositionBuffer,
3964
+ lightsBuffer: this._lightsBuffer?.buffer,
3965
+ maxDirectionalLights: this._lightsBuffer?.maxDirectionalLights,
3966
+ maxPointLights: this._lightsBuffer?.maxPointLights,
3967
+ primitiveState: this._primitiveState
3968
+ });
3969
+ if (this._pendingTexture) {
3970
+ pipeline.diffuseTexture = this._pendingTexture;
3971
+ }
3972
+ if (this._samplerSet) {
3973
+ pipeline.diffuseTextureSampler = this._pendingSampler;
3974
+ }
3975
+ this._pipeline = pipeline;
3976
+ return pipeline;
3977
+ }
3978
+ /**
3979
+ * The ambient color of this material.
3980
+ * Alpha is ignored. By default, it is (0.1,0.1,0.1).
3981
+ */
3982
+ ambientColor = new Color(0.1, 0.1, 0.1);
3983
+ /**
3984
+ * The diffuse color of this material.
3985
+ * Alpha is ignored. By default, it is (0.8,0.8,0.8).
3986
+ */
3987
+ diffuseColor = new Color(0.8, 0.8, 0.8);
3988
+ /**
3989
+ * The specular color of this material (MTL `Ks`).
3990
+ * Alpha is ignored. By default, it is (0.5,0.5,0.5).
3991
+ */
3992
+ specularColor = new Color(0.5, 0.5, 0.5);
3993
+ /**
3994
+ * The specular exponent (MTL `Ns`). Higher values give smaller, sharper highlights. By default, it is 32.
3995
+ */
3996
+ shininess = 32;
3997
+ /**
3998
+ * The camera. Setting it binds the camera's projection view and position buffers to the pipeline
3999
+ * (used by the scene when its camera changes). Setting undefined keeps the last bound buffers.
4000
+ * Undefined until set: the constructor only takes the buffers.
4001
+ */
4002
+ get camera() {
4003
+ return this._camera;
4004
+ }
4005
+ /** @inheritDoc */
4006
+ set camera(camera) {
4007
+ if (camera === this._camera) {
4008
+ return;
4009
+ }
4010
+ this._camera = camera;
4011
+ if (!camera) {
4012
+ return;
4013
+ }
4014
+ if (this._projectionViewBuffer !== camera.projectionViewBuffer) {
4015
+ this._projectionViewBuffer = camera.projectionViewBuffer;
4016
+ if (this._pipeline) {
4017
+ this._pipeline.projectionViewBuffer = this._projectionViewBuffer;
4018
+ }
4019
+ }
4020
+ if (this._cameraPositionBuffer !== camera.positionBuffer) {
4021
+ this._cameraPositionBuffer = camera.positionBuffer;
4022
+ if (this._pipeline) {
4023
+ this._pipeline.cameraPositionBuffer = this._cameraPositionBuffer;
4024
+ }
4025
+ }
4026
+ }
4027
+ /** The lights buffer with the scene's lights. Shared, not disposed by this material. */
4028
+ get lightsBuffer() {
4029
+ return this._lightsBuffer;
4030
+ }
4031
+ /** The lights buffer with the scene's lights. Shared, not disposed by this material. */
4032
+ set lightsBuffer(lightsBuffer) {
4033
+ this._lightsBuffer = lightsBuffer;
4034
+ if (lightsBuffer && this._pipeline) {
4035
+ this._pipeline.setLightCounts(lightsBuffer.maxDirectionalLights, lightsBuffer.maxPointLights);
4036
+ this._pipeline.lightsBuffer = lightsBuffer.buffer;
4037
+ }
4038
+ }
4039
+ /**
4040
+ * The texture to inspect.
4041
+ */
4042
+ get diffuseTexture() {
4043
+ return this._pipeline?.diffuseTexture ?? this._pendingTexture;
4044
+ }
4045
+ /**
4046
+ * The texture to inspect.
4047
+ */
4048
+ set diffuseTexture(value) {
4049
+ this._pendingTexture = value;
4050
+ if (this._pipeline) {
4051
+ this._pipeline.diffuseTexture = value;
4052
+ }
4053
+ }
4054
+ /**
4055
+ * The texture sampler.
4056
+ */
4057
+ get diffuseTextureSampler() {
4058
+ return this._pipeline ? this._pipeline.diffuseTextureSampler : this._pendingSampler;
4059
+ }
4060
+ /**
4061
+ * The texture sampler.
4062
+ */
4063
+ set diffuseTextureSampler(value) {
4064
+ this._pendingSampler = value;
4065
+ this._samplerSet = true;
4066
+ if (this._pipeline) {
4067
+ this._pipeline.diffuseTextureSampler = value;
4068
+ }
4069
+ }
4070
+ /**
4071
+ * The model buffer (std140 `mat4`). Owned by the mesh, which assigns it before drawing; never disposed by this material.
4072
+ * Undefined until assigned: rendering without one throws.
4073
+ */
4074
+ get modelBuffer() {
4075
+ return this._modelBuffer;
4076
+ }
4077
+ /** Binds the model buffer to the pipeline. */
4078
+ set modelBuffer(value) {
4079
+ if (value === this._modelBuffer) {
4080
+ return;
4081
+ }
4082
+ this._modelBuffer = value;
4083
+ if (!value) {
4084
+ return;
4085
+ }
4086
+ if (this._pipeline) {
4087
+ this._pipeline.modelBuffer = value;
4088
+ } else {
4089
+ this._createPipeline(value);
4090
+ }
4091
+ }
4092
+ /**
4093
+ * The geometry format.
4094
+ */
4095
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_NORMAL3;
4096
+ /** @inheritDoc */
4097
+ beforeRender() {
4098
+ if (!this._diffuseColorCache.equals(this.diffuseColor) || !this._ambientColorCache.equals(this.ambientColor) || !this._specularColorCache.equals(this.specularColor) || this._shininessCache !== this.shininess) {
4099
+ this._ambientColorCache.set(this.ambientColor);
4100
+ this._diffuseColorCache.set(this.diffuseColor);
4101
+ this._specularColorCache.set(this.specularColor);
4102
+ this._shininessCache = this.shininess;
4103
+ this._materialData[0] = this._ambientColorCache.r;
4104
+ this._materialData[1] = this._ambientColorCache.g;
4105
+ this._materialData[2] = this._ambientColorCache.b;
4106
+ this._materialData[4] = this._diffuseColorCache.r;
4107
+ this._materialData[5] = this._diffuseColorCache.g;
4108
+ this._materialData[6] = this._diffuseColorCache.b;
4109
+ this._materialData[8] = this._specularColorCache.r;
4110
+ this._materialData[9] = this._specularColorCache.g;
4111
+ this._materialData[10] = this._specularColorCache.b;
4112
+ this._materialData[11] = this._shininessCache;
4113
+ this._materialBuffer.update(this._materialData);
4114
+ }
4115
+ }
4116
+ /** @inheritDoc */
4117
+ renderMesh(mesh) {
4118
+ this.render(mesh.vertexBuffer, mesh.indexBuffer, mesh.indexCount ?? -1, mesh.indexStart ?? 0);
4119
+ }
4120
+ /** @inheritDoc */
4121
+ render(vertexBuffer, indexBuffer, indicesCount, indicesOffset) {
4122
+ if (!this._modelBuffer) {
4123
+ throw new Error("ForwardBlinnPhongMaterial: no modelBuffer. Render it through a mesh (the mesh assigns its model buffer) or set material.modelBuffer.");
4124
+ }
4125
+ (this._pipeline ?? this._createPipeline(this._modelBuffer)).render(vertexBuffer, indexBuffer, indicesCount, indicesOffset);
4126
+ }
4127
+ /** @inheritDoc */
4128
+ dispose() {
4129
+ this._pipeline?.dispose();
4130
+ this._pipeline = void 0;
3089
4131
  this._materialBuffer.dispose();
3090
4132
  }
3091
4133
  }
@@ -3106,6 +4148,318 @@ class MaterialFactory {
3106
4148
  createUnlitMaterial(descriptor) {
3107
4149
  return new UnlitMaterial(this._framework, descriptor);
3108
4150
  }
4151
+ /** @inheritDoc */
4152
+ createSpriteMaterial(descriptor) {
4153
+ return new SpriteMaterial(this._framework, descriptor);
4154
+ }
4155
+ /** @inheritDoc */
4156
+ createForwardBlinnPhongMaterial(descriptor) {
4157
+ return new ForwardBlinnPhongMaterial(this._framework, descriptor);
4158
+ }
4159
+ }
4160
+ let nextLightId = 1;
4161
+ function createLightId() {
4162
+ return nextLightId++;
4163
+ }
4164
+ class AmbientLight {
4165
+ /**
4166
+ * The constructor.
4167
+ * @param descriptor The optional descriptor. The colour is copied.
4168
+ * @param scene The optional scene to register with.
4169
+ */
4170
+ constructor(descriptor, scene) {
4171
+ this.id = descriptor?.id ?? createLightId();
4172
+ this.name = descriptor?.name;
4173
+ this.color = descriptor?.color?.copy() ?? Color.black();
4174
+ this.intensity = descriptor?.intensity ?? 1;
4175
+ scene?.add(this);
4176
+ }
4177
+ /** The unique id of this light. */
4178
+ id;
4179
+ /** The optional name for a light. */
4180
+ name;
4181
+ /** The light color. Alpha is ignored. */
4182
+ color;
4183
+ /** Multiplies {@link color} when written to the lights buffer. */
4184
+ intensity;
4185
+ }
4186
+ class DirectionalLight {
4187
+ /**
4188
+ * The constructor.
4189
+ * @param descriptor The optional descriptor. Colour and direction are copied.
4190
+ * @param scene The optional scene; the light adds itself via {@link Scene.add}.
4191
+ * @throws Error when the scene has no free directional light slot left (the light is then not in the scene).
4192
+ */
4193
+ constructor(descriptor, scene) {
4194
+ this.id = descriptor?.id ?? createLightId();
4195
+ this.name = descriptor?.name;
4196
+ this.color = descriptor?.color?.copy() ?? Color.white();
4197
+ this.intensity = descriptor?.intensity ?? 1;
4198
+ this.direction = descriptor?.direction ? vec3.clone(descriptor.direction) : vec3.fromValues(0, -1, 0);
4199
+ scene?.add(this);
4200
+ }
4201
+ /** The unique id of this light. */
4202
+ id;
4203
+ /** The optional name for a light. */
4204
+ name;
4205
+ /** The light colour. Alpha is ignored. */
4206
+ color;
4207
+ /** Multiplies {@link color} when written to the lights buffer. */
4208
+ intensity;
4209
+ /** The direction in which the light travels. */
4210
+ direction;
4211
+ }
4212
+ class PointLight {
4213
+ /** The default constant attenuation. */
4214
+ static DEFAULT_CONSTANT = 1;
4215
+ /** The default linear attenuation. */
4216
+ static DEFAULT_LINEAR = 0.09;
4217
+ /** The default quadratic attenuation. */
4218
+ static DEFAULT_QUADRATIC = 0.032;
4219
+ /**
4220
+ * The constructor.
4221
+ * @param descriptor The optional descriptor. Colour and position are copied.
4222
+ * @param scene The optional scene; the light adds itself via {@link Scene.add}.
4223
+ * @throws Error when the scene has no free point light slot left (the light is then not in the scene).
4224
+ */
4225
+ constructor(descriptor, scene) {
4226
+ this.id = descriptor?.id ?? createLightId();
4227
+ this.name = descriptor?.name;
4228
+ this.color = descriptor?.color?.copy() ?? Color.white();
4229
+ this.intensity = descriptor?.intensity ?? 1;
4230
+ this.position = descriptor?.position ? vec3.clone(descriptor.position) : vec3.create();
4231
+ this.constant = descriptor?.constant ?? PointLight.DEFAULT_CONSTANT;
4232
+ this.linear = descriptor?.linear ?? PointLight.DEFAULT_LINEAR;
4233
+ this.quadratic = descriptor?.quadratic ?? PointLight.DEFAULT_QUADRATIC;
4234
+ scene?.add(this);
4235
+ }
4236
+ /** The unique id of this light. */
4237
+ id;
4238
+ /** The optional name for a light. */
4239
+ name;
4240
+ /** The light colour. Alpha is ignored. */
4241
+ color;
4242
+ /** Multiplies {@link color} when written to the lights buffer. */
4243
+ intensity;
4244
+ /** World-space position of the light. */
4245
+ position;
4246
+ /** Constant attenuation term. */
4247
+ constant;
4248
+ /** Linear attenuation term. */
4249
+ linear;
4250
+ /** Quadratic attenuation term. */
4251
+ quadratic;
4252
+ }
4253
+ const DEFAULT_DIRECTIONAL_LIGHTS_COUNT = 3;
4254
+ const DEFAULT_POINT_LIGHTS_COUNT = 3;
4255
+ class Scene {
4256
+ _meshes = [];
4257
+ _activeAmbientLight;
4258
+ _disposed = false;
4259
+ _activeCamera;
4260
+ /**
4261
+ * The camera the scene renders with; undefined until one is set or added.
4262
+ * Setting it assigns it to the materials of every mesh (and submesh) whose camera differs;
4263
+ * {@link update} also assigns it to meshes and submeshes added later.
4264
+ */
4265
+ get camera() {
4266
+ return this._activeCamera;
4267
+ }
4268
+ set camera(value) {
4269
+ this._activeCamera = value;
4270
+ if (!value) {
4271
+ return;
4272
+ }
4273
+ for (const mesh of this._meshes) {
4274
+ Scene._assignCameraToMesh(mesh, value);
4275
+ }
4276
+ }
4277
+ /** The ambient lights registered with this scene. */
4278
+ ambientLights = [];
4279
+ /**
4280
+ * The constructor.
4281
+ * @param framework The framework.
4282
+ */
4283
+ constructor(framework) {
4284
+ this._activeAmbientLight = framework.lightsFactory.createAmbientLight(void 0, this);
4285
+ this.__forwardLightsBuffer = framework.lightsFactory.__createLightsBuffer({
4286
+ ambientLight: this._activeAmbientLight,
4287
+ maxDirectionalLights: 3,
4288
+ maxPointLights: 5
4289
+ });
4290
+ }
4291
+ /**
4292
+ * The lights buffer used for forward shading.
4293
+ * @internal
4294
+ */
4295
+ __forwardLightsBuffer;
4296
+ /** The meshes in this scene, in the order they were added. */
4297
+ get meshes() {
4298
+ return this._meshes;
4299
+ }
4300
+ /** The directional lights in this scene. */
4301
+ get directionalLights() {
4302
+ return this.__forwardLightsBuffer.directionalLights;
4303
+ }
4304
+ /** The point lights in this scene. */
4305
+ get pointLights() {
4306
+ return this.__forwardLightsBuffer.pointLights;
4307
+ }
4308
+ /**
4309
+ * Gets the active ambient light.
4310
+ */
4311
+ get activeAmbientLight() {
4312
+ return this._activeAmbientLight;
4313
+ }
4314
+ /** Sets the active ambient light; it is added to {@link ambientLights} if needed. */
4315
+ set activeAmbientLight(value) {
4316
+ this._addAmbientLight(value);
4317
+ this._activeAmbientLight = value;
4318
+ this.__forwardLightsBuffer.ambientLight = value;
4319
+ }
4320
+ /**
4321
+ * Adds a mesh, a light or a camera. Adding an item that is already in the scene does nothing.
4322
+ * - Mesh: drawn by {@link render}; gets the scene camera.
4323
+ * - Ambient light: added to {@link ambientLights} (use {@link activeAmbientLight} to make it the lit one).
4324
+ * - Directional / point light: added to the lights buffer.
4325
+ * - Camera: becomes the scene {@link camera}.
4326
+ * Constructors given a scene (meshes, lights, the perspective camera) call this themselves.
4327
+ * @param item The item to add.
4328
+ * @throws Error when all slots for a light kind are in use (3 directional, 5 point), or for an unsupported item.
4329
+ */
4330
+ add(item) {
4331
+ if (item instanceof AmbientLight) {
4332
+ this._addAmbientLight(item);
4333
+ } else if (item instanceof DirectionalLight || item instanceof PointLight) {
4334
+ this.__forwardLightsBuffer.add(item);
4335
+ } else if (Scene._isCamera(item)) {
4336
+ this.camera = item;
4337
+ } else if (Scene._isMesh(item)) {
4338
+ if (!this._meshes.includes(item)) {
4339
+ this._meshes.push(item);
4340
+ if (this._activeCamera) {
4341
+ Scene._assignCameraToMesh(item, this._activeCamera);
4342
+ }
4343
+ }
4344
+ } else {
4345
+ throw new Error("Scene.add: expected a mesh, an AmbientLight, DirectionalLight, PointLight or a camera.");
4346
+ }
4347
+ }
4348
+ /**
4349
+ * Removes a mesh, a light or a camera. The mesh is not disposed.
4350
+ * Removing the active camera clears {@link camera}.
4351
+ * @param item The item to remove.
4352
+ * @returns True when the item was in the scene (for a camera: was the active camera).
4353
+ * @throws Error when removing the active ambient light (set another {@link activeAmbientLight} first), or for an unsupported item.
4354
+ */
4355
+ remove(item) {
4356
+ if (item instanceof AmbientLight) {
4357
+ if (item === this._activeAmbientLight) {
4358
+ throw new Error("Scene.remove: cannot remove the active ambient light; set scene.activeAmbientLight to another light first.");
4359
+ }
4360
+ return Scene._removeFrom(this.ambientLights, item);
4361
+ }
4362
+ if (item instanceof DirectionalLight || item instanceof PointLight) {
4363
+ return this.__forwardLightsBuffer.remove(item);
4364
+ }
4365
+ if (Scene._isCamera(item)) {
4366
+ if (item !== this._activeCamera) {
4367
+ return false;
4368
+ }
4369
+ this._activeCamera = void 0;
4370
+ return true;
4371
+ }
4372
+ if (Scene._isMesh(item)) {
4373
+ return Scene._removeFrom(this._meshes, item);
4374
+ }
4375
+ throw new Error("Scene.remove: expected a mesh, an AmbientLight, DirectionalLight, PointLight or a camera.");
4376
+ }
4377
+ /**
4378
+ * Adds a directional or point light.
4379
+ * @deprecated Use {@link add}.
4380
+ * @throws Error when all slots for that kind are in use (3 directional, 5 point).
4381
+ */
4382
+ addLight(light) {
4383
+ this.add(light);
4384
+ }
4385
+ /**
4386
+ * Removes a directional or point light.
4387
+ * @deprecated Use {@link remove}.
4388
+ * @returns True when the light was in the scene.
4389
+ */
4390
+ removeLight(light) {
4391
+ return this.remove(light);
4392
+ }
4393
+ _addAmbientLight(ambientLight) {
4394
+ if (!this.ambientLights.includes(ambientLight)) {
4395
+ this.ambientLights.push(ambientLight);
4396
+ }
4397
+ }
4398
+ static _removeFrom(list, item) {
4399
+ const index = list.indexOf(item);
4400
+ if (index < 0) {
4401
+ return false;
4402
+ }
4403
+ list.splice(index, 1);
4404
+ return true;
4405
+ }
4406
+ /** Cameras have the camera buffers and updateBuffers(); meshes and lights do not. */
4407
+ static _isCamera(item) {
4408
+ const camera = item;
4409
+ return typeof camera === "object" && camera !== null && "projectionViewBuffer" in camera && "viewMatrix" in camera && typeof camera.updateBuffers === "function";
4410
+ }
4411
+ /** Meshes have render() and a material slot or submeshes. */
4412
+ static _isMesh(item) {
4413
+ const mesh = item;
4414
+ return typeof mesh === "object" && mesh !== null && typeof mesh.render === "function" && ("material" in mesh || "subMeshes" in mesh);
4415
+ }
4416
+ /**
4417
+ * Updates the scene: assigns {@link camera} to the mesh materials that don't have it yet, and uploads changed lights.
4418
+ * @param _time The game time.
4419
+ * @public
4420
+ */
4421
+ update(_time) {
4422
+ if (this._activeCamera) {
4423
+ for (const mesh of this._meshes) {
4424
+ Scene._assignCameraToMesh(mesh, this._activeCamera);
4425
+ }
4426
+ }
4427
+ this.__forwardLightsBuffer.update();
4428
+ }
4429
+ static _assignCameraToMesh(mesh, camera) {
4430
+ Scene._assignCamera(mesh.material, camera);
4431
+ for (const subMesh of mesh.subMeshes ?? []) {
4432
+ Scene._assignCamera(subMesh.material, camera);
4433
+ }
4434
+ }
4435
+ static _assignCamera(material, camera) {
4436
+ if (!camera.projectionViewBuffer || !camera.positionBuffer) {
4437
+ return;
4438
+ }
4439
+ if (material && "camera" in material && material.camera !== camera) {
4440
+ material.camera = camera;
4441
+ }
4442
+ }
4443
+ /**
4444
+ * Renders the meshes.
4445
+ */
4446
+ render() {
4447
+ for (const mesh of this._meshes) {
4448
+ mesh.render();
4449
+ }
4450
+ }
4451
+ /** Disposes the meshes still in the scene and the lights buffer. Calling it twice does nothing. */
4452
+ dispose() {
4453
+ if (this._disposed) {
4454
+ return;
4455
+ }
4456
+ this._disposed = true;
4457
+ for (const mesh of this._meshes.slice()) {
4458
+ mesh.dispose();
4459
+ }
4460
+ this._meshes.length = 0;
4461
+ this.__forwardLightsBuffer.dispose();
4462
+ }
3109
4463
  }
3110
4464
  var Key = /* @__PURE__ */ ((Key2) => {
3111
4465
  Key2[Key2["NONE"] = 0] = "NONE";
@@ -3380,16 +4734,195 @@ class CpuImageProcessor {
3380
4734
  return promise;
3381
4735
  }
3382
4736
  }
4737
+ class WebGlLightsBuffer {
4738
+ /** Floats for the ambient light: vec3 padded to 16 bytes. */
4739
+ static AMBIENT_FLOAT_COUNT = 4;
4740
+ /** Floats per directional light: two vec3, each padded to 16 bytes. */
4741
+ static DIRECTIONAL_LIGHT_FLOAT_COUNT = 8;
4742
+ /** Floats per point light: vec3 light + float constant, vec3 position + float linear, float quadratic, padded to 48 bytes. */
4743
+ static POINT_LIGHT_FLOAT_COUNT = 12;
4744
+ _ambientLight;
4745
+ _buffer;
4746
+ _data;
4747
+ _uploaded;
4748
+ _directionalLights = [];
4749
+ _pointLights = [];
4750
+ _disposed = false;
4751
+ /**
4752
+ * The constructor.
4753
+ * @param framework The framework, used to create the uniform buffer.
4754
+ * @param descriptor The optional initial lights and directional light count.
4755
+ */
4756
+ constructor(framework, descriptor) {
4757
+ this.ambientLight = descriptor?.ambientLight ?? new AmbientLight();
4758
+ this.maxDirectionalLights = descriptor?.maxDirectionalLights ?? DEFAULT_DIRECTIONAL_LIGHTS_COUNT;
4759
+ this.maxPointLights = descriptor?.maxPointLights ?? DEFAULT_POINT_LIGHTS_COUNT;
4760
+ for (const light of descriptor?.directionalLights ?? []) {
4761
+ this.add(light);
4762
+ }
4763
+ for (const light of descriptor?.pointLights ?? []) {
4764
+ this.add(light);
4765
+ }
4766
+ const floatCount = WebGlLightsBuffer.AMBIENT_FLOAT_COUNT + this.maxDirectionalLights * WebGlLightsBuffer.DIRECTIONAL_LIGHT_FLOAT_COUNT + this.maxPointLights * WebGlLightsBuffer.POINT_LIGHT_FLOAT_COUNT;
4767
+ this._data = new Array(floatCount).fill(0);
4768
+ this._writeData();
4769
+ this._uploaded = [...this._data];
4770
+ this._buffer = framework.bufferFactory.createUniformBuffer(this._data, BufferUsage.UNIFORM | BufferUsage.COPY_DST);
4771
+ }
4772
+ /** @inheritDoc */
4773
+ get ambientLight() {
4774
+ return this._ambientLight;
4775
+ }
4776
+ /** @inheritDoc */
4777
+ set ambientLight(ambientLight) {
4778
+ this._ambientLight = ambientLight;
4779
+ }
4780
+ /** @inheritDoc */
4781
+ get directionalLights() {
4782
+ return this._directionalLights;
4783
+ }
4784
+ /** @inheritDoc */
4785
+ maxDirectionalLights;
4786
+ /** @inheritDoc */
4787
+ get pointLights() {
4788
+ return this._pointLights;
4789
+ }
4790
+ /** @inheritDoc */
4791
+ add(light) {
4792
+ const [list, max, kind] = this._listFor(light);
4793
+ if (list.includes(light)) {
4794
+ return;
4795
+ }
4796
+ if (list.length >= max) {
4797
+ throw new Error(`LightsBuffer: cannot add ${kind} light ${light.id}; all ${max} ${kind} light slots are in use (max${kind === "directional" ? "Directional" : "Point"}Lights = ${max}).`);
4798
+ }
4799
+ list.push(light);
4800
+ }
4801
+ /** @inheritDoc */
4802
+ remove(light) {
4803
+ const [list] = this._listFor(light);
4804
+ const index = list.indexOf(light);
4805
+ if (index < 0) {
4806
+ return false;
4807
+ }
4808
+ list.splice(index, 1);
4809
+ return true;
4810
+ }
4811
+ /** @inheritDoc */
4812
+ maxPointLights;
4813
+ /** @inheritDoc */
4814
+ get buffer() {
4815
+ return this._buffer;
4816
+ }
4817
+ /** @inheritDoc */
4818
+ update() {
4819
+ this._writeData();
4820
+ if (!this._hasChanged()) {
4821
+ return;
4822
+ }
4823
+ this._buffer.update(this._data);
4824
+ for (let i2 = 0; i2 < this._data.length; i2++) {
4825
+ this._uploaded[i2] = this._data[i2];
4826
+ }
4827
+ }
4828
+ /** @inheritDoc */
4829
+ dispose() {
4830
+ if (this._disposed) {
4831
+ return;
4832
+ }
4833
+ this._disposed = true;
4834
+ this._buffer.dispose();
4835
+ }
4836
+ _writeAmbientLight() {
4837
+ this._writeColor(0, this.ambientLight.color, this.ambientLight.intensity);
4838
+ this._data[3] = 0;
4839
+ }
4840
+ _listFor(light) {
4841
+ if (light instanceof DirectionalLight) {
4842
+ return [this._directionalLights, this.maxDirectionalLights, "directional"];
4843
+ }
4844
+ if (light instanceof PointLight) {
4845
+ return [this._pointLights, this.maxPointLights, "point"];
4846
+ }
4847
+ throw new Error("LightsBuffer: expected a DirectionalLight or PointLight instance.");
4848
+ }
4849
+ /** Writes color * intensity as vec3 at offset (the fourth float is left to the caller). */
4850
+ _writeColor(offset, color, intensity) {
4851
+ this._data[offset] = color ? color.r * intensity : 0;
4852
+ this._data[offset + 1] = color ? color.g * intensity : 0;
4853
+ this._data[offset + 2] = color ? color.b * intensity : 0;
4854
+ }
4855
+ _hasChanged() {
4856
+ for (let i2 = 0; i2 < this._data.length; i2++) {
4857
+ if (this._data[i2] !== this._uploaded[i2]) {
4858
+ return true;
4859
+ }
4860
+ }
4861
+ return false;
4862
+ }
4863
+ _writeData() {
4864
+ this._writeAmbientLight();
4865
+ for (let i2 = 0; i2 < this.maxDirectionalLights; i2++) {
4866
+ const offset = WebGlLightsBuffer.AMBIENT_FLOAT_COUNT + i2 * WebGlLightsBuffer.DIRECTIONAL_LIGHT_FLOAT_COUNT;
4867
+ const light = this._directionalLights[i2];
4868
+ this._writeColor(offset, light?.color, light?.intensity ?? 0);
4869
+ this._data[offset + 3] = 0;
4870
+ this._data[offset + 4] = light ? light.direction[0] : 0;
4871
+ this._data[offset + 5] = light ? light.direction[1] : 0;
4872
+ this._data[offset + 6] = light ? light.direction[2] : 0;
4873
+ this._data[offset + 7] = 0;
4874
+ }
4875
+ const pointLightsStart = WebGlLightsBuffer.AMBIENT_FLOAT_COUNT + this.maxDirectionalLights * WebGlLightsBuffer.DIRECTIONAL_LIGHT_FLOAT_COUNT;
4876
+ for (let i2 = 0; i2 < this.maxPointLights; i2++) {
4877
+ const offset = pointLightsStart + i2 * WebGlLightsBuffer.POINT_LIGHT_FLOAT_COUNT;
4878
+ const light = this._pointLights[i2];
4879
+ this._writeColor(offset, light?.color, light?.intensity ?? 0);
4880
+ this._data[offset + 3] = light ? light.constant : 1;
4881
+ this._data[offset + 4] = light ? light.position[0] : 0;
4882
+ this._data[offset + 5] = light ? light.position[1] : 0;
4883
+ this._data[offset + 6] = light ? light.position[2] : 0;
4884
+ this._data[offset + 7] = light ? light.linear : 0;
4885
+ this._data[offset + 8] = light ? light.quadratic : 0;
4886
+ this._data[offset + 9] = 0;
4887
+ this._data[offset + 10] = 0;
4888
+ this._data[offset + 11] = 0;
4889
+ }
4890
+ }
4891
+ }
4892
+ class WebGlAmbientLightFactory {
4893
+ /**
4894
+ * The constructor.
4895
+ * @param _framework The framework.
4896
+ */
4897
+ constructor(_framework) {
4898
+ this._framework = _framework;
4899
+ }
4900
+ _framework;
4901
+ /** The colour used when no descriptor is given. */
4902
+ static DEFAULT_COLOR = new Color(0.1, 0.1, 0.1);
4903
+ /** @inheritDoc */
4904
+ createAmbientLight(desc, scene) {
4905
+ return new AmbientLight({ ...desc, color: desc?.color ?? WebGlAmbientLightFactory.DEFAULT_COLOR }, scene);
4906
+ }
4907
+ /** @inheritDoc */
4908
+ __createLightsBuffer(desc) {
4909
+ return new WebGlLightsBuffer(this._framework, desc);
4910
+ }
4911
+ }
3383
4912
  class WindowManager {
3384
4913
  _canvas;
3385
4914
  _updateCallbacks = [];
3386
4915
  _renderCallbacks = [];
4916
+ _ownsCanvas;
4917
+ _animationFrame = null;
4918
+ _disposed = false;
3387
4919
  /**
3388
4920
  * The constructor for the WindowManager class.
3389
4921
  * @param canvas An optional HTMLCanvasElement to associate with the window manager. If `null`, the window manager creates its own canvas and appends it to `document.body`.
3390
4922
  */
3391
4923
  constructor(canvas = null) {
3392
4924
  this._canvas = canvas ?? document.createElement("canvas");
4925
+ this._ownsCanvas = !canvas;
3393
4926
  if (!canvas) {
3394
4927
  document.body.appendChild(this._canvas);
3395
4928
  }
@@ -3427,18 +4960,34 @@ class WindowManager {
3427
4960
  initializeForWebGl() {
3428
4961
  throw new Error("Method not implemented.");
3429
4962
  }
4963
+ /** Stops the event loop, drops all callbacks and removes the canvas when this manager created it. */
3430
4964
  dispose() {
3431
- throw new Error("Method not implemented.");
4965
+ if (this._disposed) {
4966
+ return;
4967
+ }
4968
+ this._disposed = true;
4969
+ if (this._animationFrame !== null) {
4970
+ cancelAnimationFrame(this._animationFrame);
4971
+ this._animationFrame = null;
4972
+ }
4973
+ this._updateCallbacks.length = 0;
4974
+ this._renderCallbacks.length = 0;
4975
+ if (this._ownsCanvas) {
4976
+ this._canvas.remove();
4977
+ }
3432
4978
  }
3433
4979
  /** @inheritdoc */
3434
4980
  runEventLoop() {
4981
+ if (this._disposed) {
4982
+ return;
4983
+ }
3435
4984
  for (const callback of this._updateCallbacks) {
3436
4985
  callback();
3437
4986
  }
3438
4987
  for (const callback of this._renderCallbacks) {
3439
4988
  callback();
3440
4989
  }
3441
- requestAnimationFrame(() => this.runEventLoop());
4990
+ this._animationFrame = requestAnimationFrame(() => this.runEventLoop());
3442
4991
  }
3443
4992
  /** @inheritdoc */
3444
4993
  get canvas() {
@@ -3465,123 +5014,6 @@ class FrameworkConfiguration {
3465
5014
  /** @inheritDoc */
3466
5015
  powerPreference = PowerPreferenceType.DEFAULT;
3467
5016
  }
3468
- const RenderConfigurationSymbol = /* @__PURE__ */ Symbol("RenderConfiguration");
3469
- class BaseGeometry {
3470
- /** @inheritdoc */
3471
- vertexCount = 0;
3472
- /** @inheritdoc */
3473
- positions;
3474
- /** @inheritdoc */
3475
- positionsFormat = VertexFormat.FLOAT_32X3;
3476
- /** @inheritdoc */
3477
- colors;
3478
- /** @inheritdoc */
3479
- colorFormat = VertexFormat.FLOAT_32X4;
3480
- /** @inheritdoc */
3481
- textureCoords;
3482
- /** @inheritdoc */
3483
- textureFormat = VertexFormat.FLOAT_32X2;
3484
- /** @inheritdoc */
3485
- normals;
3486
- /** @inheritdoc */
3487
- normalsFormat = VertexFormat.FLOAT_32X3;
3488
- /** The indices of geometry. */
3489
- indices;
3490
- /**
3491
- * Pushes vertex data from source format to destination format.
3492
- * @param index The index of the vertex to push.
3493
- * @param source The source vertex data.
3494
- * @param sourceFormat The vertex format of the source vertex data.
3495
- * @param destination The destination array to push the vertex data to.
3496
- * @param destinationFormat The vertex format of the destination vertex data.
3497
- */
3498
- pushFormat(index, source, sourceFormat, destination, destinationFormat) {
3499
- let _0 = 0;
3500
- let _1 = 0;
3501
- let _2 = 0;
3502
- let _3 = 0;
3503
- if (sourceFormat == VertexFormat.FLOAT_32) {
3504
- _0 = source[index];
3505
- } else if (sourceFormat == VertexFormat.FLOAT_32X2) {
3506
- _0 = source[index * 2];
3507
- _1 = source[index * 2 + 1];
3508
- } else if (sourceFormat == VertexFormat.FLOAT_32X3) {
3509
- _0 = source[index * 3];
3510
- _1 = source[index * 3 + 1];
3511
- _2 = source[index * 3 + 2];
3512
- } else if (sourceFormat == VertexFormat.FLOAT_32X4) {
3513
- _0 = source[index * 4];
3514
- _1 = source[index * 4 + 1];
3515
- _2 = source[index * 4 + 2];
3516
- _3 = source[index * 4 + 3];
3517
- }
3518
- destination.push(_0);
3519
- if (destinationFormat >= VertexFormat.FLOAT_32X2) {
3520
- destination.push(_1);
3521
- }
3522
- if (destinationFormat >= VertexFormat.FLOAT_32X3) {
3523
- destination.push(_2);
3524
- }
3525
- if (destinationFormat >= VertexFormat.FLOAT_32X4) {
3526
- destination.push(_3);
3527
- }
3528
- }
3529
- /**
3530
- * Converts the geometry data to interleaved vertex data based on the specified geometry format.
3531
- * @param format The geometry format to convert the geometry data to.
3532
- * @returns The interleaved vertex data as an array of numbers.
3533
- */
3534
- toInterleaved(format) {
3535
- let interleavedData = [];
3536
- switch (format) {
3537
- case GeometryFormat.POS3_COLOR4: {
3538
- let count = this.positions.length / 3;
3539
- for (let i2 = 0; i2 < count; i2++) {
3540
- this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
3541
- this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
3542
- }
3543
- break;
3544
- }
3545
- case GeometryFormat.POS3_TEXTURECOORDS2: {
3546
- let count = this.positions.length / 3;
3547
- for (let i2 = 0; i2 < count; i2++) {
3548
- this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
3549
- this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
3550
- }
3551
- break;
3552
- }
3553
- case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2: {
3554
- let count = this.positions.length / 3;
3555
- for (let i2 = 0; i2 < count; i2++) {
3556
- this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
3557
- this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
3558
- this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
3559
- }
3560
- break;
3561
- }
3562
- case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_NORMAL3: {
3563
- let count = this.positions.length / 3;
3564
- for (let i2 = 0; i2 < count; i2++) {
3565
- this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
3566
- this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
3567
- this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X2);
3568
- this.pushFormat(i2, this.normals, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X3);
3569
- }
3570
- break;
3571
- }
3572
- case GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_ALIGNED: {
3573
- let count = this.positions.length / 3;
3574
- for (let i2 = 0; i2 < count; i2++) {
3575
- this.pushFormat(i2, this.positions, VertexFormat.FLOAT_32X3, interleavedData, VertexFormat.FLOAT_32X4);
3576
- this.pushFormat(i2, this.colors, VertexFormat.FLOAT_32X4, interleavedData, VertexFormat.FLOAT_32X4);
3577
- this.pushFormat(i2, this.textureCoords, VertexFormat.FLOAT_32X2, interleavedData, VertexFormat.FLOAT_32X4);
3578
- }
3579
- break;
3580
- }
3581
- }
3582
- return interleavedData;
3583
- }
3584
- }
3585
5017
  class GeometryBuilder {
3586
5018
  static DEFAULT_SCALE = vec2.fromValues(1, 1);
3587
5019
  /** @inheritdoc */
@@ -3678,6 +5110,35 @@ class GeometryBuilder {
3678
5110
  ];
3679
5111
  return geometry;
3680
5112
  }
5113
+ /** @inheritdoc */
5114
+ create(descriptor) {
5115
+ const vertexCount = descriptor.positions.length / 3;
5116
+ return new BaseGeometry(
5117
+ [...descriptor.positions],
5118
+ descriptor.indices ? [...descriptor.indices] : void 0,
5119
+ descriptor.normals ? [...descriptor.normals] : new Array(vertexCount * 3).fill(0),
5120
+ descriptor.textureCoords ? [...descriptor.textureCoords] : new Array(vertexCount * 2).fill(0),
5121
+ descriptor.colors ? [...descriptor.colors] : new Array(vertexCount * 4).fill(1)
5122
+ );
5123
+ }
5124
+ }
5125
+ class ObjGeometryMapper {
5126
+ /**
5127
+ * Creates a geometry descriptor from an OBJ geometry definition.
5128
+ * @param definition The geometry definition, e.g. `result.geometry` from `ObjLoader.loadAsync`.
5129
+ * @param useMaterialColors True keeps the loader's per-vertex `Kd` colours; false (the default) uses white,
5130
+ * for when each submesh's material carries the colour instead.
5131
+ * @returns The descriptor. It references the definition's arrays; `geometryBuilder.create` copies them.
5132
+ */
5133
+ static toDescriptor(definition, useMaterialColors = false) {
5134
+ return {
5135
+ positions: definition.positions,
5136
+ indices: definition.indices,
5137
+ normals: definition.normals,
5138
+ textureCoords: definition.textureCoords,
5139
+ colors: useMaterialColors ? definition.colors : void 0
5140
+ };
5141
+ }
3681
5142
  }
3682
5143
  class ContentManager {
3683
5144
  /**
@@ -3693,6 +5154,7 @@ class ContentManager {
3693
5154
  shaderModuleLoader;
3694
5155
  _ktx2Factory = new x();
3695
5156
  _ktx2TextureCache = {};
5157
+ _objLoader = new ObjLoader();
3696
5158
  /** @inheritDoc */
3697
5159
  async loadKtx2Async(path, contentConfig) {
3698
5160
  let ktx2Texture = this._ktx2TextureCache[path];
@@ -3705,9 +5167,19 @@ class ContentManager {
3705
5167
  }
3706
5168
  return ktx2Texture;
3707
5169
  }
5170
+ /**
5171
+ * Disposes the cached shader modules (their GL programs) and forgets cached KTX2 data.
5172
+ * Called from `Framework.dispose()`.
5173
+ */
5174
+ dispose() {
5175
+ this.shaderModuleLoader.dispose?.();
5176
+ for (const key of Object.keys(this._ktx2TextureCache)) {
5177
+ delete this._ktx2TextureCache[key];
5178
+ }
5179
+ }
3708
5180
  /** @inheritDoc */
3709
- loadShaderModule(shaderModuleId) {
3710
- return this.shaderModuleLoader.load(shaderModuleId);
5181
+ loadShaderModule(shaderModuleId, keyValueDictionary) {
5182
+ return this.shaderModuleLoader.load(shaderModuleId, keyValueDictionary);
3711
5183
  }
3712
5184
  /** @inheritDoc */
3713
5185
  async loadTexture2DAsync(path, textureDescriptor, contentConfig) {
@@ -3733,10 +5205,152 @@ class ContentManager {
3733
5205
  textureDescriptor?.generateMipmaps
3734
5206
  );
3735
5207
  }
3736
- /** @inheritDoc */
3737
- loadTexture2DFromPixels(width, height, pixels) {
3738
- const view = new Uint8Array(pixels);
3739
- return this._framework.textureFactory.create(width, height, view);
5208
+ /** @inheritDoc */
5209
+ loadTexture2DFromPixels(width, height, pixels) {
5210
+ const view = new Uint8Array(pixels);
5211
+ return this._framework.textureFactory.create(width, height, view);
5212
+ }
5213
+ /** @inheritDoc */
5214
+ async loadMeshAsync(path, scene) {
5215
+ const objResult = await this._loadObj(path);
5216
+ const geometry = this._createGeometry(objResult.geometry);
5217
+ const subMeshes = this._createSubMeshDescriptors(
5218
+ objResult,
5219
+ (definition) => this._createUnlitMaterial(definition, scene.camera)
5220
+ );
5221
+ return this._framework.meshFactory.createWithSubMeshes(scene, geometry, subMeshes);
5222
+ }
5223
+ /** @inheritDoc */
5224
+ async loadBlinnPhongMeshAsync(path, scene) {
5225
+ const camera = scene.camera;
5226
+ if (!camera) {
5227
+ throw new Error(`loadBlinnPhongMeshAsync("${path}"): the scene has no camera. Set scene.camera (or pass the scene to createPerspectiveCamera) before loading a Blinn-Phong mesh.`);
5228
+ }
5229
+ if (!camera.positionBuffer || !camera.projectionViewBuffer) {
5230
+ const what = [camera.projectionViewBuffer ? void 0 : "projectionViewBuffer", camera.positionBuffer ? void 0 : "positionBuffer"].filter((x2) => x2 !== void 0).join(" and ");
5231
+ throw new Error(`loadBlinnPhongMeshAsync("${path}"): the scene camera has no ${what}. Call camera.initialize() first (the camera factory does).`);
5232
+ }
5233
+ const objResult = await this._loadObj(path);
5234
+ const geometry = this._createGeometry(objResult.geometry);
5235
+ const subMeshes = this._createSubMeshDescriptors(
5236
+ objResult,
5237
+ (definition) => this._createBlinnPhongMaterial(definition, camera, scene.__forwardLightsBuffer)
5238
+ );
5239
+ return this._framework.meshFactory.createWithSubMeshes(scene, geometry, subMeshes);
5240
+ }
5241
+ /**
5242
+ * Loads the OBJ file and its .mtl material libraries.
5243
+ * @param path The path or url to the .obj file.
5244
+ * @returns The plain OBJ data.
5245
+ */
5246
+ _loadObj(path) {
5247
+ return this._objLoader.loadAsync(path);
5248
+ }
5249
+ /**
5250
+ * Creates framework geometry from the OBJ geometry.
5251
+ * Vertex colours stay white, because each submesh's material carries the colour.
5252
+ * @param definition The OBJ geometry definition.
5253
+ * @returns The geometry.
5254
+ */
5255
+ _createGeometry(definition) {
5256
+ const descriptor = ObjGeometryMapper.toDescriptor(definition);
5257
+ return this._framework.geometryBuilder.create(descriptor);
5258
+ }
5259
+ /**
5260
+ * Creates one submesh descriptor per material range.
5261
+ * Ranges that use the same material name share one material instance.
5262
+ * @param objResult The loaded OBJ data.
5263
+ * @param createMaterial Creates the material for a definition (undefined without `usemtl` or .mtl entry).
5264
+ * @returns The submesh descriptors, in file order.
5265
+ */
5266
+ _createSubMeshDescriptors(objResult, createMaterial) {
5267
+ const definitionsByName = this._indexMaterialDefinitions(objResult.materials);
5268
+ const materialsByName = /* @__PURE__ */ new Map();
5269
+ const subMeshes = [];
5270
+ for (const range of objResult.geometry.materialRanges) {
5271
+ let material = materialsByName.get(range.materialName);
5272
+ if (material === void 0) {
5273
+ const definition = this._findMaterialDefinition(definitionsByName, range.materialName);
5274
+ material = createMaterial(definition);
5275
+ materialsByName.set(range.materialName, material);
5276
+ }
5277
+ subMeshes.push({
5278
+ material,
5279
+ indexStart: range.indexStart,
5280
+ indexCount: range.indexCount
5281
+ });
5282
+ }
5283
+ return subMeshes;
5284
+ }
5285
+ /**
5286
+ * Indexes material definitions by name.
5287
+ * @param definitions The material definitions from the .mtl files.
5288
+ * @returns A map from material name to definition.
5289
+ */
5290
+ _indexMaterialDefinitions(definitions) {
5291
+ const definitionsByName = /* @__PURE__ */ new Map();
5292
+ for (const definition of definitions) {
5293
+ definitionsByName.set(definition.name, definition);
5294
+ }
5295
+ return definitionsByName;
5296
+ }
5297
+ /**
5298
+ * Finds the definition for a `usemtl` name.
5299
+ * @param definitionsByName The definitions by name.
5300
+ * @param materialName The material name, or undefined for faces without `usemtl`.
5301
+ * @returns The definition, or undefined when there is none.
5302
+ */
5303
+ _findMaterialDefinition(definitionsByName, materialName) {
5304
+ if (materialName === void 0) {
5305
+ return void 0;
5306
+ }
5307
+ return definitionsByName.get(materialName);
5308
+ }
5309
+ /**
5310
+ * Creates the default unlit material for an OBJ material definition.
5311
+ * The diffuse colour is the definition's `Kd` with `d` as alpha; white when there is no definition.
5312
+ * @param definition The material definition, or undefined.
5313
+ * @param camera The scene's current camera, or undefined; the scene assigns it on update.
5314
+ * @returns The unlit material.
5315
+ */
5316
+ _createUnlitMaterial(definition, camera) {
5317
+ const material = this._framework.materialFactory.createUnlitMaterial({
5318
+ camera
5319
+ });
5320
+ const diffuse = definition?.diffuseColor ?? [1, 1, 1];
5321
+ const opacity = definition?.opacity ?? 1;
5322
+ material.diffuseColor = new Color(diffuse[0], diffuse[1], diffuse[2], opacity);
5323
+ return material;
5324
+ }
5325
+ /**
5326
+ * Creates a Blinn-Phong material for an OBJ material definition.
5327
+ * `Kd` (with `d` as alpha) becomes the diffuse colour, white when missing.
5328
+ * `Ka` becomes the ambient colour; when missing the diffuse colour is used, so ambient light tints by the surface colour.
5329
+ * `Ks` and `Ns` become specular colour and shininess; when missing the material defaults stay (0.5 grey, 32).
5330
+ * @param definition The material definition, or undefined.
5331
+ * @param camera The scene's camera; its projection view and position buffers are bound.
5332
+ * @param lightsBuffer The optional lights buffer; the shader is compiled for its light counts.
5333
+ * @returns The Blinn-Phong material.
5334
+ */
5335
+ _createBlinnPhongMaterial(definition, camera, lightsBuffer) {
5336
+ const material = this._framework.materialFactory.createForwardBlinnPhongMaterial({
5337
+ projectionViewBuffer: camera.projectionViewBuffer,
5338
+ cameraPositionBuffer: camera.positionBuffer,
5339
+ lightsBuffer
5340
+ });
5341
+ material.camera = camera;
5342
+ const diffuse = definition?.diffuseColor ?? [1, 1, 1];
5343
+ const ambient = definition?.ambientColor ?? diffuse;
5344
+ material.diffuseColor = new Color(diffuse[0], diffuse[1], diffuse[2], definition?.opacity ?? 1);
5345
+ material.ambientColor = new Color(ambient[0], ambient[1], ambient[2]);
5346
+ if (definition?.specularColor) {
5347
+ const specular = definition.specularColor;
5348
+ material.specularColor = new Color(specular[0], specular[1], specular[2]);
5349
+ }
5350
+ if (definition?.specularExponent !== void 0) {
5351
+ material.shininess = definition.specularExponent;
5352
+ }
5353
+ return material;
3740
5354
  }
3741
5355
  }
3742
5356
  class ARenderer {
@@ -4445,16 +6059,15 @@ class WebGLFilterAnisotropicUtilities {
4445
6059
  * The max anisotropy extension string.
4446
6060
  */
4447
6061
  MAX_ANISOTROPY_EXT = "EXT_texture_filter_anisotropic";
4448
- _extension;
4449
6062
  /**
4450
6063
  * Gets the max anisotropy.
4451
6064
  * @param gl The WebGL2 rendering context.
4452
6065
  * @returns The maximum supported level of anisotropic filtering. `0` if not supported.
4453
6066
  */
4454
6067
  getMaxAnisotropy(gl) {
4455
- this._extension = gl.getExtension(this.MAX_ANISOTROPY_EXT);
4456
- if (this._extension) {
4457
- return gl.getParameter(this._extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
6068
+ const extension = gl.getExtension(this.MAX_ANISOTROPY_EXT);
6069
+ if (extension) {
6070
+ return gl.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
4458
6071
  }
4459
6072
  return 0;
4460
6073
  }
@@ -4465,8 +6078,12 @@ class WebGLFilterAnisotropicUtilities {
4465
6078
  * @param anisotropy The level of anisotropic filtering
4466
6079
  */
4467
6080
  setAnisotropy(gl, texture, anisotropy) {
6081
+ const extension = gl.getExtension(this.MAX_ANISOTROPY_EXT);
6082
+ if (!extension) {
6083
+ return;
6084
+ }
4468
6085
  gl.bindTexture(gl.TEXTURE_2D, texture);
4469
- gl.texParameterf(gl.TEXTURE_2D, this._extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
6086
+ gl.texParameterf(gl.TEXTURE_2D, extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
4470
6087
  }
4471
6088
  }
4472
6089
  class WebGlFrameBufferUtilities {
@@ -5464,25 +7081,38 @@ class WebGlTexture2D extends ATexture2D {
5464
7081
  listener(this);
5465
7082
  }
5466
7083
  }
5467
- static _defaultFilled = null;
7084
+ /** One default texture per framework, so a new framework (and GL context) never gets a texture of an old one. */
7085
+ static _defaultFilled = /* @__PURE__ */ new WeakMap();
5468
7086
  /**
5469
7087
  * Creates a default 1x1 white texture if it doesn't already exist and returns it.
5470
7088
  * This can be used as a placeholder texture when a texture is expected but not available.
5471
7089
  * @param framework The framework instance.
7090
+ * The texture belongs to that framework's GL context; each framework gets its own.
5472
7091
  * @returns The default 1x1 white texture.
5473
7092
  */
5474
7093
  static getOrCreateDefault(framework) {
5475
- if (this._defaultFilled === null || this._defaultFilled.state === State.DISPOSED) {
7094
+ let texture = WebGlTexture2D._defaultFilled.get(framework);
7095
+ if (!texture || texture.state === State.DISPOSED) {
5476
7096
  const desc = new TextureDescriptor();
5477
7097
  desc.width = 1;
5478
7098
  desc.height = 1;
5479
7099
  desc.data = [new Uint8Array([255, 255, 255, 255])];
5480
7100
  desc.textureFormat = framework.renderer.preferredTextureFormat;
5481
7101
  desc.label = "RisDefaultFilledTexture";
5482
- this._defaultFilled = new WebGlTexture2D(framework, desc);
5483
- this._defaultFilled.initialize();
7102
+ texture = new WebGlTexture2D(framework, desc);
7103
+ texture.initialize();
7104
+ WebGlTexture2D._defaultFilled.set(framework, texture);
5484
7105
  }
5485
- return this._defaultFilled;
7106
+ return texture;
7107
+ }
7108
+ /**
7109
+ * Disposes and forgets the default texture of a framework. Called from `Framework.dispose()`.
7110
+ * @param framework The framework instance.
7111
+ */
7112
+ static disposeDefault(framework) {
7113
+ const texture = WebGlTexture2D._defaultFilled.get(framework);
7114
+ WebGlTexture2D._defaultFilled.delete(framework);
7115
+ texture?.dispose();
5486
7116
  }
5487
7117
  }
5488
7118
  function asWebGLTexture2D(texture) {
@@ -5959,7 +7589,6 @@ class WebGlGraphicsDevice extends AGraphicsDevice {
5959
7589
  /** @inheritdoc */
5960
7590
  initialize() {
5961
7591
  this._canvas = this._windowManager.canvas;
5962
- debugger;
5963
7592
  const contextOptions = {
5964
7593
  antialias: false,
5965
7594
  alpha: this._descriptor.alpha,
@@ -6080,11 +7709,13 @@ class WebGlBuffersFactory {
6080
7709
  const mainRenderTargetFlipYVS = "#version 300 es\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec2 a_texCoord;\nout vec2 v_texCoord;\nvoid main()\n {\n gl_Position = vec4(a_position, 1.0);\n v_texCoord = a_texCoord;\n}";
6081
7710
  const mainRenderTargetFlipYFS = "#version 300 es\nprecision highp float;\nin vec2 v_texCoord;\nout vec4 outColor;\nuniform sampler2D u_texture;\nvoid main() \n{\n outColor = texture(u_texture, vec2(v_texCoord.x, 1.0 - v_texCoord.y));\n}";
6082
7711
  const spriteVS = "#version 300 es\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in vec2 a_texCoord;\n\nout vec4 v_color;\nout vec2 v_texCoords;\n\nlayout(std140) uniform CameraBuffer\n{\n mat4 u_projectionView;\n};\n\nvoid main()\n{\n gl_Position = u_projectionView * vec4(a_position, 1.0);\n v_color = a_color;\n v_texCoords = a_texCoord;\n}";
6083
- const spriteFS = "#version 300 es\nprecision mediump float;\n\nin vec4 v_color;\nin vec2 v_texCoords;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n o_outputColor = texture(u_texture, v_texCoords) * v_color + vec4(1.0);\n}";
7712
+ const spriteFS = "#version 300 es\nprecision mediump float;\n\nin vec4 v_color;\nin vec2 v_texCoords;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n o_outputColor = texture(u_texture, v_texCoords) * v_color;\n}";
6084
7713
  const inspectTextureMipsVS = "#version 300 es\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in vec2 a_texCoord;\n\nout vec4 v_color;\nout vec2 v_texCoords;\n\nlayout(std140) uniform CameraBuffer\n{\n mat4 projectionView;\n};\n\nlayout(std140) uniform ModelBuffer\n{\n mat4 model;\n};\n\nvoid main()\n{\n gl_Position = projectionView * model * vec4(a_position, 1.0);\n v_color = a_color;\n v_texCoords = a_texCoord;\n}";
6085
7714
  const inspectTextureMipsFS = "#version 300 es\nprecision mediump float;\n\nin vec4 v_color;\nin vec2 v_texCoords;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nlayout(std140) uniform TextureConstantsBuffer {\n float mipLevel;\n};\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n o_outputColor = textureLod(u_texture, v_texCoords, mipLevel) * v_color;\n}";
6086
7715
  const unlitMaterialVS = "#version 300 es\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in vec2 a_texCoord;\n\nout vec4 v_color;\nout vec2 v_texCoords;\n\nlayout(std140) uniform CameraBuffer\n{\n mat4 projectionView;\n};\n\nlayout(std140) uniform ModelBuffer\n{\n mat4 model;\n};\n\nvoid main()\n{\n gl_Position = projectionView * model * vec4(a_position, 1.0);\n v_color = a_color;\n v_texCoords = a_texCoord;\n}";
6087
- const unlitMaterialFS = "#version 300 es\nprecision mediump float;\n\nin vec4 v_color;\nin vec2 v_texCoords;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nlayout(std140) uniform MaterialBuffer\n {\n vec4 diffuseColor;\n};\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n o_outputColor = texture(u_texture, v_texCoords) * v_color * diffuseColor;\n}";
7716
+ const unlitMaterialFS = "#version 300 es\nprecision mediump float;\n\nin vec4 v_color;\nin vec2 v_texCoords;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nlayout(std140) uniform MaterialBuffer\n{\n vec4 diffuseColor;\n};\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n o_outputColor = texture(u_texture, v_texCoords) * v_color * diffuseColor;\n}";
7717
+ const forwardBlinnPhongVS = "#version 300 es\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in vec2 a_texCoord;\nlayout(location = 3) in vec3 a_normal;\n\nout vec4 v_color;\nout vec2 v_texCoords;\nout vec3 v_normal;\nout vec3 v_worldPosition;\n\nlayout(std140) uniform CameraBuffer\n{\n mat4 projectionView;\n};\n\nlayout(std140) uniform ModelBuffer\n{\n mat4 model;\n};\n\nvoid main()\n{\n vec4 worldPosition = model * vec4(a_position, 1.0);\n gl_Position = projectionView * worldPosition;\n v_color = a_color;\n v_texCoords = a_texCoord;\n // World-space normal through the normal matrix, correct for non-uniform mesh scale.\n v_normal = transpose(inverse(mat3(model))) * a_normal;\n v_worldPosition = worldPosition.xyz;\n}\n";
7718
+ const forwardBlinnPhongFS = "#version 300 es\nprecision mediump float;\n\n#define DIRECTIONAL_LIGHTS_COUNT ##DIRECTIONAL_LIGHTS_COUNT##\n#define POINT_LIGHTS_COUNT ##POINT_LIGHTS_COUNT##\n\nin vec4 v_color;\nin vec2 v_texCoords;\nin vec3 v_normal;\nin vec3 v_worldPosition;\n\nlayout(location = 0) out vec4 o_outputColor;\n\nlayout(std140) uniform MaterialBuffer\n{\n vec3 u_ambientColor;\n vec3 u_diffuseColor;\n vec3 u_specularColor;\n float u_shininess;\n};\n\nlayout(std140) uniform CameraPositionBuffer\n{\n vec3 u_cameraPosition;\n};\n\nstruct DirectionalLight\n{\n vec3 light;\n vec3 direction;\n};\n\nstruct PointLight\n{\n vec3 light;\n float constant;\n vec3 position;\n float linear;\n float quadratic;\n};\n\nlayout(std140) uniform LightsBuffer\n{\n vec3 u_ambientLight;\n DirectionalLight u_directionalLight[DIRECTIONAL_LIGHTS_COUNT];\n PointLight u_pointLight[POINT_LIGHTS_COUNT];\n};\n\nuniform sampler2D u_texture;\n\nvoid main()\n{\n vec3 normal = normalize(v_normal);\n\n // Ambient lighting\n vec3 ambient = u_ambientColor * u_ambientLight;\n\n vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);\n\n // Directional diffuse and Blinn-Phong specular lighting\n vec3 diffuse = vec3(0.0);\n vec3 specular = vec3(0.0);\n\n for (int i = 0; i < DIRECTIONAL_LIGHTS_COUNT; ++i)\n {\n vec3 lightColor = u_directionalLight[i].light;\n vec3 lightDirection = normalize(-u_directionalLight[i].direction);\n\n float NdotL = max(dot(normal, lightDirection), 0.0);\n\n diffuse += u_diffuseColor * lightColor * NdotL;\n\n if (NdotL > 0.0)\n {\n vec3 halfway = normalize(lightDirection + viewDirection);\n float NdotH = max(dot(normal, halfway), 0.0);\n specular += u_specularColor * lightColor * pow(NdotH, u_shininess);\n }\n }\n\n // Point lights: same diffuse and specular, scaled by distance attenuation.\n for (int i = 0; i < POINT_LIGHTS_COUNT; ++i)\n {\n vec3 toLight = u_pointLight[i].position - v_worldPosition;\n float lightDistance = length(toLight);\n vec3 lightDirection = toLight / max(lightDistance, 0.0001);\n float attenuation = 1.0 / (u_pointLight[i].constant\n + u_pointLight[i].linear * lightDistance\n + u_pointLight[i].quadratic * lightDistance * lightDistance);\n vec3 lightColor = u_pointLight[i].light * attenuation;\n\n float NdotL = max(dot(normal, lightDirection), 0.0);\n\n diffuse += u_diffuseColor * lightColor * NdotL;\n\n if (NdotL > 0.0)\n {\n vec3 halfway = normalize(lightDirection + viewDirection);\n float NdotH = max(dot(normal, halfway), 0.0);\n specular += u_specularColor * lightColor * pow(NdotH, u_shininess);\n }\n }\n\n // Apply material lighting, texture, and vertex color\n vec4 texel = texture(u_texture, v_texCoords) * v_color;\n // Specular highlights are not tinted by the texture.\n vec3 finalColor = texel.rgb * (ambient + diffuse) + specular;\n\n o_outputColor = vec4(finalColor, texel.a);\n}";
6088
7719
  const ShaderModuleContent = {
6089
7720
  "main_render_target_flip_y": {
6090
7721
  "vertex": mainRenderTargetFlipYVS,
@@ -6105,6 +7736,11 @@ const ShaderModuleContent = {
6105
7736
  "vertex": unlitMaterialVS,
6106
7737
  "fragment": unlitMaterialFS,
6107
7738
  "reflection": {}
7739
+ },
7740
+ "forward_blinn_phong_material": {
7741
+ "vertex": forwardBlinnPhongVS,
7742
+ "fragment": forwardBlinnPhongFS,
7743
+ "reflection": {}
6108
7744
  }
6109
7745
  };
6110
7746
  class WebGlShaderModule {
@@ -6115,11 +7751,12 @@ class WebGlShaderModule {
6115
7751
  * The constructor of the WebGL shader module.
6116
7752
  * @param framework The framework.
6117
7753
  * @param shaderFilePath
7754
+ * @param @param keyValueDictionary - If present, tries to find key in shader such that it replaced ##DIRECTIONAL_LIGHT_COUNT## by given value for key DIRECTIONAL_LIGHST_COUNT.
6118
7755
  */
6119
- constructor(framework, shaderFilePath) {
7756
+ constructor(framework, shaderFilePath, keyValueDictionary) {
6120
7757
  this._graphicsDevice = framework.renderer.graphicsDevice;
6121
7758
  this._gl = this._graphicsDevice.gl;
6122
- this._program = this._loadShaders(shaderFilePath);
7759
+ this._program = this._loadShaders(shaderFilePath, keyValueDictionary);
6123
7760
  }
6124
7761
  /** @inheritDoc */
6125
7762
  stages = [];
@@ -6147,7 +7784,7 @@ class WebGlShaderModule {
6147
7784
  get program() {
6148
7785
  return this._program;
6149
7786
  }
6150
- _loadShaders(shaderFilePath) {
7787
+ _loadShaders(shaderFilePath, keyValueDictionary) {
6151
7788
  const internalShader = this._tryResolveFrameworkShader(shaderFilePath);
6152
7789
  let vertexShaderSource = "";
6153
7790
  let fragmentShaderSource = "";
@@ -6157,6 +7794,12 @@ class WebGlShaderModule {
6157
7794
  } else {
6158
7795
  throw new Error("Not implemented");
6159
7796
  }
7797
+ if (keyValueDictionary) {
7798
+ for (const key in keyValueDictionary) {
7799
+ vertexShaderSource = vertexShaderSource.replace(`##${key}##`, String(keyValueDictionary[key]));
7800
+ fragmentShaderSource = fragmentShaderSource.replace(`##${key}##`, String(keyValueDictionary[key]));
7801
+ }
7802
+ }
6160
7803
  const vertexShader = WebGlUtilities.shader.createVertexShader(this._gl, vertexShaderSource);
6161
7804
  const fragmentShader = WebGlUtilities.shader.createFragmentShader(this._gl, fragmentShaderSource);
6162
7805
  const program = WebGlUtilities.program.create(this._gl, vertexShader, fragmentShader);
@@ -6172,6 +7815,13 @@ class WebGlShaderModule {
6172
7815
  }
6173
7816
  }
6174
7817
  }
7818
+ function shaderModuleCacheKey(id, keyValueDictionary) {
7819
+ if (!keyValueDictionary) {
7820
+ return id;
7821
+ }
7822
+ const entries = Object.keys(keyValueDictionary).sort().map((key) => `${key}=${keyValueDictionary[key]}`);
7823
+ return entries.length === 0 ? id : `${id}?${entries.join("&")}`;
7824
+ }
6175
7825
  class WebGlShaderModuleLoader {
6176
7826
  _shaderModules = /* @__PURE__ */ new Map();
6177
7827
  _framework;
@@ -6182,18 +7832,23 @@ class WebGlShaderModuleLoader {
6182
7832
  constructor(framework) {
6183
7833
  this._framework = framework;
6184
7834
  }
6185
- /**
6186
- * The content types provided by this content module.
6187
- * @param assetFilePathOrIdentifier The asset file path or identifier of the content to load.
6188
- */
6189
- load(assetFilePathOrIdentifier) {
6190
- let shaderModule = this._shaderModules.get(assetFilePathOrIdentifier);
7835
+ /** @inheritDoc */
7836
+ load(assetFilePathOrIdentifier, keyValueDictionary) {
7837
+ const cacheKey = shaderModuleCacheKey(assetFilePathOrIdentifier, keyValueDictionary);
7838
+ let shaderModule = this._shaderModules.get(cacheKey);
6191
7839
  if (!shaderModule) {
6192
- shaderModule = new WebGlShaderModule(this._framework, assetFilePathOrIdentifier);
6193
- this._shaderModules.set(assetFilePathOrIdentifier, shaderModule);
7840
+ shaderModule = new WebGlShaderModule(this._framework, assetFilePathOrIdentifier, keyValueDictionary);
7841
+ this._shaderModules.set(cacheKey, shaderModule);
6194
7842
  }
6195
7843
  return shaderModule;
6196
7844
  }
7845
+ /** Deletes every cached shader program and clears the cache. */
7846
+ dispose() {
7847
+ for (const shaderModule of this._shaderModules.values()) {
7848
+ shaderModule.dispose();
7849
+ }
7850
+ this._shaderModules.clear();
7851
+ }
6197
7852
  }
6198
7853
  class SpriteBatchMesh extends Mesh {
6199
7854
  /**
@@ -6203,13 +7858,22 @@ class SpriteBatchMesh extends Mesh {
6203
7858
  static TOTAL_FLOATS_PER_VERTEX = 9;
6204
7859
  /** The total number of floats in a sprite. 4 for each vertex * 9 floats per vertex (xyz rgba uv). */
6205
7860
  static TOTAL_FLOATS_IN_SPRITE = 4 * this.TOTAL_FLOATS_PER_VERTEX;
7861
+ _spriteMaterial;
6206
7862
  _maxInstances = 0;
6207
7863
  _lastIndex = -1;
7864
+ /** The offset of the first index to draw. Defaults to 0. */
7865
+ indicesOffset = 0;
7866
+ /** The number of indices to draw. If -1 draws all indices. */
7867
+ indicesCount = -1;
6208
7868
  /**
6209
7869
  * The constructor for the batch sprite mesh.
7870
+ * @param framework - The framework.
7871
+ * @param material - The material.
7872
+ * @param numberOfInstances - The number of instances.
6210
7873
  */
6211
- constructor(framework, numberOfInstances) {
6212
- super(framework);
7874
+ constructor(framework, material, numberOfInstances) {
7875
+ super(framework, null, material);
7876
+ this._spriteMaterial = material;
6213
7877
  this._maxInstances = numberOfInstances;
6214
7878
  this._vertexData = new Array(this.maxInstances * SpriteBatchMesh.TOTAL_FLOATS_IN_SPRITE);
6215
7879
  this._setupIndices();
@@ -6227,6 +7891,12 @@ class SpriteBatchMesh extends Mesh {
6227
7891
  }
6228
7892
  this._indices = indices;
6229
7893
  }
7894
+ /**
7895
+ * Draws the mesh with its sprite material, using {@link indicesCount} and {@link indicesOffset}.
7896
+ */
7897
+ draw() {
7898
+ this._spriteMaterial.render(this._vertexBuffer, this._indexBuffer, this.indicesCount, this.indicesOffset);
7899
+ }
6230
7900
  /** The maximum number of instances that can be batched. */
6231
7901
  get maxInstances() {
6232
7902
  return this._maxInstances;
@@ -6393,7 +8063,7 @@ class SpriteBatchDrawable {
6393
8063
  _sampler;
6394
8064
  _maxBatchSize;
6395
8065
  _drawingMesh = null;
6396
- _renderPipeline = null;
8066
+ _material = null;
6397
8067
  _needsResize = false;
6398
8068
  _fromInstance = 0;
6399
8069
  _toInstance = 0;
@@ -6418,10 +8088,12 @@ class SpriteBatchDrawable {
6418
8088
  projectionViewBuffer;
6419
8089
  /** Initialize the drawable */
6420
8090
  initialize() {
6421
- this._renderPipeline = this._framework.renderPipelineFactory.createSpriteRenderPipeline(this.projectionViewBuffer);
6422
- this._renderPipeline.spriteTexture = this._texture;
6423
- this._renderPipeline.textureSampler = this._sampler;
6424
- this._drawingMesh = new SpriteBatchMesh(this._framework, this._maxBatchSize);
8091
+ this._material = this._framework.materialFactory.createSpriteMaterial({
8092
+ projectionViewBuffer: this.projectionViewBuffer,
8093
+ texture: this._texture,
8094
+ sampler: this._sampler
8095
+ });
8096
+ this._drawingMesh = new SpriteBatchMesh(this._framework, this._material, this._maxBatchSize);
6425
8097
  this._drawingMesh.initialize();
6426
8098
  }
6427
8099
  /**
@@ -6459,15 +8131,10 @@ class SpriteBatchDrawable {
6459
8131
  if (this._toInstance > this._maxBatchSize) {
6460
8132
  this._toInstance = this._maxBatchSize;
6461
8133
  }
6462
- const indicesOffset = this._fromInstance * 6;
6463
- const indicesCount = this._toInstance * 6 - indicesOffset;
8134
+ this._drawingMesh.indicesOffset = this._fromInstance * 6;
8135
+ this._drawingMesh.indicesCount = this._toInstance * 6 - this._drawingMesh.indicesOffset;
6464
8136
  this._drawingMesh.applyChanges();
6465
- this._renderPipeline.render(
6466
- this._drawingMesh.vertexBuffer,
6467
- this._drawingMesh.indexBuffer,
6468
- indicesCount,
6469
- indicesOffset
6470
- );
8137
+ this._drawingMesh.draw();
6471
8138
  this._fromInstance = this._toInstance;
6472
8139
  }
6473
8140
  /**
@@ -6482,7 +8149,7 @@ class SpriteBatchDrawable {
6482
8149
  }
6483
8150
  /** @inheritDoc */
6484
8151
  dispose() {
6485
- this._renderPipeline.dispose();
8152
+ this._material.dispose();
6486
8153
  this._drawingMesh.dispose();
6487
8154
  }
6488
8155
  }
@@ -6827,6 +8494,35 @@ class VertexBufferLayout {
6827
8494
  ]
6828
8495
  );
6829
8496
  }
8497
+ static createFloat3Float4Float2Float3Layout() {
8498
+ const floatSize = 4;
8499
+ return new VertexBufferLayout(
8500
+ (3 + 4 + 2 + 3) * floatSize,
8501
+ VertexStepMode.VERTEX,
8502
+ [
8503
+ {
8504
+ format: VertexFormat.FLOAT_32X3,
8505
+ offset: 0,
8506
+ shaderLocation: 0
8507
+ },
8508
+ {
8509
+ format: VertexFormat.FLOAT_32X4,
8510
+ offset: 3 * floatSize,
8511
+ shaderLocation: 1
8512
+ },
8513
+ {
8514
+ format: VertexFormat.FLOAT_32X2,
8515
+ offset: (3 + 4) * floatSize,
8516
+ shaderLocation: 2
8517
+ },
8518
+ {
8519
+ format: VertexFormat.FLOAT_32X3,
8520
+ offset: (3 + 4 + 2) * floatSize,
8521
+ shaderLocation: 3
8522
+ }
8523
+ ]
8524
+ );
8525
+ }
6830
8526
  }
6831
8527
  class AWebGlRenderPipeline {
6832
8528
  /** The framework instance. */
@@ -6920,6 +8616,8 @@ class WebGlMainRenderTargetRenderPipeline extends AWebGlRenderPipeline {
6920
8616
  super(framework);
6921
8617
  this._mainRenderTarget = mainRenderTarget;
6922
8618
  }
8619
+ /** @inheritDoc */
8620
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2;
6923
8621
  /** @inheritdoc */
6924
8622
  _provideBuffers() {
6925
8623
  return [this._vertexBuffer.buffer];
@@ -6995,6 +8693,8 @@ class WebGlSpriteRenderPipeline extends AWebGlRenderPipeline {
6995
8693
  super(framework);
6996
8694
  this._projectionViewBuffer = asWebGLUniformBuffer(projectionViewBuffer);
6997
8695
  }
8696
+ /** @inheritDoc */
8697
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2;
6998
8698
  /** @inheritdoc */
6999
8699
  get projectionViewBuffer() {
7000
8700
  return this._projectionViewBuffer;
@@ -7088,11 +8788,16 @@ class WebGlInspectTextureMipsRenderPipeline extends AWebGlRenderPipeline {
7088
8788
  * @param textureConstantsBuffer The texture constants buffer.
7089
8789
  */
7090
8790
  constructor(framework, projectionViewBuffer, modelBuffer, textureConstantsBuffer, primitiveState) {
8791
+ if (!modelBuffer) {
8792
+ throw new Error("WebGlInspectTextureMipsRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
8793
+ }
7091
8794
  super(framework, primitiveState);
7092
8795
  this._projectionViewBuffer = projectionViewBuffer;
7093
8796
  this._modelBuffer = modelBuffer;
7094
8797
  this._textureConstantsBuffer = textureConstantsBuffer;
7095
8798
  }
8799
+ /** @inheritDoc */
8800
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2;
7096
8801
  /** @inheritdoc */
7097
8802
  get projectionViewBuffer() {
7098
8803
  return this._projectionViewBuffer;
@@ -7107,6 +8812,9 @@ class WebGlInspectTextureMipsRenderPipeline extends AWebGlRenderPipeline {
7107
8812
  }
7108
8813
  /** @inheritdoc */
7109
8814
  set modelBuffer(value) {
8815
+ if (!value) {
8816
+ throw new Error("WebGlInspectTextureMipsRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
8817
+ }
7110
8818
  this._modelBuffer = asWebGLUniformBuffer(value);
7111
8819
  }
7112
8820
  /** @inheritdoc */
@@ -7223,11 +8931,16 @@ class WebGlUnlitRenderPipeline extends AWebGlRenderPipeline {
7223
8931
  * @param primitiveState The primitive state.
7224
8932
  */
7225
8933
  constructor(framework, projectionViewBuffer, modelBuffer, materialBuffer, primitiveState) {
8934
+ if (!modelBuffer) {
8935
+ throw new Error("WebGlUnlitRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
8936
+ }
7226
8937
  super(framework, primitiveState);
7227
8938
  this._projectionViewBuffer = projectionViewBuffer;
7228
8939
  this._modelBuffer = modelBuffer;
7229
8940
  this._materialBuffer = materialBuffer;
7230
8941
  }
8942
+ /** @inheritDoc */
8943
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2;
7231
8944
  /** @inheritdoc */
7232
8945
  get projectionViewBuffer() {
7233
8946
  return this._projectionViewBuffer;
@@ -7242,6 +8955,9 @@ class WebGlUnlitRenderPipeline extends AWebGlRenderPipeline {
7242
8955
  }
7243
8956
  /** @inheritdoc */
7244
8957
  set modelBuffer(value) {
8958
+ if (!value) {
8959
+ throw new Error("WebGlUnlitRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
8960
+ }
7245
8961
  this._modelBuffer = asWebGLUniformBuffer(value);
7246
8962
  }
7247
8963
  /** @inheritdoc */
@@ -7335,6 +9051,223 @@ class WebGlUnlitRenderPipeline extends AWebGlRenderPipeline {
7335
9051
  this._gl.drawElements(this.primitiveState.glPrimitiveType, toIndices, type, fromIndices);
7336
9052
  }
7337
9053
  }
9054
+ class WebGlForwardBlinnPhongRenderPipeline extends AWebGlRenderPipeline {
9055
+ static CAMERA_BINDING_POINT = 0;
9056
+ static MODEL_BINDING_POINT = 1;
9057
+ static MATERIAL_BINDING_POINT = 2;
9058
+ static LIGHTS_BINDING_POINT = 3;
9059
+ static CAMERA_POSITION_BINDING_POINT = 4;
9060
+ _texture = null;
9061
+ _sampler;
9062
+ _projectionViewBuffer;
9063
+ _modelBuffer;
9064
+ _materialBuffer;
9065
+ _lightsBuffer;
9066
+ _cameraBlockIndex = -1;
9067
+ _modelBlockIndex = -1;
9068
+ _materialBlockIndex = -1;
9069
+ _lightsBlockIndex = -1;
9070
+ _cameraPositionBuffer;
9071
+ _cameraPositionBlockIndex = -1;
9072
+ _maxDirectionalLights = DEFAULT_DIRECTIONAL_LIGHTS_COUNT;
9073
+ _maxPointLights = DEFAULT_POINT_LIGHTS_COUNT;
9074
+ _initialized = false;
9075
+ _buffersArray = [null];
9076
+ _lastVertexBuffer = null;
9077
+ /**
9078
+ * The constructor.
9079
+ * @param framework The framework.
9080
+ * @param projectionViewBuffer The projection view buffer.
9081
+ * @param modelBuffer The model buffer.
9082
+ * @param materialBuffer The material buffer.
9083
+ * @param lightsBuffer The lights buffer (`LightsBuffer.buffer`). May be set later.
9084
+ * @param cameraPositionBuffer The camera position buffer (world-space eye).
9085
+ * @param primitiveState The primitive state.
9086
+ */
9087
+ constructor(framework, projectionViewBuffer, modelBuffer, materialBuffer, lightsBuffer, cameraPositionBuffer, primitiveState) {
9088
+ if (!modelBuffer) {
9089
+ throw new Error("WebGlForwardBlinnPhongRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
9090
+ }
9091
+ super(framework, primitiveState);
9092
+ this._projectionViewBuffer = projectionViewBuffer;
9093
+ this._modelBuffer = modelBuffer;
9094
+ this._materialBuffer = materialBuffer;
9095
+ this._lightsBuffer = lightsBuffer;
9096
+ this._cameraPositionBuffer = cameraPositionBuffer;
9097
+ }
9098
+ /** @inheritDoc */
9099
+ geometryFormat = GeometryFormat.POS3_COLOR4_TEXTURECOORDS2_NORMAL3;
9100
+ /** @inheritdoc */
9101
+ get projectionViewBuffer() {
9102
+ return this._projectionViewBuffer;
9103
+ }
9104
+ /** @inheritdoc */
9105
+ set projectionViewBuffer(value) {
9106
+ this._projectionViewBuffer = asWebGLUniformBuffer(value);
9107
+ }
9108
+ /** @inheritdoc */
9109
+ get modelBuffer() {
9110
+ return this._modelBuffer;
9111
+ }
9112
+ /** @inheritdoc */
9113
+ set modelBuffer(value) {
9114
+ if (!value) {
9115
+ throw new Error("WebGlForwardBlinnPhongRenderPipeline: modelBuffer is required (the mesh's model buffer, std140 mat4).");
9116
+ }
9117
+ this._modelBuffer = asWebGLUniformBuffer(value);
9118
+ }
9119
+ /** @inheritdoc */
9120
+ get materialBuffer() {
9121
+ return this._materialBuffer;
9122
+ }
9123
+ /** @inheritdoc */
9124
+ set materialBuffer(value) {
9125
+ this._materialBuffer = asWebGLUniformBuffer(value);
9126
+ }
9127
+ /** @inheritdoc */
9128
+ get lightsBuffer() {
9129
+ return this._lightsBuffer;
9130
+ }
9131
+ /** @inheritdoc */
9132
+ set lightsBuffer(value) {
9133
+ this._lightsBuffer = asWebGLUniformBuffer(value);
9134
+ }
9135
+ /** @inheritdoc */
9136
+ get maxDirectionalLights() {
9137
+ return this._maxDirectionalLights;
9138
+ }
9139
+ /** @inheritdoc */
9140
+ get maxPointLights() {
9141
+ return this._maxPointLights;
9142
+ }
9143
+ /** @inheritdoc */
9144
+ setLightCounts(maxDirectionalLights, maxPointLights) {
9145
+ if (maxDirectionalLights === this._maxDirectionalLights && maxPointLights === this._maxPointLights) {
9146
+ return;
9147
+ }
9148
+ this._maxDirectionalLights = maxDirectionalLights;
9149
+ this._maxPointLights = maxPointLights;
9150
+ if (this._initialized) {
9151
+ this._loadProgram();
9152
+ }
9153
+ }
9154
+ /** @inheritdoc */
9155
+ get cameraPositionBuffer() {
9156
+ return this._cameraPositionBuffer;
9157
+ }
9158
+ /** @inheritdoc */
9159
+ set cameraPositionBuffer(value) {
9160
+ this._cameraPositionBuffer = asWebGLUniformBuffer(value);
9161
+ }
9162
+ /** @inheritdoc */
9163
+ get diffuseTexture() {
9164
+ return this._texture;
9165
+ }
9166
+ /** @inheritdoc */
9167
+ set diffuseTexture(value) {
9168
+ this._texture = value;
9169
+ }
9170
+ /** @inheritdoc */
9171
+ get diffuseTextureSampler() {
9172
+ return this._sampler;
9173
+ }
9174
+ set diffuseTextureSampler(value) {
9175
+ this._sampler = value;
9176
+ }
9177
+ /** @inheritdoc */
9178
+ initialize() {
9179
+ this._loadProgram();
9180
+ this.vertexBufferLayouts = [VertexBufferLayout.createFloat3Float4Float2Float3Layout()];
9181
+ super.initialize();
9182
+ this._initialized = true;
9183
+ }
9184
+ /** Loads the shader variant for the current light counts (cached per counts by the shader loader). */
9185
+ _loadProgram() {
9186
+ const module = this._framework.content.loadShaderModule("forward_blinn_phong_material", {
9187
+ "DIRECTIONAL_LIGHTS_COUNT": this._maxDirectionalLights,
9188
+ "POINT_LIGHTS_COUNT": this._maxPointLights
9189
+ });
9190
+ this._program = module.program;
9191
+ this._createResources();
9192
+ }
9193
+ _createResources() {
9194
+ {
9195
+ this._texture = WebGlTexture2D.getOrCreateDefault(this._framework);
9196
+ this._cameraBlockIndex = this._gl.getUniformBlockIndex(this._program, "CameraBuffer");
9197
+ this._gl.uniformBlockBinding(this._program, this._cameraBlockIndex, WebGlForwardBlinnPhongRenderPipeline.CAMERA_BINDING_POINT);
9198
+ this._modelBlockIndex = this._gl.getUniformBlockIndex(this._program, "ModelBuffer");
9199
+ this._gl.uniformBlockBinding(this._program, this._modelBlockIndex, WebGlForwardBlinnPhongRenderPipeline.MODEL_BINDING_POINT);
9200
+ this._materialBlockIndex = this._gl.getUniformBlockIndex(this._program, "MaterialBuffer");
9201
+ this._gl.uniformBlockBinding(this._program, this._materialBlockIndex, WebGlForwardBlinnPhongRenderPipeline.MATERIAL_BINDING_POINT);
9202
+ this._lightsBlockIndex = this._gl.getUniformBlockIndex(this._program, "LightsBuffer");
9203
+ this._gl.uniformBlockBinding(this._program, this._lightsBlockIndex, WebGlForwardBlinnPhongRenderPipeline.LIGHTS_BINDING_POINT);
9204
+ this._cameraPositionBlockIndex = this._gl.getUniformBlockIndex(this._program, "CameraPositionBuffer");
9205
+ this._gl.uniformBlockBinding(this._program, this._cameraPositionBlockIndex, WebGlForwardBlinnPhongRenderPipeline.CAMERA_POSITION_BINDING_POINT);
9206
+ }
9207
+ }
9208
+ /** @inheritdoc */
9209
+ _provideBuffers() {
9210
+ if (this._lastVertexBuffer === null) {
9211
+ return null;
9212
+ }
9213
+ this._buffersArray[0] = this._lastVertexBuffer.buffer;
9214
+ return this._buffersArray;
9215
+ }
9216
+ /** @inheritdoc */
9217
+ render(vertexBuffer, indexBuffer, indicesCount = -1, indicesOffset = 0) {
9218
+ if (indicesCount == 0) {
9219
+ return;
9220
+ }
9221
+ const webGlVertexBuffer = vertexBuffer;
9222
+ const webGlIndexBuffer = indexBuffer;
9223
+ const webGlSampler = this._sampler ?? this._defaultTextureSampler;
9224
+ this.primitiveState.apply(this._gl);
9225
+ this._blendState.apply(this._gl);
9226
+ if (this._lastVertexBuffer != webGlVertexBuffer) {
9227
+ this._lastVertexBuffer = webGlVertexBuffer;
9228
+ this._createVertexArrayObject();
9229
+ }
9230
+ this._gl.useProgram(this._program);
9231
+ this._gl.bindVertexArray(this._vertexArrayObject);
9232
+ this._gl.bindBuffer(this._gl.ELEMENT_ARRAY_BUFFER, webGlIndexBuffer.buffer);
9233
+ this._gl.bindBufferBase(
9234
+ this._gl.UNIFORM_BUFFER,
9235
+ WebGlForwardBlinnPhongRenderPipeline.CAMERA_BINDING_POINT,
9236
+ this._projectionViewBuffer.glBuffer
9237
+ );
9238
+ this._gl.bindBufferBase(
9239
+ this._gl.UNIFORM_BUFFER,
9240
+ WebGlForwardBlinnPhongRenderPipeline.MODEL_BINDING_POINT,
9241
+ this._modelBuffer.glBuffer
9242
+ );
9243
+ if (this.materialBuffer) {
9244
+ this._gl.bindBufferBase(
9245
+ this._gl.UNIFORM_BUFFER,
9246
+ WebGlForwardBlinnPhongRenderPipeline.MATERIAL_BINDING_POINT,
9247
+ this._materialBuffer.glBuffer
9248
+ );
9249
+ }
9250
+ if (this._lightsBuffer) {
9251
+ this._gl.bindBufferBase(
9252
+ this._gl.UNIFORM_BUFFER,
9253
+ WebGlForwardBlinnPhongRenderPipeline.LIGHTS_BINDING_POINT,
9254
+ this._lightsBuffer.glBuffer
9255
+ );
9256
+ }
9257
+ this._gl.bindBufferBase(
9258
+ this._gl.UNIFORM_BUFFER,
9259
+ WebGlForwardBlinnPhongRenderPipeline.CAMERA_POSITION_BINDING_POINT,
9260
+ this._cameraPositionBuffer.glBuffer
9261
+ );
9262
+ this._gl.activeTexture(this._gl.TEXTURE0);
9263
+ this._gl.bindTexture(this._gl.TEXTURE_2D, this._texture.glTexture);
9264
+ this._gl.bindSampler(0, webGlSampler.glSampler);
9265
+ const type = indexBuffer.type == IndexBufferType.UINT_16 ? this._gl.UNSIGNED_SHORT : this._gl.UNSIGNED_INT;
9266
+ const toIndices = indicesCount > 0 ? indicesCount : indexBuffer.indicesCount;
9267
+ const fromIndices = indicesOffset * indexBuffer.elementByteSize;
9268
+ this._gl.drawElements(this.primitiveState.glPrimitiveType, toIndices, type, fromIndices);
9269
+ }
9270
+ }
7338
9271
  class WebGlRenderPipelineFactory {
7339
9272
  /**
7340
9273
  * The constructor for the WebGLRenderPipelineFactory class.
@@ -7357,6 +9290,26 @@ class WebGlRenderPipelineFactory {
7357
9290
  return pipeline;
7358
9291
  }
7359
9292
  /** @inheritDoc */
9293
+ createForwardBlinnPhongRenderPipeline(descriptor) {
9294
+ const pipeline = new WebGlForwardBlinnPhongRenderPipeline(
9295
+ this._framework,
9296
+ descriptor.projectionViewBuffer,
9297
+ descriptor.modelBuffer,
9298
+ descriptor.materialBuffer,
9299
+ descriptor.lightsBuffer,
9300
+ descriptor.cameraPositionBuffer,
9301
+ descriptor.primitiveState
9302
+ );
9303
+ if (descriptor.maxDirectionalLights !== void 0 || descriptor.maxPointLights !== void 0) {
9304
+ pipeline.setLightCounts(
9305
+ descriptor.maxDirectionalLights ?? pipeline.maxDirectionalLights,
9306
+ descriptor.maxPointLights ?? pipeline.maxPointLights
9307
+ );
9308
+ }
9309
+ pipeline.initialize();
9310
+ return pipeline;
9311
+ }
9312
+ /** @inheritDoc */
7360
9313
  createMainFrameBufferPipeline(mainFrameBuffer) {
7361
9314
  const renderPipeline = new WebGlMainRenderTargetRenderPipeline(this._framework, mainFrameBuffer);
7362
9315
  renderPipeline.initialize();
@@ -7452,6 +9405,8 @@ class InputManager {
7452
9405
  _touchCapabilities;
7453
9406
  _contacts;
7454
9407
  _touchListening = false;
9408
+ /** Aborting it removes every canvas listener added in initialize(). */
9409
+ _listeners = new AbortController();
7455
9410
  /** Set in update, consumed in afterUpdate. Ageing before a publish would hide the press. */
7456
9411
  _touchPublished = false;
7457
9412
  static _PHASE_BEGIN = 0;
@@ -7478,6 +9433,12 @@ class InputManager {
7478
9433
  getTouchCapabilities() {
7479
9434
  return this._touchCapabilities;
7480
9435
  }
9436
+ /** Removes the canvas listeners. Called from `Framework.dispose()`. */
9437
+ dispose() {
9438
+ this._listeners.abort();
9439
+ this._listeners = new AbortController();
9440
+ this._touchListening = false;
9441
+ }
7481
9442
  /** @inheritDoc */
7482
9443
  initialize() {
7483
9444
  const canvas = this._framework.windowManager.canvas;
@@ -7488,18 +9449,18 @@ class InputManager {
7488
9449
  canvas.addEventListener("mousemove", (e4) => {
7489
9450
  this._mousePosition[0] = e4.clientX;
7490
9451
  this._mousePosition[1] = e4.clientY;
7491
- });
9452
+ }, { signal: this._listeners.signal });
7492
9453
  canvas.addEventListener("mousedown", (e4) => {
7493
9454
  const idx = e4.button;
7494
9455
  const btn = this._mouseButtonMap[idx] ?? MouseButton.NONE;
7495
9456
  this._mouseButtonDown[btn] = true;
7496
- });
9457
+ }, { signal: this._listeners.signal });
7497
9458
  canvas.addEventListener("mouseup", (e4) => {
7498
9459
  const idx = e4.button;
7499
9460
  const btn = this._mouseButtonMap[idx] ?? MouseButton.NONE;
7500
9461
  this._mouseButtonDown[btn] = false;
7501
9462
  this._mouseButtonReleased[btn] = true;
7502
- });
9463
+ }, { signal: this._listeners.signal });
7503
9464
  canvas.addEventListener("wheel", (e4) => {
7504
9465
  let x2 = 0;
7505
9466
  let y2 = 0;
@@ -7515,7 +9476,7 @@ class InputManager {
7515
9476
  }
7516
9477
  vec2.set(this._scrollWheelPosition, x2, y2);
7517
9478
  e4.preventDefault();
7518
- });
9479
+ }, { signal: this._listeners.signal });
7519
9480
  }
7520
9481
  _handleTouchEvents(canvas) {
7521
9482
  if (this._touchListening) {
@@ -7523,7 +9484,7 @@ class InputManager {
7523
9484
  }
7524
9485
  this._touchListening = true;
7525
9486
  canvas.style.touchAction = "none";
7526
- const options = InputManager._touchListenerOptions;
9487
+ const options = { ...InputManager._touchListenerOptions, signal: this._listeners.signal };
7527
9488
  canvas.addEventListener("touchstart", this._onTouchStart, options);
7528
9489
  canvas.addEventListener("touchmove", this._onTouchMove, options);
7529
9490
  canvas.addEventListener("touchend", this._onTouchEnd, options);
@@ -7808,12 +9769,12 @@ class Framework {
7808
9769
  _onInitializeListeners = [];
7809
9770
  _onUpdateListeners = [];
7810
9771
  _onRenderListeners = [];
7811
- _container;
7812
9772
  _textureFactory;
7813
9773
  _buffersFactory;
7814
9774
  _contentManager;
7815
9775
  _geometryBuilder;
7816
9776
  _meshFactory;
9777
+ _disposed = false;
7817
9778
  /**
7818
9779
  * The constructor for the Framework class.
7819
9780
  * @param options The optional framework configuration. When omitted, a default `FrameworkConfiguration` is used.
@@ -7824,7 +9785,6 @@ class Framework {
7824
9785
  console.warn("Canvas not provided. Creating canvas and appending it to document.body.");
7825
9786
  }
7826
9787
  options.backBufferSize = options.backBufferSize ?? vec2.fromValues(800, 600);
7827
- this._container = container.createChildContainer();
7828
9788
  this.windowManager = new WindowManager(options.canvas ?? null);
7829
9789
  const rendererConfig = {
7830
9790
  backBufferSize: options.backBufferSize,
@@ -7832,7 +9792,6 @@ class Framework {
7832
9792
  alpha: options.alpha ?? false,
7833
9793
  powerPreference: options.powerPreference ?? PowerPreferenceType.DEFAULT
7834
9794
  };
7835
- this._container.registerInstance(RenderConfigurationSymbol, rendererConfig);
7836
9795
  this.renderer = new WebGlRenderer(this, rendererConfig);
7837
9796
  this.renderingBackend = RenderingBackend.WEB_GL;
7838
9797
  this._textureFactory = new WebGlTextureFactory(this);
@@ -7841,6 +9800,7 @@ class Framework {
7841
9800
  this._contentManager = new ContentManager(this, new WebGlShaderModuleLoader(this));
7842
9801
  this._meshFactory = new MeshFactory(this);
7843
9802
  this.materialFactory = new MaterialFactory(this);
9803
+ this.lightsFactory = new WebGlAmbientLightFactory(this);
7844
9804
  this._buffersFactory = new WebGlBuffersFactory(this);
7845
9805
  this.spriteBatch = new SpriteBatch(this);
7846
9806
  this.cameraFactory = new CameraFactory(this);
@@ -7938,6 +9898,8 @@ class Framework {
7938
9898
  /** @inheritDoc */
7939
9899
  materialFactory;
7940
9900
  /** @inheritDoc */
9901
+ lightsFactory;
9902
+ /** @inheritDoc */
7941
9903
  timeManager;
7942
9904
  /** @inheritDoc */
7943
9905
  input;
@@ -7950,10 +9912,10 @@ class Framework {
7950
9912
  this.spriteBatch.initialize();
7951
9913
  this.input.initialize();
7952
9914
  this.timeManager.prepareStart();
7953
- for (const listener of this._onLoadContentListeners) {
9915
+ for (const listener of this._onInitializeListeners) {
7954
9916
  listener();
7955
9917
  }
7956
- for (const listener of this._onInitializeListeners) {
9918
+ for (const listener of this._onLoadContentListeners) {
7957
9919
  listener();
7958
9920
  }
7959
9921
  this.renderer.afterInitialize();
@@ -7975,12 +9937,31 @@ class Framework {
7975
9937
  });
7976
9938
  this.windowManager.runEventLoop();
7977
9939
  }
7978
- /** @inheritDoc */
9940
+ /**
9941
+ * Stops the event loop and disposes the framework's services and GL resources,
9942
+ * including per-framework caches such as the default texture and the shader modules.
9943
+ * A new `Framework` afterwards creates fresh resources on its own context. Calling it twice does nothing.
9944
+ */
7979
9945
  dispose() {
9946
+ if (this._disposed) {
9947
+ return;
9948
+ }
9949
+ this._disposed = true;
9950
+ this.windowManager.dispose();
9951
+ this.input.dispose?.();
9952
+ this._onLoadContentListeners.length = 0;
9953
+ this._onInitializeListeners.length = 0;
9954
+ this._onUpdateListeners.length = 0;
9955
+ this._onRenderListeners.length = 0;
9956
+ WebGlTexture2D.disposeDefault(this);
9957
+ this._contentManager.dispose?.();
9958
+ this.renderer.graphicsDevice?.dispose();
7980
9959
  }
7981
9960
  }
7982
9961
  export {
7983
9962
  AlignUtilities,
9963
+ AmbientLight,
9964
+ BaseGeometry,
7984
9965
  BufferUsage,
7985
9966
  Button,
7986
9967
  CameraFactory,
@@ -7988,6 +9969,11 @@ export {
7988
9969
  ContentConfig,
7989
9970
  CpuImageProcessor,
7990
9971
  CullMode,
9972
+ DEFAULT_DIRECTIONAL_LIGHTS_COUNT,
9973
+ DEFAULT_POINT_LIGHTS_COUNT,
9974
+ DirectionalLight,
9975
+ ForwardBlinnPhongMaterial,
9976
+ ForwardBlinnPhongMaterialDescriptor,
7991
9977
  Framework,
7992
9978
  FrameworkConfiguration,
7993
9979
  FrontFace,
@@ -8012,9 +9998,12 @@ export {
8012
9998
  MipMapSamplerFilter,
8013
9999
  MouseButton,
8014
10000
  MouseState,
10001
+ ObjLoader,
10002
+ ObjMaterialDefinition,
8015
10003
  OrbitCamera,
8016
10004
  OrthographicCamera,
8017
10005
  PerspectiveCamera,
10006
+ PointLight,
8018
10007
  PowerPreferenceType,
8019
10008
  PrimitiveStateDescriptor,
8020
10009
  PrimitiveTopology,
@@ -8028,9 +10017,12 @@ export {
8028
10017
  SamplerAddressMode,
8029
10018
  SamplerDescriptor$1 as SamplerDescriptor,
8030
10019
  SamplerFilter,
10020
+ Scene,
8031
10021
  ShaderStage,
10022
+ SpriteMaterial,
8032
10023
  State,
8033
10024
  StoreAction,
10025
+ SubMesh,
8034
10026
  SurfacePresentMode,
8035
10027
  SwapChainDescriptor,
8036
10028
  TextureDescriptor,