diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index aae1e58e2..e7ddbcc1e 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -152,7 +152,7 @@ set_source_files_properties(src/toast/debugger.cpp PROPERTIES SKIP_UNITY_BUILD_I file(GLOB _TOAST_PROTO_SOURCES "generated/*.pb.cc") set_source_files_properties(${_TOAST_PROTO_SOURCES} PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) # OFF for clang-tidy since unity TUs leave compile_commands.json without per file entries -option(TOAST_UNITY_BUILD "Merge engine sources into unity translation units" ON) +option(TOAST_UNITY_BUILD "Merge engine sources into unity translation units" OFF) if (TOAST_UNITY_BUILD) set_target_properties(toast_engine PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 8) endif () diff --git a/engine/assets/textures/AABB.ktx2 b/engine/assets/textures/AABB.ktx2 new file mode 100644 index 000000000..404ad5712 Binary files /dev/null and b/engine/assets/textures/AABB.ktx2 differ diff --git a/engine/assets/textures/AABB.ktx2.meta b/engine/assets/textures/AABB.ktx2.meta new file mode 100644 index 000000000..8c6e521e4 --- /dev/null +++ b/engine/assets/textures/AABB.ktx2.meta @@ -0,0 +1,4 @@ +uid = "hI3aupwsoSA" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AimModifier.ktx2 b/engine/assets/textures/AimModifier.ktx2 new file mode 100644 index 000000000..380940c6b Binary files /dev/null and b/engine/assets/textures/AimModifier.ktx2 differ diff --git a/engine/assets/textures/AimModifier.ktx2.meta b/engine/assets/textures/AimModifier.ktx2.meta new file mode 100644 index 000000000..d69d8490a --- /dev/null +++ b/engine/assets/textures/AimModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ALz-iJ6F9yE" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/Anchor.ktx2 b/engine/assets/textures/Anchor.ktx2 new file mode 100644 index 000000000..6c80c4168 Binary files /dev/null and b/engine/assets/textures/Anchor.ktx2 differ diff --git a/engine/assets/textures/Anchor.ktx2.meta b/engine/assets/textures/Anchor.ktx2.meta new file mode 100644 index 000000000..6e79aa14c --- /dev/null +++ b/engine/assets/textures/Anchor.ktx2.meta @@ -0,0 +1,4 @@ +uid = "97yBbN-U0Vk" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AnimatableBody.ktx2 b/engine/assets/textures/AnimatableBody.ktx2 new file mode 100644 index 000000000..9fffe89c7 Binary files /dev/null and b/engine/assets/textures/AnimatableBody.ktx2 differ diff --git a/engine/assets/textures/AnimatableBody.ktx2.meta b/engine/assets/textures/AnimatableBody.ktx2.meta new file mode 100644 index 000000000..9c2c6c238 --- /dev/null +++ b/engine/assets/textures/AnimatableBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "wSrS5j2MLzc" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AnimatedSprite.ktx2 b/engine/assets/textures/AnimatedSprite.ktx2 new file mode 100644 index 000000000..1607b2dde Binary files /dev/null and b/engine/assets/textures/AnimatedSprite.ktx2 differ diff --git a/engine/assets/textures/AnimatedSprite.ktx2.meta b/engine/assets/textures/AnimatedSprite.ktx2.meta new file mode 100644 index 000000000..79d420ce7 --- /dev/null +++ b/engine/assets/textures/AnimatedSprite.ktx2.meta @@ -0,0 +1,4 @@ +uid = "gbs1Rpeavq0" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AnimatedTexture.ktx2 b/engine/assets/textures/AnimatedTexture.ktx2 new file mode 100644 index 000000000..ec8921e1b Binary files /dev/null and b/engine/assets/textures/AnimatedTexture.ktx2 differ diff --git a/engine/assets/textures/AnimatedTexture.ktx2.meta b/engine/assets/textures/AnimatedTexture.ktx2.meta new file mode 100644 index 000000000..5d26d9135 --- /dev/null +++ b/engine/assets/textures/AnimatedTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Im8gfO_qecQ" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/Animation.ktx2 b/engine/assets/textures/Animation.ktx2 new file mode 100644 index 000000000..6e9a57de5 Binary files /dev/null and b/engine/assets/textures/Animation.ktx2 differ diff --git a/engine/assets/textures/Animation.ktx2.meta b/engine/assets/textures/Animation.ktx2.meta new file mode 100644 index 000000000..a4566a870 --- /dev/null +++ b/engine/assets/textures/Animation.ktx2.meta @@ -0,0 +1,4 @@ +uid = "XFtUVxMxH6I" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AnimationTreeDock.ktx2 b/engine/assets/textures/AnimationTreeDock.ktx2 new file mode 100644 index 000000000..7cc3e1e8d Binary files /dev/null and b/engine/assets/textures/AnimationTreeDock.ktx2 differ diff --git a/engine/assets/textures/AnimationTreeDock.ktx2.meta b/engine/assets/textures/AnimationTreeDock.ktx2.meta new file mode 100644 index 000000000..3a9a58934 --- /dev/null +++ b/engine/assets/textures/AnimationTreeDock.ktx2.meta @@ -0,0 +1,4 @@ +uid = "pCgoGVRtb8c" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/Area.ktx2 b/engine/assets/textures/Area.ktx2 new file mode 100644 index 000000000..5e955040c Binary files /dev/null and b/engine/assets/textures/Area.ktx2 differ diff --git a/engine/assets/textures/Area.ktx2.meta b/engine/assets/textures/Area.ktx2.meta new file mode 100644 index 000000000..75e8b7483 --- /dev/null +++ b/engine/assets/textures/Area.ktx2.meta @@ -0,0 +1,4 @@ +uid = "waW6l_d0o-Q" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AreaLight.ktx2 b/engine/assets/textures/AreaLight.ktx2 new file mode 100644 index 000000000..13e81b72f Binary files /dev/null and b/engine/assets/textures/AreaLight.ktx2 differ diff --git a/engine/assets/textures/AreaLight.ktx2.meta b/engine/assets/textures/AreaLight.ktx2.meta new file mode 100644 index 000000000..21ad5e53e --- /dev/null +++ b/engine/assets/textures/AreaLight.ktx2.meta @@ -0,0 +1,4 @@ +uid = "zq2rUSFDVAw" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/ArrayMesh.ktx2 b/engine/assets/textures/ArrayMesh.ktx2 new file mode 100644 index 000000000..0a3043ca5 Binary files /dev/null and b/engine/assets/textures/ArrayMesh.ktx2 differ diff --git a/engine/assets/textures/ArrayMesh.ktx2.meta b/engine/assets/textures/ArrayMesh.ktx2.meta new file mode 100644 index 000000000..4dd768bfd --- /dev/null +++ b/engine/assets/textures/ArrayMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "He_ypNsSEKQ" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/ArrayOccluder.ktx2 b/engine/assets/textures/ArrayOccluder.ktx2 new file mode 100644 index 000000000..c18f858af Binary files /dev/null and b/engine/assets/textures/ArrayOccluder.ktx2 differ diff --git a/engine/assets/textures/ArrayOccluder.ktx2.meta b/engine/assets/textures/ArrayOccluder.ktx2.meta new file mode 100644 index 000000000..3a15f3c58 --- /dev/null +++ b/engine/assets/textures/ArrayOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "uZpCeULEgMA" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AspectRatioContainer.ktx2 b/engine/assets/textures/AspectRatioContainer.ktx2 new file mode 100644 index 000000000..4584a830d Binary files /dev/null and b/engine/assets/textures/AspectRatioContainer.ktx2 differ diff --git a/engine/assets/textures/AspectRatioContainer.ktx2.meta b/engine/assets/textures/AspectRatioContainer.ktx2.meta new file mode 100644 index 000000000..36df0be70 --- /dev/null +++ b/engine/assets/textures/AspectRatioContainer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "OPQyo9-3_DM" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AtlasTexture.ktx2 b/engine/assets/textures/AtlasTexture.ktx2 new file mode 100644 index 000000000..83799db9c Binary files /dev/null and b/engine/assets/textures/AtlasTexture.ktx2 differ diff --git a/engine/assets/textures/AtlasTexture.ktx2.meta b/engine/assets/textures/AtlasTexture.ktx2.meta new file mode 100644 index 000000000..23b8e6fa1 --- /dev/null +++ b/engine/assets/textures/AtlasTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "zxsceD-jbLo" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AudioBusLayout.ktx2 b/engine/assets/textures/AudioBusLayout.ktx2 new file mode 100644 index 000000000..abe816244 Binary files /dev/null and b/engine/assets/textures/AudioBusLayout.ktx2 differ diff --git a/engine/assets/textures/AudioBusLayout.ktx2.meta b/engine/assets/textures/AudioBusLayout.ktx2.meta new file mode 100644 index 000000000..4298ad9bd --- /dev/null +++ b/engine/assets/textures/AudioBusLayout.ktx2.meta @@ -0,0 +1,4 @@ +uid = "G6BzyWQyI7A" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AudioListener.ktx2 b/engine/assets/textures/AudioListener.ktx2 new file mode 100644 index 000000000..986bcf4f3 Binary files /dev/null and b/engine/assets/textures/AudioListener.ktx2 differ diff --git a/engine/assets/textures/AudioListener.ktx2.meta b/engine/assets/textures/AudioListener.ktx2.meta new file mode 100644 index 000000000..2c32be504 --- /dev/null +++ b/engine/assets/textures/AudioListener.ktx2.meta @@ -0,0 +1,4 @@ +uid = "GEkraOq8KaI" +type = "texture" +created_at = "2026-10-31T10:28:00.0000000Z" +modified_at = "2026-10-31T10:28:00.0000000Z" diff --git a/engine/assets/textures/AudioStream.ktx2 b/engine/assets/textures/AudioStream.ktx2 new file mode 100644 index 000000000..bac9f443c Binary files /dev/null and b/engine/assets/textures/AudioStream.ktx2 differ diff --git a/engine/assets/textures/AudioStream.ktx2.meta b/engine/assets/textures/AudioStream.ktx2.meta new file mode 100644 index 000000000..f777f9b8b --- /dev/null +++ b/engine/assets/textures/AudioStream.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ATA8NBxPbKM" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamGenerator.ktx2 b/engine/assets/textures/AudioStreamGenerator.ktx2 new file mode 100644 index 000000000..ab69ea783 Binary files /dev/null and b/engine/assets/textures/AudioStreamGenerator.ktx2 differ diff --git a/engine/assets/textures/AudioStreamGenerator.ktx2.meta b/engine/assets/textures/AudioStreamGenerator.ktx2.meta new file mode 100644 index 000000000..c4f8e96dd --- /dev/null +++ b/engine/assets/textures/AudioStreamGenerator.ktx2.meta @@ -0,0 +1,4 @@ +uid = "h-b19at-pow" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamMP3.ktx2 b/engine/assets/textures/AudioStreamMP3.ktx2 new file mode 100644 index 000000000..1e06cba52 Binary files /dev/null and b/engine/assets/textures/AudioStreamMP3.ktx2 differ diff --git a/engine/assets/textures/AudioStreamMP3.ktx2.meta b/engine/assets/textures/AudioStreamMP3.ktx2.meta new file mode 100644 index 000000000..e3e4ffcd6 --- /dev/null +++ b/engine/assets/textures/AudioStreamMP3.ktx2.meta @@ -0,0 +1,4 @@ +uid = "t6DRqPMe8u8" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamMicrophone.ktx2 b/engine/assets/textures/AudioStreamMicrophone.ktx2 new file mode 100644 index 000000000..eb1bd876c Binary files /dev/null and b/engine/assets/textures/AudioStreamMicrophone.ktx2 differ diff --git a/engine/assets/textures/AudioStreamMicrophone.ktx2.meta b/engine/assets/textures/AudioStreamMicrophone.ktx2.meta new file mode 100644 index 000000000..fe01f16e1 --- /dev/null +++ b/engine/assets/textures/AudioStreamMicrophone.ktx2.meta @@ -0,0 +1,4 @@ +uid = "d3p4YNt75yY" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamPlayer.ktx2 b/engine/assets/textures/AudioStreamPlayer.ktx2 new file mode 100644 index 000000000..38c398745 Binary files /dev/null and b/engine/assets/textures/AudioStreamPlayer.ktx2 differ diff --git a/engine/assets/textures/AudioStreamPlayer.ktx2.meta b/engine/assets/textures/AudioStreamPlayer.ktx2.meta new file mode 100644 index 000000000..b5c8ef8ab --- /dev/null +++ b/engine/assets/textures/AudioStreamPlayer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "BmdoelAs3yo" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamPolyphonic.ktx2 b/engine/assets/textures/AudioStreamPolyphonic.ktx2 new file mode 100644 index 000000000..1063ba1bc Binary files /dev/null and b/engine/assets/textures/AudioStreamPolyphonic.ktx2 differ diff --git a/engine/assets/textures/AudioStreamPolyphonic.ktx2.meta b/engine/assets/textures/AudioStreamPolyphonic.ktx2.meta new file mode 100644 index 000000000..57cda69dd --- /dev/null +++ b/engine/assets/textures/AudioStreamPolyphonic.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ysRP1aa4OX8" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamRandomizer.ktx2 b/engine/assets/textures/AudioStreamRandomizer.ktx2 new file mode 100644 index 000000000..5396ce797 Binary files /dev/null and b/engine/assets/textures/AudioStreamRandomizer.ktx2 differ diff --git a/engine/assets/textures/AudioStreamRandomizer.ktx2.meta b/engine/assets/textures/AudioStreamRandomizer.ktx2.meta new file mode 100644 index 000000000..3e56a2085 --- /dev/null +++ b/engine/assets/textures/AudioStreamRandomizer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2vInODQUXxw" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/AudioStreamWAV.ktx2 b/engine/assets/textures/AudioStreamWAV.ktx2 new file mode 100644 index 000000000..1e06cba52 Binary files /dev/null and b/engine/assets/textures/AudioStreamWAV.ktx2 differ diff --git a/engine/assets/textures/AudioStreamWAV.ktx2.meta b/engine/assets/textures/AudioStreamWAV.ktx2.meta new file mode 100644 index 000000000..1b6f63d49 --- /dev/null +++ b/engine/assets/textures/AudioStreamWAV.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Z4o2vuCzfmE" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BackBufferCopy.ktx2 b/engine/assets/textures/BackBufferCopy.ktx2 new file mode 100644 index 000000000..01cbea5c9 Binary files /dev/null and b/engine/assets/textures/BackBufferCopy.ktx2 differ diff --git a/engine/assets/textures/BackBufferCopy.ktx2.meta b/engine/assets/textures/BackBufferCopy.ktx2.meta new file mode 100644 index 000000000..4dce05586 --- /dev/null +++ b/engine/assets/textures/BackBufferCopy.ktx2.meta @@ -0,0 +1,4 @@ +uid = "OcTWDhBCe8g" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BezierHandlesBalanced.ktx2 b/engine/assets/textures/BezierHandlesBalanced.ktx2 new file mode 100644 index 000000000..cae05990d Binary files /dev/null and b/engine/assets/textures/BezierHandlesBalanced.ktx2 differ diff --git a/engine/assets/textures/BezierHandlesBalanced.ktx2.meta b/engine/assets/textures/BezierHandlesBalanced.ktx2.meta new file mode 100644 index 000000000..8cdb6a5fe --- /dev/null +++ b/engine/assets/textures/BezierHandlesBalanced.ktx2.meta @@ -0,0 +1,4 @@ +uid = "mahrnIcJfrY" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BitMap.ktx2 b/engine/assets/textures/BitMap.ktx2 new file mode 100644 index 000000000..5ea950a17 Binary files /dev/null and b/engine/assets/textures/BitMap.ktx2 differ diff --git a/engine/assets/textures/BitMap.ktx2.meta b/engine/assets/textures/BitMap.ktx2.meta new file mode 100644 index 000000000..6123bc3b7 --- /dev/null +++ b/engine/assets/textures/BitMap.ktx2.meta @@ -0,0 +1,4 @@ +uid = "kbnJZXbDoNw" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/Blend.ktx2 b/engine/assets/textures/Blend.ktx2 new file mode 100644 index 000000000..c803c5e53 Binary files /dev/null and b/engine/assets/textures/Blend.ktx2 differ diff --git a/engine/assets/textures/Blend.ktx2.meta b/engine/assets/textures/Blend.ktx2.meta new file mode 100644 index 000000000..d5745511d --- /dev/null +++ b/engine/assets/textures/Blend.ktx2.meta @@ -0,0 +1,4 @@ +uid = "AP0zrxxcKAQ" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/Bone.ktx2 b/engine/assets/textures/Bone.ktx2 new file mode 100644 index 000000000..d7201fad5 Binary files /dev/null and b/engine/assets/textures/Bone.ktx2 differ diff --git a/engine/assets/textures/Bone.ktx2.meta b/engine/assets/textures/Bone.ktx2.meta new file mode 100644 index 000000000..069074268 --- /dev/null +++ b/engine/assets/textures/Bone.ktx2.meta @@ -0,0 +1,4 @@ +uid = "o3BmVYw3d-k" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoneAttachment.ktx2 b/engine/assets/textures/BoneAttachment.ktx2 new file mode 100644 index 000000000..8300773d8 Binary files /dev/null and b/engine/assets/textures/BoneAttachment.ktx2 differ diff --git a/engine/assets/textures/BoneAttachment.ktx2.meta b/engine/assets/textures/BoneAttachment.ktx2.meta new file mode 100644 index 000000000..994b79e2d --- /dev/null +++ b/engine/assets/textures/BoneAttachment.ktx2.meta @@ -0,0 +1,4 @@ +uid = "JE4ynwFrjyw" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoneConstraint.ktx2 b/engine/assets/textures/BoneConstraint.ktx2 new file mode 100644 index 000000000..682cb2786 Binary files /dev/null and b/engine/assets/textures/BoneConstraint.ktx2 differ diff --git a/engine/assets/textures/BoneConstraint.ktx2.meta b/engine/assets/textures/BoneConstraint.ktx2.meta new file mode 100644 index 000000000..48c669a0d --- /dev/null +++ b/engine/assets/textures/BoneConstraint.ktx2.meta @@ -0,0 +1,4 @@ +uid = "357gex83bno" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoneTwistDisperser.ktx2 b/engine/assets/textures/BoneTwistDisperser.ktx2 new file mode 100644 index 000000000..04ea5a5f5 Binary files /dev/null and b/engine/assets/textures/BoneTwistDisperser.ktx2 differ diff --git a/engine/assets/textures/BoneTwistDisperser.ktx2.meta b/engine/assets/textures/BoneTwistDisperser.ktx2.meta new file mode 100644 index 000000000..000da95b7 --- /dev/null +++ b/engine/assets/textures/BoneTwistDisperser.ktx2.meta @@ -0,0 +1,4 @@ +uid = "rV1sMKQaQro" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoxContainer.ktx2 b/engine/assets/textures/BoxContainer.ktx2 new file mode 100644 index 000000000..f1cd73f60 Binary files /dev/null and b/engine/assets/textures/BoxContainer.ktx2 differ diff --git a/engine/assets/textures/BoxContainer.ktx2.meta b/engine/assets/textures/BoxContainer.ktx2.meta new file mode 100644 index 000000000..0df40274a --- /dev/null +++ b/engine/assets/textures/BoxContainer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "L25KBgbb_8M" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoxMesh.ktx2 b/engine/assets/textures/BoxMesh.ktx2 new file mode 100644 index 000000000..ce1966026 Binary files /dev/null and b/engine/assets/textures/BoxMesh.ktx2 differ diff --git a/engine/assets/textures/BoxMesh.ktx2.meta b/engine/assets/textures/BoxMesh.ktx2.meta new file mode 100644 index 000000000..23eae6e40 --- /dev/null +++ b/engine/assets/textures/BoxMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "YpKE54pHxXI" +type = "texture" +created_at = "2026-10-31T10:28:01.0000000Z" +modified_at = "2026-10-31T10:28:01.0000000Z" diff --git a/engine/assets/textures/BoxOccluder.ktx2 b/engine/assets/textures/BoxOccluder.ktx2 new file mode 100644 index 000000000..f6ecf5b8a Binary files /dev/null and b/engine/assets/textures/BoxOccluder.ktx2 differ diff --git a/engine/assets/textures/BoxOccluder.ktx2.meta b/engine/assets/textures/BoxOccluder.ktx2.meta new file mode 100644 index 000000000..80085dd20 --- /dev/null +++ b/engine/assets/textures/BoxOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "FCAz5q-qIjE" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/Bucket.ktx2 b/engine/assets/textures/Bucket.ktx2 new file mode 100644 index 000000000..951dd18e8 Binary files /dev/null and b/engine/assets/textures/Bucket.ktx2 differ diff --git a/engine/assets/textures/Bucket.ktx2.meta b/engine/assets/textures/Bucket.ktx2.meta new file mode 100644 index 000000000..b64966018 --- /dev/null +++ b/engine/assets/textures/Bucket.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Q-hqqEhtAgc" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/ButtonGroup.ktx2 b/engine/assets/textures/ButtonGroup.ktx2 new file mode 100644 index 000000000..18c4b4afd Binary files /dev/null and b/engine/assets/textures/ButtonGroup.ktx2 differ diff --git a/engine/assets/textures/ButtonGroup.ktx2.meta b/engine/assets/textures/ButtonGroup.ktx2.meta new file mode 100644 index 000000000..52da84134 --- /dev/null +++ b/engine/assets/textures/ButtonGroup.ktx2.meta @@ -0,0 +1,4 @@ +uid = "QPadNgjU3nU" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CCDIK.ktx2 b/engine/assets/textures/CCDIK.ktx2 new file mode 100644 index 000000000..c57de6ed6 Binary files /dev/null and b/engine/assets/textures/CCDIK.ktx2 differ diff --git a/engine/assets/textures/CCDIK.ktx2.meta b/engine/assets/textures/CCDIK.ktx2.meta new file mode 100644 index 000000000..8d8fdbb0d --- /dev/null +++ b/engine/assets/textures/CCDIK.ktx2.meta @@ -0,0 +1,4 @@ +uid = "m2bLWtztSf8" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CPUParticles.ktx2 b/engine/assets/textures/CPUParticles.ktx2 new file mode 100644 index 000000000..887b756df Binary files /dev/null and b/engine/assets/textures/CPUParticles.ktx2 differ diff --git a/engine/assets/textures/CPUParticles.ktx2.meta b/engine/assets/textures/CPUParticles.ktx2.meta new file mode 100644 index 000000000..e5b5aa329 --- /dev/null +++ b/engine/assets/textures/CPUParticles.ktx2.meta @@ -0,0 +1,4 @@ +uid = "xoUMOrYOJxc" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Callable.ktx2 b/engine/assets/textures/Callable.ktx2 new file mode 100644 index 000000000..43a612def Binary files /dev/null and b/engine/assets/textures/Callable.ktx2 differ diff --git a/engine/assets/textures/Callable.ktx2.meta b/engine/assets/textures/Callable.ktx2.meta new file mode 100644 index 000000000..d82b65a05 --- /dev/null +++ b/engine/assets/textures/Callable.ktx2.meta @@ -0,0 +1,4 @@ +uid = "MkhCTsZC33k" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/Camera.ktx2 b/engine/assets/textures/Camera.ktx2 new file mode 100644 index 000000000..32bf7e2bb Binary files /dev/null and b/engine/assets/textures/Camera.ktx2 differ diff --git a/engine/assets/textures/Camera.ktx2.meta b/engine/assets/textures/Camera.ktx2.meta new file mode 100644 index 000000000..9f4a6b2cd --- /dev/null +++ b/engine/assets/textures/Camera.ktx2.meta @@ -0,0 +1,4 @@ +uid = "NwQxvGDXg2k" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CameraAttributes.ktx2 b/engine/assets/textures/CameraAttributes.ktx2 new file mode 100644 index 000000000..131c7f17c Binary files /dev/null and b/engine/assets/textures/CameraAttributes.ktx2 differ diff --git a/engine/assets/textures/CameraAttributes.ktx2.meta b/engine/assets/textures/CameraAttributes.ktx2.meta new file mode 100644 index 000000000..e10760df5 --- /dev/null +++ b/engine/assets/textures/CameraAttributes.ktx2.meta @@ -0,0 +1,4 @@ +uid = "F9xtS_Iwo1g" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CameraTexture.ktx2 b/engine/assets/textures/CameraTexture.ktx2 new file mode 100644 index 000000000..2d7a90073 Binary files /dev/null and b/engine/assets/textures/CameraTexture.ktx2 differ diff --git a/engine/assets/textures/CameraTexture.ktx2.meta b/engine/assets/textures/CameraTexture.ktx2.meta new file mode 100644 index 000000000..11683a3a6 --- /dev/null +++ b/engine/assets/textures/CameraTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "dfMpVB9CMUk" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CapsuleMesh.ktx2 b/engine/assets/textures/CapsuleMesh.ktx2 new file mode 100644 index 000000000..eb8e8dbdb Binary files /dev/null and b/engine/assets/textures/CapsuleMesh.ktx2 differ diff --git a/engine/assets/textures/CapsuleMesh.ktx2.meta b/engine/assets/textures/CapsuleMesh.ktx2.meta new file mode 100644 index 000000000..6a9f28e1c --- /dev/null +++ b/engine/assets/textures/CapsuleMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "SO-glu_Mdds" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CapsuleShape.ktx2 b/engine/assets/textures/CapsuleShape.ktx2 new file mode 100644 index 000000000..c6751e043 Binary files /dev/null and b/engine/assets/textures/CapsuleShape.ktx2 differ diff --git a/engine/assets/textures/CapsuleShape.ktx2.meta b/engine/assets/textures/CapsuleShape.ktx2.meta new file mode 100644 index 000000000..34c7111cf --- /dev/null +++ b/engine/assets/textures/CapsuleShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "vhi-awNsdhk" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CenterContainer.ktx2 b/engine/assets/textures/CenterContainer.ktx2 new file mode 100644 index 000000000..17a5a687d Binary files /dev/null and b/engine/assets/textures/CenterContainer.ktx2 differ diff --git a/engine/assets/textures/CenterContainer.ktx2.meta b/engine/assets/textures/CenterContainer.ktx2.meta new file mode 100644 index 000000000..802bf6d15 --- /dev/null +++ b/engine/assets/textures/CenterContainer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "a_m_MPqrZvQ" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CharacterBody.ktx2 b/engine/assets/textures/CharacterBody.ktx2 new file mode 100644 index 000000000..2d2b11507 Binary files /dev/null and b/engine/assets/textures/CharacterBody.ktx2 differ diff --git a/engine/assets/textures/CharacterBody.ktx2.meta b/engine/assets/textures/CharacterBody.ktx2.meta new file mode 100644 index 000000000..7c625d3f0 --- /dev/null +++ b/engine/assets/textures/CharacterBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "_pTy24hgThs" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CheckBox.ktx2 b/engine/assets/textures/CheckBox.ktx2 new file mode 100644 index 000000000..502fa706c Binary files /dev/null and b/engine/assets/textures/CheckBox.ktx2 differ diff --git a/engine/assets/textures/CheckBox.ktx2.meta b/engine/assets/textures/CheckBox.ktx2.meta new file mode 100644 index 000000000..e14554eb9 --- /dev/null +++ b/engine/assets/textures/CheckBox.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ocwXeBPSibQ" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CheckButton.ktx2 b/engine/assets/textures/CheckButton.ktx2 new file mode 100644 index 000000000..3c18b173d Binary files /dev/null and b/engine/assets/textures/CheckButton.ktx2 differ diff --git a/engine/assets/textures/CheckButton.ktx2.meta b/engine/assets/textures/CheckButton.ktx2.meta new file mode 100644 index 000000000..8a9aa86bd --- /dev/null +++ b/engine/assets/textures/CheckButton.ktx2.meta @@ -0,0 +1,4 @@ +uid = "mCBrSiAfJ9s" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/Circle.ktx2 b/engine/assets/textures/Circle.ktx2 new file mode 100644 index 000000000..1bfd924ab Binary files /dev/null and b/engine/assets/textures/Circle.ktx2 differ diff --git a/engine/assets/textures/Circle.ktx2.meta b/engine/assets/textures/Circle.ktx2.meta new file mode 100644 index 000000000..6d6a4b163 --- /dev/null +++ b/engine/assets/textures/Circle.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2kMT-5ror3w" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/ClassList.ktx2 b/engine/assets/textures/ClassList.ktx2 new file mode 100644 index 000000000..1e8e40967 Binary files /dev/null and b/engine/assets/textures/ClassList.ktx2 differ diff --git a/engine/assets/textures/ClassList.ktx2.meta b/engine/assets/textures/ClassList.ktx2.meta new file mode 100644 index 000000000..ae587b70c --- /dev/null +++ b/engine/assets/textures/ClassList.ktx2.meta @@ -0,0 +1,4 @@ +uid = "TUuXyIYOsL4" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/CollisionPolygon.ktx2 b/engine/assets/textures/CollisionPolygon.ktx2 new file mode 100644 index 000000000..cfda2a48c Binary files /dev/null and b/engine/assets/textures/CollisionPolygon.ktx2 differ diff --git a/engine/assets/textures/CollisionPolygon.ktx2.meta b/engine/assets/textures/CollisionPolygon.ktx2.meta new file mode 100644 index 000000000..6cbe6b931 --- /dev/null +++ b/engine/assets/textures/CollisionPolygon.ktx2.meta @@ -0,0 +1,4 @@ +uid = "RWip9vISQSE" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/ConeTwistJoint.ktx2 b/engine/assets/textures/ConeTwistJoint.ktx2 new file mode 100644 index 000000000..7cfedef69 Binary files /dev/null and b/engine/assets/textures/ConeTwistJoint.ktx2 differ diff --git a/engine/assets/textures/ConeTwistJoint.ktx2.meta b/engine/assets/textures/ConeTwistJoint.ktx2.meta new file mode 100644 index 000000000..0f08b3ba1 --- /dev/null +++ b/engine/assets/textures/ConeTwistJoint.ktx2.meta @@ -0,0 +1,4 @@ +uid = "HCdkBok5-eI" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/Container.ktx2 b/engine/assets/textures/Container.ktx2 new file mode 100644 index 000000000..cb10bed60 Binary files /dev/null and b/engine/assets/textures/Container.ktx2 differ diff --git a/engine/assets/textures/Container.ktx2.meta b/engine/assets/textures/Container.ktx2.meta new file mode 100644 index 000000000..25063ff14 --- /dev/null +++ b/engine/assets/textures/Container.ktx2.meta @@ -0,0 +1,4 @@ +uid = "AP98UOTgbys" +type = "texture" +created_at = "2026-10-31T10:28:02.0000000Z" +modified_at = "2026-10-31T10:28:02.0000000Z" diff --git a/engine/assets/textures/ContainerLayout.ktx2 b/engine/assets/textures/ContainerLayout.ktx2 new file mode 100644 index 000000000..3efd9469e Binary files /dev/null and b/engine/assets/textures/ContainerLayout.ktx2 differ diff --git a/engine/assets/textures/ContainerLayout.ktx2.meta b/engine/assets/textures/ContainerLayout.ktx2.meta new file mode 100644 index 000000000..7f8196adf --- /dev/null +++ b/engine/assets/textures/ContainerLayout.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2usfvVKD12M" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Control.ktx2 b/engine/assets/textures/Control.ktx2 new file mode 100644 index 000000000..280a1afe3 Binary files /dev/null and b/engine/assets/textures/Control.ktx2 differ diff --git a/engine/assets/textures/Control.ktx2.meta b/engine/assets/textures/Control.ktx2.meta new file mode 100644 index 000000000..f4ea8b0ec --- /dev/null +++ b/engine/assets/textures/Control.ktx2.meta @@ -0,0 +1,4 @@ +uid = "r5zB6TPAzcU" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/ControlLayout.ktx2 b/engine/assets/textures/ControlLayout.ktx2 new file mode 100644 index 000000000..8315ceec1 Binary files /dev/null and b/engine/assets/textures/ControlLayout.ktx2 differ diff --git a/engine/assets/textures/ControlLayout.ktx2.meta b/engine/assets/textures/ControlLayout.ktx2.meta new file mode 100644 index 000000000..0b2728865 --- /dev/null +++ b/engine/assets/textures/ControlLayout.ktx2.meta @@ -0,0 +1,4 @@ +uid = "SlrTxouV9dA" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/ConvexPolygonShape.ktx2 b/engine/assets/textures/ConvexPolygonShape.ktx2 new file mode 100644 index 000000000..5d094626e Binary files /dev/null and b/engine/assets/textures/ConvexPolygonShape.ktx2 differ diff --git a/engine/assets/textures/ConvexPolygonShape.ktx2.meta b/engine/assets/textures/ConvexPolygonShape.ktx2.meta new file mode 100644 index 000000000..5d7248fe1 --- /dev/null +++ b/engine/assets/textures/ConvexPolygonShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "q71rjapNLdc" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/CopyTransformModifier.ktx2 b/engine/assets/textures/CopyTransformModifier.ktx2 new file mode 100644 index 000000000..6a7102b63 Binary files /dev/null and b/engine/assets/textures/CopyTransformModifier.ktx2 differ diff --git a/engine/assets/textures/CopyTransformModifier.ktx2.meta b/engine/assets/textures/CopyTransformModifier.ktx2.meta new file mode 100644 index 000000000..fbd676f48 --- /dev/null +++ b/engine/assets/textures/CopyTransformModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "6j6wT7dagXE" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Cubemap.ktx2 b/engine/assets/textures/Cubemap.ktx2 new file mode 100644 index 000000000..5e9c59d22 Binary files /dev/null and b/engine/assets/textures/Cubemap.ktx2 differ diff --git a/engine/assets/textures/Cubemap.ktx2.meta b/engine/assets/textures/Cubemap.ktx2.meta new file mode 100644 index 000000000..19ea8af06 --- /dev/null +++ b/engine/assets/textures/Cubemap.ktx2.meta @@ -0,0 +1,4 @@ +uid = "i1OxTmv2Fiw" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Curve.ktx2 b/engine/assets/textures/Curve.ktx2 new file mode 100644 index 000000000..838925c6c Binary files /dev/null and b/engine/assets/textures/Curve.ktx2 differ diff --git a/engine/assets/textures/Curve.ktx2.meta b/engine/assets/textures/Curve.ktx2.meta new file mode 100644 index 000000000..6ff7c1493 --- /dev/null +++ b/engine/assets/textures/Curve.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2pe_8B-rF_A" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/CurveTexture.ktx2 b/engine/assets/textures/CurveTexture.ktx2 new file mode 100644 index 000000000..1e50636ba Binary files /dev/null and b/engine/assets/textures/CurveTexture.ktx2 differ diff --git a/engine/assets/textures/CurveTexture.ktx2.meta b/engine/assets/textures/CurveTexture.ktx2.meta new file mode 100644 index 000000000..eefa3e69b --- /dev/null +++ b/engine/assets/textures/CurveTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "5LceaCYQfrI" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/CylinderMesh.ktx2 b/engine/assets/textures/CylinderMesh.ktx2 new file mode 100644 index 000000000..59e7e2e39 Binary files /dev/null and b/engine/assets/textures/CylinderMesh.ktx2 differ diff --git a/engine/assets/textures/CylinderMesh.ktx2.meta b/engine/assets/textures/CylinderMesh.ktx2.meta new file mode 100644 index 000000000..94a0e15f5 --- /dev/null +++ b/engine/assets/textures/CylinderMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "IauMbsPX-9k" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/DirectionalLight.ktx2 b/engine/assets/textures/DirectionalLight.ktx2 index 667ead484..99c2562cb 100644 Binary files a/engine/assets/textures/DirectionalLight.ktx2 and b/engine/assets/textures/DirectionalLight.ktx2 differ diff --git a/engine/assets/textures/EditBezier.ktx2 b/engine/assets/textures/EditBezier.ktx2 new file mode 100644 index 000000000..a974a8b12 Binary files /dev/null and b/engine/assets/textures/EditBezier.ktx2 differ diff --git a/engine/assets/textures/EditBezier.ktx2.meta b/engine/assets/textures/EditBezier.ktx2.meta new file mode 100644 index 000000000..1e90f50fd --- /dev/null +++ b/engine/assets/textures/EditBezier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "NGuPOSeCi1Q" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Enum.ktx2 b/engine/assets/textures/Enum.ktx2 new file mode 100644 index 000000000..026c1fe37 Binary files /dev/null and b/engine/assets/textures/Enum.ktx2 differ diff --git a/engine/assets/textures/Enum.ktx2.meta b/engine/assets/textures/Enum.ktx2.meta new file mode 100644 index 000000000..833f43d16 --- /dev/null +++ b/engine/assets/textures/Enum.ktx2.meta @@ -0,0 +1,4 @@ +uid = "-AG_69eK_Dc" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Environment.ktx2 b/engine/assets/textures/Environment.ktx2 new file mode 100644 index 000000000..f79f47377 Binary files /dev/null and b/engine/assets/textures/Environment.ktx2 differ diff --git a/engine/assets/textures/Environment.ktx2.meta b/engine/assets/textures/Environment.ktx2.meta new file mode 100644 index 000000000..3740d864e --- /dev/null +++ b/engine/assets/textures/Environment.ktx2.meta @@ -0,0 +1,4 @@ +uid = "HtUWbs7u_mk" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/FogVolume.ktx2 b/engine/assets/textures/FogVolume.ktx2 new file mode 100644 index 000000000..b29c235fa Binary files /dev/null and b/engine/assets/textures/FogVolume.ktx2 differ diff --git a/engine/assets/textures/FogVolume.ktx2.meta b/engine/assets/textures/FogVolume.ktx2.meta new file mode 100644 index 000000000..010a0abbb --- /dev/null +++ b/engine/assets/textures/FogVolume.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ZDqDssLFPgE" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Font.ktx2 b/engine/assets/textures/Font.ktx2 new file mode 100644 index 000000000..5a05d7f59 Binary files /dev/null and b/engine/assets/textures/Font.ktx2 differ diff --git a/engine/assets/textures/Font.ktx2.meta b/engine/assets/textures/Font.ktx2.meta new file mode 100644 index 000000000..a0965a714 --- /dev/null +++ b/engine/assets/textures/Font.ktx2.meta @@ -0,0 +1,4 @@ +uid = "LVivTdH8Qxk" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/FontFile.ktx2 b/engine/assets/textures/FontFile.ktx2 new file mode 100644 index 000000000..8dd922a8d Binary files /dev/null and b/engine/assets/textures/FontFile.ktx2 differ diff --git a/engine/assets/textures/FontFile.ktx2.meta b/engine/assets/textures/FontFile.ktx2.meta new file mode 100644 index 000000000..6cd3f7351 --- /dev/null +++ b/engine/assets/textures/FontFile.ktx2.meta @@ -0,0 +1,4 @@ +uid = "UEouLLyoBo0" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/GPUParticles.ktx2 b/engine/assets/textures/GPUParticles.ktx2 new file mode 100644 index 000000000..6a2f0c160 Binary files /dev/null and b/engine/assets/textures/GPUParticles.ktx2 differ diff --git a/engine/assets/textures/GPUParticles.ktx2.meta b/engine/assets/textures/GPUParticles.ktx2.meta new file mode 100644 index 000000000..e862e737b --- /dev/null +++ b/engine/assets/textures/GPUParticles.ktx2.meta @@ -0,0 +1,4 @@ +uid = "97JtKvo3C8s" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesAttractorBox.ktx2 b/engine/assets/textures/GPUParticlesAttractorBox.ktx2 new file mode 100644 index 000000000..f7170020c Binary files /dev/null and b/engine/assets/textures/GPUParticlesAttractorBox.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesAttractorBox.ktx2.meta b/engine/assets/textures/GPUParticlesAttractorBox.ktx2.meta new file mode 100644 index 000000000..77f464e92 --- /dev/null +++ b/engine/assets/textures/GPUParticlesAttractorBox.ktx2.meta @@ -0,0 +1,4 @@ +uid = "m6tb2gsvsPc" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesAttractorSphere.ktx2 b/engine/assets/textures/GPUParticlesAttractorSphere.ktx2 new file mode 100644 index 000000000..645ce41f7 Binary files /dev/null and b/engine/assets/textures/GPUParticlesAttractorSphere.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesAttractorSphere.ktx2.meta b/engine/assets/textures/GPUParticlesAttractorSphere.ktx2.meta new file mode 100644 index 000000000..599f011a0 --- /dev/null +++ b/engine/assets/textures/GPUParticlesAttractorSphere.ktx2.meta @@ -0,0 +1,4 @@ +uid = "PB4iCDi4ebA" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2 b/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2 new file mode 100644 index 000000000..00469a74e Binary files /dev/null and b/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2.meta b/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2.meta new file mode 100644 index 000000000..d68f83b97 --- /dev/null +++ b/engine/assets/textures/GPUParticlesAttractorVectorField.ktx2.meta @@ -0,0 +1,4 @@ +uid = "dQj60Xjz00Q" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesCollisionBox.ktx2 b/engine/assets/textures/GPUParticlesCollisionBox.ktx2 new file mode 100644 index 000000000..a07480ba0 Binary files /dev/null and b/engine/assets/textures/GPUParticlesCollisionBox.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesCollisionBox.ktx2.meta b/engine/assets/textures/GPUParticlesCollisionBox.ktx2.meta new file mode 100644 index 000000000..678613f0b --- /dev/null +++ b/engine/assets/textures/GPUParticlesCollisionBox.ktx2.meta @@ -0,0 +1,4 @@ +uid = "n-4rBRtmg8A" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2 b/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2 new file mode 100644 index 000000000..8cec9fea3 Binary files /dev/null and b/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2.meta b/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2.meta new file mode 100644 index 000000000..a3c644f8b --- /dev/null +++ b/engine/assets/textures/GPUParticlesCollisionHeightField.ktx2.meta @@ -0,0 +1,4 @@ +uid = "mtLO27-j2nE" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesCollisionSDF.ktx2 b/engine/assets/textures/GPUParticlesCollisionSDF.ktx2 new file mode 100644 index 000000000..cfb13ec02 Binary files /dev/null and b/engine/assets/textures/GPUParticlesCollisionSDF.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesCollisionSDF.ktx2.meta b/engine/assets/textures/GPUParticlesCollisionSDF.ktx2.meta new file mode 100644 index 000000000..694fc1a7f --- /dev/null +++ b/engine/assets/textures/GPUParticlesCollisionSDF.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2HNLqn0t_eQ" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/GPUParticlesCollisionSphere.ktx2 b/engine/assets/textures/GPUParticlesCollisionSphere.ktx2 new file mode 100644 index 000000000..95960835a Binary files /dev/null and b/engine/assets/textures/GPUParticlesCollisionSphere.ktx2 differ diff --git a/engine/assets/textures/GPUParticlesCollisionSphere.ktx2.meta b/engine/assets/textures/GPUParticlesCollisionSphere.ktx2.meta new file mode 100644 index 000000000..12f6b6e62 --- /dev/null +++ b/engine/assets/textures/GPUParticlesCollisionSphere.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Y-hfHunqRYA" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/Geometry.ktx2 b/engine/assets/textures/Geometry.ktx2 new file mode 100644 index 000000000..1a359a53a Binary files /dev/null and b/engine/assets/textures/Geometry.ktx2 differ diff --git a/engine/assets/textures/Geometry.ktx2.meta b/engine/assets/textures/Geometry.ktx2.meta new file mode 100644 index 000000000..9413750f5 --- /dev/null +++ b/engine/assets/textures/Geometry.ktx2.meta @@ -0,0 +1,4 @@ +uid = "az8R1RKhmgA" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/GeometryInstance.ktx2 b/engine/assets/textures/GeometryInstance.ktx2 new file mode 100644 index 000000000..f92fded34 Binary files /dev/null and b/engine/assets/textures/GeometryInstance.ktx2 differ diff --git a/engine/assets/textures/GeometryInstance.ktx2.meta b/engine/assets/textures/GeometryInstance.ktx2.meta new file mode 100644 index 000000000..41cb281f5 --- /dev/null +++ b/engine/assets/textures/GeometryInstance.ktx2.meta @@ -0,0 +1,4 @@ +uid = "bw6VXiCeyIE" +type = "texture" +created_at = "2026-10-31T10:28:03.0000000Z" +modified_at = "2026-10-31T10:28:03.0000000Z" diff --git a/engine/assets/textures/Heart.ktx2 b/engine/assets/textures/Heart.ktx2 new file mode 100644 index 000000000..b2a16528d Binary files /dev/null and b/engine/assets/textures/Heart.ktx2 differ diff --git a/engine/assets/textures/Heart.ktx2.meta b/engine/assets/textures/Heart.ktx2.meta new file mode 100644 index 000000000..a27518dbd --- /dev/null +++ b/engine/assets/textures/Heart.ktx2.meta @@ -0,0 +1,4 @@ +uid = "p31SKjfDwog" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/HeightMapShape.ktx2 b/engine/assets/textures/HeightMapShape.ktx2 new file mode 100644 index 000000000..79222c359 Binary files /dev/null and b/engine/assets/textures/HeightMapShape.ktx2 differ diff --git a/engine/assets/textures/HeightMapShape.ktx2.meta b/engine/assets/textures/HeightMapShape.ktx2.meta new file mode 100644 index 000000000..a04a53a5e --- /dev/null +++ b/engine/assets/textures/HeightMapShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "QcxZjHNTT6g" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/HingeJoint.ktx2 b/engine/assets/textures/HingeJoint.ktx2 new file mode 100644 index 000000000..b96a59b4b Binary files /dev/null and b/engine/assets/textures/HingeJoint.ktx2 differ diff --git a/engine/assets/textures/HingeJoint.ktx2.meta b/engine/assets/textures/HingeJoint.ktx2.meta new file mode 100644 index 000000000..86a98e63c --- /dev/null +++ b/engine/assets/textures/HingeJoint.ktx2.meta @@ -0,0 +1,4 @@ +uid = "NFpiKhfdh4A" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/IKModifier.ktx2 b/engine/assets/textures/IKModifier.ktx2 new file mode 100644 index 000000000..99cd9b221 Binary files /dev/null and b/engine/assets/textures/IKModifier.ktx2 differ diff --git a/engine/assets/textures/IKModifier.ktx2.meta b/engine/assets/textures/IKModifier.ktx2.meta new file mode 100644 index 000000000..a1de8dddb --- /dev/null +++ b/engine/assets/textures/IKModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "c3Zm7SMAc1I" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/Image.ktx2 b/engine/assets/textures/Image.ktx2 new file mode 100644 index 000000000..651a8ee82 Binary files /dev/null and b/engine/assets/textures/Image.ktx2 differ diff --git a/engine/assets/textures/Image.ktx2.meta b/engine/assets/textures/Image.ktx2.meta new file mode 100644 index 000000000..29b7039f5 --- /dev/null +++ b/engine/assets/textures/Image.ktx2.meta @@ -0,0 +1,4 @@ +uid = "82bgmzLfG60" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/InputEventJoypadMotion.ktx2 b/engine/assets/textures/InputEventJoypadMotion.ktx2 new file mode 100644 index 000000000..25c882961 Binary files /dev/null and b/engine/assets/textures/InputEventJoypadMotion.ktx2 differ diff --git a/engine/assets/textures/InputEventJoypadMotion.ktx2.meta b/engine/assets/textures/InputEventJoypadMotion.ktx2.meta new file mode 100644 index 000000000..0d7ee358e --- /dev/null +++ b/engine/assets/textures/InputEventJoypadMotion.ktx2.meta @@ -0,0 +1,4 @@ +uid = "9vDQ5kGrxxU" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/Instance.ktx2 b/engine/assets/textures/Instance.ktx2 new file mode 100644 index 000000000..c70e28bd9 Binary files /dev/null and b/engine/assets/textures/Instance.ktx2 differ diff --git a/engine/assets/textures/Instance.ktx2.meta b/engine/assets/textures/Instance.ktx2.meta new file mode 100644 index 000000000..168243b25 --- /dev/null +++ b/engine/assets/textures/Instance.ktx2.meta @@ -0,0 +1,4 @@ +uid = "AI3e6_QG5Us" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/InterpCubic.ktx2 b/engine/assets/textures/InterpCubic.ktx2 new file mode 100644 index 000000000..29f31ca16 Binary files /dev/null and b/engine/assets/textures/InterpCubic.ktx2 differ diff --git a/engine/assets/textures/InterpCubic.ktx2.meta b/engine/assets/textures/InterpCubic.ktx2.meta new file mode 100644 index 000000000..23245b565 --- /dev/null +++ b/engine/assets/textures/InterpCubic.ktx2.meta @@ -0,0 +1,4 @@ +uid = "bKNlfaPC0sA" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/InterpLinearAngle.ktx2 b/engine/assets/textures/InterpLinearAngle.ktx2 new file mode 100644 index 000000000..b1e3b66d2 Binary files /dev/null and b/engine/assets/textures/InterpLinearAngle.ktx2 differ diff --git a/engine/assets/textures/InterpLinearAngle.ktx2.meta b/engine/assets/textures/InterpLinearAngle.ktx2.meta new file mode 100644 index 000000000..20f8722e5 --- /dev/null +++ b/engine/assets/textures/InterpLinearAngle.ktx2.meta @@ -0,0 +1,4 @@ +uid = "0r8-UdE05nQ" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/InterpRaw.ktx2 b/engine/assets/textures/InterpRaw.ktx2 new file mode 100644 index 000000000..eedf9f521 Binary files /dev/null and b/engine/assets/textures/InterpRaw.ktx2 differ diff --git a/engine/assets/textures/InterpRaw.ktx2.meta b/engine/assets/textures/InterpRaw.ktx2.meta new file mode 100644 index 000000000..3a4b72322 --- /dev/null +++ b/engine/assets/textures/InterpRaw.ktx2.meta @@ -0,0 +1,4 @@ +uid = "PuVtAH3n00o" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/Joypad.ktx2 b/engine/assets/textures/Joypad.ktx2 new file mode 100644 index 000000000..81b767448 Binary files /dev/null and b/engine/assets/textures/Joypad.ktx2 differ diff --git a/engine/assets/textures/Joypad.ktx2.meta b/engine/assets/textures/Joypad.ktx2.meta new file mode 100644 index 000000000..a3ede27e4 --- /dev/null +++ b/engine/assets/textures/Joypad.ktx2.meta @@ -0,0 +1,4 @@ +uid = "np42hsQEvlE" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/Label.ktx2 b/engine/assets/textures/Label.ktx2 new file mode 100644 index 000000000..9de677c72 Binary files /dev/null and b/engine/assets/textures/Label.ktx2 differ diff --git a/engine/assets/textures/Label.ktx2.meta b/engine/assets/textures/Label.ktx2.meta new file mode 100644 index 000000000..ab3c3eba5 --- /dev/null +++ b/engine/assets/textures/Label.ktx2.meta @@ -0,0 +1,4 @@ +uid = "stl1JKgzsp4" +type = "texture" +created_at = "2026-10-31T10:28:04.0000000Z" +modified_at = "2026-10-31T10:28:04.0000000Z" diff --git a/engine/assets/textures/LightOccluder.ktx2 b/engine/assets/textures/LightOccluder.ktx2 new file mode 100644 index 000000000..94c64f8ec Binary files /dev/null and b/engine/assets/textures/LightOccluder.ktx2 differ diff --git a/engine/assets/textures/LightOccluder.ktx2.meta b/engine/assets/textures/LightOccluder.ktx2.meta new file mode 100644 index 000000000..5b66c9e94 --- /dev/null +++ b/engine/assets/textures/LightOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "IlQIS_yiwWE" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/LightmapProbe.ktx2 b/engine/assets/textures/LightmapProbe.ktx2 new file mode 100644 index 000000000..7842c40b3 Binary files /dev/null and b/engine/assets/textures/LightmapProbe.ktx2 differ diff --git a/engine/assets/textures/LightmapProbe.ktx2.meta b/engine/assets/textures/LightmapProbe.ktx2.meta new file mode 100644 index 000000000..cbfce0692 --- /dev/null +++ b/engine/assets/textures/LightmapProbe.ktx2.meta @@ -0,0 +1,4 @@ +uid = "5tsLMDdvV5I" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/LookAtModifier.ktx2 b/engine/assets/textures/LookAtModifier.ktx2 new file mode 100644 index 000000000..a45de05c3 Binary files /dev/null and b/engine/assets/textures/LookAtModifier.ktx2 differ diff --git a/engine/assets/textures/LookAtModifier.ktx2.meta b/engine/assets/textures/LookAtModifier.ktx2.meta new file mode 100644 index 000000000..a774ab9d1 --- /dev/null +++ b/engine/assets/textures/LookAtModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "rOk2r1HpGNo" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/MeshItem.ktx2 b/engine/assets/textures/MeshItem.ktx2 new file mode 100644 index 000000000..65845aadc Binary files /dev/null and b/engine/assets/textures/MeshItem.ktx2 differ diff --git a/engine/assets/textures/MeshItem.ktx2.meta b/engine/assets/textures/MeshItem.ktx2.meta new file mode 100644 index 000000000..4488ef8d3 --- /dev/null +++ b/engine/assets/textures/MeshItem.ktx2.meta @@ -0,0 +1,4 @@ +uid = "CsY_7g8Zf70" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/MeshLibraryEditor.ktx2 b/engine/assets/textures/MeshLibraryEditor.ktx2 new file mode 100644 index 000000000..9bb1634d0 Binary files /dev/null and b/engine/assets/textures/MeshLibraryEditor.ktx2 differ diff --git a/engine/assets/textures/MeshLibraryEditor.ktx2.meta b/engine/assets/textures/MeshLibraryEditor.ktx2.meta new file mode 100644 index 000000000..013531ec7 --- /dev/null +++ b/engine/assets/textures/MeshLibraryEditor.ktx2.meta @@ -0,0 +1,4 @@ +uid = "_c7-UAGW700" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/MeshTexture.ktx2 b/engine/assets/textures/MeshTexture.ktx2 new file mode 100644 index 000000000..15b64132b Binary files /dev/null and b/engine/assets/textures/MeshTexture.ktx2 differ diff --git a/engine/assets/textures/MeshTexture.ktx2.meta b/engine/assets/textures/MeshTexture.ktx2.meta new file mode 100644 index 000000000..dc930eb9b --- /dev/null +++ b/engine/assets/textures/MeshTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "OwDLg1yieo0" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/MultiMesh.ktx2 b/engine/assets/textures/MultiMesh.ktx2 new file mode 100644 index 000000000..17fb57a26 Binary files /dev/null and b/engine/assets/textures/MultiMesh.ktx2 differ diff --git a/engine/assets/textures/MultiMesh.ktx2.meta b/engine/assets/textures/MultiMesh.ktx2.meta new file mode 100644 index 000000000..9597607db --- /dev/null +++ b/engine/assets/textures/MultiMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "jiQWvqF2xCE" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationAgent.ktx2 b/engine/assets/textures/NavigationAgent.ktx2 new file mode 100644 index 000000000..1dddcce64 Binary files /dev/null and b/engine/assets/textures/NavigationAgent.ktx2 differ diff --git a/engine/assets/textures/NavigationAgent.ktx2.meta b/engine/assets/textures/NavigationAgent.ktx2.meta new file mode 100644 index 000000000..4631eefd6 --- /dev/null +++ b/engine/assets/textures/NavigationAgent.ktx2.meta @@ -0,0 +1,4 @@ +uid = "7bse7XfYM3o" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationLink.ktx2 b/engine/assets/textures/NavigationLink.ktx2 new file mode 100644 index 000000000..6fd04224f Binary files /dev/null and b/engine/assets/textures/NavigationLink.ktx2 differ diff --git a/engine/assets/textures/NavigationLink.ktx2.meta b/engine/assets/textures/NavigationLink.ktx2.meta new file mode 100644 index 000000000..8e41d38e5 --- /dev/null +++ b/engine/assets/textures/NavigationLink.ktx2.meta @@ -0,0 +1,4 @@ +uid = "jncq0Et35Hc" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationMesh.ktx2 b/engine/assets/textures/NavigationMesh.ktx2 new file mode 100644 index 000000000..5a293dd28 Binary files /dev/null and b/engine/assets/textures/NavigationMesh.ktx2 differ diff --git a/engine/assets/textures/NavigationMesh.ktx2.meta b/engine/assets/textures/NavigationMesh.ktx2.meta new file mode 100644 index 000000000..f5e724a42 --- /dev/null +++ b/engine/assets/textures/NavigationMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Ztp8pJrkPHQ" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationObstacle.ktx2 b/engine/assets/textures/NavigationObstacle.ktx2 new file mode 100644 index 000000000..621d53460 Binary files /dev/null and b/engine/assets/textures/NavigationObstacle.ktx2 differ diff --git a/engine/assets/textures/NavigationObstacle.ktx2.meta b/engine/assets/textures/NavigationObstacle.ktx2.meta new file mode 100644 index 000000000..b90bb6d5b --- /dev/null +++ b/engine/assets/textures/NavigationObstacle.ktx2.meta @@ -0,0 +1,4 @@ +uid = "3fYmweDQzS8" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationPolygon.ktx2 b/engine/assets/textures/NavigationPolygon.ktx2 new file mode 100644 index 000000000..710734c0e Binary files /dev/null and b/engine/assets/textures/NavigationPolygon.ktx2 differ diff --git a/engine/assets/textures/NavigationPolygon.ktx2.meta b/engine/assets/textures/NavigationPolygon.ktx2.meta new file mode 100644 index 000000000..b50af7dfd --- /dev/null +++ b/engine/assets/textures/NavigationPolygon.ktx2.meta @@ -0,0 +1,4 @@ +uid = "gujMWq1JvqY" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NavigationRegion.ktx2 b/engine/assets/textures/NavigationRegion.ktx2 new file mode 100644 index 000000000..f49b7113f Binary files /dev/null and b/engine/assets/textures/NavigationRegion.ktx2 differ diff --git a/engine/assets/textures/NavigationRegion.ktx2.meta b/engine/assets/textures/NavigationRegion.ktx2.meta new file mode 100644 index 000000000..35327a6de --- /dev/null +++ b/engine/assets/textures/NavigationRegion.ktx2.meta @@ -0,0 +1,4 @@ +uid = "P7kvVeF-EIA" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/Node.ktx2 b/engine/assets/textures/Node.ktx2 new file mode 100644 index 000000000..280a1afe3 Binary files /dev/null and b/engine/assets/textures/Node.ktx2 differ diff --git a/engine/assets/textures/Node.ktx2.meta b/engine/assets/textures/Node.ktx2.meta new file mode 100644 index 000000000..780375cb2 --- /dev/null +++ b/engine/assets/textures/Node.ktx2.meta @@ -0,0 +1,4 @@ +uid = "juHpymr0V0Q" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/NodeInfo.ktx2 b/engine/assets/textures/NodeInfo.ktx2 new file mode 100644 index 000000000..85d27f2a0 Binary files /dev/null and b/engine/assets/textures/NodeInfo.ktx2 differ diff --git a/engine/assets/textures/NodeInfo.ktx2.meta b/engine/assets/textures/NodeInfo.ktx2.meta new file mode 100644 index 000000000..bd3d1a713 --- /dev/null +++ b/engine/assets/textures/NodeInfo.ktx2.meta @@ -0,0 +1,4 @@ +uid = "w0pIgCjt6uU" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/ORMMaterial.ktx2 b/engine/assets/textures/ORMMaterial.ktx2 new file mode 100644 index 000000000..eb7ba24d7 Binary files /dev/null and b/engine/assets/textures/ORMMaterial.ktx2 differ diff --git a/engine/assets/textures/ORMMaterial.ktx2.meta b/engine/assets/textures/ORMMaterial.ktx2.meta new file mode 100644 index 000000000..eab503fbb --- /dev/null +++ b/engine/assets/textures/ORMMaterial.ktx2.meta @@ -0,0 +1,4 @@ +uid = "wofk2zWIOsk" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/Occluder.ktx2 b/engine/assets/textures/Occluder.ktx2 new file mode 100644 index 000000000..925259427 Binary files /dev/null and b/engine/assets/textures/Occluder.ktx2 differ diff --git a/engine/assets/textures/Occluder.ktx2.meta b/engine/assets/textures/Occluder.ktx2.meta new file mode 100644 index 000000000..c879397a7 --- /dev/null +++ b/engine/assets/textures/Occluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "vO8VWZBejYA" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/OccluderInstance.ktx2 b/engine/assets/textures/OccluderInstance.ktx2 new file mode 100644 index 000000000..4b4e7a352 Binary files /dev/null and b/engine/assets/textures/OccluderInstance.ktx2 differ diff --git a/engine/assets/textures/OccluderInstance.ktx2.meta b/engine/assets/textures/OccluderInstance.ktx2.meta new file mode 100644 index 000000000..4e77b6ca6 --- /dev/null +++ b/engine/assets/textures/OccluderInstance.ktx2.meta @@ -0,0 +1,4 @@ +uid = "TUCzHi3XZQM" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/OmniLight.ktx2 b/engine/assets/textures/OmniLight.ktx2 index b6c3676f5..770d2d7d8 100644 Binary files a/engine/assets/textures/OmniLight.ktx2 and b/engine/assets/textures/OmniLight.ktx2 differ diff --git a/engine/assets/textures/Output.ktx2 b/engine/assets/textures/Output.ktx2 new file mode 100644 index 000000000..7d3cf35eb Binary files /dev/null and b/engine/assets/textures/Output.ktx2 differ diff --git a/engine/assets/textures/Output.ktx2.meta b/engine/assets/textures/Output.ktx2.meta new file mode 100644 index 000000000..cb8def141 --- /dev/null +++ b/engine/assets/textures/Output.ktx2.meta @@ -0,0 +1,4 @@ +uid = "FvkHw-Hi2rs" +type = "texture" +created_at = "2026-10-31T10:28:05.0000000Z" +modified_at = "2026-10-31T10:28:05.0000000Z" diff --git a/engine/assets/textures/ParticleProcessMaterial.ktx2 b/engine/assets/textures/ParticleProcessMaterial.ktx2 new file mode 100644 index 000000000..da844ad0f Binary files /dev/null and b/engine/assets/textures/ParticleProcessMaterial.ktx2 differ diff --git a/engine/assets/textures/ParticleProcessMaterial.ktx2.meta b/engine/assets/textures/ParticleProcessMaterial.ktx2.meta new file mode 100644 index 000000000..fc2f77ad0 --- /dev/null +++ b/engine/assets/textures/ParticleProcessMaterial.ktx2.meta @@ -0,0 +1,4 @@ +uid = "lU9uSB7uhCk" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/Path.ktx2 b/engine/assets/textures/Path.ktx2 new file mode 100644 index 000000000..cc09f64e7 Binary files /dev/null and b/engine/assets/textures/Path.ktx2 differ diff --git a/engine/assets/textures/Path.ktx2.meta b/engine/assets/textures/Path.ktx2.meta new file mode 100644 index 000000000..b309dfefa --- /dev/null +++ b/engine/assets/textures/Path.ktx2.meta @@ -0,0 +1,4 @@ +uid = "WWcYS4HSrv8" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PathFollow.ktx2 b/engine/assets/textures/PathFollow.ktx2 new file mode 100644 index 000000000..1205dbe75 Binary files /dev/null and b/engine/assets/textures/PathFollow.ktx2 differ diff --git a/engine/assets/textures/PathFollow.ktx2.meta b/engine/assets/textures/PathFollow.ktx2.meta new file mode 100644 index 000000000..600da7d99 --- /dev/null +++ b/engine/assets/textures/PathFollow.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Z9P51cph1q4" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PhysicalBone.ktx2 b/engine/assets/textures/PhysicalBone.ktx2 new file mode 100644 index 000000000..ad4ff7a53 Binary files /dev/null and b/engine/assets/textures/PhysicalBone.ktx2 differ diff --git a/engine/assets/textures/PhysicalBone.ktx2.meta b/engine/assets/textures/PhysicalBone.ktx2.meta new file mode 100644 index 000000000..4dbb37413 --- /dev/null +++ b/engine/assets/textures/PhysicalBone.ktx2.meta @@ -0,0 +1,4 @@ +uid = "_UNEZAAvFOA" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PhysicalBoneSimulator.ktx2 b/engine/assets/textures/PhysicalBoneSimulator.ktx2 new file mode 100644 index 000000000..0e9699b5b Binary files /dev/null and b/engine/assets/textures/PhysicalBoneSimulator.ktx2 differ diff --git a/engine/assets/textures/PhysicalBoneSimulator.ktx2.meta b/engine/assets/textures/PhysicalBoneSimulator.ktx2.meta new file mode 100644 index 000000000..7c83f2356 --- /dev/null +++ b/engine/assets/textures/PhysicalBoneSimulator.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Lzp1ll2Kl2Y" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PhysicsBody.ktx2 b/engine/assets/textures/PhysicsBody.ktx2 new file mode 100644 index 000000000..c4d7de2c6 Binary files /dev/null and b/engine/assets/textures/PhysicsBody.ktx2 differ diff --git a/engine/assets/textures/PhysicsBody.ktx2.meta b/engine/assets/textures/PhysicsBody.ktx2.meta new file mode 100644 index 000000000..a634527c8 --- /dev/null +++ b/engine/assets/textures/PhysicsBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "9H-l9GtDOPc" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PhysicsMaterial.ktx2 b/engine/assets/textures/PhysicsMaterial.ktx2 new file mode 100644 index 000000000..24c9289f5 Binary files /dev/null and b/engine/assets/textures/PhysicsMaterial.ktx2 differ diff --git a/engine/assets/textures/PhysicsMaterial.ktx2.meta b/engine/assets/textures/PhysicsMaterial.ktx2.meta new file mode 100644 index 000000000..6f79d2ca1 --- /dev/null +++ b/engine/assets/textures/PhysicsMaterial.ktx2.meta @@ -0,0 +1,4 @@ +uid = "xKXLtRd3bPo" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PinJoint.ktx2 b/engine/assets/textures/PinJoint.ktx2 new file mode 100644 index 000000000..06c46a17a Binary files /dev/null and b/engine/assets/textures/PinJoint.ktx2 differ diff --git a/engine/assets/textures/PinJoint.ktx2.meta b/engine/assets/textures/PinJoint.ktx2.meta new file mode 100644 index 000000000..917e6bb12 --- /dev/null +++ b/engine/assets/textures/PinJoint.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Pq9gEmMup3M" +type = "texture" +created_at = "2026-10-31T10:28:37.0000000Z" +modified_at = "2026-10-31T10:28:37.0000000Z" diff --git a/engine/assets/textures/PlaceholderMesh.ktx2 b/engine/assets/textures/PlaceholderMesh.ktx2 new file mode 100644 index 000000000..a5132db85 Binary files /dev/null and b/engine/assets/textures/PlaceholderMesh.ktx2 differ diff --git a/engine/assets/textures/PlaceholderMesh.ktx2.meta b/engine/assets/textures/PlaceholderMesh.ktx2.meta new file mode 100644 index 000000000..47b94a4db --- /dev/null +++ b/engine/assets/textures/PlaceholderMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "mWY9tjJ_8ZQ" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PlaceholderTexture.ktx2 b/engine/assets/textures/PlaceholderTexture.ktx2 new file mode 100644 index 000000000..d41bc1b2b Binary files /dev/null and b/engine/assets/textures/PlaceholderTexture.ktx2 differ diff --git a/engine/assets/textures/PlaceholderTexture.ktx2.meta b/engine/assets/textures/PlaceholderTexture.ktx2.meta new file mode 100644 index 000000000..0570557f2 --- /dev/null +++ b/engine/assets/textures/PlaceholderTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "8laCaBV8k4w" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PointLight.ktx2 b/engine/assets/textures/PointLight.ktx2 index b6c3676f5..770d2d7d8 100644 Binary files a/engine/assets/textures/PointLight.ktx2 and b/engine/assets/textures/PointLight.ktx2 differ diff --git a/engine/assets/textures/PointMesh.ktx2 b/engine/assets/textures/PointMesh.ktx2 new file mode 100644 index 000000000..c09c2eb63 Binary files /dev/null and b/engine/assets/textures/PointMesh.ktx2 differ diff --git a/engine/assets/textures/PointMesh.ktx2.meta b/engine/assets/textures/PointMesh.ktx2.meta new file mode 100644 index 000000000..9e48e0bc5 --- /dev/null +++ b/engine/assets/textures/PointMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "pKRqg5_huBk" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PolygonDock.ktx2 b/engine/assets/textures/PolygonDock.ktx2 new file mode 100644 index 000000000..9b62a7805 Binary files /dev/null and b/engine/assets/textures/PolygonDock.ktx2 differ diff --git a/engine/assets/textures/PolygonDock.ktx2.meta b/engine/assets/textures/PolygonDock.ktx2.meta new file mode 100644 index 000000000..7beff9466 --- /dev/null +++ b/engine/assets/textures/PolygonDock.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Wr4Np00DVDc" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PolygonOccluder.ktx2 b/engine/assets/textures/PolygonOccluder.ktx2 new file mode 100644 index 000000000..e457049d5 Binary files /dev/null and b/engine/assets/textures/PolygonOccluder.ktx2 differ diff --git a/engine/assets/textures/PolygonOccluder.ktx2.meta b/engine/assets/textures/PolygonOccluder.ktx2.meta new file mode 100644 index 000000000..9d34f8369 --- /dev/null +++ b/engine/assets/textures/PolygonOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "5vmR7oNTdZ0" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PolygonPathFinder.ktx2 b/engine/assets/textures/PolygonPathFinder.ktx2 new file mode 100644 index 000000000..2073ea4cc Binary files /dev/null and b/engine/assets/textures/PolygonPathFinder.ktx2 differ diff --git a/engine/assets/textures/PolygonPathFinder.ktx2.meta b/engine/assets/textures/PolygonPathFinder.ktx2.meta new file mode 100644 index 000000000..92832981e --- /dev/null +++ b/engine/assets/textures/PolygonPathFinder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "izAGULy5QN8" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PortableCompressedTexture.ktx2 b/engine/assets/textures/PortableCompressedTexture.ktx2 new file mode 100644 index 000000000..4f4526b5f Binary files /dev/null and b/engine/assets/textures/PortableCompressedTexture.ktx2 differ diff --git a/engine/assets/textures/PortableCompressedTexture.ktx2.meta b/engine/assets/textures/PortableCompressedTexture.ktx2.meta new file mode 100644 index 000000000..d497858db --- /dev/null +++ b/engine/assets/textures/PortableCompressedTexture.ktx2.meta @@ -0,0 +1,4 @@ +uid = "X213HLrpZCQ" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/PrismMesh.ktx2 b/engine/assets/textures/PrismMesh.ktx2 new file mode 100644 index 000000000..b8d165226 Binary files /dev/null and b/engine/assets/textures/PrismMesh.ktx2 differ diff --git a/engine/assets/textures/PrismMesh.ktx2.meta b/engine/assets/textures/PrismMesh.ktx2.meta new file mode 100644 index 000000000..16c66086c --- /dev/null +++ b/engine/assets/textures/PrismMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "nXXU6VCawNU" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/ProceduralSkyMaterial.ktx2 b/engine/assets/textures/ProceduralSkyMaterial.ktx2 new file mode 100644 index 000000000..3cd46bf6e Binary files /dev/null and b/engine/assets/textures/ProceduralSkyMaterial.ktx2 differ diff --git a/engine/assets/textures/ProceduralSkyMaterial.ktx2.meta b/engine/assets/textures/ProceduralSkyMaterial.ktx2.meta new file mode 100644 index 000000000..e0000c4f8 --- /dev/null +++ b/engine/assets/textures/ProceduralSkyMaterial.ktx2.meta @@ -0,0 +1,4 @@ +uid = "wprpigTRAg8" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/ProgressBar.ktx2 b/engine/assets/textures/ProgressBar.ktx2 new file mode 100644 index 000000000..3b6fe75c3 Binary files /dev/null and b/engine/assets/textures/ProgressBar.ktx2 differ diff --git a/engine/assets/textures/ProgressBar.ktx2.meta b/engine/assets/textures/ProgressBar.ktx2.meta new file mode 100644 index 000000000..279679f31 --- /dev/null +++ b/engine/assets/textures/ProgressBar.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Tx8zo_PH5S4" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/QuadMesh.ktx2 b/engine/assets/textures/QuadMesh.ktx2 new file mode 100644 index 000000000..f7bcaee47 Binary files /dev/null and b/engine/assets/textures/QuadMesh.ktx2 differ diff --git a/engine/assets/textures/QuadMesh.ktx2.meta b/engine/assets/textures/QuadMesh.ktx2.meta new file mode 100644 index 000000000..38fdd7cfe --- /dev/null +++ b/engine/assets/textures/QuadMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "kJfGf42k0V8" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/QuadOccluder.ktx2 b/engine/assets/textures/QuadOccluder.ktx2 new file mode 100644 index 000000000..68a216e74 Binary files /dev/null and b/engine/assets/textures/QuadOccluder.ktx2 differ diff --git a/engine/assets/textures/QuadOccluder.ktx2.meta b/engine/assets/textures/QuadOccluder.ktx2.meta new file mode 100644 index 000000000..f2e891100 --- /dev/null +++ b/engine/assets/textures/QuadOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Tbp8gP787TE" +type = "texture" +created_at = "2026-10-31T10:28:06.0000000Z" +modified_at = "2026-10-31T10:28:06.0000000Z" diff --git a/engine/assets/textures/RandomNumberGenerator.ktx2 b/engine/assets/textures/RandomNumberGenerator.ktx2 new file mode 100644 index 000000000..a9a87d0ea Binary files /dev/null and b/engine/assets/textures/RandomNumberGenerator.ktx2 differ diff --git a/engine/assets/textures/RandomNumberGenerator.ktx2.meta b/engine/assets/textures/RandomNumberGenerator.ktx2.meta new file mode 100644 index 000000000..090fc1d49 --- /dev/null +++ b/engine/assets/textures/RandomNumberGenerator.ktx2.meta @@ -0,0 +1,4 @@ +uid = "bz2l_oASMWQ" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/Rectangle.ktx2 b/engine/assets/textures/Rectangle.ktx2 new file mode 100644 index 000000000..9752db989 Binary files /dev/null and b/engine/assets/textures/Rectangle.ktx2 differ diff --git a/engine/assets/textures/Rectangle.ktx2.meta b/engine/assets/textures/Rectangle.ktx2.meta new file mode 100644 index 000000000..21ee93875 --- /dev/null +++ b/engine/assets/textures/Rectangle.ktx2.meta @@ -0,0 +1,4 @@ +uid = "R2yJ6FCNFk0" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/ReferenceRect.ktx2 b/engine/assets/textures/ReferenceRect.ktx2 new file mode 100644 index 000000000..5c6725115 Binary files /dev/null and b/engine/assets/textures/ReferenceRect.ktx2 differ diff --git a/engine/assets/textures/ReferenceRect.ktx2.meta b/engine/assets/textures/ReferenceRect.ktx2.meta new file mode 100644 index 000000000..0bf496c70 --- /dev/null +++ b/engine/assets/textures/ReferenceRect.ktx2.meta @@ -0,0 +1,4 @@ +uid = "AAOzYBqPk9c" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/ReflectionProbe.ktx2 b/engine/assets/textures/ReflectionProbe.ktx2 new file mode 100644 index 000000000..88e3773db Binary files /dev/null and b/engine/assets/textures/ReflectionProbe.ktx2 differ diff --git a/engine/assets/textures/ReflectionProbe.ktx2.meta b/engine/assets/textures/ReflectionProbe.ktx2.meta new file mode 100644 index 000000000..51fd5668f --- /dev/null +++ b/engine/assets/textures/ReflectionProbe.ktx2.meta @@ -0,0 +1,4 @@ +uid = "LEEtiP-PBx4" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/RemoteTransform.ktx2 b/engine/assets/textures/RemoteTransform.ktx2 new file mode 100644 index 000000000..6f760d39c Binary files /dev/null and b/engine/assets/textures/RemoteTransform.ktx2 differ diff --git a/engine/assets/textures/RemoteTransform.ktx2.meta b/engine/assets/textures/RemoteTransform.ktx2.meta new file mode 100644 index 000000000..1d8b71e43 --- /dev/null +++ b/engine/assets/textures/RemoteTransform.ktx2.meta @@ -0,0 +1,4 @@ +uid = "9jciGxCyJR0" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/RigidBody.ktx2 b/engine/assets/textures/RigidBody.ktx2 new file mode 100644 index 000000000..60e643b4b Binary files /dev/null and b/engine/assets/textures/RigidBody.ktx2 differ diff --git a/engine/assets/textures/RigidBody.ktx2.meta b/engine/assets/textures/RigidBody.ktx2.meta new file mode 100644 index 000000000..58cb17cfe --- /dev/null +++ b/engine/assets/textures/RigidBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "WyakMgP_XKw" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/RootMotionView.ktx2 b/engine/assets/textures/RootMotionView.ktx2 new file mode 100644 index 000000000..3e1feb22a Binary files /dev/null and b/engine/assets/textures/RootMotionView.ktx2 differ diff --git a/engine/assets/textures/RootMotionView.ktx2.meta b/engine/assets/textures/RootMotionView.ktx2.meta new file mode 100644 index 000000000..11a262208 --- /dev/null +++ b/engine/assets/textures/RootMotionView.ktx2.meta @@ -0,0 +1,4 @@ +uid = "fTXyhyhs7uM" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SegmentShape.ktx2 b/engine/assets/textures/SegmentShape.ktx2 new file mode 100644 index 000000000..e6f4d5ccd Binary files /dev/null and b/engine/assets/textures/SegmentShape.ktx2 differ diff --git a/engine/assets/textures/SegmentShape.ktx2.meta b/engine/assets/textures/SegmentShape.ktx2.meta new file mode 100644 index 000000000..c54b9f0c8 --- /dev/null +++ b/engine/assets/textures/SegmentShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "i1Ni2dmfPQ4" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SeparationRayShape.ktx2 b/engine/assets/textures/SeparationRayShape.ktx2 new file mode 100644 index 000000000..8bdb9d1b9 Binary files /dev/null and b/engine/assets/textures/SeparationRayShape.ktx2 differ diff --git a/engine/assets/textures/SeparationRayShape.ktx2.meta b/engine/assets/textures/SeparationRayShape.ktx2.meta new file mode 100644 index 000000000..7ce04d164 --- /dev/null +++ b/engine/assets/textures/SeparationRayShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "CMLgYQ2uQ2c" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/ShaderDock.ktx2 b/engine/assets/textures/ShaderDock.ktx2 new file mode 100644 index 000000000..91a9ec37a Binary files /dev/null and b/engine/assets/textures/ShaderDock.ktx2 differ diff --git a/engine/assets/textures/ShaderDock.ktx2.meta b/engine/assets/textures/ShaderDock.ktx2.meta new file mode 100644 index 000000000..7c71dad15 --- /dev/null +++ b/engine/assets/textures/ShaderDock.ktx2.meta @@ -0,0 +1,4 @@ +uid = "2vMnFWIyHA4" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/Shape.ktx2 b/engine/assets/textures/Shape.ktx2 new file mode 100644 index 000000000..fa9376410 Binary files /dev/null and b/engine/assets/textures/Shape.ktx2 differ diff --git a/engine/assets/textures/Shape.ktx2.meta b/engine/assets/textures/Shape.ktx2.meta new file mode 100644 index 000000000..a4eeb8667 --- /dev/null +++ b/engine/assets/textures/Shape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "5XDV8ViJkkY" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/ShapeCast.ktx2 b/engine/assets/textures/ShapeCast.ktx2 new file mode 100644 index 000000000..3c3a6ddd9 Binary files /dev/null and b/engine/assets/textures/ShapeCast.ktx2 differ diff --git a/engine/assets/textures/ShapeCast.ktx2.meta b/engine/assets/textures/ShapeCast.ktx2.meta new file mode 100644 index 000000000..3fc3f0ea4 --- /dev/null +++ b/engine/assets/textures/ShapeCast.ktx2.meta @@ -0,0 +1,4 @@ +uid = "HtHCSUQddCE" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/Signals.ktx2 b/engine/assets/textures/Signals.ktx2 new file mode 100644 index 000000000..522897d9f Binary files /dev/null and b/engine/assets/textures/Signals.ktx2 differ diff --git a/engine/assets/textures/Signals.ktx2.meta b/engine/assets/textures/Signals.ktx2.meta new file mode 100644 index 000000000..d868ba407 --- /dev/null +++ b/engine/assets/textures/Signals.ktx2.meta @@ -0,0 +1,4 @@ +uid = "chwo6-ouk7g" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SignalsAndGroups.ktx2 b/engine/assets/textures/SignalsAndGroups.ktx2 new file mode 100644 index 000000000..1efc8b609 Binary files /dev/null and b/engine/assets/textures/SignalsAndGroups.ktx2 differ diff --git a/engine/assets/textures/SignalsAndGroups.ktx2.meta b/engine/assets/textures/SignalsAndGroups.ktx2.meta new file mode 100644 index 000000000..a7edf9fe7 --- /dev/null +++ b/engine/assets/textures/SignalsAndGroups.ktx2.meta @@ -0,0 +1,4 @@ +uid = "DQaXX69160U" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SkeletonModifier.ktx2 b/engine/assets/textures/SkeletonModifier.ktx2 new file mode 100644 index 000000000..6977f2813 Binary files /dev/null and b/engine/assets/textures/SkeletonModifier.ktx2 differ diff --git a/engine/assets/textures/SkeletonModifier.ktx2.meta b/engine/assets/textures/SkeletonModifier.ktx2.meta new file mode 100644 index 000000000..c068d3447 --- /dev/null +++ b/engine/assets/textures/SkeletonModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ajhA73Q7tz8" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SkeletonPreview.ktx2 b/engine/assets/textures/SkeletonPreview.ktx2 new file mode 100644 index 000000000..fb00f72c9 Binary files /dev/null and b/engine/assets/textures/SkeletonPreview.ktx2 differ diff --git a/engine/assets/textures/SkeletonPreview.ktx2.meta b/engine/assets/textures/SkeletonPreview.ktx2.meta new file mode 100644 index 000000000..6aa8db7ff --- /dev/null +++ b/engine/assets/textures/SkeletonPreview.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Ytt2mr3xHH0" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/Sky.ktx2 b/engine/assets/textures/Sky.ktx2 new file mode 100644 index 000000000..6481ef915 Binary files /dev/null and b/engine/assets/textures/Sky.ktx2 differ diff --git a/engine/assets/textures/Sky.ktx2.meta b/engine/assets/textures/Sky.ktx2.meta new file mode 100644 index 000000000..7c1ed5a58 --- /dev/null +++ b/engine/assets/textures/Sky.ktx2.meta @@ -0,0 +1,4 @@ +uid = "kSnD9eAASnY" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SliderJoint.ktx2 b/engine/assets/textures/SliderJoint.ktx2 new file mode 100644 index 000000000..2778c672e Binary files /dev/null and b/engine/assets/textures/SliderJoint.ktx2 differ diff --git a/engine/assets/textures/SliderJoint.ktx2.meta b/engine/assets/textures/SliderJoint.ktx2.meta new file mode 100644 index 000000000..03f212656 --- /dev/null +++ b/engine/assets/textures/SliderJoint.ktx2.meta @@ -0,0 +1,4 @@ +uid = "PaccGDnrwaE" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/Slot.ktx2 b/engine/assets/textures/Slot.ktx2 new file mode 100644 index 000000000..d8f3d993e Binary files /dev/null and b/engine/assets/textures/Slot.ktx2 differ diff --git a/engine/assets/textures/Slot.ktx2.meta b/engine/assets/textures/Slot.ktx2.meta new file mode 100644 index 000000000..6e795ca43 --- /dev/null +++ b/engine/assets/textures/Slot.ktx2.meta @@ -0,0 +1,4 @@ +uid = "IJMx-ST8EOY" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SoftBody.ktx2 b/engine/assets/textures/SoftBody.ktx2 new file mode 100644 index 000000000..ffefb4b05 Binary files /dev/null and b/engine/assets/textures/SoftBody.ktx2 differ diff --git a/engine/assets/textures/SoftBody.ktx2.meta b/engine/assets/textures/SoftBody.ktx2.meta new file mode 100644 index 000000000..091539d6d --- /dev/null +++ b/engine/assets/textures/SoftBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "OZ3jjBqnqZQ" +type = "texture" +created_at = "2026-10-31T10:28:07.0000000Z" +modified_at = "2026-10-31T10:28:07.0000000Z" diff --git a/engine/assets/textures/SphereMesh.ktx2 b/engine/assets/textures/SphereMesh.ktx2 new file mode 100644 index 000000000..d90f6caa7 Binary files /dev/null and b/engine/assets/textures/SphereMesh.ktx2 differ diff --git a/engine/assets/textures/SphereMesh.ktx2.meta b/engine/assets/textures/SphereMesh.ktx2.meta new file mode 100644 index 000000000..edcf2b1d9 --- /dev/null +++ b/engine/assets/textures/SphereMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "d6eSIgAPXP4" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SphereOccluder.ktx2 b/engine/assets/textures/SphereOccluder.ktx2 new file mode 100644 index 000000000..4b4e7a352 Binary files /dev/null and b/engine/assets/textures/SphereOccluder.ktx2 differ diff --git a/engine/assets/textures/SphereOccluder.ktx2.meta b/engine/assets/textures/SphereOccluder.ktx2.meta new file mode 100644 index 000000000..8af37baa5 --- /dev/null +++ b/engine/assets/textures/SphereOccluder.ktx2.meta @@ -0,0 +1,4 @@ +uid = "_SwWTqoQQxc" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SphereShape.ktx2 b/engine/assets/textures/SphereShape.ktx2 new file mode 100644 index 000000000..badd46ee5 Binary files /dev/null and b/engine/assets/textures/SphereShape.ktx2 differ diff --git a/engine/assets/textures/SphereShape.ktx2.meta b/engine/assets/textures/SphereShape.ktx2.meta new file mode 100644 index 000000000..9ff6fa0b5 --- /dev/null +++ b/engine/assets/textures/SphereShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "CP0b1D4CxEM" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SplineIK.ktx2 b/engine/assets/textures/SplineIK.ktx2 new file mode 100644 index 000000000..9b9c39476 Binary files /dev/null and b/engine/assets/textures/SplineIK.ktx2 differ diff --git a/engine/assets/textures/SplineIK.ktx2.meta b/engine/assets/textures/SplineIK.ktx2.meta new file mode 100644 index 000000000..b42fb4a68 --- /dev/null +++ b/engine/assets/textures/SplineIK.ktx2.meta @@ -0,0 +1,4 @@ +uid = "7w8HaeUbqc8" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SplitContainer.ktx2 b/engine/assets/textures/SplitContainer.ktx2 new file mode 100644 index 000000000..6a77f6148 Binary files /dev/null and b/engine/assets/textures/SplitContainer.ktx2 differ diff --git a/engine/assets/textures/SplitContainer.ktx2.meta b/engine/assets/textures/SplitContainer.ktx2.meta new file mode 100644 index 000000000..3696baa70 --- /dev/null +++ b/engine/assets/textures/SplitContainer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Dqpha1vcgCs" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SpotLight.ktx2 b/engine/assets/textures/SpotLight.ktx2 index 5a630c25a..7753137b7 100644 Binary files a/engine/assets/textures/SpotLight.ktx2 and b/engine/assets/textures/SpotLight.ktx2 differ diff --git a/engine/assets/textures/SpringArm.ktx2 b/engine/assets/textures/SpringArm.ktx2 new file mode 100644 index 000000000..6858874a8 Binary files /dev/null and b/engine/assets/textures/SpringArm.ktx2 differ diff --git a/engine/assets/textures/SpringArm.ktx2.meta b/engine/assets/textures/SpringArm.ktx2.meta new file mode 100644 index 000000000..5deb0f68f --- /dev/null +++ b/engine/assets/textures/SpringArm.ktx2.meta @@ -0,0 +1,4 @@ +uid = "o8-XMudU7jU" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/Sprite.ktx2 b/engine/assets/textures/Sprite.ktx2 new file mode 100644 index 000000000..27e4425a1 Binary files /dev/null and b/engine/assets/textures/Sprite.ktx2 differ diff --git a/engine/assets/textures/Sprite.ktx2.meta b/engine/assets/textures/Sprite.ktx2.meta new file mode 100644 index 000000000..31924b054 --- /dev/null +++ b/engine/assets/textures/Sprite.ktx2.meta @@ -0,0 +1,4 @@ +uid = "aBFYA_7QiW0" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SpriteFrames.ktx2 b/engine/assets/textures/SpriteFrames.ktx2 new file mode 100644 index 000000000..e7ed0b1f6 Binary files /dev/null and b/engine/assets/textures/SpriteFrames.ktx2 differ diff --git a/engine/assets/textures/SpriteFrames.ktx2.meta b/engine/assets/textures/SpriteFrames.ktx2.meta new file mode 100644 index 000000000..5359f3740 --- /dev/null +++ b/engine/assets/textures/SpriteFrames.ktx2.meta @@ -0,0 +1,4 @@ +uid = "8udhHt-eR0E" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/SpriteSheet.ktx2 b/engine/assets/textures/SpriteSheet.ktx2 new file mode 100644 index 000000000..2135dacc4 Binary files /dev/null and b/engine/assets/textures/SpriteSheet.ktx2 differ diff --git a/engine/assets/textures/SpriteSheet.ktx2.meta b/engine/assets/textures/SpriteSheet.ktx2.meta new file mode 100644 index 000000000..6debeff6d --- /dev/null +++ b/engine/assets/textures/SpriteSheet.ktx2.meta @@ -0,0 +1,4 @@ +uid = "ZsabqOjBhqc" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/StyleBoxFlat.ktx2 b/engine/assets/textures/StyleBoxFlat.ktx2 new file mode 100644 index 000000000..be80376b6 Binary files /dev/null and b/engine/assets/textures/StyleBoxFlat.ktx2 differ diff --git a/engine/assets/textures/StyleBoxFlat.ktx2.meta b/engine/assets/textures/StyleBoxFlat.ktx2.meta new file mode 100644 index 000000000..501023191 --- /dev/null +++ b/engine/assets/textures/StyleBoxFlat.ktx2.meta @@ -0,0 +1,4 @@ +uid = "YIswp4jxn48" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/TerrainMatchCorners.ktx2 b/engine/assets/textures/TerrainMatchCorners.ktx2 new file mode 100644 index 000000000..d737d1796 Binary files /dev/null and b/engine/assets/textures/TerrainMatchCorners.ktx2 differ diff --git a/engine/assets/textures/TerrainMatchCorners.ktx2.meta b/engine/assets/textures/TerrainMatchCorners.ktx2.meta new file mode 100644 index 000000000..d34bee762 --- /dev/null +++ b/engine/assets/textures/TerrainMatchCorners.ktx2.meta @@ -0,0 +1,4 @@ +uid = "9CHmsPqr6H4" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/TerrainMatchCornersAndSides.ktx2 b/engine/assets/textures/TerrainMatchCornersAndSides.ktx2 new file mode 100644 index 000000000..0105d20bd Binary files /dev/null and b/engine/assets/textures/TerrainMatchCornersAndSides.ktx2 differ diff --git a/engine/assets/textures/TerrainMatchCornersAndSides.ktx2.meta b/engine/assets/textures/TerrainMatchCornersAndSides.ktx2.meta new file mode 100644 index 000000000..ebe79d155 --- /dev/null +++ b/engine/assets/textures/TerrainMatchCornersAndSides.ktx2.meta @@ -0,0 +1,4 @@ +uid = "rjDxBkl1NaM" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/TerrainMatchSides.ktx2 b/engine/assets/textures/TerrainMatchSides.ktx2 new file mode 100644 index 000000000..c138e0cb5 Binary files /dev/null and b/engine/assets/textures/TerrainMatchSides.ktx2 differ diff --git a/engine/assets/textures/TerrainMatchSides.ktx2.meta b/engine/assets/textures/TerrainMatchSides.ktx2.meta new file mode 100644 index 000000000..3d1442cd8 --- /dev/null +++ b/engine/assets/textures/TerrainMatchSides.ktx2.meta @@ -0,0 +1,4 @@ +uid = "VYDf8eSBgTw" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/TextureRect.ktx2 b/engine/assets/textures/TextureRect.ktx2 new file mode 100644 index 000000000..82ca70e79 Binary files /dev/null and b/engine/assets/textures/TextureRect.ktx2 differ diff --git a/engine/assets/textures/TextureRect.ktx2.meta b/engine/assets/textures/TextureRect.ktx2.meta new file mode 100644 index 000000000..47bfaf504 --- /dev/null +++ b/engine/assets/textures/TextureRect.ktx2.meta @@ -0,0 +1,4 @@ +uid = "4Ub0NDpGjXM" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/ThemeDock.ktx2 b/engine/assets/textures/ThemeDock.ktx2 new file mode 100644 index 000000000..50f5e290c Binary files /dev/null and b/engine/assets/textures/ThemeDock.ktx2 differ diff --git a/engine/assets/textures/ThemeDock.ktx2.meta b/engine/assets/textures/ThemeDock.ktx2.meta new file mode 100644 index 000000000..f1e8b0982 --- /dev/null +++ b/engine/assets/textures/ThemeDock.ktx2.meta @@ -0,0 +1,4 @@ +uid = "W1tS5EZTj0U" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/Timer.ktx2 b/engine/assets/textures/Timer.ktx2 new file mode 100644 index 000000000..217b43409 Binary files /dev/null and b/engine/assets/textures/Timer.ktx2 differ diff --git a/engine/assets/textures/Timer.ktx2.meta b/engine/assets/textures/Timer.ktx2.meta new file mode 100644 index 000000000..cf1abf61b --- /dev/null +++ b/engine/assets/textures/Timer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "4zXApvFIKAQ" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/ToolBoneSelect.ktx2 b/engine/assets/textures/ToolBoneSelect.ktx2 new file mode 100644 index 000000000..15bfd343f Binary files /dev/null and b/engine/assets/textures/ToolBoneSelect.ktx2 differ diff --git a/engine/assets/textures/ToolBoneSelect.ktx2.meta b/engine/assets/textures/ToolBoneSelect.ktx2.meta new file mode 100644 index 000000000..c1180f1b4 --- /dev/null +++ b/engine/assets/textures/ToolBoneSelect.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Gl6RFWcnIXU" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/ToolTriangle.ktx2 b/engine/assets/textures/ToolTriangle.ktx2 new file mode 100644 index 000000000..49e171cab Binary files /dev/null and b/engine/assets/textures/ToolTriangle.ktx2 differ diff --git a/engine/assets/textures/ToolTriangle.ktx2.meta b/engine/assets/textures/ToolTriangle.ktx2.meta new file mode 100644 index 000000000..de54d9a1c --- /dev/null +++ b/engine/assets/textures/ToolTriangle.ktx2.meta @@ -0,0 +1,4 @@ +uid = "uWk7ZeEnBGA" +type = "texture" +created_at = "2026-10-31T10:28:08.0000000Z" +modified_at = "2026-10-31T10:28:08.0000000Z" diff --git a/engine/assets/textures/TorusMesh.ktx2 b/engine/assets/textures/TorusMesh.ktx2 new file mode 100644 index 000000000..b61824e14 Binary files /dev/null and b/engine/assets/textures/TorusMesh.ktx2 differ diff --git a/engine/assets/textures/TorusMesh.ktx2.meta b/engine/assets/textures/TorusMesh.ktx2.meta new file mode 100644 index 000000000..988d9e7b9 --- /dev/null +++ b/engine/assets/textures/TorusMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "1hu1uNs8_z8" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/Trackball.ktx2 b/engine/assets/textures/Trackball.ktx2 new file mode 100644 index 000000000..1f0a77945 Binary files /dev/null and b/engine/assets/textures/Trackball.ktx2 differ diff --git a/engine/assets/textures/Trackball.ktx2.meta b/engine/assets/textures/Trackball.ktx2.meta new file mode 100644 index 000000000..4379f5a9f --- /dev/null +++ b/engine/assets/textures/Trackball.ktx2.meta @@ -0,0 +1,4 @@ +uid = "PPUljBLKXaQ" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/TransitionImmediate.ktx2 b/engine/assets/textures/TransitionImmediate.ktx2 new file mode 100644 index 000000000..46146eed6 Binary files /dev/null and b/engine/assets/textures/TransitionImmediate.ktx2 differ diff --git a/engine/assets/textures/TransitionImmediate.ktx2.meta b/engine/assets/textures/TransitionImmediate.ktx2.meta new file mode 100644 index 000000000..e2765da6b --- /dev/null +++ b/engine/assets/textures/TransitionImmediate.ktx2.meta @@ -0,0 +1,4 @@ +uid = "_scpgph5JFU" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/Translation.ktx2 b/engine/assets/textures/Translation.ktx2 new file mode 100644 index 000000000..1bbe615d0 Binary files /dev/null and b/engine/assets/textures/Translation.ktx2 differ diff --git a/engine/assets/textures/Translation.ktx2.meta b/engine/assets/textures/Translation.ktx2.meta new file mode 100644 index 000000000..7d9f2747b --- /dev/null +++ b/engine/assets/textures/Translation.ktx2.meta @@ -0,0 +1,4 @@ +uid = "kdmGwQxSVqU" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/TubeTrailMesh.ktx2 b/engine/assets/textures/TubeTrailMesh.ktx2 new file mode 100644 index 000000000..26aa4147b Binary files /dev/null and b/engine/assets/textures/TubeTrailMesh.ktx2 differ diff --git a/engine/assets/textures/TubeTrailMesh.ktx2.meta b/engine/assets/textures/TubeTrailMesh.ktx2.meta new file mode 100644 index 000000000..d81994a76 --- /dev/null +++ b/engine/assets/textures/TubeTrailMesh.ktx2.meta @@ -0,0 +1,4 @@ +uid = "IP4LbsZTCik" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/Tween.ktx2 b/engine/assets/textures/Tween.ktx2 new file mode 100644 index 000000000..628f28069 Binary files /dev/null and b/engine/assets/textures/Tween.ktx2 differ diff --git a/engine/assets/textures/Tween.ktx2.meta b/engine/assets/textures/Tween.ktx2.meta new file mode 100644 index 000000000..4fdfbb1ea --- /dev/null +++ b/engine/assets/textures/Tween.ktx2.meta @@ -0,0 +1,4 @@ +uid = "8IAi5DeA-3k" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/UseBlendDisable.ktx2 b/engine/assets/textures/UseBlendDisable.ktx2 new file mode 100644 index 000000000..3a01af835 Binary files /dev/null and b/engine/assets/textures/UseBlendDisable.ktx2 differ diff --git a/engine/assets/textures/UseBlendDisable.ktx2.meta b/engine/assets/textures/UseBlendDisable.ktx2.meta new file mode 100644 index 000000000..4c7bcc545 --- /dev/null +++ b/engine/assets/textures/UseBlendDisable.ktx2.meta @@ -0,0 +1,4 @@ +uid = "uBZoOHcTack" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/UseBlendEnable.ktx2 b/engine/assets/textures/UseBlendEnable.ktx2 new file mode 100644 index 000000000..9b29d66e5 Binary files /dev/null and b/engine/assets/textures/UseBlendEnable.ktx2 differ diff --git a/engine/assets/textures/UseBlendEnable.ktx2.meta b/engine/assets/textures/UseBlendEnable.ktx2.meta new file mode 100644 index 000000000..0989cea50 --- /dev/null +++ b/engine/assets/textures/UseBlendEnable.ktx2.meta @@ -0,0 +1,4 @@ +uid = "bCzymjGc6Ss" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VFlowContainer.ktx2 b/engine/assets/textures/VFlowContainer.ktx2 new file mode 100644 index 000000000..c8eb8e364 Binary files /dev/null and b/engine/assets/textures/VFlowContainer.ktx2 differ diff --git a/engine/assets/textures/VFlowContainer.ktx2.meta b/engine/assets/textures/VFlowContainer.ktx2.meta new file mode 100644 index 000000000..ffe868104 --- /dev/null +++ b/engine/assets/textures/VFlowContainer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "UQ6PjkY9XfQ" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VehicleBody.ktx2 b/engine/assets/textures/VehicleBody.ktx2 new file mode 100644 index 000000000..d89b95735 Binary files /dev/null and b/engine/assets/textures/VehicleBody.ktx2 differ diff --git a/engine/assets/textures/VehicleBody.ktx2.meta b/engine/assets/textures/VehicleBody.ktx2.meta new file mode 100644 index 000000000..8e56502c1 --- /dev/null +++ b/engine/assets/textures/VehicleBody.ktx2.meta @@ -0,0 +1,4 @@ +uid = "MjMTHFuf7eE" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VehicleWheel.ktx2 b/engine/assets/textures/VehicleWheel.ktx2 new file mode 100644 index 000000000..0672419ee Binary files /dev/null and b/engine/assets/textures/VehicleWheel.ktx2 differ diff --git a/engine/assets/textures/VehicleWheel.ktx2.meta b/engine/assets/textures/VehicleWheel.ktx2.meta new file mode 100644 index 000000000..4f7f24806 --- /dev/null +++ b/engine/assets/textures/VehicleWheel.ktx2.meta @@ -0,0 +1,4 @@ +uid = "yMyRl-qEanc" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VideoStream.ktx2 b/engine/assets/textures/VideoStream.ktx2 new file mode 100644 index 000000000..fd154b619 Binary files /dev/null and b/engine/assets/textures/VideoStream.ktx2 differ diff --git a/engine/assets/textures/VideoStream.ktx2.meta b/engine/assets/textures/VideoStream.ktx2.meta new file mode 100644 index 000000000..a075f5c56 --- /dev/null +++ b/engine/assets/textures/VideoStream.ktx2.meta @@ -0,0 +1,4 @@ +uid = "4LKe9BDqxlk" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VideoStreamPlayer.ktx2 b/engine/assets/textures/VideoStreamPlayer.ktx2 new file mode 100644 index 000000000..2e5be6961 Binary files /dev/null and b/engine/assets/textures/VideoStreamPlayer.ktx2 differ diff --git a/engine/assets/textures/VideoStreamPlayer.ktx2.meta b/engine/assets/textures/VideoStreamPlayer.ktx2.meta new file mode 100644 index 000000000..18b9c357b --- /dev/null +++ b/engine/assets/textures/VideoStreamPlayer.ktx2.meta @@ -0,0 +1,4 @@ +uid = "qgT_cHCYQ_Y" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VideoStreamTheora.ktx2 b/engine/assets/textures/VideoStreamTheora.ktx2 new file mode 100644 index 000000000..94de48f28 Binary files /dev/null and b/engine/assets/textures/VideoStreamTheora.ktx2 differ diff --git a/engine/assets/textures/VideoStreamTheora.ktx2.meta b/engine/assets/textures/VideoStreamTheora.ktx2.meta new file mode 100644 index 000000000..90af996f0 --- /dev/null +++ b/engine/assets/textures/VideoStreamTheora.ktx2.meta @@ -0,0 +1,4 @@ +uid = "HHVnJmf14eI" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisibleOnScreenEnabler.ktx2 b/engine/assets/textures/VisibleOnScreenEnabler.ktx2 new file mode 100644 index 000000000..3ac60c690 Binary files /dev/null and b/engine/assets/textures/VisibleOnScreenEnabler.ktx2 differ diff --git a/engine/assets/textures/VisibleOnScreenEnabler.ktx2.meta b/engine/assets/textures/VisibleOnScreenEnabler.ktx2.meta new file mode 100644 index 000000000..2f37d9a4d --- /dev/null +++ b/engine/assets/textures/VisibleOnScreenEnabler.ktx2.meta @@ -0,0 +1,4 @@ +uid = "qqem80h6BmY" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisibleOnScreenNotifier.ktx2 b/engine/assets/textures/VisibleOnScreenNotifier.ktx2 new file mode 100644 index 000000000..82791eabc Binary files /dev/null and b/engine/assets/textures/VisibleOnScreenNotifier.ktx2 differ diff --git a/engine/assets/textures/VisibleOnScreenNotifier.ktx2.meta b/engine/assets/textures/VisibleOnScreenNotifier.ktx2.meta new file mode 100644 index 000000000..b63ec4e34 --- /dev/null +++ b/engine/assets/textures/VisibleOnScreenNotifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "v475JcvdNUw" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2 b/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2 new file mode 100644 index 000000000..d01dcc036 Binary files /dev/null and b/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2 differ diff --git a/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2.meta b/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2.meta new file mode 100644 index 000000000..013c2a281 --- /dev/null +++ b/engine/assets/textures/VisualShaderGraphTextureUniform.ktx2.meta @@ -0,0 +1,4 @@ +uid = "NPJPGpPoTCo" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisualShaderNodeComment.ktx2 b/engine/assets/textures/VisualShaderNodeComment.ktx2 new file mode 100644 index 000000000..ab9d5460d Binary files /dev/null and b/engine/assets/textures/VisualShaderNodeComment.ktx2 differ diff --git a/engine/assets/textures/VisualShaderNodeComment.ktx2.meta b/engine/assets/textures/VisualShaderNodeComment.ktx2.meta new file mode 100644 index 000000000..b85badccc --- /dev/null +++ b/engine/assets/textures/VisualShaderNodeComment.ktx2.meta @@ -0,0 +1,4 @@ +uid = "VD14Neo3rLE" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisualShaderNodeCubemap.ktx2 b/engine/assets/textures/VisualShaderNodeCubemap.ktx2 new file mode 100644 index 000000000..5966b1e88 Binary files /dev/null and b/engine/assets/textures/VisualShaderNodeCubemap.ktx2 differ diff --git a/engine/assets/textures/VisualShaderNodeCubemap.ktx2.meta b/engine/assets/textures/VisualShaderNodeCubemap.ktx2.meta new file mode 100644 index 000000000..6587065a1 --- /dev/null +++ b/engine/assets/textures/VisualShaderNodeCubemap.ktx2.meta @@ -0,0 +1,4 @@ +uid = "RCMy2bXLFmM" +type = "texture" +created_at = "2026-10-31T10:28:09.0000000Z" +modified_at = "2026-10-31T10:28:09.0000000Z" diff --git a/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2 b/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2 new file mode 100644 index 000000000..6a8497c24 Binary files /dev/null and b/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2 differ diff --git a/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2.meta b/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2.meta new file mode 100644 index 000000000..9bfc5bad4 --- /dev/null +++ b/engine/assets/textures/VisualShaderNodeTransformUniform.ktx2.meta @@ -0,0 +1,4 @@ +uid = "zLFd-aqEetA" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/VoxelGI.ktx2 b/engine/assets/textures/VoxelGI.ktx2 new file mode 100644 index 000000000..eca717e3f Binary files /dev/null and b/engine/assets/textures/VoxelGI.ktx2 differ diff --git a/engine/assets/textures/VoxelGI.ktx2.meta b/engine/assets/textures/VoxelGI.ktx2.meta new file mode 100644 index 000000000..2b75e5909 --- /dev/null +++ b/engine/assets/textures/VoxelGI.ktx2.meta @@ -0,0 +1,4 @@ +uid = "23mAc9pnuFY" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/VoxelGIData.ktx2 b/engine/assets/textures/VoxelGIData.ktx2 new file mode 100644 index 000000000..72d7603e7 Binary files /dev/null and b/engine/assets/textures/VoxelGIData.ktx2 differ diff --git a/engine/assets/textures/VoxelGIData.ktx2.meta b/engine/assets/textures/VoxelGIData.ktx2.meta new file mode 100644 index 000000000..a44408d3c --- /dev/null +++ b/engine/assets/textures/VoxelGIData.ktx2.meta @@ -0,0 +1,4 @@ +uid = "lyvioN_UAo0" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/WarningPattern.ktx2 b/engine/assets/textures/WarningPattern.ktx2 new file mode 100644 index 000000000..12371fe95 Binary files /dev/null and b/engine/assets/textures/WarningPattern.ktx2 differ diff --git a/engine/assets/textures/WarningPattern.ktx2.meta b/engine/assets/textures/WarningPattern.ktx2.meta new file mode 100644 index 000000000..b3345086e --- /dev/null +++ b/engine/assets/textures/WarningPattern.ktx2.meta @@ -0,0 +1,4 @@ +uid = "sXqTql_Zd4k" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/Window.ktx2 b/engine/assets/textures/Window.ktx2 new file mode 100644 index 000000000..3cdfa686b Binary files /dev/null and b/engine/assets/textures/Window.ktx2 differ diff --git a/engine/assets/textures/Window.ktx2.meta b/engine/assets/textures/Window.ktx2.meta new file mode 100644 index 000000000..0b57c9af2 --- /dev/null +++ b/engine/assets/textures/Window.ktx2.meta @@ -0,0 +1,4 @@ +uid = "hSX1I3L2LzY" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/World.ktx2 b/engine/assets/textures/World.ktx2 new file mode 100644 index 000000000..3c25909f4 Binary files /dev/null and b/engine/assets/textures/World.ktx2 differ diff --git a/engine/assets/textures/World.ktx2.meta b/engine/assets/textures/World.ktx2.meta new file mode 100644 index 000000000..698b9dcc4 --- /dev/null +++ b/engine/assets/textures/World.ktx2.meta @@ -0,0 +1,4 @@ +uid = "3y4psaybYY0" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/WorldBoundaryShape.ktx2 b/engine/assets/textures/WorldBoundaryShape.ktx2 new file mode 100644 index 000000000..b8b931a0a Binary files /dev/null and b/engine/assets/textures/WorldBoundaryShape.ktx2 differ diff --git a/engine/assets/textures/WorldBoundaryShape.ktx2.meta b/engine/assets/textures/WorldBoundaryShape.ktx2.meta new file mode 100644 index 000000000..bfc974e89 --- /dev/null +++ b/engine/assets/textures/WorldBoundaryShape.ktx2.meta @@ -0,0 +1,4 @@ +uid = "vyI8W6tp2YI" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/WorldEnvironment.ktx2 b/engine/assets/textures/WorldEnvironment.ktx2 new file mode 100644 index 000000000..46b9b3541 Binary files /dev/null and b/engine/assets/textures/WorldEnvironment.ktx2 differ diff --git a/engine/assets/textures/WorldEnvironment.ktx2.meta b/engine/assets/textures/WorldEnvironment.ktx2.meta new file mode 100644 index 000000000..a7a9a4f1c --- /dev/null +++ b/engine/assets/textures/WorldEnvironment.ktx2.meta @@ -0,0 +1,4 @@ +uid = "op3nq8pmNVg" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRAnchor.ktx2 b/engine/assets/textures/XRAnchor.ktx2 new file mode 100644 index 000000000..5bbae8d9f Binary files /dev/null and b/engine/assets/textures/XRAnchor.ktx2 differ diff --git a/engine/assets/textures/XRAnchor.ktx2.meta b/engine/assets/textures/XRAnchor.ktx2.meta new file mode 100644 index 000000000..ddddbdb6c --- /dev/null +++ b/engine/assets/textures/XRAnchor.ktx2.meta @@ -0,0 +1,4 @@ +uid = "-sR_OP0WH-w" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRBodyModifier.ktx2 b/engine/assets/textures/XRBodyModifier.ktx2 new file mode 100644 index 000000000..756aa6fa4 Binary files /dev/null and b/engine/assets/textures/XRBodyModifier.ktx2 differ diff --git a/engine/assets/textures/XRBodyModifier.ktx2.meta b/engine/assets/textures/XRBodyModifier.ktx2.meta new file mode 100644 index 000000000..7b057ca6d --- /dev/null +++ b/engine/assets/textures/XRBodyModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "Q-ulWDYWVRQ" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRCamera.ktx2 b/engine/assets/textures/XRCamera.ktx2 new file mode 100644 index 000000000..841b8e635 Binary files /dev/null and b/engine/assets/textures/XRCamera.ktx2 differ diff --git a/engine/assets/textures/XRCamera.ktx2.meta b/engine/assets/textures/XRCamera.ktx2.meta new file mode 100644 index 000000000..2c95f01e5 --- /dev/null +++ b/engine/assets/textures/XRCamera.ktx2.meta @@ -0,0 +1,4 @@ +uid = "XoXEEClx8QI" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRController.ktx2 b/engine/assets/textures/XRController.ktx2 new file mode 100644 index 000000000..4d9cdea2c Binary files /dev/null and b/engine/assets/textures/XRController.ktx2 differ diff --git a/engine/assets/textures/XRController.ktx2.meta b/engine/assets/textures/XRController.ktx2.meta new file mode 100644 index 000000000..2999c52ee --- /dev/null +++ b/engine/assets/textures/XRController.ktx2.meta @@ -0,0 +1,4 @@ +uid = "rEOMN1zg3f8" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRFaceModifier.ktx2 b/engine/assets/textures/XRFaceModifier.ktx2 new file mode 100644 index 000000000..c279aa394 Binary files /dev/null and b/engine/assets/textures/XRFaceModifier.ktx2 differ diff --git a/engine/assets/textures/XRFaceModifier.ktx2.meta b/engine/assets/textures/XRFaceModifier.ktx2.meta new file mode 100644 index 000000000..dd6e5eac2 --- /dev/null +++ b/engine/assets/textures/XRFaceModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "o_t6w2ODk6Y" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRHandModifier.ktx2 b/engine/assets/textures/XRHandModifier.ktx2 new file mode 100644 index 000000000..52b8f0537 Binary files /dev/null and b/engine/assets/textures/XRHandModifier.ktx2 differ diff --git a/engine/assets/textures/XRHandModifier.ktx2.meta b/engine/assets/textures/XRHandModifier.ktx2.meta new file mode 100644 index 000000000..a2f1f851f --- /dev/null +++ b/engine/assets/textures/XRHandModifier.ktx2.meta @@ -0,0 +1,4 @@ +uid = "xCTkGm2vrIQ" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XRNode.ktx2 b/engine/assets/textures/XRNode.ktx2 new file mode 100644 index 000000000..e71b7abf1 Binary files /dev/null and b/engine/assets/textures/XRNode.ktx2 differ diff --git a/engine/assets/textures/XRNode.ktx2.meta b/engine/assets/textures/XRNode.ktx2.meta new file mode 100644 index 000000000..cf16fa151 --- /dev/null +++ b/engine/assets/textures/XRNode.ktx2.meta @@ -0,0 +1,4 @@ +uid = "-lu-Gb-vUj4" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/XROrigin.ktx2 b/engine/assets/textures/XROrigin.ktx2 new file mode 100644 index 000000000..4446cb374 Binary files /dev/null and b/engine/assets/textures/XROrigin.ktx2 differ diff --git a/engine/assets/textures/XROrigin.ktx2.meta b/engine/assets/textures/XROrigin.ktx2.meta new file mode 100644 index 000000000..4585c5278 --- /dev/null +++ b/engine/assets/textures/XROrigin.ktx2.meta @@ -0,0 +1,4 @@ +uid = "AVKqH81q-pA" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/YSort.ktx2 b/engine/assets/textures/YSort.ktx2 new file mode 100644 index 000000000..3c1c0544e Binary files /dev/null and b/engine/assets/textures/YSort.ktx2 differ diff --git a/engine/assets/textures/YSort.ktx2.meta b/engine/assets/textures/YSort.ktx2.meta new file mode 100644 index 000000000..1d0c8149c --- /dev/null +++ b/engine/assets/textures/YSort.ktx2.meta @@ -0,0 +1,4 @@ +uid = "I9u9TDZTGfg" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/assets/textures/Zoom.ktx2 b/engine/assets/textures/Zoom.ktx2 new file mode 100644 index 000000000..c37174c3b Binary files /dev/null and b/engine/assets/textures/Zoom.ktx2 differ diff --git a/engine/assets/textures/Zoom.ktx2.meta b/engine/assets/textures/Zoom.ktx2.meta new file mode 100644 index 000000000..0270c51e4 --- /dev/null +++ b/engine/assets/textures/Zoom.ktx2.meta @@ -0,0 +1,4 @@ +uid = "7FPPSl53KWI" +type = "texture" +created_at = "2026-10-31T10:28:10.0000000Z" +modified_at = "2026-10-31T10:28:10.0000000Z" diff --git a/engine/src/toast/physics/body.hpp b/engine/src/toast/physics/body.hpp index 79a24bbac..4ce9d2e08 100644 --- a/engine/src/toast/physics/body.hpp +++ b/engine/src/toast/physics/body.hpp @@ -57,6 +57,12 @@ struct BodyState { glm::vec3 angular_velocity = {}; }; +struct BodyMassProperties { + float mass = 0.0f; + glm::vec3 local_center_of_mass = {}; + glm::mat3 inverse_inertia_local = {0.0f}; +}; + struct Body { BodyType type = BodyType::dynamic_body; bool enabled = true; @@ -95,6 +101,11 @@ struct Body { } }; +[[nodiscard]] +inline auto movable(const Body& body) -> bool { + return body.inverse_mass > 0.0f && (body.awake || body.type != BodyType::dynamic_body); +} + struct BodySlot { Body body; uint32_t generation = 1; diff --git a/engine/src/toast/physics/broad_phase.cpp b/engine/src/toast/physics/broad_phase.cpp index c5cb9ee58..141090904 100644 --- a/engine/src/toast/physics/broad_phase.cpp +++ b/engine/src/toast/physics/broad_phase.cpp @@ -241,6 +241,7 @@ auto BroadPhase::findPairs(CollisionWorldView world) -> std::vectorowner, shape_b->owner)) { + ++stats.rejected_joint_excluded; + return std::nullopt; + } + const Body* body_a = world.body(shape_a->owner); const Body* body_b = world.body(shape_b->owner); if (not body_a || not body_b || not body_a->enabled || not body_b->enabled) { diff --git a/engine/src/toast/physics/broad_phase.hpp b/engine/src/toast/physics/broad_phase.hpp index 97b223002..2b925ad82 100644 --- a/engine/src/toast/physics/broad_phase.hpp +++ b/engine/src/toast/physics/broad_phase.hpp @@ -41,6 +41,7 @@ struct BroadPhaseStats { size_t rejected_invalid_bodies = 0; size_t rejected_immovable_bodies = 0; size_t rejected_layers = 0; + size_t rejected_joint_excluded = 0; size_t tree_nodes = 0; }; diff --git a/engine/src/toast/physics/collision_world.hpp b/engine/src/toast/physics/collision_world.hpp index 0967e882e..63f29612b 100644 --- a/engine/src/toast/physics/collision_world.hpp +++ b/engine/src/toast/physics/collision_world.hpp @@ -9,14 +9,29 @@ #include "shape.hpp" #include "voxel_shape_data.hpp" +#include +#include #include namespace physics { +[[nodiscard]] +constexpr auto bodyPairKey(BodyID a, BodyID b) -> uint64_t { + const uint32_t low = std::min(a.slot, b.slot); + const uint32_t high = std::max(a.slot, b.slot); + return (static_cast(low) << 32) | high; +} + struct CollisionWorldView { std::span bodies; std::span shapes; std::span voxel_shapes; + std::span excluded_pairs; + + [[nodiscard]] + auto pairExcluded(BodyID a, BodyID b) const -> bool { + return not excluded_pairs.empty() && std::binary_search(excluded_pairs.begin(), excluded_pairs.end(), bodyPairKey(a, b)); + } [[nodiscard]] auto body(BodyID id) const -> const Body* { diff --git a/engine/src/toast/physics/constraint.hpp b/engine/src/toast/physics/constraint.hpp index 7724af480..136ea522f 100644 --- a/engine/src/toast/physics/constraint.hpp +++ b/engine/src/toast/physics/constraint.hpp @@ -34,6 +34,7 @@ struct Constraint { float tangent_mass_1 = 0.0f; float tangent_mass_2 = 0.0f; float restitution_bias = 0.0f; + float closing_speed = 0.0f; float static_friction = material_defaults::k_static_friction; float dynamic_friction = material_defaults::k_dynamic_friction; diff --git a/engine/src/toast/physics/joint.hpp b/engine/src/toast/physics/joint.hpp new file mode 100644 index 000000000..b97fac7e4 --- /dev/null +++ b/engine/src/toast/physics/joint.hpp @@ -0,0 +1,156 @@ +/** + * @file joint.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Joint parameters + */ + +#pragma once + +#include "body.hpp" +#include "shape.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace physics { + +struct JointID { + uint32_t slot = std::numeric_limits::max(); + uint32_t generation = 0; + auto operator<=>(const JointID&) const = default; +}; + +enum class JointType : uint8_t { + pin, + hinge, + slider, + fixed +}; + +// Joint friction is the torque that holds the ends against turning per newton the joint carries, the friction of the +// material times the radius of the pin, in metres +constexpr float k_default_joint_friction = 0.01f; + +// The ends of a hinge or a pin with collision between them don't touch closer to the pin than this +constexpr float k_joint_knuckle_radius = 0.1f; + +struct PinParams { + float bias = 0.3f; + float impulse_clamp = 0.0f; + float friction = k_default_joint_friction; + auto operator==(const PinParams&) const -> bool = default; +}; + +struct HingeParams { + float bias = 0.3f; + bool limit_enabled = false; + float limit_lower = -glm::half_pi(); // radians + float limit_upper = glm::half_pi(); + float limit_bias = 0.3f; + float limit_restitution = 0.0f; + float friction = k_default_joint_friction; + bool motor_enabled = false; + float motor_target_velocity = 0.0f; // rad/s + float motor_max_impulse = 1.0f; // per step + + auto operator==(const HingeParams&) const -> bool = default; +}; + +// todo: sliderjoint needs to be able to freely rotate +struct SliderParams { + float bias = 0.3f; + float linear_limit_lower = -1.0f; + float linear_limit_upper = 1.0f; + float limit_restitution = 0.0f; + + auto operator==(const SliderParams&) const -> bool = default; +}; + +struct FixedParams { + float bias = 0.3f; + auto operator==(const FixedParams&) const -> bool = default; +}; + +using JointParams = std::variant; + +struct JointFrame { + glm::vec3 position = {}; + glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); +}; + +struct VoxelAnchorSettings { + bool enabled = true; + int radius_voxels = 2; + float min_remaining = 0.5f; +}; + +enum class JointBreakReason : uint8_t { + requested, + force, + torque, + anchor_destroyed +}; + +struct JointDescriptor { + BodyID body_a; + BodyID body_b; + glm::vec3 anchor = {}; + glm::quat frame = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + bool exclude_collision = true; + JointParams params = PinParams {}; + float break_force = 0.0f; + float break_torque = 0.0f; + VoxelAnchorSettings voxel_anchor; +}; + +struct JointVoxelEnd { + ShapeID shape; + uint32_t baseline = 0; + uint32_t revision = 0; +}; + +struct JointImpulses { + glm::vec3 point = {}; // hinge and pin anchor + glm::vec2 perpendicular = {}; // hinge angular, slider linear, along the Y and Z of the frame of body a + glm::vec3 angular_lock = {}; // slider rotation + float limit_lower = 0.0f; + float limit_upper = 0.0f; + float motor = 0.0f; + glm::vec3 friction = {}; // along the axes of the frame of body a +}; + +struct Joint { + JointDescriptor descriptor; + JointFrame frame_a; + JointFrame frame_b; + JointImpulses impulses; + float hinge_raw = 0.0f; + float hinge_angle = 0.0f; + float slider_position = 0.0f; + JointVoxelEnd voxel_end_a; + JointVoxelEnd voxel_end_b; + float last_force = 0.0f; + float last_torque = 0.0f; +}; + +struct JointState { + float angle = 0.0f; + float position = 0.0f; + float force = 0.0f; + float torque = 0.0f; +}; + +struct JointSlot { + Joint joint; + uint32_t generation = 1; + bool occupied = false; +}; + +} diff --git a/engine/src/toast/physics/joint_solver.cpp b/engine/src/toast/physics/joint_solver.cpp new file mode 100644 index 000000000..efa651980 --- /dev/null +++ b/engine/src/toast/physics/joint_solver.cpp @@ -0,0 +1,590 @@ +#include "joint_solver.hpp" + +#include +#include +#include + +namespace physics::_detail { + +namespace { + +constexpr float k_infinity = std::numeric_limits::infinity(); +constexpr float k_max_position_correction = 0.2f; +constexpr float k_min_hinge_arm = 0.01f; + +auto skew(const glm::vec3& v) -> glm::mat3 { + // skew(v) * x = cross(v, x) + return glm::mat3 {0.0f, v.z, -v.y, -v.z, 0.0f, v.x, v.y, -v.x, 0.0f}; +} + +auto inverseMass(const Body& body) -> float { + return movable(body) ? body.inverse_mass : 0.0f; +} + +auto inverseInertia(const Body& body) -> glm::mat3 { + return movable(body) ? body.inverse_inertia_world : glm::mat3 {0.0f}; +} + +auto velocityAt(const Body& body, const glm::vec3& r) -> glm::vec3 { + return body.linear_velocity + glm::cross(body.angular_velocity, r); +} + +void applyLinear(Body& a, Body& b, const glm::vec3& r_a, const glm::vec3& r_b, const glm::vec3& impulse) { + if (movable(a)) { + a.linear_velocity -= impulse * a.inverse_mass; + a.angular_velocity -= a.inverse_inertia_world * glm::cross(r_a, impulse); + } + if (movable(b)) { + b.linear_velocity += impulse * b.inverse_mass; + b.angular_velocity += b.inverse_inertia_world * glm::cross(r_b, impulse); + } +} + +void applyAngular(Body& a, Body& b, const glm::vec3& impulse) { + if (movable(a)) { + a.angular_velocity -= a.inverse_inertia_world * impulse; + } + if (movable(b)) { + b.angular_velocity += b.inverse_inertia_world * impulse; + } +} + +auto linearMass(const Body& a, const Body& b, const glm::vec3& r_a, const glm::vec3& r_b, const glm::vec3& direction) -> float { + const glm::vec3 angular_a = inverseInertia(a) * glm::cross(r_a, direction); + const glm::vec3 angular_b = inverseInertia(b) * glm::cross(r_b, direction); + const float denominator = + inverseMass(a) + inverseMass(b) + glm::dot(direction, glm::cross(angular_a, r_a) + glm::cross(angular_b, r_b)); + return std::isfinite(denominator) && denominator > 1.0e-8f ? 1.0f / denominator : 0.0f; +} + +auto angularMass(const Body& a, const Body& b, const glm::vec3& axis) -> float { + const float denominator = glm::dot(axis, (inverseInertia(a) + inverseInertia(b)) * axis); + return std::isfinite(denominator) && denominator > 1.0e-8f ? 1.0f / denominator : 0.0f; +} + +auto limitBias(float error, float inverse_dt) -> float { + return error > 0.0f ? error * inverse_dt : 0.0f; +} + +constexpr float k_min_bounce_speed = 0.2f; + +auto bounce(float error, float speed, float restitution, float dt) -> float { + if (restitution <= 0.0f || speed > -k_min_bounce_speed || error + (speed * dt) > 0.0f) { + return 0.0f; + } + return restitution * speed; +} + +auto limitTarget(float bounce_bias, float error, float inverse_dt) -> float { + return bounce_bias < 0.0f ? bounce_bias : limitBias(error, inverse_dt); +} + +void solveLinear( + const JointRow& row, Body& a, Body& b, const glm::vec3& direction, float bias_velocity, float& accumulated, float lower, + float upper +) { + const float mass = linearMass(a, b, row.r_a, row.r_b, direction); + if (mass <= 0.0f) { + return; + } + + const float speed = glm::dot(velocityAt(b, row.r_b) - velocityAt(a, row.r_a), direction); + const float next = std::clamp(accumulated - ((speed + bias_velocity) * mass), lower, upper); + const float applied = next - accumulated; + accumulated = next; + applyLinear(a, b, row.r_a, row.r_b, direction * applied); +} + +void solveAngular(Body& a, Body& b, const glm::vec3& axis, float bias_velocity, float& accumulated, float lower, float upper) { + const float mass = angularMass(a, b, axis); + if (mass <= 0.0f) { + return; + } + + const float speed = glm::dot(b.angular_velocity - a.angular_velocity, axis); + const float next = std::clamp(accumulated - ((speed + bias_velocity) * mass), lower, upper); + const float applied = next - accumulated; + accumulated = next; + applyAngular(a, b, axis * applied); +} + +auto turnInverse(const Body& body, const glm::vec3& r, const glm::vec3& axis) -> float { + if (not movable(body)) { + return 0.0f; + } + const float about_center = glm::dot(axis, body.inverse_inertia_world * axis); + if (not(about_center > 1.0e-12f)) { + return 0.0f; + } + const glm::vec3 lever = r - (axis * glm::dot(axis, r)); + const float moment = (1.0f / about_center) + (body.inverse_mass > 0.0f ? glm::dot(lever, lever) / body.inverse_mass : 0.0f); + return std::isfinite(moment) && moment > 0.0f ? 1.0f / moment : 0.0f; +} + +void applyTurn(Body& a, Body& b, const JointRow& row, const glm::vec3& axis, float impulse) { + if (movable(a)) { + const glm::vec3 change = axis * (impulse * turnInverse(a, row.r_a, axis)); + a.angular_velocity -= change; + a.linear_velocity -= glm::cross(row.r_a, change); + } + if (movable(b)) { + const glm::vec3 change = axis * (impulse * turnInverse(b, row.r_b, axis)); + b.angular_velocity += change; + b.linear_velocity += glm::cross(row.r_b, change); + } +} + +void solveTurn( + const JointRow& row, Body& a, Body& b, const glm::vec3& axis, float bias_velocity, float& accumulated, float lower, + float upper +) { + const float inverse = turnInverse(a, row.r_a, axis) + turnInverse(b, row.r_b, axis); + if (not(inverse > 0.0f)) { + return; + } + + const float speed = glm::dot(b.angular_velocity - a.angular_velocity, axis); + const float next = std::clamp(accumulated - ((speed + bias_velocity) / inverse), lower, upper); + const float applied = next - accumulated; + accumulated = next; + applyTurn(a, b, row, axis, applied); +} + +void solvePoint(const JointRow& row, Body& a, Body& b, glm::vec3& accumulated, float impulse_clamp) { + const glm::vec3 relative = velocityAt(b, row.r_b) - velocityAt(a, row.r_a); + glm::vec3 impulse = row.inverse_point_mass * -relative; + if (impulse_clamp > 0.0f) { + const float length = glm::length(impulse); + if (length > impulse_clamp) { + impulse *= impulse_clamp / length; + } + } + accumulated += impulse; + applyLinear(a, b, row.r_a, row.r_b, impulse); +} + +// Friction in the pin holds the ends against turning with a torque that grows with the load the pin carries +void solveFriction(const JointRow& row, JointImpulses& impulses, float friction, int first_axis, Body& a, Body& b) { + const float limit = friction * glm::length(impulses.point); + for (int index = first_axis; index < 3; ++index) { + solveTurn(row, a, b, row.axes_a[index], 0.0f, impulses.friction[index], -limit, limit); + } +} + +void solvePin(const JointRow& row, Joint& joint, const PinParams& params, Body& a, Body& b) { + solvePoint(row, a, b, joint.impulses.point, params.impulse_clamp); + solveFriction(row, joint.impulses, params.friction, 0, a, b); +} + +void solveHinge(const JointRow& row, Joint& joint, const HingeParams& params, Body& a, Body& b) { + JointImpulses& impulses = joint.impulses; + const glm::vec3 axis = row.axes_a[2]; + + if (params.motor_enabled) { + solveTurn( + row, a, b, axis, -params.motor_target_velocity, impulses.motor, -params.motor_max_impulse, params.motor_max_impulse + ); + } + + solvePoint(row, a, b, impulses.point, 0.0f); + solveFriction(row, impulses, params.friction, 2, a, b); + + for (int index = 0; index < 2; ++index) { + solveAngular(a, b, row.axes_a[index], 0.0f, impulses.perpendicular[index], -k_infinity, k_infinity); + } + + if (params.limit_enabled) { + solveTurn( + row, + a, + b, + axis, + limitTarget(row.bounce_lower, row.angle - params.limit_lower, row.inverse_dt), + impulses.limit_lower, + 0.0f, + k_infinity + ); + solveTurn( + row, + a, + b, + -axis, + limitTarget(row.bounce_upper, params.limit_upper - row.angle, row.inverse_dt), + impulses.limit_upper, + 0.0f, + k_infinity + ); + } +} + +void solveSlider(const JointRow& row, Joint& joint, const SliderParams& params, Body& a, Body& b) { + JointImpulses& impulses = joint.impulses; + const glm::vec3 axis = row.axes_a[0]; + + const glm::vec3 angular_impulse = row.inverse_angular_mass * -(b.angular_velocity - a.angular_velocity); + impulses.angular_lock += angular_impulse; + applyAngular(a, b, angular_impulse); + + for (int index = 0; index < 2; ++index) { + solveLinear(row, a, b, row.axes_a[index + 1], 0.0f, impulses.perpendicular[index], -k_infinity, k_infinity); + } + + solveLinear( + row, + a, + b, + axis, + limitTarget(row.bounce_lower, row.position - params.linear_limit_lower, row.inverse_dt), + impulses.limit_lower, + 0.0f, + k_infinity + ); + solveLinear( + row, + a, + b, + -axis, + limitTarget(row.bounce_upper, params.linear_limit_upper - row.position, row.inverse_dt), + impulses.limit_upper, + 0.0f, + k_infinity + ); +} + +void solveFixed(const JointRow& row, Joint& joint, Body& a, Body& b) { + // Neither body turns relative to the other + const glm::vec3 angular_impulse = row.inverse_angular_mass * -(b.angular_velocity - a.angular_velocity); + joint.impulses.angular_lock += angular_impulse; + applyAngular(a, b, angular_impulse); + + solvePoint(row, a, b, joint.impulses.point, 0.0f); +} + +auto measure(const Joint& joint, const Body& body_a, const Body& body_b) -> JointRow { + JointRow row; + + const glm::quat frame_a = body_a.rotation * joint.frame_a.rotation; + const glm::quat frame_b = body_b.rotation * joint.frame_b.rotation; + const glm::vec3 anchor_a = body_a.position + (body_a.rotation * joint.frame_a.position); + const glm::vec3 anchor_b = body_b.position + (body_b.rotation * joint.frame_b.position); + row.r_a = anchor_a - body_a.worldCenterOfMass(); + row.r_b = anchor_b - body_b.worldCenterOfMass(); + row.error = anchor_b - anchor_a; + row.axes_a = glm::mat3_cast(frame_a); + row.axes_b = glm::mat3_cast(frame_b); + + if (movable(body_a) || movable(body_b)) { + const glm::mat3 skew_a = skew(row.r_a); + const glm::mat3 skew_b = skew(row.r_b); + const glm::mat3 point_mass = (glm::mat3 {1.0f} * (inverseMass(body_a) + inverseMass(body_b))) - + (skew_a * inverseInertia(body_a) * skew_a) - (skew_b * inverseInertia(body_b) * skew_b); + const glm::mat3 inverse = glm::inverse(point_mass); + if (std::isfinite(inverse[0][0]) && std::isfinite(inverse[1][1]) && std::isfinite(inverse[2][2])) { + row.inverse_point_mass = inverse; + } + } + + if (std::holds_alternative(joint.descriptor.params) || + std::holds_alternative(joint.descriptor.params)) { + row.position = glm::dot(row.error, row.axes_a[0]); + + glm::quat difference = frame_b * glm::conjugate(frame_a); + if (difference.w < 0.0f) { + difference = -difference; + } + row.angular_error = 2.0f * glm::vec3 {difference.x, difference.y, difference.z}; + + if (movable(body_a) || movable(body_b)) { + const glm::mat3 inverse = glm::inverse(inverseInertia(body_a) + inverseInertia(body_b)); + if (std::isfinite(inverse[0][0]) && std::isfinite(inverse[1][1]) && std::isfinite(inverse[2][2])) { + row.inverse_angular_mass = inverse; + } + } + } + return row; +} + +auto wrapToPi(float angle) -> float { + if (angle > glm::pi()) { + return angle - glm::two_pi(); + } + if (angle < -glm::pi()) { + return angle + glm::two_pi(); + } + return angle; +} + +auto rawHingeAngle(const JointRow& row) -> float { + return std::atan2(glm::dot(glm::cross(row.axes_a[0], row.axes_b[0]), row.axes_a[2]), glm::dot(row.axes_a[0], row.axes_b[0])); +} + +void displace(Body& body, const glm::vec3& translation, const glm::vec3& rotation_vector) { + const glm::vec3 center = body.worldCenterOfMass() + translation; + const glm::quat spin {0.0f, rotation_vector.x, rotation_vector.y, rotation_vector.z}; + body.rotation = glm::normalize(body.rotation + (0.5f * (spin * body.rotation))); + body.position = center - (body.rotation * body.local_center_of_mass); + const glm::mat3 rotation = glm::mat3_cast(body.rotation); + body.inverse_inertia_world = rotation * body.inverse_inertia_local * glm::transpose(rotation); +} + +void shiftLinear(Body& a, Body& b, const glm::vec3& r_a, const glm::vec3& r_b, const glm::vec3& push) { + if (movable(a)) { + displace(a, -push * a.inverse_mass, -(a.inverse_inertia_world * glm::cross(r_a, push))); + } + if (movable(b)) { + displace(b, push * b.inverse_mass, b.inverse_inertia_world * glm::cross(r_b, push)); + } +} + +void shiftAngular(Body& a, Body& b, const glm::vec3& push) { + if (movable(a)) { + displace(a, glm::vec3 {0.0f}, -(a.inverse_inertia_world * push)); + } + if (movable(b)) { + displace(b, glm::vec3 {0.0f}, b.inverse_inertia_world * push); + } +} + +auto clampLength(const glm::vec3& value, float limit) -> glm::vec3 { + const float length = glm::length(value); + return length > limit ? value * (limit / length) : value; +} + +auto clampValue(float value) -> float { + return std::clamp(value, -k_max_position_correction, k_max_position_correction); +} + +void correctLinear(const JointRow& row, Body& a, Body& b, const glm::vec3& direction, float error, float bias) { + const float mass = linearMass(a, b, row.r_a, row.r_b, direction); + if (mass > 0.0f) { + shiftLinear(a, b, row.r_a, row.r_b, direction * (-bias * clampValue(error) * mass)); + } +} + +void correctTurn(const JointRow& row, Body& a, Body& b, const glm::vec3& axis, float error, float bias) { + const float inverse_a = turnInverse(a, row.r_a, axis); + const float inverse_b = turnInverse(b, row.r_b, axis); + const float inverse = inverse_a + inverse_b; + if (not(inverse > 0.0f)) { + return; + } + + const float push = -bias * clampValue(error) / inverse; + if (inverse_a > 0.0f) { + const glm::vec3 turn = axis * (-push * inverse_a); + displace(a, glm::cross(row.r_a, turn), turn); + } + if (inverse_b > 0.0f) { + const glm::vec3 turn = axis * (push * inverse_b); + displace(b, glm::cross(row.r_b, turn), turn); + } +} + +void correctAngular(Body& a, Body& b, const glm::vec3& axis, float error, float bias) { + const float mass = angularMass(a, b, axis); + if (mass > 0.0f) { + shiftAngular(a, b, axis * (-bias * clampValue(error) * mass)); + } +} + +} + +auto makeJoint(const JointDescriptor& descriptor, const Body& body_a, const Body& body_b) -> Joint { + const auto frame_in = [&](const Body& body) { + const glm::quat inverse_rotation = glm::inverse(body.rotation); + return JointFrame { + .position = inverse_rotation * (descriptor.anchor - body.position), + .rotation = inverse_rotation * descriptor.frame, + }; + }; + + Joint joint; + joint.descriptor = descriptor; + joint.frame_a = frame_in(body_a); + joint.frame_b = frame_in(body_b); + + // A hinge angle is where b sits around the axis from the x of the frame, the way the limits are drawn. b is the end that + // moves when only one does. A body centred on the axis, like a wheel, has no side to sit on and starts at zero + if (const auto* hinge = std::get_if(&descriptor.params); hinge != nullptr) { + const glm::mat3 axes = glm::mat3_cast(descriptor.frame); + const glm::vec3 arm = body_b.worldCenterOfMass() - descriptor.anchor; + const float along_x = glm::dot(arm, axes[0]); + const float along_y = glm::dot(arm, axes[1]); + if ((along_x * along_x) + (along_y * along_y) > k_min_hinge_arm * k_min_hinge_arm) { + joint.hinge_angle = std::atan2(along_y, along_x); + } + + if (hinge->limit_enabled) { + const auto outside = [&](float angle) { return std::max({hinge->limit_lower - angle, angle - hinge->limit_upper, 0.0f}); }; + const float start = joint.hinge_angle; + for (int turns = -2; turns <= 2; ++turns) { + const float angle = start + (static_cast(turns) * glm::two_pi()); + if (outside(angle) < outside(joint.hinge_angle)) { + joint.hinge_angle = angle; + } + } + } + } + return joint; +} + +auto prepareJoint(Joint& joint, const Body& body_a, const Body& body_b, float dt) -> JointRow { + JointRow row = measure(joint, body_a, body_b); + row.inverse_dt = 1.0f / dt; + + if (const auto* hinge = std::get_if(&joint.descriptor.params); hinge != nullptr) { + const float raw = rawHingeAngle(row); + joint.hinge_angle += wrapToPi(raw - joint.hinge_raw); + joint.hinge_raw = raw; + row.angle = joint.hinge_angle; + if (hinge->limit_enabled) { + const float speed = glm::dot(body_b.angular_velocity - body_a.angular_velocity, row.axes_a[2]); + row.bounce_lower = bounce(row.angle - hinge->limit_lower, speed, hinge->limit_restitution, dt); + row.bounce_upper = bounce(hinge->limit_upper - row.angle, -speed, hinge->limit_restitution, dt); + } + } else if (const auto* slider = std::get_if(&joint.descriptor.params); slider != nullptr) { + joint.slider_position = row.position; + const float speed = glm::dot(velocityAt(body_b, row.r_b) - velocityAt(body_a, row.r_a), row.axes_a[0]); + row.bounce_lower = bounce(row.position - slider->linear_limit_lower, speed, slider->limit_restitution, dt); + row.bounce_upper = bounce(slider->linear_limit_upper - row.position, -speed, slider->limit_restitution, dt); + } + return row; +} + +void warmStartJoint(const JointRow& row, const Joint& joint, Body& body_a, Body& body_b) { + const JointImpulses& impulses = joint.impulses; + if (std::holds_alternative(joint.descriptor.params)) { + applyLinear(body_a, body_b, row.r_a, row.r_b, impulses.point); + for (int index = 0; index < 3; ++index) { + applyTurn(body_a, body_b, row, row.axes_a[index], impulses.friction[index]); + } + } else if (std::holds_alternative(joint.descriptor.params)) { + applyLinear(body_a, body_b, row.r_a, row.r_b, impulses.point); + applyAngular(body_a, body_b, (row.axes_a[0] * impulses.perpendicular.x) + (row.axes_a[1] * impulses.perpendicular.y)); + applyTurn( + body_a, body_b, row, row.axes_a[2], impulses.motor + impulses.limit_lower - impulses.limit_upper + impulses.friction.z + ); + } else if (std::holds_alternative(joint.descriptor.params)) { + applyAngular(body_a, body_b, impulses.angular_lock); + applyLinear(body_a, body_b, row.r_a, row.r_b, impulses.point); + } else { + applyAngular(body_a, body_b, impulses.angular_lock); + applyLinear( + body_a, + body_b, + row.r_a, + row.r_b, + (row.axes_a[1] * impulses.perpendicular.x) + (row.axes_a[2] * impulses.perpendicular.y) + + (row.axes_a[0] * (impulses.limit_lower - impulses.limit_upper)) + ); + } +} + +void solveJoint(const JointRow& row, Joint& joint, Body& body_a, Body& body_b) { + if (const auto* pin = std::get_if(&joint.descriptor.params); pin != nullptr) { + solvePin(row, joint, *pin, body_a, body_b); + } else if (const auto* hinge = std::get_if(&joint.descriptor.params); hinge != nullptr) { + solveHinge(row, joint, *hinge, body_a, body_b); + } else if (const auto* slider = std::get_if(&joint.descriptor.params); slider != nullptr) { + solveSlider(row, joint, *slider, body_a, body_b); + } else if (std::holds_alternative(joint.descriptor.params)) { + solveFixed(row, joint, body_a, body_b); + } +} + +auto jointReaction(const Joint& joint) -> JointReaction { + const JointImpulses& impulses = joint.impulses; + // The frames of a joint are orthonormal + if (std::holds_alternative(joint.descriptor.params)) { + const float axial = impulses.motor + impulses.limit_lower - impulses.limit_upper + impulses.friction.z; + return { + .linear = glm::length(impulses.point), + .angular = std::sqrt( + (impulses.perpendicular.x * impulses.perpendicular.x) + (impulses.perpendicular.y * impulses.perpendicular.y) + + (axial * axial) + ), + }; + } + if (std::holds_alternative(joint.descriptor.params)) { + const float axial = impulses.limit_lower - impulses.limit_upper; + return { + .linear = std::sqrt( + (impulses.perpendicular.x * impulses.perpendicular.x) + (impulses.perpendicular.y * impulses.perpendicular.y) + + (axial * axial) + ), + .angular = glm::length(impulses.angular_lock), + }; + } + if (std::holds_alternative(joint.descriptor.params)) { + return {.linear = glm::length(impulses.point), .angular = glm::length(impulses.angular_lock)}; + } + return {.linear = glm::length(impulses.point), .angular = glm::length(impulses.friction)}; +} + +void correctJointPositions(Joint& joint, Body& body_a, Body& body_b) { + if (not movable(body_a) && not movable(body_b)) { + return; + } + + const auto close_ends = [&](float bias) { + const JointRow row = measure(joint, body_a, body_b); + shiftLinear( + body_a, body_b, row.r_a, row.r_b, row.inverse_point_mass * (clampLength(row.error, k_max_position_correction) * -bias) + ); + }; + + if (const auto* pin = std::get_if(&joint.descriptor.params); pin != nullptr) { + close_ends(pin->bias); + } else if (const auto* hinge = std::get_if(&joint.descriptor.params); hinge != nullptr) { + close_ends(hinge->bias); + + // The axes of both ends back together + const JointRow aligned = measure(joint, body_a, body_b); + const glm::vec3 misalignment = glm::cross(aligned.axes_a[2], aligned.axes_b[2]); + for (int index = 0; index < 2; ++index) { + correctAngular(body_a, body_b, aligned.axes_a[index], glm::dot(misalignment, aligned.axes_a[index]), hinge->bias); + } + + if (hinge->limit_enabled) { + const JointRow turned = measure(joint, body_a, body_b); + const float angle = joint.hinge_angle + wrapToPi(rawHingeAngle(turned) - joint.hinge_raw); + const glm::vec3 axis = turned.axes_a[2]; + if (angle < hinge->limit_lower) { + correctTurn(turned, body_a, body_b, axis, angle - hinge->limit_lower, hinge->limit_bias); + } else if (angle > hinge->limit_upper) { + correctTurn(turned, body_a, body_b, -axis, hinge->limit_upper - angle, hinge->limit_bias); + } + } + } else if (const auto* slider = std::get_if(&joint.descriptor.params); slider != nullptr) { + const JointRow before = measure(joint, body_a, body_b); + shiftAngular( + body_a, + body_b, + before.inverse_angular_mass * (clampLength(before.angular_error, k_max_position_correction) * -slider->bias) + ); + + // Only the way along the axis is free + const JointRow turned = measure(joint, body_a, body_b); + for (int index = 1; index < 3; ++index) { + const glm::vec3 direction = turned.axes_a[index]; + correctLinear(turned, body_a, body_b, direction, glm::dot(turned.error, direction), slider->bias); + } + + const JointRow moved = measure(joint, body_a, body_b); + if (moved.position < slider->linear_limit_lower) { + correctLinear(moved, body_a, body_b, moved.axes_a[0], moved.position - slider->linear_limit_lower, slider->bias); + } else if (moved.position > slider->linear_limit_upper) { + correctLinear(moved, body_a, body_b, -moved.axes_a[0], slider->linear_limit_upper - moved.position, slider->bias); + } + } else if (const auto* fixed = std::get_if(&joint.descriptor.params); fixed != nullptr) { + const JointRow before = measure(joint, body_a, body_b); + shiftAngular( + body_a, + body_b, + before.inverse_angular_mass * (clampLength(before.angular_error, k_max_position_correction) * -fixed->bias) + ); + close_ends(fixed->bias); + } +} + +} diff --git a/engine/src/toast/physics/joint_solver.hpp b/engine/src/toast/physics/joint_solver.hpp new file mode 100644 index 000000000..9a20da042 --- /dev/null +++ b/engine/src/toast/physics/joint_solver.hpp @@ -0,0 +1,50 @@ +/** + * @file joint_solver.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Sequential impulses of the joints + */ + +#pragma once + +#include "joint.hpp" + +namespace physics::_detail { + +struct JointRow { + glm::vec3 r_a = {}; + glm::vec3 r_b = {}; + glm::vec3 error = {}; + glm::mat3 axes_a = {1.0f}; + glm::mat3 axes_b = {1.0f}; + glm::mat3 inverse_point_mass = {0.0f}; + glm::mat3 inverse_angular_mass = {0.0f}; + glm::vec3 angular_error = {}; // slider + float position = 0.0f; // slider + float angle = 0.0f; // hinge + float bounce_lower = 0.0f; + float bounce_upper = 0.0f; + float inverse_dt = 0.0f; +}; + +[[nodiscard]] +TOAST_API auto makeJoint(const JointDescriptor& descriptor, const Body& body_a, const Body& body_b) -> Joint; + +[[nodiscard]] +TOAST_API auto prepareJoint(Joint& joint, const Body& body_a, const Body& body_b, float dt) -> JointRow; + +TOAST_API void warmStartJoint(const JointRow& row, const Joint& joint, Body& body_a, Body& body_b); + +TOAST_API void solveJoint(const JointRow& row, Joint& joint, Body& body_a, Body& body_b); + +struct JointReaction { + float linear = 0.0f; + float angular = 0.0f; +}; + +[[nodiscard]] +TOAST_API auto jointReaction(const Joint& joint) -> JointReaction; + +TOAST_API void correctJointPositions(Joint& joint, Body& body_a, Body& body_b); + +} diff --git a/engine/src/toast/physics/nodes/fixed_joint.cpp b/engine/src/toast/physics/nodes/fixed_joint.cpp new file mode 100644 index 000000000..f8bddd095 --- /dev/null +++ b/engine/src/toast/physics/nodes/fixed_joint.cpp @@ -0,0 +1,12 @@ +#include "fixed_joint.hpp" + +#include + +namespace physics { + +void FixedJoint::drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const { + constexpr float k_half_size = 0.06f; + debug::drawBox(origin - glm::vec3(k_half_size), origin + glm::vec3(k_half_size), color); +} + +} diff --git a/engine/src/toast/physics/nodes/fixed_joint.hpp b/engine/src/toast/physics/nodes/fixed_joint.hpp new file mode 100644 index 000000000..b67ca6fcd --- /dev/null +++ b/engine/src/toast/physics/nodes/fixed_joint.hpp @@ -0,0 +1,27 @@ +/** + * @file fixed_joint.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Welds two bodies + */ + +#pragma once +#include "joint3d.hpp" + +namespace physics { + +class [[ToastNode, Icon("Anchor"), Color("Green")]] TOAST_API FixedJoint : public physics::Joint3D { +public: + [[Reflect, Range(0.0, 1.0)]] + float bias = 0.3f; + + [[nodiscard]] + auto makeParams() const -> JointParams override { + return FixedParams {.bias = bias}; + } + +protected: + void drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const override; +}; + +} diff --git a/engine/src/toast/physics/nodes/hinge_joint.cpp b/engine/src/toast/physics/nodes/hinge_joint.cpp new file mode 100644 index 000000000..f10ed7ae0 --- /dev/null +++ b/engine/src/toast/physics/nodes/hinge_joint.cpp @@ -0,0 +1,138 @@ +#include "hinge_joint.hpp" + +#include +#include +#include +#include +#include + +namespace physics { + +void HingeJoint::setMotor(float target_velocity_degrees, float max_impulse) { + motor_target_velocity = target_velocity_degrees; + motor_max_impulse = max_impulse; + motor_enabled = true; +} + +auto HingeJoint::makeParams() const -> JointParams { + return HingeParams { + .bias = bias, + .limit_enabled = limit_enabled, + .limit_lower = glm::radians(limit_lower), + .limit_upper = glm::radians(limit_upper), + .limit_bias = limit_bias, + .limit_restitution = limit_restitution, + .friction = friction, + .motor_enabled = motor_enabled, + .motor_target_velocity = glm::radians(motor_target_velocity), + .motor_max_impulse = motor_max_impulse, + }; +} + +auto HingeJoint::limitHandles() const -> LimitHandles { + if (not limit_enabled) { + return {{}, 0}; + } + + const auto at = [](float degrees) { + const float angle = glm::radians(degrees); + return glm::vec3 {std::cos(angle) * k_arc_radius, std::sin(angle) * k_arc_radius, 0.0f}; + }; + return { + {LimitHandle {.handle = toast::GizmoHandle::size_neg_x, .local = at(limit_lower)}, + LimitHandle {.handle = toast::GizmoHandle::size_pos_x, .local = at(limit_upper)}}, + 2, + }; +} + +auto HingeJoint::limitValue(toast::GizmoHandle handle) const -> float { + return handle == toast::GizmoHandle::size_neg_x ? limit_lower : limit_upper; +} + +void HingeJoint::setLimitValue(toast::GizmoHandle handle, float value) { + value = std::clamp(value, -720.0f, 720.0f); + if (handle == toast::GizmoHandle::size_neg_x) { + limit_lower = std::min(value, limit_upper); + } else if (handle == toast::GizmoHandle::size_pos_x) { + limit_upper = std::max(value, limit_lower); + } +} + +auto HingeJoint::limitField(toast::GizmoHandle handle) const -> const char* { + if (handle == toast::GizmoHandle::size_neg_x) { + return "limit_lower"; + } + return handle == toast::GizmoHandle::size_pos_x ? "limit_upper" : nullptr; +} + +void HingeJoint::applyState(const JointState& state) { + current_angle = glm::degrees(state.angle); +} + +void HingeJoint::updateInspectorMessages() { + Joint3D::updateInspectorMessages(); + + static const toast::NodeMessage limits_message { + .severity = toast::NodeMessage::warning, + .id = 14, + .text = "The lower limit is above the upper limit", + }; + static const toast::NodeMessage axis_message { + .severity = toast::NodeMessage::warning, + .id = 15, + .text = "Another hinge between the same two nodes turns around a different axis, the two would fight", + }; + + if (limit_enabled && limit_lower > limit_upper) { + addInspectorMessage(limits_message); + } else { + removeInspectorMessage(limits_message); + } + + // Two hinges hold a door only when their axes are the same line + bool bent = false; + if (parent().exists()) { + syncWorldTransform(); + const glm::vec3 axis = frameRotation() * glm::vec3(0.0f, 0.0f, 1.0f); + for (const auto& sibling : parent()->children()) { + const auto other = sibling.as(); + if (other.exists() && not(other == box().as()) && other->node_a == node_a && other->node_b == node_b) { + other->syncWorldTransform(); + const glm::vec3 other_axis = other->frameRotation() * glm::vec3(0.0f, 0.0f, 1.0f); + const glm::vec3 apart = other->world_position - world_position; + const bool parallel = std::abs(glm::dot(axis, other_axis)) > 0.999f; + const bool same_line = glm::length(glm::cross(apart, axis)) < 0.01f * std::max(1.0f, glm::length(apart)); + bent = bent || not parallel || not same_line; + } + } + } + if (bent) { + addInspectorMessage(axis_message); + } else { + removeInspectorMessage(axis_message); + } +} + +void HingeJoint::drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const { + const glm::vec3 axis = axes[2]; + debug::drawLine(origin - (axis * 0.4f), origin + (axis * 0.4f), color); + if (not limit_enabled) { + return; + } + + constexpr float k_radius = k_arc_radius; + constexpr int k_segments = 16; + const float lower = glm::radians(limit_lower); + const float upper = glm::radians(limit_upper); + glm::vec3 previous = origin + (glm::angleAxis(lower, axis) * axes[0] * k_radius); + debug::drawLine(origin, previous, color); + for (int segment = 1; segment <= k_segments; ++segment) { + const float angle = lower + ((upper - lower) * static_cast(segment) / static_cast(k_segments)); + const glm::vec3 point = origin + (glm::angleAxis(angle, axis) * axes[0] * k_radius); + debug::drawLine(previous, point, color); + previous = point; + } + debug::drawLine(origin, previous, color); +} + +} diff --git a/engine/src/toast/physics/nodes/hinge_joint.hpp b/engine/src/toast/physics/nodes/hinge_joint.hpp new file mode 100644 index 000000000..44f3561e2 --- /dev/null +++ b/engine/src/toast/physics/nodes/hinge_joint.hpp @@ -0,0 +1,84 @@ +/** + * @file hinge_joint.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Like a door hinge + */ + +#pragma once +#include "joint3d.hpp" + +namespace physics { + +class [[ToastNode, Icon("HingeJoint"), Color("Green")]] TOAST_API HingeJoint : public physics::Joint3D { +public: + /// Share of the error that is closed every step + [[Reflect, Range(0.0, 1.0)]] + float bias = 0.3f; + + /// Torque that holds the hinge against turning per newton it carries, the friction of the material times the radius of + /// the pin. Zero swings forever + [[Reflect, Unit("m"), Range(0.0, 0.1)]] + float friction = k_default_joint_friction; + + [[Reflect, Group("Angular Limit")]] + bool limit_enabled = false; + + /// Angles go around the axis from the x of the joint to the body that turns + [[Reflect, Group("Angular Limit"), Unit("°")]] + float limit_lower = -90.0f; + + [[Reflect, Group("Angular Limit"), Unit("°")]] + float limit_upper = 90.0f; + + [[Reflect, Group("Angular Limit"), Range(0.0, 1.0)]] + float limit_bias = 0.3f; + + [[Reflect, Group("Angular Limit"), Range(0.0, 1.0)]] + float limit_restitution = 0.0f; + + [[Reflect, Group("Motor")]] + bool motor_enabled = false; + + [[Reflect, Group("Motor"), Unit("°/s")]] + float motor_target_velocity = 0.0f; + + /// Most the motor pushes with in one step + [[Reflect, Group("Motor")]] + float motor_max_impulse = 1.0f; + + [[Reflect, Group("State"), ReadOnly, Unit("°")]] + float current_angle = 0.0f; + + /// Turns the motor on at the speed with the push it is allowed + [[Reflect]] + void setMotor(float target_velocity_degrees, float max_impulse); + + [[nodiscard]] + auto makeParams() const -> JointParams override; + + [[nodiscard]] + auto limitHandles() const -> LimitHandles override; + + [[nodiscard]] + auto limitsAreAngles() const -> bool override { + return true; + } + + [[nodiscard]] + auto limitValue(toast::GizmoHandle handle) const -> float override; + void setLimitValue(toast::GizmoHandle handle, float value) override; + + [[nodiscard]] + auto limitField(toast::GizmoHandle handle) const -> const char* override; + + static constexpr float k_arc_radius = 0.35f; + +protected: + void drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const override; + + void applyState(const JointState& state) override; + void updateInspectorMessages() override; +}; + +} diff --git a/engine/src/toast/physics/nodes/joint3d.cpp b/engine/src/toast/physics/nodes/joint3d.cpp new file mode 100644 index 000000000..bdddc3b1b --- /dev/null +++ b/engine/src/toast/physics/nodes/joint3d.cpp @@ -0,0 +1,164 @@ +#include "joint3d.hpp" + +#include "rigidbody.hpp" + +#include +#include +#include +#include +#include + +namespace physics { + +namespace { + +const glm::vec4 k_color {1.0f, 0.6f, 0.1f, 1.0f}; +const glm::vec4 k_broken_color {0.5f, 0.5f, 0.5f, 1.0f}; + +auto hasBody(const toast::Box& node) -> bool { + for (toast::Box current = toast::Box(node); current.exists(); current = current->parent()) { + if (current.as().exists() || current.as().exists()) { + return true; + } + } + return false; +} + +} + +void Joint3D::updateInspectorMessages() { + static const toast::NodeMessage empty_message { + .severity = toast::NodeMessage::warning, + .id = 10, + .text = "A joint needs node_a or node_b to hold something", + }; + static const toast::NodeMessage same_message { + .severity = toast::NodeMessage::error, + .id = 11, + .text = "node_a and node_b are the same node", + }; + + if (node_a.exists() || node_b.exists()) { + removeInspectorMessage(empty_message); + } else { + addInspectorMessage(empty_message); + } + + if (node_a.exists() && node_b.exists() && node_a == node_b) { + addInspectorMessage(same_message); + } else { + removeInspectorMessage(same_message); + } + static const toast::NodeMessage no_body_a_message { + .severity = toast::NodeMessage::warning, + .id = 12, + .text = "node_a can't find a rigidbody", + }; + static const toast::NodeMessage no_body_b_message { + .severity = toast::NodeMessage::warning, + .id = 13, + .text = "node_b can't find a rigidbody", + }; + if (node_a.exists() && not hasBody(node_a)) { + addInspectorMessage(no_body_a_message); + } else { + removeInspectorMessage(no_body_a_message); + } + if (node_b.exists() && not hasBody(node_b)) { + addInspectorMessage(no_body_b_message); + } else { + removeInspectorMessage(no_body_b_message); + } +} + +void Joint3D::init() { + if (renderer::VulkanRenderer::instance) { + renderer::VulkanRenderer::instance->registerDebugDraw(this, [](toast::Node3D& node) { + static_cast(node).drawDebug(); + }); + } +} + +void Joint3D::destroy() { + if (renderer::VulkanRenderer::instance) { + renderer::VulkanRenderer::instance->unregisterDebugDraw(this); + } +} + +void Joint3D::begin() { + if (not m_registered && participatesIn(toast::NodeOwnerParticipation::gameplay_tick)) { + m_registered = true; + Simulator::registerJointNode(*this); + } +} + +void Joint3D::end() { + if (m_registered) { + m_registered = false; + Simulator::unregisterJointNode(*this); + } +} + +void Joint3D::breakJoint() { + Simulator::breakJointNode(*this); +} + +void Joint3D::repair() { + Simulator::repairJointNode(*this); +} + +void Joint3D::publishState(const JointState& state) { + current_force = state.force; + current_torque = state.torque; + applyState(state); +} + +void Joint3D::markBroken() { + broken = true; + current_force = 0.0f; + current_torque = 0.0f; + joint_broken.fire(); +} + +auto Joint3D::frameRotation() const -> glm::quat { + syncWorldTransform(); + const glm::mat4& world = getWorldTransform(); + glm::vec3 x {world[0]}; + glm::vec3 y {world[1]}; + glm::vec3 z {world[2]}; + + const auto usable = [](const glm::vec3& axis) { return glm::dot(axis, axis) > 1.0e-12f; }; + if (not usable(x) && usable(y) && usable(z)) { + x = glm::cross(y, z); + } else if (not usable(y) && usable(x) && usable(z)) { + y = glm::cross(z, x); + } + if (not usable(x) || not usable(y)) { + return world_rotation; + } + + x = glm::normalize(x); + y = glm::normalize(y - (x * glm::dot(x, y))); + return glm::normalize(glm::quat_cast(glm::mat3 {x, y, glm::cross(x, y)})); +} + +void Joint3D::drawDebug() { + ZoneScoped; + if (not draw_debug) { + return; + } + syncWorldTransform(); + + const glm::vec4 color = broken ? k_broken_color : k_color; + const glm::mat3 axes = glm::mat3_cast(frameRotation()); + drawGizmo(world_position, axes, color); + const glm::vec4 link {color.r, color.g, color.b, 0.35f}; + for (const toast::Box& end : {node_a, node_b}) { + if (end.exists()) { + end->syncWorldTransform(); + debug::drawLine(world_position, end->world_position, link); + } + } +} + +} diff --git a/engine/src/toast/physics/nodes/joint3d.hpp b/engine/src/toast/physics/nodes/joint3d.hpp new file mode 100644 index 000000000..e09b97ea2 --- /dev/null +++ b/engine/src/toast/physics/nodes/joint3d.hpp @@ -0,0 +1,130 @@ +/** + * @file joint3d.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Interface for all joint types + */ + +#pragma once +#include "../joint.hpp" + +#include +#include +#include +#include + +namespace physics { + +class Simulator; + +struct LimitHandle { + toast::GizmoHandle handle = toast::GizmoHandle::none; + glm::vec3 local {0.0f}; +}; + +using LimitHandles = std::pair, uint32_t>; + +class [[ToastNode, Hidden, Interface, Color("Green")]] TOAST_API Joint3D : public toast::Node3D { + friend class Simulator; + +public: + /// The joint broke because of what it held or because a script asked for it + signals::Signal<> joint_broken; + + [[Reflect]] + toast::Box node_a; + + [[Reflect]] + toast::Box node_b; + + /// The two bodies do not collide with each other while the joint holds them + [[Reflect]] + bool exclude_nodes_from_collision = true; + + [[Reflect, ReadOnly]] + bool broken = false; + + [[Reflect]] + bool draw_debug = true; + + /// 0 never breaks + [[Reflect, Group("Breaking"), Unit("N")]] + float break_force = 0.0f; + + [[Reflect, Group("Breaking"), Unit("N•m")]] + float break_torque = 0.0f; + + [[Reflect, Group("Breaking"), ReadOnly, Unit("N")]] + float current_force = 0.0f; + + [[Reflect, Group("Breaking"), ReadOnly, Unit("N•m")]] + float current_torque = 0.0f; + + /// Lets go when the voxels around its end in a voxel body are destroyed + [[Reflect, Group("Voxel Anchor")]] + bool break_when_anchor_destroyed = true; + + /// How many voxels each way around the end are looked at + [[Reflect, Group("Voxel Anchor"), Range(1, 8)]] + int anchor_radius_voxels = 2; + + /// Percentage that needs to be kept for it to still be alive + [[Reflect, Group("Voxel Anchor"), Range(0.0, 1.0)]] + float anchor_min_remaining = 0.5f; + + [[Reflect]] + void breakJoint(); + + [[Reflect]] + void repair(); + + [[nodiscard]] + auto frameRotation() const -> glm::quat; + + [[nodiscard]] + virtual auto makeParams() const -> JointParams { + return PinParams {}; + } + + [[nodiscard]] + virtual auto limitHandles() const -> LimitHandles { + return {{}, 0}; + } + + [[nodiscard]] + virtual auto limitsAreAngles() const -> bool { + return false; + } + + [[nodiscard]] + virtual auto limitValue(toast::GizmoHandle handle) const -> float { + return 0.0f; + } + + virtual void setLimitValue(toast::GizmoHandle handle, float value) { } + + [[nodiscard]] + virtual auto limitField(toast::GizmoHandle handle) const -> const char* { + return nullptr; + } + +protected: + virtual void drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const { } + + virtual void applyState(const JointState& state) { } + + void updateInspectorMessages() override; + +private: + void init(); + void destroy(); + void begin(); + void end(); + void drawDebug(); + void publishState(const JointState& state); + void markBroken(); + + bool m_registered = false; +}; + +} diff --git a/engine/src/toast/physics/nodes/pin_joint.cpp b/engine/src/toast/physics/nodes/pin_joint.cpp new file mode 100644 index 000000000..e9919dc8b --- /dev/null +++ b/engine/src/toast/physics/nodes/pin_joint.cpp @@ -0,0 +1,11 @@ +#include "pin_joint.hpp" + +#include + +namespace physics { + +void PinJoint::drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const { + debug::drawSphere(origin, 0.05f, color, 12); +} + +} diff --git a/engine/src/toast/physics/nodes/pin_joint.hpp b/engine/src/toast/physics/nodes/pin_joint.hpp new file mode 100644 index 000000000..c528d9577 --- /dev/null +++ b/engine/src/toast/physics/nodes/pin_joint.hpp @@ -0,0 +1,35 @@ +/** + * @file pin_joint.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Bodies are maintained by one single point + */ + +#pragma once +#include "joint3d.hpp" + +namespace physics { + +class [[ToastNode, Color("Green")]] TOAST_API PinJoint : public physics::Joint3D { +public: + /// Percentage of the error between the two points + [[Reflect, Range(0.0, 1.0)]] + float bias = 0.3f; + + /// Most the joint pushes with in one iteration + [[Reflect]] + float impulse_clamp = 0.0f; + + [[Reflect, Unit("m"), Range(0.0, 0.1)]] + float friction = k_default_joint_friction; + + [[nodiscard]] + auto makeParams() const -> JointParams override { + return PinParams {.bias = bias, .impulse_clamp = impulse_clamp, .friction = friction}; + } + +protected: + void drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const override; +}; + +} diff --git a/engine/src/toast/physics/nodes/slider_joint.cpp b/engine/src/toast/physics/nodes/slider_joint.cpp new file mode 100644 index 000000000..9badd5790 --- /dev/null +++ b/engine/src/toast/physics/nodes/slider_joint.cpp @@ -0,0 +1,19 @@ +#include "slider_joint.hpp" + +#include + +namespace physics { + +void SliderJoint::drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const { + // The rail it runs on with a stop at each end + const glm::vec3 axis = axes[0]; + const glm::vec3 start = origin + (axis * linear_limit_lower); + const glm::vec3 end = origin + (axis * linear_limit_upper); + debug::drawLine(start, end, color); + for (const glm::vec3& stop : {start, end}) { + debug::drawLine(stop - (axes[1] * 0.05f), stop + (axes[1] * 0.05f), color); + debug::drawLine(stop - (axes[2] * 0.05f), stop + (axes[2] * 0.05f), color); + } +} + +} diff --git a/engine/src/toast/physics/nodes/slider_joint.hpp b/engine/src/toast/physics/nodes/slider_joint.hpp new file mode 100644 index 000000000..e30a547db --- /dev/null +++ b/engine/src/toast/physics/nodes/slider_joint.hpp @@ -0,0 +1,79 @@ +/** + * @file slider_joint.hpp + * @author Xein + * @date 31 Oct 2026 + * @brief Lets two bodies slide along one axis + */ + +#pragma once +#include "joint3d.hpp" + +#include + +namespace physics { + +class [[ToastNode, Icon("SliderJoint"), Color("Green")]] TOAST_API SliderJoint : public physics::Joint3D { +public: + /// Percentage of the error that is closed every step + [[Reflect, Range(0.0, 1.0)]] + float bias = 0.3f; + + [[Reflect, Unit("m")]] + float linear_limit_lower = -1.0f; + + [[Reflect, Unit("m")]] + float linear_limit_upper = 1.0f; + + [[Reflect, Range(0.0, 1.0)]] + float limit_restitution = 0.0f; + + [[Reflect, ReadOnly, Unit("m")]] + float current_distance = 0.0f; + + [[nodiscard]] + auto makeParams() const -> JointParams override { + return SliderParams { + .bias = bias, + .linear_limit_lower = linear_limit_lower, + .linear_limit_upper = linear_limit_upper, + .limit_restitution = limit_restitution + }; + } + + [[nodiscard]] + auto limitHandles() const -> LimitHandles override { + return { + {LimitHandle {.handle = toast::GizmoHandle::size_neg_x, .local = {linear_limit_lower, 0.0f, 0.0f}}, + LimitHandle {.handle = toast::GizmoHandle::size_pos_x, .local = {linear_limit_upper, 0.0f, 0.0f}}}, + 2, + }; + } + + [[nodiscard]] + auto limitValue(toast::GizmoHandle handle) const -> float override { + return handle == toast::GizmoHandle::size_neg_x ? linear_limit_lower : linear_limit_upper; + } + + void setLimitValue(toast::GizmoHandle handle, float value) override { + if (handle == toast::GizmoHandle::size_neg_x) { + linear_limit_lower = std::min(value, linear_limit_upper); + } else if (handle == toast::GizmoHandle::size_pos_x) { + linear_limit_upper = std::max(value, linear_limit_lower); + } + } + + [[nodiscard]] + auto limitField(toast::GizmoHandle handle) const -> const char* override { + if (handle == toast::GizmoHandle::size_neg_x) { + return "linear_limit_lower"; + } + return handle == toast::GizmoHandle::size_pos_x ? "linear_limit_upper" : nullptr; + } + +protected: + void drawGizmo(const glm::vec3& origin, const glm::mat3& axes, const glm::vec4& color) const override; + + void applyState(const JointState& state) override { current_distance = state.position; } +}; + +} diff --git a/engine/src/toast/physics/physics_settings.cpp b/engine/src/toast/physics/physics_settings.cpp index c8a797614..a4251e37d 100644 --- a/engine/src/toast/physics/physics_settings.cpp +++ b/engine/src/toast/physics/physics_settings.cpp @@ -96,6 +96,17 @@ void registerPhysicsSettings() { .step = 1.0}, &Tunables::position_iterations ); + uint_setting( + "physics.solver.joint_substeps", + 4, + {.label = "Joint substeps", + .category = "Solver", + .description = "Times a step is split for bodies held by joints so swinging keeps its speed", + .min = 1.0, + .max = 16.0, + .step = 1.0}, + &Tunables::joint_substeps + ); flt("physics.solver.penetration_slop", 0.005, {.label = "Penetration slop", @@ -566,6 +577,17 @@ void registerPhysicsSettings() { .step = 1.0}, &Tunables::min_wave_constraints_for_dispatch ); + uint_setting( + "physics.jobs.min_wave_joints_for_dispatch", + 64, + {.label = "Min wave joints per dispatch", + .category = "Jobs", + .description = "Joint waves below this size solve inline", + .min = 1.0, + .max = 65536.0, + .step = 1.0}, + &Tunables::min_wave_joints_for_dispatch + ); uint_setting( "physics.jobs.box_voxel_split_min_voxels", 500000, diff --git a/engine/src/toast/physics/physics_settings.hpp b/engine/src/toast/physics/physics_settings.hpp index bf280b206..03e4b4567 100644 --- a/engine/src/toast/physics/physics_settings.hpp +++ b/engine/src/toast/physics/physics_settings.hpp @@ -66,11 +66,13 @@ struct Tunables { uint32_t min_shapes_per_query_job = 16; uint32_t min_bodies_per_job = 64; uint32_t min_wave_constraints_for_dispatch = 512; + uint32_t min_wave_joints_for_dispatch = 64; uint32_t box_voxel_split_min_voxels = 500000; uint32_t voxel_voxel_split_min_voxels = 250000; uint32_t voxel_pair_split_regions = 8; uint32_t sweep_refine_iterations = 6; uint32_t position_iterations = 3; + uint32_t joint_substeps = 4; float air_density = 1.225f; glm::vec3 wind = {0.0f, 0.0f, 0.0f}; float wind_shelter_distance = 64.0f; diff --git a/engine/src/toast/physics/simulator.cpp b/engine/src/toast/physics/simulator.cpp index 54e5282cf..74efba273 100644 --- a/engine/src/toast/physics/simulator.cpp +++ b/engine/src/toast/physics/simulator.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -699,6 +700,7 @@ void Simulator::tick() { reapFragments(); syncEnabledState(); + syncJointNodes(); applyDamageCommands(); const auto after_damage = std::chrono::steady_clock::now(); m_profile.damage_apply_ms = elapsed_ms(tick_start, after_damage); @@ -711,15 +713,19 @@ void Simulator::tick() { spawnBudgetedFragments(); enforceFragmentBudget(dt); despawnSettledFragments(dt); + checkJointAnchors(); integrate(dt); checkTriggers(); { PhaseScope worker_phase {*this, SimulationPhase::mutation, SimulationPhase::worker_execution}; - const CollisionWorldView world {.bodies = m_bodies, .shapes = m_shapes, .voxel_shapes = m_voxel_shapes}; + const CollisionWorldView world { + .bodies = m_bodies, .shapes = m_shapes, .voxel_shapes = m_voxel_shapes, .excluded_pairs = m_excluded_pairs + }; const auto candidates = m_broad_phase.findPairs(world); m_manifolds = generateManifoldsAsync(world, candidates); } + adjustJointContacts(m_manifolds); const auto after_narrow = std::chrono::steady_clock::now(); m_profile.narrow_phase_ms = elapsed_ms(after_connectivity, after_narrow); @@ -728,12 +734,14 @@ void Simulator::tick() { m_profile.cache_update_ms = elapsed_ms(after_narrow, after_cache); wakeDisturbedSleepers(dt); + wakeJointPartners(); unlockDisturbedFragments(); const auto after_wake = std::chrono::steady_clock::now(); m_profile.wake_groups_ms = elapsed_ms(after_cache, after_wake); // resolve auto constraints = prepareConstraints(m_manifolds); + prepareJoints(dt); const auto after_prepare = std::chrono::steady_clock::now(); m_profile.prepare_constraints_ms = elapsed_ms(after_wake, after_prepare); @@ -742,6 +750,7 @@ void Simulator::tick() { m_profile.build_islands_ms = elapsed_ms(after_prepare, after_islands); solveIslands(islands); + breakOverloadedJoints(dt); const auto after_solve = std::chrono::steady_clock::now(); m_profile.island_solve_ms = elapsed_ms(after_islands, after_solve); @@ -769,6 +778,7 @@ void Simulator::tick() { // push poses after simulation settles publishTransforms(); + publishJoints(); publishVoxelRenderRecords(); m_profile.tick_ms = elapsed_ms(tick_start, std::chrono::steady_clock::now()); publishProfile(islands); @@ -1334,11 +1344,6 @@ void Simulator::spawnBudgetedFragments() { namespace { -/// Sleeping bodies are immovable in the solver -auto movable(const Body& body) -> bool { - return body.inverse_mass > 0.0f && (body.awake || body.type != BodyType::dynamic_body); -} - /// Approach speed into a sleeper that wakes it in sleep thresholds constexpr float k_wake_approach = 0.75f; @@ -1969,6 +1974,19 @@ void Simulator::wakeBodiesTouching(BodyID id) { wakeBody(manifold.pair.a.body); } } + + if (m_joint_count > 0) { + for (const JointSlot& slot : m_joints) { + if (not slot.occupied) { + continue; + } + if (slot.joint.descriptor.body_a == id) { + wakeBody(slot.joint.descriptor.body_b); + } else if (slot.joint.descriptor.body_b == id) { + wakeBody(slot.joint.descriptor.body_a); + } + } + } } void Simulator::wakeBodiesTouching(ShapeID id) { @@ -1992,6 +2010,59 @@ void Simulator::markSupportedBodies() { m_body_supported[manifold.pair.a.body.slot] = 1; m_body_supported[manifold.pair.b.body.slot] = 1; } + + // A joint holds up what hangs from it + const auto holds_up = [this](BodyID id) { + const Body* body = tryGetBody(id); + return body == nullptr || body->type != BodyType::dynamic_body || m_body_supported[id.slot] != 0; + }; + bool changed = m_joint_count > 0; + for (size_t pass = 0; changed && pass <= m_joint_count; ++pass) { + changed = false; + for (const JointSlot& slot : m_joints) { + if (not slot.occupied) { + continue; + } + const BodyID a = slot.joint.descriptor.body_a; + const BodyID b = slot.joint.descriptor.body_b; + const Body* body_a = tryGetBody(a); + const Body* body_b = tryGetBody(b); + if (body_a != nullptr && body_a->type == BodyType::dynamic_body && m_body_supported[a.slot] == 0 && holds_up(b)) { + m_body_supported[a.slot] = 1; + changed = true; + } + if (body_b != nullptr && body_b->type == BodyType::dynamic_body && m_body_supported[b.slot] == 0 && holds_up(a)) { + m_body_supported[b.slot] = 1; + changed = true; + } + } + } +} + +void Simulator::wakeJointPartners() { + if (m_joint_count == 0) { + return; + } + + // A chain of joints wakes from one end to the other + bool changed = true; + for (size_t pass = 0; changed && pass <= m_joint_count; ++pass) { + changed = false; + for (const JointSlot& slot : m_joints) { + if (not slot.occupied) { + continue; + } + const Body* body_a = tryGetBody(slot.joint.descriptor.body_a); + const Body* body_b = tryGetBody(slot.joint.descriptor.body_b); + if (body_a == nullptr || body_b == nullptr || body_a->type != BodyType::dynamic_body || + body_b->type != BodyType::dynamic_body || not body_a->enabled || not body_b->enabled || + body_a->awake == body_b->awake) { + continue; + } + wakeBody(body_a->awake ? slot.joint.descriptor.body_b : slot.joint.descriptor.body_a); + changed = true; + } + } } auto Simulator::sleepScale(size_t slot) const -> float { @@ -2512,7 +2583,11 @@ auto Simulator::solveNormal(Constraint& constraint, Body& body_a, Body& body_b) return false; } - float impulse_delta = (constraint.restitution_bias - normal_speed) * constraint.normal_mass; + if (constraint.closing_speed > 0.0f && constraint.restitution_bias > 0.0f && normal_speed < -constraint.closing_speed) { + constraint.closing_speed = 0.0f; + } + const float target_speed = constraint.closing_speed > 0.0f ? -constraint.closing_speed : constraint.restitution_bias; + float impulse_delta = (target_speed - normal_speed) * constraint.normal_mass; float old_impulse = constraint.accumulated_normal_impulse; constraint.accumulated_normal_impulse = std::max(0.0f, old_impulse + impulse_delta); float applied_impulse = constraint.accumulated_normal_impulse - old_impulse; @@ -2967,6 +3042,30 @@ void Simulator::updateSleeping(float dt) { m_contact_supported[cached.pair.b.body.slot] = 1; } } + + const auto holds_up = [this](BodyID id) { + const Body* body = tryGetBody(id); + return body == nullptr || body->type != BodyType::dynamic_body || m_contact_supported[id.slot] != 0; + }; + bool changed = m_joint_count > 0; + for (size_t pass = 0; changed && pass <= m_joint_count; ++pass) { + changed = false; + for (const JointSlot& slot : m_joints) { + if (not slot.occupied) { + continue; + } + for (const auto& [end, other] : { + std::pair {slot.joint.descriptor.body_a, slot.joint.descriptor.body_b}, + std::pair {slot.joint.descriptor.body_b, slot.joint.descriptor.body_a} + }) { + const Body* body = tryGetBody(end); + if (body != nullptr && body->type == BodyType::dynamic_body && m_contact_supported[end.slot] == 0 && holds_up(other)) { + m_contact_supported[end.slot] = 1; + changed = true; + } + } + } + } } m_sleep_ready.assign(m_bodies.size(), 0); @@ -3035,6 +3134,51 @@ void Simulator::updateSleeping(float dt) { } } + if (m_joint_count > 0) { + // Bodies in a joint sleep only when everyone is ready + std::vector parents(m_bodies.size()); + std::iota(parents.begin(), parents.end(), size_t {0}); + const auto find_root = [&parents](size_t index) { + while (parents[index] != index) { + parents[index] = parents[parents[index]]; + index = parents[index]; + } + return index; + }; + const auto jointed_dynamic = [this](const JointSlot& slot) { + const Body* body_a = tryGetBody(slot.joint.descriptor.body_a); + const Body* body_b = tryGetBody(slot.joint.descriptor.body_b); + return slot.occupied && body_a != nullptr && body_b != nullptr && body_a->type == BodyType::dynamic_body && + body_b->type == BodyType::dynamic_body; + }; + + for (const JointSlot& slot : m_joints) { + if (jointed_dynamic(slot)) { + parents[find_root(slot.joint.descriptor.body_a.slot)] = find_root(slot.joint.descriptor.body_b.slot); + } + } + + std::vector group_ready(m_bodies.size(), 1); + const auto vote = [&](BodyID id) { + const Body* body = tryGetBody(id); + if (body != nullptr && body->enabled && body->awake && (m_sleep_ready[id.slot] == 0 || m_sleep_blocked[id.slot] != 0)) { + group_ready[find_root(id.slot)] = 0; + } + }; + for (const JointSlot& slot : m_joints) { + if (jointed_dynamic(slot)) { + vote(slot.joint.descriptor.body_a); + vote(slot.joint.descriptor.body_b); + } + } + for (const JointSlot& slot : m_joints) { + if (jointed_dynamic(slot) && group_ready[find_root(slot.joint.descriptor.body_a.slot)] == 0) { + m_sleep_ready[slot.joint.descriptor.body_a.slot] = 0; + m_sleep_ready[slot.joint.descriptor.body_b.slot] = 0; + } + } + } + for (size_t index = 0; index < m_bodies.size(); ++index) { if (m_sleep_ready[index] != 0 && m_sleep_blocked[index] == 0) { sleepBody(BodyID {.slot = static_cast(index), .generation = m_bodies[index].generation}); @@ -3467,6 +3611,15 @@ void Simulator::destroyBody(BodyID body) { return; } + if (m_joint_count > 0) { + for (uint32_t index = 0; index < m_joints.size(); ++index) { + const JointSlot& joint_slot = m_joints[index]; + if (joint_slot.occupied && (joint_slot.joint.descriptor.body_a == body || joint_slot.joint.descriptor.body_b == body)) { + destroyJoint({.slot = index, .generation = joint_slot.generation}); + } + } + } + for (uint32_t index = 0; index < m_shapes.size(); ++index) { ShapeSlot& shape_slot = m_shapes[index]; if (shape_slot.occupied && shape_slot.shape.owner == body) { @@ -3484,6 +3637,231 @@ void Simulator::destroyBody(BodyID body) { m_free_body_slots.emplace_back(body.slot); } +auto Simulator::createJoint(const JointDescriptor& descriptor) -> JointID { + ZoneScopedN("physics::CreateJoint"); + if (not mainThreadMutationAllowed()) { + return {}; + } + + // An end without a body is the world + JointDescriptor made = descriptor; + const bool a_is_body = valid(made.body_a); + const bool b_is_body = valid(made.body_b); + if (not a_is_body && not b_is_body) { + TOAST_WARN("Physics", "Rejected joint without a valid body"); + return {}; + } + const bool holds_world = not(a_is_body && b_is_body); + if (holds_world) { + made.body_b = a_is_body ? made.body_a : made.body_b; + made.body_a = {}; + } else if (made.body_a == made.body_b) { + TOAST_WARN("Physics", "Rejected joint between a body and itself"); + return {}; + } else if (tryGetBody(made.body_a)->type == BodyType::dynamic_body && tryGetBody(made.body_b)->type != BodyType::dynamic_body) { + std::swap(made.body_a, made.body_b); + } + + const float frame_length_squared = glm::dot(made.frame, made.frame); + if (not std::isfinite(frame_length_squared) || frame_length_squared <= 0.0f || not std::isfinite(made.anchor.x) || + not std::isfinite(made.anchor.y) || not std::isfinite(made.anchor.z)) { + TOAST_WARN("Physics", "Rejected joint with a non-finite anchor or a zero-length frame"); + return {}; + } + made.frame = glm::normalize(made.frame); + + Body* body_a = holds_world ? &m_world_body : tryGetBody(made.body_a); + Body* body_b = tryGetBody(made.body_b); + + Joint made_joint = _detail::makeJoint(made, *body_a, *body_b); + if (not holds_world) { + made_joint.voxel_end_a = voxelEndOf(made.body_a, made_joint.frame_a, made.voxel_anchor); + } + made_joint.voxel_end_b = voxelEndOf(made.body_b, made_joint.frame_b, made.voxel_anchor); + JointSlot joint_slot {.joint = made_joint, .occupied = true}; + JointID id; + if (not m_free_joint_slots.empty()) { + id.slot = m_free_joint_slots.back(); + m_free_joint_slots.pop_back(); + joint_slot.generation = m_joints[id.slot].generation; + m_joints[id.slot] = joint_slot; + } else { + id.slot = static_cast(m_joints.size()); + m_joints.emplace_back(joint_slot); + } + id.generation = m_joints[id.slot].generation; + ++m_joint_count; + + if (made.exclude_collision && not holds_world) { + addExclusion(made.body_a, made.body_b); + } + wakeBody(made.body_a); + wakeBody(made.body_b); + return id; +} + +void Simulator::destroyJoint(JointID joint) { + ZoneScopedN("physics::DestroyJoint"); + if (not mainThreadMutationAllowed() || not valid(joint)) { + return; + } + + JointSlot& slot = m_joints[joint.slot]; + const JointDescriptor descriptor = slot.joint.descriptor; + slot.occupied = false; + slot.joint = {}; + ++slot.generation; + if (slot.generation == 0) { + ++slot.generation; + } + m_free_joint_slots.emplace_back(joint.slot); + --m_joint_count; + + if (descriptor.exclude_collision && valid(descriptor.body_a) && valid(descriptor.body_b)) { + removeExclusion(descriptor.body_a, descriptor.body_b); + } + // What hung from it falls + wakeBody(descriptor.body_a); + wakeBody(descriptor.body_b); +} + +auto Simulator::valid(JointID joint) const -> bool { + return joint.slot < m_joints.size() && m_joints[joint.slot].occupied && m_joints[joint.slot].generation == joint.generation; +} + +auto Simulator::jointState(JointID joint) const -> std::optional { + if (not valid(joint)) { + return std::nullopt; + } + const Joint& value = m_joints[joint.slot].joint; + return JointState {.angle = value.hinge_angle, .position = value.slider_position}; +} + +auto Simulator::setJointParams(JointID joint, const JointParams& params) -> bool { + if (not valid(joint)) { + return false; + } + JointParams& current = m_joints[joint.slot].joint.descriptor.params; + if (current.index() != params.index()) { + return false; + } + current = params; + wakeBody(m_joints[joint.slot].joint.descriptor.body_a); + wakeBody(m_joints[joint.slot].joint.descriptor.body_b); + return true; +} + +void Simulator::addExclusion(BodyID a, BodyID b) { + const uint64_t key = bodyPairKey(a, b); + m_excluded_pairs.insert(std::upper_bound(m_excluded_pairs.begin(), m_excluded_pairs.end(), key), key); +} + +void Simulator::removeExclusion(BodyID a, BodyID b) { + const uint64_t key = bodyPairKey(a, b); + const auto found = std::lower_bound(m_excluded_pairs.begin(), m_excluded_pairs.end(), key); + if (found != m_excluded_pairs.end() && *found == key) { + m_excluded_pairs.erase(found); + } +} + +// The ends of a joint that collide with each other +// - A hinge or a pin turns in a knuckle around its pin, so the two ends it holds never press on each other right there. A +// contact between them that close to the pin fights the joint, the joint pulls the ends back in and the contact pushes +// them out, and each push lifts the bodies a little for free, which keeps them swinging forever +// - The other end is a stop like the limits, a hinge or a slider bounces off it at least as much as off its limits +void Simulator::adjustJointContacts(std::vector& manifolds) { + if (m_joint_count == 0 || manifolds.empty()) { + return; + } + + for (const JointSlot& slot : m_joints) { + const Joint& joint = slot.joint; + if (not slot.occupied || joint.descriptor.exclude_collision || std::holds_alternative(joint.descriptor.params)) { + continue; + } + const bool hinge = std::holds_alternative(joint.descriptor.params); + const bool knuckle = hinge || std::holds_alternative(joint.descriptor.params); + float restitution = 0.0f; + if (const auto* hinge_params = std::get_if(&joint.descriptor.params); hinge_params != nullptr) { + restitution = hinge_params->limit_restitution; + } else if (const auto* slider_params = std::get_if(&joint.descriptor.params); slider_params != nullptr) { + restitution = slider_params->limit_restitution; + } + const Body* body_a = tryGetBody(joint.descriptor.body_a); + const Body* body_b = tryGetBody(joint.descriptor.body_b); + if (body_a == nullptr || body_b == nullptr) { + continue; + } + + const glm::vec3 anchor = body_a->position + (body_a->rotation * joint.frame_a.position); + const glm::vec3 axis = body_a->rotation * joint.frame_a.rotation * glm::vec3(0.0f, 0.0f, 1.0f); + const auto in_knuckle = [&](const ContactPoint& contact) { + const glm::vec3 offset = contact.position - anchor; + const float distance = hinge ? glm::length(glm::cross(offset, axis)) : glm::length(offset); + return knuckle && distance < k_joint_knuckle_radius; + }; + + for (Manifold& manifold : manifolds) { + const bool between = (manifold.pair.a.body == joint.descriptor.body_a && manifold.pair.b.body == joint.descriptor.body_b) || + (manifold.pair.a.body == joint.descriptor.body_b && manifold.pair.b.body == joint.descriptor.body_a); + if (not between) { + continue; + } + uint8_t kept = 0; + for (uint8_t index = 0; index < manifold.contact_count; ++index) { + ContactPoint& contact = manifold.contacts[index]; + if (not in_knuckle(contact)) { + contact.material.restitution = std::max(contact.material.restitution, restitution); + manifold.contacts[kept++] = contact; + } + } + manifold.contact_count = kept; + } + } + std::erase_if(manifolds, [](const Manifold& manifold) { return manifold.contact_count == 0; }); +} + +void Simulator::jointBodies(const Joint& joint, Body*& body_a, Body*& body_b) { + body_a = tryGetBody(joint.descriptor.body_a); + body_b = tryGetBody(joint.descriptor.body_b); + if (body_a == nullptr) { + body_a = &m_world_body; + } + if (body_b == nullptr) { + body_b = &m_world_body; + } +} + +void Simulator::prepareJoints(float dt) { + m_joint_rows.resize(m_joints.size()); + m_joint_active.assign(m_joints.size(), 0); + if (m_joint_count == 0) { + return; + } + ZoneScopedN("physics::PrepareJoints"); + + const auto awake_dynamic = [](const Body* body) { + return body->type == BodyType::dynamic_body && body->enabled && body->awake; + }; + for (size_t index = 0; index < m_joints.size(); ++index) { + JointSlot& slot = m_joints[index]; + if (not slot.occupied) { + continue; + } + + Body* body_a = nullptr; + Body* body_b = nullptr; + jointBodies(slot.joint, body_a, body_b); + if (body_a == nullptr || not body_a->enabled || not body_b->enabled || not(awake_dynamic(body_a) || awake_dynamic(body_b))) { + continue; + } + + m_joint_rows[index] = _detail::prepareJoint(slot.joint, *body_a, *body_b, dt); + m_joint_active[index] = 1; + ++m_profile.joint_count; + } +} + auto Simulator::createSphere(BodyID owner, const SphereShape& sphere, PhysicsMaterial material) -> ShapeID { ZoneScopedN("physics::CreateSphere"); ZoneValue(static_cast(owner.slot)); @@ -3951,6 +4329,154 @@ auto dragBoxAreaAxis(const glm::vec3& h) -> glm::vec3 { return 4.0f * glm::vec3 {h.y * h.z, h.x * h.z, h.x * h.y}; } +struct PrimitiveMass { + float volume = 0.0f; + glm::vec3 center = {}; + glm::mat3 unit_inertia = {0.0f}; + glm::vec3 half_extents = {}; + float radius = 0.0f; +}; + +[[nodiscard]] +auto primitiveMass(const Shape& shape) -> std::optional { + PrimitiveMass part; + part.center = shape.getLocalCenter(); + + glm::vec3 unit_diagonal = {}; + glm::vec3 local_half = {}; + switch (shape.type) { + case ShapeType::sphere: { + const float radius = shape.sphere.radius; + part.volume = (4.0f / 3.0f) * glm::pi() * radius * radius * radius; + // I = 2/5 m r2 + unit_diagonal = glm::vec3 {0.4f * radius * radius}; + local_half = glm::vec3 {radius}; + part.radius = radius; + break; + } + case ShapeType::box: { + const glm::vec3 size = shape.box.size; + part.volume = size.x * size.y * size.z; + // Ix = 1/12 m (h2 + d2) + unit_diagonal = + glm::vec3 { + (size.y * size.y) + (size.z * size.z), + (size.x * size.x) + (size.z * size.z), + (size.x * size.x) + (size.y * size.y), + } / + 12.0f; + local_half = size * 0.5f; + part.radius = 0.5f * glm::length(size); + break; + } + case ShapeType::capsule: { + const float radius = shape.capsule.radius; + const float height = shape.capsule.height; + part.volume = (glm::pi() * radius * radius * height) + ((4.0f / 3.0f) * glm::pi() * radius * radius * radius); + const float diameter_sq = 4.0f * radius * radius; + const float side = (diameter_sq + (height * height)) / 12.0f; + unit_diagonal = glm::vec3 {side, side, (diameter_sq + diameter_sq) / 12.0f}; + local_half = glm::vec3 {radius, radius, (0.5f * height) + radius}; + part.radius = (0.5f * height) + radius; + break; + } + default: return std::nullopt; + } + + const auto usable = [](float value) { return std::isfinite(value) && value > 0.0f; }; + if (not usable(part.volume) || not usable(unit_diagonal.x) || not usable(unit_diagonal.y) || not usable(unit_diagonal.z)) { + return std::nullopt; + } + + glm::mat3 diagonal = {0.0f}; + diagonal[0][0] = unit_diagonal.x; + diagonal[1][1] = unit_diagonal.y; + diagonal[2][2] = unit_diagonal.z; + const glm::mat3 rotation = glm::mat3_cast(shape.getLocalRotation()); + part.unit_inertia = rotation * diagonal * glm::transpose(rotation); + + const glm::mat3 absolute_rotation {glm::abs(rotation[0]), glm::abs(rotation[1]), glm::abs(rotation[2])}; + part.half_extents = absolute_rotation * local_half; + return part; +} + +[[nodiscard]] +auto combinePrimitiveMass(Body& body, std::span shapes) -> bool { + std::vector parts; + parts.reserve(shapes.size()); + float total_volume = 0.0f; + for (const Shape* shape : shapes) { + std::optional part = primitiveMass(*shape); + if (not part) { + TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid or unknown shape"); + return false; + } + total_volume += part->volume; + parts.push_back(*part); + } + + const float mass = 1.0f / body.inverse_mass; + glm::vec3 center = {}; + for (const PrimitiveMass& part : parts) { + center += part.center * (part.volume / total_volume); + } + + // its own inertia plus steiner + glm::mat3 inertia = {0.0f}; + for (const PrimitiveMass& part : parts) { + const glm::vec3 offset = part.center - center; + const float part_mass = mass * part.volume / total_volume; + inertia += + part_mass * (part.unit_inertia + (glm::dot(offset, offset) * glm::mat3 {1.0f}) - glm::outerProduct(offset, offset)); + } + + const glm::mat3 inverse_inertia = glm::inverse(inertia); + if (not std::isfinite(inverse_inertia[0][0]) || not std::isfinite(inverse_inertia[1][1]) || + not std::isfinite(inverse_inertia[2][2])) { + TOAST_WARN("Physics", "rebuildMassProperties() was aborted: singular inertia"); + return false; + } + + body.local_center_of_mass = center; + body.inverse_inertia_local = inverse_inertia; + const glm::mat3 rotation = glm::mat3_cast(body.rotation); + body.inverse_inertia_world = rotation * body.inverse_inertia_local * glm::transpose(rotation); + + body.extent_radius = 0.0f; + glm::vec3 box_min = glm::vec3 {std::numeric_limits::max()}; + glm::vec3 box_max = glm::vec3 {std::numeric_limits::lowest()}; + for (const PrimitiveMass& part : parts) { + body.extent_radius = std::max(body.extent_radius, glm::length(part.center - center) + part.radius); + box_min = glm::min(box_min, part.center - part.half_extents); + box_max = glm::max(box_max, part.center + part.half_extents); + } + + // Drag + if (shapes.size() == 1) { + const Shape& shape = *shapes[0]; + switch (shape.type) { + case ShapeType::sphere: + body.drag_area_base = glm::pi() * shape.sphere.radius * shape.sphere.radius; + body.drag_coefficient = 0.47f; + break; + case ShapeType::box: + body.drag_area_axis = dragBoxAreaAxis(shape.box.size * 0.5f); + body.drag_coefficient = 1.05f; + break; + case ShapeType::capsule: + // just treat like a box ngl + body.drag_area_axis = dragBoxAreaAxis({shape.capsule.radius, shape.capsule.radius, 0.5f * shape.capsule.height}); + body.drag_coefficient = 0.82f; + break; + default: break; + } + } else { + body.drag_area_axis = dragBoxAreaAxis((box_max - box_min) * 0.5f); + body.drag_coefficient = 1.05f; + } + return true; +} + } void Simulator::rebuildMassProperties(BodyID id) { @@ -3997,8 +4523,6 @@ void Simulator::rebuildMassProperties(BodyID id) { return; } - // TODO: Add support to multishapes - // right now just pick the first one auto* shape = tryGetShape(shapes[0]); if (not shape) { TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid shape"); @@ -4006,68 +4530,6 @@ void Simulator::rebuildMassProperties(BodyID id) { } switch (shape->type) { - case ShapeType::sphere: { - float radius_sq = shape->sphere.radius * shape->sphere.radius; - if (not std::isfinite(radius_sq) || radius_sq <= 0.0f) { - TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid sphere inertia denominator"); - return; - } - - // I = 2/5 m r2 - // inv(I) = 5/(2 m r2) - float inverse_inertia = 2.5f * body->inverse_mass / radius_sq; - body->inverse_inertia_local = {inverse_inertia}; - glm::mat3 rotation = glm::mat3_cast(body->rotation); - body->inverse_inertia_world = rotation * body->inverse_inertia_local * glm::transpose(rotation); - - body->extent_radius = glm::length(shape->sphere.local_center) + shape->sphere.radius; - - // drag - body->drag_area_base = glm::pi() * radius_sq; - body->drag_coefficient = 0.47f; - - break; - } - case ShapeType::box: { - float width_sq = shape->box.size.x * shape->box.size.x; - float height_sq = shape->box.size.y * shape->box.size.y; - float depth_sq = shape->box.size.z * shape->box.size.z; - float denominator_x = height_sq + depth_sq; - float denominator_y = width_sq + depth_sq; - float denominator_z = width_sq + height_sq; - if (not std::isfinite(denominator_x) || denominator_x <= 0.0f || not std::isfinite(denominator_y) || - denominator_y <= 0.0f || not std::isfinite(denominator_z) || denominator_z <= 0.0f) { - TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid box inertia denominator"); - return; - } - - // Ix = 1/12 m (h2 + d2) - // inv(Ix) = 12/(m (h2 + d2)) - float inverse_inertia_x = 12.0f * body->inverse_mass / denominator_x; - // Iy = 1/12 m (w2 + d2) - // inv(Iy) = 12/(m (w2 + d2)) - float inverse_inertia_y = 12.0f * body->inverse_mass / denominator_y; - // Iz = 1/12 m (w2 + h2) - // inv(Iz) = 12/(m (w2 + h2)) - float inverse_inertia_z = 12.0f * body->inverse_mass / denominator_z; - - glm::mat3 inverse_inertia = {0.0f}; - inverse_inertia[0][0] = inverse_inertia_x; - inverse_inertia[1][1] = inverse_inertia_y; - inverse_inertia[2][2] = inverse_inertia_z; - glm::mat3 local_rotation = glm::mat3_cast(shape->box.local_rotation); - body->inverse_inertia_local = local_rotation * inverse_inertia * glm::transpose(local_rotation); - glm::mat3 rotation = glm::mat3_cast(body->rotation); - body->inverse_inertia_world = rotation * body->inverse_inertia_local * glm::transpose(rotation); - - body->extent_radius = glm::length(shape->box.local_center) + (0.5f * glm::length(shape->box.size)); - - // Drag - body->drag_area_axis = dragBoxAreaAxis(shape->box.size * 0.5f); - body->drag_coefficient = 1.05f; - - break; - } case ShapeType::voxel: { const VoxelShapeData* voxel_data = tryGetVoxelData(shape->voxel.data); if (voxel_data == nullptr || voxel_data->volume == nullptr) { @@ -4124,51 +4586,22 @@ void Simulator::rebuildMassProperties(BodyID id) { break; } - case ShapeType::capsule: { - // Conservative approximation using a box with dimensions (2r, 2r, height) - float diameter = 2.0f * shape->capsule.radius; - float diameter_sq = diameter * diameter; - float height_sq = shape->capsule.height * shape->capsule.height; - float denominator_xy = diameter_sq + height_sq; - float denominator_z = diameter_sq + diameter_sq; - if (not std::isfinite(denominator_xy) || denominator_xy <= 0.0f || not std::isfinite(denominator_z) || - denominator_z <= 0.0f) { - TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid capsule inertia denominator"); + default: { + std::vector primitives; + primitives.reserve(shapes.size()); + for (const ShapeID shape_id : shapes) { + const Shape* primitive = tryGetShape(shape_id); + if (not primitive) { + TOAST_WARN("Physics", "rebuildMassProperties() was aborted: invalid shape"); + return; + } + primitives.push_back(primitive); + } + if (not combinePrimitiveMass(*body, primitives)) { return; } - - // Ix = 1/12 m (d2 + h2) - // inv(Ix) = 12/(m (d2 + h2)) - float inverse_inertia_x = 12.0f * body->inverse_mass / denominator_xy; - // Iy = 1/12 m (d2 + h2) - // inv(Iy) = 12/(m (d2 + h2)) - float inverse_inertia_y = 12.0f * body->inverse_mass / denominator_xy; - // Iz = 1/12 m (d2 + d2) - // inv(Iz) = 12/(m (d2 + d2)) - float inverse_inertia_z = 12.0f * body->inverse_mass / denominator_z; - - glm::mat3 inverse_inertia = {0.0f}; - inverse_inertia[0][0] = inverse_inertia_x; - inverse_inertia[1][1] = inverse_inertia_y; - inverse_inertia[2][2] = inverse_inertia_z; - glm::mat3 local_rotation = glm::mat3_cast(shape->capsule.local_rotation); - body->inverse_inertia_local = local_rotation * inverse_inertia * glm::transpose(local_rotation); - glm::mat3 rotation = glm::mat3_cast(body->rotation); - body->inverse_inertia_world = rotation * body->inverse_inertia_local * glm::transpose(rotation); - - body->extent_radius = glm::length(shape->capsule.local_center) + (0.5f * shape->capsule.height) + shape->capsule.radius; - - // Drag - // just treat like a box ngl - body->drag_area_axis = dragBoxAreaAxis({shape->capsule.radius, shape->capsule.radius, 0.5f * shape->capsule.height}); - body->drag_coefficient = 0.82; - break; } - default: { - TOAST_WARN("Physics", "rebuildMassProperties() was aborted: unknown shape"); - return; - } } const glm::vec3 center_shift = body->rotation * (body->local_center_of_mass - previous_center_of_mass); @@ -4198,6 +4631,19 @@ auto Simulator::state(BodyID body) const -> std::optional { }; } +auto Simulator::massProperties(BodyID body) const -> std::optional { + const Body* value = tryGetBody(body); + if (not value) { + return std::nullopt; + } + + return BodyMassProperties { + .mass = value->inverse_mass > 0.0f ? 1.0f / value->inverse_mass : 0.0f, + .local_center_of_mass = value->local_center_of_mass, + .inverse_inertia_local = value->inverse_inertia_local, + }; +} + auto Simulator::shapeWorldBounds(ShapeID shape) -> std::optional { if (not instance) { return std::nullopt; @@ -5782,6 +6228,34 @@ auto Simulator::buildIslands(std::span manifolds, const std::vec } } + // Force all objects in a joint in one island + for (size_t joint_index = 0; joint_index < m_joints.size(); ++joint_index) { + if (m_joint_active[joint_index] == 0) { + continue; + } + + const Joint& joint = m_joints[joint_index].joint; + const BodyID id_a = joint.descriptor.body_a; + const BodyID id_b = joint.descriptor.body_b; + const bool a_is_dynamic = is_active_dynamic(tryGetBody(id_a)); + const bool b_is_dynamic = is_active_dynamic(tryGetBody(id_b)); + if (a_is_dynamic) { + m_island_participates[id_a.slot] = true; + } + if (b_is_dynamic) { + m_island_participates[id_b.slot] = true; + } + if (a_is_dynamic && b_is_dynamic) { + const size_t root_a = find_root(id_a.slot); + const size_t root_b = find_root(id_b.slot); + if (root_a < root_b) { + m_island_parents[root_b] = root_a; + } else if (root_b < root_a) { + m_island_parents[root_a] = root_b; + } + } + } + const size_t no_island = m_bodies.size(); m_island_by_root.assign(m_bodies.size(), no_island); std::vector islands; @@ -5840,6 +6314,20 @@ auto Simulator::buildIslands(std::span manifolds, const std::vec } } + for (size_t joint_index = 0; joint_index < m_joints.size(); ++joint_index) { + if (m_joint_active[joint_index] == 0) { + continue; + } + + const Joint& joint = m_joints[joint_index].joint; + const bool a_is_dynamic = is_active_dynamic(tryGetBody(joint.descriptor.body_a)); + const BodyID anchor_body = a_is_dynamic ? joint.descriptor.body_a : joint.descriptor.body_b; + const size_t island_index = m_island_by_root[find_root(anchor_body.slot)]; + if (island_index != no_island) { + islands[island_index].joint_indices.emplace_back(static_cast(joint_index)); + } + } + // Shared across islands since none share a dynamic body so never reset between them, only once per call m_island_next_free_batch.assign(m_bodies.size(), 0); @@ -5918,6 +6406,55 @@ auto Simulator::buildIslands(std::span manifolds, const std::vec island.constraints.swap(m_island_reordered_scratch); } + // one joint at a time + m_joint_next_free_batch.assign(m_bodies.size(), 0); + for (SimulationIsland& island : islands) { + if (island.joint_indices.empty()) { + continue; + } + + std::ranges::sort(island.joint_indices); + std::vector batch_of(island.joint_indices.size(), 0); + uint32_t batch_count = 1; + for (size_t position = 0; position < island.joint_indices.size(); ++position) { + const Joint& joint = m_joints[island.joint_indices[position]].joint; + const Body* body_a = tryGetBody(joint.descriptor.body_a); + const Body* body_b = tryGetBody(joint.descriptor.body_b); + const bool a_moves = body_a != nullptr && movable(*body_a); + const bool b_moves = body_b != nullptr && movable(*body_b); + + uint32_t batch = 0; + if (a_moves) { + batch = std::max(batch, m_joint_next_free_batch[joint.descriptor.body_a.slot]); + } + if (b_moves) { + batch = std::max(batch, m_joint_next_free_batch[joint.descriptor.body_b.slot]); + } + batch_of[position] = batch; + batch_count = std::max(batch_count, batch + 1); + if (a_moves) { + m_joint_next_free_batch[joint.descriptor.body_a.slot] = batch + 1; + } + if (b_moves) { + m_joint_next_free_batch[joint.descriptor.body_b.slot] = batch + 1; + } + } + + island.joint_batch_offsets.assign(batch_count + 1, 0); + for (const uint32_t batch : batch_of) { + ++island.joint_batch_offsets[batch + 1]; + } + for (size_t i = 1; i < island.joint_batch_offsets.size(); ++i) { + island.joint_batch_offsets[i] += island.joint_batch_offsets[i - 1]; + } + std::vector reordered(island.joint_indices.size()); + std::vector cursor(island.joint_batch_offsets.begin(), island.joint_batch_offsets.end() - 1); + for (size_t position = 0; position < island.joint_indices.size(); ++position) { + reordered[cursor[batch_of[position]]++] = island.joint_indices[position]; + } + island.joint_indices.swap(reordered); + } + std::ranges::sort(islands, [](const SimulationIsland& lhs, const SimulationIsland& rhs) { return lhs.sort_key < rhs.sort_key; }); @@ -6107,15 +6644,67 @@ void Simulator::solveIslands(std::vector& islands) { } m_profile.max_constraint_batches = max_batches; + // Runs the function on every joint wave by wave + size_t max_joint_batches = 0; + for (const SimulationIsland& island : islands) { + max_joint_batches = + std::max(max_joint_batches, island.joint_batch_offsets.empty() ? size_t {0} : island.joint_batch_offsets.size() - 1); + } + const auto run_joint_waves = [&](const auto& per_joint) { + for (size_t wave = 0; wave < max_joint_batches; ++wave) { + m_joint_wave.clear(); + for (const SimulationIsland& island : islands) { + if (wave + 1 < island.joint_batch_offsets.size()) { + m_joint_wave.insert( + m_joint_wave.end(), + island.joint_indices.begin() + static_cast(island.joint_batch_offsets[wave]), + island.joint_indices.begin() + static_cast(island.joint_batch_offsets[wave + 1]) + ); + } + } + if (m_joint_wave.empty()) { + continue; + } + + const size_t chunk_count = std::min(worker_count, m_joint_wave.size()); + if (m_joint_wave.size() < tunables().min_wave_joints_for_dispatch || chunk_count <= 1) { + for (const uint32_t joint_index : m_joint_wave) { + per_joint(joint_index); + } + continue; + } + + const size_t per_chunk = (m_joint_wave.size() + chunk_count - 1) / chunk_count; + std::atomic chunks_remaining {chunk_count}; + for (size_t chunk = 0; chunk < chunk_count; ++chunk) { + const size_t begin = chunk * per_chunk; + const size_t end = std::min(m_joint_wave.size(), begin + per_chunk); + toast::ThreadPool::pushRaw([this, begin, end, &per_joint, &chunks_remaining] { + ZoneScopedN("physics::JointWaveChunk"); // NOLINT + for (size_t position = begin; position < end; ++position) { + per_joint(m_joint_wave[position]); + } + chunks_remaining.fetch_sub(1, std::memory_order_acq_rel); + }); + } + while (chunks_remaining.load(std::memory_order_acquire) != 0) { + std::this_thread::yield(); + } + } + }; + + std::vector plain_islands; + std::vector jointed_islands; + for (size_t island_index = 0; island_index < islands.size(); ++island_index) { + (islands[island_index].joint_indices.empty() ? plain_islands : jointed_islands).push_back(island_index); + } + size_t invalid_constraint_count = 0; std::vector> wave_spans; std::vector active_islands; - for (uint32_t iteration = 0; iteration < solver_iterations; ++iteration) { - ZoneScopedN("iteration"); - ZoneValue(static_cast(iteration)); - + const auto solve_contact_waves = [&](std::span island_set) { active_islands.clear(); - for (size_t island_index = 0; island_index < islands.size(); ++island_index) { + for (const size_t island_index : island_set) { if (islands[island_index].batch_offsets.size() > 1) { active_islands.push_back(island_index); } @@ -6177,6 +6766,131 @@ void Simulator::solveIslands(std::vector& islands) { invalid_constraint_count += invalid; } } + }; + + for (uint32_t iteration = 0; iteration < solver_iterations; ++iteration) { + ZoneScopedN("iteration"); + ZoneValue(static_cast(iteration)); + solve_contact_waves(plain_islands); + } + + std::vector predicted_centers; + if (not jointed_islands.empty()) { + ZoneScopedN("physics::JointSubsteps"); + const uint32_t substeps = std::max(tunables().joint_substeps, 1u); + const float substep_dt = static_cast(Accumulator::fixedDelta()) / static_cast(substeps); + const uint32_t substep_iterations = std::max((solver_iterations + substeps - 1) / substeps, 1u); + + const auto settle_inertia = [](Body& body) { + const glm::mat3 rotation = glm::mat3_cast(body.rotation); + body.inverse_inertia_world = rotation * body.inverse_inertia_local * glm::transpose(rotation); + }; + const auto advance = [&settle_inertia, substep_dt](Body& body) { + for (int axis = 0; axis < 3; ++axis) { + if (body.lock_position[axis]) { + body.linear_velocity[axis] = 0.0f; + } + if (body.lock_rotation[axis]) { + body.angular_velocity[axis] = 0.0f; + } + } + const glm::vec3 center = body.worldCenterOfMass() + (body.linear_velocity * substep_dt); + const glm::quat spin {0.0f, body.angular_velocity.x, body.angular_velocity.y, body.angular_velocity.z}; + const glm::quat next = body.rotation + ((0.5f * substep_dt) * (spin * body.rotation)); + const float length_squared = glm::dot(next, next); + if (std::isfinite(length_squared) && length_squared > 1.0e-10f) { + body.rotation = glm::normalize(next); + } + body.position = center - (body.rotation * body.local_center_of_mass); + settle_inertia(body); + }; + const auto for_each_jointed_body = [&](const auto& per_body) { + for (const size_t island_index : jointed_islands) { + for (const BodyID id : islands[island_index].dynamic_bodies) { + if (Body* body = tryGetBody(id); body != nullptr) { + per_body(id, *body); + } + } + } + }; + + for_each_jointed_body([&](BodyID, Body& body) { + predicted_centers.push_back(body.worldCenterOfMass()); + body.position = body.previous_position; + body.rotation = body.previous_rotation; + settle_inertia(body); + }); + + if (m_position_shift.size() < m_bodies.size()) { + m_position_shift.resize(m_bodies.size(), glm::vec3(0.0f)); + } + const auto measure_shift = [&] { + size_t body_index = 0; + for_each_jointed_body([&](BodyID id, Body& body) { + m_position_shift[id.slot] = body.worldCenterOfMass() - predicted_centers[body_index++]; + }); + }; + const auto open_contacts = [&](bool first) { + for (const size_t island_index : jointed_islands) { + for (Constraint& constraint : islands[island_index].constraints) { + const float separation = + -constraint.penetration + + glm::dot(m_position_shift[constraint.body_b.slot] - m_position_shift[constraint.body_a.slot], constraint.normal); + const float closing = std::max(separation, 0.0f) / substep_dt; + constraint.closing_speed = first ? closing : std::min(constraint.closing_speed, closing); + } + } + }; + measure_shift(); + + for (uint32_t substep = 0; substep < substeps; ++substep) { + run_joint_waves([this, substep, substep_dt](uint32_t joint_index) { + JointSlot& slot = m_joints[joint_index]; + Body* body_a = nullptr; + Body* body_b = nullptr; + jointBodies(slot.joint, body_a, body_b); + const _detail::JointRow previous = m_joint_rows[joint_index]; + m_joint_rows[joint_index] = _detail::prepareJoint(slot.joint, *body_a, *body_b, substep_dt); + if (substep == 0) { + _detail::warmStartJoint(m_joint_rows[joint_index], slot.joint, *body_a, *body_b); + } else { + m_joint_rows[joint_index].bounce_lower = std::min(m_joint_rows[joint_index].bounce_lower, previous.bounce_lower); + m_joint_rows[joint_index].bounce_upper = std::min(m_joint_rows[joint_index].bounce_upper, previous.bounce_upper); + } + }); + + open_contacts(substep == 0); + for (uint32_t iteration = 0; iteration < substep_iterations; ++iteration) { + run_joint_waves([this](uint32_t joint_index) { + JointSlot& slot = m_joints[joint_index]; + Body* body_a = nullptr; + Body* body_b = nullptr; + jointBodies(slot.joint, body_a, body_b); + _detail::solveJoint(m_joint_rows[joint_index], slot.joint, *body_a, *body_b); + }); + solve_contact_waves(jointed_islands); + } + + for_each_jointed_body([&](BodyID, Body& body) { advance(body); }); + + const uint32_t passes = substep + 1 == substeps ? tunables().position_iterations : 1u; + for (uint32_t pass = 0; pass < passes; ++pass) { + run_joint_waves([this](uint32_t joint_index) { + JointSlot& slot = m_joints[joint_index]; + Body* body_a = nullptr; + Body* body_b = nullptr; + jointBodies(slot.joint, body_a, body_b); + _detail::correctJointPositions(slot.joint, *body_a, *body_b); + }); + } + measure_shift(); + } + + for (const size_t island_index : jointed_islands) { + for (Constraint& constraint : islands[island_index].constraints) { + constraint.closing_speed = 0.0f; + } + } } if (invalid_constraint_count > 0) { @@ -6189,6 +6903,13 @@ void Simulator::solveIslands(std::vector& islands) { for (SimulationIsland& island : islands) { position_correction_count += correctPositions(island.manifold_indices); } + if (not predicted_centers.empty()) { + for (const size_t island_index : jointed_islands) { + for (const BodyID id : islands[island_index].dynamic_bodies) { + m_position_shift[id.slot] = glm::vec3(0.0f); + } + } + } m_profile.position_corrections = position_correction_count; m_profile.island_jobs = islands.size(); diff --git a/engine/src/toast/physics/simulator.hpp b/engine/src/toast/physics/simulator.hpp index bb4b1e1a2..7e38767ae 100644 --- a/engine/src/toast/physics/simulator.hpp +++ b/engine/src/toast/physics/simulator.hpp @@ -13,6 +13,8 @@ #include "damage_command.hpp" #include "debris.hpp" #include "fragment_extraction.hpp" +#include "joint.hpp" +#include "joint_solver.hpp" #include "manifold.hpp" #include "narrow_phase.hpp" #include "physics_material.hpp" @@ -47,6 +49,8 @@ class Trigger; class Rigidbody; class DynamicRigidbody; class Collider; +class Joint3D; +struct SimulatorTestAccess; struct SmashVolume; struct SmashPieces; @@ -59,6 +63,7 @@ class TOAST_API Simulator { friend class Rigidbody; friend class DynamicRigidbody; friend class toast::VoxelNode; + friend struct SimulatorTestAccess; public: struct DebugDirtyBrick { @@ -85,6 +90,27 @@ class TOAST_API Simulator { auto createBody(const BodyDescriptor& descriptor) -> BodyID; void destroyBody(BodyID body); + [[nodiscard]] + auto createJoint(const JointDescriptor& descriptor) -> JointID; + void destroyJoint(JointID joint); + + [[nodiscard]] + auto valid(JointID joint) const -> bool; + [[nodiscard]] + + auto jointState(JointID joint) const -> std::optional; + auto setJointParams(JointID joint, const JointParams& params) -> bool; + + [[nodiscard]] + auto jointCount() const -> size_t { + return m_joint_count; + } + + static void registerJointNode(Joint3D& node); + static void unregisterJointNode(Joint3D& node); + static void breakJointNode(Joint3D& node); + static void repairJointNode(Joint3D& node); + /// @param layers Only bodies including one of these layers are hit static auto raycast( glm::vec3 origin, glm::vec3 direction, float max_distance = -1.0f, BodyID ignored = {}, uint32_t layers = k_all_layers @@ -103,6 +129,8 @@ class TOAST_API Simulator { auto valid(BodyID body) const -> bool; [[nodiscard]] auto state(BodyID body) const -> std::optional; + [[nodiscard]] + auto massProperties(BodyID body) const -> std::optional; auto setTransform(BodyID body, const glm::vec3& position, const glm::quat& rotation) -> bool; auto setLinearVelocity(BodyID body, const glm::vec3& velocity) -> bool; @@ -175,6 +203,8 @@ class TOAST_API Simulator { size_t island_jobs = 0; size_t invalid_constraints = 0; size_t position_corrections = 0; + size_t joint_count = 0; + size_t joints_broken = 0; double tick_ms = 0.0; double damage_apply_ms = 0.0; @@ -380,6 +410,8 @@ class TOAST_API Simulator { std::vector dynamic_bodies; std::vector constraints; std::vector manifold_indices; + std::vector joint_indices; + std::vector joint_batch_offsets; /// constraints[batch_offsets[i] .. batch_offsets[i + 1]) only share a dynamic body within one body pair run /// and each body pair run is contiguous, so a batch can be split anywhere except inside a run @@ -524,6 +556,23 @@ class TOAST_API Simulator { void releaseSleeper(BodyID sleeper, BodyID other, const glm::vec3& normal_to_other); void wakeBodiesTouching(BodyID id); void wakeBodiesTouching(ShapeID id); + void wakeJointPartners(); + void prepareJoints(float dt); + void syncJointNodes(); + void checkJointAnchors(); + void breakOverloadedJoints(float dt); + void breakJoint(JointID joint, JointBreakReason reason); + void publishJoints(); + [[nodiscard]] + auto findVoxelShape(BodyID body) const -> ShapeID; + [[nodiscard]] + auto voxelEndOf(BodyID body, const JointFrame& frame, const VoxelAnchorSettings& settings) const -> JointVoxelEnd; + [[nodiscard]] + auto countSolidAround(ShapeID shape, const glm::vec3& body_local, int radius_voxels) const -> std::optional; + void jointBodies(const Joint& joint, Body*& body_a, Body*& body_b); + void addExclusion(BodyID a, BodyID b); + void removeExclusion(BodyID a, BodyID b); + void adjustJointContacts(std::vector& manifolds); void wakeBodiesInBounds(const AABB& bounds); void convertImpulsesToDamage(std::span islands); void wakeDisturbedSleepers(float dt); @@ -666,6 +715,30 @@ class TOAST_API Simulator { std::vector m_bodies; std::deque m_free_body_slots; + + std::vector m_joints; + std::deque m_free_joint_slots; + size_t m_joint_count = 0; + std::vector<_detail::JointRow> m_joint_rows; + std::vector m_joint_active; + std::vector m_joint_next_free_batch; + std::vector m_joint_wave; + std::vector m_excluded_pairs; + Body m_world_body {.type = BodyType::static_body, .inverse_mass = 0.0f}; + + struct JointBinding { + JointID joint; + toast::Box node; + bool broken = false; + }; + + struct BrokenJoint { + JointID joint; + JointBreakReason reason = JointBreakReason::requested; + }; + + std::vector m_joint_bindings; + std::vector m_broken_joints; std::vector m_node_bindings; std::vector m_voxel_bindings; diff --git a/engine/src/toast/physics/simulator_joints.cpp b/engine/src/toast/physics/simulator_joints.cpp new file mode 100644 index 000000000..cdf02498d --- /dev/null +++ b/engine/src/toast/physics/simulator_joints.cpp @@ -0,0 +1,345 @@ +#include "joint_solver.hpp" +#include "nodes/joint3d.hpp" +#include "simulator.hpp" + +#include +#include +#include +#include + +namespace physics { + +void Simulator::registerJointNode(Joint3D& node) { + if (not instance) { + return; + } + if (not onSimulatorThread()) { + request([box = node.box().as()]() mutable { + if (box.exists()) { + registerJointNode(*box); + } + }); + return; + } + runPendingRequests(); + + const toast::Box box = node.box().as(); + const bool known = + std::ranges::any_of(instance->m_joint_bindings, [&](const JointBinding& binding) { return binding.node == box; }); + if (not known) { + instance->m_joint_bindings.push_back(JointBinding {.node = box}); + } +} + +void Simulator::unregisterJointNode(Joint3D& node) { + if (not instance) { + return; + } + if (not onSimulatorThread()) { + request([box = node.box().as()]() mutable { + if (box.exists()) { + unregisterJointNode(*box); + } + }); + return; + } + runPendingRequests(); + + const toast::Box box = node.box().as(); + std::vector& bindings = instance->m_joint_bindings; + const auto found = std::ranges::find_if(bindings, [&](const JointBinding& binding) { return binding.node == box; }); + if (found != bindings.end()) { + instance->destroyJoint(found->joint); + bindings.erase(found); + } +} + +void Simulator::breakJointNode(Joint3D& node) { + if (not instance) { + return; + } + if (not onSimulatorThread()) { + request([box = node.box().as()]() mutable { + if (box.exists()) { + breakJointNode(*box); + } + }); + return; + } + runPendingRequests(); + + const toast::Box box = node.box().as(); + for (const JointBinding& binding : instance->m_joint_bindings) { + if (binding.node == box && instance->valid(binding.joint)) { + instance->breakJoint(binding.joint, JointBreakReason::requested); + } + } +} + +void Simulator::repairJointNode(Joint3D& node) { + if (not instance) { + return; + } + if (not onSimulatorThread()) { + request([box = node.box().as()]() mutable { + if (box.exists()) { + repairJointNode(*box); + } + }); + return; + } + runPendingRequests(); + + const toast::Box box = node.box().as(); + for (JointBinding& binding : instance->m_joint_bindings) { + if (binding.node == box && binding.broken) { + binding.broken = false; + node.broken = false; + } + } +} + +void Simulator::syncJointNodes() { + if (m_joint_bindings.empty()) { + return; + } + ZoneScopedN("physics::SyncJointNodes"); + + for (size_t index = 0; index < m_joint_bindings.size();) { + JointBinding& binding = m_joint_bindings[index]; + if (not binding.node.exists()) { + destroyJoint(binding.joint); + m_joint_bindings.erase(m_joint_bindings.begin() + static_cast(index)); + continue; + } + ++index; + + Joint3D& node = *binding.node; + const JointParams params = node.makeParams(); + const VoxelAnchorSettings anchor { + .enabled = node.break_when_anchor_destroyed, + .radius_voxels = std::max(node.anchor_radius_voxels, 1), + .min_remaining = node.anchor_min_remaining, + }; + + if (valid(binding.joint)) { + // What the node says now is what the joint does + Joint& joint = m_joints[binding.joint.slot].joint; + if (joint.descriptor.params.index() == params.index() && joint.descriptor.params != params) { + setJointParams(binding.joint, params); + } + joint.descriptor.break_force = node.break_force; + joint.descriptor.break_torque = node.break_torque; + joint.descriptor.voxel_anchor = anchor; + continue; + } + if (binding.broken) { + continue; + } + + const BodyID body_a = node.node_a.exists() ? bodyFor(*node.node_a) : BodyID {}; + const BodyID body_b = node.node_b.exists() ? bodyFor(*node.node_b) : BodyID {}; + if ((node.node_a.exists() && not valid(body_a)) || (node.node_b.exists() && not valid(body_b))) { + continue; + } + if (not valid(body_a) && not valid(body_b)) { + continue; + } + + node.syncWorldTransform(); + binding.joint = createJoint( + JointDescriptor { + .body_a = body_a, + .body_b = body_b, + .anchor = node.world_position, + .frame = node.frameRotation(), + .exclude_collision = node.exclude_nodes_from_collision, + .params = params, + .break_force = node.break_force, + .break_torque = node.break_torque, + .voxel_anchor = anchor, + } + ); + } +} + +void Simulator::publishJoints() { + for (JointBinding& binding : m_joint_bindings) { + if (binding.node.exists() && valid(binding.joint)) { + const Joint& joint = m_joints[binding.joint.slot].joint; + binding.node->publishState( + JointState { + .angle = joint.hinge_angle, + .position = joint.slider_position, + .force = joint.last_force, + .torque = joint.last_torque, + } + ); + } + } + + for (const BrokenJoint& broken : m_broken_joints) { + for (JointBinding& binding : m_joint_bindings) { + if (binding.joint == broken.joint) { + binding.broken = true; + if (binding.node.exists()) { + binding.node->markBroken(); + } + } + } + } + m_broken_joints.clear(); +} + +void Simulator::breakJoint(JointID joint, JointBreakReason reason) { + if (not valid(joint)) { + return; + } + m_broken_joints.push_back(BrokenJoint {.joint = joint, .reason = reason}); + // not when the node hears of it + for (JointBinding& binding : m_joint_bindings) { + if (binding.joint == joint) { + binding.broken = true; + } + } + destroyJoint(joint); + ++m_profile.joints_broken; +} + +void Simulator::breakOverloadedJoints(float dt) { + if (m_joint_count == 0) { + return; + } + + for (uint32_t index = 0; index < m_joints.size(); ++index) { + if (index >= m_joint_active.size() || m_joint_active[index] == 0 || not m_joints[index].occupied) { + continue; + } + + Joint& joint = m_joints[index].joint; + const _detail::JointReaction reaction = _detail::jointReaction(joint); + joint.last_force = reaction.linear / dt; + joint.last_torque = reaction.angular / dt; + + const JointID id {.slot = index, .generation = m_joints[index].generation}; + if (joint.descriptor.break_force > 0.0f && joint.last_force > joint.descriptor.break_force) { + breakJoint(id, JointBreakReason::force); + } else if (joint.descriptor.break_torque > 0.0f && joint.last_torque > joint.descriptor.break_torque) { + breakJoint(id, JointBreakReason::torque); + } + } +} + +auto Simulator::findVoxelShape(BodyID body) const -> ShapeID { + for (uint32_t index = 0; index < m_shapes.size(); ++index) { + const ShapeSlot& slot = m_shapes[index]; + if (slot.occupied && slot.shape.owner == body && slot.shape.type == ShapeType::voxel) { + return {.slot = index, .generation = slot.generation}; + } + } + return {}; +} + +auto Simulator::countSolidAround(ShapeID shape_id, const glm::vec3& body_local, int radius_voxels) const + -> std::optional { + const Shape* shape = tryGetShape(shape_id); + if (shape == nullptr || shape->type != ShapeType::voxel) { + return std::nullopt; + } + const VoxelShapeData* data = tryGetVoxelDataConst(shape->voxel.data); + if (data == nullptr || data->volume == nullptr) { + return std::nullopt; + } + + const glm::vec3 lattice = glm::inverse(shape->voxel.local_rotation) * (body_local - shape->voxel.local_center); + const glm::ivec3 center = glm::ivec3(glm::floor(lattice / voxel::k_voxel_size)); + uint32_t count = 0; + for (int z = -radius_voxels; z <= radius_voxels; ++z) { + for (int y = -radius_voxels; y <= radius_voxels; ++y) { + for (int x = -radius_voxels; x <= radius_voxels; ++x) { + const glm::ivec3 cell = center + glm::ivec3(x, y, z); + if (data->volume->containsVoxel(cell) && data->volume->isSolidAt(cell)) { + ++count; + } + } + } + } + return count; +} + +auto Simulator::voxelEndOf(BodyID body, const JointFrame& frame, const VoxelAnchorSettings& settings) const -> JointVoxelEnd { + if (not settings.enabled) { + return {}; + } + + const ShapeID shape = findVoxelShape(body); + const std::optional count = countSolidAround(shape, frame.position, settings.radius_voxels); + if (not count) { + return {}; + } + + const Shape* value = tryGetShape(shape); + const VoxelShapeData* data = tryGetVoxelDataConst(value->voxel.data); + return JointVoxelEnd {.shape = shape, .baseline = *count, .revision = data->volume->revision()}; +} + +void Simulator::checkJointAnchors() { + if (m_joint_count == 0) { + return; + } + ZoneScopedN("physics::CheckJointAnchors"); + + for (uint32_t index = 0; index < m_joints.size(); ++index) { + JointSlot& slot = m_joints[index]; + if (not slot.occupied || not slot.joint.descriptor.voxel_anchor.enabled) { + continue; + } + + Joint& joint = slot.joint; + const VoxelAnchorSettings& settings = joint.descriptor.voxel_anchor; + bool let_go = false; + const auto check = [&](BodyID body, const JointFrame& frame, JointVoxelEnd& end) { + if (end.shape.generation == 0) { + return; // not a voxel body + } + + const Shape* shape = tryGetShape(end.shape); + if (shape == nullptr) { + end.shape = findVoxelShape(body); + end.revision = 0; + shape = tryGetShape(end.shape); + } + if (shape == nullptr) { + let_go = true; + return; + } + + const VoxelShapeData* data = tryGetVoxelDataConst(shape->voxel.data); + if (data == nullptr || data->volume == nullptr) { + let_go = true; + return; + } + if (data->volume->revision() == end.revision) { + return; // nothing changedd + } + end.revision = data->volume->revision(); + + const std::optional count = countSolidAround(end.shape, frame.position, settings.radius_voxels); + if (not count || static_cast(*count) < static_cast(end.baseline) * settings.min_remaining) { + let_go = true; + } + }; + + if (valid(joint.descriptor.body_a)) { + check(joint.descriptor.body_a, joint.frame_a, joint.voxel_end_a); + } + if (valid(joint.descriptor.body_b)) { + check(joint.descriptor.body_b, joint.frame_b, joint.voxel_end_b); + } + if (let_go) { + breakJoint({.slot = index, .generation = slot.generation}, JointBreakReason::anchor_destroyed); + } + } +} + +} diff --git a/engine/src/toast/reflect/reflect.hpp b/engine/src/toast/reflect/reflect.hpp index 2a6c5c34d..90b2c89ce 100644 --- a/engine/src/toast/reflect/reflect.hpp +++ b/engine/src/toast/reflect/reflect.hpp @@ -89,6 +89,15 @@ struct TOAST_API FieldInfo { FieldGetterPtr get; FieldSetterPtr set; + [[nodiscard]] + auto isNodeReference() const -> bool { + std::string_view bare = type; + if (bare.starts_with("toast::")) { + bare.remove_prefix(std::string_view("toast::").size()); + } + return value_type == FieldType::uid_t && not is_array && bare.starts_with("Box<"); + } + /** * @brief Returns the generated attribute objectpr */ diff --git a/engine/src/toast/renderer/debug/voxel_debug.cpp b/engine/src/toast/renderer/debug/voxel_debug.cpp index 25576af89..a065a7a52 100644 --- a/engine/src/toast/renderer/debug/voxel_debug.cpp +++ b/engine/src/toast/renderer/debug/voxel_debug.cpp @@ -559,15 +559,15 @@ void Monitor::drawPanel(const RenderFrame& frame) const { ImGui::TextDisabled("%u pool slots, %u palettes, %u records", info.packed_slots, info.packed_palettes, storage.recordCount()); size_t occupied = 0; size_t glass = 0; - uint32_t small = 0; + uint32_t on_unit_box = 0; for (uint32_t record = 0; record < storage.recordCount(); ++record) { if (const auto& list = storage.brickList(record)) { occupied += list->occupied.size(); glass += list->glass.size(); - small += list->occupied.size() <= VoxelBlasCache::k_unit_box_bricks ? 1u : 0u; + on_unit_box += list->occupied.size() <= VoxelBlasCache::k_unit_box_bricks ? 1u : 0u; } } - ImGui::TextDisabled("%zu occupied bricks, %zu glass, %u records on the unit box", occupied, glass, small); + ImGui::TextDisabled("%zu occupied bricks, %zu glass, %u records on the unit box", occupied, glass, on_unit_box); if (const VoxelBlasCache* blas = VulkanRenderer::instance->voxelBlasCache()) { const VoxelBlasCache::Stats s = blas->stats(); ImGui::TextDisabled( diff --git a/engine/src/toast/scripting/node_proxy.cpp b/engine/src/toast/scripting/node_proxy.cpp index 35cec25e9..03347da7d 100644 --- a/engine/src/toast/scripting/node_proxy.cpp +++ b/engine/src/toast/scripting/node_proxy.cpp @@ -405,12 +405,19 @@ auto proxyMethodDispatch(lua_State* l) -> int; // Strips the outer "Box<" ... ">" from a field.type string and returns the inner type name auto innerBoxType(std::string_view type) -> std::string_view { + constexpr std::string_view toast_prefix = "toast::"; constexpr std::string_view prefix = "Box<"; + if (type.starts_with(toast_prefix)) { + type.remove_prefix(toast_prefix.size()); + } if (!type.starts_with(prefix) || !type.ends_with('>')) { return {}; } type.remove_prefix(prefix.size()); type.remove_suffix(1); + if (type.starts_with(toast_prefix)) { + type.remove_prefix(toast_prefix.size()); + } return type; } @@ -1598,7 +1605,7 @@ auto nodeProxyNewindex(NodeProxy& proxy, const luabridge::LuaRef& key, const lua case FieldType::uid_t: { const std::string_view type_str = f->type; - if (!f->is_array && type_str.starts_with("Box<")) { + if (f->isNodeReference()) { if (!value.isInstance()) { luaL_error(l, "Field '%s' expects Node", key_str.c_str()); } diff --git a/engine/src/toast/world/node_3d.cpp b/engine/src/toast/world/node_3d.cpp index 6bc0409fb..db0796948 100644 --- a/engine/src/toast/world/node_3d.cpp +++ b/engine/src/toast/world/node_3d.cpp @@ -3,6 +3,7 @@ #include "toast/physics/simulator.hpp" #include "world.hpp" +#include #include namespace toast { @@ -18,10 +19,37 @@ void composeTransform(glm::mat4& transform, glm::vec3 position, glm::quat rotati } void decomposeTransform(const glm::mat4& transform, glm::vec3& position, glm::quat& rotation, glm::vec3& scale) { + glm::mat4 patched = transform; + std::array collapsed {}; + int collapsed_count = 0; + for (int axis = 0; axis < 3; ++axis) { + collapsed[axis] = glm::dot(glm::vec3(transform[axis]), glm::vec3(transform[axis])) < 1.0e-12f; + collapsed_count += collapsed[axis] ? 1 : 0; + } + if (collapsed_count == 1) { + for (int axis = 0; axis < 3; ++axis) { + if (collapsed[axis]) { + patched[axis] = glm::vec4(glm::cross(glm::vec3(transform[(axis + 1) % 3]), glm::vec3(transform[(axis + 2) % 3])), 0.0f); + } + } + } else if (collapsed_count > 1) { + position = glm::vec3(transform[3]); + rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + for (int axis = 0; axis < 3; ++axis) { + scale[axis] = glm::length(glm::vec3(transform[axis])); + } + return; + } + glm::vec3 skew; glm::vec4 perspective; - glm::decompose(transform, scale, rotation, position, skew, perspective); + glm::decompose(patched, scale, rotation, position, skew, perspective); rotation = glm::normalize(rotation); + for (int axis = 0; axis < 3; ++axis) { + if (collapsed[axis]) { + scale[axis] = 0.0f; + } + } } } diff --git a/engine/src/toast/world/node_owner.cpp b/engine/src/toast/world/node_owner.cpp index 5f382ef58..00f086b98 100644 --- a/engine/src/toast/world/node_owner.cpp +++ b/engine/src/toast/world/node_owner.cpp @@ -681,6 +681,37 @@ auto INodeOwner::buildTree(std::vector>&& nodes, const assets::Handle< } } + for (size_t i = 0; i < nodes.size(); ++i) { + const NodeInfo* info = nodes[i]->info(); + if (not info) { + continue; + } + const auto& data = file->nodes[i]; + info->forEachBaseType([&](const NodeInfo& level) { + for (const auto& field : level.all_fields) { + if (not field.isNodeReference() || not field.set || field.name == "m_parent" || field.hasAttribute("ReadOnly") || + field.hasAttribute("NoSerialize")) { + continue; + } + + const auto stored = data.find(field.name); + const auto* uid = stored.has_value() ? std::any_cast(&stored->value) : nullptr; + if (uid == nullptr || uid->data() == 0) { + continue; + } + + const auto target = uid_map.find(uid->data()); + if (target == uid_map.end()) { + TOAST_WARN( + "World", "Field '{}' of '{}' references UID {} which is not in this prefab", field.name, nodes[i]->name(), *uid + ); + continue; + } + field.set(&*nodes[i], std::any {target->second}); + } + }); + } + // Signals are restored after every node has been allocated and indexed, so connection targets // can be resolved regardless of their order in the prefab file. for (size_t i = 0; i < nodes.size(); ++i) { diff --git a/engine/src/toast/world/workspace.cpp b/engine/src/toast/world/workspace.cpp index 48223f58c..f017f076e 100644 --- a/engine/src/toast/world/workspace.cpp +++ b/engine/src/toast/world/workspace.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -329,6 +330,10 @@ static auto gizmoField(GizmoTool tool, GizmoHandle handle, Node& node) -> const const char* name = light_gizmo::handleField(*light, handle); return name != nullptr ? node.info()->getField(name) : nullptr; } + if (const auto joint = box.as(); joint.exists()) { + const char* name = joint->limitField(handle); + return name != nullptr ? node.info()->getField(name) : nullptr; + } if (box.as().exists()) { return node.info()->getField("size"); } @@ -351,6 +356,17 @@ static auto gizmoField(GizmoTool tool, GizmoHandle handle, Node& node) -> const } } +static auto hingeRayAngle(const Ray& ray, const glm::vec3& origin, const glm::quat& rotation, float fallback) -> float { + const auto hit_t = rayPlaneIntersect(ray, origin, rotation * glm::vec3(0.0f, 0.0f, 1.0f)); + if (not hit_t.has_value()) { + return fallback; + } + const glm::vec3 hit = ray.origin + (ray.direction * (*hit_t)) - origin; + return glm::degrees( + std::atan2(glm::dot(hit, rotation * glm::vec3(0.0f, 1.0f, 0.0f)), glm::dot(hit, rotation * glm::vec3(1.0f, 0.0f, 0.0f))) + ); +} + Workspace::Workspace(UID handle, EmptyTag) : m_handle(handle) { m_editor_camera = std::make_unique(); m_editor_camera->position = {0.0f, -10.0f, 10.0f}; @@ -1122,6 +1138,15 @@ auto Workspace::collectSizeHandles() const -> std::pair(); joint.exists()) { + const auto [entries, entry_count] = joint->limitHandles(); + for (uint32_t i = 0; i < entry_count; ++i) { + out[count++] = SizeHandlePoint {.world_position = origin + rotation * entries[i].local, .handle = entries[i].handle}; + } + return {out, count}; + } + if (auto volume = m_focused_node.as(); volume.exists()) { const glm::vec3 half = glm::vec3(volume->sizeVoxels()) * voxel::k_voxel_size * 0.5f; const glm::mat4& world = node3d->getWorldTransform(); @@ -1187,6 +1212,15 @@ void Workspace::gizmoApplySizeDrag(float delta) { return; } + if (auto joint = m_focused_node.as(); joint.exists()) { + float value = m_gizmo_drag_start_size[0] + delta; + if (m_translate_snap.enabled && m_translate_snap.value > 0.0001f) { + value = std::round(value / m_translate_snap.value) * m_translate_snap.value; + } + joint->setLimitValue(m_gizmo_drag, value); + return; + } + // Volumes grow a whole voxel at a time and the negative faces move the corner if (auto volume = m_focused_node.as(); volume.exists()) { const int32_t start = static_cast(m_gizmo_drag_start_size[axis]); @@ -1303,6 +1337,8 @@ void Workspace::gizmoBeginDrag(GizmoHandle handle) { if (gizmo_layout::isSizeHandle(handle)) { if (auto light = m_focused_node.as(); light.exists()) { m_gizmo_drag_start_size[gizmo_layout::sizeHandleAxis(handle)] = light_gizmo::handleDistance(*light, handle); + } else if (auto joint = m_focused_node.as(); joint.exists()) { + m_gizmo_drag_start_size[0] = joint->limitValue(handle); } else if (auto volume = m_focused_node.as(); volume.exists()) { m_gizmo_drag_start_size = glm::vec3(volume->sizeVoxels()); } else if (auto box = m_focused_node.as(); box.exists()) { @@ -1333,6 +1369,16 @@ void Workspace::gizmoBeginDrag(GizmoHandle handle) { const glm::vec2 viewport_size {static_cast(extent.width), static_cast(extent.height)}; const Ray ray = camera->screenPointToRay(m_gizmo_mouse_pos, viewport_size); + // hinge limit + if (gizmo_layout::isSizeHandle(handle)) { + if (auto joint = m_focused_node.as(); joint.exists() && joint->limitsAreAngles()) { + m_gizmo_drag_axis = glm::normalize(node3d->world_rotation * glm::vec3(0.0f, 0.0f, 1.0f)); + m_gizmo_drag_plane_normal = m_gizmo_drag_axis; + m_gizmo_drag_start_angle = hingeRayAngle(ray, origin, node3d->world_rotation, m_gizmo_drag_start_size[0]); + return; + } + } + if (gizmo_layout::isSizeHandle(handle)) { glm::vec3 axis {0.0f}; axis[gizmo_layout::sizeHandleAxis(handle)] = 1.0f; @@ -1398,13 +1444,31 @@ void Workspace::gizmoUpdateDrag() { m_gizmo_drag == GizmoHandle::axis_x || m_gizmo_drag == GizmoHandle::axis_y || m_gizmo_drag == GizmoHandle::axis_z; if (gizmo_layout::isSizeHandle(m_gizmo_drag)) { + auto joint = m_focused_node.as(); + + if (joint.exists() && joint->limitsAreAngles()) { + const float angle = hingeRayAngle(ray, origin, m_gizmo_drag_start_rotation, m_gizmo_drag_start_angle); + float value = m_gizmo_drag_start_size[0] + std::remainder(angle - m_gizmo_drag_start_angle, 360.0f); + if (m_rotate_snap.enabled && m_rotate_snap.value > 0.0001f) { + value = std::round(value / m_rotate_snap.value) * m_rotate_snap.value; + } + joint->setLimitValue(m_gizmo_drag, value); + if (const char* field = joint->limitField(m_gizmo_drag); field != nullptr) { + m_focused_node->onReflectedFieldChanged(field); + } + return; + } + const auto [_, t2] = closestPointsBetweenLines(ray.origin, ray.direction, origin, m_gizmo_drag_axis); const glm::vec3 current = origin + m_gizmo_drag_axis * t2; gizmoApplySizeDrag(glm::dot(current - m_gizmo_drag_anchor, m_gizmo_drag_axis)); const auto light = m_focused_node.as(); - const char* light_field = light.exists() ? light_gizmo::handleField(*light, m_gizmo_drag) : nullptr; - m_focused_node->onReflectedFieldChanged(light_field != nullptr ? light_field : "size"); + const char* field = light.exists() ? light_gizmo::handleField(*light, m_gizmo_drag) : nullptr; + if (field == nullptr && joint.exists()) { + field = joint->limitField(m_gizmo_drag); + } + m_focused_node->onReflectedFieldChanged(field != nullptr ? field : "size"); return; } @@ -2078,7 +2142,7 @@ void Workspace::eventSubscriptions() { std::istringstream ss(e.value); ss >> deg.x >> deg.y >> deg.z; value = std::any {glm::quat(glm::radians(deg))}; - } else if (field->value_type == FieldType::uid_t && not field->is_array && field->type.starts_with("Box<")) { + } else if (field->isNodeReference()) { auto parsed = assets::Prefab::valueFromString(field->value_type, false, e.value); if (not parsed.has_value()) { TOAST_WARN("World", "NodeChangeParam: couldn't parse '{}' for parameter '{}'", e.value, e.parameter);