@bloomengine/engine 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (562) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +20 -0
  5. package/native/android/src/lib.rs +1747 -0
  6. package/native/ios/Cargo.lock +1688 -0
  7. package/native/ios/Cargo.toml +19 -0
  8. package/native/ios/src/lib.rs +2258 -0
  9. package/native/linux/Cargo.lock +1719 -0
  10. package/native/linux/Cargo.toml +22 -0
  11. package/native/linux/src/lib.rs +2236 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +29 -0
  14. package/native/macos/src/lib.rs +3444 -0
  15. package/native/shared/Cargo.lock +1898 -0
  16. package/native/shared/Cargo.toml +42 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +77 -0
  19. package/native/shared/shaders/common/fog.wgsl +16 -0
  20. package/native/shared/shaders/common/imposter.wgsl +112 -0
  21. package/native/shared/shaders/common/pbr.wgsl +186 -0
  22. package/native/shared/shaders/common/shadows.wgsl +77 -0
  23. package/native/shared/shaders/common/sky.wgsl +8 -0
  24. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  25. package/native/shared/shaders/impulse_field.wgsl +53 -0
  26. package/native/shared/shaders/material_abi.wgsl +360 -0
  27. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  28. package/native/shared/src/audio.rs +363 -0
  29. package/native/shared/src/custom_shaders.rs +104 -0
  30. package/native/shared/src/drs.rs +211 -0
  31. package/native/shared/src/engine.rs +186 -0
  32. package/native/shared/src/frame_callbacks.rs +88 -0
  33. package/native/shared/src/geometry.rs +236 -0
  34. package/native/shared/src/handles.rs +76 -0
  35. package/native/shared/src/input.rs +273 -0
  36. package/native/shared/src/jolt_sys.rs +822 -0
  37. package/native/shared/src/lib.rs +43 -0
  38. package/native/shared/src/models.rs +1941 -0
  39. package/native/shared/src/physics_jolt.rs +1528 -0
  40. package/native/shared/src/picking.rs +298 -0
  41. package/native/shared/src/postfx.rs +339 -0
  42. package/native/shared/src/profiler.rs +416 -0
  43. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  44. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  45. package/native/shared/src/renderer/formats.rs +778 -0
  46. package/native/shared/src/renderer/graph.rs +465 -0
  47. package/native/shared/src/renderer/hot_reload.rs +390 -0
  48. package/native/shared/src/renderer/impulse_field.rs +455 -0
  49. package/native/shared/src/renderer/material_pipeline.rs +604 -0
  50. package/native/shared/src/renderer/material_system.rs +2106 -0
  51. package/native/shared/src/renderer/mod.rs +13923 -0
  52. package/native/shared/src/renderer/planar_reflection.rs +458 -0
  53. package/native/shared/src/renderer/post_pass.rs +249 -0
  54. package/native/shared/src/renderer/shader_include.rs +205 -0
  55. package/native/shared/src/renderer/shader_library.rs +134 -0
  56. package/native/shared/src/renderer/shaders.rs +5855 -0
  57. package/native/shared/src/renderer/transient.rs +576 -0
  58. package/native/shared/src/renderer/types.rs +259 -0
  59. package/native/shared/src/renderer/util.rs +151 -0
  60. package/native/shared/src/scene.rs +1066 -0
  61. package/native/shared/src/sdf_cache.rs +274 -0
  62. package/native/shared/src/shadows.rs +551 -0
  63. package/native/shared/src/staging.rs +90 -0
  64. package/native/shared/src/string_header.rs +35 -0
  65. package/native/shared/src/text_renderer.rs +456 -0
  66. package/native/shared/src/textures.rs +154 -0
  67. package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.cpp +242 -0
  68. package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.h +121 -0
  69. package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeToBuffer.h +296 -0
  70. package/native/third_party/JoltPhysics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h +323 -0
  71. package/native/third_party/JoltPhysics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h +555 -0
  72. package/native/third_party/JoltPhysics/Jolt/ConfigurationString.h +112 -0
  73. package/native/third_party/JoltPhysics/Jolt/Core/ARMNeon.h +94 -0
  74. package/native/third_party/JoltPhysics/Jolt/Core/Array.h +713 -0
  75. package/native/third_party/JoltPhysics/Jolt/Core/Atomics.h +44 -0
  76. package/native/third_party/JoltPhysics/Jolt/Core/BinaryHeap.h +96 -0
  77. package/native/third_party/JoltPhysics/Jolt/Core/ByteBuffer.h +74 -0
  78. package/native/third_party/JoltPhysics/Jolt/Core/Color.cpp +38 -0
  79. package/native/third_party/JoltPhysics/Jolt/Core/Color.h +98 -0
  80. package/native/third_party/JoltPhysics/Jolt/Core/Core.h +652 -0
  81. package/native/third_party/JoltPhysics/Jolt/Core/FPControlWord.h +143 -0
  82. package/native/third_party/JoltPhysics/Jolt/Core/FPException.h +96 -0
  83. package/native/third_party/JoltPhysics/Jolt/Core/FPFlushDenormals.h +43 -0
  84. package/native/third_party/JoltPhysics/Jolt/Core/Factory.cpp +92 -0
  85. package/native/third_party/JoltPhysics/Jolt/Core/Factory.h +54 -0
  86. package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.h +122 -0
  87. package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.inl +215 -0
  88. package/native/third_party/JoltPhysics/Jolt/Core/HashCombine.h +234 -0
  89. package/native/third_party/JoltPhysics/Jolt/Core/HashTable.h +876 -0
  90. package/native/third_party/JoltPhysics/Jolt/Core/InsertionSort.h +58 -0
  91. package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.cpp +27 -0
  92. package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.h +38 -0
  93. package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.h +311 -0
  94. package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.inl +56 -0
  95. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.cpp +65 -0
  96. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.h +62 -0
  97. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.cpp +364 -0
  98. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.h +101 -0
  99. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.cpp +230 -0
  100. package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.h +85 -0
  101. package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.cpp +51 -0
  102. package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.h +67 -0
  103. package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.h +182 -0
  104. package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.inl +351 -0
  105. package/native/third_party/JoltPhysics/Jolt/Core/Memory.cpp +85 -0
  106. package/native/third_party/JoltPhysics/Jolt/Core/Memory.h +85 -0
  107. package/native/third_party/JoltPhysics/Jolt/Core/Mutex.h +223 -0
  108. package/native/third_party/JoltPhysics/Jolt/Core/MutexArray.h +98 -0
  109. package/native/third_party/JoltPhysics/Jolt/Core/NonCopyable.h +18 -0
  110. package/native/third_party/JoltPhysics/Jolt/Core/Profiler.cpp +677 -0
  111. package/native/third_party/JoltPhysics/Jolt/Core/Profiler.h +301 -0
  112. package/native/third_party/JoltPhysics/Jolt/Core/Profiler.inl +90 -0
  113. package/native/third_party/JoltPhysics/Jolt/Core/QuickSort.h +137 -0
  114. package/native/third_party/JoltPhysics/Jolt/Core/RTTI.cpp +149 -0
  115. package/native/third_party/JoltPhysics/Jolt/Core/RTTI.h +436 -0
  116. package/native/third_party/JoltPhysics/Jolt/Core/Reference.h +244 -0
  117. package/native/third_party/JoltPhysics/Jolt/Core/Result.h +174 -0
  118. package/native/third_party/JoltPhysics/Jolt/Core/STLAlignedAllocator.h +72 -0
  119. package/native/third_party/JoltPhysics/Jolt/Core/STLAllocator.h +127 -0
  120. package/native/third_party/JoltPhysics/Jolt/Core/STLLocalAllocator.h +170 -0
  121. package/native/third_party/JoltPhysics/Jolt/Core/STLTempAllocator.h +80 -0
  122. package/native/third_party/JoltPhysics/Jolt/Core/ScopeExit.h +49 -0
  123. package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.cpp +135 -0
  124. package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.h +68 -0
  125. package/native/third_party/JoltPhysics/Jolt/Core/StaticArray.h +329 -0
  126. package/native/third_party/JoltPhysics/Jolt/Core/StreamIn.h +120 -0
  127. package/native/third_party/JoltPhysics/Jolt/Core/StreamOut.h +97 -0
  128. package/native/third_party/JoltPhysics/Jolt/Core/StreamUtils.h +168 -0
  129. package/native/third_party/JoltPhysics/Jolt/Core/StreamWrapper.h +53 -0
  130. package/native/third_party/JoltPhysics/Jolt/Core/StridedPtr.h +63 -0
  131. package/native/third_party/JoltPhysics/Jolt/Core/StringTools.cpp +101 -0
  132. package/native/third_party/JoltPhysics/Jolt/Core/StringTools.h +38 -0
  133. package/native/third_party/JoltPhysics/Jolt/Core/TempAllocator.h +209 -0
  134. package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.cpp +37 -0
  135. package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.h +58 -0
  136. package/native/third_party/JoltPhysics/Jolt/Core/UnorderedMap.h +80 -0
  137. package/native/third_party/JoltPhysics/Jolt/Core/UnorderedSet.h +32 -0
  138. package/native/third_party/JoltPhysics/Jolt/Geometry/AABox.h +313 -0
  139. package/native/third_party/JoltPhysics/Jolt/Geometry/AABox4.h +224 -0
  140. package/native/third_party/JoltPhysics/Jolt/Geometry/ClipPoly.h +200 -0
  141. package/native/third_party/JoltPhysics/Jolt/Geometry/ClosestPoint.h +498 -0
  142. package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.cpp +1467 -0
  143. package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.h +276 -0
  144. package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.cpp +335 -0
  145. package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.h +105 -0
  146. package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexSupport.h +188 -0
  147. package/native/third_party/JoltPhysics/Jolt/Geometry/EPAConvexHullBuilder.h +845 -0
  148. package/native/third_party/JoltPhysics/Jolt/Geometry/EPAPenetrationDepth.h +557 -0
  149. package/native/third_party/JoltPhysics/Jolt/Geometry/Ellipse.h +77 -0
  150. package/native/third_party/JoltPhysics/Jolt/Geometry/GJKClosestPoint.h +945 -0
  151. package/native/third_party/JoltPhysics/Jolt/Geometry/IndexedTriangle.h +130 -0
  152. package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.cpp +222 -0
  153. package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.h +19 -0
  154. package/native/third_party/JoltPhysics/Jolt/Geometry/MortonCode.h +40 -0
  155. package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.cpp +178 -0
  156. package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.h +39 -0
  157. package/native/third_party/JoltPhysics/Jolt/Geometry/Plane.h +104 -0
  158. package/native/third_party/JoltPhysics/Jolt/Geometry/RayAABox.h +241 -0
  159. package/native/third_party/JoltPhysics/Jolt/Geometry/RayCapsule.h +37 -0
  160. package/native/third_party/JoltPhysics/Jolt/Geometry/RayCylinder.h +101 -0
  161. package/native/third_party/JoltPhysics/Jolt/Geometry/RaySphere.h +96 -0
  162. package/native/third_party/JoltPhysics/Jolt/Geometry/RayTriangle.h +158 -0
  163. package/native/third_party/JoltPhysics/Jolt/Geometry/Sphere.h +72 -0
  164. package/native/third_party/JoltPhysics/Jolt/Geometry/Triangle.h +34 -0
  165. package/native/third_party/JoltPhysics/Jolt/Jolt.cmake +703 -0
  166. package/native/third_party/JoltPhysics/Jolt/Jolt.h +16 -0
  167. package/native/third_party/JoltPhysics/Jolt/Jolt.natvis +116 -0
  168. package/native/third_party/JoltPhysics/Jolt/Math/BVec16.h +99 -0
  169. package/native/third_party/JoltPhysics/Jolt/Math/BVec16.inl +177 -0
  170. package/native/third_party/JoltPhysics/Jolt/Math/DMat44.h +158 -0
  171. package/native/third_party/JoltPhysics/Jolt/Math/DMat44.inl +310 -0
  172. package/native/third_party/JoltPhysics/Jolt/Math/DVec3.h +291 -0
  173. package/native/third_party/JoltPhysics/Jolt/Math/DVec3.inl +941 -0
  174. package/native/third_party/JoltPhysics/Jolt/Math/Double3.h +48 -0
  175. package/native/third_party/JoltPhysics/Jolt/Math/DynMatrix.h +31 -0
  176. package/native/third_party/JoltPhysics/Jolt/Math/EigenValueSymmetric.h +177 -0
  177. package/native/third_party/JoltPhysics/Jolt/Math/FindRoot.h +42 -0
  178. package/native/third_party/JoltPhysics/Jolt/Math/Float2.h +36 -0
  179. package/native/third_party/JoltPhysics/Jolt/Math/Float3.h +50 -0
  180. package/native/third_party/JoltPhysics/Jolt/Math/Float4.h +44 -0
  181. package/native/third_party/JoltPhysics/Jolt/Math/GaussianElimination.h +102 -0
  182. package/native/third_party/JoltPhysics/Jolt/Math/HalfFloat.h +208 -0
  183. package/native/third_party/JoltPhysics/Jolt/Math/Mat44.h +243 -0
  184. package/native/third_party/JoltPhysics/Jolt/Math/Mat44.inl +952 -0
  185. package/native/third_party/JoltPhysics/Jolt/Math/Math.h +208 -0
  186. package/native/third_party/JoltPhysics/Jolt/Math/MathTypes.h +32 -0
  187. package/native/third_party/JoltPhysics/Jolt/Math/Matrix.h +259 -0
  188. package/native/third_party/JoltPhysics/Jolt/Math/Quat.h +268 -0
  189. package/native/third_party/JoltPhysics/Jolt/Math/Quat.inl +406 -0
  190. package/native/third_party/JoltPhysics/Jolt/Math/Real.h +44 -0
  191. package/native/third_party/JoltPhysics/Jolt/Math/Swizzle.h +19 -0
  192. package/native/third_party/JoltPhysics/Jolt/Math/Trigonometry.h +79 -0
  193. package/native/third_party/JoltPhysics/Jolt/Math/UVec4.h +232 -0
  194. package/native/third_party/JoltPhysics/Jolt/Math/UVec4.inl +636 -0
  195. package/native/third_party/JoltPhysics/Jolt/Math/Vec3.cpp +71 -0
  196. package/native/third_party/JoltPhysics/Jolt/Math/Vec3.h +308 -0
  197. package/native/third_party/JoltPhysics/Jolt/Math/Vec3.inl +942 -0
  198. package/native/third_party/JoltPhysics/Jolt/Math/Vec4.h +320 -0
  199. package/native/third_party/JoltPhysics/Jolt/Math/Vec4.inl +1152 -0
  200. package/native/third_party/JoltPhysics/Jolt/Math/Vector.h +211 -0
  201. package/native/third_party/JoltPhysics/Jolt/ObjectStream/GetPrimitiveTypeOfType.h +54 -0
  202. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.cpp +38 -0
  203. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.h +337 -0
  204. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.cpp +252 -0
  205. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.h +57 -0
  206. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.cpp +165 -0
  207. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.h +57 -0
  208. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.cpp +635 -0
  209. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.h +148 -0
  210. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.cpp +166 -0
  211. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.h +101 -0
  212. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.cpp +418 -0
  213. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.h +55 -0
  214. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.cpp +255 -0
  215. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.h +62 -0
  216. package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTypes.h +26 -0
  217. package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttribute.h +111 -0
  218. package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeEnum.h +67 -0
  219. package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeTyped.h +60 -0
  220. package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.cpp +15 -0
  221. package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.h +170 -0
  222. package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.cpp +70 -0
  223. package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.h +45 -0
  224. package/native/third_party/JoltPhysics/Jolt/Physics/Body/AllowedDOFs.h +68 -0
  225. package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.cpp +426 -0
  226. package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.h +452 -0
  227. package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.inl +197 -0
  228. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyAccess.h +68 -0
  229. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyActivationListener.h +28 -0
  230. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.cpp +234 -0
  231. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.h +124 -0
  232. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyFilter.h +130 -0
  233. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyID.h +101 -0
  234. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.cpp +1099 -0
  235. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.h +324 -0
  236. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLock.h +111 -0
  237. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockInterface.h +134 -0
  238. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockMulti.h +120 -0
  239. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.cpp +1220 -0
  240. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.h +403 -0
  241. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyPair.h +36 -0
  242. package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyType.h +19 -0
  243. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.cpp +185 -0
  244. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.h +58 -0
  245. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.cpp +92 -0
  246. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.h +308 -0
  247. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.inl +178 -0
  248. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionQuality.h +31 -0
  249. package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionType.h +17 -0
  250. package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.cpp +354 -0
  251. package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.h +159 -0
  252. package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.cpp +59 -0
  253. package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.h +157 -0
  254. package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterID.h +98 -0
  255. package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.cpp +1933 -0
  256. package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.h +752 -0
  257. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/AABoxCast.h +20 -0
  258. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdgeMode.h +17 -0
  259. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdges.h +114 -0
  260. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BackFaceMode.h +16 -0
  261. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.cpp +16 -0
  262. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h +109 -0
  263. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp +313 -0
  264. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h +38 -0
  265. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h +148 -0
  266. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h +92 -0
  267. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h +64 -0
  268. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp +629 -0
  269. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h +108 -0
  270. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuery.h +56 -0
  271. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h +35 -0
  272. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h +66 -0
  273. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp +1768 -0
  274. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.h +389 -0
  275. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.cpp +107 -0
  276. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.h +46 -0
  277. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastResult.h +37 -0
  278. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp +223 -0
  279. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.h +49 -0
  280. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollectFacesMode.h +16 -0
  281. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp +155 -0
  282. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.h +56 -0
  283. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollidePointResult.h +25 -0
  284. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShape.h +106 -0
  285. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h +94 -0
  286. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h +110 -0
  287. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h +102 -0
  288. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp +121 -0
  289. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.h +50 -0
  290. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollector.h +109 -0
  291. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollectorImpl.h +219 -0
  292. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.cpp +107 -0
  293. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.h +97 -0
  294. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.cpp +35 -0
  295. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.h +97 -0
  296. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ContactListener.h +143 -0
  297. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.cpp +213 -0
  298. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.h +48 -0
  299. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.cpp +32 -0
  300. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.h +46 -0
  301. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.cpp +38 -0
  302. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.h +130 -0
  303. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h +279 -0
  304. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp +271 -0
  305. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h +44 -0
  306. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.cpp +448 -0
  307. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.h +77 -0
  308. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.cpp +62 -0
  309. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.h +110 -0
  310. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayer.h +111 -0
  311. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h +52 -0
  312. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h +78 -0
  313. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.cpp +35 -0
  314. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.h +57 -0
  315. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.cpp +38 -0
  316. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.h +37 -0
  317. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/RayCast.h +87 -0
  318. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.cpp +318 -0
  319. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.h +115 -0
  320. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.cpp +438 -0
  321. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.h +129 -0
  322. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.cpp +433 -0
  323. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.h +354 -0
  324. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h +461 -0
  325. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp +1311 -0
  326. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.h +202 -0
  327. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.cpp +566 -0
  328. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.h +150 -0
  329. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.cpp +418 -0
  330. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.h +126 -0
  331. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.cpp +87 -0
  332. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.h +80 -0
  333. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.cpp +64 -0
  334. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.h +75 -0
  335. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/GetTrianglesContext.h +248 -0
  336. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp +2754 -0
  337. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.h +380 -0
  338. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.cpp +1305 -0
  339. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.h +228 -0
  340. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp +596 -0
  341. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.h +176 -0
  342. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp +217 -0
  343. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h +140 -0
  344. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.cpp +541 -0
  345. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.h +147 -0
  346. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h +319 -0
  347. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp +333 -0
  348. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h +161 -0
  349. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaleHelpers.h +83 -0
  350. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.cpp +238 -0
  351. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.h +145 -0
  352. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.cpp +325 -0
  353. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.h +466 -0
  354. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.cpp +347 -0
  355. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.h +125 -0
  356. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.cpp +674 -0
  357. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.h +139 -0
  358. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeID.h +138 -0
  359. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h +65 -0
  360. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp +453 -0
  361. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.gliffy +1 -0
  362. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h +135 -0
  363. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp +691 -0
  364. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.h +132 -0
  365. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.cpp +430 -0
  366. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.h +143 -0
  367. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeCast.h +173 -0
  368. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeFilter.h +73 -0
  369. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilter.h +40 -0
  370. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilterWrapper.h +58 -0
  371. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SortReverseAndStore.h +48 -0
  372. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.cpp +180 -0
  373. package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.h +194 -0
  374. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/CalculateSolverSteps.h +70 -0
  375. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.cpp +246 -0
  376. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.h +133 -0
  377. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.cpp +73 -0
  378. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.h +243 -0
  379. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.cpp +289 -0
  380. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.h +100 -0
  381. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h +257 -0
  382. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h +682 -0
  383. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h +276 -0
  384. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h +195 -0
  385. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h +222 -0
  386. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/IndependentAxisConstraintPart.h +246 -0
  387. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/PointConstraintPart.h +239 -0
  388. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h +196 -0
  389. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h +283 -0
  390. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h +246 -0
  391. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h +169 -0
  392. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h +597 -0
  393. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.cpp +1804 -0
  394. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.h +524 -0
  395. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.cpp +266 -0
  396. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.h +120 -0
  397. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.cpp +215 -0
  398. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.h +96 -0
  399. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.cpp +188 -0
  400. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.h +116 -0
  401. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.cpp +443 -0
  402. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.h +205 -0
  403. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.cpp +43 -0
  404. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.h +66 -0
  405. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.cpp +458 -0
  406. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.h +191 -0
  407. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.cpp +85 -0
  408. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.h +76 -0
  409. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp +308 -0
  410. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.h +54 -0
  411. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.cpp +157 -0
  412. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.h +94 -0
  413. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.cpp +253 -0
  414. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.h +137 -0
  415. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp +189 -0
  416. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.h +118 -0
  417. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.cpp +900 -0
  418. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.h +289 -0
  419. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.cpp +501 -0
  420. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.h +198 -0
  421. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.cpp +35 -0
  422. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.h +70 -0
  423. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.cpp +524 -0
  424. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.h +197 -0
  425. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.cpp +56 -0
  426. package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.h +65 -0
  427. package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.cpp +17 -0
  428. package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.h +159 -0
  429. package/native/third_party/JoltPhysics/Jolt/Physics/EActivation.h +16 -0
  430. package/native/third_party/JoltPhysics/Jolt/Physics/EPhysicsUpdateError.h +37 -0
  431. package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.cpp +492 -0
  432. package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.h +144 -0
  433. package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.cpp +582 -0
  434. package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.h +187 -0
  435. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsLock.h +169 -0
  436. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.cpp +261 -0
  437. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.h +104 -0
  438. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSettings.h +125 -0
  439. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsStepListener.h +37 -0
  440. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.cpp +2915 -0
  441. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.h +391 -0
  442. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.cpp +25 -0
  443. package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.h +176 -0
  444. package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.cpp +744 -0
  445. package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.h +245 -0
  446. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyContactListener.h +55 -0
  447. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.cpp +128 -0
  448. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h +75 -0
  449. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyManifold.h +74 -0
  450. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp +1501 -0
  451. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h +333 -0
  452. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.cpp +354 -0
  453. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.h +73 -0
  454. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp +1487 -0
  455. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h +390 -0
  456. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h +63 -0
  457. package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyVertex.h +36 -0
  458. package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorder.h +136 -0
  459. package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.cpp +90 -0
  460. package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.h +50 -0
  461. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.cpp +306 -0
  462. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.h +119 -0
  463. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.cpp +547 -0
  464. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.h +169 -0
  465. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.cpp +33 -0
  466. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.h +33 -0
  467. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp +376 -0
  468. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.h +146 -0
  469. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.cpp +703 -0
  470. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.h +252 -0
  471. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.cpp +17 -0
  472. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.h +87 -0
  473. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.cpp +81 -0
  474. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.h +39 -0
  475. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.cpp +122 -0
  476. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.h +93 -0
  477. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.cpp +52 -0
  478. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.h +56 -0
  479. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.cpp +159 -0
  480. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.h +87 -0
  481. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.cpp +93 -0
  482. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.h +148 -0
  483. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.cpp +866 -0
  484. package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.h +205 -0
  485. package/native/third_party/JoltPhysics/Jolt/RegisterTypes.cpp +204 -0
  486. package/native/third_party/JoltPhysics/Jolt/RegisterTypes.h +29 -0
  487. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.cpp +1107 -0
  488. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.h +383 -0
  489. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.cpp +168 -0
  490. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.h +48 -0
  491. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.cpp +158 -0
  492. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.h +130 -0
  493. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.cpp +80 -0
  494. package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.h +88 -0
  495. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.cpp +165 -0
  496. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.h +91 -0
  497. package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.cpp +82 -0
  498. package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.h +72 -0
  499. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.cpp +237 -0
  500. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.h +145 -0
  501. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.cpp +87 -0
  502. package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.h +82 -0
  503. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.cpp +73 -0
  504. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.h +84 -0
  505. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.cpp +139 -0
  506. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.h +52 -0
  507. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.cpp +43 -0
  508. package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.h +28 -0
  509. package/native/third_party/JoltPhysics/LICENSE +7 -0
  510. package/native/third_party/JoltPhysics/README.md +173 -0
  511. package/native/third_party/bloom_jolt/CMakeLists.txt +78 -0
  512. package/native/third_party/bloom_jolt/include/bloom_jolt.h +519 -0
  513. package/native/third_party/bloom_jolt/src/bloom_jolt.cpp +1780 -0
  514. package/native/tvos/Cargo.lock +1692 -0
  515. package/native/tvos/Cargo.toml +22 -0
  516. package/native/tvos/src/lib.rs +3179 -0
  517. package/native/watchos/Cargo.lock +16 -0
  518. package/native/watchos/Cargo.toml +19 -0
  519. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  520. package/native/watchos/src/BloomWatchApp.swift +1236 -0
  521. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  522. package/native/watchos/src/audio.rs +55 -0
  523. package/native/watchos/src/draw_list.rs +223 -0
  524. package/native/watchos/src/ffi_stubs.rs +454 -0
  525. package/native/watchos/src/lib.rs +1013 -0
  526. package/native/watchos/src/models.rs +746 -0
  527. package/native/watchos/src/postfx.rs +91 -0
  528. package/native/watchos/src/scene.rs +534 -0
  529. package/native/watchos/src/textures.rs +184 -0
  530. package/native/web/Cargo.lock +1656 -0
  531. package/native/web/Cargo.toml +38 -0
  532. package/native/web/bloom_glue.js +218 -0
  533. package/native/web/build.sh +101 -0
  534. package/native/web/index.html +390 -0
  535. package/native/web/jolt_bridge.js +1311 -0
  536. package/native/web/src/lib.rs +2739 -0
  537. package/native/windows/Cargo.lock +1813 -0
  538. package/native/windows/Cargo.toml +31 -0
  539. package/native/windows/src/lib.rs +1933 -0
  540. package/package.json +558 -0
  541. package/src/audio/index.ts +151 -0
  542. package/src/core/colors.ts +56 -0
  543. package/src/core/index.ts +903 -0
  544. package/src/core/keys.ts +63 -0
  545. package/src/core/types.ts +102 -0
  546. package/src/index.ts +158 -0
  547. package/src/math/index.ts +502 -0
  548. package/src/mobile/index.ts +294 -0
  549. package/src/models/index.ts +859 -0
  550. package/src/physics/index.ts +1072 -0
  551. package/src/scene/index.ts +570 -0
  552. package/src/shapes/index.ts +120 -0
  553. package/src/text/index.ts +48 -0
  554. package/src/textures/index.ts +173 -0
  555. package/src/world/index.ts +22 -0
  556. package/src/world/loader.ts +385 -0
  557. package/src/world/prefab.ts +205 -0
  558. package/src/world/saver.ts +61 -0
  559. package/src/world/terrain.ts +254 -0
  560. package/src/world/types.ts +136 -0
  561. package/src/world/validate.ts +202 -0
  562. package/src/world/version.ts +47 -0
