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