@series-inc/rundot-syncplay 6.0.0-rc.3 → 6.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/native/physics/buoyancy.cpp +431 -54
- package/core/native/physics/buoyancy.hpp +48 -0
- package/core/native/physics/geometry_store.cpp +20 -1
- package/core/native/physics/physics.cpp +346 -5
- package/core/native/physics/physics.hpp +16 -1
- package/core/native/physics/topology.cpp +236 -8
- package/core/native/physics/topology.hpp +60 -2
- package/core/native/physics/world.cpp +459 -29
- package/core/native/physics/world.hpp +43 -3
- package/core/native/physics/world_abi.cpp +312 -37
- package/core/native/physics/world_abi.hpp +65 -7
- package/core/scripts/build-wasm-physics.mjs +11 -1
- package/core/src/physics/abi-constants.mjs +4 -4
- package/core/src/physics/buoyancy.d.mts +5 -1
- package/core/src/physics/buoyancy.mjs +160 -4
- package/core/src/physics/physics-wasm-module.generated.mjs +6 -6
- package/core/src/physics/voxel-cooking.d.mts +1 -1
- package/core/src/physics/voxel-cooking.mjs +16 -2
- package/core/src/physics/world.d.mts +3 -0
- package/core/src/physics/world.mjs +257 -26
- package/core/world.d.ts +51 -1
- package/dist/physics/3d.d.ts +63 -8
- package/dist/physics/3d.js +140 -22
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <array>
|
|
5
5
|
#include <cmath>
|
|
6
6
|
#include <cstddef>
|
|
7
|
+
#include <vector>
|
|
7
8
|
|
|
8
9
|
namespace rundot::kinetix::physics {
|
|
9
10
|
namespace {
|
|
@@ -726,6 +727,66 @@ BuoyancyStatus calculateSubmergedGeometryWithPlane(
|
|
|
726
727
|
return BuoyancyStatus::UnsupportedShape;
|
|
727
728
|
}
|
|
728
729
|
|
|
730
|
+
BuoyancyStatus finishFluidForces(
|
|
731
|
+
const FluidBodyInput3D& body,
|
|
732
|
+
const FluidVolume3D& volume,
|
|
733
|
+
const BuoyancyVector3& gravity,
|
|
734
|
+
double fullVolume,
|
|
735
|
+
FluidForcesResult3D& output) {
|
|
736
|
+
if (output.submerged.volume <= 0.0) {
|
|
737
|
+
output.submerged = {0.0, body.position};
|
|
738
|
+
return BuoyancyStatus::Success;
|
|
739
|
+
}
|
|
740
|
+
const double normSq = body.orientation.x * body.orientation.x
|
|
741
|
+
+ body.orientation.y * body.orientation.y
|
|
742
|
+
+ body.orientation.z * body.orientation.z
|
|
743
|
+
+ body.orientation.w * body.orientation.w;
|
|
744
|
+
if (!(normSq > 0.0) || !finite(normSq)) return BuoyancyStatus::InvalidInput;
|
|
745
|
+
const BuoyancyVector3 centerOfMassWorld = add(
|
|
746
|
+
body.position,
|
|
747
|
+
rotate(body.orientation, 1.0 / normSq, body.centerOfMass));
|
|
748
|
+
const BuoyancyVector3 effectiveGravity = scale(gravity, body.gravityScale);
|
|
749
|
+
output.buoyantForce = scale(
|
|
750
|
+
effectiveGravity, -volume.density * output.submerged.volume);
|
|
751
|
+
const BuoyancyVector3 arm = subtract(
|
|
752
|
+
output.submerged.center, centerOfMassWorld);
|
|
753
|
+
const double submergedFraction = fullVolume > 0.0
|
|
754
|
+
? std::min(1.0, output.submerged.volume / fullVolume) : 0.0;
|
|
755
|
+
if (submergedFraction >= 0.999999999
|
|
756
|
+
&& dot(body.centerOfMass, body.centerOfMass) < 1e-12) {
|
|
757
|
+
output.buoyantTorque = {};
|
|
758
|
+
} else {
|
|
759
|
+
output.buoyantTorque = cross(arm, output.buoyantForce);
|
|
760
|
+
}
|
|
761
|
+
const BuoyancyVector3 pointVelocity = add(
|
|
762
|
+
body.linearVelocity, cross(body.angularVelocity, arm));
|
|
763
|
+
output.relativeVelocity = subtract(pointVelocity, volume.flowVelocity);
|
|
764
|
+
const double dragCoefficient = volume.density * output.submerged.volume
|
|
765
|
+
* volume.linearDrag;
|
|
766
|
+
output.dragForce = scale(output.relativeVelocity, -dragCoefficient);
|
|
767
|
+
const BuoyancyVector3 dragTorqueAtPoint = cross(arm, output.dragForce);
|
|
768
|
+
const BuoyancyVector3 angularDamping = scale(
|
|
769
|
+
body.angularVelocity, -submergedFraction * volume.angularDrag);
|
|
770
|
+
output.dragTorque = add(dragTorqueAtPoint, angularDamping);
|
|
771
|
+
output.totalForce = add(output.buoyantForce, output.dragForce);
|
|
772
|
+
output.totalTorque = add(output.buoyantTorque, output.dragTorque);
|
|
773
|
+
if (!finite(output.totalForce) || !finite(output.totalTorque)) {
|
|
774
|
+
return BuoyancyStatus::InvalidInput;
|
|
775
|
+
}
|
|
776
|
+
const BuoyancyVector3 gravitationalForce = scale(
|
|
777
|
+
effectiveGravity, body.mass);
|
|
778
|
+
const BuoyancyVector3 netForce = add(
|
|
779
|
+
output.buoyantForce, gravitationalForce);
|
|
780
|
+
const bool equilibrium = dot(netForce, netForce) < 1e-6
|
|
781
|
+
&& dot(output.buoyantTorque, output.buoyantTorque) < 1e-6
|
|
782
|
+
&& dot(output.relativeVelocity, output.relativeVelocity) < 1e-4
|
|
783
|
+
&& dot(body.angularVelocity, body.angularVelocity) < 1e-4;
|
|
784
|
+
output.isInEquilibrium = equilibrium;
|
|
785
|
+
output.shouldWake = body.sleeping
|
|
786
|
+
&& (!equilibrium || dot(volume.flowVelocity, volume.flowVelocity) > 1e-6);
|
|
787
|
+
return BuoyancyStatus::Success;
|
|
788
|
+
}
|
|
789
|
+
|
|
729
790
|
BuoyancyStatus calculateFluidForces(
|
|
730
791
|
const FluidBodyInput3D& body,
|
|
731
792
|
const FluidVolume3D& volume,
|
|
@@ -754,74 +815,216 @@ BuoyancyStatus calculateFluidForces(
|
|
|
754
815
|
volume.bounds,
|
|
755
816
|
output.submerged);
|
|
756
817
|
if (geomStatus != BuoyancyStatus::Success) return geomStatus;
|
|
757
|
-
if (output.submerged.volume <= 0.0) {
|
|
758
|
-
output.submerged = {0.0, body.position};
|
|
759
|
-
return BuoyancyStatus::Success;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const double normSq = body.orientation.x * body.orientation.x
|
|
763
|
-
+ body.orientation.y * body.orientation.y
|
|
764
|
-
+ body.orientation.z * body.orientation.z
|
|
765
|
-
+ body.orientation.w * body.orientation.w;
|
|
766
|
-
if (!(normSq > 0.0) || !finite(normSq)) return BuoyancyStatus::InvalidInput;
|
|
767
|
-
const BuoyancyVector3 centerOfMassWorld = add(
|
|
768
|
-
body.position,
|
|
769
|
-
rotate(body.orientation, 1.0 / normSq, body.centerOfMass));
|
|
770
|
-
|
|
771
|
-
const BuoyancyVector3 effectiveGravity = scale(gravity, body.gravityScale);
|
|
772
|
-
|
|
773
|
-
output.buoyantForce = scale(effectiveGravity, -volume.density * output.submerged.volume);
|
|
774
|
-
|
|
775
|
-
const BuoyancyVector3 arm = subtract(output.submerged.center, centerOfMassWorld);
|
|
776
|
-
|
|
777
818
|
const double fullVol = (body.shape == ShapeKind::Box)
|
|
778
819
|
? (8.0 * body.size.x * body.size.y * body.size.z)
|
|
779
820
|
: ((4.0 / 3.0) * kPi * body.size.x * body.size.x * body.size.x);
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
: 0.0;
|
|
821
|
+
return finishFluidForces(body, volume, gravity, fullVol, output);
|
|
822
|
+
}
|
|
783
823
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
824
|
+
BuoyancyStatus validateVoxelBoxes(std::span<const VoxelBoxShape3D> boxes) {
|
|
825
|
+
if (boxes.empty()) return BuoyancyStatus::InvalidInput;
|
|
826
|
+
for (std::size_t index = 0; index < boxes.size(); ++index) {
|
|
827
|
+
const VoxelBoxShape3D& box = boxes[index];
|
|
828
|
+
if (!finite(box.halfSize) || !finite(box.offset)
|
|
829
|
+
|| !(box.halfSize.x > 0.0) || !(box.halfSize.y > 0.0)
|
|
830
|
+
|| !(box.halfSize.z > 0.0)) return BuoyancyStatus::InvalidInput;
|
|
831
|
+
for (std::size_t otherIndex = 0; otherIndex < index; ++otherIndex) {
|
|
832
|
+
const VoxelBoxShape3D& other = boxes[otherIndex];
|
|
833
|
+
const bool overlap = box.offset.x - box.halfSize.x
|
|
834
|
+
< other.offset.x + other.halfSize.x
|
|
835
|
+
&& other.offset.x - other.halfSize.x
|
|
836
|
+
< box.offset.x + box.halfSize.x
|
|
837
|
+
&& box.offset.y - box.halfSize.y
|
|
838
|
+
< other.offset.y + other.halfSize.y
|
|
839
|
+
&& other.offset.y - other.halfSize.y
|
|
840
|
+
< box.offset.y + box.halfSize.y
|
|
841
|
+
&& box.offset.z - box.halfSize.z
|
|
842
|
+
< other.offset.z + other.halfSize.z
|
|
843
|
+
&& other.offset.z - other.halfSize.z
|
|
844
|
+
< box.offset.z + box.halfSize.z;
|
|
845
|
+
if (overlap) return BuoyancyStatus::OverlapUnsupported;
|
|
846
|
+
}
|
|
788
847
|
}
|
|
848
|
+
return BuoyancyStatus::Success;
|
|
849
|
+
}
|
|
789
850
|
|
|
790
|
-
|
|
791
|
-
|
|
851
|
+
template <typename Calculate>
|
|
852
|
+
BuoyancyStatus calculateVoxelGeometry(
|
|
853
|
+
const BuoyancyVector3& position,
|
|
854
|
+
const BuoyancyQuaternion& orientation,
|
|
855
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
856
|
+
bool validateBoxes,
|
|
857
|
+
Calculate calculate,
|
|
858
|
+
SubmergedGeometry& output,
|
|
859
|
+
double& fullVolume) {
|
|
860
|
+
output = {};
|
|
861
|
+
fullVolume = 0.0;
|
|
862
|
+
if (validateBoxes) {
|
|
863
|
+
const BuoyancyStatus validation = validateVoxelBoxes(boxes);
|
|
864
|
+
if (validation != BuoyancyStatus::Success) return validation;
|
|
865
|
+
}
|
|
866
|
+
const double normSquared = orientation.x * orientation.x
|
|
867
|
+
+ orientation.y * orientation.y + orientation.z * orientation.z
|
|
868
|
+
+ orientation.w * orientation.w;
|
|
869
|
+
if (!(normSquared > 0.0) || !finite(normSquared) || !finite(position)) {
|
|
870
|
+
return BuoyancyStatus::InvalidInput;
|
|
871
|
+
}
|
|
872
|
+
BuoyancyVector3 centerMoment{};
|
|
873
|
+
for (const VoxelBoxShape3D& box : boxes) {
|
|
874
|
+
const BuoyancyVector3 center = add(position,
|
|
875
|
+
rotate(orientation, 1.0 / normSquared, box.offset));
|
|
876
|
+
SubmergedGeometry child{};
|
|
877
|
+
const BuoyancyStatus status = calculate(center, box.halfSize, child);
|
|
878
|
+
if (status != BuoyancyStatus::Success) return status;
|
|
879
|
+
fullVolume += 8.0 * box.halfSize.x * box.halfSize.y * box.halfSize.z;
|
|
880
|
+
output.volume += child.volume;
|
|
881
|
+
centerMoment = add(centerMoment, scale(child.center, child.volume));
|
|
882
|
+
}
|
|
883
|
+
if (!finite(fullVolume) || !finite(output.volume)) {
|
|
884
|
+
return BuoyancyStatus::NumericRange;
|
|
885
|
+
}
|
|
886
|
+
output.center = output.volume > 0.0
|
|
887
|
+
? scale(centerMoment, 1.0 / output.volume) : position;
|
|
888
|
+
return finite(output.center)
|
|
889
|
+
? BuoyancyStatus::Success : BuoyancyStatus::NumericRange;
|
|
890
|
+
}
|
|
792
891
|
|
|
793
|
-
|
|
794
|
-
|
|
892
|
+
BuoyancyStatus calculateVoxelBuoyancy(
|
|
893
|
+
const BuoyancyInput& input,
|
|
894
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
895
|
+
BuoyancyResult& output) {
|
|
896
|
+
output = {};
|
|
897
|
+
if (!(input.density > 0.0) || !finite(input.density)
|
|
898
|
+
|| !finite(input.gravity) || !finite(input.bodyCenter)
|
|
899
|
+
|| !finite(input.waterY)) return BuoyancyStatus::InvalidInput;
|
|
900
|
+
double fullVolume = 0.0;
|
|
901
|
+
const BuoyancyStatus status = calculateVoxelGeometry(
|
|
902
|
+
input.shapeCenter, input.orientation, boxes, true,
|
|
903
|
+
[&](const BuoyancyVector3& center, const BuoyancyVector3& halfSize,
|
|
904
|
+
SubmergedGeometry& child) {
|
|
905
|
+
return calculateSubmergedGeometry(ShapeKind::Box, center,
|
|
906
|
+
input.orientation, halfSize, input.waterY, child);
|
|
907
|
+
}, output.submerged, fullVolume);
|
|
908
|
+
(void) fullVolume;
|
|
909
|
+
if (status != BuoyancyStatus::Success) return status;
|
|
910
|
+
output.force = scale(input.gravity,
|
|
911
|
+
-input.density * output.submerged.volume);
|
|
912
|
+
output.torque = cross(
|
|
913
|
+
subtract(output.submerged.center, input.bodyCenter), output.force);
|
|
914
|
+
return finite(output.force) && finite(output.torque)
|
|
915
|
+
? BuoyancyStatus::Success : BuoyancyStatus::NumericRange;
|
|
916
|
+
}
|
|
795
917
|
|
|
796
|
-
|
|
918
|
+
BuoyancyStatus calculateVoxelFluidForcesInternal(
|
|
919
|
+
const FluidBodyInput3D& body,
|
|
920
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
921
|
+
const FluidVolume3D& volume,
|
|
922
|
+
const BuoyancyVector3& gravity,
|
|
923
|
+
FluidForcesResult3D& output,
|
|
924
|
+
bool validateBoxes) {
|
|
925
|
+
output = {};
|
|
926
|
+
if (!finite(gravity) || !finite(body.position)
|
|
927
|
+
|| !finite(body.orientation) || !finite(body.linearVelocity)
|
|
928
|
+
|| !finite(body.angularVelocity)) return BuoyancyStatus::InvalidInput;
|
|
929
|
+
const BuoyancyStatus volumeStatus = validateFluidVolume(volume);
|
|
930
|
+
if (volumeStatus != BuoyancyStatus::Success) return volumeStatus;
|
|
931
|
+
const bool filterEnabled = volume.layer != 0 || volume.mask != 0;
|
|
932
|
+
if (filterEnabled && ((volume.mask & body.layer) == 0
|
|
933
|
+
|| (body.mask & volume.layer) == 0)) return BuoyancyStatus::Success;
|
|
934
|
+
double fullVolume = 0.0;
|
|
935
|
+
const BuoyancyStatus status = calculateVoxelGeometry(
|
|
936
|
+
body.position, body.orientation, boxes, validateBoxes,
|
|
937
|
+
[&](const BuoyancyVector3& center, const BuoyancyVector3& halfSize,
|
|
938
|
+
SubmergedGeometry& child) {
|
|
939
|
+
return calculateSubmergedGeometryWithPlane(ShapeKind::Box, center,
|
|
940
|
+
body.orientation, halfSize, volume.plane, volume.bounds, child);
|
|
941
|
+
}, output.submerged, fullVolume);
|
|
942
|
+
if (status != BuoyancyStatus::Success) return status;
|
|
943
|
+
return finishFluidForces(body, volume, gravity, fullVolume, output);
|
|
944
|
+
}
|
|
797
945
|
|
|
798
|
-
|
|
799
|
-
|
|
946
|
+
BuoyancyStatus calculateVoxelFluidForces(
|
|
947
|
+
const FluidBodyInput3D& body,
|
|
948
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
949
|
+
const FluidVolume3D& volume,
|
|
950
|
+
const BuoyancyVector3& gravity,
|
|
951
|
+
FluidForcesResult3D& output) {
|
|
952
|
+
return calculateVoxelFluidForcesInternal(
|
|
953
|
+
body, boxes, volume, gravity, output, true);
|
|
954
|
+
}
|
|
800
955
|
|
|
801
|
-
|
|
802
|
-
|
|
956
|
+
BuoyancyStatus calculateValidatedVoxelFluidForces(
|
|
957
|
+
const FluidBodyInput3D& body,
|
|
958
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
959
|
+
const FluidVolume3D& volume,
|
|
960
|
+
const BuoyancyVector3& gravity,
|
|
961
|
+
FluidForcesResult3D& output) {
|
|
962
|
+
return calculateVoxelFluidForcesInternal(
|
|
963
|
+
body, boxes, volume, gravity, output, false);
|
|
964
|
+
}
|
|
803
965
|
|
|
804
|
-
|
|
805
|
-
|
|
966
|
+
BuoyancyStatus calculateVoxelFluidStateForces(
|
|
967
|
+
const FluidBodyInput3D& body,
|
|
968
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
969
|
+
const FluidState3D& state,
|
|
970
|
+
const BuoyancyVector3& gravity,
|
|
971
|
+
FluidForcesResult3D& output) {
|
|
972
|
+
output = {};
|
|
973
|
+
const BuoyancyStatus stateStatus = validateFluidState(state);
|
|
974
|
+
if (stateStatus != BuoyancyStatus::Success) return stateStatus;
|
|
975
|
+
const BuoyancyStatus boxesStatus = validateVoxelBoxes(boxes);
|
|
976
|
+
if (boxesStatus != BuoyancyStatus::Success) return boxesStatus;
|
|
977
|
+
double maximumVolume = 0.0;
|
|
978
|
+
for (const VoxelBoxShape3D& box : boxes) {
|
|
979
|
+
maximumVolume += 8.0 * box.halfSize.x * box.halfSize.y * box.halfSize.z;
|
|
806
980
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
981
|
+
BuoyancyVector3 centerMoment{};
|
|
982
|
+
BuoyancyVector3 velocityMoment{};
|
|
983
|
+
bool equilibrium = true;
|
|
984
|
+
for (std::uint32_t index = 0; index < state.volumeCount; ++index) {
|
|
985
|
+
FluidForcesResult3D current{};
|
|
986
|
+
const BuoyancyStatus status = calculateVoxelFluidForces(
|
|
987
|
+
body, boxes, state.volumes[index], gravity, current);
|
|
988
|
+
if (status != BuoyancyStatus::Success) return status;
|
|
989
|
+
if (state.overlapPolicy == FluidOverlapPolicy::PriorityPartition
|
|
990
|
+
&& output.submerged.volume < maximumVolume
|
|
991
|
+
&& current.submerged.volume > maximumVolume - output.submerged.volume) {
|
|
992
|
+
const double remaining = maximumVolume - output.submerged.volume;
|
|
993
|
+
const double factor = remaining / current.submerged.volume;
|
|
994
|
+
current.submerged.volume = remaining;
|
|
995
|
+
current.buoyantForce = scale(current.buoyantForce, factor);
|
|
996
|
+
current.buoyantTorque = scale(current.buoyantTorque, factor);
|
|
997
|
+
current.dragForce = scale(current.dragForce, factor);
|
|
998
|
+
current.dragTorque = scale(current.dragTorque, factor);
|
|
999
|
+
current.totalForce = add(current.buoyantForce, current.dragForce);
|
|
1000
|
+
current.totalTorque = add(current.buoyantTorque, current.dragTorque);
|
|
1001
|
+
}
|
|
1002
|
+
if (state.overlapPolicy == FluidOverlapPolicy::PriorityPartition
|
|
1003
|
+
&& output.submerged.volume >= maximumVolume) continue;
|
|
1004
|
+
output.buoyantForce = add(output.buoyantForce, current.buoyantForce);
|
|
1005
|
+
output.buoyantTorque = add(output.buoyantTorque, current.buoyantTorque);
|
|
1006
|
+
output.dragForce = add(output.dragForce, current.dragForce);
|
|
1007
|
+
output.dragTorque = add(output.dragTorque, current.dragTorque);
|
|
1008
|
+
output.totalForce = add(output.totalForce, current.totalForce);
|
|
1009
|
+
output.totalTorque = add(output.totalTorque, current.totalTorque);
|
|
1010
|
+
centerMoment = add(centerMoment,
|
|
1011
|
+
scale(current.submerged.center, current.submerged.volume));
|
|
1012
|
+
velocityMoment = add(velocityMoment,
|
|
1013
|
+
scale(current.relativeVelocity, current.submerged.volume));
|
|
1014
|
+
output.submerged.volume += current.submerged.volume;
|
|
1015
|
+
output.shouldWake = output.shouldWake || current.shouldWake;
|
|
1016
|
+
if (current.submerged.volume > 0.0 && !current.isInEquilibrium) {
|
|
1017
|
+
equilibrium = false;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (output.submerged.volume > 0.0) {
|
|
1021
|
+
const double inverseVolume = 1.0 / output.submerged.volume;
|
|
1022
|
+
output.submerged.center = scale(centerMoment, inverseVolume);
|
|
1023
|
+
output.relativeVelocity = scale(velocityMoment, inverseVolume);
|
|
1024
|
+
output.isInEquilibrium = equilibrium;
|
|
821
1025
|
} else {
|
|
822
|
-
output.
|
|
1026
|
+
output.submerged.center = body.position;
|
|
823
1027
|
}
|
|
824
|
-
|
|
825
1028
|
return BuoyancyStatus::Success;
|
|
826
1029
|
}
|
|
827
1030
|
|
|
@@ -924,6 +1127,51 @@ BuoyancyStatus stepBodyInFluids(
|
|
|
924
1127
|
|
|
925
1128
|
} // namespace rundot::kinetix::physics
|
|
926
1129
|
|
|
1130
|
+
namespace {
|
|
1131
|
+
|
|
1132
|
+
bool parseVoxelBoxes(
|
|
1133
|
+
const double* values,
|
|
1134
|
+
std::uint32_t valueCount,
|
|
1135
|
+
std::vector<rundot::kinetix::physics::VoxelBoxShape3D>& output) {
|
|
1136
|
+
using rundot::kinetix::physics::VoxelBoxShape3D;
|
|
1137
|
+
if (values == nullptr || valueCount == 0 || valueCount % 6 != 0) return false;
|
|
1138
|
+
output.resize(valueCount / 6);
|
|
1139
|
+
for (std::size_t index = 0; index < output.size(); ++index) {
|
|
1140
|
+
const double* record = values + index * 6;
|
|
1141
|
+
for (std::size_t field = 0; field < 6; ++field) {
|
|
1142
|
+
if (!std::isfinite(record[field])) return false;
|
|
1143
|
+
}
|
|
1144
|
+
output[index] = VoxelBoxShape3D{
|
|
1145
|
+
{record[0], record[1], record[2]},
|
|
1146
|
+
{record[3], record[4], record[5]},
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
void writeFluidResult(
|
|
1153
|
+
const rundot::kinetix::physics::FluidForcesResult3D& result,
|
|
1154
|
+
double* output) {
|
|
1155
|
+
const double values[rundot::kinetix::physics::kFluidForcesOutputStride] = {
|
|
1156
|
+
result.submerged.volume,
|
|
1157
|
+
result.submerged.center.x, result.submerged.center.y,
|
|
1158
|
+
result.submerged.center.z,
|
|
1159
|
+
result.buoyantForce.x, result.buoyantForce.y, result.buoyantForce.z,
|
|
1160
|
+
result.buoyantTorque.x, result.buoyantTorque.y, result.buoyantTorque.z,
|
|
1161
|
+
result.dragForce.x, result.dragForce.y, result.dragForce.z,
|
|
1162
|
+
result.dragTorque.x, result.dragTorque.y, result.dragTorque.z,
|
|
1163
|
+
result.totalForce.x, result.totalForce.y, result.totalForce.z,
|
|
1164
|
+
result.totalTorque.x, result.totalTorque.y, result.totalTorque.z,
|
|
1165
|
+
result.relativeVelocity.x, result.relativeVelocity.y,
|
|
1166
|
+
result.relativeVelocity.z,
|
|
1167
|
+
result.shouldWake ? 1.0 : 0.0,
|
|
1168
|
+
result.isInEquilibrium ? 1.0 : 0.0,
|
|
1169
|
+
};
|
|
1170
|
+
std::copy(std::begin(values), std::end(values), output);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
} // namespace
|
|
1174
|
+
|
|
927
1175
|
extern "C" std::int32_t rundot_kinetix_physics_buoyancy(
|
|
928
1176
|
const double* input,
|
|
929
1177
|
std::uint32_t input_values,
|
|
@@ -983,6 +1231,64 @@ extern "C" std::int32_t rundot_kinetix_physics_buoyancy(
|
|
|
983
1231
|
return static_cast<std::int32_t>(status);
|
|
984
1232
|
}
|
|
985
1233
|
|
|
1234
|
+
extern "C" std::int32_t rundot_kinetix_physics_voxel_buoyancy(
|
|
1235
|
+
const double* input,
|
|
1236
|
+
std::uint32_t input_values,
|
|
1237
|
+
const double* children,
|
|
1238
|
+
std::uint32_t child_values,
|
|
1239
|
+
double* output,
|
|
1240
|
+
std::uint32_t output_values) {
|
|
1241
|
+
using namespace rundot::kinetix::physics;
|
|
1242
|
+
if (output == nullptr || output_values < kBuoyancyOutputStride) {
|
|
1243
|
+
return static_cast<std::int32_t>(BuoyancyStatus::OutputTooSmall);
|
|
1244
|
+
}
|
|
1245
|
+
if (input == nullptr || input_values != kBuoyancyInputStride) {
|
|
1246
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1247
|
+
}
|
|
1248
|
+
for (std::uint32_t index = 0; index < input_values; ++index) {
|
|
1249
|
+
if (!std::isfinite(input[index])) {
|
|
1250
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
std::vector<VoxelBoxShape3D> boxes;
|
|
1254
|
+
if (!parseVoxelBoxes(children, child_values, boxes)) {
|
|
1255
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1256
|
+
}
|
|
1257
|
+
BuoyancyInput parsed{
|
|
1258
|
+
ShapeKind::Compound,
|
|
1259
|
+
{input[1], input[2], input[3]},
|
|
1260
|
+
{input[4], input[5], input[6], input[7]},
|
|
1261
|
+
{},
|
|
1262
|
+
input[11],
|
|
1263
|
+
input[12],
|
|
1264
|
+
{input[13], input[14], input[15]},
|
|
1265
|
+
{input[16], input[17], input[18]},
|
|
1266
|
+
};
|
|
1267
|
+
const double normSquared = parsed.orientation.x * parsed.orientation.x
|
|
1268
|
+
+ parsed.orientation.y * parsed.orientation.y
|
|
1269
|
+
+ parsed.orientation.z * parsed.orientation.z
|
|
1270
|
+
+ parsed.orientation.w * parsed.orientation.w;
|
|
1271
|
+
if (!(normSquared > 0.0) || !std::isfinite(normSquared)) {
|
|
1272
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1273
|
+
}
|
|
1274
|
+
parsed.bodyCenter = add(parsed.shapeCenter,
|
|
1275
|
+
rotate(parsed.orientation, 1.0 / normSquared, parsed.bodyCenter));
|
|
1276
|
+
BuoyancyResult result{};
|
|
1277
|
+
const BuoyancyStatus status = calculateVoxelBuoyancy(parsed, boxes, result);
|
|
1278
|
+
if (status != BuoyancyStatus::Success) {
|
|
1279
|
+
return static_cast<std::int32_t>(status);
|
|
1280
|
+
}
|
|
1281
|
+
const double values[kBuoyancyOutputStride] = {
|
|
1282
|
+
result.submerged.volume,
|
|
1283
|
+
result.submerged.center.x, result.submerged.center.y,
|
|
1284
|
+
result.submerged.center.z,
|
|
1285
|
+
result.force.x, result.force.y, result.force.z,
|
|
1286
|
+
result.torque.x, result.torque.y, result.torque.z,
|
|
1287
|
+
};
|
|
1288
|
+
std::copy(std::begin(values), std::end(values), output);
|
|
1289
|
+
return static_cast<std::int32_t>(BuoyancyStatus::Success);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
986
1292
|
extern "C" std::uint32_t rundot_kinetix_physics_buoyancy_abi_version() {
|
|
987
1293
|
return 1;
|
|
988
1294
|
}
|
|
@@ -1091,6 +1397,77 @@ extern "C" std::int32_t rundot_kinetix_physics_fluid_forces(
|
|
|
1091
1397
|
return static_cast<std::int32_t>(BuoyancyStatus::Success);
|
|
1092
1398
|
}
|
|
1093
1399
|
|
|
1400
|
+
extern "C" std::int32_t rundot_kinetix_physics_voxel_fluid_forces(
|
|
1401
|
+
const double* input,
|
|
1402
|
+
std::uint32_t input_values,
|
|
1403
|
+
const double* children,
|
|
1404
|
+
std::uint32_t child_values,
|
|
1405
|
+
double* output,
|
|
1406
|
+
std::uint32_t output_values) {
|
|
1407
|
+
using namespace rundot::kinetix::physics;
|
|
1408
|
+
if (output == nullptr || output_values < kFluidForcesOutputStride) {
|
|
1409
|
+
return static_cast<std::int32_t>(BuoyancyStatus::OutputTooSmall);
|
|
1410
|
+
}
|
|
1411
|
+
if (input == nullptr || input_values != kFluidForcesInputStride) {
|
|
1412
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1413
|
+
}
|
|
1414
|
+
for (std::uint32_t index = 0; index < input_values; ++index) {
|
|
1415
|
+
if (!std::isfinite(input[index])) {
|
|
1416
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
std::vector<VoxelBoxShape3D> boxes;
|
|
1420
|
+
if (!parseVoxelBoxes(children, child_values, boxes)) {
|
|
1421
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1422
|
+
}
|
|
1423
|
+
const auto u32 = [](double value, std::uint32_t& result) {
|
|
1424
|
+
if (value < 0.0 || value > 4294967295.0
|
|
1425
|
+
|| std::floor(value) != value) return false;
|
|
1426
|
+
result = static_cast<std::uint32_t>(value);
|
|
1427
|
+
return true;
|
|
1428
|
+
};
|
|
1429
|
+
std::uint32_t ignoredShape = 0;
|
|
1430
|
+
std::uint32_t booleanValue = 0;
|
|
1431
|
+
FluidBodyInput3D body{};
|
|
1432
|
+
FluidVolume3D volume{};
|
|
1433
|
+
if (!u32(input[0], ignoredShape)
|
|
1434
|
+
|| !u32(input[25], booleanValue) || booleanValue > 1
|
|
1435
|
+
|| !u32(input[26], body.layer) || !u32(input[27], body.mask)
|
|
1436
|
+
|| !u32(input[28], volume.id)
|
|
1437
|
+
|| !u32(input[35], booleanValue) || booleanValue > 1
|
|
1438
|
+
|| !u32(input[46], volume.layer) || !u32(input[47], volume.mask)) {
|
|
1439
|
+
return static_cast<std::int32_t>(BuoyancyStatus::InvalidInput);
|
|
1440
|
+
}
|
|
1441
|
+
body.shape = ShapeKind::Compound;
|
|
1442
|
+
body.position = {input[1], input[2], input[3]};
|
|
1443
|
+
body.orientation = {input[4], input[5], input[6], input[7]};
|
|
1444
|
+
body.centerOfMass = {input[11], input[12], input[13]};
|
|
1445
|
+
body.linearVelocity = {input[14], input[15], input[16]};
|
|
1446
|
+
body.angularVelocity = {input[17], input[18], input[19]};
|
|
1447
|
+
body.mass = input[20];
|
|
1448
|
+
body.inertiaDiag = {input[21], input[22], input[23]};
|
|
1449
|
+
body.gravityScale = input[24];
|
|
1450
|
+
body.sleeping = input[25] != 0.0;
|
|
1451
|
+
volume.bounds = {
|
|
1452
|
+
input[29], input[30], input[31], input[32], input[33], input[34]};
|
|
1453
|
+
volume.plane.enabled = input[35] != 0.0;
|
|
1454
|
+
volume.plane.normal = {input[36], input[37], input[38]};
|
|
1455
|
+
volume.plane.offset = input[39];
|
|
1456
|
+
volume.density = input[40];
|
|
1457
|
+
volume.linearDrag = input[41];
|
|
1458
|
+
volume.angularDrag = input[42];
|
|
1459
|
+
volume.flowVelocity = {input[43], input[44], input[45]};
|
|
1460
|
+
const BuoyancyVector3 gravity{input[48], input[49], input[50]};
|
|
1461
|
+
FluidForcesResult3D result{};
|
|
1462
|
+
const BuoyancyStatus status = calculateVoxelFluidForces(
|
|
1463
|
+
body, boxes, volume, gravity, result);
|
|
1464
|
+
if (status != BuoyancyStatus::Success) {
|
|
1465
|
+
return static_cast<std::int32_t>(status);
|
|
1466
|
+
}
|
|
1467
|
+
writeFluidResult(result, output);
|
|
1468
|
+
return static_cast<std::int32_t>(BuoyancyStatus::Success);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1094
1471
|
extern "C" std::int32_t rundot_kinetix_physics_fluid_state_forces(
|
|
1095
1472
|
const double* input,
|
|
1096
1473
|
std::uint32_t input_values,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <cstdint>
|
|
4
|
+
#include <span>
|
|
4
5
|
|
|
5
6
|
#include "physics.hpp"
|
|
6
7
|
|
|
@@ -129,6 +130,11 @@ struct FluidBodyInput3D {
|
|
|
129
130
|
std::uint32_t mask = 1;
|
|
130
131
|
};
|
|
131
132
|
|
|
133
|
+
struct VoxelBoxShape3D {
|
|
134
|
+
BuoyancyVector3 halfSize{};
|
|
135
|
+
BuoyancyVector3 offset{};
|
|
136
|
+
};
|
|
137
|
+
|
|
132
138
|
struct FluidForcesResult3D {
|
|
133
139
|
SubmergedGeometry submerged{};
|
|
134
140
|
BuoyancyVector3 buoyantForce{};
|
|
@@ -161,6 +167,34 @@ BuoyancyStatus calculateFluidForces(
|
|
|
161
167
|
const BuoyancyVector3& gravity,
|
|
162
168
|
FluidForcesResult3D& output);
|
|
163
169
|
|
|
170
|
+
BuoyancyStatus validateVoxelBoxes(std::span<const VoxelBoxShape3D> boxes);
|
|
171
|
+
|
|
172
|
+
BuoyancyStatus calculateVoxelBuoyancy(
|
|
173
|
+
const BuoyancyInput& input,
|
|
174
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
175
|
+
BuoyancyResult& output);
|
|
176
|
+
|
|
177
|
+
BuoyancyStatus calculateVoxelFluidForces(
|
|
178
|
+
const FluidBodyInput3D& body,
|
|
179
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
180
|
+
const FluidVolume3D& volume,
|
|
181
|
+
const BuoyancyVector3& gravity,
|
|
182
|
+
FluidForcesResult3D& output);
|
|
183
|
+
|
|
184
|
+
BuoyancyStatus calculateValidatedVoxelFluidForces(
|
|
185
|
+
const FluidBodyInput3D& body,
|
|
186
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
187
|
+
const FluidVolume3D& volume,
|
|
188
|
+
const BuoyancyVector3& gravity,
|
|
189
|
+
FluidForcesResult3D& output);
|
|
190
|
+
|
|
191
|
+
BuoyancyStatus calculateVoxelFluidStateForces(
|
|
192
|
+
const FluidBodyInput3D& body,
|
|
193
|
+
std::span<const VoxelBoxShape3D> boxes,
|
|
194
|
+
const FluidState3D& state,
|
|
195
|
+
const BuoyancyVector3& gravity,
|
|
196
|
+
FluidForcesResult3D& output);
|
|
197
|
+
|
|
164
198
|
BuoyancyStatus stepBodyInFluids(
|
|
165
199
|
FluidBodyInput3D& body,
|
|
166
200
|
const FluidState3D& state,
|
|
@@ -195,6 +229,20 @@ extern "C" std::int32_t rundot_kinetix_physics_fluid_state_forces(
|
|
|
195
229
|
std::uint32_t input_values,
|
|
196
230
|
double* output,
|
|
197
231
|
std::uint32_t output_values);
|
|
232
|
+
extern "C" std::int32_t rundot_kinetix_physics_voxel_buoyancy(
|
|
233
|
+
const double* input,
|
|
234
|
+
std::uint32_t input_values,
|
|
235
|
+
const double* children,
|
|
236
|
+
std::uint32_t child_values,
|
|
237
|
+
double* output,
|
|
238
|
+
std::uint32_t output_values);
|
|
239
|
+
extern "C" std::int32_t rundot_kinetix_physics_voxel_fluid_forces(
|
|
240
|
+
const double* input,
|
|
241
|
+
std::uint32_t input_values,
|
|
242
|
+
const double* children,
|
|
243
|
+
std::uint32_t child_values,
|
|
244
|
+
double* output,
|
|
245
|
+
std::uint32_t output_values);
|
|
198
246
|
extern "C" std::int32_t rundot_kinetix_physics_validate_fluid_volume(
|
|
199
247
|
const rundot::kinetix::physics::FluidVolume3D* volume);
|
|
200
248
|
extern "C" std::int32_t rundot_kinetix_physics_validate_fluid_state(
|
|
@@ -103,7 +103,8 @@ bool validGeometry(std::span<const double> geometry, ShapeKind kind,
|
|
|
103
103
|
if (primitive && !validPrimitive(childKind, record + 4)) return false;
|
|
104
104
|
if (format == 4) continue;
|
|
105
105
|
if (!nonNegativeInteger(record[13], count) || !nonNegativeInteger(record[14], count)
|
|
106
|
-
|| !nonNegativeInteger(record[15], 5) || record[16]
|
|
106
|
+
|| !nonNegativeInteger(record[15], 5) || !nonNegativeInteger(record[16], 1)
|
|
107
|
+
|| record[16] != values[16] || record[17] != children) return false;
|
|
107
108
|
const auto childOffset = static_cast<std::uint64_t>(record[13]);
|
|
108
109
|
const auto childCount = static_cast<std::uint64_t>(record[14]);
|
|
109
110
|
if (primitive) {
|
|
@@ -115,6 +116,24 @@ bool validGeometry(std::span<const double> geometry, ShapeKind kind,
|
|
|
115
116
|
static_cast<std::uint32_t>(record[15]), depth + 1)) return false;
|
|
116
117
|
payloadEnd = childOffset + childCount;
|
|
117
118
|
}
|
|
119
|
+
if (format == 5 && values[16] == 1) {
|
|
120
|
+
constexpr std::uint64_t recordStride = 18;
|
|
121
|
+
for (std::uint64_t child = 0; child < children; ++child) {
|
|
122
|
+
const double* first = values + child * recordStride;
|
|
123
|
+
if (first[0] != static_cast<double>(ShapeKind::Box)
|
|
124
|
+
|| first[13] != 0 || first[14] != 0 || first[15] != 0) return false;
|
|
125
|
+
for (std::uint64_t other = 0; other < child; ++other) {
|
|
126
|
+
const double* second = values + other * recordStride;
|
|
127
|
+
const bool overlap = first[1] - first[4] < second[1] + second[4]
|
|
128
|
+
&& second[1] - second[4] < first[1] + first[4]
|
|
129
|
+
&& first[2] - first[5] < second[2] + second[5]
|
|
130
|
+
&& second[2] - second[5] < first[2] + first[5]
|
|
131
|
+
&& first[3] - first[6] < second[3] + second[6]
|
|
132
|
+
&& second[3] - second[6] < first[3] + first[6];
|
|
133
|
+
if (overlap) return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
118
137
|
return true;
|
|
119
138
|
}
|
|
120
139
|
|