@@ -0,0 +1,1941 @@
1
+ use crate::handles::HandleRegistry;
2
+ use crate::renderer::Vertex3D;
3
+
4
+ pub struct MeshData {
5
+ pub vertices: Vec<Vertex3D>,
6
+ pub indices: Vec<u32>,
7
+ pub texture_idx: Option<u32>,
8
+ pub normal_texture_idx: Option<u32>,
9
+ pub metallic_roughness_texture_idx: Option<u32>,
10
+ pub emissive_texture_idx: Option<u32>,
11
+ pub occlusion_texture_idx: Option<u32>,
12
+ pub metallic_factor: f32,
13
+ pub roughness_factor: f32,
14
+ pub emissive_factor: [f32; 3],
15
+ /// glTF alpha cutoff for MASK mode — fragments with base-colour
16
+ /// alpha below this are discarded. 0.0 means OPAQUE mode (no
17
+ /// discard); glTF spec default for MASK is 0.5. BLEND mode is
18
+ /// currently treated as MASK @ 0.5 since we don't have a sorted
19
+ /// transparent pipeline yet.
20
+ pub alpha_cutoff: f32,
21
+ }
22
+
23
+ pub struct ModelData {
24
+ pub meshes: Vec<MeshData>,
25
+ pub bbox_min: [f32; 3],
26
+ pub bbox_max: [f32; 3],
27
+ }
28
+
29
+ pub struct JointData {
30
+ pub inverse_bind: [[f32; 4]; 4],
31
+ pub children: Vec<usize>,
32
+ pub name: String,
33
+ pub rest_translation: [f32; 3],
34
+ pub rest_rotation: [f32; 4],
35
+ pub rest_scale: [f32; 3],
36
+ }
37
+
38
+ pub struct AnimationChannel {
39
+ pub joint_index: usize,
40
+ pub timestamps: Vec<f32>,
41
+ pub translations: Vec<[f32; 3]>,
42
+ pub rotation_timestamps: Vec<f32>,
43
+ pub rotations: Vec<[f32; 4]>,
44
+ pub scale_timestamps: Vec<f32>,
45
+ pub scales: Vec<[f32; 3]>,
46
+ }
47
+
48
+ pub struct AnimationData {
49
+ pub channels: Vec<AnimationChannel>,
50
+ pub duration: f32,
51
+ pub name: String,
52
+ }
53
+
54
+ pub struct SkeletonData {
55
+ pub joints: Vec<JointData>,
56
+ pub root_joints: Vec<usize>,
57
+ }
58
+
59
+ pub struct ModelAnimation {
60
+ pub skeleton: Option<SkeletonData>,
61
+ pub animations: Vec<AnimationData>,
62
+ pub joint_matrices: Vec<[[f32; 4]; 4]>,
63
+ /// Reference rest-pose rotations (from first animation, sampled at t=0).
64
+ /// Used for retargeting when multiple armatures have different rest orientations.
65
+ pub ref_rest_rotations: Option<Vec<[f32; 4]>>,
66
+ }
67
+
68
+ pub struct ModelManager {
69
+ pub models: HandleRegistry<ModelData>,
70
+ pub animations: HandleRegistry<ModelAnimation>,
71
+ }
72
+
73
+ impl ModelManager {
74
+ pub fn new() -> Self {
75
+ Self {
76
+ models: HandleRegistry::new(),
77
+ animations: HandleRegistry::new(),
78
+ }
79
+ }
80
+
81
+ pub fn load_model(&mut self, file_data: &[u8]) -> f64 {
82
+ match load_gltf(file_data) {
83
+ Some(model) => self.models.alloc(model),
84
+ None => 0.0,
85
+ }
86
+ }
87
+
88
+ pub fn load_model_with_textures(&mut self, file_data: &[u8], renderer: &mut crate::renderer::Renderer) -> f64 {
89
+ match load_gltf_with_textures(file_data, renderer, None) {
90
+ Some(model) => self.models.alloc(model),
91
+ None => 0.0,
92
+ }
93
+ }
94
+
95
+ /// Like `load_model_with_textures` but also resolves external `.bin`
96
+ /// and image URIs relative to `base_dir` — required for loose glTF
97
+ /// files (as opposed to single-file .glb). Intel Sponza etc.
98
+ pub fn load_model_with_textures_from_path(
99
+ &mut self,
100
+ file_data: &[u8],
101
+ base_dir: &std::path::Path,
102
+ renderer: &mut crate::renderer::Renderer,
103
+ ) -> f64 {
104
+ match load_gltf_with_textures(file_data, renderer, Some(base_dir)) {
105
+ Some(model) => self.models.alloc(model),
106
+ None => 0.0,
107
+ }
108
+ }
109
+
110
+ pub fn get(&self, handle: f64) -> Option<&ModelData> {
111
+ self.models.get(handle)
112
+ }
113
+
114
+ /// Return the axis-aligned bounding box of a loaded model as
115
+ /// `(min_xyz, max_xyz)`. Used by editors to size move/rotate gizmos,
116
+ /// auto-frame the camera on selection, and snap placed entities onto
117
+ /// terrain. Returns the origin for unknown handles so callers can read
118
+ /// without checking for None.
119
+ pub fn get_bounds(&self, handle: f64) -> ([f32; 3], [f32; 3]) {
120
+ match self.models.get(handle) {
121
+ Some(model) => (model.bbox_min, model.bbox_max),
122
+ None => ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
123
+ }
124
+ }
125
+
126
+ pub fn get_animation(&self, handle: f64) -> Option<&ModelAnimation> {
127
+ self.animations.get(handle)
128
+ }
129
+
130
+ pub fn unload_model(&mut self, handle: f64) {
131
+ self.models.free(handle);
132
+ }
133
+
134
+ pub fn gen_mesh_cube(&mut self, w: f32, h: f32, d: f32) -> f64 {
135
+ let hw = w * 0.5;
136
+ let hh = h * 0.5;
137
+ let hd = d * 0.5;
138
+ let white = [1.0, 1.0, 1.0, 1.0];
139
+
140
+ #[rustfmt::skip]
141
+ let faces: &[([f32; 3], [f32; 3], [f32; 2])] = &[
142
+ // Front face (+Z)
143
+ ([-hw, -hh, hd], [0.0, 0.0, 1.0], [0.0, 1.0]),
144
+ ([ hw, -hh, hd], [0.0, 0.0, 1.0], [1.0, 1.0]),
145
+ ([ hw, hh, hd], [0.0, 0.0, 1.0], [1.0, 0.0]),
146
+ ([-hw, hh, hd], [0.0, 0.0, 1.0], [0.0, 0.0]),
147
+ // Back face (-Z)
148
+ ([ hw, -hh, -hd], [0.0, 0.0, -1.0], [0.0, 1.0]),
149
+ ([-hw, -hh, -hd], [0.0, 0.0, -1.0], [1.0, 1.0]),
150
+ ([-hw, hh, -hd], [0.0, 0.0, -1.0], [1.0, 0.0]),
151
+ ([ hw, hh, -hd], [0.0, 0.0, -1.0], [0.0, 0.0]),
152
+ // Right face (+X)
153
+ ([ hw, -hh, hd], [1.0, 0.0, 0.0], [0.0, 1.0]),
154
+ ([ hw, -hh, -hd], [1.0, 0.0, 0.0], [1.0, 1.0]),
155
+ ([ hw, hh, -hd], [1.0, 0.0, 0.0], [1.0, 0.0]),
156
+ ([ hw, hh, hd], [1.0, 0.0, 0.0], [0.0, 0.0]),
157
+ // Left face (-X)
158
+ ([-hw, -hh, -hd], [-1.0, 0.0, 0.0], [0.0, 1.0]),
159
+ ([-hw, -hh, hd], [-1.0, 0.0, 0.0], [1.0, 1.0]),
160
+ ([-hw, hh, hd], [-1.0, 0.0, 0.0], [1.0, 0.0]),
161
+ ([-hw, hh, -hd], [-1.0, 0.0, 0.0], [0.0, 0.0]),
162
+ // Top face (+Y)
163
+ ([-hw, hh, hd], [0.0, 1.0, 0.0], [0.0, 1.0]),
164
+ ([ hw, hh, hd], [0.0, 1.0, 0.0], [1.0, 1.0]),
165
+ ([ hw, hh, -hd], [0.0, 1.0, 0.0], [1.0, 0.0]),
166
+ ([-hw, hh, -hd], [0.0, 1.0, 0.0], [0.0, 0.0]),
167
+ // Bottom face (-Y)
168
+ ([-hw, -hh, -hd], [0.0, -1.0, 0.0], [0.0, 1.0]),
169
+ ([ hw, -hh, -hd], [0.0, -1.0, 0.0], [1.0, 1.0]),
170
+ ([ hw, -hh, hd], [0.0, -1.0, 0.0], [1.0, 0.0]),
171
+ ([-hw, -hh, hd], [0.0, -1.0, 0.0], [0.0, 0.0]),
172
+ ];
173
+
174
+ let vertices: Vec<Vertex3D> = faces.iter().map(|(pos, norm, uv)| Vertex3D {
175
+ position: *pos,
176
+ normal: *norm,
177
+ color: white,
178
+ uv: *uv,
179
+ joints: [0.0; 4],
180
+ weights: [0.0; 4],
181
+ tangent: [0.0; 4],
182
+ }).collect();
183
+
184
+ let mut indices = Vec::with_capacity(36);
185
+ for face in 0..6u32 {
186
+ let base = face * 4;
187
+ indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
188
+ }
189
+
190
+ let model = ModelData {
191
+ meshes: vec![MeshData { vertices, indices, texture_idx: None, normal_texture_idx: None, metallic_roughness_texture_idx: None, emissive_texture_idx: None, occlusion_texture_idx: None, metallic_factor: 0.0, roughness_factor: 1.0, emissive_factor: [0.0; 3], alpha_cutoff: 0.0 }],
192
+ bbox_min: [-hw, -hh, -hd],
193
+ bbox_max: [hw, hh, hd],
194
+ };
195
+ self.models.alloc(model)
196
+ }
197
+
198
+ pub fn gen_mesh_heightmap(&mut self, image_data: &[u8], img_w: u32, img_h: u32, size_x: f32, size_y: f32, size_z: f32) -> f64 {
199
+ let cols = img_w as usize;
200
+ let rows = img_h as usize;
201
+ if cols < 2 || rows < 2 { return 0.0; }
202
+
203
+ let mut vertices = Vec::with_capacity(cols * rows);
204
+ let white = [1.0, 1.0, 1.0, 1.0];
205
+
206
+ for z in 0..rows {
207
+ for x in 0..cols {
208
+ let pixel_idx = (z * cols + x) * 4;
209
+ let luminance = if pixel_idx + 2 < image_data.len() {
210
+ (image_data[pixel_idx] as f32 * 0.299
211
+ + image_data[pixel_idx + 1] as f32 * 0.587
212
+ + image_data[pixel_idx + 2] as f32 * 0.114) / 255.0
213
+ } else {
214
+ 0.0
215
+ };
216
+
217
+ let px = (x as f32 / (cols - 1) as f32 - 0.5) * size_x;
218
+ let py = luminance * size_y;
219
+ let pz = (z as f32 / (rows - 1) as f32 - 0.5) * size_z;
220
+ let u = x as f32 / (cols - 1) as f32;
221
+ let v = z as f32 / (rows - 1) as f32;
222
+
223
+ vertices.push(Vertex3D {
224
+ position: [px, py, pz],
225
+ normal: [0.0, 1.0, 0.0],
226
+ color: white,
227
+ uv: [u, v],
228
+ joints: [0.0; 4],
229
+ weights: [0.0; 4],
230
+ tangent: [0.0; 4],
231
+ });
232
+ }
233
+ }
234
+
235
+ // Compute normals from neighboring heights
236
+ for z in 0..rows {
237
+ for x in 0..cols {
238
+ let idx = z * cols + x;
239
+ let left = if x > 0 { vertices[z * cols + x - 1].position[1] } else { vertices[idx].position[1] };
240
+ let right = if x < cols - 1 { vertices[z * cols + x + 1].position[1] } else { vertices[idx].position[1] };
241
+ let up = if z > 0 { vertices[(z - 1) * cols + x].position[1] } else { vertices[idx].position[1] };
242
+ let down = if z < rows - 1 { vertices[(z + 1) * cols + x].position[1] } else { vertices[idx].position[1] };
243
+ let sx = size_x / (cols - 1) as f32;
244
+ let sz = size_z / (rows - 1) as f32;
245
+ let nx = (left - right) / (2.0 * sx);
246
+ let nz = (up - down) / (2.0 * sz);
247
+ let len = (nx * nx + 1.0 + nz * nz).sqrt();
248
+ vertices[idx].normal = [nx / len, 1.0 / len, nz / len];
249
+ }
250
+ }
251
+
252
+ let mut indices = Vec::with_capacity((cols - 1) * (rows - 1) * 6);
253
+ for z in 0..rows - 1 {
254
+ for x in 0..cols - 1 {
255
+ let tl = (z * cols + x) as u32;
256
+ let tr = tl + 1;
257
+ let bl = ((z + 1) * cols + x) as u32;
258
+ let br = bl + 1;
259
+ indices.extend_from_slice(&[tl, bl, tr, tr, bl, br]);
260
+ }
261
+ }
262
+
263
+ let model = ModelData {
264
+ meshes: vec![MeshData { vertices, indices, texture_idx: None, normal_texture_idx: None, metallic_roughness_texture_idx: None, emissive_texture_idx: None, occlusion_texture_idx: None, metallic_factor: 0.0, roughness_factor: 1.0, emissive_factor: [0.0; 3], alpha_cutoff: 0.0 }],
265
+ bbox_min: [-size_x * 0.5, 0.0, -size_z * 0.5],
266
+ bbox_max: [size_x * 0.5, size_y, size_z * 0.5],
267
+ };
268
+ self.models.alloc(model)
269
+ }
270
+
271
+ /// Create a mesh from raw float data passed from TS.
272
+ /// vertex_data layout: [x,y,z, nx,ny,nz, r,g,b,a, u,v] per vertex (12 floats each)
273
+ pub fn create_mesh(&mut self, vertex_data: &[f32], index_data: &[u32]) -> f64 {
274
+ let floats_per_vert = 12;
275
+ let vert_count = vertex_data.len() / floats_per_vert;
276
+ if vert_count == 0 { return 0.0; }
277
+
278
+ let mut vertices = Vec::with_capacity(vert_count);
279
+ let mut bbox_min = [f32::MAX; 3];
280
+ let mut bbox_max = [f32::MIN; 3];
281
+
282
+ for i in 0..vert_count {
283
+ let o = i * floats_per_vert;
284
+ let pos = [vertex_data[o], vertex_data[o+1], vertex_data[o+2]];
285
+ for k in 0..3 {
286
+ if pos[k] < bbox_min[k] { bbox_min[k] = pos[k]; }
287
+ if pos[k] > bbox_max[k] { bbox_max[k] = pos[k]; }
288
+ }
289
+ vertices.push(Vertex3D {
290
+ position: pos,
291
+ normal: [vertex_data[o+3], vertex_data[o+4], vertex_data[o+5]],
292
+ color: [vertex_data[o+6], vertex_data[o+7], vertex_data[o+8], vertex_data[o+9]],
293
+ uv: [vertex_data[o+10], vertex_data[o+11]],
294
+ joints: [0.0; 4],
295
+ weights: [0.0; 4],
296
+ tangent: [0.0; 4],
297
+ });
298
+ }
299
+
300
+ let indices = index_data.to_vec();
301
+ let model = ModelData {
302
+ meshes: vec![MeshData { vertices, indices, texture_idx: None, normal_texture_idx: None, metallic_roughness_texture_idx: None, emissive_texture_idx: None, occlusion_texture_idx: None, metallic_factor: 0.0, roughness_factor: 1.0, emissive_factor: [0.0; 3], alpha_cutoff: 0.0 }],
303
+ bbox_min,
304
+ bbox_max,
305
+ };
306
+ self.models.alloc(model)
307
+ }
308
+
309
+ /// Q9: Generate a ribbon mesh along a Catmull-Rom spline. Used by the
310
+ /// editor's river tool. `points` is flat [x0,y0,z0, x1,y1,z1, ...],
311
+ /// `widths` has one width per control point.
312
+ pub fn gen_mesh_spline_ribbon(&mut self, points: &[f32], widths: &[f32]) -> f64 {
313
+ let n = points.len() / 3;
314
+ if n < 2 || widths.len() < n { return 0.0; }
315
+
316
+ // Evaluate Catmull-Rom at fine intervals.
317
+ let segments = (n - 1) * 8; // 8 subdivisions per segment.
318
+ let mut center_pts: Vec<[f32; 3]> = Vec::with_capacity(segments + 1);
319
+ let mut center_widths: Vec<f32> = Vec::with_capacity(segments + 1);
320
+
321
+ for i in 0..n - 1 {
322
+ for sub in 0..8 {
323
+ let t = sub as f32 / 8.0;
324
+ let p = catmull_rom_point(points, n, i, t);
325
+ let w = widths[i] * (1.0 - t) + widths[i + 1] * t;
326
+ center_pts.push(p);
327
+ center_widths.push(w);
328
+ }
329
+ }
330
+ // Add the last point.
331
+ let last = n - 1;
332
+ center_pts.push([points[last * 3], points[last * 3 + 1], points[last * 3 + 2]]);
333
+ center_widths.push(widths[last]);
334
+
335
+ // Build ribbon vertices (two per center point: left and right).
336
+ let ribbon_len = center_pts.len();
337
+ let mut vertices = Vec::with_capacity(ribbon_len * 2);
338
+ let mut bbox_min = [f32::MAX; 3];
339
+ let mut bbox_max = [f32::MIN; 3];
340
+ let white = [0.3, 0.5, 0.8, 0.7]; // Water-blue tint.
341
+
342
+ for i in 0..ribbon_len {
343
+ // Tangent direction.
344
+ let tangent = if i < ribbon_len - 1 {
345
+ let dx = center_pts[i + 1][0] - center_pts[i][0];
346
+ let dz = center_pts[i + 1][2] - center_pts[i][2];
347
+ let len = (dx * dx + dz * dz).sqrt().max(1e-6);
348
+ [dx / len, dz / len]
349
+ } else if i > 0 {
350
+ let dx = center_pts[i][0] - center_pts[i - 1][0];
351
+ let dz = center_pts[i][2] - center_pts[i - 1][2];
352
+ let len = (dx * dx + dz * dz).sqrt().max(1e-6);
353
+ [dx / len, dz / len]
354
+ } else {
355
+ [0.0, 1.0]
356
+ };
357
+
358
+ // Perpendicular in XZ plane (rotate tangent 90 degrees).
359
+ let perp = [-tangent[1], tangent[0]];
360
+ let hw = center_widths[i] * 0.5;
361
+ let cp = center_pts[i];
362
+ let u = i as f32 / (ribbon_len - 1).max(1) as f32;
363
+
364
+ // Left vertex.
365
+ let lx = cp[0] + perp[0] * hw;
366
+ let ly = cp[1];
367
+ let lz = cp[2] + perp[1] * hw;
368
+ update_bounds(&mut bbox_min, &mut bbox_max, lx, ly, lz);
369
+ vertices.push(Vertex3D {
370
+ position: [lx, ly, lz],
371
+ normal: [0.0, 1.0, 0.0],
372
+ color: white,
373
+ uv: [u, 0.0],
374
+ joints: [0.0; 4],
375
+ weights: [0.0; 4],
376
+ tangent: [0.0; 4],
377
+ });
378
+
379
+ // Right vertex.
380
+ let rx = cp[0] - perp[0] * hw;
381
+ let ry = cp[1];
382
+ let rz = cp[2] - perp[1] * hw;
383
+ update_bounds(&mut bbox_min, &mut bbox_max, rx, ry, rz);
384
+ vertices.push(Vertex3D {
385
+ position: [rx, ry, rz],
386
+ normal: [0.0, 1.0, 0.0],
387
+ color: white,
388
+ uv: [u, 1.0],
389
+ joints: [0.0; 4],
390
+ weights: [0.0; 4],
391
+ tangent: [0.0; 4],
392
+ });
393
+ }
394
+
395
+ // Triangle strip indices.
396
+ let mut indices = Vec::with_capacity((ribbon_len - 1) * 6);
397
+ for i in 0..(ribbon_len - 1) as u32 {
398
+ let bl = i * 2;
399
+ let br = bl + 1;
400
+ let tl = bl + 2;
401
+ let tr = bl + 3;
402
+ indices.extend_from_slice(&[bl, tl, br, br, tl, tr]);
403
+ }
404
+
405
+ if vertices.is_empty() {
406
+ bbox_min = [0.0; 3];
407
+ bbox_max = [0.0; 3];
408
+ }
409
+
410
+ let model = ModelData {
411
+ meshes: vec![MeshData { vertices, indices, texture_idx: None, normal_texture_idx: None, metallic_roughness_texture_idx: None, emissive_texture_idx: None, occlusion_texture_idx: None, metallic_factor: 0.0, roughness_factor: 1.0, emissive_factor: [0.0; 3], alpha_cutoff: 0.0 }],
412
+ bbox_min,
413
+ bbox_max,
414
+ };
415
+ self.models.alloc(model)
416
+ }
417
+
418
+ pub fn load_model_animation(&mut self, file_data: &[u8]) -> f64 {
419
+ match load_gltf_animation(file_data) {
420
+ Some(anim) => self.animations.alloc(anim),
421
+ None => 0.0,
422
+ }
423
+ }
424
+
425
+ pub fn update_model_animation(&mut self, handle: f64, anim_index: usize, time: f32) {
426
+ if let Some(model_anim) = self.animations.get_mut(handle) {
427
+ let skeleton = match &model_anim.skeleton {
428
+ Some(s) => s,
429
+ None => return,
430
+ };
431
+ if anim_index >= model_anim.animations.len() { return; }
432
+
433
+ let joint_count = skeleton.joints.len();
434
+ if model_anim.joint_matrices.len() != joint_count {
435
+ model_anim.joint_matrices = vec![mat4_identity(); joint_count];
436
+ }
437
+
438
+ // Initialize from rest-pose transforms (fallback for non-animated joints)
439
+ let mut local_translations: Vec<[f32; 3]> = skeleton.joints.iter()
440
+ .map(|j| j.rest_translation).collect();
441
+ let mut local_rotations: Vec<[f32; 4]> = skeleton.joints.iter()
442
+ .map(|j| j.rest_rotation).collect();
443
+ let mut local_scales: Vec<[f32; 3]> = skeleton.joints.iter()
444
+ .map(|j| j.rest_scale).collect();
445
+
446
+ let anim = &model_anim.animations[anim_index];
447
+ let t = if anim.duration > 0.0 { time % anim.duration } else { 0.0 };
448
+
449
+ #[cfg(debug_assertions)]
450
+ let mut channels_applied = 0usize;
451
+ for channel in &anim.channels {
452
+ let ji = channel.joint_index;
453
+ if ji >= joint_count { continue; }
454
+ #[cfg(debug_assertions)]
455
+ { channels_applied += 1; }
456
+
457
+ if !channel.translations.is_empty() && !channel.timestamps.is_empty() {
458
+ local_translations[ji] = sample_vec3(&channel.timestamps, &channel.translations, t);
459
+ }
460
+ if !channel.rotations.is_empty() {
461
+ let rot_ts = if !channel.rotation_timestamps.is_empty() { &channel.rotation_timestamps } else { &channel.timestamps };
462
+ if !rot_ts.is_empty() {
463
+ local_rotations[ji] = sample_quat(rot_ts, &channel.rotations, t);
464
+ }
465
+ }
466
+ if !channel.scales.is_empty() {
467
+ let scale_ts = if !channel.scale_timestamps.is_empty() { &channel.scale_timestamps } else { &channel.timestamps };
468
+ if !scale_ts.is_empty() {
469
+ local_scales[ji] = sample_vec3(scale_ts, &channel.scales, t);
470
+ }
471
+ }
472
+ }
473
+
474
+ // Lock root translation to rest pose (strip all root motion)
475
+ local_translations[0] = skeleton.joints[0].rest_translation;
476
+
477
+ // Build world transforms by walking the hierarchy from roots
478
+ let mut world_transforms = vec![mat4_identity(); joint_count];
479
+
480
+ let root_joints = skeleton.root_joints.clone();
481
+ for &root in &root_joints {
482
+ compute_joint_transforms(
483
+ skeleton, root, &mat4_identity(),
484
+ &local_translations, &local_rotations, &local_scales,
485
+ &mut world_transforms,
486
+ );
487
+ }
488
+
489
+ // Multiply by inverse bind matrices to get final joint matrices
490
+ for i in 0..joint_count {
491
+ model_anim.joint_matrices[i] = mat4_mul(&world_transforms[i], &skeleton.joints[i].inverse_bind);
492
+ }
493
+
494
+ #[cfg(debug_assertions)]
495
+ {
496
+ static mut DEBUG_PRINTED: bool = false;
497
+ unsafe {
498
+ if !DEBUG_PRINTED {
499
+ DEBUG_PRINTED = true;
500
+ eprintln!("[anim] channels_applied={}, t={:.3}, anim_index={}", channels_applied, t, anim_index);
501
+ eprintln!("[anim] Joint0 local: t=[{:.2},{:.2},{:.2}] r=[{:.4},{:.4},{:.4},{:.4}]",
502
+ local_translations[0][0], local_translations[0][1], local_translations[0][2],
503
+ local_rotations[0][0], local_rotations[0][1], local_rotations[0][2], local_rotations[0][3]);
504
+ let m = &model_anim.joint_matrices[0];
505
+ eprintln!("[anim] Joint0 final diag=[{:.4},{:.4},{:.4}] trans=[{:.4},{:.4},{:.4}]",
506
+ m[0][0], m[1][1], m[2][2], m[3][0], m[3][1], m[3][2]);
507
+ }
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ // ============================================================
515
+ // Matrix / quaternion helpers for skeletal animation
516
+ // ============================================================
517
+
518
+ fn mat4_identity() -> [[f32; 4]; 4] {
519
+ [
520
+ [1.0, 0.0, 0.0, 0.0],
521
+ [0.0, 1.0, 0.0, 0.0],
522
+ [0.0, 0.0, 1.0, 0.0],
523
+ [0.0, 0.0, 0.0, 1.0],
524
+ ]
525
+ }
526
+
527
+ fn mat4_mul(a: &[[f32; 4]; 4], b: &[[f32; 4]; 4]) -> [[f32; 4]; 4] {
528
+ let mut out = [[0.0f32; 4]; 4];
529
+ for col in 0..4 {
530
+ for row in 0..4 {
531
+ out[col][row] = a[0][row]*b[col][0] + a[1][row]*b[col][1] + a[2][row]*b[col][2] + a[3][row]*b[col][3];
532
+ }
533
+ }
534
+ out
535
+ }
536
+
537
+ /// Walk the scene graph and collect EVERY world-space transform that
538
+ /// references each mesh. Unlike `walk_scene_for_mesh_transforms` which
539
+ /// records only the first occurrence, this version captures every
540
+ /// instance — so glTF scenes with heavy mesh reuse (Bistro: 5910 nodes
541
+ /// referencing 551 unique meshes) render every chair / bollard / chain
542
+ /// / bush instead of collapsing to a single copy each.
543
+ fn walk_scene_collect_instances(
544
+ node: &gltf::Node,
545
+ parent: &[[f32; 4]; 4],
546
+ out: &mut [Vec<[[f32; 4]; 4]>],
547
+ ) {
548
+ let local = node.transform().matrix();
549
+ let world = mat4_mul(parent, &local);
550
+ if let Some(mesh) = node.mesh() {
551
+ let idx = mesh.index();
552
+ if idx < out.len() {
553
+ out[idx].push(world);
554
+ }
555
+ }
556
+ for child in node.children() {
557
+ walk_scene_collect_instances(&child, &world, out);
558
+ }
559
+ }
560
+
561
+ /// Transform a 3D point by a 4x4 matrix (column-major). Treats the
562
+ /// point as having w=1 and drops w from the result.
563
+ fn mat4_transform_point(m: &[[f32; 4]; 4], p: &[f32; 3]) -> [f32; 3] {
564
+ [
565
+ m[0][0]*p[0] + m[1][0]*p[1] + m[2][0]*p[2] + m[3][0],
566
+ m[0][1]*p[0] + m[1][1]*p[1] + m[2][1]*p[2] + m[3][1],
567
+ m[0][2]*p[0] + m[1][2]*p[1] + m[2][2]*p[2] + m[3][2],
568
+ ]
569
+ }
570
+
571
+ /// Transform a direction vector by a 3x3 matrix (extracted from a 4x4
572
+ /// column-major stored as the top-left 3x3). Used for normals under
573
+ /// the inverse-transpose matrix.
574
+ fn mat3_transform_vec(m: &[[f32; 3]; 3], v: &[f32; 3]) -> [f32; 3] {
575
+ [
576
+ m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2],
577
+ m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2],
578
+ m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2],
579
+ ]
580
+ }
581
+
582
+ /// Inverse-transpose of the 3x3 rotation+scale part of a 4x4 matrix.
583
+ /// Correct way to transform normals when the matrix has non-uniform
584
+ /// scale; falls back to identity if the 3x3 block isn't invertible.
585
+ fn mat4_inverse_transpose_3x3(m: &[[f32; 4]; 4]) -> [[f32; 3]; 3] {
586
+ let a = m[0][0]; let b = m[1][0]; let c = m[2][0];
587
+ let d = m[0][1]; let e = m[1][1]; let f = m[2][1];
588
+ let g = m[0][2]; let h = m[1][2]; let i = m[2][2];
589
+
590
+ let inv00 = e*i - f*h;
591
+ let inv01 = f*g - d*i;
592
+ let inv02 = d*h - e*g;
593
+ let inv10 = c*h - b*i;
594
+ let inv11 = a*i - c*g;
595
+ let inv12 = b*g - a*h;
596
+ let inv20 = b*f - c*e;
597
+ let inv21 = c*d - a*f;
598
+ let inv22 = a*e - b*d;
599
+
600
+ let det = a*inv00 + b*inv01 + c*inv02;
601
+ if det.abs() < 1e-10 {
602
+ return [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
603
+ }
604
+ let inv_det = 1.0 / det;
605
+ // Store in column-major like the rest of the file (columns first).
606
+ // The result is the inverse-transpose, so rows/cols are swapped
607
+ // from the plain inverse.
608
+ [
609
+ [inv00 * inv_det, inv01 * inv_det, inv02 * inv_det],
610
+ [inv10 * inv_det, inv11 * inv_det, inv12 * inv_det],
611
+ [inv20 * inv_det, inv21 * inv_det, inv22 * inv_det],
612
+ ]
613
+ }
614
+
615
+ fn mat4_from_trs(t: &[f32; 3], r: &[f32; 4], s: &[f32; 3]) -> [[f32; 4]; 4] {
616
+ let (x, y, z, w) = (r[0], r[1], r[2], r[3]);
617
+ let x2 = x + x; let y2 = y + y; let z2 = z + z;
618
+ let xx = x * x2; let xy = x * y2; let xz = x * z2;
619
+ let yy = y * y2; let yz = y * z2; let zz = z * z2;
620
+ let wx = w * x2; let wy = w * y2; let wz = w * z2;
621
+
622
+ // Column-major: m[col][row]
623
+ [
624
+ [(1.0 - (yy + zz)) * s[0], (xy + wz) * s[0], (xz - wy) * s[0], 0.0], // column 0
625
+ [(xy - wz) * s[1], (1.0 - (xx + zz)) * s[1], (yz + wx) * s[1], 0.0], // column 1
626
+ [(xz + wy) * s[2], (yz - wx) * s[2], (1.0 - (xx + yy)) * s[2], 0.0], // column 2
627
+ [t[0], t[1], t[2], 1.0], // column 3 (translation)
628
+ ]
629
+ }
630
+
631
+ fn quat_slerp(a: &[f32; 4], b: &[f32; 4], t: f32) -> [f32; 4] {
632
+ let mut dot = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3];
633
+ let mut b2 = *b;
634
+ if dot < 0.0 {
635
+ dot = -dot;
636
+ b2 = [-b[0], -b[1], -b[2], -b[3]];
637
+ }
638
+ if dot > 0.9995 {
639
+ let mut out = [
640
+ a[0] + t * (b2[0] - a[0]),
641
+ a[1] + t * (b2[1] - a[1]),
642
+ a[2] + t * (b2[2] - a[2]),
643
+ a[3] + t * (b2[3] - a[3]),
644
+ ];
645
+ let len = (out[0]*out[0] + out[1]*out[1] + out[2]*out[2] + out[3]*out[3]).sqrt();
646
+ if len > 0.0 { for v in &mut out { *v /= len; } }
647
+ return out;
648
+ }
649
+ let theta = dot.acos();
650
+ let sin_theta = theta.sin();
651
+ let wa = ((1.0 - t) * theta).sin() / sin_theta;
652
+ let wb = (t * theta).sin() / sin_theta;
653
+ [
654
+ wa * a[0] + wb * b2[0],
655
+ wa * a[1] + wb * b2[1],
656
+ wa * a[2] + wb * b2[2],
657
+ wa * a[3] + wb * b2[3],
658
+ ]
659
+ }
660
+
661
+ fn lerp_vec3(a: &[f32; 3], b: &[f32; 3], t: f32) -> [f32; 3] {
662
+ [
663
+ a[0] + t * (b[0] - a[0]),
664
+ a[1] + t * (b[1] - a[1]),
665
+ a[2] + t * (b[2] - a[2]),
666
+ ]
667
+ }
668
+
669
+ fn find_keyframe_pair(timestamps: &[f32], time: f32) -> (usize, usize, f32) {
670
+ if timestamps.len() <= 1 {
671
+ return (0, 0, 0.0);
672
+ }
673
+ if time <= timestamps[0] {
674
+ return (0, 0, 0.0);
675
+ }
676
+ if time >= timestamps[timestamps.len() - 1] {
677
+ let last = timestamps.len() - 1;
678
+ return (last, last, 0.0);
679
+ }
680
+ for i in 0..timestamps.len() - 1 {
681
+ if time >= timestamps[i] && time < timestamps[i + 1] {
682
+ let dt = timestamps[i + 1] - timestamps[i];
683
+ let t = if dt > 0.0 { (time - timestamps[i]) / dt } else { 0.0 };
684
+ return (i, i + 1, t);
685
+ }
686
+ }
687
+ let last = timestamps.len() - 1;
688
+ (last, last, 0.0)
689
+ }
690
+
691
+ fn sample_vec3(timestamps: &[f32], values: &[[f32; 3]], time: f32) -> [f32; 3] {
692
+ if values.is_empty() { return [0.0; 3]; }
693
+ if values.len() == 1 { return values[0]; }
694
+ let (i0, i1, t) = find_keyframe_pair(timestamps, time);
695
+ if i0 >= values.len() { return values[values.len() - 1]; }
696
+ if i1 >= values.len() { return values[values.len() - 1]; }
697
+ lerp_vec3(&values[i0], &values[i1], t)
698
+ }
699
+
700
+ fn sample_quat(timestamps: &[f32], values: &[[f32; 4]], time: f32) -> [f32; 4] {
701
+ if values.is_empty() { return [0.0, 0.0, 0.0, 1.0]; }
702
+ if values.len() == 1 { return values[0]; }
703
+ let (i0, i1, t) = find_keyframe_pair(timestamps, time);
704
+ if i0 >= values.len() { return values[values.len() - 1]; }
705
+ if i1 >= values.len() { return values[values.len() - 1]; }
706
+ quat_slerp(&values[i0], &values[i1], t)
707
+ }
708
+
709
+ fn compute_joint_transforms(
710
+ skeleton: &SkeletonData,
711
+ joint_idx: usize,
712
+ parent_transform: &[[f32; 4]; 4],
713
+ translations: &[[f32; 3]],
714
+ rotations: &[[f32; 4]],
715
+ scales: &[[f32; 3]],
716
+ world_transforms: &mut [[[f32; 4]; 4]],
717
+ ) {
718
+ if joint_idx >= skeleton.joints.len() { return; }
719
+ let local = mat4_from_trs(&translations[joint_idx], &rotations[joint_idx], &scales[joint_idx]);
720
+ let world = mat4_mul(parent_transform, &local);
721
+ world_transforms[joint_idx] = world;
722
+ let children = skeleton.joints[joint_idx].children.clone();
723
+ for &child in &children {
724
+ compute_joint_transforms(skeleton, child, &world, translations, rotations, scales, world_transforms);
725
+ }
726
+ }
727
+
728
+ // ============================================================
729
+ // glTF animation loader
730
+ // ============================================================
731
+
732
+ fn read_accessor_f32(_gltf: &gltf::Gltf, buffer_data: &[Vec<u8>], accessor: &gltf::Accessor) -> Vec<f32> {
733
+ let view = match accessor.view() {
734
+ Some(v) => v,
735
+ None => return Vec::new(),
736
+ };
737
+ let buf_idx = view.buffer().index();
738
+ if buf_idx >= buffer_data.len() { return Vec::new(); }
739
+ let buf = &buffer_data[buf_idx];
740
+ let offset = view.offset() + accessor.offset();
741
+ let count = accessor.count();
742
+ let stride = view.stride().unwrap_or(accessor.size());
743
+ let component_count = match accessor.dimensions() {
744
+ gltf::accessor::Dimensions::Scalar => 1,
745
+ gltf::accessor::Dimensions::Vec2 => 2,
746
+ gltf::accessor::Dimensions::Vec3 => 3,
747
+ gltf::accessor::Dimensions::Vec4 => 4,
748
+ gltf::accessor::Dimensions::Mat4 => 16,
749
+ _ => 1,
750
+ };
751
+
752
+ let mut result = Vec::with_capacity(count * component_count);
753
+ for i in 0..count {
754
+ let base = offset + i * stride;
755
+ for c in 0..component_count {
756
+ let byte_offset = base + c * 4;
757
+ if byte_offset + 4 <= buf.len() {
758
+ let val = f32::from_le_bytes([buf[byte_offset], buf[byte_offset+1], buf[byte_offset+2], buf[byte_offset+3]]);
759
+ result.push(val);
760
+ } else {
761
+ result.push(0.0);
762
+ }
763
+ }
764
+ }
765
+ result
766
+ }
767
+
768
+ fn load_gltf_animation(data: &[u8]) -> Option<ModelAnimation> {
769
+ let gltf = gltf::Gltf::from_slice(data).ok()?;
770
+
771
+ // Get buffer data
772
+ let mut buffer_data: Vec<Vec<u8>> = Vec::new();
773
+ for buffer in gltf.buffers() {
774
+ match buffer.source() {
775
+ gltf::buffer::Source::Bin => {
776
+ if let Some(blob) = gltf.blob.as_ref() {
777
+ buffer_data.push(blob.clone());
778
+ }
779
+ }
780
+ gltf::buffer::Source::Uri(uri) => {
781
+ if let Some(encoded) = uri.strip_prefix("data:application/octet-stream;base64,") {
782
+ let mut decoded = Vec::new();
783
+ let _ = base64_decode(encoded, &mut decoded);
784
+ buffer_data.push(decoded);
785
+ } else {
786
+ buffer_data.push(Vec::new());
787
+ }
788
+ }
789
+ }
790
+ }
791
+
792
+ // Parse skeleton from the first skin
793
+ let skeleton = if let Some(skin) = gltf.skins().next() {
794
+ let joints_nodes: Vec<_> = skin.joints().collect();
795
+ let joint_count = joints_nodes.len();
796
+
797
+ // Build a mapping from node index to joint index
798
+ let mut node_to_joint = std::collections::HashMap::new();
799
+ for (ji, node) in joints_nodes.iter().enumerate() {
800
+ node_to_joint.insert(node.index(), ji);
801
+ }
802
+
803
+ // Read inverse bind matrices
804
+ let ibm_data = if let Some(accessor) = skin.inverse_bind_matrices() {
805
+ read_accessor_f32(&gltf, &buffer_data, &accessor)
806
+ } else {
807
+ let mut default_ibm = Vec::with_capacity(joint_count * 16);
808
+ for _ in 0..joint_count {
809
+ default_ibm.extend_from_slice(&[
810
+ 1.0, 0.0, 0.0, 0.0,
811
+ 0.0, 1.0, 0.0, 0.0,
812
+ 0.0, 0.0, 1.0, 0.0,
813
+ 0.0, 0.0, 0.0, 1.0,
814
+ ]);
815
+ }
816
+ default_ibm
817
+ };
818
+
819
+ let mut joints = Vec::with_capacity(joint_count);
820
+ let mut root_joints = Vec::new();
821
+
822
+ for (ji, node) in joints_nodes.iter().enumerate() {
823
+ let mut ibm = [[0.0f32; 4]; 4];
824
+ let base = ji * 16;
825
+ if base + 16 <= ibm_data.len() {
826
+ // glTF stores column-major; read directly (we also use column-major)
827
+ for a in 0..4 {
828
+ for b in 0..4 {
829
+ ibm[a][b] = ibm_data[base + a * 4 + b];
830
+ }
831
+ }
832
+ } else {
833
+ ibm = mat4_identity();
834
+ }
835
+
836
+ // Blender FBX export bakes 100x scale into IBMs (converts m→cm for bone space).
837
+ // This is NEEDED because Blender also pre-scales vertex positions to meters.
838
+ // The 100x in IBMs converts meter-space vertices to cm-space bone transforms.
839
+ // DO NOT normalize — the scale is intentional and required.
840
+
841
+ let children: Vec<usize> = node.children()
842
+ .filter_map(|child| node_to_joint.get(&child.index()).copied())
843
+ .collect();
844
+
845
+ let name = node.name().unwrap_or("").to_string();
846
+ let (t, r, s) = node.transform().decomposed();
847
+
848
+ joints.push(JointData {
849
+ inverse_bind: ibm, children, name,
850
+ rest_translation: t,
851
+ rest_rotation: r,
852
+ rest_scale: s,
853
+ });
854
+ }
855
+
856
+ // Find root joints (joints that are not children of any other joint)
857
+ let mut is_child = vec![false; joint_count];
858
+ for joint in &joints {
859
+ for &child in &joint.children {
860
+ if child < joint_count { is_child[child] = true; }
861
+ }
862
+ }
863
+ for i in 0..joint_count {
864
+ if !is_child[i] { root_joints.push(i); }
865
+ }
866
+
867
+ #[cfg(debug_assertions)]
868
+ {
869
+ eprintln!("[anim] Skeleton: {} joints, {} roots", joints.len(), root_joints.len());
870
+ for (i, j) in joints.iter().enumerate() {
871
+ if i < 5 || i == joints.len() - 1 {
872
+ eprintln!("[anim] joint {}: '{}' children={:?}", i, j.name, j.children);
873
+ }
874
+ }
875
+ }
876
+
877
+ Some(SkeletonData { joints, root_joints })
878
+ } else {
879
+ #[cfg(debug_assertions)]
880
+ eprintln!("[anim] No skin found in glTF!");
881
+ None
882
+ };
883
+
884
+ // Parse animations
885
+ let mut animations = Vec::new();
886
+ for anim in gltf.animations() {
887
+ let mut channels = Vec::new();
888
+ let mut duration: f32 = 0.0;
889
+
890
+ // Build node-to-joint mapping for channel resolution
891
+ let node_to_joint: std::collections::HashMap<usize, usize> = if let Some(skin) = gltf.skins().next() {
892
+ skin.joints().enumerate().map(|(ji, node)| (node.index(), ji)).collect()
893
+ } else {
894
+ std::collections::HashMap::new()
895
+ };
896
+
897
+ // Group channels by target node: (trans_ts, translations, rot_ts, rotations, scale_ts, scales)
898
+ let mut node_channels: std::collections::HashMap<usize, (Vec<f32>, Vec<[f32; 3]>, Vec<f32>, Vec<[f32; 4]>, Vec<f32>, Vec<[f32; 3]>)> = std::collections::HashMap::new();
899
+
900
+ #[cfg(debug_assertions)]
901
+ let mut skipped_channels = 0usize;
902
+ #[cfg(debug_assertions)]
903
+ let mut mapped_channels = 0usize;
904
+ #[cfg(debug_assertions)]
905
+ {
906
+ eprintln!("[anim] Animation '{}' has {} channels, node_to_joint map has {} entries",
907
+ anim.name().unwrap_or("?"), anim.channels().count(), node_to_joint.len());
908
+ for (ci, ch) in anim.channels().enumerate() {
909
+ if ci < 5 {
910
+ let tn = ch.target().node();
911
+ eprintln!("[anim] channel {} targets node {} '{}' mapped={}",
912
+ ci, tn.index(), tn.name().unwrap_or("?"),
913
+ node_to_joint.contains_key(&tn.index()));
914
+ }
915
+ }
916
+ }
917
+ for channel in anim.channels() {
918
+ let target_node = channel.target().node().index();
919
+ let joint_index = match node_to_joint.get(&target_node) {
920
+ Some(&ji) => {
921
+ #[cfg(debug_assertions)]
922
+ { mapped_channels += 1; }
923
+ ji
924
+ },
925
+ None => {
926
+ #[cfg(debug_assertions)]
927
+ { skipped_channels += 1; }
928
+ continue;
929
+ },
930
+ };
931
+
932
+ let sampler = channel.sampler();
933
+ let input_accessor = sampler.input();
934
+ let output_accessor = sampler.output();
935
+
936
+ let timestamps = read_accessor_f32(&gltf, &buffer_data, &input_accessor);
937
+ let values = read_accessor_f32(&gltf, &buffer_data, &output_accessor);
938
+
939
+ if let Some(&last) = timestamps.last() {
940
+ if last > duration { duration = last; }
941
+ }
942
+
943
+ let entry = node_channels.entry(joint_index).or_insert_with(|| (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()));
944
+
945
+ match channel.target().property() {
946
+ gltf::animation::Property::Translation => {
947
+ entry.0 = timestamps;
948
+ entry.1 = values.chunks(3).map(|c| [c[0], c[1], c[2]]).collect();
949
+ }
950
+ gltf::animation::Property::Rotation => {
951
+ entry.2 = timestamps;
952
+ entry.3 = values.chunks(4).map(|c| [c[0], c[1], c[2], c[3]]).collect();
953
+ }
954
+ gltf::animation::Property::Scale => {
955
+ entry.4 = timestamps;
956
+ entry.5 = values.chunks(3).map(|c| [c[0], c[1], c[2]]).collect();
957
+ }
958
+ _ => {}
959
+ }
960
+ }
961
+
962
+ for (joint_index, (trans_ts, translations, rot_ts, rotations, scale_ts, scales)) in node_channels {
963
+ // Use the longest timestamp array as the primary (for backward compat)
964
+ let timestamps = if rot_ts.len() >= trans_ts.len() && rot_ts.len() >= scale_ts.len() {
965
+ rot_ts.clone()
966
+ } else if trans_ts.len() >= scale_ts.len() {
967
+ trans_ts.clone()
968
+ } else {
969
+ scale_ts.clone()
970
+ };
971
+ channels.push(AnimationChannel {
972
+ joint_index,
973
+ timestamps,
974
+ translations,
975
+ rotation_timestamps: rot_ts,
976
+ rotations,
977
+ scale_timestamps: scale_ts,
978
+ scales,
979
+ });
980
+ }
981
+
982
+ let name = anim.name().unwrap_or("").to_string();
983
+ #[cfg(debug_assertions)]
984
+ {
985
+ let total_kf: usize = channels.iter().map(|c| c.timestamps.len()).sum();
986
+ let avg_kf = if !channels.is_empty() { total_kf / channels.len() } else { 0 };
987
+ eprintln!("[anim] Animation '{}': {} channels mapped, {} skipped, duration={:.2}s, avg {}/ch keyframes",
988
+ name, mapped_channels, skipped_channels, duration, avg_kf);
989
+ }
990
+ animations.push(AnimationData { channels, duration, name });
991
+ }
992
+
993
+ let joint_count = skeleton.as_ref().map(|s| s.joints.len()).unwrap_or(0);
994
+ // Build reference rest rotations from the first animation at t=0
995
+ let ref_rest_rotations = if animations.len() > 1 {
996
+ if let Some(ref skel) = skeleton {
997
+ let joint_count_s = skel.joints.len();
998
+ let mut rest_rots = vec![[0.0f32, 0.0, 0.0, 1.0]; joint_count_s];
999
+ // Sample first animation at t=0 to get reference rest rotations
1000
+ let anim0 = &animations[0];
1001
+ for ch in &anim0.channels {
1002
+ if ch.joint_index < joint_count_s && !ch.rotations.is_empty() {
1003
+ rest_rots[ch.joint_index] = if ch.rotations.len() > 0 { ch.rotations[0] } else { [0.0, 0.0, 0.0, 1.0] };
1004
+ }
1005
+ }
1006
+ #[cfg(debug_assertions)]
1007
+ eprintln!("[retarget] Built reference rest rotations from anim 0 for {} joints", joint_count_s);
1008
+ Some(rest_rots)
1009
+ } else { None }
1010
+ } else { None };
1011
+
1012
+ Some(ModelAnimation {
1013
+ skeleton,
1014
+ animations,
1015
+ joint_matrices: vec![mat4_identity(); joint_count],
1016
+ ref_rest_rotations,
1017
+ })
1018
+ }
1019
+
1020
+ fn load_gltf_with_textures(
1021
+ data: &[u8],
1022
+ renderer: &mut crate::renderer::Renderer,
1023
+ base_dir: Option<&std::path::Path>,
1024
+ ) -> Option<ModelData> {
1025
+ let gltf = gltf::Gltf::from_slice(data).ok()?;
1026
+
1027
+ // Get buffer data
1028
+ let mut buffer_data: Vec<Vec<u8>> = Vec::new();
1029
+ for buffer in gltf.buffers() {
1030
+ match buffer.source() {
1031
+ gltf::buffer::Source::Bin => {
1032
+ if let Some(blob) = gltf.blob.as_ref() { buffer_data.push(blob.clone()); }
1033
+ }
1034
+ gltf::buffer::Source::Uri(uri) => {
1035
+ if let Some(encoded) = uri.strip_prefix("data:application/octet-stream;base64,") {
1036
+ let mut decoded = Vec::new();
1037
+ let _ = base64_decode(encoded, &mut decoded);
1038
+ buffer_data.push(decoded);
1039
+ } else if let Some(dir) = base_dir {
1040
+ // External .bin file alongside the .gltf.
1041
+ let path = dir.join(uri);
1042
+ match std::fs::read(&path) {
1043
+ Ok(bytes) => buffer_data.push(bytes),
1044
+ Err(_) => buffer_data.push(Vec::new()),
1045
+ }
1046
+ } else {
1047
+ buffer_data.push(Vec::new());
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ // Pre-walk materials to identify which image indices are normal
1054
+ // maps. They need LEADR-style vector-space mip generation and per-
1055
+ // mip variance baked into alpha; see register_texture_kind.
1056
+ let mut normal_image_set: std::collections::HashSet<usize> = Default::default();
1057
+ for mat in gltf.materials() {
1058
+ if let Some(nt) = mat.normal_texture() {
1059
+ normal_image_set.insert(nt.texture().source().index());
1060
+ }
1061
+ }
1062
+
1063
+ // Extract and register textures
1064
+ let mut texture_indices: Vec<u32> = Vec::new(); // maps glTF image index -> renderer texture index
1065
+ for (image_idx, image) in gltf.images().enumerate() {
1066
+ let is_normal = normal_image_set.contains(&image_idx);
1067
+ match image.source() {
1068
+ gltf::image::Source::View { view, .. } => {
1069
+ let buf_idx = view.buffer().index();
1070
+ if buf_idx < buffer_data.len() {
1071
+ let offset = view.offset();
1072
+ let length = view.length();
1073
+ if offset + length <= buffer_data[buf_idx].len() {
1074
+ let img_data = &buffer_data[buf_idx][offset..offset + length];
1075
+ // Decode image (PNG/JPEG)
1076
+ if let Ok(img) = image::load_from_memory(img_data) {
1077
+ let rgba = img.to_rgba8();
1078
+ let (w, h) = (rgba.width(), rgba.height());
1079
+ let tex_idx = renderer.register_texture_kind(w, h, &rgba, is_normal);
1080
+ texture_indices.push(tex_idx);
1081
+ } else {
1082
+ texture_indices.push(0); // fallback to white
1083
+ }
1084
+ } else {
1085
+ texture_indices.push(0);
1086
+ }
1087
+ } else {
1088
+ texture_indices.push(0);
1089
+ }
1090
+ }
1091
+ gltf::image::Source::Uri { uri, .. } => {
1092
+ // External image file (loose glTF). Resolve relative to
1093
+ // the .gltf file's directory.
1094
+ let (bytes, effective_uri): (Option<Vec<u8>>, String) =
1095
+ if let Some(encoded) = uri.strip_prefix("data:") {
1096
+ let decoded = encoded.find(";base64,").map(|pos| {
1097
+ let b64 = &encoded[pos + 8..];
1098
+ let mut out = Vec::new();
1099
+ let _ = base64_decode(b64, &mut out);
1100
+ out
1101
+ });
1102
+ (decoded, uri.to_string())
1103
+ } else if let Some(dir) = base_dir {
1104
+ let primary = dir.join(uri);
1105
+ if let Ok(b) = std::fs::read(&primary) {
1106
+ (Some(b), uri.to_string())
1107
+ } else {
1108
+ // Asset packs sometimes ship DDS-only while the
1109
+ // glTF still references a .png URI (Lumberyard
1110
+ // Bistro does this). Retry with a .dds
1111
+ // sibling before giving up.
1112
+ let swapped = swap_extension(uri, "dds");
1113
+ let alt = dir.join(&swapped);
1114
+ match std::fs::read(&alt) {
1115
+ Ok(b) => (Some(b), swapped),
1116
+ Err(_) => (None, uri.to_string()),
1117
+ }
1118
+ }
1119
+ } else {
1120
+ (None, uri.to_string())
1121
+ };
1122
+ match bytes.and_then(|b| decode_texture_bytes(&b, &effective_uri)) {
1123
+ Some((rgba, w, h)) => {
1124
+ texture_indices.push(renderer.register_texture_kind(w, h, &rgba, is_normal));
1125
+ }
1126
+ None => texture_indices.push(0),
1127
+ }
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ // Detect armature scale for skinned meshes.
1133
+ // Blender FBX imports set armature scale to 0.01 (cm→m conversion).
1134
+ // Vertex positions inherit this scale but bone transforms don't,
1135
+ // creating a unit mismatch. We apply the inverse to vertex positions.
1136
+ let skin_vertex_scale: f32 = {
1137
+ let mut scale = 1.0f32;
1138
+ for node in gltf.nodes() {
1139
+ if node.mesh().is_some() && node.skin().is_some() {
1140
+ // Found a skinned mesh node — look for parent with scale
1141
+ for parent in gltf.nodes() {
1142
+ for child in parent.children() {
1143
+ if child.index() == node.index() {
1144
+ let (_, _, s) = parent.transform().decomposed();
1145
+ let avg_scale = (s[0] + s[1] + s[2]) / 3.0;
1146
+ if avg_scale > 0.001 && (avg_scale - 1.0).abs() > 0.01 {
1147
+ scale = 1.0 / avg_scale;
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ }
1154
+ // Fallback: check IBMs for large scale (Blender FBX baked 100x)
1155
+ if (scale - 1.0).abs() < 0.01 {
1156
+ if let Some(skin) = gltf.skins().next() {
1157
+ if let Some(accessor) = skin.inverse_bind_matrices() {
1158
+ let view = accessor.view().unwrap();
1159
+ let buf_idx = view.buffer().index();
1160
+ if buf_idx < buffer_data.len() {
1161
+ let offset = view.offset() + accessor.offset();
1162
+ let data = &buffer_data[buf_idx];
1163
+ if offset + 12 <= data.len() {
1164
+ // Read first 3 floats (first column of first IBM)
1165
+ let f0 = f32::from_le_bytes([data[offset], data[offset+1], data[offset+2], data[offset+3]]);
1166
+ let f1 = f32::from_le_bytes([data[offset+4], data[offset+5], data[offset+6], data[offset+7]]);
1167
+ let f2 = f32::from_le_bytes([data[offset+8], data[offset+9], data[offset+10], data[offset+11]]);
1168
+ let diag = (f0*f0 + f1*f1 + f2*f2).sqrt();
1169
+ if diag > 10.0 {
1170
+ scale = diag;
1171
+ #[cfg(debug_assertions)]
1172
+ eprintln!("[skin] IBM col0 len={:.1}, applying {:.0}x vertex scale", diag, scale);
1173
+ }
1174
+ }
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+ if (scale - 1.0).abs() > 0.01 {
1180
+ #[cfg(debug_assertions)]
1181
+ eprintln!("[skin] Applying {:.0}x vertex scale to compensate armature transform", scale);
1182
+ }
1183
+ scale
1184
+ };
1185
+
1186
+ let mut meshes = Vec::new();
1187
+ let mut bbox_min = [f32::MAX; 3];
1188
+ let mut bbox_max = [f32::MIN; 3];
1189
+
1190
+ // Walk the scene node tree to collect world-space transforms for
1191
+ // each mesh-referencing node. glTF supports instancing by having
1192
+ // multiple nodes reference the same mesh at different transforms
1193
+ // — Bistro uses this heavily (5910 nodes, 551 meshes: chairs,
1194
+ // bollards, chains, foliage repeated everywhere). We emit one
1195
+ // MeshData PER (mesh, transform) pair so every instance actually
1196
+ // shows up in the scene. Memory cost is linear in node count;
1197
+ // not great for deep instancing but correct. Animated / skinned
1198
+ // meshes are unaffected — the armature transforms apply on top.
1199
+ let mesh_count = gltf.meshes().count();
1200
+ let mut mesh_instances: Vec<Vec<[[f32; 4]; 4]>> = vec![Vec::new(); mesh_count];
1201
+ let identity = [[1.0f32, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]];
1202
+ for scene in gltf.scenes() {
1203
+ for node in scene.nodes() {
1204
+ walk_scene_collect_instances(&node, &identity, &mut mesh_instances);
1205
+ }
1206
+ }
1207
+
1208
+ for mesh in gltf.meshes() {
1209
+ let instances = mesh_instances[mesh.index()].clone();
1210
+ // Meshes reachable from no scene node would have no instances;
1211
+ // fall back to a single identity transform so orphan meshes
1212
+ // still render (matches prior behaviour for simple models).
1213
+ let instance_transforms: Vec<Option<[[f32; 4]; 4]>> = if instances.is_empty() {
1214
+ vec![None]
1215
+ } else {
1216
+ instances.into_iter().map(Some).collect()
1217
+ };
1218
+
1219
+ for mesh_world in &instance_transforms {
1220
+ let mesh_world = *mesh_world;
1221
+ // Inverse-transpose 3×3 for normals under non-uniform scale.
1222
+ let normal_xform = mesh_world.map(|m| mat4_inverse_transpose_3x3(&m));
1223
+ for primitive in mesh.primitives() {
1224
+ let reader = primitive.reader(|buf| buffer_data.get(buf.index()).map(|d| d.as_slice()));
1225
+ let positions: Vec<[f32; 3]> = match reader.read_positions() {
1226
+ Some(iter) => iter.collect(),
1227
+ None => continue,
1228
+ };
1229
+ let normals: Vec<[f32; 3]> = reader.read_normals()
1230
+ .map(|iter| iter.collect())
1231
+ .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
1232
+ let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0)
1233
+ .map(|iter| iter.into_f32().collect())
1234
+ .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
1235
+ // Tangents (vec4: xyz = tangent, w = bitangent sign ±1).
1236
+ // If absent, we leave them as zero so the shader knows to
1237
+ // skip normal-map perturbation for this mesh.
1238
+ let tangents: Vec<[f32; 4]> = reader.read_tangents()
1239
+ .map(|iter| iter.collect())
1240
+ .unwrap_or_else(|| vec![[0.0; 4]; positions.len()]);
1241
+
1242
+ // Get vertex colors if available
1243
+ let vert_colors: Option<Vec<[f32; 4]>> = reader.read_colors(0)
1244
+ .map(|iter| iter.into_rgba_f32().collect());
1245
+
1246
+ let mat = primitive.material();
1247
+ let pbr = mat.pbr_metallic_roughness();
1248
+ let emissive_factor = mat.emissive_factor();
1249
+
1250
+ let tex_idx_of = |img_idx: usize| -> Option<u32> {
1251
+ texture_indices.get(img_idx).copied()
1252
+ };
1253
+
1254
+ let normal_tex_idx = mat.normal_texture()
1255
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1256
+ let emissive_tex_idx = mat.emissive_texture()
1257
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1258
+ let occlusion_tex_idx = mat.occlusion_texture()
1259
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1260
+
1261
+ // Metallic-roughness first; fall back to
1262
+ // KHR_materials_pbrSpecularGlossiness when only that's
1263
+ // authored (Lumberyard Bistro + many FBX exports).
1264
+ // Conversion matches the load_gltf_staged path — see
1265
+ // specgloss_to_metalrough for the algorithm.
1266
+ let (mut base_color, mut metallic_factor, mut roughness_factor, tex_idx, mr_tex_idx) =
1267
+ if pbr.base_color_texture().is_none() {
1268
+ if let Some(sg) = mat.pbr_specular_glossiness() {
1269
+ let diffuse = sg.diffuse_factor();
1270
+ let spec = sg.specular_factor();
1271
+ let (base_color, metallic) =
1272
+ specgloss_to_metalrough(diffuse, spec);
1273
+ let roughness = 1.0 - sg.glossiness_factor();
1274
+ let diffuse_tex = sg.diffuse_texture()
1275
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1276
+ (base_color, metallic, roughness, diffuse_tex, None)
1277
+ } else {
1278
+ (pbr.base_color_factor(), pbr.metallic_factor(), pbr.roughness_factor(), None, None)
1279
+ }
1280
+ } else {
1281
+ let tex = pbr.base_color_texture()
1282
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1283
+ let mr = pbr.metallic_roughness_texture()
1284
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1285
+ (pbr.base_color_factor(), pbr.metallic_factor(), pbr.roughness_factor(), tex, mr)
1286
+ };
1287
+
1288
+ if let Some(t) = mat.transmission() {
1289
+ apply_transmission_hack(
1290
+ t.transmission_factor(),
1291
+ &mut base_color,
1292
+ &mut metallic_factor,
1293
+ &mut roughness_factor,
1294
+ );
1295
+ }
1296
+
1297
+ let mut vertices = Vec::with_capacity(positions.len());
1298
+ for i in 0..positions.len() {
1299
+ let p = positions[i];
1300
+ for k in 0..3 {
1301
+ if p[k] < bbox_min[k] { bbox_min[k] = p[k]; }
1302
+ if p[k] > bbox_max[k] { bbox_max[k] = p[k]; }
1303
+ }
1304
+ let color = if let Some(ref vc) = vert_colors {
1305
+ vc[i]
1306
+ } else {
1307
+ [base_color[0], base_color[1], base_color[2], base_color[3]]
1308
+ };
1309
+ // Skin data (joints + weights)
1310
+ let joint_vals: Option<Vec<[u16; 4]>> = reader.read_joints(0)
1311
+ .map(|iter| iter.into_u16().collect());
1312
+ let weight_vals: Option<Vec<[f32; 4]>> = reader.read_weights(0)
1313
+ .map(|iter| iter.into_f32().collect());
1314
+
1315
+ let jv = if let Some(ref j) = joint_vals {
1316
+ [j[i][0] as f32, j[i][1] as f32, j[i][2] as f32, j[i][3] as f32]
1317
+ } else {
1318
+ [0.0; 4]
1319
+ };
1320
+ let wv = if let Some(ref w) = weight_vals {
1321
+ w[i]
1322
+ } else {
1323
+ [0.0; 4]
1324
+ };
1325
+ // Apply inverse armature scale to skinned vertex positions
1326
+ let is_skinned = wv[0] + wv[1] + wv[2] + wv[3] > 0.01;
1327
+ let base_pos = if is_skinned && (skin_vertex_scale - 1.0).abs() > 0.01 {
1328
+ [p[0] * skin_vertex_scale, p[1] * skin_vertex_scale, p[2] * skin_vertex_scale]
1329
+ } else {
1330
+ p
1331
+ };
1332
+ // Bake the mesh's scene node transform into world-space
1333
+ // position/normal. Skinned meshes are NOT world-baked:
1334
+ // their node transform is expected to be consumed by the
1335
+ // armature, and the pose is driven by joint matrices at
1336
+ // draw time. Static (non-skinned) meshes get the baked
1337
+ // transform so drawModel's position/scale arguments
1338
+ // apply on top of the correct base pose.
1339
+ let (final_pos, final_normal, final_tangent) = if is_skinned {
1340
+ (base_pos, normals[i], tangents[i])
1341
+ } else if let Some(xform) = mesh_world {
1342
+ let t_in = [tangents[i][0], tangents[i][1], tangents[i][2]];
1343
+ let t_out = match normal_xform {
1344
+ // Tangents transform like positions (as directions)
1345
+ // under the linear part of the transform — we use
1346
+ // the upper 3×3 of the model matrix, not its
1347
+ // inverse-transpose. But since our mesh_world is
1348
+ // rigid-ish (no shear), the normal_xform gets us
1349
+ // close enough for the common case. For a purely
1350
+ // orthonormal node transform these are identical.
1351
+ Some(ref n) => mat3_transform_vec(n, &t_in),
1352
+ None => t_in,
1353
+ };
1354
+ (
1355
+ mat4_transform_point(&xform, &base_pos),
1356
+ match normal_xform {
1357
+ Some(ref n) => mat3_transform_vec(n, &normals[i]),
1358
+ None => normals[i],
1359
+ },
1360
+ [t_out[0], t_out[1], t_out[2], tangents[i][3]],
1361
+ )
1362
+ } else {
1363
+ (base_pos, normals[i], tangents[i])
1364
+ };
1365
+ // Update bbox to reflect the final (possibly transformed)
1366
+ // position so the camera auto-framing still works right.
1367
+ for k in 0..3 {
1368
+ if final_pos[k] < bbox_min[k] { bbox_min[k] = final_pos[k]; }
1369
+ if final_pos[k] > bbox_max[k] { bbox_max[k] = final_pos[k]; }
1370
+ }
1371
+ vertices.push(Vertex3D {
1372
+ position: final_pos,
1373
+ normal: final_normal,
1374
+ color,
1375
+ uv: tex_coords[i],
1376
+ joints: jv,
1377
+ weights: wv,
1378
+ tangent: final_tangent,
1379
+ });
1380
+ }
1381
+ let indices: Vec<u32> = match reader.read_indices() {
1382
+ Some(iter) => iter.into_u32().collect(),
1383
+ None => (0..positions.len() as u32).collect(),
1384
+ };
1385
+ meshes.push(MeshData {
1386
+ vertices,
1387
+ indices,
1388
+ texture_idx: tex_idx,
1389
+ normal_texture_idx: normal_tex_idx,
1390
+ metallic_roughness_texture_idx: mr_tex_idx,
1391
+ emissive_texture_idx: emissive_tex_idx,
1392
+ occlusion_texture_idx: occlusion_tex_idx,
1393
+ metallic_factor,
1394
+ roughness_factor,
1395
+ emissive_factor,
1396
+ alpha_cutoff: alpha_cutoff_from_material(&mat),
1397
+ });
1398
+ }
1399
+ } // end instance loop
1400
+ }
1401
+
1402
+ if meshes.is_empty() { return None; }
1403
+ Some(ModelData { meshes, bbox_min, bbox_max })
1404
+ }
1405
+
1406
+ /// Like load_gltf_with_textures but decodes textures to RGBA without GPU registration.
1407
+ /// Returns a StagedModel with decoded textures that can later be committed on the main thread.
1408
+ pub fn load_gltf_staged(data: &[u8]) -> Option<crate::staging::StagedModel> {
1409
+ use crate::staging::{StagedTexture, StagedModel};
1410
+
1411
+ let gltf = gltf::Gltf::from_slice(data).ok()?;
1412
+
1413
+ let mut buffer_data: Vec<Vec<u8>> = Vec::new();
1414
+ for buffer in gltf.buffers() {
1415
+ match buffer.source() {
1416
+ gltf::buffer::Source::Bin => {
1417
+ if let Some(blob) = gltf.blob.as_ref() { buffer_data.push(blob.clone()); }
1418
+ }
1419
+ gltf::buffer::Source::Uri(uri) => {
1420
+ if let Some(encoded) = uri.strip_prefix("data:application/octet-stream;base64,") {
1421
+ let mut decoded = Vec::new();
1422
+ let _ = base64_decode(encoded, &mut decoded);
1423
+ buffer_data.push(decoded);
1424
+ } else {
1425
+ buffer_data.push(Vec::new());
1426
+ }
1427
+ }
1428
+ }
1429
+ }
1430
+
1431
+ // Decode textures to RGBA without GPU registration.
1432
+ // staged_textures[i] corresponds to glTF image index i.
1433
+ // texture_indices maps glTF image index -> 1-based index into staged_textures (0 = no texture).
1434
+ let mut staged_textures: Vec<StagedTexture> = Vec::new();
1435
+ let mut texture_indices: Vec<u32> = Vec::new();
1436
+ for image in gltf.images() {
1437
+ match image.source() {
1438
+ gltf::image::Source::View { view, .. } => {
1439
+ let buf_idx = view.buffer().index();
1440
+ if buf_idx < buffer_data.len() {
1441
+ let offset = view.offset();
1442
+ let length = view.length();
1443
+ if offset + length <= buffer_data[buf_idx].len() {
1444
+ let img_data = &buffer_data[buf_idx][offset..offset + length];
1445
+ if let Ok(img) = image::load_from_memory(img_data) {
1446
+ let rgba = img.to_rgba8();
1447
+ let (w, h) = (rgba.width(), rgba.height());
1448
+ staged_textures.push(StagedTexture {
1449
+ data: rgba.into_raw(),
1450
+ width: w,
1451
+ height: h,
1452
+ });
1453
+ // 1-based index into staged_textures
1454
+ texture_indices.push(staged_textures.len() as u32);
1455
+ } else {
1456
+ texture_indices.push(0);
1457
+ }
1458
+ } else {
1459
+ texture_indices.push(0);
1460
+ }
1461
+ } else {
1462
+ texture_indices.push(0);
1463
+ }
1464
+ }
1465
+ _ => { texture_indices.push(0); }
1466
+ }
1467
+ }
1468
+
1469
+ // Detect armature scale (same logic as load_gltf_with_textures)
1470
+ let skin_vertex_scale: f32 = {
1471
+ let mut scale = 1.0f32;
1472
+ for node in gltf.nodes() {
1473
+ if node.mesh().is_some() && node.skin().is_some() {
1474
+ for parent in gltf.nodes() {
1475
+ for child in parent.children() {
1476
+ if child.index() == node.index() {
1477
+ let (_, _, s) = parent.transform().decomposed();
1478
+ let avg_scale = (s[0] + s[1] + s[2]) / 3.0;
1479
+ if avg_scale > 0.001 && (avg_scale - 1.0).abs() > 0.01 {
1480
+ scale = 1.0 / avg_scale;
1481
+ }
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+ if (scale - 1.0).abs() < 0.01 {
1488
+ if let Some(skin) = gltf.skins().next() {
1489
+ if let Some(accessor) = skin.inverse_bind_matrices() {
1490
+ let view = accessor.view().unwrap();
1491
+ let buf_idx = view.buffer().index();
1492
+ if buf_idx < buffer_data.len() {
1493
+ let offset = view.offset() + accessor.offset();
1494
+ let data = &buffer_data[buf_idx];
1495
+ if offset + 12 <= data.len() {
1496
+ let f0 = f32::from_le_bytes([data[offset], data[offset+1], data[offset+2], data[offset+3]]);
1497
+ let f1 = f32::from_le_bytes([data[offset+4], data[offset+5], data[offset+6], data[offset+7]]);
1498
+ let f2 = f32::from_le_bytes([data[offset+8], data[offset+9], data[offset+10], data[offset+11]]);
1499
+ let diag = (f0*f0 + f1*f1 + f2*f2).sqrt();
1500
+ if diag > 10.0 {
1501
+ scale = diag;
1502
+ }
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+ }
1508
+ scale
1509
+ };
1510
+
1511
+ let mut meshes = Vec::new();
1512
+ let mut bbox_min = [f32::MAX; 3];
1513
+ let mut bbox_max = [f32::MIN; 3];
1514
+
1515
+ for mesh in gltf.meshes() {
1516
+ for primitive in mesh.primitives() {
1517
+ let reader = primitive.reader(|buf| buffer_data.get(buf.index()).map(|d| d.as_slice()));
1518
+ let positions: Vec<[f32; 3]> = match reader.read_positions() {
1519
+ Some(iter) => iter.collect(),
1520
+ None => continue,
1521
+ };
1522
+ let normals: Vec<[f32; 3]> = reader.read_normals()
1523
+ .map(|iter| iter.collect())
1524
+ .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
1525
+ let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0)
1526
+ .map(|iter| iter.into_f32().collect())
1527
+ .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
1528
+ let tangents: Vec<[f32; 4]> = reader.read_tangents()
1529
+ .map(|iter| iter.collect())
1530
+ .unwrap_or_else(|| vec![[0.0; 4]; positions.len()]);
1531
+ let vert_colors: Option<Vec<[f32; 4]>> = reader.read_colors(0)
1532
+ .map(|iter| iter.into_rgba_f32().collect());
1533
+
1534
+ let mat = primitive.material();
1535
+ let pbr = mat.pbr_metallic_roughness();
1536
+ let emissive_factor = mat.emissive_factor();
1537
+
1538
+ let tex_idx_of = |img_idx: usize| -> Option<u32> {
1539
+ texture_indices.get(img_idx).copied()
1540
+ };
1541
+
1542
+ let normal_tex_idx = mat.normal_texture()
1543
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1544
+ let emissive_tex_idx = mat.emissive_texture()
1545
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1546
+ let occlusion_tex_idx = mat.occlusion_texture()
1547
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1548
+
1549
+ // Prefer the glTF 2.0 metallic-roughness model. Fall back
1550
+ // to KHR_materials_pbrSpecularGlossiness when the material
1551
+ // only ships the legacy spec-gloss extension (Lumberyard
1552
+ // Bistro and many FBX-exported scenes do). Conversion
1553
+ // follows the reference Khronos algorithm: pick metallic
1554
+ // that best explains the diffuse/specular split under the
1555
+ // assumption of a 0.04 dielectric baseline, then blend
1556
+ // base_color between diffuse and specular weighted by
1557
+ // metallic² (metals tint their reflection, dielectrics
1558
+ // show their diffuse).
1559
+ let (mut base_color, mut metallic_factor, mut roughness_factor, tex_idx, mr_tex_idx) =
1560
+ if pbr.base_color_texture().is_none() {
1561
+ if let Some(sg) = mat.pbr_specular_glossiness() {
1562
+ let diffuse = sg.diffuse_factor();
1563
+ let spec = sg.specular_factor();
1564
+ let (base_color, metallic) =
1565
+ specgloss_to_metalrough(diffuse, spec);
1566
+ let roughness = 1.0 - sg.glossiness_factor();
1567
+ let diffuse_tex = sg.diffuse_texture()
1568
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1569
+ (base_color, metallic, roughness, diffuse_tex, None)
1570
+ } else {
1571
+ (pbr.base_color_factor(), pbr.metallic_factor(), pbr.roughness_factor(), None, None)
1572
+ }
1573
+ } else {
1574
+ let tex = pbr.base_color_texture()
1575
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1576
+ let mr = pbr.metallic_roughness_texture()
1577
+ .and_then(|info| tex_idx_of(info.texture().source().index()));
1578
+ (pbr.base_color_factor(), pbr.metallic_factor(), pbr.roughness_factor(), tex, mr)
1579
+ };
1580
+
1581
+ if let Some(t) = mat.transmission() {
1582
+ apply_transmission_hack(
1583
+ t.transmission_factor(),
1584
+ &mut base_color,
1585
+ &mut metallic_factor,
1586
+ &mut roughness_factor,
1587
+ );
1588
+ }
1589
+
1590
+ let mut vertices = Vec::with_capacity(positions.len());
1591
+ for i in 0..positions.len() {
1592
+ let p = positions[i];
1593
+ for k in 0..3 {
1594
+ if p[k] < bbox_min[k] { bbox_min[k] = p[k]; }
1595
+ if p[k] > bbox_max[k] { bbox_max[k] = p[k]; }
1596
+ }
1597
+ let color = if let Some(ref vc) = vert_colors {
1598
+ vc[i]
1599
+ } else {
1600
+ [base_color[0], base_color[1], base_color[2], base_color[3]]
1601
+ };
1602
+ let joint_vals: Option<Vec<[u16; 4]>> = reader.read_joints(0)
1603
+ .map(|iter| iter.into_u16().collect());
1604
+ let weight_vals: Option<Vec<[f32; 4]>> = reader.read_weights(0)
1605
+ .map(|iter| iter.into_f32().collect());
1606
+ let jv = if let Some(ref j) = joint_vals {
1607
+ [j[i][0] as f32, j[i][1] as f32, j[i][2] as f32, j[i][3] as f32]
1608
+ } else {
1609
+ [0.0; 4]
1610
+ };
1611
+ let wv = if let Some(ref w) = weight_vals { w[i] } else { [0.0; 4] };
1612
+ let is_skinned = wv[0] + wv[1] + wv[2] + wv[3] > 0.01;
1613
+ let final_pos = if is_skinned && (skin_vertex_scale - 1.0).abs() > 0.01 {
1614
+ [p[0] * skin_vertex_scale, p[1] * skin_vertex_scale, p[2] * skin_vertex_scale]
1615
+ } else {
1616
+ p
1617
+ };
1618
+ vertices.push(Vertex3D {
1619
+ position: final_pos,
1620
+ normal: normals[i],
1621
+ color,
1622
+ uv: tex_coords[i],
1623
+ joints: jv,
1624
+ weights: wv,
1625
+ tangent: tangents[i],
1626
+ });
1627
+ }
1628
+ let indices: Vec<u32> = match reader.read_indices() {
1629
+ Some(iter) => iter.into_u32().collect(),
1630
+ None => (0..positions.len() as u32).collect(),
1631
+ };
1632
+ meshes.push(MeshData {
1633
+ vertices,
1634
+ indices,
1635
+ texture_idx: tex_idx,
1636
+ normal_texture_idx: normal_tex_idx,
1637
+ metallic_roughness_texture_idx: mr_tex_idx,
1638
+ emissive_texture_idx: emissive_tex_idx,
1639
+ occlusion_texture_idx: occlusion_tex_idx,
1640
+ metallic_factor,
1641
+ roughness_factor,
1642
+ emissive_factor,
1643
+ alpha_cutoff: alpha_cutoff_from_material(&mat),
1644
+ });
1645
+ }
1646
+ }
1647
+
1648
+ if meshes.is_empty() { return None; }
1649
+ Some(StagedModel {
1650
+ model: ModelData { meshes, bbox_min, bbox_max },
1651
+ textures: staged_textures,
1652
+ })
1653
+ }
1654
+
1655
+ fn load_gltf(data: &[u8]) -> Option<ModelData> {
1656
+ let gltf = gltf::Gltf::from_slice(data).ok()?;
1657
+
1658
+ // Get buffer data (for .glb, embedded; for .gltf, inline base64)
1659
+ let mut buffer_data: Vec<Vec<u8>> = Vec::new();
1660
+ for buffer in gltf.buffers() {
1661
+ match buffer.source() {
1662
+ gltf::buffer::Source::Bin => {
1663
+ if let Some(blob) = gltf.blob.as_ref() {
1664
+ buffer_data.push(blob.clone());
1665
+ }
1666
+ }
1667
+ gltf::buffer::Source::Uri(uri) => {
1668
+ if let Some(encoded) = uri.strip_prefix("data:application/octet-stream;base64,") {
1669
+ // Try to decode base64 inline data
1670
+ let mut decoded = Vec::new();
1671
+ let _ = base64_decode(encoded, &mut decoded);
1672
+ buffer_data.push(decoded);
1673
+ } else {
1674
+ buffer_data.push(Vec::new());
1675
+ }
1676
+ }
1677
+ }
1678
+ }
1679
+
1680
+ let mut meshes = Vec::new();
1681
+ let mut bbox_min = [f32::MAX; 3];
1682
+ let mut bbox_max = [f32::MIN; 3];
1683
+
1684
+ for mesh in gltf.meshes() {
1685
+ for primitive in mesh.primitives() {
1686
+ let reader = primitive.reader(|buf| buffer_data.get(buf.index()).map(|d| d.as_slice()));
1687
+
1688
+ let positions: Vec<[f32; 3]> = match reader.read_positions() {
1689
+ Some(iter) => iter.collect(),
1690
+ None => continue,
1691
+ };
1692
+
1693
+ let normals: Vec<[f32; 3]> = reader.read_normals()
1694
+ .map(|iter| iter.collect())
1695
+ .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
1696
+
1697
+ let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0)
1698
+ .map(|iter| iter.into_f32().collect())
1699
+ .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
1700
+
1701
+ // Material base color
1702
+ let base_color = primitive.material().pbr_metallic_roughness()
1703
+ .base_color_factor();
1704
+ let color = [base_color[0], base_color[1], base_color[2], base_color[3]];
1705
+
1706
+ let mut vertices = Vec::with_capacity(positions.len());
1707
+ for i in 0..positions.len() {
1708
+ let p = positions[i];
1709
+ for k in 0..3 {
1710
+ if p[k] < bbox_min[k] { bbox_min[k] = p[k]; }
1711
+ if p[k] > bbox_max[k] { bbox_max[k] = p[k]; }
1712
+ }
1713
+ // Read skin data if available
1714
+ let joint_vals: Option<Vec<[u16; 4]>> = reader.read_joints(0)
1715
+ .map(|iter| iter.into_u16().collect());
1716
+ let weight_vals: Option<Vec<[f32; 4]>> = reader.read_weights(0)
1717
+ .map(|iter| iter.into_f32().collect());
1718
+ let jv = if let Some(ref j) = joint_vals {
1719
+ [j[i][0] as f32, j[i][1] as f32, j[i][2] as f32, j[i][3] as f32]
1720
+ } else { [0.0; 4] };
1721
+ let wv = if let Some(ref w) = weight_vals { w[i] } else { [0.0; 4] };
1722
+
1723
+ vertices.push(Vertex3D {
1724
+ position: p,
1725
+ normal: normals[i],
1726
+ color,
1727
+ uv: tex_coords[i],
1728
+ joints: jv,
1729
+ weights: wv,
1730
+ tangent: [0.0; 4],
1731
+ });
1732
+ }
1733
+
1734
+ let indices: Vec<u32> = match reader.read_indices() {
1735
+ Some(iter) => iter.into_u32().collect(),
1736
+ None => (0..positions.len() as u32).collect(),
1737
+ };
1738
+
1739
+ meshes.push(MeshData { vertices, indices, texture_idx: None, normal_texture_idx: None, metallic_roughness_texture_idx: None, emissive_texture_idx: None, occlusion_texture_idx: None, metallic_factor: 0.0, roughness_factor: 1.0, emissive_factor: [0.0; 3], alpha_cutoff: 0.0 });
1740
+ }
1741
+ }
1742
+
1743
+ if meshes.is_empty() { return None; }
1744
+ Some(ModelData { meshes, bbox_min, bbox_max })
1745
+ }
1746
+
1747
+ /// Convert a KHR_materials_pbrSpecularGlossiness (diffuse + specular
1748
+ /// + glossiness) material to the metallic-roughness model. Uses the
1749
+ /// reference Khronos two-path formula so materials authored in
1750
+ /// Substance/3ds Max/FBX pipelines (Lumberyard Bistro, many ORCA
1751
+ /// assets) render correctly on a metal-rough pipeline.
1752
+ ///
1753
+ /// High-level idea: assume a 0.04 dielectric reflectance baseline,
1754
+ /// solve for the metallic factor that best reconciles the authored
1755
+ /// diffuse and specular colors, then blend base_color between the
1756
+ /// two weighted by metallic². Metals have specular ≈ albedo, so the
1757
+ /// specular color becomes their base_color; dielectrics carry their
1758
+ /// diffuse color through at metallic ≈ 0.
1759
+ ///
1760
+ /// Map glTF alpha mode + cutoff to a single shader cutoff value.
1761
+ /// OPAQUE → 0.0 (fragment shader's `< cutoff` discard never fires).
1762
+ /// MASK → material-authored cutoff (default 0.5 per glTF spec).
1763
+ /// BLEND → treated as MASK @ 0.5 since we don't yet have a sorted
1764
+ /// transparent pipeline. Better than silently rendering
1765
+ /// foliage + fabric as fully opaque — an alpha-cutout leaf
1766
+ /// card is at least the right *shape*.
1767
+ fn alpha_cutoff_from_material(mat: &gltf::Material) -> f32 {
1768
+ match mat.alpha_mode() {
1769
+ gltf::material::AlphaMode::Opaque => 0.0,
1770
+ gltf::material::AlphaMode::Mask => mat.alpha_cutoff().unwrap_or(0.5),
1771
+ gltf::material::AlphaMode::Blend => 0.5,
1772
+ }
1773
+ }
1774
+
1775
+ /// Fake KHR_materials_transmission as a near-mirror dielectric.
1776
+ ///
1777
+ /// We don't implement real refractive transmission (no back-buffer
1778
+ /// refraction pass, no thin-walled/volume distinction), so a
1779
+ /// transmission=0.9 glass pane loads as a plain diffuse white surface
1780
+ /// that drowns the 4% Fresnel specular in a bright diffuse term — the
1781
+ /// classic "painted white window" look.
1782
+ ///
1783
+ /// As a stand-in we force heavy transmission materials to behave like
1784
+ /// chrome: metallic=1 so f0=base_color (not 0.04), roughness ≤ 0.05
1785
+ /// so reflections stay crisp, and a mild (0.85×) tint on base_color
1786
+ /// so pure-white glass doesn't read as perfectly reflective chrome.
1787
+ /// Not physically correct for glass — real glass only reflects 4% at
1788
+ /// normal incidence — but it matches how windows *read* in photos
1789
+ /// (reflecting sky/buildings) far better than a flat diffuse surface.
1790
+ fn apply_transmission_hack(
1791
+ transmission: f32,
1792
+ base_color: &mut [f32; 4],
1793
+ metallic: &mut f32,
1794
+ roughness: &mut f32,
1795
+ ) {
1796
+ if transmission > 0.5 {
1797
+ *metallic = 1.0;
1798
+ *roughness = roughness.min(0.05);
1799
+ base_color[0] *= 0.85;
1800
+ base_color[1] *= 0.85;
1801
+ base_color[2] *= 0.85;
1802
+ base_color[3] = 1.0;
1803
+ }
1804
+ }
1805
+
1806
+ /// Reference: Khronos glTF sample specGloss→metallicRoughness
1807
+ /// converter (https://github.com/KhronosGroup/glTF/pull/1355).
1808
+ fn specgloss_to_metalrough(diffuse: [f32; 4], specular: [f32; 3]) -> ([f32; 4], f32) {
1809
+ let dielectric_specular = 0.04_f32;
1810
+ let epsilon = 1e-6_f32;
1811
+
1812
+ let one_minus_dielectric = 1.0 - dielectric_specular;
1813
+ let diffuse_max = diffuse[0].max(diffuse[1]).max(diffuse[2]);
1814
+ let specular_max = specular[0].max(specular[1]).max(specular[2]);
1815
+
1816
+ // Solve a quadratic for metallic. Coefficients from the Khronos
1817
+ // reference: mapping perceived brightness split between diffuse
1818
+ // and specular back to a single metallic parameter.
1819
+ let a = dielectric_specular;
1820
+ let b = diffuse_max * one_minus_dielectric / dielectric_specular.max(epsilon)
1821
+ + specular_max
1822
+ - 2.0 * dielectric_specular;
1823
+ let c = dielectric_specular - specular_max;
1824
+ let discriminant = (b * b - 4.0 * a * c).max(0.0);
1825
+ let metallic = if specular_max < dielectric_specular {
1826
+ 0.0
1827
+ } else {
1828
+ (((-b + discriminant.sqrt()) / (2.0 * a)).clamp(0.0, 1.0)).min(1.0)
1829
+ };
1830
+
1831
+ // base_color = mix(diffuse, specular, metallic²) with the diffuse
1832
+ // branch scaled to undo the dielectric energy split.
1833
+ let diffuse_branch_scale = one_minus_dielectric
1834
+ / (1.0 - metallic * dielectric_specular).max(epsilon);
1835
+ let metal_weight = metallic * metallic;
1836
+ let lerp = |a: f32, b: f32, t: f32| a * (1.0 - t) + b * t;
1837
+ let r = lerp(diffuse[0] * diffuse_branch_scale, specular[0], metal_weight);
1838
+ let g = lerp(diffuse[1] * diffuse_branch_scale, specular[1], metal_weight);
1839
+ let bl = lerp(diffuse[2] * diffuse_branch_scale, specular[2], metal_weight);
1840
+ ([r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), bl.clamp(0.0, 1.0), diffuse[3]], metallic)
1841
+ }
1842
+
1843
+ /// Replace the extension on a URI (keeps directories / query strings
1844
+ /// untouched). Used to fall back from `foo.png` → `foo.dds` when a
1845
+ /// glTF references a PNG URI that isn't on disk but the DDS sibling is.
1846
+ fn swap_extension(uri: &str, new_ext: &str) -> String {
1847
+ let q = uri.find('?').unwrap_or(uri.len());
1848
+ let (path, query) = uri.split_at(q);
1849
+ let new_path = match path.rfind('.') {
1850
+ Some(dot) if dot > path.rfind('/').unwrap_or(0) => {
1851
+ format!("{}.{}", &path[..dot], new_ext)
1852
+ }
1853
+ _ => format!("{}.{}", path, new_ext),
1854
+ };
1855
+ format!("{}{}", new_path, query)
1856
+ }
1857
+
1858
+ /// Decode a texture byte slice into RGBA8 pixels + dimensions. Tries
1859
+ /// DDS first when the URI extension suggests it (for asset packs like
1860
+ /// Lumberyard Bistro that ship BC-compressed textures), falling back
1861
+ /// to the `image` crate for PNG/JPEG/etc. Returns None on failure.
1862
+ fn decode_texture_bytes(bytes: &[u8], uri: &str) -> Option<(Vec<u8>, u32, u32)> {
1863
+ let is_dds = uri.to_ascii_lowercase().ends_with(".dds")
1864
+ || bytes.len() >= 4 && &bytes[..4] == b"DDS ";
1865
+ if is_dds {
1866
+ if let Ok(dds) = image_dds::ddsfile::Dds::read(bytes) {
1867
+ // Decode mip 0 → RGBA8. image_from_dds handles the common
1868
+ // BC1–BC7 formats; anything it can't decode falls through
1869
+ // to the image crate which will almost certainly fail too.
1870
+ if let Ok(rgba) = image_dds::image_from_dds(&dds, 0) {
1871
+ let (w, h) = (rgba.width(), rgba.height());
1872
+ return Some((rgba.into_raw(), w, h));
1873
+ }
1874
+ }
1875
+ }
1876
+ let img = image::load_from_memory(bytes).ok()?;
1877
+ let rgba = img.to_rgba8();
1878
+ let (w, h) = (rgba.width(), rgba.height());
1879
+ Some((rgba.into_raw(), w, h))
1880
+ }
1881
+
1882
+ fn base64_decode(input: &str, output: &mut Vec<u8>) {
1883
+ let mut buf = 0u32;
1884
+ let mut bits = 0u32;
1885
+ for &b in input.as_bytes() {
1886
+ let val = match b {
1887
+ b'A'..=b'Z' => b - b'A',
1888
+ b'a'..=b'z' => b - b'a' + 26,
1889
+ b'0'..=b'9' => b - b'0' + 52,
1890
+ b'+' => 62,
1891
+ b'/' => 63,
1892
+ b'=' | b'\n' | b'\r' => continue,
1893
+ _ => continue,
1894
+ };
1895
+ buf = (buf << 6) | val as u32;
1896
+ bits += 6;
1897
+ if bits >= 8 {
1898
+ bits -= 8;
1899
+ output.push((buf >> bits) as u8);
1900
+ buf &= (1 << bits) - 1;
1901
+ }
1902
+ }
1903
+ }
1904
+
1905
+ // ---- Catmull-Rom spline helpers (Q9) ----
1906
+
1907
+ fn catmull_rom_point(points: &[f32], n: usize, segment: usize, t: f32) -> [f32; 3] {
1908
+ // Indices: p0 = segment - 1, p1 = segment, p2 = segment + 1, p3 = segment + 2.
1909
+ // Clamp at boundaries.
1910
+ let i0 = if segment > 0 { segment - 1 } else { 0 };
1911
+ let i1 = segment;
1912
+ let i2 = if segment + 1 < n { segment + 1 } else { n - 1 };
1913
+ let i3 = if segment + 2 < n { segment + 2 } else { n - 1 };
1914
+
1915
+ let p0 = [points[i0 * 3], points[i0 * 3 + 1], points[i0 * 3 + 2]];
1916
+ let p1 = [points[i1 * 3], points[i1 * 3 + 1], points[i1 * 3 + 2]];
1917
+ let p2 = [points[i2 * 3], points[i2 * 3 + 1], points[i2 * 3 + 2]];
1918
+ let p3 = [points[i3 * 3], points[i3 * 3 + 1], points[i3 * 3 + 2]];
1919
+
1920
+ let t2 = t * t;
1921
+ let t3 = t2 * t;
1922
+ let mut out = [0.0f32; 3];
1923
+ for k in 0..3 {
1924
+ out[k] = 0.5 * (
1925
+ (2.0 * p1[k]) +
1926
+ (-p0[k] + p2[k]) * t +
1927
+ (2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k]) * t2 +
1928
+ (-p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k]) * t3
1929
+ );
1930
+ }
1931
+ out
1932
+ }
1933
+
1934
+ fn update_bounds(bmin: &mut [f32; 3], bmax: &mut [f32; 3], x: f32, y: f32, z: f32) {
1935
+ if x < bmin[0] { bmin[0] = x; }
1936
+ if y < bmin[1] { bmin[1] = y; }
1937
+ if z < bmin[2] { bmin[2] = z; }
1938
+ if x > bmax[0] { bmax[0] = x; }
1939
+ if y > bmax[1] { bmax[1] = y; }
1940
+ if z > bmax[2] { bmax[2] = z; }
1941
+ }