@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.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +20 -0
- package/native/android/src/lib.rs +1747 -0
- package/native/ios/Cargo.lock +1688 -0
- package/native/ios/Cargo.toml +19 -0
- package/native/ios/src/lib.rs +2258 -0
- package/native/linux/Cargo.lock +1719 -0
- package/native/linux/Cargo.toml +22 -0
- package/native/linux/src/lib.rs +2236 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +29 -0
- package/native/macos/src/lib.rs +3444 -0
- package/native/shared/Cargo.lock +1898 -0
- package/native/shared/Cargo.toml +42 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +77 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +77 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +53 -0
- package/native/shared/shaders/material_abi.wgsl +360 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/audio.rs +363 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +186 -0
- package/native/shared/src/frame_callbacks.rs +88 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +76 -0
- package/native/shared/src/input.rs +273 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +43 -0
- package/native/shared/src/models.rs +1941 -0
- package/native/shared/src/physics_jolt.rs +1528 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +339 -0
- package/native/shared/src/profiler.rs +416 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/formats.rs +778 -0
- package/native/shared/src/renderer/graph.rs +465 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +455 -0
- package/native/shared/src/renderer/material_pipeline.rs +604 -0
- package/native/shared/src/renderer/material_system.rs +2106 -0
- package/native/shared/src/renderer/mod.rs +13923 -0
- package/native/shared/src/renderer/planar_reflection.rs +458 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +134 -0
- package/native/shared/src/renderer/shaders.rs +5855 -0
- package/native/shared/src/renderer/transient.rs +576 -0
- package/native/shared/src/renderer/types.rs +259 -0
- package/native/shared/src/renderer/util.rs +151 -0
- package/native/shared/src/scene.rs +1066 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +551 -0
- package/native/shared/src/staging.rs +90 -0
- package/native/shared/src/string_header.rs +35 -0
- package/native/shared/src/text_renderer.rs +456 -0
- package/native/shared/src/textures.rs +154 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.cpp +242 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeBuilder.h +121 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/AABBTreeToBuffer.h +296 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h +323 -0
- package/native/third_party/JoltPhysics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h +555 -0
- package/native/third_party/JoltPhysics/Jolt/ConfigurationString.h +112 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ARMNeon.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Array.h +713 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Atomics.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Core/BinaryHeap.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ByteBuffer.h +74 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Color.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Color.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Core.h +652 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPControlWord.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPException.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FPFlushDenormals.h +43 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Factory.cpp +92 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Factory.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.h +122 -0
- package/native/third_party/JoltPhysics/Jolt/Core/FixedSizeFreeList.inl +215 -0
- package/native/third_party/JoltPhysics/Jolt/Core/HashCombine.h +234 -0
- package/native/third_party/JoltPhysics/Jolt/Core/HashTable.h +876 -0
- package/native/third_party/JoltPhysics/Jolt/Core/InsertionSort.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.cpp +27 -0
- package/native/third_party/JoltPhysics/Jolt/Core/IssueReporting.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.h +311 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystem.inl +56 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.cpp +65 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemSingleThreaded.h +62 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.cpp +364 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemThreadPool.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.cpp +230 -0
- package/native/third_party/JoltPhysics/Jolt/Core/JobSystemWithBarrier.h +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.cpp +51 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LinearCurve.h +67 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.h +182 -0
- package/native/third_party/JoltPhysics/Jolt/Core/LockFreeHashMap.inl +351 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Memory.cpp +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Memory.h +85 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Mutex.h +223 -0
- package/native/third_party/JoltPhysics/Jolt/Core/MutexArray.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Core/NonCopyable.h +18 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.cpp +677 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.h +301 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Profiler.inl +90 -0
- package/native/third_party/JoltPhysics/Jolt/Core/QuickSort.h +137 -0
- package/native/third_party/JoltPhysics/Jolt/Core/RTTI.cpp +149 -0
- package/native/third_party/JoltPhysics/Jolt/Core/RTTI.h +436 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Reference.h +244 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Result.h +174 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLAlignedAllocator.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLAllocator.h +127 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLLocalAllocator.h +170 -0
- package/native/third_party/JoltPhysics/Jolt/Core/STLTempAllocator.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Core/ScopeExit.h +49 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.cpp +135 -0
- package/native/third_party/JoltPhysics/Jolt/Core/Semaphore.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StaticArray.h +329 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamIn.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamOut.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamUtils.h +168 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StreamWrapper.h +53 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StridedPtr.h +63 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StringTools.cpp +101 -0
- package/native/third_party/JoltPhysics/Jolt/Core/StringTools.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TempAllocator.h +209 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.cpp +37 -0
- package/native/third_party/JoltPhysics/Jolt/Core/TickCounter.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Core/UnorderedMap.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Core/UnorderedSet.h +32 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/AABox.h +313 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/AABox4.h +224 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ClipPoly.h +200 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ClosestPoint.h +498 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.cpp +1467 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder.h +276 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.cpp +335 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexHullBuilder2D.h +105 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/ConvexSupport.h +188 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/EPAConvexHullBuilder.h +845 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/EPAPenetrationDepth.h +557 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Ellipse.h +77 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/GJKClosestPoint.h +945 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/IndexedTriangle.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.cpp +222 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Indexify.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/MortonCode.h +40 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.cpp +178 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/OrientedBox.h +39 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Plane.h +104 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayAABox.h +241 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayCapsule.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayCylinder.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RaySphere.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/RayTriangle.h +158 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Sphere.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Geometry/Triangle.h +34 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.cmake +703 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Jolt.natvis +116 -0
- package/native/third_party/JoltPhysics/Jolt/Math/BVec16.h +99 -0
- package/native/third_party/JoltPhysics/Jolt/Math/BVec16.inl +177 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DMat44.h +158 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DMat44.inl +310 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DVec3.h +291 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DVec3.inl +941 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Double3.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Math/DynMatrix.h +31 -0
- package/native/third_party/JoltPhysics/Jolt/Math/EigenValueSymmetric.h +177 -0
- package/native/third_party/JoltPhysics/Jolt/Math/FindRoot.h +42 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float2.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float3.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Float4.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Math/GaussianElimination.h +102 -0
- package/native/third_party/JoltPhysics/Jolt/Math/HalfFloat.h +208 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Mat44.h +243 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Mat44.inl +952 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Math.h +208 -0
- package/native/third_party/JoltPhysics/Jolt/Math/MathTypes.h +32 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Matrix.h +259 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Quat.h +268 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Quat.inl +406 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Real.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Swizzle.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Trigonometry.h +79 -0
- package/native/third_party/JoltPhysics/Jolt/Math/UVec4.h +232 -0
- package/native/third_party/JoltPhysics/Jolt/Math/UVec4.inl +636 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.cpp +71 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.h +308 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec3.inl +942 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec4.h +320 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vec4.inl +1152 -0
- package/native/third_party/JoltPhysics/Jolt/Math/Vector.h +211 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/GetPrimitiveTypeOfType.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStream.h +337 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.cpp +252 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryIn.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.cpp +165 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamBinaryOut.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.cpp +635 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamIn.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.cpp +166 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamOut.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.cpp +418 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextIn.h +55 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.cpp +255 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTextOut.h +62 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/ObjectStreamTypes.h +26 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttribute.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeEnum.h +67 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableAttributeTyped.h +60 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.cpp +15 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/SerializableObject.h +170 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.cpp +70 -0
- package/native/third_party/JoltPhysics/Jolt/ObjectStream/TypeDeclarations.h +45 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/AllowedDOFs.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.cpp +426 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.h +452 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/Body.inl +197 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyAccess.h +68 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyActivationListener.h +28 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.cpp +234 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyCreationSettings.h +124 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyFilter.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyID.h +101 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.cpp +1099 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyInterface.h +324 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLock.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockInterface.h +134 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyLockMulti.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.cpp +1220 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyManager.h +403 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyPair.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/BodyType.h +19 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.cpp +185 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MassProperties.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.cpp +92 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.h +308 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionProperties.inl +178 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionQuality.h +31 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Body/MotionType.h +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.cpp +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/Character.h +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.cpp +59 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterBase.h +157 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterID.h +98 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.cpp +1933 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Character/CharacterVirtual.h +752 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/AABoxCast.h +20 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdgeMode.h +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ActiveEdges.h +114 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BackFaceMode.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.cpp +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h +109 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp +313 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.h +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceMask.h +92 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseLayerInterfaceTable.h +64 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp +629 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h +108 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuery.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterMask.h +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/ObjectVsBroadPhaseLayerFilterTable.h +66 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp +1768 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/BroadPhase/QuadTree.h +389 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.cpp +107 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastConvexVsTriangles.h +46 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastResult.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp +223 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CastSphereVsTriangles.h +49 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollectFacesMode.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.cpp +155 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideConvexVsTriangles.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollidePointResult.h +25 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShape.h +106 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h +110 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h +102 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.cpp +121 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollideSphereVsTriangles.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollector.h +109 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionCollectorImpl.h +219 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.cpp +107 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionDispatch.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/CollisionGroup.h +97 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ContactListener.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.cpp +213 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/EstimateCollisionResponse.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.cpp +32 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilter.h +46 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/GroupFilterTable.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/InternalEdgeRemovingCollector.h +279 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.cpp +271 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h +44 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.cpp +448 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseQuery.h +77 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.cpp +62 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/NarrowPhaseStats.h +110 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayer.h +111 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterMask.h +52 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ObjectLayerPairFilterTable.h +78 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterial.h +57 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.cpp +38 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/PhysicsMaterialSimple.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/RayCast.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.cpp +318 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/BoxShape.h +115 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.cpp +438 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CapsuleShape.h +129 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.cpp +433 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShape.h +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h +461 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp +1311 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexHullShape.h +202 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.cpp +566 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ConvexShape.h +150 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.cpp +418 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/CylinderShape.h +126 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.cpp +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/DecoratedShape.h +80 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.cpp +64 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/EmptyShape.h +75 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/GetTrianglesContext.h +248 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp +2754 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/HeightFieldShape.h +380 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.cpp +1305 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MeshShape.h +228 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp +596 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/MutableCompoundShape.h +176 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp +217 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h +140 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.cpp +541 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PlaneShape.h +147 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h +319 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp +333 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h +161 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaleHelpers.h +83 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.cpp +238 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/ScaledShape.h +145 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.cpp +325 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/Shape.h +466 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.cpp +347 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SphereShape.h +125 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.cpp +674 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/StaticCompoundShape.h +139 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeID.h +138 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h +65 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp +453 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.gliffy +1 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h +135 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp +691 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.h +132 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.cpp +430 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/Shape/TriangleShape.h +143 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeCast.h +173 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/ShapeFilter.h +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilter.h +40 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SimShapeFilterWrapper.h +58 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/SortReverseAndStore.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.cpp +180 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Collision/TransformedShape.h +194 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/CalculateSolverSteps.h +70 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.cpp +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConeConstraint.h +133 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.cpp +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/Constraint.h +243 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.cpp +289 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintManager.h +100 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h +257 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h +682 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h +276 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h +195 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h +222 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/IndependentAxisConstraintPart.h +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/PointConstraintPart.h +239 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h +196 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h +283 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h +246 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SpringPart.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h +597 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.cpp +1804 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/ContactConstraintManager.h +524 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.cpp +266 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/DistanceConstraint.h +120 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.cpp +215 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/FixedConstraint.h +96 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.cpp +188 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/GearConstraint.h +116 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.cpp +443 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/HingeConstraint.h +205 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.cpp +43 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/MotorSettings.h +66 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.cpp +458 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraint.h +191 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.cpp +85 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPath.h +76 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp +308 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PathConstraintPathHermite.h +54 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.cpp +157 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PointConstraint.h +94 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.cpp +253 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/PulleyConstraint.h +137 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.cpp +189 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/RackAndPinionConstraint.h +118 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.cpp +900 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SixDOFConstraint.h +289 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.cpp +501 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SliderConstraint.h +198 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.cpp +35 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SpringSettings.h +70 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.cpp +524 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/SwingTwistConstraint.h +197 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.cpp +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Constraints/TwoBodyConstraint.h +65 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.cpp +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/DeterminismLog.h +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/EActivation.h +16 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/EPhysicsUpdateError.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.cpp +492 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/IslandBuilder.h +144 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.cpp +582 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/LargeIslandSplitter.h +187 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsLock.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.cpp +261 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsScene.h +104 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSettings.h +125 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsStepListener.h +37 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.cpp +2915 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsSystem.h +391 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.cpp +25 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/PhysicsUpdateContext.h +176 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.cpp +744 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Ragdoll/Ragdoll.h +245 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyContactListener.h +55 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.cpp +128 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyCreationSettings.h +75 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyManifold.h +74 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp +1501 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h +333 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.cpp +354 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyShape.h +73 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp +1487 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h +390 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyUpdateContext.h +63 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/SoftBody/SoftBodyVertex.h +36 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorder.h +136 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.cpp +90 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/StateRecorderImpl.h +50 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.cpp +306 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/MotorcycleController.h +119 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.cpp +547 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/TrackedVehicleController.h +169 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.cpp +33 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleAntiRollBar.h +33 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp +376 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleCollisionTester.h +146 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.cpp +703 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleConstraint.h +252 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.cpp +17 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleController.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.cpp +81 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleDifferential.h +39 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.cpp +122 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleEngine.h +93 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.cpp +52 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTrack.h +56 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.cpp +159 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/VehicleTransmission.h +87 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.cpp +93 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/Wheel.h +148 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.cpp +866 -0
- package/native/third_party/JoltPhysics/Jolt/Physics/Vehicle/WheeledVehicleController.h +205 -0
- package/native/third_party/JoltPhysics/Jolt/RegisterTypes.cpp +204 -0
- package/native/third_party/JoltPhysics/Jolt/RegisterTypes.h +29 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.cpp +1107 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRenderer.h +383 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.cpp +168 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererPlayback.h +48 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.cpp +158 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererRecorder.h +130 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.cpp +80 -0
- package/native/third_party/JoltPhysics/Jolt/Renderer/DebugRendererSimple.h +88 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.cpp +165 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletalAnimation.h +91 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.cpp +82 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/Skeleton.h +72 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.cpp +237 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonMapper.h +145 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.cpp +87 -0
- package/native/third_party/JoltPhysics/Jolt/Skeleton/SkeletonPose.h +82 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.cpp +73 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitter.h +84 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.cpp +139 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterBinning.h +52 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.cpp +43 -0
- package/native/third_party/JoltPhysics/Jolt/TriangleSplitter/TriangleSplitterMean.h +28 -0
- package/native/third_party/JoltPhysics/LICENSE +7 -0
- package/native/third_party/JoltPhysics/README.md +173 -0
- package/native/third_party/bloom_jolt/CMakeLists.txt +78 -0
- package/native/third_party/bloom_jolt/include/bloom_jolt.h +519 -0
- package/native/third_party/bloom_jolt/src/bloom_jolt.cpp +1780 -0
- package/native/tvos/Cargo.lock +1692 -0
- package/native/tvos/Cargo.toml +22 -0
- package/native/tvos/src/lib.rs +3179 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1236 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +223 -0
- package/native/watchos/src/ffi_stubs.rs +454 -0
- package/native/watchos/src/lib.rs +1013 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +91 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1656 -0
- package/native/web/Cargo.toml +38 -0
- package/native/web/bloom_glue.js +218 -0
- package/native/web/build.sh +101 -0
- package/native/web/index.html +390 -0
- package/native/web/jolt_bridge.js +1311 -0
- package/native/web/src/lib.rs +2739 -0
- package/native/windows/Cargo.lock +1813 -0
- package/native/windows/Cargo.toml +31 -0
- package/native/windows/src/lib.rs +1933 -0
- package/package.json +558 -0
- package/src/audio/index.ts +151 -0
- package/src/core/colors.ts +56 -0
- package/src/core/index.ts +903 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +102 -0
- package/src/index.ts +158 -0
- package/src/math/index.ts +502 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +859 -0
- package/src/physics/index.ts +1072 -0
- package/src/scene/index.ts +570 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +173 -0
- package/src/world/index.ts +22 -0
- package/src/world/loader.ts +385 -0
- package/src/world/prefab.ts +205 -0
- package/src/world/saver.ts +61 -0
- package/src/world/terrain.ts +254 -0
- package/src/world/types.ts +136 -0
- package/src/world/validate.ts +202 -0
- package/src/world/version.ts +47 -0
|
@@ -0,0 +1,3444 @@
|
|
|
1
|
+
// `static mut` is intentional throughout this FFI surface — Perry calls
|
|
2
|
+
// us from a single OS thread (the macOS run-loop), so the engine
|
|
3
|
+
// singleton + scratch state never race. The 2024 lint flagging
|
|
4
|
+
// `&LAST_PICK`-style accesses is a real concern in multi-threaded
|
|
5
|
+
// code, but inapplicable here. Suppress at the crate root to avoid
|
|
6
|
+
// 16+ noise lines in every build.
|
|
7
|
+
#![allow(static_mut_refs)]
|
|
8
|
+
|
|
9
|
+
use bloom_shared::engine::EngineState;
|
|
10
|
+
use bloom_shared::renderer::Renderer;
|
|
11
|
+
use bloom_shared::string_header::str_from_header;
|
|
12
|
+
use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3};
|
|
13
|
+
|
|
14
|
+
use objc2::rc::Retained;
|
|
15
|
+
use objc2::{msg_send, MainThreadMarker, MainThreadOnly};
|
|
16
|
+
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEventMask, NSEventType, NSWindow, NSWindowStyleMask};
|
|
17
|
+
use objc2_foundation::{NSDate, NSDefaultRunLoopMode, NSPoint, NSRect, NSSize, NSString};
|
|
18
|
+
|
|
19
|
+
use raw_window_handle::{RawWindowHandle, AppKitWindowHandle};
|
|
20
|
+
use std::sync::OnceLock;
|
|
21
|
+
|
|
22
|
+
static mut ENGINE: OnceLock<EngineState> = OnceLock::new();
|
|
23
|
+
static mut WINDOW: Option<Retained<NSWindow>> = None;
|
|
24
|
+
// Set by bloom_init_window when BLOOM_HEADLESS=1. Skips the
|
|
25
|
+
// `!isVisible → should_close` shortcut (hidden windows aren't
|
|
26
|
+
// 'closed', just invisible) so headless --capture can run to
|
|
27
|
+
// completion.
|
|
28
|
+
static mut HEADLESS: bool = false;
|
|
29
|
+
static mut AUDIO_UNIT: Option<AudioUnitInstance> = None;
|
|
30
|
+
|
|
31
|
+
fn engine() -> &'static mut EngineState {
|
|
32
|
+
unsafe { ENGINE.get_mut().expect("Engine not initialized") }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Map macOS virtual key code to Bloom key code.
|
|
36
|
+
fn map_keycode(keycode: u16) -> usize {
|
|
37
|
+
match keycode {
|
|
38
|
+
0 => 65, // A
|
|
39
|
+
1 => 83, // S
|
|
40
|
+
2 => 68, // D
|
|
41
|
+
3 => 70, // F
|
|
42
|
+
4 => 72, // H
|
|
43
|
+
5 => 71, // G
|
|
44
|
+
6 => 90, // Z
|
|
45
|
+
7 => 88, // X
|
|
46
|
+
8 => 67, // C
|
|
47
|
+
9 => 86, // V
|
|
48
|
+
11 => 66, // B
|
|
49
|
+
12 => 81, // Q
|
|
50
|
+
13 => 87, // W
|
|
51
|
+
14 => 69, // E
|
|
52
|
+
15 => 82, // R
|
|
53
|
+
16 => 89, // Y
|
|
54
|
+
17 => 84, // T
|
|
55
|
+
18 => 49, // 1
|
|
56
|
+
19 => 50, // 2
|
|
57
|
+
20 => 51, // 3
|
|
58
|
+
21 => 52, // 4
|
|
59
|
+
22 => 54, // 6
|
|
60
|
+
23 => 53, // 5
|
|
61
|
+
24 => 61, // =
|
|
62
|
+
25 => 57, // 9
|
|
63
|
+
26 => 55, // 7
|
|
64
|
+
27 => 45, // -
|
|
65
|
+
28 => 56, // 8
|
|
66
|
+
29 => 48, // 0
|
|
67
|
+
30 => 93, // ]
|
|
68
|
+
31 => 79, // O
|
|
69
|
+
32 => 85, // U
|
|
70
|
+
33 => 91, // [
|
|
71
|
+
34 => 73, // I
|
|
72
|
+
35 => 80, // P
|
|
73
|
+
36 => 265, // Enter (mapped to Bloom ENTER = 265)
|
|
74
|
+
37 => 76, // L
|
|
75
|
+
38 => 74, // J
|
|
76
|
+
39 => 39, // '
|
|
77
|
+
40 => 75, // K
|
|
78
|
+
41 => 59, // ;
|
|
79
|
+
42 => 92, // backslash
|
|
80
|
+
43 => 44, // ,
|
|
81
|
+
44 => 47, // /
|
|
82
|
+
45 => 78, // N
|
|
83
|
+
46 => 77, // M
|
|
84
|
+
47 => 46, // .
|
|
85
|
+
48 => 9, // Tab
|
|
86
|
+
49 => 32, // Space
|
|
87
|
+
50 => 96, // `
|
|
88
|
+
51 => 8, // Backspace
|
|
89
|
+
53 => 27, // Escape
|
|
90
|
+
// Arrow keys
|
|
91
|
+
123 => 258, // Left
|
|
92
|
+
124 => 259, // Right
|
|
93
|
+
125 => 257, // Down
|
|
94
|
+
126 => 256, // Up
|
|
95
|
+
// Function keys
|
|
96
|
+
122 => 112, // F1
|
|
97
|
+
120 => 113, // F2
|
|
98
|
+
99 => 114, // F3
|
|
99
|
+
118 => 115, // F4
|
|
100
|
+
96 => 116, // F5
|
|
101
|
+
97 => 117, // F6
|
|
102
|
+
98 => 118, // F7
|
|
103
|
+
100 => 119, // F8
|
|
104
|
+
101 => 120, // F9
|
|
105
|
+
109 => 121, // F10
|
|
106
|
+
103 => 122, // F11
|
|
107
|
+
111 => 123, // F12
|
|
108
|
+
// Modifiers
|
|
109
|
+
56 => 280, // Left Shift
|
|
110
|
+
60 => 281, // Right Shift
|
|
111
|
+
59 => 282, // Left Control
|
|
112
|
+
62 => 283, // Right Control
|
|
113
|
+
58 => 284, // Left Alt/Option
|
|
114
|
+
61 => 285, // Right Alt/Option
|
|
115
|
+
55 => 286, // Left Command
|
|
116
|
+
54 => 287, // Right Command
|
|
117
|
+
_ => 0,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================
|
|
122
|
+
// CoreAudio FFI types and setup
|
|
123
|
+
// ============================================================
|
|
124
|
+
|
|
125
|
+
type AudioUnit = *mut std::ffi::c_void;
|
|
126
|
+
type OSStatus = i32;
|
|
127
|
+
type AudioUnitPropertyID = u32;
|
|
128
|
+
type AudioUnitScope = u32;
|
|
129
|
+
type AudioUnitElement = u32;
|
|
130
|
+
|
|
131
|
+
#[repr(C)]
|
|
132
|
+
#[derive(Clone, Copy)]
|
|
133
|
+
struct AudioComponentDescription {
|
|
134
|
+
component_type: u32,
|
|
135
|
+
component_sub_type: u32,
|
|
136
|
+
component_manufacturer: u32,
|
|
137
|
+
component_flags: u32,
|
|
138
|
+
component_flags_mask: u32,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[repr(C)]
|
|
142
|
+
#[derive(Clone, Copy)]
|
|
143
|
+
struct AudioStreamBasicDescription {
|
|
144
|
+
sample_rate: f64,
|
|
145
|
+
format_id: u32,
|
|
146
|
+
format_flags: u32,
|
|
147
|
+
bytes_per_packet: u32,
|
|
148
|
+
frames_per_packet: u32,
|
|
149
|
+
bytes_per_frame: u32,
|
|
150
|
+
channels_per_frame: u32,
|
|
151
|
+
bits_per_channel: u32,
|
|
152
|
+
reserved: u32,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[repr(C)]
|
|
156
|
+
struct AudioBufferList {
|
|
157
|
+
number_buffers: u32,
|
|
158
|
+
buffers: [AudioBuffer; 1],
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[repr(C)]
|
|
162
|
+
struct AudioBuffer {
|
|
163
|
+
number_channels: u32,
|
|
164
|
+
data_byte_size: u32,
|
|
165
|
+
data: *mut std::ffi::c_void,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type AURenderCallback = unsafe extern "C" fn(
|
|
169
|
+
in_ref_con: *mut std::ffi::c_void,
|
|
170
|
+
io_action_flags: *mut u32,
|
|
171
|
+
in_time_stamp: *const std::ffi::c_void,
|
|
172
|
+
in_bus_number: u32,
|
|
173
|
+
in_number_frames: u32,
|
|
174
|
+
io_data: *mut AudioBufferList,
|
|
175
|
+
) -> OSStatus;
|
|
176
|
+
|
|
177
|
+
#[repr(C)]
|
|
178
|
+
struct AURenderCallbackStruct {
|
|
179
|
+
input_proc: AURenderCallback,
|
|
180
|
+
input_proc_ref_con: *mut std::ffi::c_void,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type AudioComponent = *mut std::ffi::c_void;
|
|
184
|
+
|
|
185
|
+
#[link(name = "AudioToolbox", kind = "framework")]
|
|
186
|
+
extern "C" {
|
|
187
|
+
fn AudioComponentFindNext(component: AudioComponent, desc: *const AudioComponentDescription) -> AudioComponent;
|
|
188
|
+
fn AudioComponentInstanceNew(component: AudioComponent, out: *mut AudioUnit) -> OSStatus;
|
|
189
|
+
fn AudioUnitSetProperty(
|
|
190
|
+
unit: AudioUnit,
|
|
191
|
+
property_id: AudioUnitPropertyID,
|
|
192
|
+
scope: AudioUnitScope,
|
|
193
|
+
element: AudioUnitElement,
|
|
194
|
+
data: *const std::ffi::c_void,
|
|
195
|
+
data_size: u32,
|
|
196
|
+
) -> OSStatus;
|
|
197
|
+
fn AudioUnitInitialize(unit: AudioUnit) -> OSStatus;
|
|
198
|
+
fn AudioOutputUnitStart(unit: AudioUnit) -> OSStatus;
|
|
199
|
+
fn AudioOutputUnitStop(unit: AudioUnit) -> OSStatus;
|
|
200
|
+
fn AudioComponentInstanceDispose(unit: AudioUnit) -> OSStatus;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const K_AUDIO_UNIT_TYPE_OUTPUT: u32 = u32::from_be_bytes(*b"auou");
|
|
204
|
+
const K_AUDIO_UNIT_SUB_TYPE_DEFAULT_OUTPUT: u32 = u32::from_be_bytes(*b"def ");
|
|
205
|
+
const K_AUDIO_UNIT_MANUFACTURER_APPLE: u32 = u32::from_be_bytes(*b"appl");
|
|
206
|
+
|
|
207
|
+
const K_AUDIO_UNIT_PROPERTY_STREAM_FORMAT: AudioUnitPropertyID = 8;
|
|
208
|
+
const K_AUDIO_UNIT_PROPERTY_SET_RENDER_CALLBACK: AudioUnitPropertyID = 23;
|
|
209
|
+
const K_AUDIO_UNIT_SCOPE_INPUT: AudioUnitScope = 1;
|
|
210
|
+
|
|
211
|
+
const K_AUDIO_FORMAT_LINEAR_PCM: u32 = u32::from_be_bytes(*b"lpcm");
|
|
212
|
+
const K_AUDIO_FORMAT_FLAG_IS_FLOAT: u32 = 1;
|
|
213
|
+
const K_AUDIO_FORMAT_FLAG_IS_PACKED: u32 = 8;
|
|
214
|
+
|
|
215
|
+
struct AudioUnitInstance {
|
|
216
|
+
unit: AudioUnit,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Safety: AudioUnit is accessed only from audio thread callback + main thread init/deinit
|
|
220
|
+
unsafe impl Send for AudioUnitInstance {}
|
|
221
|
+
unsafe impl Sync for AudioUnitInstance {}
|
|
222
|
+
|
|
223
|
+
unsafe extern "C" fn audio_render_callback(
|
|
224
|
+
_in_ref_con: *mut std::ffi::c_void,
|
|
225
|
+
_io_action_flags: *mut u32,
|
|
226
|
+
_in_time_stamp: *const std::ffi::c_void,
|
|
227
|
+
_in_bus_number: u32,
|
|
228
|
+
in_number_frames: u32,
|
|
229
|
+
io_data: *mut AudioBufferList,
|
|
230
|
+
) -> OSStatus {
|
|
231
|
+
let buffer_list = &mut *io_data;
|
|
232
|
+
let buffer = &mut buffer_list.buffers[0];
|
|
233
|
+
let num_samples = in_number_frames as usize * 2; // stereo
|
|
234
|
+
let output = std::slice::from_raw_parts_mut(
|
|
235
|
+
buffer.data as *mut f32,
|
|
236
|
+
num_samples,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
ENGINE.get_mut().map(|eng| {
|
|
240
|
+
eng.audio.mix_output(output);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
0 // noErr
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ============================================================
|
|
247
|
+
// FFI entry points
|
|
248
|
+
// ============================================================
|
|
249
|
+
|
|
250
|
+
#[no_mangle]
|
|
251
|
+
pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u8, fullscreen: f64) {
|
|
252
|
+
let title = str_from_header(title_ptr);
|
|
253
|
+
let mtm = MainThreadMarker::from(unsafe { MainThreadMarker::new_unchecked() });
|
|
254
|
+
|
|
255
|
+
// Headless mode: BLOOM_HEADLESS=1 keeps the NSWindow + CAMetalLayer
|
|
256
|
+
// alive (wgpu's Metal backend requires a CAMetalLayer-backed view)
|
|
257
|
+
// but hides the window and suppresses dock icon / focus. Needed
|
|
258
|
+
// so an agent can spin up the renderer in a batch loop without
|
|
259
|
+
// stealing the user's focus on every sample.
|
|
260
|
+
let headless = std::env::var("BLOOM_HEADLESS")
|
|
261
|
+
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
|
262
|
+
.unwrap_or(false);
|
|
263
|
+
unsafe { HEADLESS = headless; }
|
|
264
|
+
|
|
265
|
+
let app = NSApplication::sharedApplication(mtm);
|
|
266
|
+
if headless {
|
|
267
|
+
// Prohibited = no dock icon, no menu bar, no activation.
|
|
268
|
+
app.setActivationPolicy(NSApplicationActivationPolicy::Prohibited);
|
|
269
|
+
} else {
|
|
270
|
+
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Far-off-screen origin keeps the window out of every display
|
|
274
|
+
// even if the OS insists on showing something.
|
|
275
|
+
let origin_x = if headless { -20000.0 } else { 100.0 };
|
|
276
|
+
let content_rect = NSRect::new(NSPoint::new(origin_x, 100.0), NSSize::new(width, height));
|
|
277
|
+
let style = NSWindowStyleMask::Titled
|
|
278
|
+
| NSWindowStyleMask::Closable
|
|
279
|
+
| NSWindowStyleMask::Miniaturizable
|
|
280
|
+
| NSWindowStyleMask::Resizable;
|
|
281
|
+
|
|
282
|
+
let window = unsafe {
|
|
283
|
+
NSWindow::initWithContentRect_styleMask_backing_defer(
|
|
284
|
+
NSWindow::alloc(mtm),
|
|
285
|
+
content_rect,
|
|
286
|
+
style,
|
|
287
|
+
objc2_app_kit::NSBackingStoreType(2), // NSBackingStoreBuffered
|
|
288
|
+
false,
|
|
289
|
+
)
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
let ns_title = NSString::from_str(title);
|
|
293
|
+
window.setTitle(&ns_title);
|
|
294
|
+
|
|
295
|
+
// Don't persist window size/fullscreen state across launches.
|
|
296
|
+
// NSWindow restoration was resurrecting a prior fullscreen toggle on
|
|
297
|
+
// the 4K display, which silently rendered benchmarks at 4× the
|
|
298
|
+
// requested pixel count.
|
|
299
|
+
unsafe { let _: () = msg_send![&window, setRestorable: false]; }
|
|
300
|
+
|
|
301
|
+
// BLOOM_NO_FULLSCREEN=1 hard-disables fullscreen capability: the
|
|
302
|
+
// window cannot be entered into fullscreen via the green button,
|
|
303
|
+
// cmd-ctrl-F, or inheriting a fullscreen Space from the launching
|
|
304
|
+
// terminal. Intended for benchmark harnesses where the 4K-display
|
|
305
|
+
// fullscreen path would otherwise silently quadruple render cost.
|
|
306
|
+
let no_fullscreen = std::env::var("BLOOM_NO_FULLSCREEN")
|
|
307
|
+
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
|
308
|
+
.unwrap_or(false);
|
|
309
|
+
if no_fullscreen {
|
|
310
|
+
window.setCollectionBehavior(objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if headless {
|
|
314
|
+
// Alpha 0 + off-screen origin + prohibited-activation app
|
|
315
|
+
// policy = fully invisible window that still backs the
|
|
316
|
+
// CAMetalLayer wgpu renders into. Don't call
|
|
317
|
+
// `makeKeyAndOrderFront` — that brings it to front.
|
|
318
|
+
unsafe {
|
|
319
|
+
let _: () = msg_send![&window, setAlphaValue: 0.0_f64];
|
|
320
|
+
let _: () = msg_send![&window, setIgnoresMouseEvents: true];
|
|
321
|
+
let _: () = msg_send![&window, orderOut: std::ptr::null::<objc2::runtime::AnyObject>()];
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
window.center();
|
|
325
|
+
window.makeKeyAndOrderFront(None);
|
|
326
|
+
#[allow(deprecated)]
|
|
327
|
+
app.activateIgnoringOtherApps(true);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Set up CAMetalLayer on the content view
|
|
331
|
+
let content_view = window.contentView().expect("No content view");
|
|
332
|
+
unsafe {
|
|
333
|
+
let _: () = msg_send![&content_view, setWantsLayer: true];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Create wgpu surface and renderer
|
|
337
|
+
// wgpu expects the NSView pointer (not NSWindow) for AppKit
|
|
338
|
+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
339
|
+
backends: wgpu::Backends::METAL,
|
|
340
|
+
..wgpu::InstanceDescriptor::new_without_display_handle()
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
let surface = unsafe {
|
|
344
|
+
let view_ptr = Retained::as_ptr(&content_view) as *mut std::ffi::c_void;
|
|
345
|
+
let handle = AppKitWindowHandle::new(
|
|
346
|
+
std::ptr::NonNull::new(view_ptr).unwrap()
|
|
347
|
+
);
|
|
348
|
+
let raw = RawWindowHandle::AppKit(handle);
|
|
349
|
+
instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
|
|
350
|
+
raw_display_handle: Some(raw_window_handle::RawDisplayHandle::AppKit(raw_window_handle::AppKitDisplayHandle::new())),
|
|
351
|
+
raw_window_handle: raw,
|
|
352
|
+
}).expect("Failed to create surface")
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
let adapter = pollster_block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
356
|
+
compatible_surface: Some(&surface),
|
|
357
|
+
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
358
|
+
..Default::default()
|
|
359
|
+
})).expect("No adapter found");
|
|
360
|
+
|
|
361
|
+
// Request TIMESTAMP_QUERY when the adapter supports it so the profiler
|
|
362
|
+
// can collect GPU timings. It's optional — profiler falls back to CPU
|
|
363
|
+
// only when the feature isn't available.
|
|
364
|
+
let supported = adapter.features();
|
|
365
|
+
let mut required_features = wgpu::Features::empty();
|
|
366
|
+
if supported.contains(wgpu::Features::TIMESTAMP_QUERY) {
|
|
367
|
+
required_features |= wgpu::Features::TIMESTAMP_QUERY;
|
|
368
|
+
}
|
|
369
|
+
// Ticket 007b: request ray-query + BLAS/TLAS where the adapter
|
|
370
|
+
// supports both (Apple Silicon Metal, DXR 1.1, VK_KHR_ray_query).
|
|
371
|
+
// `BLOOM_FORCE_SW_GI=1` forces the SW fallback for testing parity
|
|
372
|
+
// with non-RT adapters.
|
|
373
|
+
let force_sw_gi = std::env::var("BLOOM_FORCE_SW_GI")
|
|
374
|
+
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
|
375
|
+
.unwrap_or(false);
|
|
376
|
+
// wgpu 29 gates BLAS/TLAS creation + ray-query WGSL on a single
|
|
377
|
+
// feature bit; there's no separate "acceleration structure" flag.
|
|
378
|
+
let rt_mask = wgpu::Features::EXPERIMENTAL_RAY_QUERY;
|
|
379
|
+
if !force_sw_gi && supported.contains(rt_mask) {
|
|
380
|
+
required_features |= rt_mask;
|
|
381
|
+
}
|
|
382
|
+
// wgpu 29 requires an explicit `ExperimentalFeatures::enabled()` token
|
|
383
|
+
// when requesting any `EXPERIMENTAL_*` feature (ray query in our case).
|
|
384
|
+
// The token is constructed through an `unsafe` API acknowledging that
|
|
385
|
+
// experimental paths may hit undefined behavior — Apple Silicon's Metal
|
|
386
|
+
// ray-query path has been stable in wgpu releases since v25 so we're
|
|
387
|
+
// willing to take that risk here.
|
|
388
|
+
let experimental_features = if required_features.intersects(rt_mask) {
|
|
389
|
+
unsafe { wgpu::ExperimentalFeatures::enabled() }
|
|
390
|
+
} else {
|
|
391
|
+
wgpu::ExperimentalFeatures::disabled()
|
|
392
|
+
};
|
|
393
|
+
// Acceleration-structure limits default to 0 when RT is disabled.
|
|
394
|
+
// `using_minimum_supported_acceleration_structure_values` bumps
|
|
395
|
+
// them to the spec minimums (2^24 BLAS geometries / TLAS instances,
|
|
396
|
+
// etc.) whenever ray query was granted.
|
|
397
|
+
let mut required_limits = wgpu::Limits::default();
|
|
398
|
+
// Phase 1c: the material ABI declares 5 bind groups (PerFrame,
|
|
399
|
+
// PerView, PerMaterial, PerDraw, SceneInputs). wgpu's default
|
|
400
|
+
// limit is 4. Metal / Vulkan / D3D12 support at least 7, so 5 is
|
|
401
|
+
// safely within every real backend's capabilities.
|
|
402
|
+
required_limits.max_bind_groups = 5;
|
|
403
|
+
if required_features.intersects(rt_mask) {
|
|
404
|
+
required_limits = required_limits
|
|
405
|
+
.using_minimum_supported_acceleration_structure_values();
|
|
406
|
+
}
|
|
407
|
+
let (device, queue) = pollster_block_on(adapter.request_device(
|
|
408
|
+
&wgpu::DeviceDescriptor {
|
|
409
|
+
label: Some("bloom_device"),
|
|
410
|
+
required_features,
|
|
411
|
+
required_limits,
|
|
412
|
+
experimental_features,
|
|
413
|
+
..Default::default()
|
|
414
|
+
},
|
|
415
|
+
)).expect("Failed to create device");
|
|
416
|
+
|
|
417
|
+
let surface_caps = surface.get_capabilities(&adapter);
|
|
418
|
+
let format = surface_caps.formats.iter()
|
|
419
|
+
.find(|f| f.is_srgb())
|
|
420
|
+
.copied()
|
|
421
|
+
.unwrap_or(surface_caps.formats[0]);
|
|
422
|
+
|
|
423
|
+
// Retina/HiDPI: AppKit reports window dimensions in points, but
|
|
424
|
+
// CAMetalLayer's drawable needs to be sized in physical pixels or
|
|
425
|
+
// AppKit will bilinearly upscale a low-res image to the display.
|
|
426
|
+
// `backingScaleFactor` is typically 2.0 on Retina Macs, 1.0
|
|
427
|
+
// otherwise; on mixed-DPI setups it tracks whichever screen the
|
|
428
|
+
// window is on.
|
|
429
|
+
let scale: f64 = unsafe { msg_send![&*window, backingScaleFactor] };
|
|
430
|
+
let scale = if scale > 0.0 { scale } else { 1.0 };
|
|
431
|
+
let logical_w = width as u32;
|
|
432
|
+
let logical_h = height as u32;
|
|
433
|
+
let physical_w = ((width * scale) as u32).max(1);
|
|
434
|
+
let physical_h = ((height * scale) as u32).max(1);
|
|
435
|
+
|
|
436
|
+
let surface_config = wgpu::SurfaceConfiguration {
|
|
437
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
438
|
+
format,
|
|
439
|
+
width: physical_w,
|
|
440
|
+
height: physical_h,
|
|
441
|
+
present_mode: wgpu::PresentMode::Fifo,
|
|
442
|
+
alpha_mode: surface_caps.alpha_modes[0],
|
|
443
|
+
view_formats: vec![],
|
|
444
|
+
desired_maximum_frame_latency: 2,
|
|
445
|
+
};
|
|
446
|
+
surface.configure(&device, &surface_config);
|
|
447
|
+
|
|
448
|
+
let renderer = Renderer::new(device, queue, surface, surface_config, logical_w, logical_h);
|
|
449
|
+
let engine_state = EngineState::new(renderer);
|
|
450
|
+
|
|
451
|
+
unsafe {
|
|
452
|
+
let _ = ENGINE.set(engine_state);
|
|
453
|
+
WINDOW = Some(window);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Register Bloom's GPU screenshot capture with perry-geisterhand (if linked)
|
|
457
|
+
bloom_register_geisterhand_screenshot();
|
|
458
|
+
|
|
459
|
+
if fullscreen != 0.0 {
|
|
460
|
+
bloom_toggle_fullscreen();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
#[no_mangle]
|
|
465
|
+
pub extern "C" fn bloom_close_window() {
|
|
466
|
+
unsafe {
|
|
467
|
+
WINDOW = None;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#[no_mangle]
|
|
472
|
+
pub extern "C" fn bloom_window_should_close() -> f64 {
|
|
473
|
+
if engine().should_close { 1.0 } else { 0.0 }
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
#[no_mangle]
|
|
477
|
+
pub extern "C" fn bloom_begin_drawing() {
|
|
478
|
+
// Poll events
|
|
479
|
+
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
|
480
|
+
let app = NSApplication::sharedApplication(mtm);
|
|
481
|
+
|
|
482
|
+
loop {
|
|
483
|
+
let event = unsafe {
|
|
484
|
+
app.nextEventMatchingMask_untilDate_inMode_dequeue(
|
|
485
|
+
NSEventMask::Any,
|
|
486
|
+
Some(&NSDate::distantPast()),
|
|
487
|
+
NSDefaultRunLoopMode,
|
|
488
|
+
true,
|
|
489
|
+
)
|
|
490
|
+
};
|
|
491
|
+
match event {
|
|
492
|
+
Some(event) => {
|
|
493
|
+
let event_type = event.r#type();
|
|
494
|
+
match event_type {
|
|
495
|
+
NSEventType::KeyDown => {
|
|
496
|
+
let keycode = event.keyCode();
|
|
497
|
+
let bloom_key = map_keycode(keycode);
|
|
498
|
+
if bloom_key > 0 {
|
|
499
|
+
engine().input.set_key_down(bloom_key);
|
|
500
|
+
}
|
|
501
|
+
// Extract typed characters for text input (E3b).
|
|
502
|
+
let chars_obj = event.characters();
|
|
503
|
+
if let Some(chars) = chars_obj {
|
|
504
|
+
let s = chars.to_string();
|
|
505
|
+
for c in s.chars() {
|
|
506
|
+
let cp = c as u32;
|
|
507
|
+
// Filter out control characters (keep printable + backspace).
|
|
508
|
+
if cp >= 32 || cp == 8 || cp == 13 || cp == 9 {
|
|
509
|
+
engine().input.push_char(cp);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
NSEventType::KeyUp => {
|
|
515
|
+
let keycode = event.keyCode();
|
|
516
|
+
let bloom_key = map_keycode(keycode);
|
|
517
|
+
if bloom_key > 0 {
|
|
518
|
+
engine().input.set_key_up(bloom_key);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
NSEventType::MouseMoved | NSEventType::LeftMouseDragged | NSEventType::RightMouseDragged => {
|
|
522
|
+
if engine().input.cursor_disabled {
|
|
523
|
+
// In disabled-cursor mode, use raw deltas from NSEvent
|
|
524
|
+
let dx: f64 = unsafe { msg_send![&*event, deltaX] };
|
|
525
|
+
let dy: f64 = unsafe { msg_send![&*event, deltaY] };
|
|
526
|
+
engine().input.accumulate_mouse_delta(dx, dy);
|
|
527
|
+
} else if let Some(window) = unsafe { &WINDOW } {
|
|
528
|
+
let loc = event.locationInWindow();
|
|
529
|
+
let frame = window.contentView().map(|v| v.frame()).unwrap_or(NSRect::ZERO);
|
|
530
|
+
engine().input.set_mouse_position(loc.x, frame.size.height - loc.y);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
NSEventType::LeftMouseDown => {
|
|
534
|
+
engine().input.set_mouse_button_down(0);
|
|
535
|
+
}
|
|
536
|
+
NSEventType::LeftMouseUp => {
|
|
537
|
+
engine().input.set_mouse_button_up(0);
|
|
538
|
+
}
|
|
539
|
+
NSEventType::RightMouseDown => {
|
|
540
|
+
engine().input.set_mouse_button_down(1);
|
|
541
|
+
}
|
|
542
|
+
NSEventType::RightMouseUp => {
|
|
543
|
+
engine().input.set_mouse_button_up(1);
|
|
544
|
+
}
|
|
545
|
+
NSEventType::ScrollWheel => {
|
|
546
|
+
// NSEvent's scrollingDeltaY is positive when scrolling up
|
|
547
|
+
// (away from the user). Normalize to "positive = zoom in"
|
|
548
|
+
// by flipping the sign — matches editor orbit convention.
|
|
549
|
+
let dy: f64 = unsafe { msg_send![&*event, scrollingDeltaY] };
|
|
550
|
+
engine().input.accumulate_mouse_wheel(dy);
|
|
551
|
+
}
|
|
552
|
+
_ => {}
|
|
553
|
+
}
|
|
554
|
+
app.sendEvent(&event);
|
|
555
|
+
}
|
|
556
|
+
None => break,
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Check if window was closed. In headless mode the window is
|
|
561
|
+
// intentionally hidden — skip the isVisible check so --capture
|
|
562
|
+
// can actually run to completion without instant-exit.
|
|
563
|
+
let is_headless = unsafe { HEADLESS };
|
|
564
|
+
if !is_headless && unsafe { WINDOW.as_ref().map(|w| !w.isVisible()).unwrap_or(true) } {
|
|
565
|
+
engine().should_close = true;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Handle window resize — track physical (backing) size for the
|
|
569
|
+
// swapchain while keeping the logical (points) size for user code.
|
|
570
|
+
if let Some(window) = unsafe { &WINDOW } {
|
|
571
|
+
if let Some(content_view) = window.contentView() {
|
|
572
|
+
let frame = content_view.frame();
|
|
573
|
+
let logical_w = frame.size.width as u32;
|
|
574
|
+
let logical_h = frame.size.height as u32;
|
|
575
|
+
let scale: f64 = unsafe { msg_send![&*window, backingScaleFactor] };
|
|
576
|
+
let scale = if scale > 0.0 { scale } else { 1.0 };
|
|
577
|
+
let physical_w = ((frame.size.width * scale) as u32).max(1);
|
|
578
|
+
let physical_h = ((frame.size.height * scale) as u32).max(1);
|
|
579
|
+
let eng = engine();
|
|
580
|
+
if logical_w > 0 && logical_h > 0
|
|
581
|
+
&& (physical_w != eng.renderer.physical_width()
|
|
582
|
+
|| physical_h != eng.renderer.physical_height()
|
|
583
|
+
|| logical_w != eng.renderer.width()
|
|
584
|
+
|| logical_h != eng.renderer.height())
|
|
585
|
+
{
|
|
586
|
+
eng.renderer.resize(physical_w, physical_h, logical_w, logical_h);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Apply cursor shape (Q2). resizeLeftRightCursor /
|
|
592
|
+
// resizeUpDownCursor are deprecated in newer AppKit but still
|
|
593
|
+
// function; the suggested replacements need a richer per-frame-
|
|
594
|
+
// axis API we don't have. Keep + suppress the deprecation
|
|
595
|
+
// warning until we land that.
|
|
596
|
+
#[allow(deprecated)]
|
|
597
|
+
match engine().input.cursor_shape {
|
|
598
|
+
1 => objc2_app_kit::NSCursor::pointingHandCursor().set(),
|
|
599
|
+
2 => objc2_app_kit::NSCursor::openHandCursor().set(),
|
|
600
|
+
3 => objc2_app_kit::NSCursor::IBeamCursor().set(),
|
|
601
|
+
4 => objc2_app_kit::NSCursor::resizeLeftRightCursor().set(),
|
|
602
|
+
5 => objc2_app_kit::NSCursor::resizeUpDownCursor().set(),
|
|
603
|
+
6 => objc2_app_kit::NSCursor::crosshairCursor().set(),
|
|
604
|
+
_ => {},
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
engine().begin_frame();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
#[no_mangle]
|
|
611
|
+
pub extern "C" fn bloom_end_drawing() {
|
|
612
|
+
// Pump geisterhand BEFORE end_frame.
|
|
613
|
+
// Screenshot function re-renders inline with captured VP + vertices.
|
|
614
|
+
extern "C" { fn perry_geisterhand_pump(); }
|
|
615
|
+
unsafe { perry_geisterhand_pump(); }
|
|
616
|
+
|
|
617
|
+
engine().end_frame();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/// Request a PNG screenshot of the next rendered frame.
|
|
621
|
+
/// The capture happens during the next end_drawing(), so the caller
|
|
622
|
+
/// should call beginDrawing/endDrawing once after this for the file
|
|
623
|
+
/// to actually appear on disk. Used by bloom-diff and CI image
|
|
624
|
+
/// regression workflows.
|
|
625
|
+
#[no_mangle]
|
|
626
|
+
pub extern "C" fn bloom_take_screenshot(path_ptr: *const u8) {
|
|
627
|
+
let path = str_from_header(path_ptr).to_string();
|
|
628
|
+
let eng = engine();
|
|
629
|
+
eng.renderer.screenshot_requested = true;
|
|
630
|
+
eng.renderer.pending_screenshot_path = Some(path);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#[no_mangle]
|
|
634
|
+
pub extern "C" fn bloom_clear_background(r: f64, g: f64, b: f64, a: f64) {
|
|
635
|
+
engine().renderer.set_clear_color(r, g, b, a);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/// Load an HDR equirectangular environment map and upload it to the
|
|
639
|
+
/// GPU. Subsequent frames sample it per-background-pixel via a sky
|
|
640
|
+
/// pass, so the background matches a path-traced reference instead of
|
|
641
|
+
/// being a flat clear color. The file must be Radiance HDR (.hdr).
|
|
642
|
+
#[no_mangle]
|
|
643
|
+
pub extern "C" fn bloom_set_env_clear_from_hdr(path_ptr: *const u8) {
|
|
644
|
+
use image::ImageDecoder;
|
|
645
|
+
let path = str_from_header(path_ptr).to_string();
|
|
646
|
+
let file = match std::fs::File::open(&path) {
|
|
647
|
+
Ok(f) => f,
|
|
648
|
+
Err(_) => return,
|
|
649
|
+
};
|
|
650
|
+
let decoder = match image::codecs::hdr::HdrDecoder::new(std::io::BufReader::new(file)) {
|
|
651
|
+
Ok(d) => d,
|
|
652
|
+
Err(_) => return,
|
|
653
|
+
};
|
|
654
|
+
let (w, h) = decoder.dimensions();
|
|
655
|
+
let byte_len = (w as usize) * (h as usize) * 3 * 4;
|
|
656
|
+
let mut buf = vec![0u8; byte_len];
|
|
657
|
+
if decoder.read_image(&mut buf).is_err() {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// Reinterpret the byte buffer as f32 RGB triples for the renderer.
|
|
661
|
+
let rgb_f32: Vec<f32> = buf
|
|
662
|
+
.chunks_exact(4)
|
|
663
|
+
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
|
|
664
|
+
.collect();
|
|
665
|
+
engine().renderer.load_env_from_hdr(w, h, &rgb_f32);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
#[no_mangle]
|
|
669
|
+
pub extern "C" fn bloom_set_target_fps(fps: f64) {
|
|
670
|
+
engine().target_fps = fps;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
#[no_mangle]
|
|
674
|
+
pub extern "C" fn bloom_set_direct_2d_mode(on: f64) {
|
|
675
|
+
engine().direct_2d_mode = on > 0.5;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
#[no_mangle]
|
|
679
|
+
pub extern "C" fn bloom_get_delta_time() -> f64 {
|
|
680
|
+
engine().delta_time
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
#[no_mangle]
|
|
684
|
+
pub extern "C" fn bloom_get_fps() -> f64 {
|
|
685
|
+
engine().get_fps()
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
#[no_mangle]
|
|
689
|
+
pub extern "C" fn bloom_get_screen_width() -> f64 {
|
|
690
|
+
engine().screen_width()
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
#[no_mangle]
|
|
694
|
+
pub extern "C" fn bloom_get_screen_height() -> f64 {
|
|
695
|
+
engine().screen_height()
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#[no_mangle]
|
|
699
|
+
pub extern "C" fn bloom_get_time() -> f64 {
|
|
700
|
+
engine().get_time()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// ============================================================
|
|
704
|
+
// Input - Keyboard
|
|
705
|
+
// ============================================================
|
|
706
|
+
|
|
707
|
+
#[no_mangle]
|
|
708
|
+
pub extern "C" fn bloom_is_key_pressed(key: f64) -> f64 {
|
|
709
|
+
if engine().input.is_key_pressed(key as usize) { 1.0 } else { 0.0 }
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#[no_mangle]
|
|
713
|
+
pub extern "C" fn bloom_is_key_down(key: f64) -> f64 {
|
|
714
|
+
if engine().input.is_key_down(key as usize) { 1.0 } else { 0.0 }
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
#[no_mangle]
|
|
718
|
+
pub extern "C" fn bloom_is_key_released(key: f64) -> f64 {
|
|
719
|
+
if engine().input.is_key_released(key as usize) { 1.0 } else { 0.0 }
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ============================================================
|
|
723
|
+
// Input - Mouse
|
|
724
|
+
// ============================================================
|
|
725
|
+
|
|
726
|
+
#[no_mangle]
|
|
727
|
+
pub extern "C" fn bloom_get_mouse_x() -> f64 {
|
|
728
|
+
engine().input.mouse_x
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
#[no_mangle]
|
|
732
|
+
pub extern "C" fn bloom_get_mouse_y() -> f64 {
|
|
733
|
+
engine().input.mouse_y
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
#[no_mangle]
|
|
737
|
+
pub extern "C" fn bloom_is_mouse_button_pressed(btn: f64) -> f64 {
|
|
738
|
+
if engine().input.is_mouse_button_pressed(btn as usize) { 1.0 } else { 0.0 }
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
#[no_mangle]
|
|
742
|
+
pub extern "C" fn bloom_is_mouse_button_down(btn: f64) -> f64 {
|
|
743
|
+
if engine().input.is_mouse_button_down(btn as usize) { 1.0 } else { 0.0 }
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
#[no_mangle]
|
|
747
|
+
pub extern "C" fn bloom_is_mouse_button_released(btn: f64) -> f64 {
|
|
748
|
+
if engine().input.is_mouse_button_released(btn as usize) { 1.0 } else { 0.0 }
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ============================================================
|
|
752
|
+
// Input - Gamepad
|
|
753
|
+
// ============================================================
|
|
754
|
+
|
|
755
|
+
#[no_mangle]
|
|
756
|
+
pub extern "C" fn bloom_is_gamepad_available(gamepad: f64) -> f64 {
|
|
757
|
+
let _ = gamepad;
|
|
758
|
+
if engine().input.is_gamepad_available() { 1.0 } else { 0.0 }
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#[no_mangle]
|
|
762
|
+
pub extern "C" fn bloom_get_gamepad_axis(gamepad: f64, axis: f64) -> f64 {
|
|
763
|
+
let _ = gamepad;
|
|
764
|
+
engine().input.get_gamepad_axis(axis as usize) as f64
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
#[no_mangle]
|
|
768
|
+
pub extern "C" fn bloom_is_gamepad_button_pressed(gamepad: f64, button: f64) -> f64 {
|
|
769
|
+
let _ = gamepad;
|
|
770
|
+
if engine().input.is_gamepad_button_pressed(button as usize) { 1.0 } else { 0.0 }
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
#[no_mangle]
|
|
774
|
+
pub extern "C" fn bloom_is_gamepad_button_down(gamepad: f64, button: f64) -> f64 {
|
|
775
|
+
let _ = gamepad;
|
|
776
|
+
if engine().input.is_gamepad_button_down(button as usize) { 1.0 } else { 0.0 }
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
#[no_mangle]
|
|
780
|
+
pub extern "C" fn bloom_is_gamepad_button_released(gamepad: f64, button: f64) -> f64 {
|
|
781
|
+
let _ = gamepad;
|
|
782
|
+
if engine().input.is_gamepad_button_released(button as usize) { 1.0 } else { 0.0 }
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
#[no_mangle]
|
|
786
|
+
pub extern "C" fn bloom_get_gamepad_axis_count(gamepad: f64) -> f64 {
|
|
787
|
+
let _ = gamepad;
|
|
788
|
+
engine().input.get_gamepad_axis_count() as f64
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// ============================================================
|
|
792
|
+
// Input - Touch
|
|
793
|
+
// ============================================================
|
|
794
|
+
|
|
795
|
+
#[no_mangle]
|
|
796
|
+
pub extern "C" fn bloom_get_touch_x() -> f64 {
|
|
797
|
+
engine().input.get_touch_x(0)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
#[no_mangle]
|
|
801
|
+
pub extern "C" fn bloom_get_touch_y() -> f64 {
|
|
802
|
+
engine().input.get_touch_y(0)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
#[no_mangle]
|
|
806
|
+
pub extern "C" fn bloom_get_touch_count() -> f64 {
|
|
807
|
+
engine().input.get_touch_count() as f64
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ============================================================
|
|
811
|
+
// Shapes
|
|
812
|
+
// ============================================================
|
|
813
|
+
|
|
814
|
+
#[no_mangle]
|
|
815
|
+
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) {
|
|
816
|
+
engine().renderer.draw_line(x1, y1, x2, y2, thickness, r, g, b, a);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
#[no_mangle]
|
|
820
|
+
pub extern "C" fn bloom_draw_rect(x: f64, y: f64, w: f64, h: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
821
|
+
engine().renderer.draw_rect(x, y, w, h, r, g, b, a);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
#[no_mangle]
|
|
825
|
+
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) {
|
|
826
|
+
engine().renderer.draw_rect_lines(x, y, w, h, thickness, r, g, b, a);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
#[no_mangle]
|
|
830
|
+
pub extern "C" fn bloom_draw_circle(cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
831
|
+
engine().renderer.draw_circle(cx, cy, radius, r, g, b, a);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
#[no_mangle]
|
|
835
|
+
pub extern "C" fn bloom_draw_circle_lines(cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
836
|
+
engine().renderer.draw_circle_lines(cx, cy, radius, r, g, b, a);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
#[no_mangle]
|
|
840
|
+
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) {
|
|
841
|
+
engine().renderer.draw_triangle(x1, y1, x2, y2, x3, y3, r, g, b, a);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
#[no_mangle]
|
|
845
|
+
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) {
|
|
846
|
+
engine().renderer.draw_poly(cx, cy, sides, radius, rotation, r, g, b, a);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ============================================================
|
|
850
|
+
// Text
|
|
851
|
+
// ============================================================
|
|
852
|
+
|
|
853
|
+
#[no_mangle]
|
|
854
|
+
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) {
|
|
855
|
+
let text = str_from_header(text_ptr);
|
|
856
|
+
let eng = engine();
|
|
857
|
+
// Need to split borrow: take text out temporarily
|
|
858
|
+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
|
|
859
|
+
text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
|
|
860
|
+
eng.text = text_renderer;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
#[no_mangle]
|
|
864
|
+
pub extern "C" fn bloom_measure_text(text_ptr: *const u8, size: f64) -> f64 {
|
|
865
|
+
let text = str_from_header(text_ptr);
|
|
866
|
+
engine().text.measure_text(text, size as u32)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
#[no_mangle]
|
|
870
|
+
pub extern "C" fn bloom_load_font(path_ptr: *const u8, _size: f64) -> f64 {
|
|
871
|
+
let path = str_from_header(path_ptr);
|
|
872
|
+
match std::fs::read(path) {
|
|
873
|
+
Ok(data) => engine().text.load_font(&data) as f64,
|
|
874
|
+
Err(_) => 0.0,
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#[no_mangle]
|
|
879
|
+
pub extern "C" fn bloom_unload_font(font_handle: f64) {
|
|
880
|
+
engine().text.unload_font(font_handle as usize);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
#[no_mangle]
|
|
884
|
+
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) {
|
|
885
|
+
let text = str_from_header(text_ptr);
|
|
886
|
+
let eng = engine();
|
|
887
|
+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
|
|
888
|
+
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);
|
|
889
|
+
eng.text = text_renderer;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
#[no_mangle]
|
|
893
|
+
pub extern "C" fn bloom_measure_text_ex(font_handle: f64, text_ptr: *const u8, size: f64, spacing: f64) -> f64 {
|
|
894
|
+
let text = str_from_header(text_ptr);
|
|
895
|
+
engine().text.measure_text_ex(font_handle as usize, text, size as u32, spacing as f32)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ============================================================
|
|
899
|
+
// Textures
|
|
900
|
+
// ============================================================
|
|
901
|
+
|
|
902
|
+
#[no_mangle]
|
|
903
|
+
pub extern "C" fn bloom_load_texture(path_ptr: *const u8) -> f64 {
|
|
904
|
+
let path = str_from_header(path_ptr);
|
|
905
|
+
match std::fs::read(path) {
|
|
906
|
+
Ok(data) => {
|
|
907
|
+
let eng = engine();
|
|
908
|
+
let EngineState { ref mut textures, ref mut renderer, .. } = *eng;
|
|
909
|
+
textures.load_texture(renderer, &data)
|
|
910
|
+
}
|
|
911
|
+
Err(_) => 0.0,
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
#[no_mangle]
|
|
916
|
+
pub extern "C" fn bloom_unload_texture(handle: f64) {
|
|
917
|
+
let eng = engine();
|
|
918
|
+
let EngineState { ref mut textures, ref mut renderer, .. } = *eng;
|
|
919
|
+
textures.unload_texture(handle, renderer);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
#[no_mangle]
|
|
923
|
+
pub extern "C" fn bloom_draw_texture(handle: f64, x: f64, y: f64, tint_r: f64, tint_g: f64, tint_b: f64, tint_a: f64) {
|
|
924
|
+
let eng = engine();
|
|
925
|
+
if let Some(tex) = eng.textures.get(handle) {
|
|
926
|
+
let bind_group_idx = tex.bind_group_idx;
|
|
927
|
+
eng.renderer.draw_texture(bind_group_idx, x, y, tint_r, tint_g, tint_b, tint_a);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
#[no_mangle]
|
|
932
|
+
pub extern "C" fn bloom_draw_texture_pro(
|
|
933
|
+
handle: f64,
|
|
934
|
+
src_x: f64, src_y: f64, src_w: f64, src_h: f64,
|
|
935
|
+
dst_x: f64, dst_y: f64, dst_w: f64, dst_h: f64,
|
|
936
|
+
origin_x: f64, origin_y: f64, rotation: f64,
|
|
937
|
+
tint_r: f64, tint_g: f64, tint_b: f64, tint_a: f64,
|
|
938
|
+
) {
|
|
939
|
+
let eng = engine();
|
|
940
|
+
if let Some(tex) = eng.textures.get(handle) {
|
|
941
|
+
let bind_group_idx = tex.bind_group_idx;
|
|
942
|
+
eng.renderer.draw_texture_pro(
|
|
943
|
+
bind_group_idx,
|
|
944
|
+
src_x, src_y, src_w, src_h,
|
|
945
|
+
dst_x, dst_y, dst_w, dst_h,
|
|
946
|
+
origin_x, origin_y, rotation,
|
|
947
|
+
tint_r, tint_g, tint_b, tint_a,
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
#[no_mangle]
|
|
953
|
+
pub extern "C" fn bloom_draw_texture_rec(
|
|
954
|
+
handle: f64,
|
|
955
|
+
src_x: f64, src_y: f64, src_w: f64, src_h: f64,
|
|
956
|
+
dst_x: f64, dst_y: f64,
|
|
957
|
+
tint_r: f64, tint_g: f64, tint_b: f64, tint_a: f64,
|
|
958
|
+
) {
|
|
959
|
+
let eng = engine();
|
|
960
|
+
if let Some(tex) = eng.textures.get(handle) {
|
|
961
|
+
let bind_group_idx = tex.bind_group_idx;
|
|
962
|
+
eng.renderer.draw_texture_rec(
|
|
963
|
+
bind_group_idx,
|
|
964
|
+
src_x, src_y, src_w, src_h,
|
|
965
|
+
dst_x, dst_y,
|
|
966
|
+
tint_r, tint_g, tint_b, tint_a,
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
#[no_mangle]
|
|
972
|
+
pub extern "C" fn bloom_get_texture_width(handle: f64) -> f64 {
|
|
973
|
+
let eng = engine();
|
|
974
|
+
eng.textures.get(handle).map(|t| t.width as f64).unwrap_or(0.0)
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
#[no_mangle]
|
|
978
|
+
pub extern "C" fn bloom_get_texture_height(handle: f64) -> f64 {
|
|
979
|
+
let eng = engine();
|
|
980
|
+
eng.textures.get(handle).map(|t| t.height as f64).unwrap_or(0.0)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
#[no_mangle]
|
|
984
|
+
pub extern "C" fn bloom_load_image(path_ptr: *const u8) -> f64 {
|
|
985
|
+
let path = str_from_header(path_ptr);
|
|
986
|
+
match std::fs::read(path) {
|
|
987
|
+
Ok(data) => engine().textures.load_image(&data),
|
|
988
|
+
Err(_) => 0.0,
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
#[no_mangle]
|
|
993
|
+
pub extern "C" fn bloom_image_resize(handle: f64, w: f64, h: f64) {
|
|
994
|
+
engine().textures.image_resize(handle, w as u32, h as u32);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
#[no_mangle]
|
|
998
|
+
pub extern "C" fn bloom_image_crop(handle: f64, x: f64, y: f64, w: f64, h: f64) {
|
|
999
|
+
engine().textures.image_crop(handle, x as u32, y as u32, w as u32, h as u32);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
#[no_mangle]
|
|
1003
|
+
pub extern "C" fn bloom_image_flip_h(handle: f64) {
|
|
1004
|
+
engine().textures.image_flip_h(handle);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
#[no_mangle]
|
|
1008
|
+
pub extern "C" fn bloom_image_flip_v(handle: f64) {
|
|
1009
|
+
engine().textures.image_flip_v(handle);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
#[no_mangle]
|
|
1013
|
+
pub extern "C" fn bloom_load_texture_from_image(handle: f64) -> f64 {
|
|
1014
|
+
let eng = engine();
|
|
1015
|
+
let EngineState { ref mut textures, ref mut renderer, .. } = *eng;
|
|
1016
|
+
textures.load_texture_from_image(handle, renderer)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
#[no_mangle]
|
|
1020
|
+
pub extern "C" fn bloom_gen_texture_mipmaps(_handle: f64) {
|
|
1021
|
+
// Mipmap generation is handled by the GPU texture creation pipeline
|
|
1022
|
+
// This is a no-op for now as wgpu handles mipmaps internally
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
#[no_mangle]
|
|
1026
|
+
pub extern "C" fn bloom_set_texture_filter(handle: f64, mode: f64) {
|
|
1027
|
+
let eng = engine();
|
|
1028
|
+
if let Some(tex) = eng.textures.get(handle) {
|
|
1029
|
+
let bind_group_idx = tex.bind_group_idx;
|
|
1030
|
+
eng.renderer.set_texture_filter(bind_group_idx, mode > 0.5);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// ============================================================
|
|
1035
|
+
// Camera 2D
|
|
1036
|
+
// ============================================================
|
|
1037
|
+
|
|
1038
|
+
#[no_mangle]
|
|
1039
|
+
pub extern "C" fn bloom_begin_mode_2d(offset_x: f64, offset_y: f64, target_x: f64, target_y: f64, rotation: f64, zoom: f64) {
|
|
1040
|
+
engine().renderer.begin_mode_2d(
|
|
1041
|
+
offset_x as f32, offset_y as f32,
|
|
1042
|
+
target_x as f32, target_y as f32,
|
|
1043
|
+
rotation as f32, zoom as f32,
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
#[no_mangle]
|
|
1048
|
+
pub extern "C" fn bloom_end_mode_2d() {
|
|
1049
|
+
engine().renderer.end_mode_2d();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ============================================================
|
|
1053
|
+
// Camera 3D and 3D drawing
|
|
1054
|
+
// ============================================================
|
|
1055
|
+
|
|
1056
|
+
#[no_mangle]
|
|
1057
|
+
pub extern "C" fn bloom_begin_mode_3d(
|
|
1058
|
+
pos_x: f64, pos_y: f64, pos_z: f64,
|
|
1059
|
+
target_x: f64, target_y: f64, target_z: f64,
|
|
1060
|
+
up_x: f64, up_y: f64, up_z: f64,
|
|
1061
|
+
fovy: f64, projection: f64,
|
|
1062
|
+
) {
|
|
1063
|
+
engine().renderer.begin_mode_3d(
|
|
1064
|
+
pos_x as f32, pos_y as f32, pos_z as f32,
|
|
1065
|
+
target_x as f32, target_y as f32, target_z as f32,
|
|
1066
|
+
up_x as f32, up_y as f32, up_z as f32,
|
|
1067
|
+
fovy as f32, projection as f32,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
#[no_mangle]
|
|
1072
|
+
pub extern "C" fn bloom_end_mode_3d() {
|
|
1073
|
+
engine().renderer.end_mode_3d();
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
#[no_mangle]
|
|
1077
|
+
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) {
|
|
1078
|
+
engine().renderer.draw_cube(x, y, z, w, h, d, r, g, b, a);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
#[no_mangle]
|
|
1082
|
+
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) {
|
|
1083
|
+
engine().renderer.draw_cube_wires(x, y, z, w, h, d, r, g, b, a);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
#[no_mangle]
|
|
1087
|
+
pub extern "C" fn bloom_draw_sphere(cx: f64, cy: f64, cz: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
1088
|
+
engine().renderer.draw_sphere(cx, cy, cz, radius, r, g, b, a);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
#[no_mangle]
|
|
1092
|
+
pub extern "C" fn bloom_draw_sphere_wires(cx: f64, cy: f64, cz: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
1093
|
+
engine().renderer.draw_sphere_wires(cx, cy, cz, radius, r, g, b, a);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#[no_mangle]
|
|
1097
|
+
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) {
|
|
1098
|
+
engine().renderer.draw_cylinder(x, y, z, radius_top, radius_bottom, height, r, g, b, a);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
#[no_mangle]
|
|
1102
|
+
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) {
|
|
1103
|
+
engine().renderer.draw_plane(cx, cy, cz, w, d, r, g, b, a);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
#[no_mangle]
|
|
1107
|
+
pub extern "C" fn bloom_draw_grid(slices: f64, spacing: f64) {
|
|
1108
|
+
engine().renderer.draw_grid(slices as i32, spacing);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
#[no_mangle]
|
|
1112
|
+
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) {
|
|
1113
|
+
engine().renderer.draw_ray(origin_x, origin_y, origin_z, dir_x, dir_y, dir_z, r, g, b, a);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// ============================================================
|
|
1117
|
+
// Joint test
|
|
1118
|
+
// ============================================================
|
|
1119
|
+
|
|
1120
|
+
#[no_mangle]
|
|
1121
|
+
pub extern "C" fn bloom_set_joint_test(joint_index: f64, angle: f64) {
|
|
1122
|
+
engine().renderer.set_joint_test(joint_index as usize, angle as f32);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// ============================================================
|
|
1126
|
+
// Lighting
|
|
1127
|
+
// ============================================================
|
|
1128
|
+
|
|
1129
|
+
#[no_mangle]
|
|
1130
|
+
pub extern "C" fn bloom_set_ambient_light(r: f64, g: f64, b: f64, intensity: f64) {
|
|
1131
|
+
engine().renderer.set_ambient_light(r, g, b, intensity);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
#[no_mangle]
|
|
1135
|
+
pub extern "C" fn bloom_set_directional_light(dx: f64, dy: f64, dz: f64, r: f64, g: f64, b: f64, intensity: f64) {
|
|
1136
|
+
engine().renderer.set_directional_light(dx, dy, dz, r, g, b, intensity);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// --- EN-005: procedural sky ---
|
|
1140
|
+
// Toggle procedural-atmosphere rendering and steer the sun. The
|
|
1141
|
+
// renderer owns the on/off flag + LUT state; setting the sun marks
|
|
1142
|
+
// the sky-view LUT dirty so it re-bakes before the next frame.
|
|
1143
|
+
|
|
1144
|
+
#[no_mangle]
|
|
1145
|
+
pub extern "C" fn bloom_set_procedural_sky(enabled: f64, rayleigh_density: f64, mie_density: f64, ground_albedo: f64) {
|
|
1146
|
+
engine().renderer.set_procedural_sky(
|
|
1147
|
+
enabled != 0.0,
|
|
1148
|
+
rayleigh_density as f32,
|
|
1149
|
+
mie_density as f32,
|
|
1150
|
+
ground_albedo as f32,
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
#[no_mangle]
|
|
1155
|
+
pub extern "C" fn bloom_set_sun_direction(dx: f64, dy: f64, dz: f64, intensity: f64) {
|
|
1156
|
+
engine().renderer.set_sun_direction(dx as f32, dy as f32, dz as f32, intensity as f32);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// --- Post-FX knobs (heuristic visual layer; default-off) ---
|
|
1160
|
+
|
|
1161
|
+
#[no_mangle]
|
|
1162
|
+
pub extern "C" fn bloom_set_fog(r: f64, g: f64, b: f64, density: f64, height_ref: f64, height_falloff: f64) {
|
|
1163
|
+
let r_ = engine();
|
|
1164
|
+
r_.renderer.set_fog_color(r as f32, g as f32, b as f32);
|
|
1165
|
+
r_.renderer.set_fog_density(density as f32);
|
|
1166
|
+
r_.renderer.set_fog_height_falloff(height_ref as f32, height_falloff as f32);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
#[no_mangle]
|
|
1170
|
+
pub extern "C" fn bloom_set_chromatic_aberration(strength: f64) {
|
|
1171
|
+
engine().renderer.set_chromatic_aberration(strength as f32);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
#[no_mangle]
|
|
1175
|
+
pub extern "C" fn bloom_set_vignette(strength: f64, softness: f64) {
|
|
1176
|
+
engine().renderer.set_vignette(strength as f32, softness as f32);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
#[no_mangle]
|
|
1180
|
+
pub extern "C" fn bloom_set_film_grain(strength: f64) {
|
|
1181
|
+
engine().renderer.set_film_grain(strength as f32);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
#[no_mangle]
|
|
1185
|
+
pub extern "C" fn bloom_set_sun_shafts(strength: f64, decay: f64, r: f64, g: f64, b: f64) {
|
|
1186
|
+
let eng = engine();
|
|
1187
|
+
eng.renderer.set_sun_shaft_strength(strength as f32);
|
|
1188
|
+
eng.renderer.set_sun_shaft_decay(decay as f32);
|
|
1189
|
+
eng.renderer.set_sun_shaft_color(r as f32, g as f32, b as f32);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
#[no_mangle]
|
|
1193
|
+
pub extern "C" fn bloom_set_auto_exposure(on: f64) {
|
|
1194
|
+
engine().renderer.set_auto_exposure(on != 0.0);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
#[no_mangle]
|
|
1198
|
+
pub extern "C" fn bloom_set_taa_enabled(on: f64) {
|
|
1199
|
+
engine().renderer.set_taa_enabled(on != 0.0);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
#[no_mangle]
|
|
1203
|
+
pub extern "C" fn bloom_set_render_scale(scale: f64) {
|
|
1204
|
+
engine().renderer.set_render_scale(scale as f32);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
#[no_mangle]
|
|
1208
|
+
pub extern "C" fn bloom_get_render_scale() -> f64 {
|
|
1209
|
+
engine().renderer.render_scale() as f64
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
#[no_mangle]
|
|
1213
|
+
pub extern "C" fn bloom_set_upscale_mode(mode: f64) {
|
|
1214
|
+
engine().renderer.set_upscale_mode(mode as u32);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
#[no_mangle]
|
|
1218
|
+
pub extern "C" fn bloom_set_cas_strength(strength: f64) {
|
|
1219
|
+
engine().renderer.set_cas_strength(strength as f32);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
#[no_mangle]
|
|
1223
|
+
pub extern "C" fn bloom_get_physical_width() -> f64 {
|
|
1224
|
+
engine().renderer.physical_width() as f64
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
#[no_mangle]
|
|
1228
|
+
pub extern "C" fn bloom_get_physical_height() -> f64 {
|
|
1229
|
+
engine().renderer.physical_height() as f64
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
#[no_mangle]
|
|
1233
|
+
pub extern "C" fn bloom_set_auto_resolution(target_hz: f64, enabled: f64) {
|
|
1234
|
+
let eng = engine();
|
|
1235
|
+
if enabled != 0.0 {
|
|
1236
|
+
let current = eng.renderer.render_scale();
|
|
1237
|
+
eng.drs.enable(target_hz as f32, current);
|
|
1238
|
+
} else {
|
|
1239
|
+
eng.drs.disable();
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
#[no_mangle]
|
|
1244
|
+
pub extern "C" fn bloom_set_manual_exposure(value: f64) {
|
|
1245
|
+
engine().renderer.set_manual_exposure(value as f32);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
#[no_mangle]
|
|
1249
|
+
pub extern "C" fn bloom_set_env_intensity(intensity: f64) {
|
|
1250
|
+
engine().renderer.set_env_intensity(intensity as f32);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
#[no_mangle]
|
|
1254
|
+
pub extern "C" fn bloom_set_ssgi_enabled(enabled: f64) {
|
|
1255
|
+
engine().renderer.set_ssgi_enabled(enabled != 0.0);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
#[no_mangle]
|
|
1259
|
+
pub extern "C" fn bloom_set_ssgi_intensity(intensity: f64) {
|
|
1260
|
+
engine().renderer.set_ssgi_intensity(intensity as f32);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
#[no_mangle]
|
|
1264
|
+
pub extern "C" fn bloom_set_ssgi_radius(radius: f64) {
|
|
1265
|
+
engine().renderer.set_ssgi_radius(radius as f32);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
#[no_mangle]
|
|
1269
|
+
pub extern "C" fn bloom_set_dof(enabled: f64, focus_distance: f64, aperture: f64) {
|
|
1270
|
+
let r = &mut engine().renderer;
|
|
1271
|
+
r.set_dof_enabled(enabled != 0.0);
|
|
1272
|
+
r.set_dof_focus_distance(focus_distance as f32);
|
|
1273
|
+
r.set_dof_aperture(aperture as f32);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// ============================================================
|
|
1277
|
+
// Render quality toggles (individual + preset)
|
|
1278
|
+
// ============================================================
|
|
1279
|
+
|
|
1280
|
+
#[no_mangle]
|
|
1281
|
+
pub extern "C" fn bloom_set_quality_preset(preset: f64) {
|
|
1282
|
+
engine().renderer.apply_quality_preset(preset as u32);
|
|
1283
|
+
}
|
|
1284
|
+
#[no_mangle]
|
|
1285
|
+
pub extern "C" fn bloom_set_shadows_enabled(on: f64) {
|
|
1286
|
+
engine().renderer.set_shadows_enabled(on != 0.0);
|
|
1287
|
+
}
|
|
1288
|
+
#[no_mangle]
|
|
1289
|
+
pub extern "C" fn bloom_set_shadows_always_fresh(on: f64) {
|
|
1290
|
+
engine().renderer.set_shadows_always_fresh(on != 0.0);
|
|
1291
|
+
}
|
|
1292
|
+
#[no_mangle]
|
|
1293
|
+
pub extern "C" fn bloom_set_bloom_enabled(on: f64) {
|
|
1294
|
+
engine().renderer.set_bloom_enabled(on != 0.0);
|
|
1295
|
+
}
|
|
1296
|
+
#[no_mangle]
|
|
1297
|
+
pub extern "C" fn bloom_set_ssao_enabled(on: f64) {
|
|
1298
|
+
engine().renderer.set_ssao_enabled(on != 0.0);
|
|
1299
|
+
}
|
|
1300
|
+
#[no_mangle]
|
|
1301
|
+
pub extern "C" fn bloom_set_ssao_intensity(value: f64) {
|
|
1302
|
+
engine().renderer.set_ssao_strength(value as f32);
|
|
1303
|
+
}
|
|
1304
|
+
#[no_mangle]
|
|
1305
|
+
pub extern "C" fn bloom_set_ssao_radius(world_radius: f64) {
|
|
1306
|
+
engine().renderer.set_ssao_radius(world_radius as f32);
|
|
1307
|
+
}
|
|
1308
|
+
#[no_mangle]
|
|
1309
|
+
pub extern "C" fn bloom_set_wind(dir_x: f64, dir_z: f64, amplitude: f64, frequency: f64) {
|
|
1310
|
+
engine().renderer.set_wind(dir_x as f32, dir_z as f32, amplitude as f32, frequency as f32);
|
|
1311
|
+
}
|
|
1312
|
+
#[no_mangle]
|
|
1313
|
+
pub extern "C" fn bloom_set_ssr_enabled(on: f64) {
|
|
1314
|
+
engine().renderer.set_ssr_enabled(on != 0.0);
|
|
1315
|
+
}
|
|
1316
|
+
#[no_mangle]
|
|
1317
|
+
pub extern "C" fn bloom_set_motion_blur_enabled(on: f64) {
|
|
1318
|
+
engine().renderer.set_motion_blur_enabled(on != 0.0);
|
|
1319
|
+
}
|
|
1320
|
+
#[no_mangle]
|
|
1321
|
+
pub extern "C" fn bloom_set_sss_enabled(on: f64) {
|
|
1322
|
+
engine().renderer.set_sss_enabled(on != 0.0);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// ============================================================
|
|
1326
|
+
// Profiler — CPU phase timings (always available) + GPU timestamps
|
|
1327
|
+
// (when the adapter supports TIMESTAMP_QUERY). Disabled by default.
|
|
1328
|
+
// ============================================================
|
|
1329
|
+
|
|
1330
|
+
#[no_mangle]
|
|
1331
|
+
pub extern "C" fn bloom_set_profiler_enabled(on: f64) {
|
|
1332
|
+
engine().profiler.set_enabled(on != 0.0);
|
|
1333
|
+
}
|
|
1334
|
+
#[no_mangle]
|
|
1335
|
+
pub extern "C" fn bloom_get_profiler_frame_cpu_us() -> f64 {
|
|
1336
|
+
engine().profiler.avg_frame_cpu_us()
|
|
1337
|
+
}
|
|
1338
|
+
#[no_mangle]
|
|
1339
|
+
pub extern "C" fn bloom_get_profiler_frame_gpu_us() -> f64 {
|
|
1340
|
+
engine().profiler.avg_frame_gpu_us()
|
|
1341
|
+
}
|
|
1342
|
+
#[no_mangle]
|
|
1343
|
+
pub extern "C" fn bloom_print_profiler_summary() {
|
|
1344
|
+
print!("{}", engine().profiler.summary());
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/// Phase 8 — formatted per-pass overlay text. Returns a Perry-style
|
|
1348
|
+
/// header-prefixed string (12-byte header: len, cap, flags, then
|
|
1349
|
+
/// UTF-8 bytes) with one line per pass sorted by CPU time descending:
|
|
1350
|
+
///
|
|
1351
|
+
/// "<label>|<cpu_us>|<gpu_us_or_-1>\n..."
|
|
1352
|
+
///
|
|
1353
|
+
/// The TS overlay splits on \n then | per line. Games call this once
|
|
1354
|
+
/// per overlay-draw frame.
|
|
1355
|
+
/// Phase 7 — submit a world-space impulse. The renderer's per-frame
|
|
1356
|
+
/// compute pass decays + accumulates submissions into a 256×256
|
|
1357
|
+
/// R32Float texture covering a 128 m centred square; materials that
|
|
1358
|
+
/// sample `impulse_tex` (group 4 binding 4) read the result. Up to 16
|
|
1359
|
+
/// splats per frame are accepted — overflow is dropped silently.
|
|
1360
|
+
#[no_mangle]
|
|
1361
|
+
pub extern "C" fn bloom_splat_impulse(x: f64, z: f64, radius: f64, strength: f64) {
|
|
1362
|
+
engine().renderer.impulse_field.submit_splat(
|
|
1363
|
+
x as f32, z as f32, radius as f32, strength as f32,
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/// Phase 5 — set per-material `user_params` (ABI §1.4). Bytes are
|
|
1368
|
+
/// uploaded verbatim to `@group(2) @binding(11)`; shaders cast them
|
|
1369
|
+
/// to whatever struct they declared. `param_count` is the number of
|
|
1370
|
+
/// f64 slots in the params array (each slot is 8 bytes in Perry's
|
|
1371
|
+
/// number[] layout, but we only forward the low 4 bytes as f32 since
|
|
1372
|
+
/// WGSL UBO scalars are 4-byte). Up to 64 floats (256 bytes).
|
|
1373
|
+
///
|
|
1374
|
+
/// Pass `param_count == 0` to revert the material to the default
|
|
1375
|
+
/// zero-initialised UBO.
|
|
1376
|
+
#[no_mangle]
|
|
1377
|
+
pub extern "C" fn bloom_set_material_params(
|
|
1378
|
+
handle: f64,
|
|
1379
|
+
params_ptr: *const f64,
|
|
1380
|
+
param_count: f64,
|
|
1381
|
+
) {
|
|
1382
|
+
let count = param_count as usize;
|
|
1383
|
+
if count > 64 {
|
|
1384
|
+
eprintln!("[material] set_material_params: param_count {} > 64 (256-byte UBO cap)", count);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
let mut bytes = vec![0u8; count * 4];
|
|
1388
|
+
if !params_ptr.is_null() && count > 0 {
|
|
1389
|
+
let slots = unsafe { std::slice::from_raw_parts(params_ptr, count) };
|
|
1390
|
+
for (i, &v) in slots.iter().enumerate() {
|
|
1391
|
+
let f = v as f32;
|
|
1392
|
+
bytes[i*4..i*4+4].copy_from_slice(&f.to_le_bytes());
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
let eng = engine();
|
|
1396
|
+
if let Err(e) = eng.renderer.material_system.set_user_params(
|
|
1397
|
+
&eng.renderer.device, &eng.renderer.queue,
|
|
1398
|
+
handle as u32, &bytes,
|
|
1399
|
+
) {
|
|
1400
|
+
eprintln!("[material] set_material_params failed: {}", e);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/// Phase 8 — frame history for the histogram. Header-prefixed string,
|
|
1405
|
+
/// one line per frame "<cpu_us>|<gpu_us>", oldest first, up to 120
|
|
1406
|
+
/// lines.
|
|
1407
|
+
#[no_mangle]
|
|
1408
|
+
pub extern "C" fn bloom_profiler_frame_history() -> *const u8 {
|
|
1409
|
+
let hist = engine().profiler.frame_history();
|
|
1410
|
+
let mut s = String::with_capacity(hist.len() * 24);
|
|
1411
|
+
for (cpu, gpu) in &hist {
|
|
1412
|
+
s.push_str(&format!("{:.2}|{:.2}\n", cpu, gpu));
|
|
1413
|
+
}
|
|
1414
|
+
let bytes = s.as_bytes();
|
|
1415
|
+
let len = bytes.len();
|
|
1416
|
+
let total = 12 + len;
|
|
1417
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
1418
|
+
unsafe {
|
|
1419
|
+
let ptr = std::alloc::alloc(layout);
|
|
1420
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
1421
|
+
*(ptr as *mut u32) = len as u32;
|
|
1422
|
+
*(ptr.add(4) as *mut u32) = len as u32;
|
|
1423
|
+
*(ptr.add(8) as *mut u32) = 1;
|
|
1424
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
1425
|
+
ptr
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
#[no_mangle]
|
|
1430
|
+
pub extern "C" fn bloom_profiler_overlay_text() -> *const u8 {
|
|
1431
|
+
let snap = engine().profiler.snapshot();
|
|
1432
|
+
let mut s = String::with_capacity(snap.len() * 48);
|
|
1433
|
+
for (label, cpu, gpu) in &snap {
|
|
1434
|
+
s.push_str(label);
|
|
1435
|
+
s.push('|');
|
|
1436
|
+
s.push_str(&format!("{:.2}", cpu));
|
|
1437
|
+
s.push('|');
|
|
1438
|
+
match gpu {
|
|
1439
|
+
Some(g) => s.push_str(&format!("{:.2}", g)),
|
|
1440
|
+
None => s.push_str("-1"),
|
|
1441
|
+
}
|
|
1442
|
+
s.push('\n');
|
|
1443
|
+
}
|
|
1444
|
+
let bytes = s.as_bytes();
|
|
1445
|
+
let len = bytes.len();
|
|
1446
|
+
let total = 12 + len;
|
|
1447
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
1448
|
+
unsafe {
|
|
1449
|
+
let ptr = std::alloc::alloc(layout);
|
|
1450
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
1451
|
+
*(ptr as *mut u32) = len as u32;
|
|
1452
|
+
*(ptr.add(4) as *mut u32) = len as u32;
|
|
1453
|
+
*(ptr.add(8) as *mut u32) = 1;
|
|
1454
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
1455
|
+
ptr
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// ============================================================
|
|
1460
|
+
// Models
|
|
1461
|
+
// ============================================================
|
|
1462
|
+
|
|
1463
|
+
#[no_mangle]
|
|
1464
|
+
pub extern "C" fn bloom_load_model(path_ptr: *const u8) -> f64 {
|
|
1465
|
+
let path = str_from_header(path_ptr);
|
|
1466
|
+
match std::fs::read(path) {
|
|
1467
|
+
Ok(data) => {
|
|
1468
|
+
let eng = engine();
|
|
1469
|
+
let EngineState { ref mut models, ref mut renderer, .. } = *eng;
|
|
1470
|
+
// .gltf = loose file with external .bin + image URIs; pass
|
|
1471
|
+
// the directory so relative paths resolve. .glb (binary)
|
|
1472
|
+
// embeds everything and needs no base dir.
|
|
1473
|
+
let p = std::path::Path::new(&path);
|
|
1474
|
+
let is_loose = p.extension().and_then(|e| e.to_str()) == Some("gltf");
|
|
1475
|
+
if is_loose {
|
|
1476
|
+
if let Some(dir) = p.parent() {
|
|
1477
|
+
return models.load_model_with_textures_from_path(&data, dir, renderer);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
models.load_model_with_textures(&data, renderer)
|
|
1481
|
+
}
|
|
1482
|
+
Err(_) => 0.0,
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
#[no_mangle]
|
|
1487
|
+
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) {
|
|
1488
|
+
let eng = engine();
|
|
1489
|
+
if let Some(model) = eng.models.get(handle) {
|
|
1490
|
+
let position = [x as f32, y as f32, z as f32];
|
|
1491
|
+
let scale = scale as f32;
|
|
1492
|
+
let tint = [(r / 255.0) as f32, (g / 255.0) as f32, (b / 255.0) as f32, (a / 255.0) as f32];
|
|
1493
|
+
let handle_bits = handle.to_bits();
|
|
1494
|
+
if eng.renderer.cache_model_if_static(handle_bits, &model.meshes) {
|
|
1495
|
+
eng.renderer.draw_model_cached(handle_bits, position, scale, tint);
|
|
1496
|
+
} else {
|
|
1497
|
+
for mesh in &model.meshes {
|
|
1498
|
+
let tex_idx = mesh.texture_idx.unwrap_or(0);
|
|
1499
|
+
eng.renderer.draw_model_mesh_tinted(&mesh.vertices, &mesh.indices, position, scale, tint, tex_idx);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
#[no_mangle]
|
|
1506
|
+
pub extern "C" fn bloom_draw_model_rotated(
|
|
1507
|
+
handle: f64, x: f64, y: f64, z: f64,
|
|
1508
|
+
scale: f64, rot_y: f64,
|
|
1509
|
+
color_packed_argb: f64,
|
|
1510
|
+
) {
|
|
1511
|
+
// Pack RGBA into one f64 to keep the FFI to 7 args; the 9th f64
|
|
1512
|
+
// would otherwise be corrupted by the Perry-ARM64 stack-arg quirk.
|
|
1513
|
+
let bits = color_packed_argb as u32;
|
|
1514
|
+
let a = ((bits >> 24) & 0xff) as f32 / 255.0;
|
|
1515
|
+
let r = ((bits >> 16) & 0xff) as f32 / 255.0;
|
|
1516
|
+
let g = ((bits >> 8) & 0xff) as f32 / 255.0;
|
|
1517
|
+
let b = ( bits & 0xff) as f32 / 255.0;
|
|
1518
|
+
let eng = engine();
|
|
1519
|
+
if let Some(model) = eng.models.get(handle) {
|
|
1520
|
+
let position = [x as f32, y as f32, z as f32];
|
|
1521
|
+
let scale = scale as f32;
|
|
1522
|
+
let tint = [r, g, b, a];
|
|
1523
|
+
// Skip the static-mesh cache: it bakes vertex transforms with
|
|
1524
|
+
// identity rotation, so a rotated draw must take the dynamic
|
|
1525
|
+
// path. Cheap — meshes are unlikely to land in the cache twice
|
|
1526
|
+
// anyway when callers mix rotated + unrotated draws.
|
|
1527
|
+
for mesh in &model.meshes {
|
|
1528
|
+
let tex_idx = mesh.texture_idx.unwrap_or(0);
|
|
1529
|
+
eng.renderer.draw_model_mesh_tinted_rotated(
|
|
1530
|
+
&mesh.vertices, &mesh.indices, position, scale, tint, tex_idx, rot_y as f32,
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
#[no_mangle]
|
|
1537
|
+
pub extern "C" fn bloom_unload_model(handle: f64) {
|
|
1538
|
+
engine().models.unload_model(handle);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
#[no_mangle]
|
|
1542
|
+
pub extern "C" fn bloom_gen_mesh_cube(w: f64, h: f64, d: f64) -> f64 {
|
|
1543
|
+
engine().models.gen_mesh_cube(w as f32, h as f32, d as f32)
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
#[no_mangle]
|
|
1547
|
+
pub extern "C" fn bloom_gen_mesh_heightmap(image_handle: f64, size_x: f64, size_y: f64, size_z: f64) -> f64 {
|
|
1548
|
+
let eng = engine();
|
|
1549
|
+
if let Some(img) = eng.textures.images.get(image_handle) {
|
|
1550
|
+
let data = img.data.clone();
|
|
1551
|
+
let w = img.width;
|
|
1552
|
+
let h = img.height;
|
|
1553
|
+
eng.models.gen_mesh_heightmap(&data, w, h, size_x as f32, size_y as f32, size_z as f32)
|
|
1554
|
+
} else {
|
|
1555
|
+
0.0
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
#[no_mangle]
|
|
1560
|
+
pub extern "C" fn bloom_load_shader(source_ptr: *const u8) -> f64 {
|
|
1561
|
+
let source = str_from_header(source_ptr);
|
|
1562
|
+
engine().renderer.load_custom_shader(source) as f64
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// ============================================================
|
|
1566
|
+
// Phase 1c — material system FFI
|
|
1567
|
+
// ============================================================
|
|
1568
|
+
|
|
1569
|
+
#[no_mangle]
|
|
1570
|
+
pub extern "C" fn bloom_compile_material(source_ptr: *const u8) -> f64 {
|
|
1571
|
+
let source = str_from_header(source_ptr);
|
|
1572
|
+
match engine().renderer.compile_material(source) {
|
|
1573
|
+
Ok(handle) => handle as f64,
|
|
1574
|
+
Err(e) => {
|
|
1575
|
+
eprintln!("[material] compile failed: {:?}", e);
|
|
1576
|
+
0.0
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/// Phase 4b — compile a refractive material. Shorthand for
|
|
1582
|
+
/// `compile_material_with_options(src, Translucent, Refractive,
|
|
1583
|
+
/// true)`. Perry's ARM64 FFI garbles mixed-signature calls with
|
|
1584
|
+
/// more than a pointer + 0-1 floats, so we ship dedicated entry
|
|
1585
|
+
/// points per (profile, bucket, reads_scene) combo that games
|
|
1586
|
+
/// actually need.
|
|
1587
|
+
#[no_mangle]
|
|
1588
|
+
pub extern "C" fn bloom_compile_material_refractive(source_ptr: *const u8) -> f64 {
|
|
1589
|
+
use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
|
|
1590
|
+
let source = str_from_header(source_ptr);
|
|
1591
|
+
match engine().renderer.compile_material_with_options(
|
|
1592
|
+
source, FragmentProfile::Translucent, Bucket::Refractive, true, false,
|
|
1593
|
+
) {
|
|
1594
|
+
Ok(handle) => handle as f64,
|
|
1595
|
+
Err(e) => { eprintln!("[refractive] compile failed: {:?}", e); 0.0 }
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/// Phase 4b — compile a transparent (non-refractive) material.
|
|
1600
|
+
/// Profile=Translucent, Bucket=Transparent, reads_scene=false.
|
|
1601
|
+
#[no_mangle]
|
|
1602
|
+
pub extern "C" fn bloom_compile_material_transparent(source_ptr: *const u8) -> f64 {
|
|
1603
|
+
use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
|
|
1604
|
+
let source = str_from_header(source_ptr);
|
|
1605
|
+
match engine().renderer.compile_material_with_options(
|
|
1606
|
+
source, FragmentProfile::Translucent, Bucket::Transparent, false, false,
|
|
1607
|
+
) {
|
|
1608
|
+
Ok(handle) => handle as f64,
|
|
1609
|
+
Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/// Phase 4b — compile an additive material. Profile=Translucent,
|
|
1614
|
+
/// Bucket=Additive, reads_scene=false.
|
|
1615
|
+
#[no_mangle]
|
|
1616
|
+
pub extern "C" fn bloom_compile_material_additive(source_ptr: *const u8) -> f64 {
|
|
1617
|
+
use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
|
|
1618
|
+
let source = str_from_header(source_ptr);
|
|
1619
|
+
match engine().renderer.compile_material_with_options(
|
|
1620
|
+
source, FragmentProfile::Translucent, Bucket::Additive, false, false,
|
|
1621
|
+
) {
|
|
1622
|
+
Ok(handle) => handle as f64,
|
|
1623
|
+
Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/// EN-010 — compile a cutout (alpha-tested) material. Profile=Opaque
|
|
1628
|
+
/// (full G-buffer write, sun shadow, SSAO), Bucket=Cutout (rendered
|
|
1629
|
+
/// double-sided so foliage is visible from both sides),
|
|
1630
|
+
/// reads_scene=false. The fragment shader is expected to call
|
|
1631
|
+
/// `discard` against `material.metal_rough.w`
|
|
1632
|
+
/// (`MaterialFactors.alpha_cutoff`) to drop transparent texels.
|
|
1633
|
+
#[no_mangle]
|
|
1634
|
+
pub extern "C" fn bloom_compile_material_cutout(source_ptr: *const u8) -> f64 {
|
|
1635
|
+
use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
|
|
1636
|
+
let source = str_from_header(source_ptr);
|
|
1637
|
+
match engine().renderer.compile_material_with_options(
|
|
1638
|
+
source, FragmentProfile::Opaque, Bucket::Cutout, false, false,
|
|
1639
|
+
) {
|
|
1640
|
+
Ok(handle) => handle as f64,
|
|
1641
|
+
Err(e) => { eprintln!("[material] compile failed: {:?}", e); 0.0 }
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/// EN-001 — compile a material pipeline that opts into the standard
|
|
1646
|
+
/// per-instance vertex layout (Opaque profile + Opaque bucket +
|
|
1647
|
+
/// wants_instancing). Pair with `bloom_create_instance_buffer` +
|
|
1648
|
+
/// `bloom_submit_material_draw_instanced` to drive per-instance
|
|
1649
|
+
/// rendering through a single draw_indexed call.
|
|
1650
|
+
///
|
|
1651
|
+
/// Like the other `bloom_compile_material_*` variants this is
|
|
1652
|
+
/// macOS-only: the existing material-compile FFI surface lives only
|
|
1653
|
+
/// on macOS today (per the EN-010 precedent), and porting all five
|
|
1654
|
+
/// variants to ios/tvos/windows/linux/android/web is out of scope for
|
|
1655
|
+
/// EN-001. The runtime triple
|
|
1656
|
+
/// (`bloom_create_instance_buffer` / `bloom_submit_material_draw_instanced`
|
|
1657
|
+
/// / `bloom_destroy_instance_buffer`) follows the same scope so the
|
|
1658
|
+
/// surface is consistent — they're useless without the compile FFI.
|
|
1659
|
+
#[no_mangle]
|
|
1660
|
+
pub extern "C" fn bloom_compile_material_instanced(source_ptr: *const u8) -> f64 {
|
|
1661
|
+
let source = str_from_header(source_ptr);
|
|
1662
|
+
match engine().renderer.compile_material_instanced(source) {
|
|
1663
|
+
Ok(handle) => handle as f64,
|
|
1664
|
+
Err(e) => { eprintln!("[material] instanced compile failed: {:?}", e); 0.0 }
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/// EN-001 — upload a flat per-instance buffer to GPU memory. Perry's
|
|
1669
|
+
/// `number[]` is f64-laid-out in memory; this FFI takes the pointer
|
|
1670
|
+
/// + the instance count and reads `instance_count * 9` f64 slots.
|
|
1671
|
+
/// The Rust side downcasts to f32 and pads each instance to 12 floats
|
|
1672
|
+
/// (48 bytes, vec4-aligned) for the GPU stride. Returns a 1-based
|
|
1673
|
+
/// handle, or 0 on failure (null pointer or zero count).
|
|
1674
|
+
#[no_mangle]
|
|
1675
|
+
pub extern "C" fn bloom_create_instance_buffer(
|
|
1676
|
+
data_ptr: *const f64, instance_count: f64,
|
|
1677
|
+
) -> f64 {
|
|
1678
|
+
if data_ptr.is_null() || instance_count <= 0.0 { return 0.0; }
|
|
1679
|
+
let count = instance_count as u32;
|
|
1680
|
+
let slot_count = (count as usize) * 9;
|
|
1681
|
+
let raw_f64 = unsafe { std::slice::from_raw_parts(data_ptr, slot_count) };
|
|
1682
|
+
let raw_f32: Vec<f32> = raw_f64.iter().map(|&v| v as f32).collect();
|
|
1683
|
+
engine().renderer.create_instance_buffer(&raw_f32, count) as f64
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/// EN-001 — submit an instanced material draw. The mesh at
|
|
1687
|
+
/// (mesh_handle, mesh_idx) is drawn `instance_count` times via a
|
|
1688
|
+
/// single `draw_indexed` with the instance buffer bound at vertex
|
|
1689
|
+
/// slot 1. The pipeline at `material` must have been compiled via
|
|
1690
|
+
/// `bloom_compile_material_instanced`. Capped at 5 f64 args to dodge
|
|
1691
|
+
/// the Perry-ARM64 9th-arg garbling quirk.
|
|
1692
|
+
#[no_mangle]
|
|
1693
|
+
pub extern "C" fn bloom_submit_material_draw_instanced(
|
|
1694
|
+
material: f64, mesh_handle: f64, mesh_idx: f64,
|
|
1695
|
+
instance_buffer: f64, instance_count: f64,
|
|
1696
|
+
) {
|
|
1697
|
+
let eng = engine();
|
|
1698
|
+
let handle_bits = mesh_handle.to_bits();
|
|
1699
|
+
if let Some(model) = eng.models.get(mesh_handle) {
|
|
1700
|
+
eng.renderer.cache_model_if_static(handle_bits, &model.meshes);
|
|
1701
|
+
}
|
|
1702
|
+
eng.renderer.submit_material_draw_instanced(
|
|
1703
|
+
material as u32,
|
|
1704
|
+
handle_bits,
|
|
1705
|
+
mesh_idx as usize,
|
|
1706
|
+
instance_buffer as u32,
|
|
1707
|
+
instance_count as u32,
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/// EN-001 — release the GPU memory backing an instance buffer. The
|
|
1712
|
+
/// slot stays `None` so previously-issued handles can never alias a
|
|
1713
|
+
/// future allocation. Safe to call with handle 0 or stale handles.
|
|
1714
|
+
#[no_mangle]
|
|
1715
|
+
pub extern "C" fn bloom_destroy_instance_buffer(handle: f64) {
|
|
1716
|
+
engine().renderer.destroy_instance_buffer(handle as u32);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/// EN-011 — create a planar reflection probe and return its 1-based
|
|
1720
|
+
/// handle (0 on failure). Each frame, the engine renders the world
|
|
1721
|
+
/// (minus materials linked to a probe) from a camera mirrored across
|
|
1722
|
+
/// (`plane_y`, `normal_xyz`) into a square HDR RT at `resolution²`.
|
|
1723
|
+
/// Pass `resolution = 0` to default to half the swapchain width.
|
|
1724
|
+
///
|
|
1725
|
+
/// Pair with `bloom_set_material_reflection_probe` to wire the probe
|
|
1726
|
+
/// into a water material's `@group(2) @binding(12)` slot.
|
|
1727
|
+
#[no_mangle]
|
|
1728
|
+
pub extern "C" fn bloom_create_planar_reflection(
|
|
1729
|
+
plane_y: f64, nx: f64, ny: f64, nz: f64, resolution: f64,
|
|
1730
|
+
) -> f64 {
|
|
1731
|
+
engine().renderer.create_planar_reflection(
|
|
1732
|
+
plane_y as f32,
|
|
1733
|
+
[nx as f32, ny as f32, nz as f32],
|
|
1734
|
+
resolution as u32,
|
|
1735
|
+
) as f64
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/// EN-011 — link a material to a planar reflection probe. Subsequent
|
|
1739
|
+
/// draws of `material` see the probe's RT at `@group(2) @binding(12)`.
|
|
1740
|
+
/// Pass `probe = 0` to revert to the default 1×1 black texture.
|
|
1741
|
+
#[no_mangle]
|
|
1742
|
+
pub extern "C" fn bloom_set_material_reflection_probe(
|
|
1743
|
+
material: f64, probe: f64,
|
|
1744
|
+
) {
|
|
1745
|
+
engine().renderer.set_material_reflection_probe(material as u32, probe as u32);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/// EN-014 — create a texture array from concatenated RGBA8 byte data.
|
|
1749
|
+
/// `data_ptr` points to `layer_count` × `width` × `height` × 4 bytes:
|
|
1750
|
+
/// each layer is one slab back-to-back. All layers share the same
|
|
1751
|
+
/// `width` × `height` (wgpu requires uniform extent for D2Array).
|
|
1752
|
+
/// Layer count is capped at 16 in V1; extras are dropped.
|
|
1753
|
+
///
|
|
1754
|
+
/// Returns a 1-based handle (0 on failure: null pointer, zero count,
|
|
1755
|
+
/// zero extent, or short data buffer). Pair with
|
|
1756
|
+
/// `bloom_set_material_texture_array` to bind the array to a
|
|
1757
|
+
/// material's @group(2) @binding 14/15/16 slot.
|
|
1758
|
+
///
|
|
1759
|
+
/// V1 takes raw bytes (not pre-loaded texture handles) because
|
|
1760
|
+
/// existing `bloom_load_texture` uploads to GPU and drops the source
|
|
1761
|
+
/// bytes — there's no CPU-side image registry to read from. Games
|
|
1762
|
+
/// pre-decode their layer images via the standard image-loading API
|
|
1763
|
+
/// (`loadImage`, etc.) and pass the raw RGBA bytes here.
|
|
1764
|
+
#[no_mangle]
|
|
1765
|
+
pub extern "C" fn bloom_create_texture_array(
|
|
1766
|
+
data_ptr: *const u8,
|
|
1767
|
+
data_len: f64,
|
|
1768
|
+
width: f64,
|
|
1769
|
+
height: f64,
|
|
1770
|
+
layer_count: f64,
|
|
1771
|
+
) -> f64 {
|
|
1772
|
+
// EN-014 V2 — V1 stays callable; forwards to _ex with default
|
|
1773
|
+
// format = sRGB (0) and mip_levels = 1 (no mips).
|
|
1774
|
+
bloom_create_texture_array_ex(data_ptr, data_len, width, height, layer_count, 0.0, 1.0)
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/// EN-014 V2 — create a texture array with explicit format + mip control.
|
|
1778
|
+
/// See `MaterialSystem::create_texture_array_ex` for semantics. The V1
|
|
1779
|
+
/// entry above is preserved as a thin wrapper.
|
|
1780
|
+
#[no_mangle]
|
|
1781
|
+
pub extern "C" fn bloom_create_texture_array_ex(
|
|
1782
|
+
data_ptr: *const u8,
|
|
1783
|
+
data_len: f64,
|
|
1784
|
+
width: f64,
|
|
1785
|
+
height: f64,
|
|
1786
|
+
layer_count: f64,
|
|
1787
|
+
format: f64,
|
|
1788
|
+
mip_levels: f64,
|
|
1789
|
+
) -> f64 {
|
|
1790
|
+
if data_ptr.is_null() || data_len <= 0.0 { return 0.0; }
|
|
1791
|
+
let w = width as u32;
|
|
1792
|
+
let h = height as u32;
|
|
1793
|
+
if w == 0 || h == 0 { return 0.0; }
|
|
1794
|
+
let layers_count = (layer_count as u32)
|
|
1795
|
+
.min(bloom_shared::renderer::material_system::MAX_TEXTURE_ARRAY_LAYERS);
|
|
1796
|
+
if layers_count == 0 { return 0.0; }
|
|
1797
|
+
let layer_size = (w as usize) * (h as usize) * 4;
|
|
1798
|
+
let total_bytes = (data_len as usize)
|
|
1799
|
+
.min(layers_count as usize * layer_size);
|
|
1800
|
+
let bytes = unsafe { std::slice::from_raw_parts(data_ptr, total_bytes) };
|
|
1801
|
+
let mut layers: Vec<(&[u8], u32, u32)> = Vec::with_capacity(layers_count as usize);
|
|
1802
|
+
for i in 0..(layers_count as usize) {
|
|
1803
|
+
let start = i * layer_size;
|
|
1804
|
+
let end = start + layer_size;
|
|
1805
|
+
if end > bytes.len() { break; }
|
|
1806
|
+
layers.push((&bytes[start..end], w, h));
|
|
1807
|
+
}
|
|
1808
|
+
engine().renderer.create_texture_array_ex(&layers, format as u32, mip_levels as u32) as f64
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/// EN-014 — link a texture-array handle to a material at one of three
|
|
1812
|
+
/// slots: 0 = albedo (binding 14), 1 = normal (binding 15),
|
|
1813
|
+
/// 2 = MR (binding 16). Pass `array = 0` to revert to the engine's
|
|
1814
|
+
/// 1×1×1 stub. No-op for unknown handles or out-of-range slots.
|
|
1815
|
+
#[no_mangle]
|
|
1816
|
+
pub extern "C" fn bloom_set_material_texture_array(
|
|
1817
|
+
material: f64, slot: f64, array: f64,
|
|
1818
|
+
) {
|
|
1819
|
+
engine().renderer.set_material_texture_array(
|
|
1820
|
+
material as u32, slot as u32, array as u32,
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/// EN-012 — set the shading model for a material.
|
|
1825
|
+
/// `0` = default lit (standard PBR),
|
|
1826
|
+
/// `1` = foliage (wrap-lambert + transmission via `shade_foliage`),
|
|
1827
|
+
/// `2` = subsurface (V2 stub — currently behaves as default lit).
|
|
1828
|
+
/// Lazily allocates a per-material `MaterialFactors` UBO on first
|
|
1829
|
+
/// call. Game shaders branch on `material.shading_model.x` and call
|
|
1830
|
+
/// either `shade_pbr_standard` or `shade_foliage` from the engine
|
|
1831
|
+
/// helper in `common/pbr.wgsl`.
|
|
1832
|
+
#[no_mangle]
|
|
1833
|
+
pub extern "C" fn bloom_set_material_shading_model(
|
|
1834
|
+
material: f64, model: f64,
|
|
1835
|
+
) {
|
|
1836
|
+
engine().renderer.set_material_shading_model(material as u32, model as u32);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/// EN-012 — set the foliage shading parameters for a material.
|
|
1840
|
+
/// Only takes effect when the material's `shading_model == 1`.
|
|
1841
|
+
///
|
|
1842
|
+
/// `trans_r/g/b`: rgb tint for back-lit foliage (1,1,1 = neutral).
|
|
1843
|
+
/// `trans_amount`: 0..1 — how much sun bleeds through the leaf when
|
|
1844
|
+
/// the camera looks at it backlit by the sun.
|
|
1845
|
+
/// `wrap_factor`: 0..1 — wrap-lambert intensity. 0 = standard lambert
|
|
1846
|
+
/// (back faces black), 1 = light wraps fully around.
|
|
1847
|
+
#[no_mangle]
|
|
1848
|
+
pub extern "C" fn bloom_set_material_foliage(
|
|
1849
|
+
material: f64,
|
|
1850
|
+
trans_r: f64, trans_g: f64, trans_b: f64,
|
|
1851
|
+
trans_amount: f64, wrap_factor: f64,
|
|
1852
|
+
) {
|
|
1853
|
+
engine().renderer.set_material_foliage(
|
|
1854
|
+
material as u32,
|
|
1855
|
+
[trans_r as f32, trans_g as f32, trans_b as f32],
|
|
1856
|
+
trans_amount as f32, wrap_factor as f32,
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/// Phase 6 — file-backed material compile. The path is registered
|
|
1861
|
+
/// with the hot-reload watcher; subsequent edits trigger an automatic
|
|
1862
|
+
/// recompile on the next end_frame. `bucket_kind` selects the
|
|
1863
|
+
/// (profile, bucket, reads_scene) preset:
|
|
1864
|
+
/// 0 = opaque
|
|
1865
|
+
/// 1 = transparent (translucent, no scene snapshot)
|
|
1866
|
+
/// 2 = refractive (translucent + scene snapshot)
|
|
1867
|
+
/// 3 = additive (translucent, no scene snapshot, additive blend)
|
|
1868
|
+
/// 4 = cutout (opaque profile, double-sided, fragment-discard
|
|
1869
|
+
/// against MaterialFactors.alpha_cutoff)
|
|
1870
|
+
#[no_mangle]
|
|
1871
|
+
pub extern "C" fn bloom_compile_material_from_file(
|
|
1872
|
+
path_ptr: *const u8,
|
|
1873
|
+
bucket_kind: f64,
|
|
1874
|
+
) -> f64 {
|
|
1875
|
+
use bloom_shared::renderer::material_pipeline::{FragmentProfile, Bucket};
|
|
1876
|
+
let path = str_from_header(path_ptr);
|
|
1877
|
+
let (profile, bucket, reads_scene) = match bucket_kind as u32 {
|
|
1878
|
+
0 => (FragmentProfile::Opaque, Bucket::Opaque, false),
|
|
1879
|
+
1 => (FragmentProfile::Translucent, Bucket::Transparent, false),
|
|
1880
|
+
2 => (FragmentProfile::Translucent, Bucket::Refractive, true),
|
|
1881
|
+
3 => (FragmentProfile::Translucent, Bucket::Additive, false),
|
|
1882
|
+
4 => (FragmentProfile::Opaque, Bucket::Cutout, false),
|
|
1883
|
+
_ => {
|
|
1884
|
+
eprintln!("[material] from_file: unknown bucket_kind {bucket_kind}");
|
|
1885
|
+
return 0.0;
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
match engine().renderer.compile_material_from_file(
|
|
1889
|
+
std::path::Path::new(path), profile, bucket, reads_scene,
|
|
1890
|
+
) {
|
|
1891
|
+
Ok(handle) => handle as f64,
|
|
1892
|
+
Err(e) => { eprintln!("[material] from_file failed: {e}"); 0.0 }
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/// EN-017 — compile + install a fullscreen post-pass material. Runs
|
|
1897
|
+
/// after composite + tonemapping, before the 2D overlay (HUD stays
|
|
1898
|
+
/// crisp). The fragment shader must declare:
|
|
1899
|
+
///
|
|
1900
|
+
/// @fragment fn fs_main(@location(0) uv: vec2<f32>)
|
|
1901
|
+
/// -> @location(0) vec4<f32> { ... }
|
|
1902
|
+
///
|
|
1903
|
+
/// and may sample `scene_color_tex` (LDR, post-tonemap), `scene_depth_tex`
|
|
1904
|
+
/// from the engine-provided bind group at `@group(0)` —
|
|
1905
|
+
/// see `post_pass::POST_PASS_PRELUDE` for the full ABI. Returns 1.0
|
|
1906
|
+
/// on success, 0.0 on compile failure.
|
|
1907
|
+
#[no_mangle]
|
|
1908
|
+
pub extern "C" fn bloom_set_post_pass(source_ptr: *const u8) -> f64 {
|
|
1909
|
+
let source = str_from_header(source_ptr);
|
|
1910
|
+
match engine().renderer.set_post_pass(source) {
|
|
1911
|
+
Ok(()) => 1.0,
|
|
1912
|
+
Err(e) => { eprintln!("[post_pass] compile failed: {:?}", e); 0.0 }
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
/// EN-017 — uninstall the active post-pass. Composite goes back to
|
|
1917
|
+
/// writing the swapchain directly (zero-cost original path).
|
|
1918
|
+
#[no_mangle]
|
|
1919
|
+
pub extern "C" fn bloom_clear_post_pass() {
|
|
1920
|
+
engine().renderer.clear_post_pass();
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/// EN-017 V2 — append a post-pass to the stack. Returns a 1-based
|
|
1924
|
+
/// handle on success (so 0 means failure at the FFI layer; the TS
|
|
1925
|
+
/// wrapper subtracts 1 to expose a 0-based index). The fragment
|
|
1926
|
+
/// shader sees `scene_color_tex` (LDR, post-tonemap) +
|
|
1927
|
+
/// `scene_depth_tex` at `@group(0)` — same ABI as
|
|
1928
|
+
/// `bloom_set_post_pass`, just cumulative.
|
|
1929
|
+
#[no_mangle]
|
|
1930
|
+
pub extern "C" fn bloom_add_post_pass(source_ptr: *const u8) -> f64 {
|
|
1931
|
+
let source = str_from_header(source_ptr);
|
|
1932
|
+
match engine().renderer.add_post_pass(source) {
|
|
1933
|
+
Ok(h) => h as f64,
|
|
1934
|
+
Err(e) => { eprintln!("[post_pass] compile failed: {:?}", e); 0.0 }
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/// EN-017 V2 — wipe the entire post-pass stack. Composite goes back
|
|
1939
|
+
/// to writing the swapchain directly (zero post-pass cost).
|
|
1940
|
+
#[no_mangle]
|
|
1941
|
+
pub extern "C" fn bloom_clear_all_post_passes() {
|
|
1942
|
+
engine().renderer.clear_all_post_passes();
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
#[no_mangle]
|
|
1946
|
+
pub extern "C" fn bloom_draw_material(
|
|
1947
|
+
material: f64,
|
|
1948
|
+
mesh_handle: f64,
|
|
1949
|
+
mesh_idx: f64,
|
|
1950
|
+
x: f64, y: f64, z: f64, scale: f64,
|
|
1951
|
+
r: f64, g: f64, b: f64, a: f64,
|
|
1952
|
+
) {
|
|
1953
|
+
let eng = engine();
|
|
1954
|
+
let handle_bits = mesh_handle.to_bits();
|
|
1955
|
+
if let Some(model) = eng.models.get(mesh_handle) {
|
|
1956
|
+
eng.renderer.cache_model_if_static(handle_bits, &model.meshes);
|
|
1957
|
+
}
|
|
1958
|
+
eng.renderer.submit_material_draw(
|
|
1959
|
+
material as u32,
|
|
1960
|
+
handle_bits,
|
|
1961
|
+
mesh_idx as usize,
|
|
1962
|
+
[x as f32, y as f32, z as f32],
|
|
1963
|
+
scale as f32,
|
|
1964
|
+
[(r / 255.0) as f32, (g / 255.0) as f32, (b / 255.0) as f32, (a / 255.0) as f32],
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
#[no_mangle]
|
|
1969
|
+
pub extern "C" fn bloom_create_mesh(vertex_ptr: *const f64, vertex_count: f64, index_ptr: *const f64, index_count: f64) -> f64 {
|
|
1970
|
+
// Perry's TS `number[]` is f64-laid-out in memory; Perry passes a
|
|
1971
|
+
// pointer to that data. A previous version of this FFI declared
|
|
1972
|
+
// *const f32 / *const u32, which silently read the low 4 bytes
|
|
1973
|
+
// of each f64 slot as garbage f32/u32 values — meshes registered
|
|
1974
|
+
// (non-zero handle) but were unrenderable.
|
|
1975
|
+
//
|
|
1976
|
+
// Caller must pass `vertex_count` and `index_count` derived from
|
|
1977
|
+
// a literal-initialized array OR from values it computed itself.
|
|
1978
|
+
// Don't compute these via `arr.length` after `.push()` — Perry's
|
|
1979
|
+
// `.length` property currently reflects the literal-init size,
|
|
1980
|
+
// not the post-push count (a Perry codegen bug). Workaround on
|
|
1981
|
+
// the TS side: track the count manually or use literal arrays.
|
|
1982
|
+
if vertex_ptr.is_null() || index_ptr.is_null() { return 0.0; }
|
|
1983
|
+
let vcount = vertex_count as usize;
|
|
1984
|
+
let icount = index_count as usize;
|
|
1985
|
+
let vertex_f64 = unsafe { std::slice::from_raw_parts(vertex_ptr, vcount * 12) };
|
|
1986
|
+
let index_f64 = unsafe { std::slice::from_raw_parts(index_ptr, icount) };
|
|
1987
|
+
let vertex_data: Vec<f32> = vertex_f64.iter().map(|&v| v as f32).collect();
|
|
1988
|
+
let index_data: Vec<u32> = index_f64.iter().map(|&v| v as u32).collect();
|
|
1989
|
+
engine().models.create_mesh(&vertex_data, &index_data)
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
#[no_mangle]
|
|
1993
|
+
pub extern "C" fn bloom_load_model_animation(path_ptr: *const u8) -> f64 {
|
|
1994
|
+
let path = str_from_header(path_ptr);
|
|
1995
|
+
match std::fs::read(path) {
|
|
1996
|
+
Ok(data) => engine().models.load_model_animation(&data),
|
|
1997
|
+
Err(_) => 0.0,
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
#[no_mangle]
|
|
2002
|
+
pub extern "C" fn bloom_update_model_animation(handle: f64, anim_index: f64, time: f64, scale: f64, px: f64, py: f64, pz: f64, rot_y: f64) {
|
|
2003
|
+
// Take a single Y-axis angle (radians) instead of sin/cos, so the
|
|
2004
|
+
// engine reconstructs both with full precision + correct signs.
|
|
2005
|
+
// Older callers that passed (rot_sin, rot_cos) hit a Perry-ARM64
|
|
2006
|
+
// 9th-arg garbling bug AND a sqrt(1-sin²) workaround that lost
|
|
2007
|
+
// the sign of cos — model rotation was correct only on half the
|
|
2008
|
+
// circle. 8-arg signature dodges both issues.
|
|
2009
|
+
let rot_y_f = rot_y as f32;
|
|
2010
|
+
let rot_sin = rot_y_f.sin();
|
|
2011
|
+
let rot_cos = rot_y_f.cos();
|
|
2012
|
+
let eng = engine();
|
|
2013
|
+
eng.models.update_model_animation(handle, anim_index as usize, time as f32);
|
|
2014
|
+
if let Some(anim) = eng.models.get_animation(handle) {
|
|
2015
|
+
if !anim.joint_matrices.is_empty() {
|
|
2016
|
+
eng.renderer.set_joint_matrices_scaled(&anim.joint_matrices, scale as f32, [px as f32, py as f32, pz as f32], rot_sin, rot_cos);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
#[no_mangle]
|
|
2022
|
+
pub extern "C" fn bloom_get_model_mesh_count(handle: f64) -> f64 {
|
|
2023
|
+
match engine().models.get(handle) {
|
|
2024
|
+
Some(model) => model.meshes.len() as f64,
|
|
2025
|
+
None => 0.0,
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
#[no_mangle]
|
|
2030
|
+
pub extern "C" fn bloom_get_model_material_count(handle: f64) -> f64 {
|
|
2031
|
+
match engine().models.get(handle) {
|
|
2032
|
+
Some(model) => model.meshes.len() as f64, // materials roughly equal meshes
|
|
2033
|
+
None => 0.0,
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// ============================================================
|
|
2038
|
+
// Audio
|
|
2039
|
+
// ============================================================
|
|
2040
|
+
|
|
2041
|
+
#[no_mangle]
|
|
2042
|
+
pub extern "C" fn bloom_init_audio() {
|
|
2043
|
+
unsafe {
|
|
2044
|
+
let desc = AudioComponentDescription {
|
|
2045
|
+
component_type: K_AUDIO_UNIT_TYPE_OUTPUT,
|
|
2046
|
+
component_sub_type: K_AUDIO_UNIT_SUB_TYPE_DEFAULT_OUTPUT,
|
|
2047
|
+
component_manufacturer: K_AUDIO_UNIT_MANUFACTURER_APPLE,
|
|
2048
|
+
component_flags: 0,
|
|
2049
|
+
component_flags_mask: 0,
|
|
2050
|
+
};
|
|
2051
|
+
|
|
2052
|
+
let component = AudioComponentFindNext(std::ptr::null_mut(), &desc);
|
|
2053
|
+
if component.is_null() {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
let mut unit: AudioUnit = std::ptr::null_mut();
|
|
2058
|
+
if AudioComponentInstanceNew(component, &mut unit) != 0 {
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// Set stream format: 44100 Hz, stereo, float32
|
|
2063
|
+
let stream_desc = AudioStreamBasicDescription {
|
|
2064
|
+
sample_rate: 44100.0,
|
|
2065
|
+
format_id: K_AUDIO_FORMAT_LINEAR_PCM,
|
|
2066
|
+
format_flags: K_AUDIO_FORMAT_FLAG_IS_FLOAT | K_AUDIO_FORMAT_FLAG_IS_PACKED,
|
|
2067
|
+
bytes_per_packet: 8,
|
|
2068
|
+
frames_per_packet: 1,
|
|
2069
|
+
bytes_per_frame: 8,
|
|
2070
|
+
channels_per_frame: 2,
|
|
2071
|
+
bits_per_channel: 32,
|
|
2072
|
+
reserved: 0,
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
AudioUnitSetProperty(
|
|
2076
|
+
unit,
|
|
2077
|
+
K_AUDIO_UNIT_PROPERTY_STREAM_FORMAT,
|
|
2078
|
+
K_AUDIO_UNIT_SCOPE_INPUT,
|
|
2079
|
+
0,
|
|
2080
|
+
&stream_desc as *const _ as *const std::ffi::c_void,
|
|
2081
|
+
std::mem::size_of::<AudioStreamBasicDescription>() as u32,
|
|
2082
|
+
);
|
|
2083
|
+
|
|
2084
|
+
// Set render callback
|
|
2085
|
+
let callback_struct = AURenderCallbackStruct {
|
|
2086
|
+
input_proc: audio_render_callback,
|
|
2087
|
+
input_proc_ref_con: std::ptr::null_mut(),
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
AudioUnitSetProperty(
|
|
2091
|
+
unit,
|
|
2092
|
+
K_AUDIO_UNIT_PROPERTY_SET_RENDER_CALLBACK,
|
|
2093
|
+
K_AUDIO_UNIT_SCOPE_INPUT,
|
|
2094
|
+
0,
|
|
2095
|
+
&callback_struct as *const _ as *const std::ffi::c_void,
|
|
2096
|
+
std::mem::size_of::<AURenderCallbackStruct>() as u32,
|
|
2097
|
+
);
|
|
2098
|
+
|
|
2099
|
+
AudioUnitInitialize(unit);
|
|
2100
|
+
AudioOutputUnitStart(unit);
|
|
2101
|
+
|
|
2102
|
+
AUDIO_UNIT = Some(AudioUnitInstance { unit });
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
#[no_mangle]
|
|
2107
|
+
pub extern "C" fn bloom_close_audio() {
|
|
2108
|
+
unsafe {
|
|
2109
|
+
if let Some(au) = AUDIO_UNIT.take() {
|
|
2110
|
+
AudioOutputUnitStop(au.unit);
|
|
2111
|
+
AudioComponentInstanceDispose(au.unit);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
#[no_mangle]
|
|
2117
|
+
pub extern "C" fn bloom_load_sound(path_ptr: *const u8) -> f64 {
|
|
2118
|
+
let path = str_from_header(path_ptr);
|
|
2119
|
+
match std::fs::read(path) {
|
|
2120
|
+
Ok(data) => {
|
|
2121
|
+
let sound_data = if path.ends_with(".ogg") || path.ends_with(".OGG") {
|
|
2122
|
+
parse_ogg(&data)
|
|
2123
|
+
} else if path.ends_with(".mp3") || path.ends_with(".MP3") {
|
|
2124
|
+
parse_mp3(&data)
|
|
2125
|
+
} else {
|
|
2126
|
+
parse_wav(&data)
|
|
2127
|
+
};
|
|
2128
|
+
if let Some(sound_data) = sound_data {
|
|
2129
|
+
engine().audio.load_sound(sound_data)
|
|
2130
|
+
} else {
|
|
2131
|
+
0.0
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
Err(_) => 0.0,
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
#[no_mangle]
|
|
2139
|
+
pub extern "C" fn bloom_play_sound(handle: f64) {
|
|
2140
|
+
engine().audio.play_sound(handle);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
#[no_mangle]
|
|
2144
|
+
pub extern "C" fn bloom_stop_sound(handle: f64) {
|
|
2145
|
+
engine().audio.stop_sound(handle);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
#[no_mangle]
|
|
2149
|
+
pub extern "C" fn bloom_set_sound_volume(handle: f64, volume: f64) {
|
|
2150
|
+
engine().audio.set_sound_volume(handle, volume as f32);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
#[no_mangle]
|
|
2154
|
+
pub extern "C" fn bloom_set_master_volume(volume: f64) {
|
|
2155
|
+
engine().audio.master_volume = volume as f32;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
#[no_mangle]
|
|
2159
|
+
pub extern "C" fn bloom_play_sound_3d(handle: f64, x: f64, y: f64, z: f64) {
|
|
2160
|
+
engine().audio.play_sound_3d(handle, x as f32, y as f32, z as f32);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
#[no_mangle]
|
|
2164
|
+
pub extern "C" fn bloom_set_listener_position(x: f64, y: f64, z: f64, fx: f64, fy: f64, fz: f64) {
|
|
2165
|
+
engine().audio.set_listener_position(x as f32, y as f32, z as f32, fx as f32, fy as f32, fz as f32);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// ============================================================
|
|
2169
|
+
// Music
|
|
2170
|
+
// ============================================================
|
|
2171
|
+
|
|
2172
|
+
#[no_mangle]
|
|
2173
|
+
pub extern "C" fn bloom_load_music(path_ptr: *const u8) -> f64 {
|
|
2174
|
+
let path = str_from_header(path_ptr);
|
|
2175
|
+
match std::fs::read(path) {
|
|
2176
|
+
Ok(data) => {
|
|
2177
|
+
let sound_data = if path.ends_with(".ogg") || path.ends_with(".OGG") {
|
|
2178
|
+
parse_ogg(&data)
|
|
2179
|
+
} else if path.ends_with(".mp3") || path.ends_with(".MP3") {
|
|
2180
|
+
parse_mp3(&data)
|
|
2181
|
+
} else {
|
|
2182
|
+
parse_wav(&data)
|
|
2183
|
+
};
|
|
2184
|
+
if let Some(sound_data) = sound_data {
|
|
2185
|
+
engine().audio.load_music(sound_data)
|
|
2186
|
+
} else {
|
|
2187
|
+
0.0
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
Err(_) => 0.0,
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
#[no_mangle]
|
|
2195
|
+
pub extern "C" fn bloom_play_music(handle: f64) {
|
|
2196
|
+
engine().audio.play_music(handle);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
#[no_mangle]
|
|
2200
|
+
pub extern "C" fn bloom_stop_music(handle: f64) {
|
|
2201
|
+
engine().audio.stop_music(handle);
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
#[no_mangle]
|
|
2205
|
+
pub extern "C" fn bloom_update_music_stream(handle: f64) {
|
|
2206
|
+
engine().audio.update_music_stream(handle);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
#[no_mangle]
|
|
2210
|
+
pub extern "C" fn bloom_set_music_volume(handle: f64, volume: f64) {
|
|
2211
|
+
engine().audio.set_music_volume(handle, volume as f32);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
#[no_mangle]
|
|
2215
|
+
pub extern "C" fn bloom_is_music_playing(handle: f64) -> f64 {
|
|
2216
|
+
if engine().audio.is_music_playing(handle) { 1.0 } else { 0.0 }
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// ============================================================
|
|
2220
|
+
// Utility
|
|
2221
|
+
// ============================================================
|
|
2222
|
+
|
|
2223
|
+
#[no_mangle]
|
|
2224
|
+
pub extern "C" fn bloom_toggle_fullscreen() {
|
|
2225
|
+
unsafe {
|
|
2226
|
+
if let Some(window) = &WINDOW {
|
|
2227
|
+
let _: () = msg_send![window, toggleFullScreen: std::ptr::null::<std::ffi::c_void>()];
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
#[no_mangle]
|
|
2233
|
+
pub extern "C" fn bloom_set_window_title(title_ptr: *const u8) {
|
|
2234
|
+
let title = str_from_header(title_ptr);
|
|
2235
|
+
unsafe {
|
|
2236
|
+
if let Some(window) = &WINDOW {
|
|
2237
|
+
let ns_title = NSString::from_str(title);
|
|
2238
|
+
window.setTitle(&ns_title);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
#[no_mangle]
|
|
2244
|
+
pub extern "C" fn bloom_set_window_icon(path_ptr: *const u8) {
|
|
2245
|
+
let path = str_from_header(path_ptr);
|
|
2246
|
+
unsafe {
|
|
2247
|
+
let ns_path = NSString::from_str(path);
|
|
2248
|
+
let image_cls = objc2::runtime::AnyClass::get(c"NSImage").unwrap();
|
|
2249
|
+
let image: *mut objc2::runtime::AnyObject =
|
|
2250
|
+
msg_send![image_cls, alloc];
|
|
2251
|
+
if image.is_null() { return; }
|
|
2252
|
+
let image: *mut objc2::runtime::AnyObject =
|
|
2253
|
+
msg_send![image, initWithContentsOfFile: &*ns_path];
|
|
2254
|
+
if image.is_null() { return; }
|
|
2255
|
+
let app = NSApplication::sharedApplication(MainThreadMarker::new_unchecked());
|
|
2256
|
+
let _: () = msg_send![&*app, setApplicationIconImage: image];
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
extern "C" {
|
|
2261
|
+
fn CGDisplayHideCursor(display: u32) -> i32;
|
|
2262
|
+
fn CGDisplayShowCursor(display: u32) -> i32;
|
|
2263
|
+
fn CGAssociateMouseAndMouseCursorPosition(connected: u8) -> i32;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
#[no_mangle]
|
|
2267
|
+
pub extern "C" fn bloom_disable_cursor() {
|
|
2268
|
+
let input = &mut engine().input;
|
|
2269
|
+
input.cursor_disabled = true;
|
|
2270
|
+
input.clear_mouse_delta();
|
|
2271
|
+
unsafe {
|
|
2272
|
+
CGDisplayHideCursor(0);
|
|
2273
|
+
CGAssociateMouseAndMouseCursorPosition(0); // dissociate = relative mode
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
#[no_mangle]
|
|
2278
|
+
pub extern "C" fn bloom_enable_cursor() {
|
|
2279
|
+
engine().input.cursor_disabled = false;
|
|
2280
|
+
unsafe {
|
|
2281
|
+
CGAssociateMouseAndMouseCursorPosition(1);
|
|
2282
|
+
CGDisplayShowCursor(0);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
#[no_mangle]
|
|
2287
|
+
pub extern "C" fn bloom_get_mouse_delta_x() -> f64 {
|
|
2288
|
+
engine().input.mouse_delta_x
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
#[no_mangle]
|
|
2292
|
+
pub extern "C" fn bloom_get_mouse_delta_y() -> f64 {
|
|
2293
|
+
engine().input.mouse_delta_y
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// Accumulated scroll wheel delta since the last call. Reading consumes the
|
|
2297
|
+
// value (returns 0 on the next call until the user scrolls again). Used by
|
|
2298
|
+
// the editor's orbit camera and any scrollable UI panel.
|
|
2299
|
+
#[no_mangle]
|
|
2300
|
+
pub extern "C" fn bloom_get_mouse_wheel() -> f64 {
|
|
2301
|
+
engine().input.consume_mouse_wheel()
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// Dequeue the next typed character (Unicode codepoint). Returns 0 when the
|
|
2305
|
+
// queue is empty. Call in a loop each frame to consume all pending characters.
|
|
2306
|
+
// Used by the editor's in-window text-input widget.
|
|
2307
|
+
#[no_mangle]
|
|
2308
|
+
pub extern "C" fn bloom_get_char_pressed() -> f64 {
|
|
2309
|
+
engine().input.pop_char() as f64
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// Q2: Cursor shape
|
|
2313
|
+
#[no_mangle]
|
|
2314
|
+
pub extern "C" fn bloom_set_cursor_shape(shape: f64) {
|
|
2315
|
+
engine().input.cursor_shape = shape as u32;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// E4: Clipboard
|
|
2319
|
+
#[no_mangle]
|
|
2320
|
+
pub extern "C" fn bloom_set_clipboard_text(text_ptr: *const u8) {
|
|
2321
|
+
let text = str_from_header(text_ptr);
|
|
2322
|
+
if let Ok(mut clipboard) = arboard::Clipboard::new() {
|
|
2323
|
+
let _ = clipboard.set_text(text.to_string());
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
#[no_mangle]
|
|
2328
|
+
pub extern "C" fn bloom_get_clipboard_text() -> *const u8 {
|
|
2329
|
+
match arboard::Clipboard::new() {
|
|
2330
|
+
Ok(mut clipboard) => {
|
|
2331
|
+
match clipboard.get_text() {
|
|
2332
|
+
Ok(text) => {
|
|
2333
|
+
let bytes = text.as_bytes();
|
|
2334
|
+
let len = bytes.len();
|
|
2335
|
+
let total = 12 + len;
|
|
2336
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
2337
|
+
unsafe {
|
|
2338
|
+
let ptr = std::alloc::alloc(layout);
|
|
2339
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
2340
|
+
*(ptr as *mut u32) = len as u32;
|
|
2341
|
+
*(ptr.add(4) as *mut u32) = len as u32;
|
|
2342
|
+
*(ptr.add(8) as *mut u32) = 1;
|
|
2343
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
2344
|
+
ptr
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
Err(_) => std::ptr::null(),
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
Err(_) => std::ptr::null(),
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
// E5b: Native file dialogs (via rfd crate)
|
|
2355
|
+
#[no_mangle]
|
|
2356
|
+
pub extern "C" fn bloom_open_file_dialog(filter_ptr: *const u8, title_ptr: *const u8) -> *const u8 {
|
|
2357
|
+
let filter = str_from_header(filter_ptr);
|
|
2358
|
+
let title = str_from_header(title_ptr);
|
|
2359
|
+
let mut dialog = rfd::FileDialog::new().set_title(title);
|
|
2360
|
+
if !filter.is_empty() {
|
|
2361
|
+
dialog = dialog.add_filter("Files", &[filter]);
|
|
2362
|
+
}
|
|
2363
|
+
match dialog.pick_file() {
|
|
2364
|
+
Some(path) => {
|
|
2365
|
+
let s = path.to_string_lossy();
|
|
2366
|
+
let bytes = s.as_bytes();
|
|
2367
|
+
let len = bytes.len();
|
|
2368
|
+
let total = 12 + len;
|
|
2369
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
2370
|
+
unsafe {
|
|
2371
|
+
let ptr = std::alloc::alloc(layout);
|
|
2372
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
2373
|
+
*(ptr as *mut u32) = len as u32;
|
|
2374
|
+
*(ptr.add(4) as *mut u32) = len as u32;
|
|
2375
|
+
*(ptr.add(8) as *mut u32) = 1;
|
|
2376
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
2377
|
+
ptr
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
None => std::ptr::null(),
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
#[no_mangle]
|
|
2385
|
+
pub extern "C" fn bloom_save_file_dialog(default_name_ptr: *const u8, title_ptr: *const u8) -> *const u8 {
|
|
2386
|
+
let default_name = str_from_header(default_name_ptr);
|
|
2387
|
+
let title = str_from_header(title_ptr);
|
|
2388
|
+
let dialog = rfd::FileDialog::new()
|
|
2389
|
+
.set_title(title)
|
|
2390
|
+
.set_file_name(default_name);
|
|
2391
|
+
match dialog.save_file() {
|
|
2392
|
+
Some(path) => {
|
|
2393
|
+
let s = path.to_string_lossy();
|
|
2394
|
+
let bytes = s.as_bytes();
|
|
2395
|
+
let len = bytes.len();
|
|
2396
|
+
let total = 12 + len;
|
|
2397
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
2398
|
+
unsafe {
|
|
2399
|
+
let ptr = std::alloc::alloc(layout);
|
|
2400
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
2401
|
+
*(ptr as *mut u32) = len as u32;
|
|
2402
|
+
*(ptr.add(4) as *mut u32) = len as u32;
|
|
2403
|
+
*(ptr.add(8) as *mut u32) = 1;
|
|
2404
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
2405
|
+
ptr
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
None => std::ptr::null(),
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// Model bounds accessors. Return the axis-aligned bounding box of a loaded
|
|
2413
|
+
// model in model-local coordinates. Editors use these to size gizmos, auto-
|
|
2414
|
+
// frame the camera on selection, and snap placed entities onto terrain.
|
|
2415
|
+
#[no_mangle]
|
|
2416
|
+
pub extern "C" fn bloom_get_model_bounds_min_x(model_handle: f64) -> f64 {
|
|
2417
|
+
engine().models.get_bounds(model_handle).0[0] as f64
|
|
2418
|
+
}
|
|
2419
|
+
#[no_mangle]
|
|
2420
|
+
pub extern "C" fn bloom_get_model_bounds_min_y(model_handle: f64) -> f64 {
|
|
2421
|
+
engine().models.get_bounds(model_handle).0[1] as f64
|
|
2422
|
+
}
|
|
2423
|
+
#[no_mangle]
|
|
2424
|
+
pub extern "C" fn bloom_get_model_bounds_min_z(model_handle: f64) -> f64 {
|
|
2425
|
+
engine().models.get_bounds(model_handle).0[2] as f64
|
|
2426
|
+
}
|
|
2427
|
+
#[no_mangle]
|
|
2428
|
+
pub extern "C" fn bloom_get_model_bounds_max_x(model_handle: f64) -> f64 {
|
|
2429
|
+
engine().models.get_bounds(model_handle).1[0] as f64
|
|
2430
|
+
}
|
|
2431
|
+
#[no_mangle]
|
|
2432
|
+
pub extern "C" fn bloom_get_model_bounds_max_y(model_handle: f64) -> f64 {
|
|
2433
|
+
engine().models.get_bounds(model_handle).1[1] as f64
|
|
2434
|
+
}
|
|
2435
|
+
#[no_mangle]
|
|
2436
|
+
pub extern "C" fn bloom_get_model_bounds_max_z(model_handle: f64) -> f64 {
|
|
2437
|
+
engine().models.get_bounds(model_handle).1[2] as f64
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
#[no_mangle]
|
|
2441
|
+
pub extern "C" fn bloom_write_file(path_ptr: *const u8, data_ptr: *const u8) -> f64 {
|
|
2442
|
+
let path = str_from_header(path_ptr);
|
|
2443
|
+
let data = str_from_header(data_ptr);
|
|
2444
|
+
match std::fs::write(path, data.as_bytes()) {
|
|
2445
|
+
Ok(_) => 1.0,
|
|
2446
|
+
Err(_) => 0.0,
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
#[no_mangle]
|
|
2451
|
+
pub extern "C" fn bloom_file_exists(path_ptr: *const u8) -> f64 {
|
|
2452
|
+
let path = str_from_header(path_ptr);
|
|
2453
|
+
if std::path::Path::new(path).exists() { 1.0 } else { 0.0 }
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
#[no_mangle]
|
|
2457
|
+
pub extern "C" fn bloom_read_file(path_ptr: *const u8) -> *const u8 {
|
|
2458
|
+
let path = str_from_header(path_ptr);
|
|
2459
|
+
match std::fs::read_to_string(path) {
|
|
2460
|
+
Ok(contents) => {
|
|
2461
|
+
// Return Perry-format string: StringHeader (length u32 + capacity u32 + refcount u32) followed by UTF-8 data
|
|
2462
|
+
let bytes = contents.as_bytes();
|
|
2463
|
+
let len = bytes.len();
|
|
2464
|
+
let total = 12 + len; // 12 bytes header (3 × u32) + data
|
|
2465
|
+
let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
|
|
2466
|
+
unsafe {
|
|
2467
|
+
let ptr = std::alloc::alloc(layout);
|
|
2468
|
+
if ptr.is_null() { return std::ptr::null(); }
|
|
2469
|
+
*(ptr as *mut u32) = len as u32; // length
|
|
2470
|
+
*(ptr.add(4) as *mut u32) = len as u32; // capacity
|
|
2471
|
+
*(ptr.add(8) as *mut u32) = 1; // refcount (unique)
|
|
2472
|
+
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
|
|
2473
|
+
ptr
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
Err(_) => std::ptr::null(),
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// ============================================================
|
|
2481
|
+
// Input injection + platform detection
|
|
2482
|
+
// ============================================================
|
|
2483
|
+
|
|
2484
|
+
#[no_mangle]
|
|
2485
|
+
pub extern "C" fn bloom_inject_key_down(key: f64) {
|
|
2486
|
+
engine().input.set_key_down(key as usize);
|
|
2487
|
+
}
|
|
2488
|
+
#[no_mangle]
|
|
2489
|
+
pub extern "C" fn bloom_inject_key_up(key: f64) {
|
|
2490
|
+
engine().input.set_key_up(key as usize);
|
|
2491
|
+
}
|
|
2492
|
+
#[no_mangle]
|
|
2493
|
+
pub extern "C" fn bloom_inject_gamepad_axis(axis: f64, value: f64) {
|
|
2494
|
+
engine().input.set_gamepad_axis(axis as usize, value as f32);
|
|
2495
|
+
}
|
|
2496
|
+
#[no_mangle]
|
|
2497
|
+
pub extern "C" fn bloom_inject_gamepad_button_down(button: f64) {
|
|
2498
|
+
engine().input.set_gamepad_button_down(button as usize);
|
|
2499
|
+
}
|
|
2500
|
+
#[no_mangle]
|
|
2501
|
+
pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
|
|
2502
|
+
engine().input.set_gamepad_button_up(button as usize);
|
|
2503
|
+
}
|
|
2504
|
+
#[no_mangle]
|
|
2505
|
+
pub extern "C" fn bloom_get_platform() -> f64 { 1.0 }
|
|
2506
|
+
#[no_mangle]
|
|
2507
|
+
pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
|
|
2508
|
+
if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
|
|
2509
|
+
}
|
|
2510
|
+
#[no_mangle]
|
|
2511
|
+
pub extern "C" fn bloom_get_crown_rotation() -> f64 {
|
|
2512
|
+
engine().input.consume_crown_rotation()
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// ============================================================
|
|
2516
|
+
// Frame callbacks
|
|
2517
|
+
// ============================================================
|
|
2518
|
+
|
|
2519
|
+
#[no_mangle]
|
|
2520
|
+
pub extern "C" fn bloom_register_frame_callback(priority: f64, callback: extern "C" fn(f64)) -> f64 {
|
|
2521
|
+
engine().frame_callbacks.register(priority as i32, callback) as f64
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
#[no_mangle]
|
|
2525
|
+
pub extern "C" fn bloom_unregister_frame_callback(id: f64) {
|
|
2526
|
+
engine().frame_callbacks.unregister(id as u64);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
/// Emscripten-style game loop. On native, this is a no-op — the game uses
|
|
2530
|
+
/// a while(!windowShouldClose()) loop in TypeScript instead. The TS-level
|
|
2531
|
+
/// runGame() helper handles the native loop. Only used on web where the
|
|
2532
|
+
/// JS glue intercepts this and drives requestAnimationFrame.
|
|
2533
|
+
#[no_mangle]
|
|
2534
|
+
pub extern "C" fn bloom_run_game(_callback: extern "C" fn(f64)) {
|
|
2535
|
+
// No-op on native. The TypeScript runGame() helper provides the while loop.
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// ============================================================
|
|
2539
|
+
// Multiple lights
|
|
2540
|
+
// ============================================================
|
|
2541
|
+
|
|
2542
|
+
#[no_mangle]
|
|
2543
|
+
pub extern "C" fn bloom_add_directional_light(
|
|
2544
|
+
dx: f64, dy: f64, dz: f64,
|
|
2545
|
+
r: f64, g: f64, b: f64,
|
|
2546
|
+
intensity: f64,
|
|
2547
|
+
) {
|
|
2548
|
+
engine().renderer.add_directional_light(
|
|
2549
|
+
dx as f32, dy as f32, dz as f32,
|
|
2550
|
+
r as f32, g as f32, b as f32,
|
|
2551
|
+
intensity as f32,
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
#[no_mangle]
|
|
2556
|
+
pub extern "C" fn bloom_add_point_light(
|
|
2557
|
+
x: f64, y: f64, z: f64, range: f64,
|
|
2558
|
+
r: f64, g: f64, b: f64,
|
|
2559
|
+
intensity: f64,
|
|
2560
|
+
) {
|
|
2561
|
+
engine().renderer.add_point_light(
|
|
2562
|
+
x as f32, y as f32, z as f32, range as f32,
|
|
2563
|
+
r as f32, g as f32, b as f32,
|
|
2564
|
+
intensity as f32,
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// ============================================================
|
|
2569
|
+
// Scene graph (retained mode)
|
|
2570
|
+
// ============================================================
|
|
2571
|
+
|
|
2572
|
+
#[no_mangle]
|
|
2573
|
+
pub extern "C" fn bloom_scene_create_node() -> f64 {
|
|
2574
|
+
engine().scene.create_node()
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
#[no_mangle]
|
|
2578
|
+
pub extern "C" fn bloom_scene_destroy_node(handle: f64) {
|
|
2579
|
+
engine().scene.destroy_node(handle);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
#[no_mangle]
|
|
2583
|
+
pub extern "C" fn bloom_scene_set_visible(handle: f64, visible: f64) {
|
|
2584
|
+
engine().scene.set_visible(handle, visible != 0.0);
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
#[no_mangle]
|
|
2588
|
+
pub extern "C" fn bloom_scene_set_cast_shadow(handle: f64, cast: f64) {
|
|
2589
|
+
engine().scene.set_cast_shadow(handle, cast != 0.0);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
#[no_mangle]
|
|
2593
|
+
pub extern "C" fn bloom_scene_set_receive_shadow(handle: f64, receive: f64) {
|
|
2594
|
+
engine().scene.set_receive_shadow(handle, receive != 0.0);
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
#[no_mangle]
|
|
2598
|
+
pub extern "C" fn bloom_scene_set_parent(handle: f64, parent: f64) {
|
|
2599
|
+
engine().scene.set_parent(handle, parent);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
/// Set 4x4 transform matrix (16 floats passed as pointer to f64 array).
|
|
2603
|
+
#[no_mangle]
|
|
2604
|
+
pub extern "C" fn bloom_scene_set_transform(handle: f64, mat_ptr: *const f64) {
|
|
2605
|
+
if mat_ptr.is_null() { return; }
|
|
2606
|
+
let slice = unsafe { std::slice::from_raw_parts(mat_ptr, 16) };
|
|
2607
|
+
let mut mat = [[0.0f32; 4]; 4];
|
|
2608
|
+
for col in 0..4 {
|
|
2609
|
+
for row in 0..4 {
|
|
2610
|
+
mat[col][row] = slice[col * 4 + row] as f32;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
engine().scene.set_transform(handle, mat);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/// Update geometry from flat vertex array (12 floats/vertex: xyz, nxnynz, rgba, uv)
|
|
2617
|
+
/// and index array.
|
|
2618
|
+
#[no_mangle]
|
|
2619
|
+
pub extern "C" fn bloom_scene_update_geometry(
|
|
2620
|
+
handle: f64,
|
|
2621
|
+
vert_ptr: *const f64,
|
|
2622
|
+
vert_count: f64,
|
|
2623
|
+
idx_ptr: *const f64,
|
|
2624
|
+
idx_count: f64,
|
|
2625
|
+
) {
|
|
2626
|
+
if vert_ptr.is_null() || idx_ptr.is_null() { return; }
|
|
2627
|
+
let nv = vert_count as usize;
|
|
2628
|
+
let ni = idx_count as usize;
|
|
2629
|
+
|
|
2630
|
+
let vert_floats = unsafe { std::slice::from_raw_parts(vert_ptr, nv * 12) };
|
|
2631
|
+
let idx_floats = unsafe { std::slice::from_raw_parts(idx_ptr, ni) };
|
|
2632
|
+
|
|
2633
|
+
let mut vertices = Vec::with_capacity(nv);
|
|
2634
|
+
for i in 0..nv {
|
|
2635
|
+
let base = i * 12;
|
|
2636
|
+
vertices.push(bloom_shared::renderer::Vertex3D {
|
|
2637
|
+
position: [vert_floats[base] as f32, vert_floats[base+1] as f32, vert_floats[base+2] as f32],
|
|
2638
|
+
normal: [vert_floats[base+3] as f32, vert_floats[base+4] as f32, vert_floats[base+5] as f32],
|
|
2639
|
+
color: [vert_floats[base+6] as f32, vert_floats[base+7] as f32, vert_floats[base+8] as f32, vert_floats[base+9] as f32],
|
|
2640
|
+
uv: [vert_floats[base+10] as f32, vert_floats[base+11] as f32],
|
|
2641
|
+
joints: [0.0; 4],
|
|
2642
|
+
weights: [0.0; 4],
|
|
2643
|
+
tangent: [0.0; 4],
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
let indices: Vec<u32> = idx_floats.iter().map(|&v| v as u32).collect();
|
|
2648
|
+
|
|
2649
|
+
engine().scene.update_geometry(handle, vertices, indices);
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
#[no_mangle]
|
|
2653
|
+
pub extern "C" fn bloom_scene_set_material_color(handle: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
2654
|
+
engine().scene.set_material_color(handle, r as f32, g as f32, b as f32, a as f32);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
#[no_mangle]
|
|
2658
|
+
pub extern "C" fn bloom_scene_set_material_pbr(handle: f64, roughness: f64, metalness: f64) {
|
|
2659
|
+
engine().scene.set_material_pbr(handle, roughness as f32, metalness as f32);
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
#[no_mangle]
|
|
2663
|
+
pub extern "C" fn bloom_scene_set_material_texture(handle: f64, texture_idx: f64) {
|
|
2664
|
+
engine().scene.set_material_texture(handle, texture_idx as u32);
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
#[no_mangle]
|
|
2668
|
+
pub extern "C" fn bloom_scene_node_count() -> f64 {
|
|
2669
|
+
engine().scene.node_count() as f64
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
/// Debug: get vertex count for a scene node (0 if not found or empty)
|
|
2673
|
+
#[no_mangle]
|
|
2674
|
+
pub extern "C" fn bloom_scene_node_vertex_count(handle: f64) -> f64 {
|
|
2675
|
+
match engine().scene.nodes.get(handle) {
|
|
2676
|
+
Some(node) => node.vertices.len() as f64,
|
|
2677
|
+
None => -1.0,
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
/// Debug: get index count for a scene node
|
|
2682
|
+
#[no_mangle]
|
|
2683
|
+
pub extern "C" fn bloom_scene_node_index_count(handle: f64) -> f64 {
|
|
2684
|
+
match engine().scene.nodes.get(handle) {
|
|
2685
|
+
Some(node) => node.indices.len() as f64,
|
|
2686
|
+
None => -1.0,
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
|
|
2691
|
+
// Q8: Set a water material on a scene node (translucent tint, low roughness).
|
|
2692
|
+
#[no_mangle]
|
|
2693
|
+
pub extern "C" fn bloom_scene_set_material_water(handle: f64, wave_amp: f64, wave_speed: f64, r: f64, g: f64, b: f64, a: f64) {
|
|
2694
|
+
engine().scene.set_material_water(handle, wave_amp as f32, wave_speed as f32, r as f32, g as f32, b as f32, a as f32);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// Q9: Generate a ribbon mesh along a Catmull-Rom spline.
|
|
2698
|
+
#[no_mangle]
|
|
2699
|
+
pub extern "C" fn bloom_gen_mesh_spline_ribbon(points_ptr: *const u8, point_count: f64, widths_ptr: *const u8, width_count: f64) -> f64 {
|
|
2700
|
+
let n = point_count as usize;
|
|
2701
|
+
let wn = width_count as usize;
|
|
2702
|
+
let points = unsafe { std::slice::from_raw_parts(points_ptr as *const f32, n * 3) };
|
|
2703
|
+
let widths = unsafe { std::slice::from_raw_parts(widths_ptr as *const f32, wn) };
|
|
2704
|
+
engine().models.gen_mesh_spline_ribbon(points, widths)
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// Q1: Render texture FFI — create GPU textures for render-to-texture.
|
|
2708
|
+
#[no_mangle]
|
|
2709
|
+
pub extern "C" fn bloom_load_render_texture(width: f64, height: f64) -> f64 {
|
|
2710
|
+
let w = width as u32;
|
|
2711
|
+
let h = height as u32;
|
|
2712
|
+
let eng = engine();
|
|
2713
|
+
let rt_handle = eng.textures.load_render_texture(w, h);
|
|
2714
|
+
|
|
2715
|
+
// Create the GPU texture via the renderer's public method.
|
|
2716
|
+
let (bind_group_idx, _tex_vec_idx) = eng.renderer.create_render_texture(w, h);
|
|
2717
|
+
|
|
2718
|
+
// Register as a texture handle so drawTexture can sample it.
|
|
2719
|
+
let tex_handle = eng.textures.textures.alloc(bloom_shared::textures::TextureData {
|
|
2720
|
+
bind_group_idx, width: w, height: h,
|
|
2721
|
+
});
|
|
2722
|
+
eng.textures.set_render_texture_handle(rt_handle, tex_handle);
|
|
2723
|
+
|
|
2724
|
+
rt_handle
|
|
2725
|
+
}
|
|
2726
|
+
#[no_mangle]
|
|
2727
|
+
pub extern "C" fn bloom_unload_render_texture(handle: f64) {
|
|
2728
|
+
engine().textures.unload_render_texture(handle);
|
|
2729
|
+
}
|
|
2730
|
+
#[no_mangle]
|
|
2731
|
+
pub extern "C" fn bloom_begin_texture_mode(handle: f64) {
|
|
2732
|
+
let eng = engine();
|
|
2733
|
+
let (w, h, bg_idx) = match eng.textures.render_textures.get(handle) {
|
|
2734
|
+
Some(rt) => {
|
|
2735
|
+
let tex_handle = rt.texture_handle;
|
|
2736
|
+
match eng.textures.textures.get(tex_handle) {
|
|
2737
|
+
Some(td) => (rt.width, rt.height, td.bind_group_idx as usize),
|
|
2738
|
+
None => return,
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
None => return,
|
|
2742
|
+
};
|
|
2743
|
+
if let Some(texture) = eng.renderer.get_texture_ref(bg_idx) {
|
|
2744
|
+
// We need to call begin_texture_mode with a reference to the texture,
|
|
2745
|
+
// but get_texture_ref borrows renderer immutably. Clone the texture view
|
|
2746
|
+
// data we need first, then call the mutable method.
|
|
2747
|
+
let color_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
2748
|
+
// Create depth texture for this RT.
|
|
2749
|
+
let depth_tex = eng.renderer.device.create_texture(&wgpu::TextureDescriptor {
|
|
2750
|
+
label: Some("rt_depth"), size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
|
2751
|
+
mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2,
|
|
2752
|
+
format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
2753
|
+
view_formats: &[],
|
|
2754
|
+
});
|
|
2755
|
+
let depth_view = depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
|
2756
|
+
eng.renderer.rt_color_view = Some(color_view);
|
|
2757
|
+
eng.renderer.rt_depth_view = Some(depth_view);
|
|
2758
|
+
eng.renderer.rt_depth_texture = Some(depth_tex);
|
|
2759
|
+
eng.renderer.rt_width = w;
|
|
2760
|
+
eng.renderer.rt_height = h;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
#[no_mangle]
|
|
2764
|
+
pub extern "C" fn bloom_end_texture_mode() {
|
|
2765
|
+
engine().renderer.end_texture_mode();
|
|
2766
|
+
}
|
|
2767
|
+
#[no_mangle]
|
|
2768
|
+
pub extern "C" fn bloom_get_render_texture_texture(handle: f64) -> f64 {
|
|
2769
|
+
engine().textures.get_render_texture_texture(handle)
|
|
2770
|
+
}
|
|
2771
|
+
// ============================================================
|
|
2772
|
+
// Scene graph QoL — Q4/Q5/Q6/Q7
|
|
2773
|
+
// ============================================================
|
|
2774
|
+
|
|
2775
|
+
/// Q4: Read back the 4x4 transform matrix of a scene node.
|
|
2776
|
+
/// Returns the column-major float at the specified index (0-15).
|
|
2777
|
+
#[no_mangle]
|
|
2778
|
+
pub extern "C" fn bloom_scene_get_transform(handle: f64, index: f64) -> f64 {
|
|
2779
|
+
let mat = engine().scene.get_transform(handle);
|
|
2780
|
+
let i = index as usize;
|
|
2781
|
+
let col = i / 4;
|
|
2782
|
+
let row = i % 4;
|
|
2783
|
+
if col < 4 && row < 4 { mat[col][row] as f64 } else { 0.0 }
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
/// Q5: Get the cached AABB min of a scene node.
|
|
2787
|
+
#[no_mangle]
|
|
2788
|
+
pub extern "C" fn bloom_scene_get_bounds_min_x(handle: f64) -> f64 { engine().scene.get_bounds(handle).0[0] as f64 }
|
|
2789
|
+
#[no_mangle]
|
|
2790
|
+
pub extern "C" fn bloom_scene_get_bounds_min_y(handle: f64) -> f64 { engine().scene.get_bounds(handle).0[1] as f64 }
|
|
2791
|
+
#[no_mangle]
|
|
2792
|
+
pub extern "C" fn bloom_scene_get_bounds_min_z(handle: f64) -> f64 { engine().scene.get_bounds(handle).0[2] as f64 }
|
|
2793
|
+
#[no_mangle]
|
|
2794
|
+
pub extern "C" fn bloom_scene_get_bounds_max_x(handle: f64) -> f64 { engine().scene.get_bounds(handle).1[0] as f64 }
|
|
2795
|
+
#[no_mangle]
|
|
2796
|
+
pub extern "C" fn bloom_scene_get_bounds_max_y(handle: f64) -> f64 { engine().scene.get_bounds(handle).1[1] as f64 }
|
|
2797
|
+
#[no_mangle]
|
|
2798
|
+
pub extern "C" fn bloom_scene_get_bounds_max_z(handle: f64) -> f64 { engine().scene.get_bounds(handle).1[2] as f64 }
|
|
2799
|
+
|
|
2800
|
+
/// Q7: Set arbitrary user data on a scene node.
|
|
2801
|
+
#[no_mangle]
|
|
2802
|
+
pub extern "C" fn bloom_scene_set_user_data(handle: f64, data: f64) {
|
|
2803
|
+
engine().scene.set_user_data(handle, data as i64);
|
|
2804
|
+
}
|
|
2805
|
+
#[no_mangle]
|
|
2806
|
+
pub extern "C" fn bloom_scene_get_user_data(handle: f64) -> f64 {
|
|
2807
|
+
engine().scene.get_user_data(handle) as f64
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
// ============================================================
|
|
2811
|
+
// Geometry generation
|
|
2812
|
+
// ============================================================
|
|
2813
|
+
|
|
2814
|
+
/// Extrude a 2D polygon along Y axis and store as a scene node's geometry.
|
|
2815
|
+
/// polygon_ptr: pointer to flat f64 array [x0, z0, x1, z1, ...]
|
|
2816
|
+
/// polygon_count: number of points (NOT number of floats)
|
|
2817
|
+
/// depth: extrusion height
|
|
2818
|
+
#[no_mangle]
|
|
2819
|
+
pub extern "C" fn bloom_scene_extrude_polygon(
|
|
2820
|
+
handle: f64,
|
|
2821
|
+
polygon_ptr: *const f64,
|
|
2822
|
+
polygon_count: f64,
|
|
2823
|
+
depth: f64,
|
|
2824
|
+
) {
|
|
2825
|
+
if polygon_ptr.is_null() { return; }
|
|
2826
|
+
let n = polygon_count as usize;
|
|
2827
|
+
let polygon = unsafe { std::slice::from_raw_parts(polygon_ptr, n * 2) };
|
|
2828
|
+
|
|
2829
|
+
let geo = bloom_shared::geometry::extrude_polygon(polygon, &[], depth);
|
|
2830
|
+
engine().scene.update_geometry(handle, geo.vertices, geo.indices);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
/// Subtract an axis-aligned box from a scene node's geometry.
|
|
2834
|
+
/// Removes triangles fully inside the box (simplified CSG).
|
|
2835
|
+
#[no_mangle]
|
|
2836
|
+
pub extern "C" fn bloom_scene_subtract_box(
|
|
2837
|
+
handle: f64,
|
|
2838
|
+
min_x: f64, min_y: f64, min_z: f64,
|
|
2839
|
+
max_x: f64, max_y: f64, max_z: f64,
|
|
2840
|
+
) {
|
|
2841
|
+
let eng = engine();
|
|
2842
|
+
if let Some(node) = eng.scene.nodes.get(handle) {
|
|
2843
|
+
let current = bloom_shared::geometry::GeometryData {
|
|
2844
|
+
vertices: node.vertices.clone(),
|
|
2845
|
+
indices: node.indices.clone(),
|
|
2846
|
+
};
|
|
2847
|
+
let result = bloom_shared::geometry::subtract_box(
|
|
2848
|
+
¤t,
|
|
2849
|
+
[min_x as f32, min_y as f32, min_z as f32],
|
|
2850
|
+
[max_x as f32, max_y as f32, max_z as f32],
|
|
2851
|
+
);
|
|
2852
|
+
eng.scene.update_geometry(handle, result.vertices, result.indices);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// ============================================================
|
|
2857
|
+
// Shadow mapping
|
|
2858
|
+
// ============================================================
|
|
2859
|
+
|
|
2860
|
+
#[no_mangle]
|
|
2861
|
+
pub extern "C" fn bloom_enable_shadows() {
|
|
2862
|
+
engine().renderer.shadow_map.enable();
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
#[no_mangle]
|
|
2866
|
+
pub extern "C" fn bloom_disable_shadows() {
|
|
2867
|
+
engine().renderer.shadow_map.disable();
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
#[no_mangle]
|
|
2871
|
+
pub extern "C" fn bloom_dump_shadow_map(path_ptr: *const u8) {
|
|
2872
|
+
let path = str_from_header(path_ptr).to_string();
|
|
2873
|
+
engine().renderer.dump_shadow_map(&path);
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// ============================================================
|
|
2877
|
+
// Post-processing
|
|
2878
|
+
// ============================================================
|
|
2879
|
+
|
|
2880
|
+
#[no_mangle]
|
|
2881
|
+
pub extern "C" fn bloom_enable_postfx() {
|
|
2882
|
+
let eng = engine();
|
|
2883
|
+
let w = eng.renderer.width();
|
|
2884
|
+
let h = eng.renderer.height();
|
|
2885
|
+
let fmt = eng.renderer.surface_format();
|
|
2886
|
+
eng.postfx = Some(bloom_shared::postfx::PostFxPipeline::new(
|
|
2887
|
+
&eng.renderer.device, w, h, fmt,
|
|
2888
|
+
));
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
#[no_mangle]
|
|
2892
|
+
pub extern "C" fn bloom_disable_postfx() {
|
|
2893
|
+
engine().postfx = None;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
#[no_mangle]
|
|
2897
|
+
pub extern "C" fn bloom_postfx_set_selected(handle: f64) {
|
|
2898
|
+
if let Some(pfx) = &mut engine().postfx {
|
|
2899
|
+
if handle == 0.0 {
|
|
2900
|
+
pfx.set_selected(Vec::new());
|
|
2901
|
+
} else {
|
|
2902
|
+
pfx.set_selected(vec![handle]);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
#[no_mangle]
|
|
2908
|
+
pub extern "C" fn bloom_postfx_set_hovered(handle: f64) {
|
|
2909
|
+
if let Some(pfx) = &mut engine().postfx {
|
|
2910
|
+
pfx.set_hovered(handle);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
#[no_mangle]
|
|
2915
|
+
pub extern "C" fn bloom_postfx_set_outline_color(r: f64, g: f64, b: f64, a: f64) {
|
|
2916
|
+
if let Some(pfx) = &mut engine().postfx {
|
|
2917
|
+
pfx.outline_params.color_selected = [r as f32, g as f32, b as f32, a as f32];
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
#[no_mangle]
|
|
2922
|
+
pub extern "C" fn bloom_postfx_set_outline_thickness(thickness: f64) {
|
|
2923
|
+
if let Some(pfx) = &mut engine().postfx {
|
|
2924
|
+
pfx.outline_params.thickness[0] = thickness as f32;
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
// ============================================================
|
|
2929
|
+
// 3D→2D Projection (for UI overlays positioned in 3D space)
|
|
2930
|
+
// ============================================================
|
|
2931
|
+
|
|
2932
|
+
/// Project a world-space 3D point to screen coordinates.
|
|
2933
|
+
/// Returns screen X. Call bloom_project_y for Y. Returns -9999 if behind camera.
|
|
2934
|
+
static mut LAST_PROJECT: (f64, f64) = (0.0, 0.0);
|
|
2935
|
+
|
|
2936
|
+
#[no_mangle]
|
|
2937
|
+
pub extern "C" fn bloom_project_to_screen(wx: f64, wy: f64, wz: f64) -> f64 {
|
|
2938
|
+
let eng = engine();
|
|
2939
|
+
let vp = eng.renderer.vp_matrix();
|
|
2940
|
+
let w = eng.renderer.width() as f32;
|
|
2941
|
+
let h = eng.renderer.height() as f32;
|
|
2942
|
+
|
|
2943
|
+
// Multiply by VP matrix
|
|
2944
|
+
let x = wx as f32;
|
|
2945
|
+
let y = wy as f32;
|
|
2946
|
+
let z = wz as f32;
|
|
2947
|
+
let clip_x = vp[0][0]*x + vp[1][0]*y + vp[2][0]*z + vp[3][0];
|
|
2948
|
+
let clip_y = vp[0][1]*x + vp[1][1]*y + vp[2][1]*z + vp[3][1];
|
|
2949
|
+
let clip_w = vp[0][3]*x + vp[1][3]*y + vp[2][3]*z + vp[3][3];
|
|
2950
|
+
|
|
2951
|
+
if clip_w <= 0.0 {
|
|
2952
|
+
unsafe { LAST_PROJECT = (-9999.0, -9999.0); }
|
|
2953
|
+
return -9999.0;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// NDC to screen
|
|
2957
|
+
let ndc_x = clip_x / clip_w;
|
|
2958
|
+
let ndc_y = clip_y / clip_w;
|
|
2959
|
+
let screen_x = ((ndc_x + 1.0) * 0.5 * w) as f64;
|
|
2960
|
+
let screen_y = ((1.0 - ndc_y) * 0.5 * h) as f64;
|
|
2961
|
+
|
|
2962
|
+
unsafe { LAST_PROJECT = (screen_x, screen_y); }
|
|
2963
|
+
screen_x
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
#[no_mangle]
|
|
2967
|
+
pub extern "C" fn bloom_project_screen_y() -> f64 {
|
|
2968
|
+
unsafe { LAST_PROJECT.1 }
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/// Attach a loaded GLTF model's mesh geometry to a scene node.
|
|
2972
|
+
/// Copies the vertex/index data from the model into the scene node.
|
|
2973
|
+
#[no_mangle]
|
|
2974
|
+
pub extern "C" fn bloom_scene_attach_model(node_handle: f64, model_handle: f64, mesh_index: f64) {
|
|
2975
|
+
let eng = engine();
|
|
2976
|
+
let mi = mesh_index as usize;
|
|
2977
|
+
|
|
2978
|
+
// Get model mesh data
|
|
2979
|
+
let model_data = match eng.models.models.get(model_handle) {
|
|
2980
|
+
Some(md) => md,
|
|
2981
|
+
None => return,
|
|
2982
|
+
};
|
|
2983
|
+
if mi >= model_data.meshes.len() { return; }
|
|
2984
|
+
let mesh = &model_data.meshes[mi];
|
|
2985
|
+
|
|
2986
|
+
// Copy vertices and indices to scene node
|
|
2987
|
+
let vertices = mesh.vertices.clone();
|
|
2988
|
+
let indices = mesh.indices.clone();
|
|
2989
|
+
let base_color_tex = mesh.texture_idx;
|
|
2990
|
+
let normal_tex = mesh.normal_texture_idx;
|
|
2991
|
+
let mr_tex = mesh.metallic_roughness_texture_idx;
|
|
2992
|
+
let emissive_tex = mesh.emissive_texture_idx;
|
|
2993
|
+
let emissive_factor = mesh.emissive_factor;
|
|
2994
|
+
eng.scene.update_geometry(node_handle, vertices, indices);
|
|
2995
|
+
|
|
2996
|
+
// Pipe PBR textures through to the scene node material so the
|
|
2997
|
+
// renderer's scene pipeline can sample them.
|
|
2998
|
+
if let Some(tex_idx) = base_color_tex {
|
|
2999
|
+
eng.scene.set_material_texture(node_handle, tex_idx);
|
|
3000
|
+
}
|
|
3001
|
+
if let Some(tex_idx) = normal_tex {
|
|
3002
|
+
eng.scene.set_material_normal_texture(node_handle, tex_idx);
|
|
3003
|
+
}
|
|
3004
|
+
if let Some(tex_idx) = mr_tex {
|
|
3005
|
+
eng.scene.set_material_metallic_roughness_texture(node_handle, tex_idx);
|
|
3006
|
+
}
|
|
3007
|
+
if let Some(tex_idx) = emissive_tex {
|
|
3008
|
+
eng.scene.set_material_emissive_texture(node_handle, tex_idx);
|
|
3009
|
+
}
|
|
3010
|
+
eng.scene.set_material_emissive_factor(
|
|
3011
|
+
node_handle,
|
|
3012
|
+
emissive_factor[0],
|
|
3013
|
+
emissive_factor[1],
|
|
3014
|
+
emissive_factor[2],
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// ============================================================
|
|
3019
|
+
// Thread-safe staging (for async asset loading via Perry threads)
|
|
3020
|
+
// ============================================================
|
|
3021
|
+
|
|
3022
|
+
#[no_mangle]
|
|
3023
|
+
pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
|
|
3024
|
+
let path = str_from_header(path_ptr);
|
|
3025
|
+
match std::fs::read(path) {
|
|
3026
|
+
Ok(data) => bloom_shared::staging::decode_and_stage_texture(&data),
|
|
3027
|
+
Err(_) => 0.0,
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
#[no_mangle]
|
|
3032
|
+
pub extern "C" fn bloom_stage_model(path_ptr: *const u8) -> f64 {
|
|
3033
|
+
let path = str_from_header(path_ptr);
|
|
3034
|
+
let data = match std::fs::read(path) {
|
|
3035
|
+
Ok(d) => d,
|
|
3036
|
+
Err(_) => return 0.0,
|
|
3037
|
+
};
|
|
3038
|
+
match bloom_shared::models::load_gltf_staged(&data) {
|
|
3039
|
+
Some(staged) => bloom_shared::staging::stage_model(staged),
|
|
3040
|
+
None => 0.0,
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
#[no_mangle]
|
|
3045
|
+
pub extern "C" fn bloom_stage_sound(path_ptr: *const u8) -> f64 {
|
|
3046
|
+
let path = str_from_header(path_ptr);
|
|
3047
|
+
let data = match std::fs::read(path) {
|
|
3048
|
+
Ok(d) => d,
|
|
3049
|
+
Err(_) => return 0.0,
|
|
3050
|
+
};
|
|
3051
|
+
let sound_data = if path.ends_with(".ogg") || path.ends_with(".OGG") {
|
|
3052
|
+
parse_ogg(&data)
|
|
3053
|
+
} else if path.ends_with(".mp3") || path.ends_with(".MP3") {
|
|
3054
|
+
parse_mp3(&data)
|
|
3055
|
+
} else {
|
|
3056
|
+
parse_wav(&data)
|
|
3057
|
+
};
|
|
3058
|
+
match sound_data {
|
|
3059
|
+
Some(sd) => bloom_shared::staging::stage_sound(sd),
|
|
3060
|
+
None => 0.0,
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
#[no_mangle]
|
|
3065
|
+
pub extern "C" fn bloom_commit_texture(staging_handle: f64) -> f64 {
|
|
3066
|
+
let staged = match bloom_shared::staging::take_texture(staging_handle) {
|
|
3067
|
+
Some(s) => s,
|
|
3068
|
+
None => return 0.0,
|
|
3069
|
+
};
|
|
3070
|
+
let eng = engine();
|
|
3071
|
+
let bind_group_idx = eng.renderer.register_texture(staged.width, staged.height, &staged.data);
|
|
3072
|
+
eng.textures.textures.alloc(bloom_shared::textures::TextureData {
|
|
3073
|
+
bind_group_idx, width: staged.width, height: staged.height,
|
|
3074
|
+
})
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
#[no_mangle]
|
|
3078
|
+
pub extern "C" fn bloom_commit_model(staging_handle: f64) -> f64 {
|
|
3079
|
+
let staged = match bloom_shared::staging::take_model(staging_handle) {
|
|
3080
|
+
Some(s) => s,
|
|
3081
|
+
None => return 0.0,
|
|
3082
|
+
};
|
|
3083
|
+
let eng = engine();
|
|
3084
|
+
// Register staged textures with GPU and build index map.
|
|
3085
|
+
// Staged texture_idx values are 1-based into staged.textures.
|
|
3086
|
+
// We map them to actual renderer bind_group_idx values.
|
|
3087
|
+
let mut tex_map: Vec<u32> = Vec::with_capacity(staged.textures.len());
|
|
3088
|
+
for tex in &staged.textures {
|
|
3089
|
+
tex_map.push(eng.renderer.register_texture(tex.width, tex.height, &tex.data));
|
|
3090
|
+
}
|
|
3091
|
+
let mut model = staged.model;
|
|
3092
|
+
for mesh in &mut model.meshes {
|
|
3093
|
+
if let Some(ref mut idx) = mesh.texture_idx {
|
|
3094
|
+
let staged_idx = *idx as usize;
|
|
3095
|
+
if staged_idx > 0 && staged_idx <= tex_map.len() {
|
|
3096
|
+
*idx = tex_map[staged_idx - 1];
|
|
3097
|
+
} else {
|
|
3098
|
+
mesh.texture_idx = None;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
eng.models.models.alloc(model)
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
#[no_mangle]
|
|
3106
|
+
pub extern "C" fn bloom_commit_sound(staging_handle: f64) -> f64 {
|
|
3107
|
+
match bloom_shared::staging::take_sound(staging_handle) {
|
|
3108
|
+
Some(sd) => engine().audio.load_sound(sd),
|
|
3109
|
+
None => 0.0,
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
#[no_mangle]
|
|
3114
|
+
pub extern "C" fn bloom_commit_music(staging_handle: f64) -> f64 {
|
|
3115
|
+
match bloom_shared::staging::take_sound(staging_handle) {
|
|
3116
|
+
Some(sd) => engine().audio.load_music(sd),
|
|
3117
|
+
None => 0.0,
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// ============================================================
|
|
3122
|
+
// Simple blocking executor for wgpu async calls
|
|
3123
|
+
// ============================================================
|
|
3124
|
+
|
|
3125
|
+
fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
|
|
3126
|
+
// Minimal block_on implementation using std::task
|
|
3127
|
+
use std::task::{Context, Poll, Wake, Waker};
|
|
3128
|
+
use std::pin::Pin;
|
|
3129
|
+
use std::sync::Arc;
|
|
3130
|
+
|
|
3131
|
+
struct NoopWaker;
|
|
3132
|
+
impl Wake for NoopWaker {
|
|
3133
|
+
fn wake(self: Arc<Self>) {}
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
let waker = Waker::from(Arc::new(NoopWaker));
|
|
3137
|
+
let mut cx = Context::from_waker(&waker);
|
|
3138
|
+
let mut future = unsafe { Pin::new_unchecked(Box::new(future)) };
|
|
3139
|
+
|
|
3140
|
+
loop {
|
|
3141
|
+
match future.as_mut().poll(&mut cx) {
|
|
3142
|
+
Poll::Ready(result) => return result,
|
|
3143
|
+
Poll::Pending => std::thread::yield_now(),
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// ============================================================
|
|
3149
|
+
// Geisterhand screenshot integration
|
|
3150
|
+
// ============================================================
|
|
3151
|
+
|
|
3152
|
+
/// Register Bloom's GPU-based screenshot capture with perry-geisterhand.
|
|
3153
|
+
/// This replaces perry-ui-macos's CGWindowListCreateImage approach with
|
|
3154
|
+
/// direct wgpu texture readback — works for Metal/Vulkan rendered content.
|
|
3155
|
+
fn bloom_register_geisterhand_screenshot() {
|
|
3156
|
+
// Try to register with geisterhand if it's linked (weak symbol)
|
|
3157
|
+
extern "C" {
|
|
3158
|
+
fn perry_geisterhand_register_screenshot_capture(
|
|
3159
|
+
f: extern "C" fn(*mut usize) -> *mut u8,
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
unsafe {
|
|
3163
|
+
perry_geisterhand_register_screenshot_capture(bloom_screenshot_capture);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/// Capture the Bloom framebuffer as PNG.
|
|
3168
|
+
/// Called from geisterhand pump BEFORE end_frame in bloom_end_drawing.
|
|
3169
|
+
/// The vertices_3d/2d and VP matrix from the game loop are still populated.
|
|
3170
|
+
/// We render to a fresh surface texture with screenshot capture, producing
|
|
3171
|
+
/// the same visual output as the real frame.
|
|
3172
|
+
extern "C" fn bloom_screenshot_capture(out_len: *mut usize) -> *mut u8 {
|
|
3173
|
+
let eng = engine();
|
|
3174
|
+
|
|
3175
|
+
// Set capture flag and render inline
|
|
3176
|
+
eng.renderer.screenshot_requested = true;
|
|
3177
|
+
eng.scene.prepare(
|
|
3178
|
+
&eng.renderer.device,
|
|
3179
|
+
&eng.renderer.queue,
|
|
3180
|
+
&eng.renderer.vp_matrix(),
|
|
3181
|
+
&eng.renderer.prev_vp_matrix,
|
|
3182
|
+
eng.renderer.uniform_3d_layout(),
|
|
3183
|
+
);
|
|
3184
|
+
eng.scene.prepare_materials(&eng.renderer);
|
|
3185
|
+
// Phase 1c: sync material PerFrame + PerView UBOs with the
|
|
3186
|
+
// current engine clock before the main HDR pass dispatches any
|
|
3187
|
+
// material draws that were submitted during this frame.
|
|
3188
|
+
{
|
|
3189
|
+
let t = eng.get_time() as f32;
|
|
3190
|
+
let dt = eng.delta_time as f32;
|
|
3191
|
+
eng.renderer.material_system_begin_frame(t, dt);
|
|
3192
|
+
}
|
|
3193
|
+
eng.renderer.end_frame_with_scene(&mut eng.scene, &mut eng.profiler);
|
|
3194
|
+
|
|
3195
|
+
match eng.renderer.screenshot_data.take() {
|
|
3196
|
+
Some((width, height, rgba)) => {
|
|
3197
|
+
// Encode RGBA pixels to PNG
|
|
3198
|
+
match encode_png(width, height, &rgba) {
|
|
3199
|
+
Some(png_data) => {
|
|
3200
|
+
let len = png_data.len();
|
|
3201
|
+
// Allocate with libc::malloc (caller will free with libc::free)
|
|
3202
|
+
let ptr = unsafe { libc::malloc(len) as *mut u8 };
|
|
3203
|
+
if ptr.is_null() {
|
|
3204
|
+
unsafe { *out_len = 0; }
|
|
3205
|
+
return std::ptr::null_mut();
|
|
3206
|
+
}
|
|
3207
|
+
unsafe {
|
|
3208
|
+
std::ptr::copy_nonoverlapping(png_data.as_ptr(), ptr, len);
|
|
3209
|
+
*out_len = len;
|
|
3210
|
+
}
|
|
3211
|
+
ptr
|
|
3212
|
+
}
|
|
3213
|
+
None => {
|
|
3214
|
+
unsafe { *out_len = 0; }
|
|
3215
|
+
std::ptr::null_mut()
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
None => {
|
|
3220
|
+
unsafe { *out_len = 0; }
|
|
3221
|
+
std::ptr::null_mut()
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
/// Minimal PNG encoder (no external dependency).
|
|
3227
|
+
fn encode_png(width: u32, height: u32, rgba: &[u8]) -> Option<Vec<u8>> {
|
|
3228
|
+
use std::io::Write;
|
|
3229
|
+
|
|
3230
|
+
let mut png = Vec::new();
|
|
3231
|
+
// PNG signature
|
|
3232
|
+
png.write_all(&[137, 80, 78, 71, 13, 10, 26, 10]).ok()?;
|
|
3233
|
+
|
|
3234
|
+
// IHDR chunk
|
|
3235
|
+
let mut ihdr = Vec::new();
|
|
3236
|
+
ihdr.extend_from_slice(&width.to_be_bytes());
|
|
3237
|
+
ihdr.extend_from_slice(&height.to_be_bytes());
|
|
3238
|
+
ihdr.push(8); // bit depth
|
|
3239
|
+
ihdr.push(6); // color type: RGBA
|
|
3240
|
+
ihdr.push(0); // compression
|
|
3241
|
+
ihdr.push(0); // filter
|
|
3242
|
+
ihdr.push(0); // interlace
|
|
3243
|
+
write_png_chunk(&mut png, b"IHDR", &ihdr);
|
|
3244
|
+
|
|
3245
|
+
// IDAT chunk — raw pixel data with zlib
|
|
3246
|
+
// Build raw scanlines: each row starts with filter byte 0 (None)
|
|
3247
|
+
let row_bytes = (width * 4) as usize;
|
|
3248
|
+
let mut raw = Vec::with_capacity((row_bytes + 1) * height as usize);
|
|
3249
|
+
for y in 0..height as usize {
|
|
3250
|
+
raw.push(0); // filter: None
|
|
3251
|
+
let start = y * row_bytes;
|
|
3252
|
+
// Copy BGRA pixels, swapping B and R for PNG (which expects RGBA)
|
|
3253
|
+
for x in 0..width as usize {
|
|
3254
|
+
let idx = start + x * 4;
|
|
3255
|
+
// Metal Bgra8UnormSrgb: byte order is B, G, R, A
|
|
3256
|
+
raw.push(rgba[idx + 2]); // R (was at offset 2 in BGRA)
|
|
3257
|
+
raw.push(rgba[idx + 1]); // G (same position)
|
|
3258
|
+
raw.push(rgba[idx + 0]); // B (was at offset 0 in BGRA)
|
|
3259
|
+
raw.push(255); // A (force opaque — alpha from sRGB surface is unreliable)
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
// Compress with deflate (store blocks, no actual compression for simplicity)
|
|
3264
|
+
let deflated = deflate_store(&raw);
|
|
3265
|
+
write_png_chunk(&mut png, b"IDAT", &deflated);
|
|
3266
|
+
|
|
3267
|
+
// IEND chunk
|
|
3268
|
+
write_png_chunk(&mut png, b"IEND", &[]);
|
|
3269
|
+
|
|
3270
|
+
Some(png)
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
fn write_png_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) {
|
|
3274
|
+
let len = data.len() as u32;
|
|
3275
|
+
out.extend_from_slice(&len.to_be_bytes());
|
|
3276
|
+
out.extend_from_slice(chunk_type);
|
|
3277
|
+
out.extend_from_slice(data);
|
|
3278
|
+
// CRC32 over type + data
|
|
3279
|
+
let crc = crc32(&[chunk_type.as_slice(), data].concat());
|
|
3280
|
+
out.extend_from_slice(&crc.to_be_bytes());
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
fn crc32(data: &[u8]) -> u32 {
|
|
3284
|
+
let mut crc: u32 = 0xFFFFFFFF;
|
|
3285
|
+
for &byte in data {
|
|
3286
|
+
crc ^= byte as u32;
|
|
3287
|
+
for _ in 0..8 {
|
|
3288
|
+
if crc & 1 != 0 {
|
|
3289
|
+
crc = (crc >> 1) ^ 0xEDB88320;
|
|
3290
|
+
} else {
|
|
3291
|
+
crc >>= 1;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
!crc
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
/// Minimal deflate: store blocks (no compression). Wraps in zlib format.
|
|
3299
|
+
fn deflate_store(data: &[u8]) -> Vec<u8> {
|
|
3300
|
+
let mut out = Vec::new();
|
|
3301
|
+
// Zlib header: CMF=0x78 (deflate, window=32K), FLG=0x01 (no dict, check bits)
|
|
3302
|
+
out.push(0x78);
|
|
3303
|
+
out.push(0x01);
|
|
3304
|
+
|
|
3305
|
+
// Split into 65535-byte store blocks
|
|
3306
|
+
let mut remaining = data.len();
|
|
3307
|
+
let mut offset = 0;
|
|
3308
|
+
while remaining > 0 {
|
|
3309
|
+
let block_size = remaining.min(65535);
|
|
3310
|
+
let is_last = remaining <= 65535;
|
|
3311
|
+
out.push(if is_last { 1 } else { 0 }); // BFINAL + BTYPE=00 (store)
|
|
3312
|
+
let len = block_size as u16;
|
|
3313
|
+
let nlen = !len;
|
|
3314
|
+
out.extend_from_slice(&len.to_le_bytes());
|
|
3315
|
+
out.extend_from_slice(&nlen.to_le_bytes());
|
|
3316
|
+
out.extend_from_slice(&data[offset..offset + block_size]);
|
|
3317
|
+
offset += block_size;
|
|
3318
|
+
remaining -= block_size;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
// Adler-32 checksum
|
|
3322
|
+
let adler = adler32(data);
|
|
3323
|
+
out.extend_from_slice(&adler.to_be_bytes());
|
|
3324
|
+
out
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
fn adler32(data: &[u8]) -> u32 {
|
|
3328
|
+
let mut a: u32 = 1;
|
|
3329
|
+
let mut b: u32 = 0;
|
|
3330
|
+
for &byte in data {
|
|
3331
|
+
a = (a + byte as u32) % 65521;
|
|
3332
|
+
b = (b + a) % 65521;
|
|
3333
|
+
}
|
|
3334
|
+
(b << 16) | a
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// ============================================================
|
|
3338
|
+
// Scene picking (raycasting)
|
|
3339
|
+
// ============================================================
|
|
3340
|
+
|
|
3341
|
+
static mut LAST_PICK: Option<bloom_shared::picking::PickResult> = None;
|
|
3342
|
+
|
|
3343
|
+
#[no_mangle]
|
|
3344
|
+
pub extern "C" fn bloom_scene_pick(screen_x: f64, screen_y: f64) -> f64 {
|
|
3345
|
+
let eng = engine();
|
|
3346
|
+
let inv_vp = eng.renderer.inverse_vp_matrix();
|
|
3347
|
+
let cam_pos = eng.renderer.camera_pos();
|
|
3348
|
+
let w = eng.renderer.width() as f32;
|
|
3349
|
+
let h = eng.renderer.height() as f32;
|
|
3350
|
+
|
|
3351
|
+
let (origin, direction) = bloom_shared::picking::screen_to_ray(
|
|
3352
|
+
screen_x as f32, screen_y as f32,
|
|
3353
|
+
w, h, &inv_vp, &cam_pos,
|
|
3354
|
+
);
|
|
3355
|
+
|
|
3356
|
+
let result = bloom_shared::picking::raycast_scene(&eng.scene, &origin, &direction);
|
|
3357
|
+
let hit = result.hit;
|
|
3358
|
+
unsafe { LAST_PICK = Some(result); }
|
|
3359
|
+
if hit { 1.0 } else { 0.0 }
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
#[no_mangle]
|
|
3363
|
+
pub extern "C" fn bloom_pick_hit_handle() -> f64 {
|
|
3364
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.handle).unwrap_or(0.0) }
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
#[no_mangle]
|
|
3368
|
+
pub extern "C" fn bloom_pick_hit_distance() -> f64 {
|
|
3369
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.distance as f64).unwrap_or(0.0) }
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
#[no_mangle]
|
|
3373
|
+
pub extern "C" fn bloom_pick_hit_x() -> f64 {
|
|
3374
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.point[0] as f64).unwrap_or(0.0) }
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
#[no_mangle]
|
|
3378
|
+
pub extern "C" fn bloom_pick_hit_y() -> f64 {
|
|
3379
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.point[1] as f64).unwrap_or(0.0) }
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
#[no_mangle]
|
|
3383
|
+
pub extern "C" fn bloom_pick_hit_z() -> f64 {
|
|
3384
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.point[2] as f64).unwrap_or(0.0) }
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
#[no_mangle]
|
|
3388
|
+
pub extern "C" fn bloom_pick_hit_normal_x() -> f64 {
|
|
3389
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.normal[0] as f64).unwrap_or(0.0) }
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
#[no_mangle]
|
|
3393
|
+
pub extern "C" fn bloom_pick_hit_normal_y() -> f64 {
|
|
3394
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.normal[1] as f64).unwrap_or(0.0) }
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
#[no_mangle]
|
|
3398
|
+
pub extern "C" fn bloom_pick_hit_normal_z() -> f64 {
|
|
3399
|
+
unsafe { LAST_PICK.as_ref().map(|r| r.normal[2] as f64).unwrap_or(0.0) }
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
// Q6: Multi-hit picking — returns all hits sorted by distance.
|
|
3403
|
+
static mut LAST_PICK_ALL: Vec<bloom_shared::picking::PickResult> = Vec::new();
|
|
3404
|
+
|
|
3405
|
+
#[no_mangle]
|
|
3406
|
+
pub extern "C" fn bloom_scene_pick_all(screen_x: f64, screen_y: f64, max_results: f64) -> f64 {
|
|
3407
|
+
let eng = engine();
|
|
3408
|
+
let inv_vp = eng.renderer.inverse_vp_matrix();
|
|
3409
|
+
let cam_pos = eng.renderer.camera_pos();
|
|
3410
|
+
let w = eng.renderer.width() as f32;
|
|
3411
|
+
let h = eng.renderer.height() as f32;
|
|
3412
|
+
let (origin, direction) = bloom_shared::picking::screen_to_ray(
|
|
3413
|
+
screen_x as f32, screen_y as f32, w, h, &inv_vp, &cam_pos,
|
|
3414
|
+
);
|
|
3415
|
+
let results = bloom_shared::picking::raycast_scene_all(&eng.scene, &origin, &direction, max_results as usize);
|
|
3416
|
+
let count = results.len();
|
|
3417
|
+
unsafe { LAST_PICK_ALL = results; }
|
|
3418
|
+
count as f64
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
#[no_mangle]
|
|
3422
|
+
pub extern "C" fn bloom_pick_all_handle(index: f64) -> f64 {
|
|
3423
|
+
let i = index as usize;
|
|
3424
|
+
unsafe { LAST_PICK_ALL.get(i).map(|r| r.handle).unwrap_or(0.0) }
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
#[no_mangle]
|
|
3428
|
+
pub extern "C" fn bloom_pick_all_distance(index: f64) -> f64 {
|
|
3429
|
+
let i = index as usize;
|
|
3430
|
+
unsafe { LAST_PICK_ALL.get(i).map(|r| r.distance as f64).unwrap_or(0.0) }
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
// ============================================================
|
|
3434
|
+
// Physics (Jolt 5.x) — FFI surface generated from shared macro
|
|
3435
|
+
// ============================================================
|
|
3436
|
+
|
|
3437
|
+
#[cfg(feature = "jolt")]
|
|
3438
|
+
#[inline]
|
|
3439
|
+
fn bloom_jolt_ffi_physics() -> &'static mut bloom_shared::physics_jolt::JoltPhysics {
|
|
3440
|
+
&mut engine().jolt
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
#[cfg(feature = "jolt")]
|
|
3444
|
+
bloom_shared::define_physics_ffi!();
|