@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,3179 @@
1
+ use bloom_shared::engine::EngineState;
2
+ use bloom_shared::renderer::Renderer;
3
+ use bloom_shared::string_header::str_from_header;
4
+ use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3, SoundData};
5
+
6
+ use objc2::encode::{Encode, Encoding, RefEncode};
7
+ use objc2::rc::{Allocated, Retained};
8
+ use objc2::runtime::{AnyClass, AnyObject, Bool, Sel};
9
+ use objc2::{msg_send, sel};
10
+
11
+ use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
12
+
13
+ use std::ffi::c_void;
14
+ use std::sync::OnceLock;
15
+
16
+ // ============================================================
17
+ // objc_msg_lookup shim for the `objc` v0.2 crate
18
+ // ============================================================
19
+ // The `objc` v0.2 crate (used by `metal` via wgpu-hal) only recognizes
20
+ // macOS and iOS as Apple platforms. On tvOS it falls through to the GNUstep
21
+ // codepath which expects `objc_msg_lookup`. We shim it to return
22
+ // `objc_msgSend`, which on arm64 is the universal message dispatcher.
23
+ extern "C" {
24
+ fn objc_msgSend();
25
+ fn objc_msgSendSuper();
26
+ }
27
+
28
+ #[repr(C)]
29
+ struct ObjcSuper {
30
+ receiver: *mut c_void,
31
+ super_class: *const c_void,
32
+ }
33
+
34
+ #[no_mangle]
35
+ pub unsafe extern "C" fn objc_msg_lookup(
36
+ _receiver: *mut c_void, _sel: *const c_void,
37
+ ) -> unsafe extern "C" fn() {
38
+ objc_msgSend
39
+ }
40
+
41
+ #[no_mangle]
42
+ pub unsafe extern "C" fn objc_msg_lookup_super(
43
+ _sup: *const ObjcSuper, _sel: *const c_void,
44
+ ) -> unsafe extern "C" fn() {
45
+ objc_msgSendSuper
46
+ }
47
+
48
+ static mut ENGINE: OnceLock<EngineState> = OnceLock::new();
49
+ static mut UI_WINDOW: Option<Retained<AnyObject>> = None;
50
+ static mut UI_VIEW: Option<Retained<AnyObject>> = None;
51
+ static mut TOUCH_MAP: [*const c_void; 10] = [std::ptr::null(); 10];
52
+ static mut BUNDLE_PATH: Option<String> = None;
53
+ static mut SCREEN_SCALE: f64 = 1.0;
54
+ static SCENE_PTR: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
55
+
56
+ // Atomic key event buffer: main thread writes, game thread reads before begin_frame
57
+ // Bit layout: bits 0-511 = key down, bits 512-1023 = key up (pending)
58
+ static PENDING_KEY_DOWN: [std::sync::atomic::AtomicU64; 8] = {
59
+ const INIT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
60
+ [INIT; 8] // 8 * 64 = 512 bits
61
+ };
62
+ static PENDING_KEY_UP: [std::sync::atomic::AtomicU64; 8] = {
63
+ const INIT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
64
+ [INIT; 8]
65
+ };
66
+
67
+ fn pending_key_down(key: usize) {
68
+ if key < 512 {
69
+ PENDING_KEY_DOWN[key / 64].fetch_or(1u64 << (key % 64), std::sync::atomic::Ordering::Release);
70
+ }
71
+ }
72
+ fn pending_key_up(key: usize) {
73
+ if key < 512 {
74
+ PENDING_KEY_UP[key / 64].fetch_or(1u64 << (key % 64), std::sync::atomic::Ordering::Release);
75
+ }
76
+ }
77
+ fn drain_pending_keys(eng: &mut EngineState) {
78
+ for i in 0..8 {
79
+ let down = PENDING_KEY_DOWN[i].swap(0, std::sync::atomic::Ordering::Acquire);
80
+ let up = PENDING_KEY_UP[i].swap(0, std::sync::atomic::Ordering::Acquire);
81
+ if down != 0 || up != 0 {
82
+ static DRAIN_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
83
+ let n = DRAIN_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
84
+ if n < 20 {
85
+ std::io::Write::write_all(&mut std::io::stderr(),
86
+ format!("[bloom-tvos] DRAIN: down=0x{:x} up=0x{:x} bucket={}\n", down, up, i).as_bytes()).ok();
87
+ }
88
+ for bit in 0..64 {
89
+ let key = i * 64 + bit;
90
+ let is_down = down & (1u64 << bit) != 0;
91
+ let is_up = up & (1u64 << bit) != 0;
92
+ if is_down && is_up {
93
+ // Both pressed and released in same frame — register as down now,
94
+ // re-queue the up for next frame
95
+ eng.input.set_key_down(key);
96
+ pending_key_up(key);
97
+ } else if is_down {
98
+ eng.input.set_key_down(key);
99
+ } else if is_up {
100
+ eng.input.set_key_up(key);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ #[no_mangle]
107
+ static SCREEN_DIMS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
108
+
109
+
110
+ /// Resolve a relative asset path to the app bundle path.
111
+ fn resolve_path(path: &str) -> String {
112
+ if path.starts_with('/') {
113
+ return path.to_string();
114
+ }
115
+ unsafe {
116
+ if let Some(ref base) = BUNDLE_PATH {
117
+ format!("{}/{}", base, path)
118
+ } else {
119
+ path.to_string()
120
+ }
121
+ }
122
+ }
123
+
124
+ fn engine() -> &'static mut EngineState {
125
+ unsafe { ENGINE.get_mut().expect("Engine not initialized") }
126
+ }
127
+
128
+ // ============================================================
129
+ // CG types with objc2 Encode
130
+ // ============================================================
131
+
132
+ #[repr(C)]
133
+ #[derive(Copy, Clone)]
134
+ struct CGPoint { x: f64, y: f64 }
135
+
136
+ unsafe impl Encode for CGPoint {
137
+ const ENCODING: Encoding = Encoding::Struct("CGPoint", &[Encoding::Double, Encoding::Double]);
138
+ }
139
+ unsafe impl RefEncode for CGPoint {
140
+ const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
141
+ }
142
+
143
+ #[repr(C)]
144
+ #[derive(Copy, Clone)]
145
+ struct CGSize { width: f64, height: f64 }
146
+
147
+ unsafe impl Encode for CGSize {
148
+ const ENCODING: Encoding = Encoding::Struct("CGSize", &[Encoding::Double, Encoding::Double]);
149
+ }
150
+ unsafe impl RefEncode for CGSize {
151
+ const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
152
+ }
153
+
154
+ #[repr(C)]
155
+ #[derive(Copy, Clone)]
156
+ struct CGRect { origin: CGPoint, size: CGSize }
157
+
158
+ unsafe impl Encode for CGRect {
159
+ const ENCODING: Encoding = Encoding::Struct("CGRect", &[CGPoint::ENCODING, CGSize::ENCODING]);
160
+ }
161
+ unsafe impl RefEncode for CGRect {
162
+ const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
163
+ }
164
+
165
+ // ============================================================
166
+ // ObjC runtime
167
+ // ============================================================
168
+
169
+ extern "C" {
170
+ fn objc_allocateClassPair(superclass: *const AnyClass, name: *const u8, extra_bytes: usize) -> *mut AnyClass;
171
+ fn objc_registerClassPair(cls: *mut AnyClass);
172
+ fn class_addMethod(cls: *mut AnyClass, sel: Sel, imp: *const c_void, types: *const u8) -> bool;
173
+
174
+ fn CFRunLoopRunInMode(mode: *const c_void, seconds: f64, return_after: u8) -> i32;
175
+ static kCFRunLoopDefaultMode: *const c_void;
176
+ }
177
+
178
+ fn pump_run_loop(seconds: f64) {
179
+ unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 0); }
180
+ }
181
+
182
+ // ============================================================
183
+ // Register BloomMetalView — UIView subclass with +layerClass = CAMetalLayer
184
+ // ============================================================
185
+
186
+ unsafe extern "C" fn bloom_layer_class(_cls: *const c_void, _sel: Sel) -> *const c_void {
187
+ AnyClass::get(c"CAMetalLayer").unwrap() as *const AnyClass as *const c_void
188
+ }
189
+
190
+ unsafe extern "C" fn bloom_touches_began(_this: *mut c_void, _sel: Sel, touches: *const AnyObject, _event: *const AnyObject) {
191
+ handle_touches(touches, TouchPhase::Began);
192
+ }
193
+
194
+ unsafe extern "C" fn bloom_touches_moved(_this: *mut c_void, _sel: Sel, touches: *const AnyObject, _event: *const AnyObject) {
195
+ handle_touches(touches, TouchPhase::Moved);
196
+ }
197
+
198
+ unsafe extern "C" fn bloom_touches_ended(_this: *mut c_void, _sel: Sel, touches: *const AnyObject, _event: *const AnyObject) {
199
+ handle_touches(touches, TouchPhase::Ended);
200
+ }
201
+
202
+ unsafe extern "C" fn bloom_touches_cancelled(_this: *mut c_void, _sel: Sel, touches: *const AnyObject, _event: *const AnyObject) {
203
+ handle_touches(touches, TouchPhase::Ended);
204
+ }
205
+
206
+ enum TouchPhase { Began, Moved, Ended }
207
+
208
+ unsafe fn handle_touches(touches: *const AnyObject, phase: TouchPhase) {
209
+ if touches.is_null() { return; }
210
+
211
+ let view_ptr: *const AnyObject = match UI_VIEW.as_ref() {
212
+ Some(v) => Retained::as_ptr(v),
213
+ None => std::ptr::null(),
214
+ };
215
+
216
+ let enumerator: Retained<AnyObject> = msg_send![&*touches, objectEnumerator];
217
+ loop {
218
+ let touch: *const AnyObject = msg_send![&*enumerator, nextObject];
219
+ if touch.is_null() { break; }
220
+
221
+ let touch_id = touch as *const c_void;
222
+ let loc: CGPoint = msg_send![&*touch, locationInView: view_ptr];
223
+
224
+ let index = match phase {
225
+ TouchPhase::Began => {
226
+ let mut slot = None;
227
+ for i in 0..10 {
228
+ if TOUCH_MAP[i].is_null() {
229
+ TOUCH_MAP[i] = touch_id;
230
+ slot = Some(i);
231
+ break;
232
+ }
233
+ }
234
+ match slot {
235
+ Some(i) => i,
236
+ None => continue,
237
+ }
238
+ }
239
+ TouchPhase::Moved => {
240
+ match TOUCH_MAP.iter().position(|&p| p == touch_id) {
241
+ Some(i) => i,
242
+ None => continue,
243
+ }
244
+ }
245
+ TouchPhase::Ended => {
246
+ match TOUCH_MAP.iter().position(|&p| p == touch_id) {
247
+ Some(i) => {
248
+ TOUCH_MAP[i] = std::ptr::null();
249
+ i
250
+ }
251
+ None => continue,
252
+ }
253
+ }
254
+ };
255
+
256
+ if let Some(eng) = ENGINE.get_mut() {
257
+ let active = !matches!(phase, TouchPhase::Ended);
258
+ // Scale touch from points to pixels to match getScreenWidth/Height
259
+ let sx = loc.x * SCREEN_SCALE;
260
+ let sy = loc.y * SCREEN_SCALE;
261
+ eng.input.set_touch(index, sx, sy, active);
262
+
263
+ if index == 0 {
264
+ eng.input.set_mouse_position(sx, sy);
265
+ if active {
266
+ eng.input.set_mouse_button_down(0);
267
+ } else {
268
+ eng.input.set_mouse_button_up(0);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ // tvOS: allow the metal view to become focused (required for remote events)
276
+ unsafe extern "C" fn bloom_can_become_focused(_this: *mut c_void, _sel: Sel) -> Bool {
277
+ Bool::YES
278
+ }
279
+
280
+ // tvOS: handle Siri Remote / game controller press events
281
+ // UIPressType values: 0=UpArrow, 1=DownArrow, 2=LeftArrow, 3=RightArrow,
282
+ // 4=Select, 5=Menu, 6=PlayPause
283
+ unsafe extern "C" fn bloom_presses_began(_this: *mut c_void, _sel: Sel, presses: *const AnyObject, _event: *const AnyObject) {
284
+ // Log to file since stderr doesn't always capture
285
+ static PRESS_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
286
+ let n = PRESS_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
287
+ let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/bloom_press_log.txt")
288
+ .and_then(|mut f| std::io::Write::write_all(&mut f, format!("pressesBegan #{}\n", n).as_bytes()));
289
+ handle_presses(presses, true);
290
+ }
291
+
292
+ unsafe extern "C" fn bloom_presses_ended(_this: *mut c_void, _sel: Sel, presses: *const AnyObject, _event: *const AnyObject) {
293
+ handle_presses(presses, false);
294
+ }
295
+
296
+ unsafe fn handle_presses(presses: *const AnyObject, down: bool) {
297
+ if presses.is_null() { return; }
298
+
299
+ extern "C" { fn objc_msgSend(); }
300
+ let send_ptr: unsafe extern "C" fn(*const AnyObject, Sel) -> *const AnyObject =
301
+ std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
302
+ let send_i64: unsafe extern "C" fn(*const AnyObject, Sel) -> i64 =
303
+ std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
304
+
305
+ let enumerator = send_ptr(presses, sel!(objectEnumerator));
306
+ if enumerator.is_null() { return; }
307
+ loop {
308
+ let press = send_ptr(enumerator, sel!(nextObject));
309
+ if press.is_null() { break; }
310
+
311
+ let press_type = send_i64(press, Sel::register(c"type"));
312
+ // Map press types to Bloom Key codes
313
+ // Map press types to Bloom Key codes
314
+ // Remote: 0=Up 1=Down 2=Left 3=Right 4=Select 5=Menu 6=PlayPause
315
+ // Keyboard: 2040=Enter 2041=Escape 2044=Space
316
+ // 2079=Right 2080=Left 2081=Down 2082=Up
317
+ // 2000+HID for other keys
318
+ let key = match press_type {
319
+ 0 => Some(256), 1 => Some(257), 2 => Some(258), 3 => Some(259),
320
+ 4 => Some(265), 5 => Some(27), 6 => Some(27),
321
+ 2040 => Some(265), // Enter
322
+ 2041 => Some(27), // Escape
323
+ 2044 => Some(32), // Space
324
+ 2080 => Some(258), // Left arrow
325
+ 2079 => Some(259), // Right arrow
326
+ 2081 => Some(257), // Down arrow
327
+ 2082 => Some(256), // Up arrow
328
+ _ => None,
329
+ };
330
+ if let Some(k) = key {
331
+ if down { pending_key_down(k); } else { pending_key_up(k); }
332
+ } else {
333
+ static UNK_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
334
+ if UNK_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed) < 30 {
335
+ std::io::Write::write_all(&mut std::io::stderr(),
336
+ format!("[bloom-tvos] UNKNOWN press type={} down={}\n", press_type, down).as_bytes()).ok();
337
+ }
338
+ }
339
+ // Enter/Select also triggers Space (for jump)
340
+ if press_type == 4 || press_type == 2040 || press_type == 2044 {
341
+ if down { pending_key_down(32); } else { pending_key_up(32); }
342
+ }
343
+ }
344
+ }
345
+
346
+ /// Returns an NSArray containing just the VC's view, so the focus system focuses it.
347
+ unsafe extern "C" fn bloom_vc_preferred_focus(this: *mut c_void, _sel: Sel) -> *const AnyObject {
348
+ let view: Retained<AnyObject> = msg_send![&*(this as *const AnyObject), view];
349
+ let arr_cls = AnyClass::get(c"NSArray").unwrap();
350
+ let arr: Retained<AnyObject> = msg_send![arr_cls, arrayWithObject: &*view];
351
+ let ptr = Retained::as_ptr(&arr);
352
+ std::mem::forget(arr);
353
+ ptr
354
+ }
355
+
356
+ static ORIG_SEND_EVENT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
357
+
358
+ /// BloomApplication.sendEvent: override — intercepts ALL events at the app level
359
+ unsafe extern "C" fn bloom_app_send_event(this: *mut c_void, _sel: Sel, event: *const AnyObject) {
360
+ let event_type: i64 = msg_send![&*event, type];
361
+ static APP_EVENT_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
362
+ let count = APP_EVENT_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
363
+ if count < 100 {
364
+ let subtype: i64 = msg_send![&*event, subtype];
365
+ std::io::Write::write_all(&mut std::io::stderr(),
366
+ format!("[bloom-tvos] APP sendEvent #{} type={} subtype={}\n", count, event_type, subtype).as_bytes()).ok();
367
+ }
368
+ // Let ALL events flow through to the responder chain (UIWindow → VC).
369
+ // BloomViewController's pressesBegan handles press events.
370
+ // BloomWindow's sendEvent handles keyboard events (type 4) by eating them.
371
+
372
+ // For non-press/keyboard events, call super
373
+ extern "C" { fn objc_msgSendSuper(); }
374
+ #[repr(C)]
375
+ struct ObjcSuperCall { receiver: *mut c_void, super_class: *const c_void }
376
+ let superclass = AnyClass::get(c"UIApplication").unwrap();
377
+ let sup = ObjcSuperCall { receiver: this, super_class: superclass as *const AnyClass as *const c_void };
378
+ let send_super: unsafe extern "C" fn(*const ObjcSuperCall, Sel, *const AnyObject) = std::mem::transmute(objc_msgSendSuper as unsafe extern "C" fn());
379
+ send_super(&sup, _sel, event);
380
+ }
381
+
382
+ fn register_bloom_application_class() {
383
+ if AnyClass::get(c"BloomApplication").is_some() { return; }
384
+
385
+ unsafe {
386
+ let superclass = AnyClass::get(c"UIApplication").unwrap();
387
+ let cls = objc_allocateClassPair(superclass as *const AnyClass, b"BloomApplication\0".as_ptr(), 0);
388
+ if cls.is_null() { return; }
389
+
390
+ class_addMethod(cls, sel!(sendEvent:), bloom_app_send_event as *const c_void, b"v24@0:8@16\0".as_ptr());
391
+
392
+ objc_registerClassPair(cls);
393
+ }
394
+ }
395
+
396
+ /// Window-level sendEvent: override. Intercepts ALL events (keyboard type 4, presses type 3)
397
+ /// and maps them to Bloom key input. Eats keyboard/press events to prevent system dismissal.
398
+ unsafe extern "C" fn bloom_window_send_event(this: *mut c_void, sel: Sel, event: *const AnyObject) {
399
+ let event_type: i64 = msg_send![&*event, type];
400
+
401
+ // Type 3 = UIPresses, Type 4 = keyboard events from simulator
402
+ if event_type == 3 || event_type == 4 {
403
+ static WIN_EVENT_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
404
+ let count = WIN_EVENT_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
405
+ if count < 10 {
406
+ std::io::Write::write_all(&mut std::io::stderr(),
407
+ format!("[bloom-tvos] window sendEvent type={}\n", event_type).as_bytes()).ok();
408
+ }
409
+
410
+ // For press events, extract key info
411
+ if event_type == 3 {
412
+ let all_presses: *const AnyObject = msg_send![&*event, allPresses];
413
+ if !all_presses.is_null() {
414
+ let enumerator: Retained<AnyObject> = msg_send![&*all_presses, objectEnumerator];
415
+ loop {
416
+ let press: *const AnyObject = msg_send![&*enumerator, nextObject];
417
+ if press.is_null() { break; }
418
+ let phase: i64 = msg_send![&*press, phase];
419
+ let press_type: i64 = {
420
+ let sel_type = Sel::register(c"type");
421
+ let send_i64: unsafe extern "C" fn(*const AnyObject, Sel) -> i64 =
422
+ std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
423
+ send_i64(press, sel_type)
424
+ };
425
+ let down = phase == 0;
426
+ let up = phase == 3 || phase == 4;
427
+ let key = match press_type {
428
+ 0 => Some(256), 1 => Some(257), 2 => Some(258), 3 => Some(259),
429
+ 4 => Some(265), 5 => Some(27), 6 => Some(27),
430
+ 2040 => Some(265), 2041 => Some(27), 2044 => Some(32),
431
+ 2080 => Some(258), 2079 => Some(259), 2081 => Some(257), 2082 => Some(256),
432
+ _ => None,
433
+ };
434
+ if let Some(k) = key {
435
+ if down { pending_key_down(k); }
436
+ if up { pending_key_up(k); }
437
+ }
438
+ if press_type == 4 {
439
+ if down { pending_key_down(32); }
440
+ if up { pending_key_up(32); }
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ // For keyboard events (type 4), call super to dispatch through the
447
+ // responder chain. BloomViewController.pressesBegan will handle the presses
448
+ // and NOT call super, which prevents the system from dismissing the app.
449
+ // (This only works now that the r#type selector bug is fixed.)
450
+ // For type 4 keyboard events, try multiple selectors to extract key info
451
+ if event_type == 4 {
452
+ // Try _key to get the UIKey object
453
+ let responds_key: Bool = msg_send![&*event, respondsToSelector: sel!(_key)];
454
+ if responds_key.as_bool() {
455
+ let key_obj: *const AnyObject = msg_send![&*event, _key];
456
+ if !key_obj.is_null() {
457
+ let responds_keycode: Bool = msg_send![&*key_obj, respondsToSelector: sel!(keyCode)];
458
+ if responds_keycode.as_bool() {
459
+ let keycode: i64 = msg_send![&*key_obj, keyCode];
460
+ let is_down: Bool = msg_send![&*event, _isKeyDown];
461
+ let bloom_key = match keycode {
462
+ 79 => Some(259), 80 => Some(258), 81 => Some(257), 82 => Some(256),
463
+ 40 => Some(265), 41 => Some(27), 44 => Some(32), _ => None,
464
+ };
465
+ if let Some(k) = bloom_key {
466
+ if is_down.as_bool() { pending_key_down(k); }
467
+ else { pending_key_up(k); }
468
+ }
469
+ if keycode == 40 || keycode == 44 {
470
+ if is_down.as_bool() { pending_key_down(32); }
471
+ else { pending_key_up(32); }
472
+ }
473
+ }
474
+ }
475
+ }
476
+ // If _key worked, we're done. If not, fall through to call super
477
+ // so the responder chain (BloomViewController.pressesBegan) handles it.
478
+ if responds_key.as_bool() { return; }
479
+ // Call super for type 4 — dispatches to VC's pressesBegan
480
+ extern "C" { fn objc_msgSendSuper(); }
481
+ #[repr(C)]
482
+ struct Sup2 { receiver: *mut c_void, super_class: *const c_void }
483
+ let sc2 = AnyClass::get(c"UIWindow").unwrap();
484
+ let s2 = Sup2 { receiver: this, super_class: sc2 as *const AnyClass as *const c_void };
485
+ let f2: unsafe extern "C" fn(*const Sup2, Sel, *const AnyObject) =
486
+ std::mem::transmute(objc_msgSendSuper as unsafe extern "C" fn());
487
+ f2(&s2, sel, event);
488
+ return;
489
+ }
490
+
491
+ if false {
492
+ let all_presses: *const AnyObject = msg_send![&*event, allPresses];
493
+ if !all_presses.is_null() {
494
+ let enumerator: Retained<AnyObject> = msg_send![&*all_presses, objectEnumerator];
495
+ loop {
496
+ let press: *const AnyObject = msg_send![&*enumerator, nextObject];
497
+ if press.is_null() { break; }
498
+ let phase: i64 = msg_send![&*press, phase];
499
+ let press_type: i64 = {
500
+ let sel_type = Sel::register(c"type");
501
+ let send_i64: unsafe extern "C" fn(*const AnyObject, Sel) -> i64 =
502
+ std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
503
+ send_i64(press, sel_type)
504
+ };
505
+ let eng_ptr = ENGINE.get().map(|e| e as *const EngineState as *mut EngineState);
506
+ if let Some(eng) = eng_ptr.map(|p| &mut *p) {
507
+ let down = phase == 0;
508
+ let up = phase == 3 || phase == 4;
509
+ // press_type for keyboard keys are large numbers (e.g. 2227)
510
+ // Map common ones: arrows, enter, escape, space
511
+ let key = match press_type {
512
+ 0 => Some(256), // Up arrow (remote)
513
+ 1 => Some(257), // Down arrow (remote)
514
+ 2 => Some(258), // Left arrow (remote)
515
+ 3 => Some(259), // Right arrow (remote)
516
+ 4 => Some(265), // Select (remote) → Enter
517
+ 5 => Some(27), // Menu (remote) → Escape
518
+ 6 => Some(27), // PlayPause → Escape
519
+ 2227 => Some(265), // Keyboard Enter
520
+ 2233 => Some(27), // Keyboard Escape
521
+ 2232 => Some(32), // Keyboard Space
522
+ 2228 => Some(9), // Keyboard Tab
523
+ 2103 => Some(256), // Keyboard Up
524
+ 2105 => Some(257), // Keyboard Down
525
+ 2104 => Some(258), // Keyboard Left
526
+ 2106 => Some(259), // Keyboard Right
527
+ _ => None,
528
+ };
529
+ if let Some(k) = key {
530
+ if down { eng.input.set_key_down(k); }
531
+ if up { eng.input.set_key_up(k); }
532
+ }
533
+ // Select/Enter also maps to Space for jump
534
+ if press_type == 4 || press_type == 2227 {
535
+ if down { eng.input.set_key_down(32); }
536
+ if up { eng.input.set_key_up(32); }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ return; // Don't call super for type 3 — we extracted keys above
544
+ }
545
+
546
+ // For other events, call super
547
+ extern "C" { fn objc_msgSendSuper(); }
548
+ #[repr(C)]
549
+ struct ObjcSuperCall { receiver: *mut c_void, super_class: *const c_void }
550
+ let superclass = AnyClass::get(c"UIWindow").unwrap();
551
+ let sup = ObjcSuperCall { receiver: this, super_class: superclass as *const AnyClass as *const c_void };
552
+ let send_super: unsafe extern "C" fn(*const ObjcSuperCall, Sel, *const AnyObject) =
553
+ std::mem::transmute(objc_msgSendSuper as unsafe extern "C" fn());
554
+ send_super(&sup, sel, event);
555
+ }
556
+
557
+ fn register_window_class() {
558
+ if AnyClass::get(c"BloomWindow").is_some() { return; }
559
+
560
+ unsafe {
561
+ let superclass = AnyClass::get(c"UIWindow").unwrap();
562
+ let cls = objc_allocateClassPair(superclass as *const AnyClass, b"BloomWindow\0".as_ptr(), 0);
563
+ if cls.is_null() { return; }
564
+
565
+ // Override sendEvent: to intercept ALL events at the window level
566
+ class_addMethod(cls, sel!(sendEvent:), bloom_window_send_event as *const c_void, b"v24@0:8@16\0".as_ptr());
567
+
568
+ // Also override press events for responder chain
569
+ let press_types = b"v32@0:8@16@24\0".as_ptr();
570
+ class_addMethod(cls, sel!(pressesBegan:withEvent:), bloom_presses_began as *const c_void, press_types);
571
+ class_addMethod(cls, sel!(pressesEnded:withEvent:), bloom_presses_ended as *const c_void, press_types);
572
+ class_addMethod(cls, sel!(pressesCancelled:withEvent:), bloom_presses_ended as *const c_void, press_types);
573
+
574
+ objc_registerClassPair(cls);
575
+ }
576
+ }
577
+
578
+ fn register_view_controller_class() {
579
+ if AnyClass::get(c"BloomViewController").is_some() { return; }
580
+
581
+ unsafe {
582
+ // Plain UIViewController subclass — matches what works in the Swift test
583
+ let superclass = AnyClass::get(c"UIViewController").unwrap();
584
+ let cls = objc_allocateClassPair(superclass as *const AnyClass, b"BloomViewController\0".as_ptr(), 0);
585
+ if cls.is_null() { return; }
586
+
587
+ // Override pressesBegan/pressesEnded to capture remote/keyboard events
588
+ let press_types = b"v32@0:8@16@24\0".as_ptr();
589
+ class_addMethod(cls, sel!(pressesBegan:withEvent:), bloom_presses_began as *const c_void, press_types);
590
+ class_addMethod(cls, sel!(pressesEnded:withEvent:), bloom_presses_ended as *const c_void, press_types);
591
+ class_addMethod(cls, sel!(pressesCancelled:withEvent:), bloom_presses_ended as *const c_void, press_types);
592
+
593
+ objc_registerClassPair(cls);
594
+ }
595
+ }
596
+
597
+ fn register_metal_view_class() {
598
+ if AnyClass::get(c"BloomMetalView").is_some() { return; }
599
+
600
+ unsafe {
601
+ let superclass = AnyClass::get(c"UIView").unwrap();
602
+ let cls = objc_allocateClassPair(superclass as *const AnyClass, b"BloomMetalView\0".as_ptr(), 0);
603
+ if cls.is_null() { return; }
604
+
605
+ extern "C" { fn object_getClass(obj: *const c_void) -> *mut AnyClass; }
606
+ let meta = object_getClass(cls as *const c_void);
607
+
608
+ class_addMethod(meta, sel!(layerClass), bloom_layer_class as *const c_void, b"#8@0:8\0".as_ptr());
609
+
610
+ let touch_types = b"v32@0:8@16@24\0".as_ptr();
611
+ class_addMethod(cls, sel!(touchesBegan:withEvent:), bloom_touches_began as *const c_void, touch_types);
612
+ class_addMethod(cls, sel!(touchesMoved:withEvent:), bloom_touches_moved as *const c_void, touch_types);
613
+ class_addMethod(cls, sel!(touchesEnded:withEvent:), bloom_touches_ended as *const c_void, touch_types);
614
+ class_addMethod(cls, sel!(touchesCancelled:withEvent:), bloom_touches_cancelled as *const c_void, touch_types);
615
+
616
+ // tvOS focus engine — view must be focusable to receive remote events
617
+ class_addMethod(cls, sel!(canBecomeFocused), bloom_can_become_focused as *const c_void, b"B8@0:8\0".as_ptr());
618
+ class_addMethod(cls, sel!(canBecomeFirstResponder), bloom_can_become_focused as *const c_void, b"B8@0:8\0".as_ptr());
619
+
620
+ // tvOS press events — Siri Remote physical buttons
621
+ let press_types = b"v32@0:8@16@24\0".as_ptr();
622
+ class_addMethod(cls, sel!(pressesBegan:withEvent:), bloom_presses_began as *const c_void, press_types);
623
+ class_addMethod(cls, sel!(pressesEnded:withEvent:), bloom_presses_ended as *const c_void, press_types);
624
+
625
+ objc_registerClassPair(cls);
626
+ }
627
+ }
628
+
629
+ // ============================================================
630
+ // Scene delegate — creates UIWindow + Metal view + wgpu engine
631
+ // ============================================================
632
+
633
+ unsafe extern "C" fn scene_will_connect(
634
+ _this: *mut c_void,
635
+ _sel: Sel,
636
+ scene: *const AnyObject,
637
+ _session: *const AnyObject,
638
+ _options: *const AnyObject,
639
+ ) {
640
+ let _ = std::fs::write("/tmp/bloom_scene_connect.txt", format!("scene_will_connect called, scene={:?}\n", scene));
641
+ if scene.is_null() { return; }
642
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] scene_will_connect called\n").ok();
643
+
644
+ // Get screen bounds
645
+ let screen_cls = AnyClass::get(c"UIScreen").unwrap();
646
+ let screen: Retained<AnyObject> = msg_send![screen_cls, mainScreen];
647
+ let bounds: CGRect = msg_send![&*screen, bounds];
648
+ let scale: f64 = msg_send![&*screen, scale];
649
+ let pixel_width = (bounds.size.width * scale) as u32;
650
+ let pixel_height = (bounds.size.height * scale) as u32;
651
+ SCREEN_SCALE = scale;
652
+
653
+ // Create BloomWindow (captures press events) attached to the scene
654
+ let window_cls = AnyClass::get(c"BloomWindow").unwrap();
655
+ let window: Allocated<AnyObject> = msg_send![window_cls, alloc];
656
+ let window: Retained<AnyObject> = msg_send![window, initWithWindowScene: scene];
657
+
658
+ // Use GCEventViewController directly — it prevents the system from
659
+ // intercepting remote/keyboard events when controllerUserInteractionEnabled=NO
660
+ let gc_vc_cls = AnyClass::get(c"GCEventViewController");
661
+ std::io::Write::write_all(&mut std::io::stderr(),
662
+ format!("[bloom-tvos] GCEventViewController available: {}\n", gc_vc_cls.is_some()).as_bytes()).ok();
663
+ let vc_cls = gc_vc_cls.unwrap_or_else(|| AnyClass::get(c"UIViewController").unwrap());
664
+ let vc: Allocated<AnyObject> = msg_send![vc_cls, alloc];
665
+ let vc: Retained<AnyObject> = msg_send![vc, init];
666
+ if gc_vc_cls.is_some() {
667
+ let _: () = msg_send![&*vc, setControllerUserInteractionEnabled: Bool::NO];
668
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] controllerUserInteractionEnabled = NO\n").ok();
669
+ }
670
+
671
+ // Create BloomMetalView
672
+ let view_cls = AnyClass::get(c"BloomMetalView").unwrap();
673
+ let view: Allocated<AnyObject> = msg_send![view_cls, alloc];
674
+ let view: Retained<AnyObject> = msg_send![view, initWithFrame: bounds];
675
+
676
+ // Set background to black
677
+ let color_cls = AnyClass::get(c"UIColor").unwrap();
678
+ let black: Retained<AnyObject> = msg_send![color_cls, blackColor];
679
+ let _: () = msg_send![&*view, setBackgroundColor: &*black];
680
+
681
+ // Configure CAMetalLayer - set framebufferOnly=NO for screenshot capture
682
+ let layer: Retained<AnyObject> = msg_send![&*view, layer];
683
+ let drawable_size = CGSize { width: pixel_width as f64, height: pixel_height as f64 };
684
+ let _: () = msg_send![&*layer, setDrawableSize: drawable_size];
685
+ let _: () = msg_send![&*layer, setContentsScale: scale];
686
+ let _: () = msg_send![&*layer, setOpaque: Bool::YES];
687
+ let _: () = msg_send![&*layer, setFramebufferOnly: Bool::NO];
688
+ let _: () = msg_send![&*layer, setPresentsWithTransaction: Bool::YES];
689
+
690
+ // Enable touches & focus
691
+ let _: () = msg_send![&*view, setUserInteractionEnabled: Bool::YES];
692
+ let _: () = msg_send![&*view, setMultipleTouchEnabled: Bool::YES];
693
+
694
+ // Set up window hierarchy
695
+ let _: () = msg_send![&*vc, setView: &*view];
696
+ let _: () = msg_send![&*window, setRootViewController: &*vc];
697
+ let _: () = msg_send![&*window, makeKeyAndVisible];
698
+
699
+ UI_VIEW = Some(view.clone());
700
+ UI_WINDOW = Some(window);
701
+
702
+ // Create wgpu surface and engine on the main thread (like iOS)
703
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
704
+ backends: wgpu::Backends::METAL,
705
+ ..wgpu::InstanceDescriptor::new_without_display_handle()
706
+ });
707
+
708
+ let view_ptr = Retained::as_ptr(&view) as *mut c_void;
709
+ let handle = UiKitWindowHandle::new(
710
+ std::ptr::NonNull::new(view_ptr).unwrap(),
711
+ );
712
+ let raw = RawWindowHandle::UiKit(handle);
713
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] creating wgpu surface\n").ok();
714
+ let surface = match instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
715
+ raw_display_handle: Some(RawDisplayHandle::UiKit(UiKitDisplayHandle::new())),
716
+ raw_window_handle: raw,
717
+ }) {
718
+ Ok(s) => s,
719
+ Err(e) => panic!("[bloom-tvos] Failed to create wgpu surface: {e}"),
720
+ };
721
+
722
+ let adapter = match pollster_block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
723
+ compatible_surface: Some(&surface),
724
+ power_preference: wgpu::PowerPreference::HighPerformance,
725
+ ..Default::default()
726
+ })) {
727
+ Ok(a) => a,
728
+ Err(_) => panic!("[bloom-tvos] No GPU adapter found"),
729
+ };
730
+
731
+ // Ticket 007b: HW ray-query on RT-capable tvOS hardware (A13+).
732
+ let supported = adapter.features();
733
+ let force_sw_gi = std::env::var("BLOOM_FORCE_SW_GI")
734
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
735
+ .unwrap_or(false);
736
+ let rt_mask = wgpu::Features::EXPERIMENTAL_RAY_QUERY;
737
+ let mut required_features = wgpu::Features::empty();
738
+ // Ticket 011: request TIMESTAMP_QUERY when supported so the profiler
739
+ // can record GPU timings. Optional — profiler falls back to CPU-only
740
+ // when the adapter doesn't grant it.
741
+ if supported.contains(wgpu::Features::TIMESTAMP_QUERY) {
742
+ required_features |= wgpu::Features::TIMESTAMP_QUERY;
743
+ }
744
+ if !force_sw_gi && supported.contains(rt_mask) {
745
+ required_features |= rt_mask;
746
+ }
747
+ let experimental_features = if required_features.intersects(rt_mask) {
748
+ unsafe { wgpu::ExperimentalFeatures::enabled() }
749
+ } else {
750
+ wgpu::ExperimentalFeatures::disabled()
751
+ };
752
+ let mut required_limits = wgpu::Limits::default();
753
+ if required_features.intersects(rt_mask) {
754
+ required_limits = required_limits
755
+ .using_minimum_supported_acceleration_structure_values();
756
+ }
757
+ let (device, queue) = match pollster_block_on(adapter.request_device(
758
+ &wgpu::DeviceDescriptor {
759
+ label: Some("bloom_device"),
760
+ required_features,
761
+ required_limits,
762
+ experimental_features,
763
+ ..Default::default()
764
+ },
765
+ )) {
766
+ Ok(dq) => dq,
767
+ Err(e) => panic!("[bloom-tvos] Failed to create device: {e}"),
768
+ };
769
+
770
+ let surface_caps = surface.get_capabilities(&adapter);
771
+ // Use non-sRGB format to match game's sRGB color space (colors are specified as sRGB 0-255)
772
+ let format = surface_caps.formats.iter()
773
+ .find(|f| !f.is_srgb()).copied()
774
+ .unwrap_or(surface_caps.formats[0]);
775
+
776
+ let surface_config = wgpu::SurfaceConfiguration {
777
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
778
+ format,
779
+ width: pixel_width,
780
+ height: pixel_height,
781
+ present_mode: wgpu::PresentMode::Fifo,
782
+ alpha_mode: surface_caps.alpha_modes[0],
783
+ view_formats: vec![],
784
+ desired_maximum_frame_latency: 2,
785
+ };
786
+ surface.configure(&device, &surface_config);
787
+
788
+ let renderer = Renderer::new(device, queue, surface, surface_config, pixel_width, pixel_height);
789
+ let _ = ENGINE.set(EngineState::new(renderer));
790
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] ENGINE created on main thread\n").ok();
791
+ }
792
+
793
+ /// Called by the perry runtime's main() before UIApplicationMain to register
794
+ /// ObjC classes needed for the scene lifecycle.
795
+ #[no_mangle]
796
+ unsafe extern "C" fn configuration_for_connecting_scene(
797
+ _this: *mut c_void, _sel: Sel,
798
+ _app: *const AnyObject,
799
+ scene_session: *const AnyObject,
800
+ _options: *const AnyObject,
801
+ ) -> *const AnyObject {
802
+ let _ = std::fs::write("/tmp/bloom_config_scene.txt", "configurationForConnecting called\n");
803
+ // Get the session's role
804
+ let role: *const AnyObject = msg_send![&*scene_session, role];
805
+ // Create UISceneConfiguration
806
+ let config_cls = AnyClass::get(c"UISceneConfiguration").unwrap();
807
+ let ns_cls = AnyClass::get(c"NSString").unwrap();
808
+ let name_str: Retained<AnyObject> = msg_send![ns_cls, stringWithUTF8String: b"Default Configuration\0".as_ptr()];
809
+ let config: Allocated<AnyObject> = msg_send![config_cls, alloc];
810
+ let config: Retained<AnyObject> = msg_send![config, initWithName: &*name_str sessionRole: role];
811
+ // Use PerryGameLoopAppDelegate as scene delegate (it has scene:willConnectToSession: added)
812
+ let delegate_cls = AnyClass::get(c"PerryGameLoopAppDelegate").unwrap();
813
+ let _: () = msg_send![&*config, setDelegateClass: delegate_cls];
814
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] returning scene config with delegate\n").ok();
815
+ // Leak the config — UIKit retains it. We can't use autorelease pool from extern C.
816
+ let ptr = Retained::as_ptr(&config);
817
+ std::mem::forget(config);
818
+ ptr
819
+ }
820
+
821
+ #[no_mangle]
822
+ pub unsafe extern "C" fn perry_register_native_classes() {
823
+ let _ = std::fs::write("/tmp/bloom_register_classes.txt", "perry_register_native_classes called\n");
824
+ register_bloom_application_class();
825
+ register_metal_view_class();
826
+ register_window_class();
827
+ register_view_controller_class();
828
+ register_scene_delegate();
829
+
830
+ // Add press event handlers AND configurationForConnectingSceneSession to the app delegate
831
+ if let Some(app_delegate_cls) = AnyClass::get(c"PerryGameLoopAppDelegate") {
832
+ let press_types = b"v32@0:8@16@24\0".as_ptr();
833
+ class_addMethod(
834
+ app_delegate_cls as *const AnyClass as *mut AnyClass,
835
+ sel!(pressesBegan:withEvent:),
836
+ bloom_presses_began as *const c_void,
837
+ press_types,
838
+ );
839
+ class_addMethod(
840
+ app_delegate_cls as *const AnyClass as *mut AnyClass,
841
+ sel!(pressesEnded:withEvent:),
842
+ bloom_presses_ended as *const c_void,
843
+ press_types,
844
+ );
845
+ let sel = Sel::register(c"application:configurationForConnectingSceneSession:options:");
846
+ let types = b"@48@0:8@16@24@32\0".as_ptr();
847
+ class_addMethod(
848
+ app_delegate_cls as *const AnyClass as *mut AnyClass,
849
+ sel,
850
+ configuration_for_connecting_scene as *const c_void,
851
+ types,
852
+ );
853
+
854
+ // Also add scene:willConnectToSession:connectionOptions: to the app delegate
855
+ // so it can act as its own scene delegate (PerrySceneDelegate dispatch never fires)
856
+ let scene_sel = Sel::register(c"scene:willConnectToSession:connectionOptions:");
857
+ let scene_types = b"v48@0:8@16@24@32\0".as_ptr();
858
+ class_addMethod(
859
+ app_delegate_cls as *const AnyClass as *mut AnyClass,
860
+ scene_sel,
861
+ scene_will_connect as *const c_void,
862
+ scene_types,
863
+ );
864
+
865
+ // Add UIWindowSceneDelegate protocol to app delegate
866
+ extern "C" { fn objc_getProtocol(name: *const u8) -> *const c_void; }
867
+ extern "C" { fn class_addProtocol(cls: *mut c_void, protocol: *const c_void) -> bool; }
868
+ let protocol = objc_getProtocol(b"UIWindowSceneDelegate\0".as_ptr());
869
+ if !protocol.is_null() {
870
+ class_addProtocol(app_delegate_cls as *const AnyClass as *mut c_void, protocol);
871
+ }
872
+ }
873
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] classes registered, scene delegate ready\n").ok();
874
+ }
875
+
876
+ /// Called by the runtime's scene delegate when the UIWindowScene connects.
877
+ /// This runs on the main thread — safe for all UIKit operations.
878
+ #[no_mangle]
879
+ unsafe extern "C" fn deferred_init(_ctx: *mut c_void) {
880
+ let _ = std::fs::write("/tmp/bloom_deferred_init.txt", "deferred_init called\n");
881
+
882
+ // Find the connected scene
883
+ let app_cls = AnyClass::get(c"UIApplication").unwrap();
884
+ let app: *const AnyObject = msg_send![app_cls, sharedApplication];
885
+ let scenes: *const AnyObject = msg_send![&*app, connectedScenes];
886
+ let count: usize = msg_send![&*scenes, count];
887
+ if count == 0 {
888
+ let _ = std::fs::write("/tmp/bloom_deferred_2.txt", "scenes=0, retrying in 500ms\n");
889
+ // Retry after a delay
890
+ extern "C" {
891
+ static _dispatch_main_q: c_void;
892
+ fn dispatch_after_f(when: u64, queue: *const c_void, context: *mut c_void, work: unsafe extern "C" fn(*mut c_void));
893
+ fn dispatch_time(when: u64, delta: i64) -> u64;
894
+ }
895
+ let when = dispatch_time(0, 500_000_000); // 500ms
896
+ dispatch_after_f(when, &_dispatch_main_q as *const _, std::ptr::null_mut(), deferred_init);
897
+ return;
898
+ }
899
+
900
+ let scene: Retained<AnyObject> = msg_send![&*scenes, anyObject];
901
+ // Log the scene class name for debugging
902
+ extern "C" { fn class_getName(cls: *const c_void) -> *const u8; }
903
+ let scene_class: *const c_void = msg_send![&*scene, class];
904
+ let scene_class_name = std::ffi::CStr::from_ptr(class_getName(scene_class) as *const i8).to_str().unwrap_or("?");
905
+ let scene_state: i64 = msg_send![&*scene, activationState];
906
+ let _ = std::fs::write("/tmp/bloom_deferred_2.txt", format!("scenes={}\nclass={}\nactivationState={}\n", count, scene_class_name, scene_state));
907
+
908
+ // Get screen dimensions
909
+ let screen_cls = AnyClass::get(c"UIScreen").unwrap();
910
+ let screen: Retained<AnyObject> = msg_send![screen_cls, mainScreen];
911
+ let bounds: CGRect = msg_send![&*screen, bounds];
912
+ let scale: f64 = msg_send![&*screen, scale];
913
+ let pixel_width = (bounds.size.width * scale) as u32;
914
+ let pixel_height = (bounds.size.height * scale) as u32;
915
+ SCREEN_SCALE = scale;
916
+
917
+ // Create window WITH the scene (required on tvOS for visibility)
918
+ let window_cls = AnyClass::get(c"BloomWindow").unwrap();
919
+ let w: Allocated<AnyObject> = msg_send![window_cls, alloc];
920
+ let window: Retained<AnyObject> = msg_send![w, initWithWindowScene: &*scene];
921
+
922
+ // Create view controller
923
+ let gc_vc_cls = AnyClass::get(c"GCEventViewController");
924
+ let vc_cls = gc_vc_cls.unwrap_or_else(|| AnyClass::get(c"UIViewController").unwrap());
925
+ let vc: Allocated<AnyObject> = msg_send![vc_cls, alloc];
926
+ let vc: Retained<AnyObject> = msg_send![vc, init];
927
+ if gc_vc_cls.is_some() {
928
+ let _: () = msg_send![&*vc, setControllerUserInteractionEnabled: Bool::NO];
929
+ }
930
+
931
+ // Create BloomMetalView
932
+ let view_cls = AnyClass::get(c"BloomMetalView").unwrap();
933
+ let v: Allocated<AnyObject> = msg_send![view_cls, alloc];
934
+ let view: Retained<AnyObject> = msg_send![v, initWithFrame: bounds];
935
+
936
+ let color_cls = AnyClass::get(c"UIColor").unwrap();
937
+ let black: Retained<AnyObject> = msg_send![color_cls, blackColor];
938
+ let _: () = msg_send![&*view, setBackgroundColor: &*black];
939
+ let _: () = msg_send![&*view, setUserInteractionEnabled: Bool::YES];
940
+
941
+ // Configure CAMetalLayer
942
+ let layer: Retained<AnyObject> = msg_send![&*view, layer];
943
+ let drawable_size = CGSize { width: pixel_width as f64, height: pixel_height as f64 };
944
+ let _: () = msg_send![&*layer, setDrawableSize: drawable_size];
945
+ let _: () = msg_send![&*layer, setContentsScale: scale];
946
+ let _: () = msg_send![&*layer, setOpaque: Bool::YES];
947
+ let _: () = msg_send![&*layer, setFramebufferOnly: Bool::NO];
948
+ let _: () = msg_send![&*layer, setPresentsWithTransaction: Bool::YES];
949
+
950
+ // Set up window hierarchy
951
+ let _: () = msg_send![&*vc, setView: &*view];
952
+ let _: () = msg_send![&*window, setRootViewController: &*vc];
953
+ let _: () = msg_send![&*window, makeKeyAndVisible];
954
+
955
+ // DEBUG: Add a bright UILabel to verify UIKit content is visible
956
+ let label_cls = AnyClass::get(c"UILabel").unwrap();
957
+ let lbl: Allocated<AnyObject> = msg_send![label_cls, alloc];
958
+ let label_frame = CGRect { origin: CGPoint { x: 100.0, y: 100.0 }, size: CGSize { width: 800.0, height: 200.0 } };
959
+ let lbl: Retained<AnyObject> = msg_send![lbl, initWithFrame: label_frame];
960
+ let ns_cls2 = AnyClass::get(c"NSString").unwrap();
961
+ let text: Retained<AnyObject> = msg_send![ns_cls2, stringWithUTF8String: b"BLOOM JUMP TVOS\0".as_ptr()];
962
+ let _: () = msg_send![&*lbl, setText: &*text];
963
+ let white: Retained<AnyObject> = msg_send![color_cls, whiteColor];
964
+ let _: () = msg_send![&*lbl, setTextColor: &*white];
965
+ let red_bg: Retained<AnyObject> = msg_send![color_cls, redColor];
966
+ let _: () = msg_send![&*lbl, setBackgroundColor: &*red_bg];
967
+ let font_cls = AnyClass::get(c"UIFont").unwrap();
968
+ let font: Retained<AnyObject> = msg_send![font_cls, systemFontOfSize: 72.0f64];
969
+ let _: () = msg_send![&*lbl, setFont: &*font];
970
+ let _: () = msg_send![&*window, addSubview: &*lbl];
971
+
972
+ UI_VIEW = Some(view.clone());
973
+ UI_WINDOW = Some(window.clone());
974
+
975
+ // Verify window state
976
+ let is_key: Bool = msg_send![&*window, isKeyWindow];
977
+ let is_hidden: Bool = msg_send![&*window, isHidden];
978
+ let win_scene: *const AnyObject = msg_send![&*window, windowScene];
979
+ let win_frame: CGRect = msg_send![&*window, frame];
980
+ let win_alpha: f64 = msg_send![&*window, alpha];
981
+ let root_vc: *const AnyObject = msg_send![&*window, rootViewController];
982
+ let vc_view: *const AnyObject = if !root_vc.is_null() { msg_send![&*root_vc, view] } else { std::ptr::null() };
983
+
984
+ // Check scene's windows
985
+ let scene_windows: *const AnyObject = msg_send![&*scene, windows];
986
+ let scene_win_count: usize = msg_send![&*scene_windows, count];
987
+
988
+ let debug = format!(
989
+ "window+view created with scene, {}x{}\n\
990
+ isKey={} isHidden={} alpha={}\n\
991
+ windowScene={:?} (expected={:?})\n\
992
+ frame=({},{},{},{})\n\
993
+ rootVC={:?} vcView={:?}\n\
994
+ scene.windows.count={}\n",
995
+ pixel_width, pixel_height,
996
+ is_key.as_bool(), is_hidden.as_bool(), win_alpha,
997
+ win_scene, Retained::as_ptr(&scene),
998
+ win_frame.origin.x, win_frame.origin.y, win_frame.size.width, win_frame.size.height,
999
+ root_vc, vc_view,
1000
+ scene_win_count,
1001
+ );
1002
+ let _ = std::fs::write("/tmp/bloom_deferred_3.txt", &debug);
1003
+
1004
+ // DEBUG: Add UILabel to verify window visibility (non-Metal)
1005
+ let label_cls = AnyClass::get(c"UILabel").unwrap();
1006
+ let lbl: Allocated<AnyObject> = msg_send![label_cls, alloc];
1007
+ let lf = CGRect { origin: CGPoint { x: 200.0, y: 200.0 }, size: CGSize { width: 600.0, height: 100.0 } };
1008
+ let lbl: Retained<AnyObject> = msg_send![lbl, initWithFrame: lf];
1009
+ let ns = AnyClass::get(c"NSString").unwrap();
1010
+ let txt: Retained<AnyObject> = msg_send![ns, stringWithUTF8String: b"BLOOM JUMP TVOS DEBUG\0".as_ptr()];
1011
+ let _: () = msg_send![&*lbl, setText: &*txt];
1012
+ let white_c: Retained<AnyObject> = msg_send![color_cls, whiteColor];
1013
+ let _: () = msg_send![&*lbl, setTextColor: &*white_c];
1014
+ let red_c: Retained<AnyObject> = msg_send![color_cls, redColor];
1015
+ let _: () = msg_send![&*lbl, setBackgroundColor: &*red_c];
1016
+ let font_cls = AnyClass::get(c"UIFont").unwrap();
1017
+ let fnt: Retained<AnyObject> = msg_send![font_cls, boldSystemFontOfSize: 48.0f64];
1018
+ let _: () = msg_send![&*lbl, setFont: &*fnt];
1019
+ let _: () = msg_send![&*window, addSubview: &*lbl];
1020
+
1021
+ // Create wgpu engine using CAMetalLayer
1022
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
1023
+ backends: wgpu::Backends::METAL,
1024
+ ..wgpu::InstanceDescriptor::new_without_display_handle()
1025
+ });
1026
+
1027
+ let layer_ptr = Retained::as_ptr(&layer) as *mut c_void;
1028
+ let surface = instance.create_surface_unsafe(
1029
+ wgpu::SurfaceTargetUnsafe::CoreAnimationLayer(layer_ptr)
1030
+ ).expect("[bloom-tvos] Failed to create wgpu surface");
1031
+
1032
+ let adapter = pollster_block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1033
+ compatible_surface: Some(&surface),
1034
+ power_preference: wgpu::PowerPreference::HighPerformance,
1035
+ ..Default::default()
1036
+ })).expect("[bloom-tvos] No GPU adapter found");
1037
+
1038
+ // Ticket 007b: HW ray-query on RT-capable tvOS hardware (A13+).
1039
+ let supported = adapter.features();
1040
+ let force_sw_gi = std::env::var("BLOOM_FORCE_SW_GI")
1041
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1042
+ .unwrap_or(false);
1043
+ let rt_mask = wgpu::Features::EXPERIMENTAL_RAY_QUERY;
1044
+ let mut required_features = wgpu::Features::empty();
1045
+ // Ticket 011: request TIMESTAMP_QUERY when supported so the profiler
1046
+ // can record GPU timings. Optional — profiler falls back to CPU-only
1047
+ // when the adapter doesn't grant it.
1048
+ if supported.contains(wgpu::Features::TIMESTAMP_QUERY) {
1049
+ required_features |= wgpu::Features::TIMESTAMP_QUERY;
1050
+ }
1051
+ if !force_sw_gi && supported.contains(rt_mask) {
1052
+ required_features |= rt_mask;
1053
+ }
1054
+ let experimental_features = if required_features.intersects(rt_mask) {
1055
+ unsafe { wgpu::ExperimentalFeatures::enabled() }
1056
+ } else {
1057
+ wgpu::ExperimentalFeatures::disabled()
1058
+ };
1059
+ let mut required_limits = wgpu::Limits::default();
1060
+ if required_features.intersects(rt_mask) {
1061
+ required_limits = required_limits
1062
+ .using_minimum_supported_acceleration_structure_values();
1063
+ }
1064
+ let (device, queue) = pollster_block_on(adapter.request_device(
1065
+ &wgpu::DeviceDescriptor {
1066
+ label: Some("bloom_device"),
1067
+ required_features,
1068
+ required_limits,
1069
+ experimental_features,
1070
+ ..Default::default()
1071
+ },
1072
+ )).expect("[bloom-tvos] Failed to create device");
1073
+
1074
+ let surface_caps = surface.get_capabilities(&adapter);
1075
+ let format = surface_caps.formats.iter()
1076
+ .find(|f| !f.is_srgb()).copied()
1077
+ .unwrap_or(surface_caps.formats[0]);
1078
+
1079
+ let surface_config = wgpu::SurfaceConfiguration {
1080
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1081
+ format,
1082
+ width: pixel_width,
1083
+ height: pixel_height,
1084
+ present_mode: wgpu::PresentMode::Fifo,
1085
+ alpha_mode: surface_caps.alpha_modes[0],
1086
+ view_formats: vec![],
1087
+ desired_maximum_frame_latency: 2,
1088
+ };
1089
+ surface.configure(&device, &surface_config);
1090
+
1091
+ let renderer = Renderer::new(device, queue, surface, surface_config, pixel_width, pixel_height);
1092
+ let _ = ENGINE.set(EngineState::new(renderer));
1093
+ let _ = std::fs::write("/tmp/bloom_tvos_debug.txt", format!("ENGINE created\nformat={:?}\nsize={}x{}\n", format, pixel_width, pixel_height));
1094
+ }
1095
+
1096
+ #[no_mangle]
1097
+ pub unsafe extern "C" fn perry_scene_will_connect(scene: *const c_void) {
1098
+ std::io::Write::write_all(&mut std::io::stderr(), format!("[bloom-tvos] perry_scene_will_connect scene={:?}\n", scene).as_bytes()).ok();
1099
+ // When scene is null (called from didFinishLaunchingWithOptions), create the
1100
+ // window synchronously so UIKit knows we handle events. Then dispatch async
1101
+ // to attach to the scene (needed for Metal rendering).
1102
+ if scene.is_null() {
1103
+ // On tvOS, windows MUST be attached to a UIWindowScene to be visible.
1104
+ // Dispatch deferred_init with a delay so the scene is fully connected.
1105
+ extern "C" {
1106
+ static _dispatch_main_q: c_void;
1107
+ fn dispatch_after_f(when: u64, queue: *const c_void, context: *mut c_void, work: unsafe extern "C" fn(*mut c_void));
1108
+ fn dispatch_time(when: u64, delta: i64) -> u64;
1109
+ }
1110
+ let when = dispatch_time(0, 500_000_000); // 500ms delay
1111
+ dispatch_after_f(when, &_dispatch_main_q as *const _, std::ptr::null_mut(), deferred_init);
1112
+ return;
1113
+ }
1114
+
1115
+ let screen_cls = AnyClass::get(c"UIScreen").expect("[bloom-tvos] UIScreen class not found");
1116
+ let screen: Retained<AnyObject> = msg_send![screen_cls, mainScreen];
1117
+ let bounds: CGRect = msg_send![&*screen, bounds];
1118
+ let scale: f64 = msg_send![&*screen, scale];
1119
+ eprintln!("[bloom-tvos] screen bounds: {}x{}, scale={}", bounds.size.width, bounds.size.height, scale);
1120
+
1121
+ let pixel_width = (bounds.size.width * scale) as u32;
1122
+ let pixel_height = (bounds.size.height * scale) as u32;
1123
+
1124
+ // Store scale for touch coordinate conversion (points → pixels)
1125
+ SCREEN_SCALE = scale;
1126
+
1127
+ // Create BloomWindow — attached to scene if available, otherwise plain
1128
+ let window_cls = AnyClass::get(c"BloomWindow").unwrap();
1129
+ let window: Retained<AnyObject> = if !scene.is_null() {
1130
+ let w: Allocated<AnyObject> = msg_send![window_cls, alloc];
1131
+ msg_send![w, initWithWindowScene: scene as *const AnyObject]
1132
+ } else {
1133
+ let w: Allocated<AnyObject> = msg_send![window_cls, alloc];
1134
+ msg_send![w, initWithFrame: bounds]
1135
+ };
1136
+
1137
+ // Use GCEventViewController to prevent system from intercepting remote events
1138
+ let gc_vc_cls = AnyClass::get(c"GCEventViewController");
1139
+ std::io::Write::write_all(&mut std::io::stderr(),
1140
+ format!("[bloom-tvos] GCEventViewController available: {}\n", gc_vc_cls.is_some()).as_bytes()).ok();
1141
+ let vc_cls = gc_vc_cls.unwrap_or_else(|| AnyClass::get(c"UIViewController").unwrap());
1142
+ let vc: Allocated<AnyObject> = msg_send![vc_cls, alloc];
1143
+ let vc: Retained<AnyObject> = msg_send![vc, init];
1144
+ if gc_vc_cls.is_some() {
1145
+ let _: () = msg_send![&*vc, setControllerUserInteractionEnabled: Bool::NO];
1146
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] controllerUserInteractionEnabled = NO\n").ok();
1147
+ }
1148
+
1149
+ // Create BloomMetalView
1150
+ eprintln!("[bloom-tvos] creating BloomMetalView");
1151
+ let view_cls = AnyClass::get(c"BloomMetalView").expect("[bloom-tvos] BloomMetalView class not found");
1152
+ let view: Allocated<AnyObject> = msg_send![view_cls, alloc];
1153
+ let view: Retained<AnyObject> = msg_send![view, initWithFrame: bounds];
1154
+
1155
+ // Set background to black
1156
+ let color_cls = AnyClass::get(c"UIColor").unwrap();
1157
+ let black: Retained<AnyObject> = msg_send![color_cls, blackColor];
1158
+ let _: () = msg_send![&*view, setBackgroundColor: &*black];
1159
+
1160
+ // Configure CAMetalLayer
1161
+ let layer: Retained<AnyObject> = msg_send![&*view, layer];
1162
+ let drawable_size = CGSize { width: pixel_width as f64, height: pixel_height as f64 };
1163
+ let _: () = msg_send![&*layer, setDrawableSize: drawable_size];
1164
+ let _: () = msg_send![&*layer, setContentsScale: scale];
1165
+ let _: () = msg_send![&*layer, setOpaque: Bool::YES];
1166
+
1167
+ // Enable touches
1168
+ let _: () = msg_send![&*view, setUserInteractionEnabled: Bool::YES];
1169
+ let _: () = msg_send![&*view, setMultipleTouchEnabled: Bool::YES];
1170
+
1171
+ // Set up window hierarchy
1172
+ let _: () = msg_send![&*vc, setView: &*view];
1173
+ let _: () = msg_send![&*window, setRootViewController: &*vc];
1174
+ let _: () = msg_send![&*window, makeKeyAndVisible];
1175
+
1176
+ // Store references
1177
+ UI_VIEW = Some(view.clone());
1178
+ UI_WINDOW = Some(window);
1179
+ // Add a transparent focusable button so the tvOS focus engine has something to focus
1180
+ // Without a focused element, tvOS suspends the app on any remote button press
1181
+ let btn_cls = AnyClass::get(c"UIButton").unwrap();
1182
+ let btn: Retained<AnyObject> = msg_send![btn_cls, buttonWithType: 0i64]; // UIButtonTypeCustom
1183
+ let _: () = msg_send![&*btn, setFrame: bounds];
1184
+ let _: () = msg_send![&*btn, setAlpha: 0.0f64]; // fully transparent
1185
+ let _: () = msg_send![&*view, addSubview: &*btn];
1186
+
1187
+ // Add a menu gesture recognizer to prevent the system from dismissing on Menu press
1188
+ let tap_cls = AnyClass::get(c"UITapGestureRecognizer").unwrap();
1189
+ let menu_tap: Allocated<AnyObject> = msg_send![tap_cls, alloc];
1190
+ let menu_tap: Retained<AnyObject> = msg_send![menu_tap, initWithTarget: std::ptr::null::<AnyObject>() action: std::ptr::null::<c_void>()];
1191
+ // allowedPressTypes = @[@(UIPressTypeMenu)] = @[@5]
1192
+ let num_cls = AnyClass::get(c"NSNumber").unwrap();
1193
+ let menu_num: Retained<AnyObject> = msg_send![num_cls, numberWithInteger: 5i64];
1194
+ let arr_cls = AnyClass::get(c"NSArray").unwrap();
1195
+ let press_types_arr: Retained<AnyObject> = msg_send![arr_cls, arrayWithObject: &*menu_num];
1196
+ let _: () = msg_send![&*menu_tap, setAllowedPressTypes: &*press_types_arr];
1197
+ let _: () = msg_send![&*view, addGestureRecognizer: &*menu_tap];
1198
+
1199
+ // Trigger focus
1200
+ let _: () = msg_send![&*vc, setNeedsFocusUpdate];
1201
+ let _: () = msg_send![&*vc, updateFocusIfNeeded];
1202
+ let _: () = msg_send![&*view, becomeFirstResponder];
1203
+ let is_focused: Bool = msg_send![&*view, isFocused];
1204
+ std::io::Write::write_all(&mut std::io::stderr(),
1205
+ format!("[bloom-tvos] view.isFocused={}\n", is_focused.as_bool()).as_bytes()).ok();
1206
+ // Verify GCEventViewController state
1207
+ {
1208
+ extern "C" { fn class_getName(cls: *const c_void) -> *const u8; }
1209
+ let vc_class: *const c_void = msg_send![&*vc, class];
1210
+ let vc_name = std::ffi::CStr::from_ptr(class_getName(vc_class) as *const i8).to_str().unwrap_or("?");
1211
+ // Check if responds to controllerUserInteractionEnabled
1212
+ let responds: Bool = msg_send![&*vc, respondsToSelector: sel!(controllerUserInteractionEnabled)];
1213
+ let ctrl_ui: Bool = if responds.as_bool() { msg_send![&*vc, controllerUserInteractionEnabled] } else { Bool::NO };
1214
+ std::io::Write::write_all(&mut std::io::stderr(),
1215
+ format!("[bloom-tvos] rootVC class={}, respondsToControllerUI={}, controllerUserInteractionEnabled={}\n",
1216
+ vc_name, responds.as_bool(), ctrl_ui.as_bool()).as_bytes()).ok();
1217
+ }
1218
+ // Check window state
1219
+ {
1220
+ let app_cls2 = AnyClass::get(c"UIApplication").unwrap();
1221
+ let app2: *const AnyObject = msg_send![app_cls2, sharedApplication];
1222
+ let key_win: *const AnyObject = msg_send![&*app2, keyWindow];
1223
+ let is_key = UI_WINDOW.as_ref().map(|w| Retained::as_ptr(w) as *const AnyObject == key_win).unwrap_or(false);
1224
+ // Count all windows
1225
+ let windows: *const AnyObject = msg_send![&*app2, windows];
1226
+ let win_count: usize = msg_send![&*windows, count];
1227
+ std::io::Write::write_all(&mut std::io::stderr(),
1228
+ format!("[bloom-tvos] windows={}, keyWindow==ours={}\n", win_count, is_key).as_bytes()).ok();
1229
+ }
1230
+
1231
+ eprintln!("[bloom-tvos] window hierarchy set up, signaling game thread");
1232
+ // Store the layer pointer and screen dimensions for the game thread to create wgpu
1233
+ SCENE_PTR.store(Retained::as_ptr(&layer) as u64, std::sync::atomic::Ordering::Release);
1234
+ // Store dimensions packed into u64
1235
+ SCREEN_DIMS.store(((pixel_width as u64) << 32) | (pixel_height as u64), std::sync::atomic::Ordering::Release);
1236
+ }
1237
+
1238
+ fn register_scene_delegate() {
1239
+ if AnyClass::get(c"PerrySceneDelegate").is_some() { return; }
1240
+
1241
+ unsafe {
1242
+ let superclass = AnyClass::get(c"UIResponder").unwrap();
1243
+ let cls = objc_allocateClassPair(superclass as *const AnyClass, b"PerrySceneDelegate\0".as_ptr(), 0);
1244
+ if cls.is_null() { return; }
1245
+
1246
+ // scene:willConnectToSession:connectionOptions:
1247
+ let sel = Sel::register(c"scene:willConnectToSession:connectionOptions:");
1248
+ let types = b"v48@0:8@16@24@32\0".as_ptr();
1249
+ class_addMethod(cls, sel, scene_will_connect as *const c_void, types);
1250
+
1251
+ // Add UIWindowSceneDelegate protocol
1252
+ extern "C" { fn objc_getProtocol(name: *const u8) -> *const c_void; }
1253
+ let protocol = objc_getProtocol(b"UIWindowSceneDelegate\0".as_ptr());
1254
+ if !protocol.is_null() {
1255
+ extern "C" { fn class_addProtocol(cls: *mut AnyClass, protocol: *const c_void) -> bool; }
1256
+ class_addProtocol(cls, protocol);
1257
+ }
1258
+
1259
+ objc_registerClassPair(cls);
1260
+ }
1261
+ }
1262
+
1263
+ // ============================================================
1264
+ // Minimal pollster
1265
+ // ============================================================
1266
+
1267
+ fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
1268
+ use std::task::{Context, Poll, Wake, Waker};
1269
+ use std::pin::Pin;
1270
+ use std::sync::Arc;
1271
+ struct NoopWaker;
1272
+ impl Wake for NoopWaker { fn wake(self: Arc<Self>) {} }
1273
+ let waker = Waker::from(Arc::new(NoopWaker));
1274
+ let mut cx = Context::from_waker(&waker);
1275
+ let mut future = unsafe { Pin::new_unchecked(Box::new(future)) };
1276
+ loop {
1277
+ match future.as_mut().poll(&mut cx) {
1278
+ Poll::Ready(result) => return result,
1279
+ Poll::Pending => {
1280
+ pump_run_loop(0.001);
1281
+ }
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ // ============================================================
1287
+ // GCController — Siri Remote and game controller monitoring
1288
+ // ============================================================
1289
+
1290
+ /// Poll connected game controllers and feed their state into the engine input system.
1291
+ /// Called once during init and also from bloom_begin_drawing to poll controller state.
1292
+ fn poll_game_controllers() {
1293
+ unsafe {
1294
+ let gc_cls = match AnyClass::get(c"GCController") {
1295
+ Some(c) => c,
1296
+ None => return,
1297
+ };
1298
+ let controllers: Retained<AnyObject> = msg_send![gc_cls, controllers];
1299
+ let count: usize = msg_send![&*controllers, count];
1300
+ if count == 0 { return; }
1301
+
1302
+ // Use the first connected controller
1303
+ let controller: Retained<AnyObject> = msg_send![&*controllers, objectAtIndex: 0usize];
1304
+
1305
+ // Try extended gamepad profile first (MFi, PS, Xbox controllers)
1306
+ let extended: *const AnyObject = msg_send![&*controller, extendedGamepad];
1307
+ if !extended.is_null() {
1308
+ if let Some(eng) = ENGINE.get_mut() {
1309
+ eng.input.gamepad_available = true;
1310
+
1311
+ // Left thumbstick
1312
+ let left_stick: Retained<AnyObject> = msg_send![&*extended, leftThumbstick];
1313
+ let lx: f64 = msg_send![&*left_stick, xAxis_value];
1314
+ let ly: f64 = msg_send![&*left_stick, yAxis_value];
1315
+ eng.input.set_gamepad_axis(0, lx as f32);
1316
+ eng.input.set_gamepad_axis(1, -ly as f32); // Invert Y
1317
+
1318
+ // Right thumbstick
1319
+ let right_stick: Retained<AnyObject> = msg_send![&*extended, rightThumbstick];
1320
+ let rx: f64 = msg_send![&*right_stick, xAxis_value];
1321
+ let ry: f64 = msg_send![&*right_stick, yAxis_value];
1322
+ eng.input.set_gamepad_axis(2, rx as f32);
1323
+ eng.input.set_gamepad_axis(3, -ry as f32);
1324
+
1325
+ // Buttons: A(0), B(1), X(2), Y(3)
1326
+ let btn_a: Retained<AnyObject> = msg_send![&*extended, buttonA];
1327
+ let btn_b: Retained<AnyObject> = msg_send![&*extended, buttonB];
1328
+ let btn_x: Retained<AnyObject> = msg_send![&*extended, buttonX];
1329
+ let btn_y: Retained<AnyObject> = msg_send![&*extended, buttonY];
1330
+ let a_pressed: Bool = msg_send![&*btn_a, isPressed];
1331
+ let b_pressed: Bool = msg_send![&*btn_b, isPressed];
1332
+ let x_pressed: Bool = msg_send![&*btn_x, isPressed];
1333
+ let y_pressed: Bool = msg_send![&*btn_y, isPressed];
1334
+ if a_pressed.as_bool() { eng.input.set_gamepad_button_down(0); }
1335
+ if b_pressed.as_bool() { eng.input.set_gamepad_button_down(1); }
1336
+ if x_pressed.as_bool() { eng.input.set_gamepad_button_down(2); }
1337
+ if y_pressed.as_bool() { eng.input.set_gamepad_button_down(3); }
1338
+
1339
+ // D-pad
1340
+ let dpad: Retained<AnyObject> = msg_send![&*extended, dpad];
1341
+ let up: Retained<AnyObject> = msg_send![&*dpad, up];
1342
+ let down: Retained<AnyObject> = msg_send![&*dpad, down];
1343
+ let left: Retained<AnyObject> = msg_send![&*dpad, left];
1344
+ let right: Retained<AnyObject> = msg_send![&*dpad, right];
1345
+ let up_p: Bool = msg_send![&*up, isPressed];
1346
+ let down_p: Bool = msg_send![&*down, isPressed];
1347
+ let left_p: Bool = msg_send![&*left, isPressed];
1348
+ let right_p: Bool = msg_send![&*right, isPressed];
1349
+ if up_p.as_bool() { eng.input.set_gamepad_button_down(12); }
1350
+ if down_p.as_bool() { eng.input.set_gamepad_button_down(13); }
1351
+ if left_p.as_bool() { eng.input.set_gamepad_button_down(14); }
1352
+ if right_p.as_bool() { eng.input.set_gamepad_button_down(15); }
1353
+
1354
+ // Shoulders and triggers
1355
+ let l_shoulder: Retained<AnyObject> = msg_send![&*extended, leftShoulder];
1356
+ let r_shoulder: Retained<AnyObject> = msg_send![&*extended, rightShoulder];
1357
+ let ls_p: Bool = msg_send![&*l_shoulder, isPressed];
1358
+ let rs_p: Bool = msg_send![&*r_shoulder, isPressed];
1359
+ if ls_p.as_bool() { eng.input.set_gamepad_button_down(4); }
1360
+ if rs_p.as_bool() { eng.input.set_gamepad_button_down(5); }
1361
+
1362
+ let l_trigger: Retained<AnyObject> = msg_send![&*extended, leftTrigger];
1363
+ let r_trigger: Retained<AnyObject> = msg_send![&*extended, rightTrigger];
1364
+ eng.input.set_gamepad_axis(4, { let v: f64 = msg_send![&*l_trigger, value]; v as f32 });
1365
+ eng.input.set_gamepad_axis(5, { let v: f64 = msg_send![&*r_trigger, value]; v as f32 });
1366
+ }
1367
+ return;
1368
+ }
1369
+
1370
+ // Fall back to micro gamepad profile (Siri Remote)
1371
+ let micro: *const AnyObject = msg_send![&*controller, microGamepad];
1372
+ if !micro.is_null() {
1373
+ if let Some(eng) = ENGINE.get_mut() {
1374
+ eng.input.gamepad_available = true;
1375
+
1376
+ // Siri Remote touchpad → axes 0/1
1377
+ let dpad: Retained<AnyObject> = msg_send![&*micro, dpad];
1378
+ let x_val: f64 = { let axis: Retained<AnyObject> = msg_send![&*dpad, xAxis]; msg_send![&*axis, value] };
1379
+ let y_val: f64 = { let axis: Retained<AnyObject> = msg_send![&*dpad, yAxis]; msg_send![&*axis, value] };
1380
+ eng.input.set_gamepad_axis(0, x_val as f32);
1381
+ eng.input.set_gamepad_axis(1, -y_val as f32);
1382
+
1383
+ // Button A (select/click) and Button X (play/pause)
1384
+ let btn_a: Retained<AnyObject> = msg_send![&*micro, buttonA];
1385
+ let btn_x: Retained<AnyObject> = msg_send![&*micro, buttonX];
1386
+ let a_pressed: Bool = msg_send![&*btn_a, isPressed];
1387
+ let x_pressed: Bool = msg_send![&*btn_x, isPressed];
1388
+ if a_pressed.as_bool() { eng.input.set_gamepad_button_down(0); }
1389
+ if x_pressed.as_bool() { eng.input.set_gamepad_button_down(9); } // start/pause
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+
1395
+ fn setup_game_controllers() {
1396
+ // Register for GCController connection notifications and set up input handlers.
1397
+ // This is the correct way to handle Siri Remote input on tvOS — it claims
1398
+ // the controller at the system level, preventing the OS from dismissing the app.
1399
+ unsafe {
1400
+ extern "C" {
1401
+ static _dispatch_main_q: c_void;
1402
+ fn dispatch_async_f(queue: *const c_void, context: *mut c_void, work: unsafe extern "C" fn(*mut c_void));
1403
+ }
1404
+ unsafe extern "C" fn setup_gc(_: *mut c_void) {
1405
+ let gc_cls = match AnyClass::get(c"GCController") {
1406
+ Some(c) => c,
1407
+ None => return,
1408
+ };
1409
+
1410
+ // Start wireless controller discovery (finds Siri Remote in simulator)
1411
+ let _: () = msg_send![gc_cls, startWirelessControllerDiscoveryWithCompletionHandler: std::ptr::null::<c_void>()];
1412
+
1413
+ // Check for already-connected controllers
1414
+ let controllers: Retained<AnyObject> = msg_send![gc_cls, controllers];
1415
+ let count: usize = msg_send![&*controllers, count];
1416
+ std::io::Write::write_all(&mut std::io::stderr(),
1417
+ format!("[bloom-tvos] GCControllers found: {}\n", count).as_bytes()).ok();
1418
+
1419
+ // Set up value-changed handlers on connected controllers
1420
+ for i in 0..count {
1421
+ let ctrl: Retained<AnyObject> = msg_send![&*controllers, objectAtIndex: i as usize];
1422
+ let micro: *const AnyObject = msg_send![&*ctrl, microGamepad];
1423
+ if !micro.is_null() {
1424
+ // Set reportsAbsoluteDpadValues so polled values are absolute position
1425
+ let _: () = msg_send![&*micro, setReportsAbsoluteDpadValues: Bool::YES];
1426
+ }
1427
+ }
1428
+
1429
+ // Try to create a virtual controller if none found
1430
+ if count == 0 {
1431
+ if let Some(vc_cls) = AnyClass::get(c"GCVirtualController") {
1432
+ let config_cls = AnyClass::get(c"GCVirtualControllerConfiguration").unwrap();
1433
+ let config: Retained<AnyObject> = msg_send![config_cls, new];
1434
+ let vc_ctrl: Allocated<AnyObject> = msg_send![vc_cls, alloc];
1435
+ let vc_ctrl: Retained<AnyObject> = msg_send![vc_ctrl, initWithConfiguration: &*config];
1436
+ let _: () = msg_send![&*vc_ctrl, connectWithReplyHandler: std::ptr::null::<c_void>()];
1437
+ std::mem::forget(vc_ctrl); // keep alive
1438
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] Created GCVirtualController\n").ok();
1439
+ }
1440
+ }
1441
+
1442
+ for i in 0..count {
1443
+ let controller: Retained<AnyObject> = msg_send![&*controllers, objectAtIndex: i as usize];
1444
+
1445
+ // Check micro gamepad (Siri Remote)
1446
+ let micro: *const AnyObject = msg_send![&*controller, microGamepad];
1447
+ if !micro.is_null() {
1448
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] Found micro gamepad (Siri Remote)\n").ok();
1449
+ // Set reportsAbsoluteDpadValues so we get position, not delta
1450
+ let _: () = msg_send![&*micro, setReportsAbsoluteDpadValues: Bool::YES];
1451
+ // allowsRotation for landscape usage
1452
+ let _: () = msg_send![&*micro, setAllowsRotation: Bool::YES];
1453
+ }
1454
+
1455
+ // Check extended gamepad (MFi, PS, Xbox)
1456
+ let extended: *const AnyObject = msg_send![&*controller, extendedGamepad];
1457
+ if !extended.is_null() {
1458
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] Found extended gamepad\n").ok();
1459
+ }
1460
+ }
1461
+ }
1462
+ dispatch_async_f(&_dispatch_main_q as *const _, std::ptr::null_mut(), setup_gc);
1463
+ }
1464
+ }
1465
+
1466
+ // ============================================================
1467
+ // FFI entry points
1468
+ // ============================================================
1469
+
1470
+ #[no_mangle]
1471
+ pub extern "C" fn bloom_init_window(_width: f64, _height: f64, title_ptr: *const u8, _fullscreen: f64) {
1472
+ let _title = str_from_header(title_ptr);
1473
+
1474
+ // Register ObjC classes for the scene delegate (window/view creation)
1475
+ register_metal_view_class();
1476
+ register_window_class();
1477
+ register_view_controller_class();
1478
+ register_scene_delegate();
1479
+
1480
+ // Signal the main thread that our ObjC classes are ready.
1481
+ // UIApplicationMain (on main thread) waits for this before starting.
1482
+ extern "C" { fn perry_ios_classes_registered(); }
1483
+ unsafe { perry_ios_classes_registered(); }
1484
+
1485
+ // Debug: write marker to confirm we reached this point
1486
+ let _ = std::fs::write("/tmp/bloom_checkpoint_1.txt", "classes registered\n");
1487
+
1488
+ // Get app bundle path for resolving relative asset paths
1489
+ unsafe {
1490
+ let bundle_cls = AnyClass::get(c"NSBundle").unwrap();
1491
+ let main_bundle: Retained<AnyObject> = msg_send![bundle_cls, mainBundle];
1492
+ let resource_path: *const AnyObject = msg_send![&*main_bundle, resourcePath];
1493
+ if !resource_path.is_null() {
1494
+ let utf8: *const u8 = msg_send![&*resource_path, UTF8String];
1495
+ if !utf8.is_null() {
1496
+ let cstr = std::ffi::CStr::from_ptr(utf8 as *const i8);
1497
+ if let Ok(s) = cstr.to_str() {
1498
+ BUNDLE_PATH = Some(s.to_string());
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ // With --features ios-game-loop, this function runs on the game thread.
1505
+ // The engine is created on the main thread by scene_will_connect (like iOS).
1506
+ // Just wait for it here.
1507
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] waiting for ENGINE...\n").ok();
1508
+ unsafe {
1509
+ for i in 0..3000 {
1510
+ if ENGINE.get().is_some() { break; }
1511
+ if i % 100 == 0 && i > 0 {
1512
+ let msg = format!("[bloom-tvos] still waiting for ENGINE... {}s\n", i / 100);
1513
+ std::io::Write::write_all(&mut std::io::stderr(), msg.as_bytes()).ok();
1514
+ }
1515
+ std::thread::sleep(std::time::Duration::from_millis(10));
1516
+ }
1517
+ if ENGINE.get().is_none() {
1518
+ panic!("[bloom-tvos] ENGINE not available after 30s");
1519
+ }
1520
+ }
1521
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] ENGINE ready on game thread\n").ok();
1522
+
1523
+ // Set up GCController monitoring for Siri Remote and game controllers
1524
+ setup_game_controllers();
1525
+ }
1526
+
1527
+ #[no_mangle]
1528
+ pub extern "C" fn bloom_close_window() {
1529
+ unsafe { UI_VIEW = None; UI_WINDOW = None; }
1530
+ }
1531
+
1532
+ #[no_mangle]
1533
+ pub extern "C" fn bloom_window_should_close() -> f64 {
1534
+ if engine().should_close { 1.0 } else { 0.0 }
1535
+ }
1536
+
1537
+ #[no_mangle]
1538
+ pub extern "C" fn bloom_begin_drawing() {
1539
+ static FRAME_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1540
+ let frame = FRAME_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1541
+ if frame == 0 {
1542
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] first bloom_begin_drawing\n").ok();
1543
+ }
1544
+ // Poll GCController synchronously on the game thread.
1545
+ // GCController value reading is thread-safe.
1546
+ unsafe {
1547
+ if let Some(gc_cls) = AnyClass::get(c"GCController") {
1548
+ let controllers: Retained<AnyObject> = msg_send![gc_cls, controllers];
1549
+ let count: usize = msg_send![&*controllers, count];
1550
+ {
1551
+ static POLL_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1552
+ let n = POLL_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1553
+ if n < 3 || (n % 300 == 0) {
1554
+ std::io::Write::write_all(&mut std::io::stderr(),
1555
+ format!("[bloom-tvos] GC poll: {} controllers\n", count).as_bytes()).ok();
1556
+ }
1557
+ }
1558
+ if count > 0 {
1559
+ let controller: Retained<AnyObject> = msg_send![&*controllers, objectAtIndex: 0usize];
1560
+ let eng = engine();
1561
+ eng.input.gamepad_available = true;
1562
+
1563
+ // Micro gamepad (Siri Remote)
1564
+ let micro: *const AnyObject = msg_send![&*controller, microGamepad];
1565
+ let extended_check: *const AnyObject = msg_send![&*controller, extendedGamepad];
1566
+ {
1567
+ static PROFILE_LOG: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1568
+ if !PROFILE_LOG.swap(true, std::sync::atomic::Ordering::Relaxed) {
1569
+ std::io::Write::write_all(&mut std::io::stderr(),
1570
+ format!("[bloom-tvos] micro={} extended={}\n", !micro.is_null(), !extended_check.is_null()).as_bytes()).ok();
1571
+ }
1572
+ }
1573
+ if !micro.is_null() {
1574
+ let dpad: Retained<AnyObject> = msg_send![&*micro, dpad];
1575
+ let x_axis: Retained<AnyObject> = msg_send![&*dpad, xAxis];
1576
+ let y_axis: Retained<AnyObject> = msg_send![&*dpad, yAxis];
1577
+ let x_val: f32 = msg_send![&*x_axis, value];
1578
+ let y_val: f32 = msg_send![&*y_axis, value];
1579
+ eng.input.set_gamepad_axis(0, x_val);
1580
+ eng.input.set_gamepad_axis(1, -y_val);
1581
+ let btn_a: Retained<AnyObject> = msg_send![&*micro, buttonA];
1582
+ let btn_x: Retained<AnyObject> = msg_send![&*micro, buttonX];
1583
+ let a_val: f32 = msg_send![&*btn_a, value];
1584
+ let x_btn_val: f32 = msg_send![&*btn_x, value];
1585
+ if a_val > 0.01 || x_btn_val > 0.01 || x_val.abs() > 0.01 || y_val.abs() > 0.01 {
1586
+ static BTN_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1587
+ let n = BTN_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1588
+ if n < 20 {
1589
+ std::io::Write::write_all(&mut std::io::stderr(),
1590
+ format!("[bloom-tvos] micro: x={:.2} y={:.2} a={:.2} x_btn={:.2}\n", x_val, y_val, a_val, x_btn_val).as_bytes()).ok();
1591
+ }
1592
+ }
1593
+ if a_val > 0.5 { eng.input.set_gamepad_button_down(0); }
1594
+ if x_btn_val > 0.5 { eng.input.set_gamepad_button_down(7); }
1595
+ }
1596
+
1597
+ // Extended gamepad (MFi/PS/Xbox)
1598
+ let extended: *const AnyObject = msg_send![&*controller, extendedGamepad];
1599
+ if !extended.is_null() {
1600
+ let left_stick: Retained<AnyObject> = msg_send![&*extended, leftThumbstick];
1601
+ let lx_axis: Retained<AnyObject> = msg_send![&*left_stick, xAxis];
1602
+ let ly_axis: Retained<AnyObject> = msg_send![&*left_stick, yAxis];
1603
+ let lx: f32 = msg_send![&*lx_axis, value];
1604
+ let ly: f32 = msg_send![&*ly_axis, value];
1605
+ eng.input.set_gamepad_axis(0, lx);
1606
+ eng.input.set_gamepad_axis(1, -ly);
1607
+ let btn_a: Retained<AnyObject> = msg_send![&*extended, buttonA];
1608
+ let a_val: f32 = msg_send![&*btn_a, value];
1609
+ if lx.abs() > 0.01 || ly.abs() > 0.01 || a_val > 0.01 {
1610
+ static EXT_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1611
+ let n = EXT_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1612
+ if n < 20 {
1613
+ std::io::Write::write_all(&mut std::io::stderr(),
1614
+ format!("[bloom-tvos] ext: lx={:.2} ly={:.2} a={:.2}\n", lx, ly, a_val).as_bytes()).ok();
1615
+ }
1616
+ }
1617
+ if a_val > 0.5 { eng.input.set_gamepad_button_down(0); }
1618
+ }
1619
+ }
1620
+ }
1621
+ }
1622
+ // Drain pending key events from main thread BEFORE begin_frame snapshots
1623
+ drain_pending_keys(engine());
1624
+
1625
+ // Check if any pending keys were drained
1626
+ {
1627
+ static DRAIN_LOG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1628
+ // Check if any PENDING_KEY_DOWN bits are set (before they were drained)
1629
+ let mut any = false;
1630
+ for i in 0..8 {
1631
+ if PENDING_KEY_DOWN[i].load(std::sync::atomic::Ordering::Relaxed) != 0 { any = true; }
1632
+ }
1633
+ if any {
1634
+ let n = DRAIN_LOG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1635
+ if n < 10 {
1636
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] PENDING KEYS FOUND!\n").ok();
1637
+ }
1638
+ }
1639
+ }
1640
+
1641
+ if frame == 0 {
1642
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] calling begin_frame\n").ok();
1643
+ }
1644
+ engine().begin_frame();
1645
+ if frame == 0 {
1646
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] begin_frame OK\n").ok();
1647
+ }
1648
+ }
1649
+
1650
+ #[no_mangle]
1651
+ pub extern "C" fn bloom_end_drawing() {
1652
+ static END_FRAME_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1653
+ let frame = END_FRAME_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1654
+ if frame == 0 {
1655
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] first bloom_end_drawing\n").ok();
1656
+ }
1657
+ engine().end_frame();
1658
+ if frame == 0 {
1659
+ std::io::Write::write_all(&mut std::io::stderr(), b"[bloom-tvos] end_frame OK\n").ok();
1660
+ }
1661
+ }
1662
+
1663
+ #[no_mangle]
1664
+ pub extern "C" fn bloom_clear_background(r: f64, g: f64, b: f64, a: f64) {
1665
+ engine().renderer.set_clear_color(r, g, b, a);
1666
+ }
1667
+
1668
+ #[no_mangle]
1669
+ pub extern "C" fn bloom_set_target_fps(fps: f64) { engine().target_fps = fps; }
1670
+
1671
+ #[no_mangle]
1672
+ pub extern "C" fn bloom_set_direct_2d_mode(on: f64) { engine().direct_2d_mode = on > 0.5; }
1673
+
1674
+ #[no_mangle]
1675
+ pub extern "C" fn bloom_get_delta_time() -> f64 { engine().delta_time }
1676
+
1677
+ #[no_mangle]
1678
+ pub extern "C" fn bloom_get_fps() -> f64 { engine().get_fps() }
1679
+
1680
+ #[no_mangle]
1681
+ pub extern "C" fn bloom_get_screen_width() -> f64 { engine().screen_width() }
1682
+
1683
+ #[no_mangle]
1684
+ pub extern "C" fn bloom_get_screen_height() -> f64 { engine().screen_height() }
1685
+
1686
+ // ============================================================
1687
+ // Keyboard input
1688
+ // ============================================================
1689
+
1690
+ #[no_mangle]
1691
+ pub extern "C" fn bloom_is_key_pressed(key: f64) -> f64 {
1692
+ if engine().input.is_key_pressed(key as usize) { 1.0 } else { 0.0 }
1693
+ }
1694
+
1695
+ #[no_mangle]
1696
+ pub extern "C" fn bloom_is_key_down(key: f64) -> f64 {
1697
+ if engine().input.is_key_down(key as usize) { 1.0 } else { 0.0 }
1698
+ }
1699
+
1700
+ #[no_mangle]
1701
+ pub extern "C" fn bloom_is_key_released(key: f64) -> f64 {
1702
+ if engine().input.is_key_released(key as usize) { 1.0 } else { 0.0 }
1703
+ }
1704
+
1705
+ // ============================================================
1706
+ // Mouse input
1707
+ // ============================================================
1708
+
1709
+ #[no_mangle]
1710
+ pub extern "C" fn bloom_get_mouse_x() -> f64 { engine().input.mouse_x }
1711
+
1712
+ #[no_mangle]
1713
+ pub extern "C" fn bloom_get_mouse_y() -> f64 { engine().input.mouse_y }
1714
+
1715
+ #[no_mangle]
1716
+ pub extern "C" fn bloom_is_mouse_button_pressed(btn: f64) -> f64 {
1717
+ if engine().input.is_mouse_button_pressed(btn as usize) { 1.0 } else { 0.0 }
1718
+ }
1719
+
1720
+ #[no_mangle]
1721
+ pub extern "C" fn bloom_is_mouse_button_down(btn: f64) -> f64 {
1722
+ if engine().input.is_mouse_button_down(btn as usize) { 1.0 } else { 0.0 }
1723
+ }
1724
+
1725
+ #[no_mangle]
1726
+ pub extern "C" fn bloom_is_mouse_button_released(btn: f64) -> f64 {
1727
+ if engine().input.is_mouse_button_released(btn as usize) { 1.0 } else { 0.0 }
1728
+ }
1729
+
1730
+ // ============================================================
1731
+ // Shape drawing
1732
+ // ============================================================
1733
+
1734
+ #[no_mangle]
1735
+ pub extern "C" fn bloom_draw_line(x1: f64, y1: f64, x2: f64, y2: f64, thickness: f64, r: f64, g: f64, b: f64, a: f64) {
1736
+ engine().renderer.draw_line(x1, y1, x2, y2, thickness, r, g, b, a);
1737
+ }
1738
+
1739
+ #[no_mangle]
1740
+ pub extern "C" fn bloom_draw_rect(x: f64, y: f64, w: f64, h: f64, r: f64, g: f64, b: f64, a: f64) {
1741
+ engine().renderer.draw_rect(x, y, w, h, r, g, b, a);
1742
+ }
1743
+
1744
+ #[no_mangle]
1745
+ pub extern "C" fn bloom_draw_rect_lines(x: f64, y: f64, w: f64, h: f64, thickness: f64, r: f64, g: f64, b: f64, a: f64) {
1746
+ engine().renderer.draw_rect_lines(x, y, w, h, thickness, r, g, b, a);
1747
+ }
1748
+
1749
+ #[no_mangle]
1750
+ pub extern "C" fn bloom_draw_circle(cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
1751
+ engine().renderer.draw_circle(cx, cy, radius, r, g, b, a);
1752
+ }
1753
+
1754
+ #[no_mangle]
1755
+ pub extern "C" fn bloom_draw_circle_lines(cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
1756
+ engine().renderer.draw_circle_lines(cx, cy, radius, r, g, b, a);
1757
+ }
1758
+
1759
+ #[no_mangle]
1760
+ pub extern "C" fn bloom_draw_triangle(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, r: f64, g: f64, b: f64, a: f64) {
1761
+ engine().renderer.draw_triangle(x1, y1, x2, y2, x3, y3, r, g, b, a);
1762
+ }
1763
+
1764
+ #[no_mangle]
1765
+ pub extern "C" fn bloom_draw_poly(cx: f64, cy: f64, sides: f64, radius: f64, rotation: f64, r: f64, g: f64, b: f64, a: f64) {
1766
+ engine().renderer.draw_poly(cx, cy, sides, radius, rotation, r, g, b, a);
1767
+ }
1768
+
1769
+ // ============================================================
1770
+ // Text
1771
+ // ============================================================
1772
+
1773
+ #[no_mangle]
1774
+ pub extern "C" fn bloom_draw_text(text_ptr: *const u8, x: f64, y: f64, size: f64, r: f64, g: f64, b: f64, a: f64) {
1775
+ let text = str_from_header(text_ptr);
1776
+ let eng = engine();
1777
+ let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
1778
+ text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
1779
+ eng.text = text_renderer;
1780
+ }
1781
+
1782
+ #[no_mangle]
1783
+ pub extern "C" fn bloom_measure_text(text_ptr: *const u8, size: f64) -> f64 {
1784
+ let text = str_from_header(text_ptr);
1785
+ engine().text.measure_text(text, size as u32)
1786
+ }
1787
+
1788
+ #[no_mangle]
1789
+ pub extern "C" fn bloom_load_font(path_ptr: *const u8, _size: f64) -> f64 {
1790
+ let path = str_from_header(path_ptr);
1791
+ match std::fs::read(resolve_path(path)) { Ok(data) => engine().text.load_font(&data) as f64, Err(_) => 0.0 }
1792
+ }
1793
+
1794
+ #[no_mangle]
1795
+ pub extern "C" fn bloom_unload_font(font_handle: f64) {
1796
+ engine().text.unload_font(font_handle as usize);
1797
+ }
1798
+
1799
+ #[no_mangle]
1800
+ pub extern "C" fn bloom_draw_text_ex(font_handle: f64, text_ptr: *const u8, x: f64, y: f64, size: f64, spacing: f64, r: f64, g: f64, b: f64, a: f64) {
1801
+ let text = str_from_header(text_ptr);
1802
+ let eng = engine();
1803
+ let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
1804
+ text_renderer.draw_text_ex(&mut eng.renderer, font_handle as usize, text, x, y, size as u32, spacing as f32, r, g, b, a);
1805
+ eng.text = text_renderer;
1806
+ }
1807
+
1808
+ #[no_mangle]
1809
+ pub extern "C" fn bloom_measure_text_ex(font_handle: f64, text_ptr: *const u8, size: f64, spacing: f64) -> f64 {
1810
+ let text = str_from_header(text_ptr);
1811
+ engine().text.measure_text_ex(font_handle as usize, text, size as u32, spacing as f32)
1812
+ }
1813
+
1814
+ // ============================================================
1815
+ // Textures
1816
+ // ============================================================
1817
+
1818
+ #[no_mangle]
1819
+ pub extern "C" fn bloom_load_texture(path_ptr: *const u8) -> f64 {
1820
+ let path = str_from_header(path_ptr);
1821
+ match std::fs::read(resolve_path(path)) {
1822
+ Ok(data) => {
1823
+ let eng = engine();
1824
+ let renderer_ptr = &mut eng.renderer as *mut Renderer;
1825
+ eng.textures.load_texture(unsafe { &mut *renderer_ptr }, &data)
1826
+ }
1827
+ Err(_) => 0.0,
1828
+ }
1829
+ }
1830
+
1831
+ #[no_mangle]
1832
+ pub extern "C" fn bloom_unload_texture(handle: f64) {
1833
+ let eng = engine();
1834
+ let renderer_ptr = &mut eng.renderer as *mut Renderer;
1835
+ eng.textures.unload_texture(handle, unsafe { &mut *renderer_ptr });
1836
+ }
1837
+
1838
+ #[no_mangle]
1839
+ pub extern "C" fn bloom_draw_texture(handle: f64, x: f64, y: f64, r: f64, g: f64, b: f64, a: f64) {
1840
+ let eng = engine();
1841
+ if let Some(tex) = eng.textures.get(handle) {
1842
+ let idx = tex.bind_group_idx;
1843
+ eng.renderer.draw_texture(idx, x, y, r, g, b, a);
1844
+ }
1845
+ }
1846
+
1847
+ #[no_mangle]
1848
+ pub extern "C" fn bloom_draw_texture_pro(
1849
+ handle: f64,
1850
+ src_x: f64, src_y: f64, src_w: f64, src_h: f64,
1851
+ dst_x: f64, dst_y: f64, dst_w: f64, dst_h: f64,
1852
+ origin_x: f64, origin_y: f64, rotation: f64,
1853
+ r: f64, g: f64, b: f64, a: f64,
1854
+ ) {
1855
+ let eng = engine();
1856
+ if let Some(tex) = eng.textures.get(handle) {
1857
+ let idx = tex.bind_group_idx;
1858
+ eng.renderer.draw_texture_pro(idx, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h, origin_x, origin_y, rotation, r, g, b, a);
1859
+ }
1860
+ }
1861
+
1862
+ #[no_mangle]
1863
+ pub extern "C" fn bloom_draw_texture_rec(
1864
+ handle: f64,
1865
+ src_x: f64, src_y: f64, src_w: f64, src_h: f64,
1866
+ dst_x: f64, dst_y: f64,
1867
+ r: f64, g: f64, b: f64, a: f64,
1868
+ ) {
1869
+ let eng = engine();
1870
+ if let Some(tex) = eng.textures.get(handle) {
1871
+ let idx = tex.bind_group_idx;
1872
+ eng.renderer.draw_texture_rec(idx, src_x, src_y, src_w, src_h, dst_x, dst_y, r, g, b, a);
1873
+ }
1874
+ }
1875
+
1876
+ #[no_mangle]
1877
+ pub extern "C" fn bloom_get_texture_width(handle: f64) -> f64 {
1878
+ engine().textures.get(handle).map(|t| t.width as f64).unwrap_or(0.0)
1879
+ }
1880
+
1881
+ #[no_mangle]
1882
+ pub extern "C" fn bloom_get_texture_height(handle: f64) -> f64 {
1883
+ engine().textures.get(handle).map(|t| t.height as f64).unwrap_or(0.0)
1884
+ }
1885
+
1886
+ #[no_mangle]
1887
+ pub extern "C" fn bloom_gen_texture_mipmaps(_handle: f64) {}
1888
+
1889
+ #[no_mangle]
1890
+ pub extern "C" fn bloom_set_texture_filter(handle: f64, mode: f64) {
1891
+ let eng = engine();
1892
+ if let Some(tex) = eng.textures.get(handle) {
1893
+ let bind_group_idx = tex.bind_group_idx;
1894
+ eng.renderer.set_texture_filter(bind_group_idx, mode > 0.5);
1895
+ }
1896
+ }
1897
+
1898
+ #[no_mangle]
1899
+ pub extern "C" fn bloom_load_image(path_ptr: *const u8) -> f64 {
1900
+ let path = str_from_header(path_ptr);
1901
+ match std::fs::read(resolve_path(path)) {
1902
+ Ok(data) => engine().textures.load_image(&data),
1903
+ Err(_) => 0.0,
1904
+ }
1905
+ }
1906
+
1907
+ #[no_mangle]
1908
+ pub extern "C" fn bloom_image_resize(handle: f64, w: f64, h: f64) {
1909
+ engine().textures.image_resize(handle, w as u32, h as u32);
1910
+ }
1911
+
1912
+ #[no_mangle]
1913
+ pub extern "C" fn bloom_image_crop(handle: f64, x: f64, y: f64, w: f64, h: f64) {
1914
+ engine().textures.image_crop(handle, x as u32, y as u32, w as u32, h as u32);
1915
+ }
1916
+
1917
+ #[no_mangle]
1918
+ pub extern "C" fn bloom_image_flip_h(handle: f64) {
1919
+ engine().textures.image_flip_h(handle);
1920
+ }
1921
+
1922
+ #[no_mangle]
1923
+ pub extern "C" fn bloom_image_flip_v(handle: f64) {
1924
+ engine().textures.image_flip_v(handle);
1925
+ }
1926
+
1927
+ #[no_mangle]
1928
+ pub extern "C" fn bloom_load_texture_from_image(handle: f64) -> f64 {
1929
+ let eng = engine();
1930
+ let renderer_ptr = &mut eng.renderer as *mut Renderer;
1931
+ eng.textures.load_texture_from_image(handle, unsafe { &mut *renderer_ptr })
1932
+ }
1933
+
1934
+ // ============================================================
1935
+ // Camera 2D
1936
+ // ============================================================
1937
+
1938
+ #[no_mangle]
1939
+ pub extern "C" fn bloom_begin_mode_2d(offset_x: f64, offset_y: f64, target_x: f64, target_y: f64, rotation: f64, zoom: f64) {
1940
+ engine().renderer.begin_mode_2d(
1941
+ offset_x as f32, offset_y as f32,
1942
+ target_x as f32, target_y as f32,
1943
+ rotation as f32, zoom as f32,
1944
+ );
1945
+ }
1946
+
1947
+ #[no_mangle]
1948
+ pub extern "C" fn bloom_end_mode_2d() {
1949
+ engine().renderer.end_mode_2d();
1950
+ }
1951
+
1952
+ // ============================================================
1953
+ // 3D Camera and Drawing
1954
+ // ============================================================
1955
+
1956
+ #[no_mangle]
1957
+ pub extern "C" fn bloom_begin_mode_3d(
1958
+ pos_x: f64, pos_y: f64, pos_z: f64,
1959
+ target_x: f64, target_y: f64, target_z: f64,
1960
+ up_x: f64, up_y: f64, up_z: f64,
1961
+ fovy: f64, projection: f64,
1962
+ ) {
1963
+ engine().renderer.begin_mode_3d(
1964
+ pos_x as f32, pos_y as f32, pos_z as f32,
1965
+ target_x as f32, target_y as f32, target_z as f32,
1966
+ up_x as f32, up_y as f32, up_z as f32,
1967
+ fovy as f32, projection as f32,
1968
+ );
1969
+ }
1970
+
1971
+ #[no_mangle]
1972
+ pub extern "C" fn bloom_end_mode_3d() {
1973
+ engine().renderer.end_mode_3d();
1974
+ }
1975
+
1976
+ #[no_mangle]
1977
+ pub extern "C" fn bloom_draw_cube(x: f64, y: f64, z: f64, w: f64, h: f64, d: f64, r: f64, g: f64, b: f64, a: f64) {
1978
+ engine().renderer.draw_cube(x, y, z, w, h, d, r, g, b, a);
1979
+ }
1980
+
1981
+ #[no_mangle]
1982
+ pub extern "C" fn bloom_draw_cube_wires(x: f64, y: f64, z: f64, w: f64, h: f64, d: f64, r: f64, g: f64, b: f64, a: f64) {
1983
+ engine().renderer.draw_cube_wires(x, y, z, w, h, d, r, g, b, a);
1984
+ }
1985
+
1986
+ #[no_mangle]
1987
+ pub extern "C" fn bloom_draw_sphere(cx: f64, cy: f64, cz: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
1988
+ engine().renderer.draw_sphere(cx, cy, cz, radius, r, g, b, a);
1989
+ }
1990
+
1991
+ #[no_mangle]
1992
+ pub extern "C" fn bloom_draw_sphere_wires(cx: f64, cy: f64, cz: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
1993
+ engine().renderer.draw_sphere_wires(cx, cy, cz, radius, r, g, b, a);
1994
+ }
1995
+
1996
+ #[no_mangle]
1997
+ pub extern "C" fn bloom_draw_cylinder(x: f64, y: f64, z: f64, radius_top: f64, radius_bottom: f64, height: f64, r: f64, g: f64, b: f64, a: f64) {
1998
+ engine().renderer.draw_cylinder(x, y, z, radius_top, radius_bottom, height, r, g, b, a);
1999
+ }
2000
+
2001
+ #[no_mangle]
2002
+ pub extern "C" fn bloom_draw_plane(cx: f64, cy: f64, cz: f64, w: f64, d: f64, r: f64, g: f64, b: f64, a: f64) {
2003
+ engine().renderer.draw_plane(cx, cy, cz, w, d, r, g, b, a);
2004
+ }
2005
+
2006
+ #[no_mangle]
2007
+ pub extern "C" fn bloom_draw_grid(slices: f64, spacing: f64) {
2008
+ engine().renderer.draw_grid(slices as i32, spacing);
2009
+ }
2010
+
2011
+ #[no_mangle]
2012
+ pub extern "C" fn bloom_draw_ray(origin_x: f64, origin_y: f64, origin_z: f64, dir_x: f64, dir_y: f64, dir_z: f64, r: f64, g: f64, b: f64, a: f64) {
2013
+ engine().renderer.draw_ray(origin_x, origin_y, origin_z, dir_x, dir_y, dir_z, r, g, b, a);
2014
+ }
2015
+
2016
+ // ============================================================
2017
+ // Joint test (skeletal animation debug)
2018
+ // ============================================================
2019
+
2020
+ #[no_mangle]
2021
+ pub extern "C" fn bloom_set_joint_test(_joint: f64, _angle: f64) {
2022
+ // No-op for now — skeletal animation testing
2023
+ }
2024
+
2025
+ // ============================================================
2026
+ // Lighting
2027
+ // ============================================================
2028
+
2029
+ #[no_mangle]
2030
+ pub extern "C" fn bloom_set_ambient_light(r: f64, g: f64, b: f64, intensity: f64) {
2031
+ engine().renderer.set_ambient_light(r, g, b, intensity);
2032
+ }
2033
+
2034
+ #[no_mangle]
2035
+ pub extern "C" fn bloom_set_directional_light(dx: f64, dy: f64, dz: f64, r: f64, g: f64, b: f64, intensity: f64) {
2036
+ engine().renderer.set_directional_light(dx, dy, dz, r, g, b, intensity);
2037
+ }
2038
+
2039
+ #[no_mangle]
2040
+ pub extern "C" fn bloom_set_procedural_sky(enabled: f64, rayleigh_density: f64, mie_density: f64, ground_albedo: f64) {
2041
+ engine().renderer.set_procedural_sky(
2042
+ enabled != 0.0,
2043
+ rayleigh_density as f32,
2044
+ mie_density as f32,
2045
+ ground_albedo as f32,
2046
+ );
2047
+ }
2048
+
2049
+ #[no_mangle]
2050
+ pub extern "C" fn bloom_set_sun_direction(dx: f64, dy: f64, dz: f64, intensity: f64) {
2051
+ engine().renderer.set_sun_direction(dx as f32, dy as f32, dz as f32, intensity as f32);
2052
+ }
2053
+
2054
+ // ============================================================
2055
+ // Models
2056
+ // ============================================================
2057
+
2058
+ #[no_mangle]
2059
+ pub extern "C" fn bloom_load_model(path_ptr: *const u8) -> f64 {
2060
+ let path = str_from_header(path_ptr);
2061
+ match std::fs::read(resolve_path(path)) {
2062
+ Ok(data) => {
2063
+ let eng = engine();
2064
+ let renderer_ptr = &mut eng.renderer as *mut crate::Renderer;
2065
+ eng.models.load_model_with_textures(&data, unsafe { &mut *renderer_ptr })
2066
+ }
2067
+ Err(_) => 0.0,
2068
+ }
2069
+ }
2070
+
2071
+ #[no_mangle]
2072
+ pub extern "C" fn bloom_draw_model(handle: f64, x: f64, y: f64, z: f64, scale: f64, r: f64, g: f64, b: f64, a: f64) {
2073
+ let eng = engine();
2074
+ if let Some(model) = eng.models.get(handle) {
2075
+ let tint = [(r / 255.0) as f32, (g / 255.0) as f32, (b / 255.0) as f32, (a / 255.0) as f32];
2076
+ let position = [x as f32, y as f32, z as f32];
2077
+ let handle_bits = handle.to_bits();
2078
+ if eng.renderer.cache_model_if_static(handle_bits, &model.meshes) {
2079
+ eng.renderer.draw_model_cached(handle_bits, position, scale as f32, tint);
2080
+ } else {
2081
+ for mesh in &model.meshes {
2082
+ let tex_idx = mesh.texture_idx.unwrap_or(0);
2083
+ eng.renderer.draw_model_mesh_tinted(&mesh.vertices, &mesh.indices, position, scale as f32, tint, tex_idx);
2084
+ }
2085
+ }
2086
+ }
2087
+ }
2088
+
2089
+ #[no_mangle]
2090
+ pub extern "C" fn bloom_draw_model_rotated(
2091
+ handle: f64, x: f64, y: f64, z: f64,
2092
+ scale: f64, rot_y: f64,
2093
+ color_packed_argb: f64,
2094
+ ) {
2095
+ let bits = color_packed_argb as u32;
2096
+ let a = ((bits >> 24) & 0xff) as f32 / 255.0;
2097
+ let r = ((bits >> 16) & 0xff) as f32 / 255.0;
2098
+ let g = ((bits >> 8) & 0xff) as f32 / 255.0;
2099
+ let b = ( bits & 0xff) as f32 / 255.0;
2100
+ let eng = engine();
2101
+ if let Some(model) = eng.models.get(handle) {
2102
+ let position = [x as f32, y as f32, z as f32];
2103
+ let scale = scale as f32;
2104
+ let tint = [r, g, b, a];
2105
+ for mesh in &model.meshes {
2106
+ let tex_idx = mesh.texture_idx.unwrap_or(0);
2107
+ eng.renderer.draw_model_mesh_tinted_rotated(
2108
+ &mesh.vertices, &mesh.indices, position, scale, tint, tex_idx, rot_y as f32,
2109
+ );
2110
+ }
2111
+ }
2112
+ }
2113
+
2114
+ #[no_mangle]
2115
+ pub extern "C" fn bloom_unload_model(handle: f64) {
2116
+ engine().models.unload_model(handle);
2117
+ }
2118
+
2119
+ #[no_mangle]
2120
+ pub extern "C" fn bloom_gen_mesh_cube(w: f64, h: f64, d: f64) -> f64 {
2121
+ engine().models.gen_mesh_cube(w as f32, h as f32, d as f32)
2122
+ }
2123
+
2124
+ #[no_mangle]
2125
+ pub extern "C" fn bloom_gen_mesh_heightmap(image_handle: f64, size_x: f64, size_y: f64, size_z: f64) -> f64 {
2126
+ let eng = engine();
2127
+ if let Some(img) = eng.textures.images.get(image_handle) {
2128
+ let data = img.data.clone();
2129
+ let w = img.width;
2130
+ let h = img.height;
2131
+ eng.models.gen_mesh_heightmap(&data, w, h, size_x as f32, size_y as f32, size_z as f32)
2132
+ } else {
2133
+ 0.0
2134
+ }
2135
+ }
2136
+
2137
+ #[no_mangle]
2138
+ pub extern "C" fn bloom_load_shader(source_ptr: *const u8) -> f64 {
2139
+ let source = str_from_header(source_ptr);
2140
+ engine().renderer.load_custom_shader(source) as f64
2141
+ }
2142
+
2143
+ #[no_mangle]
2144
+ pub extern "C" fn bloom_create_mesh(vertex_ptr: *const f32, vertex_count: f64, index_ptr: *const u32, index_count: f64) -> f64 {
2145
+ if vertex_ptr.is_null() || index_ptr.is_null() { return 0.0; }
2146
+ let vcount = vertex_count as usize;
2147
+ let icount = index_count as usize;
2148
+ let vertex_data = unsafe { std::slice::from_raw_parts(vertex_ptr, vcount * 12) };
2149
+ let index_data = unsafe { std::slice::from_raw_parts(index_ptr, icount) };
2150
+ engine().models.create_mesh(vertex_data, index_data)
2151
+ }
2152
+
2153
+ // ============================================================
2154
+ // Phase 1c — material system FFI
2155
+ // ============================================================
2156
+
2157
+ #[no_mangle]
2158
+ pub extern "C" fn bloom_set_material_params(
2159
+ handle: f64,
2160
+ params_ptr: *const f64,
2161
+ param_count: f64,
2162
+ ) {
2163
+ let count = param_count as usize;
2164
+ if count > 64 {
2165
+ eprintln!("[material] set_material_params: param_count {} > 64 (256-byte UBO cap)", count);
2166
+ return;
2167
+ }
2168
+ let mut bytes = vec![0u8; count * 4];
2169
+ if !params_ptr.is_null() && count > 0 {
2170
+ let slots = unsafe { std::slice::from_raw_parts(params_ptr, count) };
2171
+ for (i, &v) in slots.iter().enumerate() {
2172
+ let f = v as f32;
2173
+ bytes[i*4..i*4+4].copy_from_slice(&f.to_le_bytes());
2174
+ }
2175
+ }
2176
+ let eng = engine();
2177
+ if let Err(e) = eng.renderer.material_system.set_user_params(
2178
+ &eng.renderer.device, &eng.renderer.queue,
2179
+ handle as u32, &bytes,
2180
+ ) {
2181
+ eprintln!("[material] set_material_params failed: {}", e);
2182
+ }
2183
+ }
2184
+
2185
+ #[no_mangle]
2186
+ pub extern "C" fn bloom_compile_material(source_ptr: *const u8) -> f64 {
2187
+ let source = str_from_header(source_ptr);
2188
+ match engine().renderer.compile_material(source) {
2189
+ Ok(handle) => handle as f64,
2190
+ Err(e) => {
2191
+ eprintln!("[material] compile failed: {:?}", e);
2192
+ 0.0
2193
+ }
2194
+ }
2195
+ }
2196
+
2197
+ #[no_mangle]
2198
+ pub extern "C" fn bloom_compile_material_refractive(source_ptr: *const u8) -> f64 {
2199
+ use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
2200
+ let source = str_from_header(source_ptr);
2201
+ match engine().renderer.compile_material_with_options(
2202
+ source, FragmentProfile::Translucent, Bucket::Refractive, true, false,
2203
+ ) {
2204
+ Ok(handle) => handle as f64,
2205
+ Err(e) => { eprintln!("[refractive] compile failed: {:?}", e); 0.0 }
2206
+ }
2207
+ }
2208
+
2209
+ #[no_mangle]
2210
+ pub extern "C" fn bloom_compile_material_transparent(source_ptr: *const u8) -> f64 {
2211
+ use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
2212
+ let source = str_from_header(source_ptr);
2213
+ match engine().renderer.compile_material_with_options(
2214
+ source, FragmentProfile::Translucent, Bucket::Transparent, false, false,
2215
+ ) {
2216
+ Ok(handle) => handle as f64,
2217
+ Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
2218
+ }
2219
+ }
2220
+
2221
+ #[no_mangle]
2222
+ pub extern "C" fn bloom_compile_material_additive(source_ptr: *const u8) -> f64 {
2223
+ use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
2224
+ let source = str_from_header(source_ptr);
2225
+ match engine().renderer.compile_material_with_options(
2226
+ source, FragmentProfile::Translucent, Bucket::Additive, false, false,
2227
+ ) {
2228
+ Ok(handle) => handle as f64,
2229
+ Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
2230
+ }
2231
+ }
2232
+
2233
+ #[no_mangle]
2234
+ pub extern "C" fn bloom_compile_material_cutout(source_ptr: *const u8) -> f64 {
2235
+ use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
2236
+ let source = str_from_header(source_ptr);
2237
+ match engine().renderer.compile_material_with_options(
2238
+ source, FragmentProfile::Opaque, Bucket::Cutout, false, false,
2239
+ ) {
2240
+ Ok(handle) => handle as f64,
2241
+ Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
2242
+ }
2243
+ }
2244
+
2245
+ #[no_mangle]
2246
+ pub extern "C" fn bloom_compile_material_instanced(source_ptr: *const u8) -> f64 {
2247
+ let source = str_from_header(source_ptr);
2248
+ match engine().renderer.compile_material_instanced(source) {
2249
+ Ok(handle) => handle as f64,
2250
+ Err(e) => { eprintln!("[material] instanced compile failed: {:?}", e); 0.0 }
2251
+ }
2252
+ }
2253
+
2254
+ #[no_mangle]
2255
+ pub extern "C" fn bloom_create_instance_buffer(
2256
+ data_ptr: *const f64, instance_count: f64,
2257
+ ) -> f64 {
2258
+ if data_ptr.is_null() || instance_count <= 0.0 { return 0.0; }
2259
+ let count = instance_count as u32;
2260
+ let slot_count = (count as usize) * 9;
2261
+ let raw_f64 = unsafe { std::slice::from_raw_parts(data_ptr, slot_count) };
2262
+ let raw_f32: Vec<f32> = raw_f64.iter().map(|&v| v as f32).collect();
2263
+ engine().renderer.create_instance_buffer(&raw_f32, count) as f64
2264
+ }
2265
+
2266
+ #[no_mangle]
2267
+ pub extern "C" fn bloom_submit_material_draw_instanced(
2268
+ material: f64, mesh_handle: f64, mesh_idx: f64,
2269
+ instance_buffer: f64, instance_count: f64,
2270
+ ) {
2271
+ let eng = engine();
2272
+ let handle_bits = mesh_handle.to_bits();
2273
+ if let Some(model) = eng.models.get(mesh_handle) {
2274
+ eng.renderer.cache_model_if_static(handle_bits, &model.meshes);
2275
+ }
2276
+ eng.renderer.submit_material_draw_instanced(
2277
+ material as u32,
2278
+ handle_bits,
2279
+ mesh_idx as usize,
2280
+ instance_buffer as u32,
2281
+ instance_count as u32,
2282
+ );
2283
+ }
2284
+
2285
+ #[no_mangle]
2286
+ pub extern "C" fn bloom_destroy_instance_buffer(handle: f64) {
2287
+ engine().renderer.destroy_instance_buffer(handle as u32);
2288
+ }
2289
+
2290
+ /// EN-011 — create a planar reflection probe. See macOS lib.rs for the
2291
+ /// full doc comment; this entry-point exists on every native platform
2292
+ /// so games can target the same FFI surface across iOS/tvOS/Windows/
2293
+ /// Linux/Android.
2294
+ #[no_mangle]
2295
+ pub extern "C" fn bloom_create_planar_reflection(
2296
+ plane_y: f64, nx: f64, ny: f64, nz: f64, resolution: f64,
2297
+ ) -> f64 {
2298
+ engine().renderer.create_planar_reflection(
2299
+ plane_y as f32,
2300
+ [nx as f32, ny as f32, nz as f32],
2301
+ resolution as u32,
2302
+ ) as f64
2303
+ }
2304
+
2305
+ /// EN-011 — link a material to a planar reflection probe. `probe = 0`
2306
+ /// reverts the binding to the engine's default 1×1 black texture.
2307
+ #[no_mangle]
2308
+ pub extern "C" fn bloom_set_material_reflection_probe(
2309
+ material: f64, probe: f64,
2310
+ ) {
2311
+ engine().renderer.set_material_reflection_probe(material as u32, probe as u32);
2312
+ }
2313
+
2314
+ /// EN-014 — create a texture array from concatenated RGBA8 byte data.
2315
+ /// See macOS lib.rs for the full doc comment; this entry-point exists
2316
+ /// on every native platform so a TS game targets the same FFI across
2317
+ /// iOS / tvOS / Windows / Linux / Android.
2318
+ #[no_mangle]
2319
+ pub extern "C" fn bloom_create_texture_array(
2320
+ data_ptr: *const u8,
2321
+ data_len: f64,
2322
+ width: f64,
2323
+ height: f64,
2324
+ layer_count: f64,
2325
+ ) -> f64 {
2326
+ // EN-014 V2 — V1 forwards to _ex with default sRGB / no mips.
2327
+ bloom_create_texture_array_ex(data_ptr, data_len, width, height, layer_count, 0.0, 1.0)
2328
+ }
2329
+
2330
+ /// EN-014 V2 — explicit format + mip control. See macOS lib.rs for docs.
2331
+ #[no_mangle]
2332
+ pub extern "C" fn bloom_create_texture_array_ex(
2333
+ data_ptr: *const u8,
2334
+ data_len: f64,
2335
+ width: f64,
2336
+ height: f64,
2337
+ layer_count: f64,
2338
+ format: f64,
2339
+ mip_levels: f64,
2340
+ ) -> f64 {
2341
+ if data_ptr.is_null() || data_len <= 0.0 { return 0.0; }
2342
+ let w = width as u32;
2343
+ let h = height as u32;
2344
+ if w == 0 || h == 0 { return 0.0; }
2345
+ let layers_count = (layer_count as u32)
2346
+ .min(bloom_shared::renderer::material_system::MAX_TEXTURE_ARRAY_LAYERS);
2347
+ if layers_count == 0 { return 0.0; }
2348
+ let layer_size = (w as usize) * (h as usize) * 4;
2349
+ let total_bytes = (data_len as usize)
2350
+ .min(layers_count as usize * layer_size);
2351
+ let bytes = unsafe { std::slice::from_raw_parts(data_ptr, total_bytes) };
2352
+ let mut layers: Vec<(&[u8], u32, u32)> = Vec::with_capacity(layers_count as usize);
2353
+ for i in 0..(layers_count as usize) {
2354
+ let start = i * layer_size;
2355
+ let end = start + layer_size;
2356
+ if end > bytes.len() { break; }
2357
+ layers.push((&bytes[start..end], w, h));
2358
+ }
2359
+ engine().renderer.create_texture_array_ex(&layers, format as u32, mip_levels as u32) as f64
2360
+ }
2361
+
2362
+ /// EN-014 — link a texture-array handle to a material at one of three
2363
+ /// slots: 0 = albedo (binding 14), 1 = normal (binding 15),
2364
+ /// 2 = MR (binding 16). Pass `array = 0` to revert to the stub.
2365
+ #[no_mangle]
2366
+ pub extern "C" fn bloom_set_material_texture_array(
2367
+ material: f64, slot: f64, array: f64,
2368
+ ) {
2369
+ engine().renderer.set_material_texture_array(
2370
+ material as u32, slot as u32, array as u32,
2371
+ );
2372
+ }
2373
+
2374
+ /// EN-012 — set the shading model for a material (0=default lit,
2375
+ /// 1=foliage, 2=subsurface V2 stub).
2376
+ #[no_mangle]
2377
+ pub extern "C" fn bloom_set_material_shading_model(
2378
+ material: f64, model: f64,
2379
+ ) {
2380
+ engine().renderer.set_material_shading_model(material as u32, model as u32);
2381
+ }
2382
+
2383
+ /// EN-012 — set the foliage shading parameters for a material.
2384
+ /// Only takes effect when shading_model == 1 (foliage).
2385
+ #[no_mangle]
2386
+ pub extern "C" fn bloom_set_material_foliage(
2387
+ material: f64,
2388
+ trans_r: f64, trans_g: f64, trans_b: f64,
2389
+ trans_amount: f64, wrap_factor: f64,
2390
+ ) {
2391
+ engine().renderer.set_material_foliage(
2392
+ material as u32,
2393
+ [trans_r as f32, trans_g as f32, trans_b as f32],
2394
+ trans_amount as f32, wrap_factor as f32,
2395
+ );
2396
+ }
2397
+
2398
+ #[no_mangle]
2399
+ pub extern "C" fn bloom_compile_material_from_file(
2400
+ path_ptr: *const u8,
2401
+ bucket_kind: f64,
2402
+ ) -> f64 {
2403
+ use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
2404
+ let path = str_from_header(path_ptr);
2405
+ let (profile, bucket, reads_scene) = match bucket_kind as u32 {
2406
+ 0 => (FragmentProfile::Opaque, Bucket::Opaque, false),
2407
+ 1 => (FragmentProfile::Translucent, Bucket::Transparent, false),
2408
+ 2 => (FragmentProfile::Translucent, Bucket::Refractive, true),
2409
+ 3 => (FragmentProfile::Translucent, Bucket::Additive, false),
2410
+ 4 => (FragmentProfile::Opaque, Bucket::Cutout, false),
2411
+ _ => {
2412
+ eprintln!("[material] from_file: unknown bucket_kind {bucket_kind}");
2413
+ return 0.0;
2414
+ }
2415
+ };
2416
+ match engine().renderer.compile_material_from_file(
2417
+ std::path::Path::new(path), profile, bucket, reads_scene,
2418
+ ) {
2419
+ Ok(handle) => handle as f64,
2420
+ Err(e) => { eprintln!("[material] from_file failed: {e}"); 0.0 }
2421
+ }
2422
+ }
2423
+
2424
+ /// EN-017 — compile + install a fullscreen post-pass material.
2425
+ /// See `bloom-macos::bloom_set_post_pass` for the full ABI.
2426
+ #[no_mangle]
2427
+ pub extern "C" fn bloom_set_post_pass(source_ptr: *const u8) -> f64 {
2428
+ let source = str_from_header(source_ptr);
2429
+ match engine().renderer.set_post_pass(source) {
2430
+ Ok(()) => 1.0,
2431
+ Err(e) => { eprintln!("[post_pass] compile failed: {:?}", e); 0.0 }
2432
+ }
2433
+ }
2434
+
2435
+ /// EN-017 — uninstall the active post-pass.
2436
+ #[no_mangle]
2437
+ pub extern "C" fn bloom_clear_post_pass() {
2438
+ engine().renderer.clear_post_pass();
2439
+ }
2440
+
2441
+ /// EN-017 V2 — append a post-pass to the stack.
2442
+ /// See `bloom-macos::bloom_add_post_pass` for the full ABI.
2443
+ #[no_mangle]
2444
+ pub extern "C" fn bloom_add_post_pass(source_ptr: *const u8) -> f64 {
2445
+ let source = str_from_header(source_ptr);
2446
+ match engine().renderer.add_post_pass(source) {
2447
+ Ok(h) => h as f64,
2448
+ Err(e) => { eprintln!("[post_pass] compile failed: {:?}", e); 0.0 }
2449
+ }
2450
+ }
2451
+
2452
+ /// EN-017 V2 — wipe the entire post-pass stack.
2453
+ #[no_mangle]
2454
+ pub extern "C" fn bloom_clear_all_post_passes() {
2455
+ engine().renderer.clear_all_post_passes();
2456
+ }
2457
+
2458
+ #[no_mangle]
2459
+ pub extern "C" fn bloom_draw_material(
2460
+ material: f64,
2461
+ mesh_handle: f64,
2462
+ mesh_idx: f64,
2463
+ x: f64, y: f64, z: f64, scale: f64,
2464
+ r: f64, g: f64, b: f64, a: f64,
2465
+ ) {
2466
+ let eng = engine();
2467
+ let handle_bits = mesh_handle.to_bits();
2468
+ if let Some(model) = eng.models.get(mesh_handle) {
2469
+ eng.renderer.cache_model_if_static(handle_bits, &model.meshes);
2470
+ }
2471
+ eng.renderer.submit_material_draw(
2472
+ material as u32,
2473
+ handle_bits,
2474
+ mesh_idx as usize,
2475
+ [x as f32, y as f32, z as f32],
2476
+ scale as f32,
2477
+ [(r / 255.0) as f32, (g / 255.0) as f32, (b / 255.0) as f32, (a / 255.0) as f32],
2478
+ );
2479
+ }
2480
+
2481
+ #[no_mangle]
2482
+ pub extern "C" fn bloom_load_model_animation(path_ptr: *const u8) -> f64 {
2483
+ let path = str_from_header(path_ptr);
2484
+ match std::fs::read(resolve_path(path)) {
2485
+ Ok(data) => engine().models.load_model_animation(&data),
2486
+ Err(_) => 0.0,
2487
+ }
2488
+ }
2489
+
2490
+ #[no_mangle]
2491
+ pub extern "C" fn bloom_update_model_animation(handle: f64, anim_index: f64, time: f64, scale: f64, px: f64, py: f64, pz: f64, rot_sin: f64, rot_cos: f64) {
2492
+ let eng = engine();
2493
+ eng.models.update_model_animation(handle, anim_index as usize, time as f32);
2494
+ if let Some(anim) = eng.models.get_animation(handle) {
2495
+ if !anim.joint_matrices.is_empty() {
2496
+ eng.renderer.set_joint_matrices_scaled(&anim.joint_matrices, scale as f32, [px as f32, py as f32, pz as f32], rot_sin as f32, rot_cos as f32);
2497
+ }
2498
+ }
2499
+ }
2500
+
2501
+ #[no_mangle]
2502
+ pub extern "C" fn bloom_get_model_mesh_count(handle: f64) -> f64 {
2503
+ match engine().models.get(handle) {
2504
+ Some(model) => model.meshes.len() as f64,
2505
+ None => 0.0,
2506
+ }
2507
+ }
2508
+
2509
+ #[no_mangle]
2510
+ pub extern "C" fn bloom_get_model_material_count(handle: f64) -> f64 {
2511
+ match engine().models.get(handle) {
2512
+ Some(model) => model.meshes.len() as f64,
2513
+ None => 0.0,
2514
+ }
2515
+ }
2516
+
2517
+ // ============================================================
2518
+ // CoreAudio (iOS) — RemoteIO Audio Unit
2519
+ // ============================================================
2520
+
2521
+ type AudioUnit = *mut c_void;
2522
+ type OSStatus = i32;
2523
+ type AudioUnitPropertyID = u32;
2524
+ type AudioUnitScope = u32;
2525
+ type AudioUnitElement = u32;
2526
+
2527
+ #[repr(C)]
2528
+ #[derive(Clone, Copy)]
2529
+ struct AudioComponentDescription {
2530
+ component_type: u32,
2531
+ component_sub_type: u32,
2532
+ component_manufacturer: u32,
2533
+ component_flags: u32,
2534
+ component_flags_mask: u32,
2535
+ }
2536
+
2537
+ #[repr(C)]
2538
+ #[derive(Clone, Copy)]
2539
+ struct AudioStreamBasicDescription {
2540
+ sample_rate: f64,
2541
+ format_id: u32,
2542
+ format_flags: u32,
2543
+ bytes_per_packet: u32,
2544
+ frames_per_packet: u32,
2545
+ bytes_per_frame: u32,
2546
+ channels_per_frame: u32,
2547
+ bits_per_channel: u32,
2548
+ reserved: u32,
2549
+ }
2550
+
2551
+ #[repr(C)]
2552
+ struct AudioBufferList {
2553
+ number_buffers: u32,
2554
+ buffers: [AudioBufferData; 1],
2555
+ }
2556
+
2557
+ #[repr(C)]
2558
+ struct AudioBufferData {
2559
+ number_channels: u32,
2560
+ data_byte_size: u32,
2561
+ data: *mut c_void,
2562
+ }
2563
+
2564
+ type AURenderCallback = unsafe extern "C" fn(
2565
+ in_ref_con: *mut c_void,
2566
+ io_action_flags: *mut u32,
2567
+ in_time_stamp: *const c_void,
2568
+ in_bus_number: u32,
2569
+ in_number_frames: u32,
2570
+ io_data: *mut AudioBufferList,
2571
+ ) -> OSStatus;
2572
+
2573
+ #[repr(C)]
2574
+ struct AURenderCallbackStruct {
2575
+ input_proc: AURenderCallback,
2576
+ input_proc_ref_con: *mut c_void,
2577
+ }
2578
+
2579
+ type AudioComponent = *mut c_void;
2580
+
2581
+ #[link(name = "AudioToolbox", kind = "framework")]
2582
+ extern "C" {
2583
+ fn AudioComponentFindNext(component: AudioComponent, desc: *const AudioComponentDescription) -> AudioComponent;
2584
+ fn AudioComponentInstanceNew(component: AudioComponent, out: *mut AudioUnit) -> OSStatus;
2585
+ fn AudioUnitSetProperty(
2586
+ unit: AudioUnit,
2587
+ property_id: AudioUnitPropertyID,
2588
+ scope: AudioUnitScope,
2589
+ element: AudioUnitElement,
2590
+ data: *const c_void,
2591
+ data_size: u32,
2592
+ ) -> OSStatus;
2593
+ fn AudioUnitInitialize(unit: AudioUnit) -> OSStatus;
2594
+ fn AudioOutputUnitStart(unit: AudioUnit) -> OSStatus;
2595
+ fn AudioOutputUnitStop(unit: AudioUnit) -> OSStatus;
2596
+ fn AudioComponentInstanceDispose(unit: AudioUnit) -> OSStatus;
2597
+ }
2598
+
2599
+ const K_AUDIO_UNIT_TYPE_OUTPUT: u32 = u32::from_be_bytes(*b"auou");
2600
+ const K_AUDIO_UNIT_SUB_TYPE_REMOTE_IO: u32 = u32::from_be_bytes(*b"rioc");
2601
+ const K_AUDIO_UNIT_MANUFACTURER_APPLE: u32 = u32::from_be_bytes(*b"appl");
2602
+
2603
+ const K_AUDIO_UNIT_PROPERTY_STREAM_FORMAT: AudioUnitPropertyID = 8;
2604
+ const K_AUDIO_UNIT_PROPERTY_SET_RENDER_CALLBACK: AudioUnitPropertyID = 23;
2605
+ const K_AUDIO_UNIT_SCOPE_INPUT: AudioUnitScope = 1;
2606
+
2607
+ const K_AUDIO_FORMAT_LINEAR_PCM: u32 = u32::from_be_bytes(*b"lpcm");
2608
+ const K_AUDIO_FORMAT_FLAG_IS_FLOAT: u32 = 1;
2609
+ const K_AUDIO_FORMAT_FLAG_IS_PACKED: u32 = 8;
2610
+
2611
+ struct AudioUnitInstance { unit: AudioUnit }
2612
+ unsafe impl Send for AudioUnitInstance {}
2613
+ unsafe impl Sync for AudioUnitInstance {}
2614
+
2615
+ static mut AUDIO_UNIT: Option<AudioUnitInstance> = None;
2616
+
2617
+ unsafe extern "C" fn audio_render_callback(
2618
+ _in_ref_con: *mut c_void,
2619
+ _io_action_flags: *mut u32,
2620
+ _in_time_stamp: *const c_void,
2621
+ _in_bus_number: u32,
2622
+ in_number_frames: u32,
2623
+ io_data: *mut AudioBufferList,
2624
+ ) -> OSStatus {
2625
+ let buffer_list = &mut *io_data;
2626
+ let buffer = &mut buffer_list.buffers[0];
2627
+ let num_samples = in_number_frames as usize * 2;
2628
+ let output = std::slice::from_raw_parts_mut(buffer.data as *mut f32, num_samples);
2629
+ ENGINE.get_mut().map(|eng| { eng.audio.mix_output(output); });
2630
+ 0
2631
+ }
2632
+
2633
+ #[no_mangle]
2634
+ pub extern "C" fn bloom_init_audio() {
2635
+ unsafe {
2636
+ let desc = AudioComponentDescription {
2637
+ component_type: K_AUDIO_UNIT_TYPE_OUTPUT,
2638
+ component_sub_type: K_AUDIO_UNIT_SUB_TYPE_REMOTE_IO,
2639
+ component_manufacturer: K_AUDIO_UNIT_MANUFACTURER_APPLE,
2640
+ component_flags: 0,
2641
+ component_flags_mask: 0,
2642
+ };
2643
+
2644
+ let component = AudioComponentFindNext(std::ptr::null_mut(), &desc);
2645
+ if component.is_null() { return; }
2646
+
2647
+ let mut unit: AudioUnit = std::ptr::null_mut();
2648
+ if AudioComponentInstanceNew(component, &mut unit) != 0 { return; }
2649
+
2650
+ let stream_desc = AudioStreamBasicDescription {
2651
+ sample_rate: 44100.0,
2652
+ format_id: K_AUDIO_FORMAT_LINEAR_PCM,
2653
+ format_flags: K_AUDIO_FORMAT_FLAG_IS_FLOAT | K_AUDIO_FORMAT_FLAG_IS_PACKED,
2654
+ bytes_per_packet: 8,
2655
+ frames_per_packet: 1,
2656
+ bytes_per_frame: 8,
2657
+ channels_per_frame: 2,
2658
+ bits_per_channel: 32,
2659
+ reserved: 0,
2660
+ };
2661
+
2662
+ AudioUnitSetProperty(
2663
+ unit, K_AUDIO_UNIT_PROPERTY_STREAM_FORMAT, K_AUDIO_UNIT_SCOPE_INPUT, 0,
2664
+ &stream_desc as *const _ as *const c_void,
2665
+ std::mem::size_of::<AudioStreamBasicDescription>() as u32,
2666
+ );
2667
+
2668
+ let callback_struct = AURenderCallbackStruct {
2669
+ input_proc: audio_render_callback,
2670
+ input_proc_ref_con: std::ptr::null_mut(),
2671
+ };
2672
+
2673
+ AudioUnitSetProperty(
2674
+ unit, K_AUDIO_UNIT_PROPERTY_SET_RENDER_CALLBACK, K_AUDIO_UNIT_SCOPE_INPUT, 0,
2675
+ &callback_struct as *const _ as *const c_void,
2676
+ std::mem::size_of::<AURenderCallbackStruct>() as u32,
2677
+ );
2678
+
2679
+ AudioUnitInitialize(unit);
2680
+ AudioOutputUnitStart(unit);
2681
+ AUDIO_UNIT = Some(AudioUnitInstance { unit });
2682
+ }
2683
+ }
2684
+
2685
+ #[no_mangle]
2686
+ pub extern "C" fn bloom_close_audio() {
2687
+ unsafe {
2688
+ if let Some(au) = AUDIO_UNIT.take() {
2689
+ AudioOutputUnitStop(au.unit);
2690
+ AudioComponentInstanceDispose(au.unit);
2691
+ }
2692
+ }
2693
+ }
2694
+
2695
+ #[no_mangle]
2696
+ pub extern "C" fn bloom_load_sound(path_ptr: *const u8) -> f64 {
2697
+ let path = str_from_header(path_ptr);
2698
+ match std::fs::read(resolve_path(path)) {
2699
+ Ok(data) => {
2700
+ if let Some(s) = parse_wav(&data) {
2701
+ engine().audio.load_sound(s)
2702
+ } else if let Some(s) = parse_ogg(&data) {
2703
+ engine().audio.load_sound(s)
2704
+ } else if let Some(s) = parse_mp3(&data) {
2705
+ engine().audio.load_sound(s)
2706
+ } else {
2707
+ 0.0
2708
+ }
2709
+ }
2710
+ Err(_) => 0.0,
2711
+ }
2712
+ }
2713
+
2714
+ #[no_mangle]
2715
+ pub extern "C" fn bloom_play_sound(handle: f64) { engine().audio.play_sound(handle); }
2716
+ #[no_mangle]
2717
+ pub extern "C" fn bloom_stop_sound(handle: f64) { engine().audio.stop_sound(handle); }
2718
+ #[no_mangle]
2719
+ pub extern "C" fn bloom_set_sound_volume(handle: f64, volume: f64) { engine().audio.set_sound_volume(handle, volume as f32); }
2720
+ #[no_mangle]
2721
+ pub extern "C" fn bloom_set_master_volume(volume: f64) { engine().audio.master_volume = volume as f32; }
2722
+
2723
+ #[no_mangle]
2724
+ pub extern "C" fn bloom_play_sound_3d(handle: f64, x: f64, y: f64, z: f64) {
2725
+ engine().audio.play_sound_3d(handle, x as f32, y as f32, z as f32);
2726
+ }
2727
+
2728
+ #[no_mangle]
2729
+ pub extern "C" fn bloom_set_listener_position(x: f64, y: f64, z: f64, fx: f64, fy: f64, fz: f64) {
2730
+ engine().audio.set_listener_position(x as f32, y as f32, z as f32, fx as f32, fy as f32, fz as f32);
2731
+ }
2732
+
2733
+ // ============================================================
2734
+ // Music
2735
+ // ============================================================
2736
+
2737
+ #[no_mangle]
2738
+ pub extern "C" fn bloom_load_music(path_ptr: *const u8) -> f64 {
2739
+ let path = str_from_header(path_ptr);
2740
+ match std::fs::read(resolve_path(path)) {
2741
+ Ok(data) => {
2742
+ if let Some(s) = parse_ogg(&data) {
2743
+ engine().audio.load_music(s)
2744
+ } else if let Some(s) = parse_wav(&data) {
2745
+ engine().audio.load_music(s)
2746
+ } else if let Some(s) = parse_mp3(&data) {
2747
+ engine().audio.load_music(s)
2748
+ } else {
2749
+ 0.0
2750
+ }
2751
+ }
2752
+ Err(_) => 0.0,
2753
+ }
2754
+ }
2755
+
2756
+ #[no_mangle]
2757
+ pub extern "C" fn bloom_play_music(handle: f64) { engine().audio.play_music(handle); }
2758
+ #[no_mangle]
2759
+ pub extern "C" fn bloom_stop_music(handle: f64) { engine().audio.stop_music(handle); }
2760
+ #[no_mangle]
2761
+ pub extern "C" fn bloom_update_music_stream(handle: f64) { engine().audio.update_music_stream(handle); }
2762
+ #[no_mangle]
2763
+ pub extern "C" fn bloom_set_music_volume(handle: f64, volume: f64) { engine().audio.set_music_volume(handle, volume as f32); }
2764
+ #[no_mangle]
2765
+ pub extern "C" fn bloom_is_music_playing(handle: f64) -> f64 {
2766
+ if engine().audio.is_music_playing(handle) { 1.0 } else { 0.0 }
2767
+ }
2768
+
2769
+ // ============================================================
2770
+ // Staging / commit (thread-safe asset loading for ios-game-loop)
2771
+ // ============================================================
2772
+
2773
+ #[no_mangle]
2774
+ pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
2775
+ let path = str_from_header(path_ptr);
2776
+ match std::fs::read(resolve_path(path)) {
2777
+ Ok(data) => bloom_shared::staging::decode_and_stage_texture(&data),
2778
+ Err(_) => 0.0,
2779
+ }
2780
+ }
2781
+
2782
+ #[no_mangle]
2783
+ pub extern "C" fn bloom_stage_sound(path_ptr: *const u8) -> f64 {
2784
+ let path = str_from_header(path_ptr);
2785
+ let data = match std::fs::read(resolve_path(path)) {
2786
+ Ok(d) => d,
2787
+ Err(_) => return 0.0,
2788
+ };
2789
+ let sound_data = if path.ends_with(".ogg") || path.ends_with(".OGG") {
2790
+ parse_ogg(&data)
2791
+ } else if path.ends_with(".mp3") || path.ends_with(".MP3") {
2792
+ parse_mp3(&data)
2793
+ } else {
2794
+ parse_wav(&data)
2795
+ };
2796
+ match sound_data {
2797
+ Some(sd) => bloom_shared::staging::stage_sound(sd),
2798
+ None => 0.0,
2799
+ }
2800
+ }
2801
+
2802
+ #[no_mangle]
2803
+ pub extern "C" fn bloom_commit_texture(staging_handle: f64) -> f64 {
2804
+ let staged = match bloom_shared::staging::take_texture(staging_handle) {
2805
+ Some(s) => s,
2806
+ None => return 0.0,
2807
+ };
2808
+ let eng = engine();
2809
+ let bind_group_idx = eng.renderer.register_texture(staged.width, staged.height, &staged.data);
2810
+ eng.textures.textures.alloc(bloom_shared::textures::TextureData {
2811
+ bind_group_idx, width: staged.width, height: staged.height,
2812
+ })
2813
+ }
2814
+
2815
+ #[no_mangle]
2816
+ pub extern "C" fn bloom_commit_sound(staging_handle: f64) -> f64 {
2817
+ match bloom_shared::staging::take_sound(staging_handle) {
2818
+ Some(sd) => engine().audio.load_sound(sd),
2819
+ None => 0.0,
2820
+ }
2821
+ }
2822
+
2823
+ #[no_mangle]
2824
+ pub extern "C" fn bloom_commit_music(staging_handle: f64) -> f64 {
2825
+ match bloom_shared::staging::take_sound(staging_handle) {
2826
+ Some(sd) => engine().audio.load_music(sd),
2827
+ None => 0.0,
2828
+ }
2829
+ }
2830
+
2831
+ // ============================================================
2832
+ // Gamepad input
2833
+ // ============================================================
2834
+
2835
+ #[no_mangle]
2836
+ pub extern "C" fn bloom_is_gamepad_available(_gamepad: f64) -> f64 {
2837
+ if engine().input.is_gamepad_available() { 1.0 } else { 0.0 }
2838
+ }
2839
+
2840
+ #[no_mangle]
2841
+ pub extern "C" fn bloom_get_gamepad_axis(_gamepad: f64, axis: f64) -> f64 {
2842
+ engine().input.get_gamepad_axis(axis as usize) as f64
2843
+ }
2844
+
2845
+ #[no_mangle]
2846
+ pub extern "C" fn bloom_is_gamepad_button_pressed(_gamepad: f64, button: f64) -> f64 {
2847
+ if engine().input.is_gamepad_button_pressed(button as usize) { 1.0 } else { 0.0 }
2848
+ }
2849
+
2850
+ #[no_mangle]
2851
+ pub extern "C" fn bloom_is_gamepad_button_down(_gamepad: f64, button: f64) -> f64 {
2852
+ if engine().input.is_gamepad_button_down(button as usize) { 1.0 } else { 0.0 }
2853
+ }
2854
+
2855
+ #[no_mangle]
2856
+ pub extern "C" fn bloom_is_gamepad_button_released(_gamepad: f64, button: f64) -> f64 {
2857
+ if engine().input.is_gamepad_button_released(button as usize) { 1.0 } else { 0.0 }
2858
+ }
2859
+
2860
+ #[no_mangle]
2861
+ pub extern "C" fn bloom_get_gamepad_axis_count(_gamepad: f64) -> f64 {
2862
+ engine().input.get_gamepad_axis_count() as f64
2863
+ }
2864
+
2865
+ // ============================================================
2866
+ // Touch input
2867
+ // ============================================================
2868
+
2869
+ #[no_mangle]
2870
+ pub extern "C" fn bloom_get_touch_x(index: f64) -> f64 {
2871
+ engine().input.get_touch_x(index as usize)
2872
+ }
2873
+
2874
+ #[no_mangle]
2875
+ pub extern "C" fn bloom_get_touch_y(index: f64) -> f64 {
2876
+ engine().input.get_touch_y(index as usize)
2877
+ }
2878
+
2879
+ #[no_mangle]
2880
+ pub extern "C" fn bloom_get_touch_count() -> f64 {
2881
+ engine().input.get_touch_count() as f64
2882
+ }
2883
+
2884
+ // ============================================================
2885
+ // Utility
2886
+ // ============================================================
2887
+
2888
+ #[no_mangle]
2889
+ pub extern "C" fn bloom_toggle_fullscreen() {}
2890
+
2891
+ #[no_mangle]
2892
+ pub extern "C" fn bloom_set_window_title(_title_ptr: *const u8) {}
2893
+
2894
+ #[no_mangle]
2895
+ pub extern "C" fn bloom_set_window_icon(_path_ptr: *const u8) {}
2896
+
2897
+ #[no_mangle]
2898
+ pub extern "C" fn bloom_disable_cursor() {
2899
+ engine().input.cursor_disabled = true;
2900
+ }
2901
+
2902
+ #[no_mangle]
2903
+ pub extern "C" fn bloom_enable_cursor() {
2904
+ engine().input.cursor_disabled = false;
2905
+ }
2906
+
2907
+ #[no_mangle]
2908
+ pub extern "C" fn bloom_get_mouse_delta_x() -> f64 {
2909
+ engine().input.mouse_delta_x
2910
+ }
2911
+
2912
+ #[no_mangle]
2913
+ pub extern "C" fn bloom_get_mouse_delta_y() -> f64 {
2914
+ engine().input.mouse_delta_y
2915
+ }
2916
+
2917
+ // Accumulated scroll wheel delta since the last call. Reading consumes the
2918
+ // value (returns 0 on the next call until the user scrolls again). Used by
2919
+ // the editor's orbit camera and any scrollable UI panel.
2920
+ #[no_mangle]
2921
+ pub extern "C" fn bloom_get_mouse_wheel() -> f64 {
2922
+ engine().input.consume_mouse_wheel()
2923
+ }
2924
+
2925
+ #[no_mangle]
2926
+ pub extern "C" fn bloom_get_char_pressed() -> f64 {
2927
+ engine().input.pop_char() as f64
2928
+ }
2929
+
2930
+ // Q2: Cursor shape
2931
+ #[no_mangle]
2932
+ pub extern "C" fn bloom_set_cursor_shape(shape: f64) {
2933
+ engine().input.cursor_shape = shape as u32;
2934
+ }
2935
+
2936
+ // E4: Clipboard (stub on this platform)
2937
+ #[no_mangle]
2938
+ pub extern "C" fn bloom_set_clipboard_text(_text_ptr: *const u8) {}
2939
+ #[no_mangle]
2940
+ pub extern "C" fn bloom_get_clipboard_text() -> *const u8 { std::ptr::null() }
2941
+
2942
+ // E5b: File dialogs (stub on this platform)
2943
+ #[no_mangle]
2944
+ pub extern "C" fn bloom_open_file_dialog(_filter_ptr: *const u8, _title_ptr: *const u8) -> *const u8 { std::ptr::null() }
2945
+ #[no_mangle]
2946
+ pub extern "C" fn bloom_save_file_dialog(_default_name_ptr: *const u8, _title_ptr: *const u8) -> *const u8 { std::ptr::null() }
2947
+
2948
+ // Model bounds accessors. Return the axis-aligned bounding box of a loaded
2949
+ // model in model-local coordinates. Editors use these to size gizmos, auto-
2950
+ // frame the camera on selection, and snap placed entities onto terrain.
2951
+ #[no_mangle]
2952
+ pub extern "C" fn bloom_get_model_bounds_min_x(model_handle: f64) -> f64 {
2953
+ engine().models.get_bounds(model_handle).0[0] as f64
2954
+ }
2955
+ #[no_mangle]
2956
+ pub extern "C" fn bloom_get_model_bounds_min_y(model_handle: f64) -> f64 {
2957
+ engine().models.get_bounds(model_handle).0[1] as f64
2958
+ }
2959
+ #[no_mangle]
2960
+ pub extern "C" fn bloom_get_model_bounds_min_z(model_handle: f64) -> f64 {
2961
+ engine().models.get_bounds(model_handle).0[2] as f64
2962
+ }
2963
+ #[no_mangle]
2964
+ pub extern "C" fn bloom_get_model_bounds_max_x(model_handle: f64) -> f64 {
2965
+ engine().models.get_bounds(model_handle).1[0] as f64
2966
+ }
2967
+ #[no_mangle]
2968
+ pub extern "C" fn bloom_get_model_bounds_max_y(model_handle: f64) -> f64 {
2969
+ engine().models.get_bounds(model_handle).1[1] as f64
2970
+ }
2971
+ #[no_mangle]
2972
+ pub extern "C" fn bloom_get_model_bounds_max_z(model_handle: f64) -> f64 {
2973
+ engine().models.get_bounds(model_handle).1[2] as f64
2974
+ }
2975
+
2976
+ #[no_mangle]
2977
+ pub extern "C" fn bloom_write_file(path_ptr: *const u8, data_ptr: *const u8) -> f64 {
2978
+ let path = str_from_header(path_ptr);
2979
+ let data = str_from_header(data_ptr);
2980
+ match std::fs::write(path, data.as_bytes()) {
2981
+ Ok(_) => 1.0,
2982
+ Err(_) => 0.0,
2983
+ }
2984
+ }
2985
+
2986
+ #[no_mangle]
2987
+ pub extern "C" fn bloom_file_exists(path_ptr: *const u8) -> f64 {
2988
+ let path = str_from_header(path_ptr);
2989
+ let resolved = resolve_path(path);
2990
+ if std::path::Path::new(&resolved).exists() { 1.0 } else { 0.0 }
2991
+ }
2992
+
2993
+ #[no_mangle]
2994
+ pub extern "C" fn bloom_read_file(path_ptr: *const u8) -> *const u8 {
2995
+ let path = str_from_header(path_ptr);
2996
+ match std::fs::read_to_string(resolve_path(path)) {
2997
+ Ok(contents) => {
2998
+ // Return Perry-format string: StringHeader (length u32 + capacity u32) followed by UTF-8 data
2999
+ let bytes = contents.as_bytes();
3000
+ let len = bytes.len();
3001
+ let total = 8 + len; // 8 bytes header + data
3002
+ let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
3003
+ unsafe {
3004
+ let ptr = std::alloc::alloc(layout);
3005
+ if ptr.is_null() { return std::ptr::null(); }
3006
+ // Write length and capacity as u32
3007
+ *(ptr as *mut u32) = len as u32;
3008
+ *(ptr.add(4) as *mut u32) = len as u32;
3009
+ // Copy string data after header
3010
+ std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(8), len);
3011
+ ptr
3012
+ }
3013
+ }
3014
+ Err(_) => std::ptr::null(),
3015
+ }
3016
+ }
3017
+
3018
+ #[no_mangle]
3019
+ pub extern "C" fn bloom_get_time() -> f64 {
3020
+ engine().get_time()
3021
+ }
3022
+
3023
+ // ============================================================
3024
+ // Input injection + platform detection
3025
+ // ============================================================
3026
+
3027
+ #[no_mangle]
3028
+ pub extern "C" fn bloom_inject_key_down(key: f64) {
3029
+ engine().input.set_key_down(key as usize);
3030
+ }
3031
+ #[no_mangle]
3032
+ pub extern "C" fn bloom_inject_key_up(key: f64) {
3033
+ engine().input.set_key_up(key as usize);
3034
+ }
3035
+ #[no_mangle]
3036
+ pub extern "C" fn bloom_inject_gamepad_axis(axis: f64, value: f64) {
3037
+ engine().input.set_gamepad_axis(axis as usize, value as f32);
3038
+ }
3039
+ #[no_mangle]
3040
+ pub extern "C" fn bloom_inject_gamepad_button_down(button: f64) {
3041
+ engine().input.set_gamepad_button_down(button as usize);
3042
+ }
3043
+ #[no_mangle]
3044
+ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
3045
+ engine().input.set_gamepad_button_up(button as usize);
3046
+ }
3047
+ #[no_mangle]
3048
+ pub extern "C" fn bloom_get_platform() -> f64 { 6.0 }
3049
+ #[no_mangle]
3050
+ pub extern "C" fn bloom_get_crown_rotation() -> f64 {
3051
+ engine().input.consume_crown_rotation()
3052
+ }
3053
+ #[no_mangle]
3054
+ pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
3055
+ if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
3056
+ }
3057
+
3058
+ #[no_mangle]
3059
+ pub extern "C" fn bloom_run_game(_callback: extern "C" fn(f64)) {
3060
+ // No-op on native. The TypeScript runGame() helper provides the while loop.
3061
+ }
3062
+
3063
+
3064
+ // Q6: Multi-hit picking
3065
+ static mut LAST_PICK_ALL: Vec<bloom_shared::picking::PickResult> = Vec::new();
3066
+
3067
+ #[no_mangle]
3068
+ pub extern "C" fn bloom_scene_pick_all(screen_x: f64, screen_y: f64, max_results: f64) -> f64 {
3069
+ let eng = engine();
3070
+ let inv_vp = eng.renderer.inverse_vp_matrix();
3071
+ let cam_pos = eng.renderer.camera_pos();
3072
+ let w = eng.renderer.width() as f32;
3073
+ let h = eng.renderer.height() as f32;
3074
+ let (origin, direction) = bloom_shared::picking::screen_to_ray(
3075
+ screen_x as f32, screen_y as f32, w, h, &inv_vp, &cam_pos,
3076
+ );
3077
+ let results = bloom_shared::picking::raycast_scene_all(&eng.scene, &origin, &direction, max_results as usize);
3078
+ let count = results.len();
3079
+ unsafe { LAST_PICK_ALL = results; }
3080
+ count as f64
3081
+ }
3082
+ #[no_mangle]
3083
+ pub extern "C" fn bloom_pick_all_handle(index: f64) -> f64 {
3084
+ let i = index as usize;
3085
+ unsafe { LAST_PICK_ALL.get(i).map(|r| r.handle).unwrap_or(0.0) }
3086
+ }
3087
+ #[no_mangle]
3088
+ pub extern "C" fn bloom_pick_all_distance(index: f64) -> f64 {
3089
+ let i = index as usize;
3090
+ unsafe { LAST_PICK_ALL.get(i).map(|r| r.distance as f64).unwrap_or(0.0) }
3091
+ }
3092
+ // ============================================================
3093
+
3094
+ // ============================================================
3095
+ // Render quality toggles (individual + preset) — ticket 011
3096
+ // Mirror of the macOS FFI surface added in commit 95da6af; previously
3097
+ // macOS-only, now exposed on every native platform so non-macOS builds
3098
+ // don't fail at runtime (missing symbol) when the TS API invokes them.
3099
+ // ============================================================
3100
+
3101
+ #[no_mangle]
3102
+ pub extern "C" fn bloom_set_quality_preset(preset: f64) {
3103
+ engine().renderer.apply_quality_preset(preset as u32);
3104
+ }
3105
+ #[no_mangle]
3106
+ pub extern "C" fn bloom_set_shadows_enabled(on: f64) {
3107
+ engine().renderer.set_shadows_enabled(on != 0.0);
3108
+ }
3109
+ #[no_mangle]
3110
+ pub extern "C" fn bloom_set_shadows_always_fresh(on: f64) {
3111
+ engine().renderer.set_shadows_always_fresh(on != 0.0);
3112
+ }
3113
+ #[no_mangle]
3114
+ pub extern "C" fn bloom_set_bloom_enabled(on: f64) {
3115
+ engine().renderer.set_bloom_enabled(on != 0.0);
3116
+ }
3117
+ #[no_mangle]
3118
+ pub extern "C" fn bloom_set_ssao_enabled(on: f64) {
3119
+ engine().renderer.set_ssao_enabled(on != 0.0);
3120
+ }
3121
+ #[no_mangle]
3122
+ pub extern "C" fn bloom_set_ssao_intensity(value: f64) {
3123
+ engine().renderer.set_ssao_strength(value as f32);
3124
+ }
3125
+ #[no_mangle]
3126
+ pub extern "C" fn bloom_set_ssao_radius(world_radius: f64) {
3127
+ engine().renderer.set_ssao_radius(world_radius as f32);
3128
+ }
3129
+ #[no_mangle]
3130
+ pub extern "C" fn bloom_set_wind(dir_x: f64, dir_z: f64, amplitude: f64, frequency: f64) {
3131
+ engine().renderer.set_wind(dir_x as f32, dir_z as f32, amplitude as f32, frequency as f32);
3132
+ }
3133
+ #[no_mangle]
3134
+ pub extern "C" fn bloom_set_ssr_enabled(on: f64) {
3135
+ engine().renderer.set_ssr_enabled(on != 0.0);
3136
+ }
3137
+ #[no_mangle]
3138
+ pub extern "C" fn bloom_set_motion_blur_enabled(on: f64) {
3139
+ engine().renderer.set_motion_blur_enabled(on != 0.0);
3140
+ }
3141
+ #[no_mangle]
3142
+ pub extern "C" fn bloom_set_sss_enabled(on: f64) {
3143
+ engine().renderer.set_sss_enabled(on != 0.0);
3144
+ }
3145
+
3146
+ // ============================================================
3147
+ // Profiler — CPU phase timings (always available) + GPU timestamps
3148
+ // (when the adapter supports TIMESTAMP_QUERY). Disabled by default.
3149
+ // ============================================================
3150
+
3151
+ #[no_mangle]
3152
+ pub extern "C" fn bloom_set_profiler_enabled(on: f64) {
3153
+ engine().profiler.set_enabled(on != 0.0);
3154
+ }
3155
+ #[no_mangle]
3156
+ pub extern "C" fn bloom_get_profiler_frame_cpu_us() -> f64 {
3157
+ engine().profiler.avg_frame_cpu_us()
3158
+ }
3159
+ #[no_mangle]
3160
+ pub extern "C" fn bloom_get_profiler_frame_gpu_us() -> f64 {
3161
+ engine().profiler.avg_frame_gpu_us()
3162
+ }
3163
+ #[no_mangle]
3164
+ pub extern "C" fn bloom_print_profiler_summary() {
3165
+ print!("{}", engine().profiler.summary());
3166
+ }
3167
+
3168
+ // ============================================================
3169
+ // Physics (Jolt 5.x) — FFI surface generated from shared macro
3170
+ // ============================================================
3171
+
3172
+ #[cfg(feature = "jolt")]
3173
+ #[inline]
3174
+ fn bloom_jolt_ffi_physics() -> &'static mut bloom_shared::physics_jolt::JoltPhysics {
3175
+ &mut engine().jolt
3176
+ }
3177
+
3178
+ #[cfg(feature = "jolt")]
3179
+ bloom_shared::define_physics_ffi!();