From 6481857e8e3dd8a685e7b678b05971de08b7f3bc Mon Sep 17 00:00:00 2001 From: Orion Kindel Date: Sat, 25 Jan 2025 11:53:54 -0600 Subject: [PATCH] lots --- Cargo.lock | 6 +- Cargo.toml | 2 + prefab/Anaconda.stl | Bin 13984 -> 0 bytes prefab/anaconda-bones.json | 302 ++++++++++++++++++ prefab/anaconda.stl | Bin 0 -> 72784 bytes prefab/export_highlighted_armature.py | 15 +- ...ase-bones.json => human-female-bones.json} | 0 ...{female_base_500f.stl => human-female.stl} | Bin prefab/human-male-bones.json | 1 + prefab/human-male.stl | Bin 0 -> 52234 bytes prefab/male_base-bones.json | 1 - prefab/male_base_500f.stl | Bin 52984 -> 0 bytes src/body.rs | 126 ++++---- src/debug_render.rs | 27 +- src/either.rs | 22 ++ src/main.rs | 4 + src/mesh.rs | 113 +++++-- src/prefab.rs | 80 +++-- src/xpbd/builder.rs | 157 ++++++--- src/xpbd/constraint/graph.rs | 7 +- src/xpbd/constraint/impl_.rs | 62 +++- src/xpbd/handle.rs | 2 +- src/xpbd/mod.rs | 175 ++++++++-- src/xpbd/set.rs | 96 +++++- src/xpbd/step.rs | 113 +++++-- 25 files changed, 1077 insertions(+), 234 deletions(-) delete mode 100644 prefab/Anaconda.stl create mode 100644 prefab/anaconda-bones.json create mode 100644 prefab/anaconda.stl rename prefab/{female_base-bones.json => human-female-bones.json} (100%) rename prefab/{female_base_500f.stl => human-female.stl} (100%) create mode 100644 prefab/human-male-bones.json create mode 100644 prefab/human-male.stl delete mode 100644 prefab/male_base-bones.json delete mode 100644 prefab/male_base_500f.stl diff --git a/Cargo.lock b/Cargo.lock index 2e66800d8..0d1c89f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5128,6 +5128,7 @@ dependencies = [ "num-traits", "num_cpus", "ordered-float", + "parry3d", "postcard", "rand", "rapier3d", @@ -5141,6 +5142,7 @@ dependencies = [ "tinyvec", "tobj", "tritet", + "wide", ] [[package]] @@ -6686,9 +6688,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.31" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0ca27312d1e9218687a4e804cd4b7410bf54125d54d13e5170efbf09066d24" +checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" dependencies = [ "bytemuck", "safe_arch", diff --git a/Cargo.toml b/Cargo.toml index 9cf1b3925..e78b29cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,8 @@ itertools = "0.14.0" serde_cbor = "0.11.2" postcard = { version = "1.1.1", features = ["use-std"] } bevy_framepace = "0.18" +wide = "0.7.32" +parry3d = "0.18.0" [[bin]] name = "example" diff --git a/prefab/Anaconda.stl b/prefab/Anaconda.stl deleted file mode 100644 index ee5343261c8639a97ea57bada584548f2d586cb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13984 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 Kg=jd32>lOG3Php+ diff --git a/prefab/anaconda-bones.json b/prefab/anaconda-bones.json new file mode 100644 index 000000000..db47e5692 --- /dev/null +++ b/prefab/anaconda-bones.json @@ -0,0 +1,302 @@ +{ + "bone": { + "head": { + "x": 0.6070398688316345, + "y": 3.488184690475464, + "z": 0.19564415514469147 + }, + "tail": { + "x": 0.6236315965652466, + "y": 3.4085373878479004, + "z": 0.1993076056241989 + } + }, + "bone_009": { + "head": { + "x": 0.6236315965652466, + "y": 3.4085373878479004, + "z": 0.1993076056241989 + }, + "tail": { + "x": 0.6183035969734192, + "y": 3.316044569015503, + "z": 0.20030179619789124 + } + }, + "bone_007": { + "head": { + "x": 0.6183035969734192, + "y": 3.316044569015503, + "z": 0.20030179619789124 + }, + "tail": { + "x": 0.5486531853675842, + "y": 3.2271180152893066, + "z": 0.19193406403064728 + } + }, + "bone_008": { + "head": { + "x": 0.5486531853675842, + "y": 3.2271180152893066, + "z": 0.19193406403064728 + }, + "tail": { + "x": 0.4748899042606354, + "y": 3.1820590496063232, + "z": 0.19288358092308044 + } + }, + "bone_006": { + "head": { + "x": 0.4748899042606354, + "y": 3.1820590496063232, + "z": 0.19288358092308044 + }, + "tail": { + "x": 0.32671600580215454, + "y": 3.11191725730896, + "z": 0.19421303272247314 + } + }, + "bone_010": { + "head": { + "x": 0.32671600580215454, + "y": 3.11191725730896, + "z": 0.19421303272247314 + }, + "tail": { + "x": 0.19413162767887115, + "y": 2.997762441635132, + "z": 0.1879468560218811 + } + }, + "bone_004": { + "head": { + "x": 0.19413162767887115, + "y": 2.997762441635132, + "z": 0.1879468560218811 + }, + "tail": { + "x": 0.08068132400512695, + "y": 2.8404202461242676, + "z": 0.18485479056835175 + } + }, + "bone_011": { + "head": { + "x": 0.08068132400512695, + "y": 2.8404202461242676, + "z": 0.18485479056835175 + }, + "tail": { + "x": -0.014009974896907806, + "y": 2.670964241027832, + "z": 0.17771753668785095 + } + }, + "bone_005": { + "head": { + "x": -0.014009974896907806, + "y": 2.670964241027832, + "z": 0.17771753668785095 + }, + "tail": { + "x": -0.07677791267633438, + "y": 2.5024752616882324, + "z": 0.17266269028186798 + } + }, + "bone_012": { + "head": { + "x": -0.07677791267633438, + "y": 2.5024752616882324, + "z": 0.17266269028186798 + }, + "tail": { + "x": -0.11340910941362381, + "y": 2.3294928073883057, + "z": 0.17319327592849731 + } + }, + "bone_002": { + "head": { + "x": -0.11340910941362381, + "y": 2.3294928073883057, + "z": 0.17319327592849731 + }, + "tail": { + "x": -0.08609960973262787, + "y": 2.0730838775634766, + "z": 0.17111866176128387 + } + }, + "bone_015": { + "head": { + "x": -0.08609960973262787, + "y": 2.0730838775634766, + "z": 0.17111866176128387 + }, + "tail": { + "x": -0.05879011005163193, + "y": 1.816674828529358, + "z": 0.16904404759407043 + } + }, + "bone_014": { + "head": { + "x": -0.05879011005163193, + "y": 1.816674828529358, + "z": 0.16904404759407043 + }, + "tail": { + "x": -0.031480610370635986, + "y": 1.5602657794952393, + "z": 0.166969433426857 + } + }, + "bone_016": { + "head": { + "x": -0.031480610370635986, + "y": 1.5602657794952393, + "z": 0.166969433426857 + }, + "tail": { + "x": -0.004171114414930344, + "y": 1.3038568496704102, + "z": 0.16489481925964355 + } + }, + "bone_013": { + "head": { + "x": -0.004171114414930344, + "y": 1.3038568496704102, + "z": 0.16489481925964355 + }, + "tail": { + "x": 0.05561229586601257, + "y": 1.041410207748413, + "z": 0.16239383816719055 + } + }, + "bone_017": { + "head": { + "x": 0.05561229586601257, + "y": 1.041410207748413, + "z": 0.16239383816719055 + }, + "tail": { + "x": 0.15776894986629486, + "y": 0.6679806709289551, + "z": 0.14728130400180817 + } + }, + "bone_021": { + "head": { + "x": 0.15776894986629486, + "y": 0.6679806709289551, + "z": 0.14728130400180817 + }, + "tail": { + "x": 0.1563047468662262, + "y": 0.28363165259361267, + "z": 0.1468256264925003 + } + }, + "bone_001": { + "head": { + "x": 0.1563047468662262, + "y": 0.28363165259361267, + "z": 0.1468256264925003 + }, + "tail": { + "x": 0.14030331373214722, + "y": -0.11586377024650574, + "z": 0.16327451169490814 + } + }, + "bone_019": { + "head": { + "x": 0.14030331373214722, + "y": -0.11586377024650574, + "z": 0.16327451169490814 + }, + "tail": { + "x": 0.08491484820842743, + "y": -0.5309418439865112, + "z": 0.15609216690063477 + } + }, + "bone_018": { + "head": { + "x": 0.08491484820842743, + "y": -0.5309418439865112, + "z": 0.15609216690063477 + }, + "tail": { + "x": 0.02284509874880314, + "y": -0.9243378639221191, + "z": 0.14005453884601593 + } + }, + "bone_020": { + "head": { + "x": 0.02284509874880314, + "y": -0.9243378639221191, + "z": 0.14005453884601593 + }, + "tail": { + "x": -0.03272989019751549, + "y": -1.3230215311050415, + "z": 0.1516072154045105 + } + }, + "bone_003": { + "head": { + "x": -0.03272989019751549, + "y": -1.3230215311050415, + "z": 0.1516072154045105 + }, + "tail": { + "x": -0.0303995069116354, + "y": -1.721837043762207, + "z": 0.1625695526599884 + } + }, + "bone_023": { + "head": { + "x": -0.0303995069116354, + "y": -1.721837043762207, + "z": 0.1625695526599884 + }, + "tail": { + "x": -0.034437455236911774, + "y": -2.1189937591552734, + "z": 0.17180347442626953 + } + }, + "bone_022": { + "head": { + "x": -0.034437455236911774, + "y": -2.1189937591552734, + "z": 0.17180347442626953 + }, + "tail": { + "x": -0.05511781573295593, + "y": -2.520186185836792, + "z": 0.22017113864421844 + } + }, + "bone_024": { + "head": { + "x": -0.05511781573295593, + "y": -2.520186185836792, + "z": 0.22017113864421844 + }, + "tail": { + "x": -0.024445362389087677, + "y": -2.902860403060913, + "z": 0.22326646745204926 + } + } +} diff --git a/prefab/anaconda.stl b/prefab/anaconda.stl new file mode 100644 index 0000000000000000000000000000000000000000..e269ce77b6deb24a8122452f7606db96f09b59b1 GIT binary patch literal 72784 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 literal 0 HcmV?d00001 diff --git a/prefab/export_highlighted_armature.py b/prefab/export_highlighted_armature.py index 9d99831a0..2ecaa5ccc 100644 --- a/prefab/export_highlighted_armature.py +++ b/prefab/export_highlighted_armature.py @@ -2,14 +2,15 @@ import bpy import json from bpy import context -o = bpy.context.object -out = {} vec3 = lambda vec: {"x": vec.x, "y": vec.y, "z": vec.z} -for b in o.pose.bones: - head = o.matrix_world @ b.matrix @ b.head - tail = o.matrix_world @ b.matrix @ b.tail - name = b.name.replace('.', '_').replace('-', '_').lower() - out[name] = {"head": vec3(head), "tail": vec3(tail)} +bone_head = lambda ctx, bone: vec3(ctx.matrix_world.translation + bone.head_local) +bone_tail = lambda ctx, bone: vec3(ctx.matrix_world.translation + bone.tail_local) +bone = lambda ctx, bone: {"head": bone_head(ctx, bone), "tail": bone_tail(ctx, bone)} +bone_name = lambda name: name.replace('.', '_').replace('-', '_').lower() +arm_context = bpy.context.object +arm_data = arm_context.data + +out = {bone_name(b.name) : bone(arm_context, b) for b in arm_data.bones} print('\n\n\n\n') print(json.dumps(out)) diff --git a/prefab/female_base-bones.json b/prefab/human-female-bones.json similarity index 100% rename from prefab/female_base-bones.json rename to prefab/human-female-bones.json diff --git a/prefab/female_base_500f.stl b/prefab/human-female.stl similarity index 100% rename from prefab/female_base_500f.stl rename to prefab/human-female.stl diff --git a/prefab/human-male-bones.json b/prefab/human-male-bones.json new file mode 100644 index 000000000..bce44d548 --- /dev/null +++ b/prefab/human-male-bones.json @@ -0,0 +1 @@ +{"m_hips": {"head": {"x": 0.003162541426718235, "y": 0.10195900499820709, "z": 0.5904829502105713}, "tail": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}}, "m_waist": {"head": {"x": 0.0010016794549301267, "y": 0.10252499580383301, "z": 0.5886345505714417}, "tail": {"x": 0.001001716940663755, "y": 0.152367502450943, "z": 0.8066956996917725}}, "m_spine": {"head": {"x": 0.001001716940663755, "y": 0.152367502450943, "z": 0.8066956996917725}, "tail": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.097070336341858}}, "bone_004": {"head": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.097070336341858}, "tail": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.3747049570083618}}, "m_chest": {"head": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.3747049570083618}, "tail": {"x": 0.0010018404573202133, "y": 0.18572551012039185, "z": 1.7932240962982178}}, "bone_002": {"head": {"x": 0.0010018404573202133, "y": 0.18572551012039185, "z": 1.7932240962982178}, "tail": {"x": -0.47783803939819336, "y": 0.18558213114738464, "z": 1.6446514129638672}}, "bone_003": {"head": {"x": 0.0010018404573202133, "y": 0.18572551012039185, "z": 1.7932240962982178}, "tail": {"x": 0.5446823239326477, "y": 0.18519344925880432, "z": 1.6734424829483032}}, "bone_005": {"head": {"x": 0.0010018404573202133, "y": 0.18572551012039185, "z": 1.7932240962982178}, "tail": {"x": 0.0010019127512350678, "y": 0.18170642852783203, "z": 1.934108853340149}}, "bone_018": {"head": {"x": 0.0010019127512350678, "y": 0.18170642852783203, "z": 1.934108853340149}, "tail": {"x": 0.005812562070786953, "y": 0.1836266666650772, "z": 2.1737537384033203}}, "m_head": {"head": {"x": 0.005812562070786953, "y": 0.1836266666650772, "z": 2.1737537384033203}, "tail": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}}, "m_eyebrows": {"head": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}, "tail": {"x": 0.005812614690512419, "y": -0.19047966599464417, "z": 2.4332451820373535}}, "m_jaw": {"head": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}, "tail": {"x": 0.005812568124383688, "y": -0.1758112907409668, "z": 2.0805587768554688}}, "r_upper_arm": {"head": {"x": -0.47783803939819336, "y": 0.18558213114738464, "z": 1.6446514129638672}, "tail": {"x": -1.3812698125839233, "y": 0.20588333904743195, "z": 1.4414451122283936}}, "r_lower_arm": {"head": {"x": -1.3812698125839233, "y": 0.20588333904743195, "z": 1.4414451122283936}, "tail": {"x": -2.076347589492798, "y": 0.044203609228134155, "z": 1.304756760597229}}, "r_hand": {"head": {"x": -2.076347589492798, "y": 0.044203609228134155, "z": 1.304756760597229}, "tail": {"x": -2.343585729598999, "y": 0.056169360876083374, "z": 1.2113057374954224}}, "l_upper_arm": {"head": {"x": 0.5446823239326477, "y": 0.18519344925880432, "z": 1.6734424829483032}, "tail": {"x": 1.3663803339004517, "y": 0.20563922822475433, "z": 1.483242392539978}}, "l_lower_arm": {"head": {"x": 1.3663803339004517, "y": 0.20563922822475433, "z": 1.483242392539978}, "tail": {"x": 2.0663564205169678, "y": 0.04467569291591644, "z": 1.3416621685028076}}, "l_hand": {"head": {"x": 2.0663564205169678, "y": 0.04467569291591644, "z": 1.3416621685028076}, "tail": {"x": 2.3266260623931885, "y": 0.05705520510673523, "z": 1.2540311813354492}}, "r_upper_leg": {"head": {"x": -0.25513601303100586, "y": 0.07877105474472046, "z": 0.1387087106704712}, "tail": {"x": -0.5912657380104065, "y": 0.06895933300256729, "z": -0.9701849222183228}}, "r_lower_leg": {"head": {"x": -0.5912657380104065, "y": 0.06895933300256729, "z": -0.9701849222183228}, "tail": {"x": -0.9735252261161804, "y": 0.16303546726703644, "z": -2.18613862991333}}, "r_ancle": {"head": {"x": -0.9735252261161804, "y": 0.16303546726703644, "z": -2.18613862991333}, "tail": {"x": -1.0920854806900024, "y": -0.16820663213729858, "z": -2.3055927753448486}}, "r_toe": {"head": {"x": -1.0920854806900024, "y": -0.16820663213729858, "z": -2.3055927753448486}, "tail": {"x": -1.1538002490997314, "y": -0.4375842809677124, "z": -2.3457844257354736}}, "l_upper_leg": {"head": {"x": 0.2947828471660614, "y": 0.07972406595945358, "z": 0.13740015029907227}, "tail": {"x": 0.6011503338813782, "y": 0.07002540677785873, "z": -0.9768697023391724}}, "l_lower_leg": {"head": {"x": 0.6011503338813782, "y": 0.07002540677785873, "z": -0.9768697023391724}, "tail": {"x": 0.9844488501548767, "y": 0.163835346698761, "z": -2.193763494491577}}, "l_ancle": {"head": {"x": 0.9844488501548767, "y": 0.163835346698761, "z": -2.193763494491577}, "tail": {"x": 1.1052886247634888, "y": -0.16843479871749878, "z": -2.3099963665008545}}, "l_toe": {"head": {"x": 1.1052886247634888, "y": -0.16843479871749878, "z": -2.3099963665008545}, "tail": {"x": 1.1677669286727905, "y": -0.4420241117477417, "z": -2.3523178100585938}}, "bone": {"head": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}, "tail": {"x": 0.2947828471660614, "y": 0.07972406595945358, "z": 0.13740015029907227}}, "bone_001": {"head": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}, "tail": {"x": -0.25513601303100586, "y": 0.07877105474472046, "z": 0.1387087106704712}}} diff --git a/prefab/human-male.stl b/prefab/human-male.stl new file mode 100644 index 0000000000000000000000000000000000000000..33e37e6432e405a2bb4c3c4959450ff2321c3e6c GIT binary patch literal 52234 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 literal 0 HcmV?d00001 diff --git a/prefab/male_base-bones.json b/prefab/male_base-bones.json deleted file mode 100644 index 285319b55..000000000 --- a/prefab/male_base-bones.json +++ /dev/null @@ -1 +0,0 @@ -{"m_hips": {"head": {"x": -0.0012977259466424584, "y": 0.09873443841934204, "z": 0.5799066424369812}, "tail": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}}, "m_waist": {"head": {"x": -0.0012977259466424584, "y": 0.09873443841934204, "z": 0.5799066424369812}, "tail": {"x": 0.001001716940663755, "y": 0.152367502450943, "z": 0.8066956996917725}}, "m_spine": {"head": {"x": 0.001001716940663755, "y": 0.152367502450943, "z": 0.8066956996917725}, "tail": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.097070336341858}}, "m_chest": {"head": {"x": 0.0010017681634053588, "y": 0.18974459171295166, "z": 1.097070336341858}, "tail": {"x": 0.0011565848253667355, "y": 0.18114709854125977, "z": 1.7951523065567017}}, "bone_018": {"head": {"x": 0.0010019127512350678, "y": 0.18170642852783203, "z": 1.934108853340149}, "tail": {"x": 0.005812574177980423, "y": 0.13230469822883606, "z": 2.073798179626465}}, "m_head": {"head": {"x": 0.005812574177980423, "y": 0.13230469822883606, "z": 2.073798179626465}, "tail": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}}, "m_eyebrows": {"head": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}, "tail": {"x": 0.005812614690512419, "y": -0.19047966599464417, "z": 2.4332451820373535}}, "m_jaw": {"head": {"x": 0.005812613759189844, "y": 0.10908273607492447, "z": 2.375145435333252}, "tail": {"x": 0.005812568124383688, "y": -0.1758112907409668, "z": 2.0805587768554688}}, "r_shoulder": {"head": {"x": 0.0011565848253667355, "y": 0.18114709854125977, "z": 1.7951523065567017}, "tail": {"x": -0.5703960061073303, "y": 0.18268556892871857, "z": 1.6796873807907104}}, "r_upper_arm": {"head": {"x": -0.5788930654525757, "y": 0.1853611022233963, "z": 1.6834924221038818}, "tail": {"x": -1.3812698125839233, "y": 0.20588333904743195, "z": 1.4414451122283936}}, "r_lower_arm": {"head": {"x": -1.3812698125839233, "y": 0.20588333904743195, "z": 1.4414451122283936}, "tail": {"x": -2.076347589492798, "y": 0.044203609228134155, "z": 1.304756760597229}}, "r_hand": {"head": {"x": -2.076347589492798, "y": 0.044203609228134155, "z": 1.304756760597229}, "tail": {"x": -2.343585729598999, "y": 0.056169360876083374, "z": 1.2113057374954224}}, "r_finger_index01": {"head": {"x": -2.335391044616699, "y": -0.07816237211227417, "z": 1.215346336364746}, "tail": {"x": -2.4461824893951416, "y": -0.10163837671279907, "z": 1.1580877304077148}}, "r_finger_index01_001": {"head": {"x": -2.4461824893951416, "y": -0.10163837671279907, "z": 1.1580877304077148}, "tail": {"x": -2.55203914642334, "y": -0.12349137663841248, "z": 1.0932888984680176}}, "r_thumb01": {"head": {"x": -2.076347589492798, "y": 0.04420362412929535, "z": 1.3047566413879395}, "tail": {"x": -2.170755624771118, "y": -0.08250358700752258, "z": 1.2216516733169556}}, "r_thumb02": {"head": {"x": -2.170755624771118, "y": -0.08250358700752258, "z": 1.2216516733169556}, "tail": {"x": -2.2050626277923584, "y": -0.1487712264060974, "z": 1.1610702276229858}}, "r_thumb03": {"head": {"x": -2.2050626277923584, "y": -0.1487712264060974, "z": 1.1610702276229858}, "tail": {"x": -2.231705904006958, "y": -0.20103195309638977, "z": 1.1033203601837158}}, "r_mf01": {"head": {"x": -2.349660634994507, "y": -0.009082093834877014, "z": 1.2319086790084839}, "tail": {"x": -2.4427998065948486, "y": -0.016127586364746094, "z": 1.1780006885528564}}, "r_mf02": {"head": {"x": -2.4427998065948486, "y": -0.016127586364746094, "z": 1.1780006885528564}, "tail": {"x": -2.566545009613037, "y": -0.024504736065864563, "z": 1.0831310749053955}}, "r_rf01": {"head": {"x": -2.3528802394866943, "y": 0.05754943937063217, "z": 1.2252631187438965}, "tail": {"x": -2.4380619525909424, "y": 0.05799344927072525, "z": 1.1739680767059326}}, "r_rf02": {"head": {"x": -2.4380619525909424, "y": 0.05799344927072525, "z": 1.1739680767059326}, "tail": {"x": -2.5430376529693604, "y": 0.06054571270942688, "z": 1.0920013189315796}}, "r_pf01": {"head": {"x": -2.3462910652160645, "y": 0.11885695159435272, "z": 1.2159702777862549}, "tail": {"x": -2.422309398651123, "y": 0.12374290823936462, "z": 1.1711504459381104}}, "r_pf02": {"head": {"x": -2.422309398651123, "y": 0.12374290823936462, "z": 1.1711504459381104}, "tail": {"x": -2.495955228805542, "y": 0.12862640619277954, "z": 1.1059892177581787}}, "bone_003": {"head": {"x": -0.5703960061073303, "y": 0.18268556892871857, "z": 1.6796873807907104}, "tail": {"x": -0.5788930654525757, "y": 0.1853611022233963, "z": 1.6834924221038818}}, "l_shoulder": {"head": {"x": 0.0011565848253667355, "y": 0.18114709854125977, "z": 1.7951523065567017}, "tail": {"x": 0.5373091101646423, "y": 0.1827419400215149, "z": 1.6803903579711914}}, "l_upper_arm": {"head": {"x": 0.5446823239326477, "y": 0.18519344925880432, "z": 1.6734424829483032}, "tail": {"x": 1.3663803339004517, "y": 0.20563922822475433, "z": 1.483242392539978}}, "l_lower_arm": {"head": {"x": 1.3663803339004517, "y": 0.20563922822475433, "z": 1.483242392539978}, "tail": {"x": 2.0663564205169678, "y": 0.04467569291591644, "z": 1.3416621685028076}}, "l_hand": {"head": {"x": 2.0663564205169678, "y": 0.04467569291591644, "z": 1.3416621685028076}, "tail": {"x": 2.3266260623931885, "y": 0.05705520510673523, "z": 1.2540311813354492}}, "l_finger_index01": {"head": {"x": 2.3180785179138184, "y": -0.07684838771820068, "z": 1.258310079574585}, "tail": {"x": 2.429262399673462, "y": -0.10054698586463928, "z": 1.2034382820129395}}, "l_finger_index02": {"head": {"x": 2.429262399673462, "y": -0.10054698586463928, "z": 1.2034382820129395}, "tail": {"x": 2.531839609146118, "y": -0.12198156118392944, "z": 1.136545181274414}}, "l_thumb01": {"head": {"x": 2.0663564205169678, "y": 0.04467570781707764, "z": 1.341662049293518}, "tail": {"x": 2.1592934131622314, "y": -0.08540076017379761, "z": 1.2809035778045654}}, "l_thumb02": {"head": {"x": 2.1592934131622314, "y": -0.08540076017379761, "z": 1.2809035778045654}, "tail": {"x": 2.1965291500091553, "y": -0.14615267515182495, "z": 1.2300605773925781}}, "l_thumb03": {"head": {"x": 2.1965291500091553, "y": -0.14615267515182495, "z": 1.2300605773925781}, "tail": {"x": 2.2283248901367188, "y": -0.20005851984024048, "z": 1.1541619300842285}}, "l_mf01": {"head": {"x": 2.330841302871704, "y": -0.007817864418029785, "z": 1.2741056680679321}, "tail": {"x": 2.422492265701294, "y": -0.014490395784378052, "z": 1.2213836908340454}}, "l_mf02": {"head": {"x": 2.422492265701294, "y": -0.014490395784378052, "z": 1.2213836908340454}, "tail": {"x": 2.5443692207336426, "y": -0.022395059466362, "z": 1.1281065940856934}}, "l_rf01": {"head": {"x": 2.334294080734253, "y": 0.05875726044178009, "z": 1.2673252820968628}, "tail": {"x": 2.4181511402130127, "y": 0.059533797204494476, "z": 1.2170979976654053}}, "l_rf02": {"head": {"x": 2.4181511402130127, "y": 0.059533797204494476, "z": 1.2170979976654053}, "tail": {"x": 2.5215868949890137, "y": 0.06247615069150925, "z": 1.1364574432373047}}, "l_pf01": {"head": {"x": 2.328108549118042, "y": 0.1199670359492302, "z": 1.2577956914901733}, "tail": {"x": 2.4029579162597656, "y": 0.12514632940292358, "z": 1.2139159440994263}}, "l_pf02": {"head": {"x": 2.4029579162597656, "y": 0.12514632940292358, "z": 1.2139159440994263}, "tail": {"x": 2.4755702018737793, "y": 0.13029323518276215, "z": 1.1496829986572266}}, "bone_006": {"head": {"x": 0.0011565848253667355, "y": 0.18114709854125977, "z": 1.7951523065567017}, "tail": {"x": 0.0010019127512350678, "y": 0.18170642852783203, "z": 1.934108853340149}}, "bone_007": {"head": {"x": 0.5373091101646423, "y": 0.1827419400215149, "z": 1.6803903579711914}, "tail": {"x": 0.5446823239326477, "y": 0.18519344925880432, "z": 1.6734424829483032}}, "r_upper_leg": {"head": {"x": -0.25513601303100586, "y": 0.07877105474472046, "z": 0.1387087106704712}, "tail": {"x": -0.5912657380104065, "y": 0.06895933300256729, "z": -0.9701849222183228}}, "r_lower_leg": {"head": {"x": -0.5912657380104065, "y": 0.06895933300256729, "z": -0.9701849222183228}, "tail": {"x": -0.9735252261161804, "y": 0.16303546726703644, "z": -2.18613862991333}}, "r_ancle": {"head": {"x": -0.9735252261161804, "y": 0.16303546726703644, "z": -2.18613862991333}, "tail": {"x": -1.0920854806900024, "y": -0.16820663213729858, "z": -2.3055927753448486}}, "r_toe": {"head": {"x": -1.0920854806900024, "y": -0.16820663213729858, "z": -2.3055927753448486}, "tail": {"x": -1.1538002490997314, "y": -0.4375842809677124, "z": -2.3457844257354736}}, "l_upper_leg": {"head": {"x": 0.2947828471660614, "y": 0.07972406595945358, "z": 0.13740015029907227}, "tail": {"x": 0.6011503338813782, "y": 0.07002540677785873, "z": -0.9768697023391724}}, "l_lower_leg": {"head": {"x": 0.6011503338813782, "y": 0.07002540677785873, "z": -0.9768697023391724}, "tail": {"x": 0.9844488501548767, "y": 0.163835346698761, "z": -2.193763494491577}}, "l_ancle": {"head": {"x": 0.9844488501548767, "y": 0.163835346698761, "z": -2.193763494491577}, "tail": {"x": 1.1052886247634888, "y": -0.16843479871749878, "z": -2.3099963665008545}}, "l_toe": {"head": {"x": 1.1052886247634888, "y": -0.16843479871749878, "z": -2.3099963665008545}, "tail": {"x": 1.1677669286727905, "y": -0.4420241117477417, "z": -2.3523178100585938}}, "bone": {"head": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}, "tail": {"x": 0.2947828471660614, "y": 0.07972406595945358, "z": 0.13740015029907227}}, "bone_001": {"head": {"x": 0.010821257717907429, "y": 0.07877334952354431, "z": 0.08994382619857788}, "tail": {"x": -0.25513601303100586, "y": 0.07877105474472046, "z": 0.1387087106704712}}} diff --git a/prefab/male_base_500f.stl b/prefab/male_base_500f.stl deleted file mode 100644 index 55ab986088517ee85c700579bd206b2302384829..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52984 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 diff --git a/src/body.rs b/src/body.rs index db20b9d17..f1cf15f34 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,4 +1,5 @@ use std::ops::DerefMut; +use std::sync::Arc; use ::bevy::app::{Plugin, Update}; use ::bevy::asset::Assets; @@ -13,6 +14,7 @@ use ::bevy::prelude::{Added, Drag, DragEnd, Entity, + IntoSystemConfigs, Mesh as BevyMesh, Mesh3d, Meshable, @@ -32,7 +34,8 @@ use ::bevy::render::mesh::VertexAttributeValues; use bevy::prelude as bevy; use bevy_ecs::component::ComponentIdFor; use bevy_pbr::MeshMaterial3d; -use nalgebra::{Isometry3, Unit, Vector3}; +use nalgebra::{Isometry3, Translation3, Unit, Vector3}; +use parry3d::shape::SharedShape; use rapier3d::prelude::{Collider, TypedShape}; use crate::camera::OrbitCamera; @@ -57,12 +60,10 @@ impl Plugin for BodyPlugin { .add_systems(Update, Body::draggable_added) .add_systems(Update, Body::collider_added) .add_systems(Update, Body::mesh_added) - .add_systems(Update, Body::rigid_committed) - .add_systems(Update, Body::xpbd_committed) - .add_systems(Update, Body::sync_rigid_building) - .add_systems(Update, Body::sync_xpbd_building) - .add_systems(Update, Body::sync_rigid) - .add_systems(Update, Body::sync_xpbd); + .add_systems(Update, + (Body::rigid_committed, Body::sync_rigid_building, Body::sync_rigid).chain()) + .add_systems(Update, + (Body::xpbd_committed, Body::sync_xpbd_building, Body::sync_xpbd).chain()); } } @@ -144,47 +145,19 @@ pub mod physics { pub struct XPBDVertexIndex(pub Vec); impl XPBDVertexIndex { - pub fn new(bodies: &rapier::RigidBodySet, - xpbd: &xpbd::Set, + pub fn new(xpbd: &xpbd::Set, handle: xpbd::Handle, trimesh: &TriMesh) -> Self { - let vert2rigidbody = |body: &xpbd::Body| { - let point2handle = body.handles - .iter() - .map(|b| { - let point = bodies.get(*b).unwrap().translation(); - (*point, *b) - }) - .collect::, rapier::RigidBodyHandle)>>(); - - trimesh.vertices - .iter() - .map(|point| { - let Some((dist, body)) = - point2handle.iter() - .fold(Option::<(f32, rapier::RigidBodyHandle)>::None, - |found, (pt, body)| { - let dist = (*pt - point).magnitude(); - match found { - | Some((fdist, fbody)) if fdist < dist => { - Some((fdist, fbody)) - }, - | _ => Some((dist, *body)), - } - }) - else { - unreachable!() - }; - if dist >= 0.1 { - panic!(); - } - body - }) - .collect::>() - }; - - Self(xpbd.get(handle, vert2rigidbody).unwrap()) + let face2handle = xpbd.get(handle, |b| b.trimesh.iter().map(|xs| xs.map(|x| b.handles[x])).collect::>()).unwrap(); + let mut v2b: Vec> = trimesh.vertices.iter().map(|_| None).collect(); + trimesh.faces.iter().enumerate().for_each(|(fix, face)| { + face.iter().enumerate().for_each(|(fvix, vix)| { + v2b[(*vix) as usize] = Some(face2handle[fix][fvix]); + }); + }); + let v2b = v2b.into_iter().map(|a| a.unwrap()).collect(); + Self(v2b) } } } @@ -247,6 +220,8 @@ impl Body { }; let body = phy.bodies.get_mut(*h).unwrap(); + body.set_body_type(rapier3d::prelude::RigidBodyType::KinematicPositionBased, true); + let (cam_id, mut cam, _) = cam.single_mut(); cam.enabled = false; @@ -272,13 +247,13 @@ impl Body { let denom = plane_normal.dot(&ray_dir); let t = plane_normal.dot(&(plane_origin - ray_origin)) / denom; let t = ray_origin + t * ray_dir; - body.set_translation(t, true); - body.set_linvel(Vector3::zeros(), true); + body.set_next_kinematic_position(Isometry3 {translation: Translation3::from(t), ..Default::default()}); } fn on_drag_end(t: Trigger>, mut commands: Commands, plane_id: ComponentIdFor, + mut rigid: Query<&physics::RigidBody>, mut cam: Query<&mut OrbitCamera>, mut phy: Query<&mut PhysicsState>) { let mut phy = phy.single_mut(); @@ -287,6 +262,11 @@ impl Body { } phy.ghosting = false; + if let Ok(physics::RigidBody(h)) = rigid.get_mut(t.entity()) { + let body = phy.bodies.get_mut(*h).unwrap(); + body.set_body_type(rapier3d::prelude::RigidBodyType::Dynamic, true); + }; + commands.entity(t.entity()).remove_by_id(plane_id.get()); } @@ -358,7 +338,9 @@ impl Body { let mut phy = phy.single_mut(); let PhysicsState { bodies, colliders, .. } = phy.deref_mut(); for (id, t, builder, mut coll) in &mut q { - coll.0.shape_mut().scale_dyn(&t.0.vector, 10); + let shp = coll.0.shape().scale_dyn(&t.0.vector, 10).unwrap(); + let shp = SharedShape(Arc::from(shp)); + coll.0.set_shape(shp); let body = bodies.insert(builder.0.clone()); colliders.insert_with_parent(coll.0.clone(), body, bodies); commands.entity(id) @@ -385,33 +367,37 @@ impl Body { .. } = phy.deref_mut(); for (id, mut t, mut s, mut builder, mut tri, mut tet, mut bones) in &mut q { + let builder = &mut builder.0; + let scale = &mut s.0.vector; + let translation = builder.transform.translation.vector; + let rotation = builder.transform.rotation; + let bones = &mut bones.0; let tet = &mut tet.0; let tri = &mut tri.0; - let builder = &mut builder.0; tet.scale(scale); - tet.translate(&builder.transform.translation.vector); - tet.rotate(&builder.transform.rotation); + tet.translate(&translation); + tet.rotate(&rotation); tri.scale(scale); - tri.translate(&builder.transform.translation.vector); - tri.rotate_about(tet.centroid(), &builder.transform.rotation); + tri.translate(&translation); + tri.rotate_about(tet.centroid(), &rotation); bones.scale(scale); - bones.translate(&builder.transform.translation.vector); - bones.rotate_about(&tet.centroid(), &builder.transform.rotation); + bones.translate(&translation); + bones.rotate_about(&tet.centroid(), &rotation); builder.transform = Isometry3::identity(); - let (bone_bodies_, extra_constraints) = bones.insert_for_xpbd(0.5, - builder.stiffness.unwrap() - * 5.0, - tet, - bodies, - colliders, - impulse_joints); + let (bone_bodies_, extra_constraints) = { + bones.insert_for_xpbd(builder.stiffness.unwrap() * 1.0, + tet, + bodies, + colliders, + impulse_joints) + }; for body in bone_bodies_.iter() .flat_map(|(_, (a, b, c))| [a, b, c]) @@ -425,7 +411,10 @@ impl Body { Draggable)); } - let xpbd_handle = core::mem::take(builder).mesh(tet.clone()) + let xpbd_handle = core::mem::take(builder).distance_constraint_damping(0.10) + .tetrahedra_volume_constraint_damping(0.10) + .linear_damping(0.10) + .mesh(tri.clone(), tet.clone()) .extra_constraints(extra_constraints) .build(xpbd, bodies, colliders) .unwrap(); @@ -434,7 +423,7 @@ impl Body { t.rotation = Quat::IDENTITY; t.translation = Vec3::splat(0.0); - let verts = physics::XPBDVertexIndex::new(bodies, xpbd, xpbd_handle, tri); + let verts = physics::XPBDVertexIndex::new(xpbd, xpbd_handle, tri); commands.entity(id) .insert((physics::XPBD(xpbd_handle), verts)) .remove::(); @@ -461,7 +450,8 @@ impl Body { } } - fn sync_rigid(phy: Query<&PhysicsState>, mut q: Query<(&physics::RigidBody, &mut Transform), With>) { + fn sync_rigid(phy: Query<&PhysicsState>, + mut q: Query<(&physics::RigidBody, &mut Transform), With>) { let phy = phy.single(); for (body, mut transform) in &mut q { let body = phy.bodies.get(body.0).unwrap(); @@ -475,9 +465,11 @@ impl Body { fn sync_xpbd(mut meshes: ResMut>, phy: Query<&PhysicsState>, - mut q: Query<(&physics::XPBDVertexIndex, &Mesh3d)>) { + mut q: Query<(&physics::XPBDVertexIndex, &Mesh3d, &mut Transform)>) { let phy = phy.single(); - for (bodies, mesh) in &mut q { + for (bodies, mesh, mut t) in &mut q { + *t = Transform::IDENTITY; + let mesh = meshes.get_mut(&mesh.0).unwrap(); let vertices = match mesh.attribute_mut(BevyMesh::ATTRIBUTE_POSITION).unwrap() { diff --git a/src/debug_render.rs b/src/debug_render.rs index 4cae53f1b..2157d941c 100644 --- a/src/debug_render.rs +++ b/src/debug_render.rs @@ -1,9 +1,11 @@ use bevy::app::{App, Plugin, Update}; +use bevy::color::palettes::css::WHITE; use bevy::color::Color; -use bevy::prelude::{Gizmos, Query, ResMut, Resource}; +use bevy::prelude::{Gizmos, Query, ResMut, Resource, Transform}; use nalgebra::{SimdValue, Vector3}; use rapier3d::math::Point; -use rapier3d::prelude::{DebugRenderBackend, +use rapier3d::prelude::{Aabb, + DebugRenderBackend, DebugRenderMode, DebugRenderObject, DebugRenderPipeline, @@ -70,7 +72,7 @@ fn debug_render_scene(mut debug_render: ResMut, &phy.multibody_joints, &phy.narrow_phase); - render_xpbd_bodies(&mut backend, &phy.bodies, &phy.xpbd); + // render_xpbd_bodies(&mut backend, &phy.bodies, &phy.xpbd); } } @@ -80,9 +82,11 @@ pub fn render_xpbd_bodies(backend: &mut BevyLinesRenderBackend, struct Colors { vol: [f32; 4], dst: [f32; 4], + contact: [f32; 4], } let color = Colors { vol: [126.0, 0.9, 0.4, 0.2], - dst: [169.0, 0.9, 0.4, 0.8] }; + dst: [169.0, 0.9, 0.4, 0.8], + contact: [0.0, 1.0, 1.0, 1.0] }; macro_rules! ixs2positions { ($body:expr, $a:expr) => { @@ -101,6 +105,16 @@ pub fn render_xpbd_bodies(backend: &mut BevyLinesRenderBackend, } for (_, body) in xpbd.iter() { + let aabb = body.aabb(); + let translation = aabb.center(); + let hext = aabb.half_extents(); + + backend.gizmos + .cuboid(Transform { translation: <[f32; 3]>::from(translation).into(), + scale: [hext.x, hext.y, hext.z].map(|a| a * 2.0).into(), + ..Default::default() }, + WHITE); + body.constraints .iter() .flat_map(|c| match c { @@ -129,6 +143,11 @@ pub fn render_xpbd_bodies(backend: &mut BevyLinesRenderBackend, | xpbd::constraint::impl_::Constraint::TetrahedronVolume(single) => { vec![(color.vol, ixs2positions!(body, single.idxs))] }, + | xpbd::constraint::impl_::Constraint::Contact(contact) => { + let b = bodies.get(body.handles[contact.idx]).unwrap(); + let t = b.translation(); + vec![(color.contact, vec![*t, contact.contact_point])] + }, }) .for_each(|(color, positions)| { let center = positions.iter() diff --git a/src/either.rs b/src/either.rs index 03192eb5b..b6ab46fb7 100644 --- a/src/either.rs +++ b/src/either.rs @@ -1,3 +1,5 @@ +use core::fmt::Debug; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Either { Left(A), @@ -11,6 +13,26 @@ impl Either { } impl Either { + pub fn unwrap_left(self) -> A + where B: Debug + { + match self { + | Self::Left(t) => t, + | Self::Right(e) => panic!("called `Either::unwrap_left()` on a `Right` value: {:?}", + &e), + } + } + + pub fn unwrap_right(self) -> B + where A: Debug + { + match self { + | Self::Right(t) => t, + | Self::Left(e) => panic!("called `Either::unwrap_right()` on a `Left` value: {:?}", + &e), + } + } + pub fn as_ref(&self) -> Either<&A, &B> { match self { | Either::Left(a) => Either::Left(a), diff --git a/src/main.rs b/src/main.rs index 5a173c67e..df9a4a902 100644 --- a/src/main.rs +++ b/src/main.rs @@ -119,6 +119,10 @@ pub fn map_array16(a: [T; 16], f: impl Fn(T) -> R) -> [R; 16] { #[macro_export] macro_rules! map_array { + (3; $a:expr, $f:expr) => {{ + let [a0, a1, a2] = $a; + [$f(a0), $f(a1), $f(a2)] + }}; (4; $a:expr, $f:expr) => {{ let [a0, a1, a2, a3] = $a; [$f(a0), $f(a1), $f(a2), $f(a3)] diff --git a/src/mesh.rs b/src/mesh.rs index 11ae366d9..bbfac9670 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -1,5 +1,6 @@ use fixed::types::{I24F8, I32F32}; use nalgebra::{Scale3, UnitQuaternion, Vector3}; +use parry3d::query::sat::cuboid_segment_find_local_separating_edge_twoway; use rapier3d::prelude::*; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{Deserialize, Serialize}; @@ -15,6 +16,33 @@ pub struct TriMesh { } impl TriMesh { + pub fn new(faces: Vec<[u32; 3]>, vertices: Vec>) -> Self { + let faces = faces.into_iter() + .map(|ixs| ixs.map(|x| vertices[x as usize])) + .collect::>(); + let vertices = vertices.iter() + .copied() + .map(|a| a.map(I32F32::from_num)) + .collect::>() + .into_iter() + .collect::>(); + let faces = faces.into_iter() + .map(|pts| { + pts.map(|a| { + vertices.iter() + .enumerate() + .find(|(_, a_)| **a_ == a.map(I32F32::from_num)) + .unwrap() + .0 as u32 + }) + }) + .collect::>(); + Self { faces, + vertices: vertices.into_iter() + .map(|a| a.map(I32F32::to_num)) + .collect() } + } + pub fn scale(&mut self, scale: &Vector3) { let scale = |pt: &mut Vector3| { pt[0] *= scale[0]; @@ -120,19 +148,40 @@ impl Mesh { let mut vertices: FxHashSet> = Default::default(); let mut edges: FxHashSet<(Vector3, Vector3)> = Default::default(); - tets.iter().for_each(|pts| { - let [(_, a), (_, b), (_, c), (_, d)] = pts; - [a,b,c,d].into_iter().map(|a| a.map(I32F32::from_num)).for_each(|a| {vertices.insert(a);}); - [(a,b), (a,c),(a,d), (b,c), (b,d), (c,d)] - .into_iter() - .map(|(a, b)| (a.map(I32F32::from_num), b.map(I32F32::from_num))) - .map(|(a, b)| if a <= b {(a, b)} else {(b, a)}) - .for_each(|(a, b)| {edges.insert((a, b));}); - }); + let tets = tets.into_iter() + .inspect(|pts| { + let [(_, a), (_, b), (_, c), (_, d)] = pts; + [a, b, c, d].into_iter() + .map(|a| a.map(I32F32::from_num)) + .for_each(|a| { + vertices.insert(a); + }); + [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)].into_iter() + .map(|(a, b)| { + (a.map(I32F32::from_num), + b.map(I32F32::from_num)) + }) + .map(|(a, b)| { + if a <= b { + (a, b) + } else { + (b, a) + } + }) + .for_each(|(a, b)| { + edges.insert((a, b)); + }); + }) + .map(|pts| pts.map(|(b, pt)| (b, pt.map(I32F32::from_num).map(I32F32::to_num)))) + .collect(); Mesh { tets, - edges: edges.into_iter().map(|(a, b)| (a.map(I32F32::to_num), b.map(I32F32::to_num))).collect(), - vertices: vertices.into_iter().map(|a| a.map(I32F32::to_num)).collect() } + edges: edges.into_iter() + .map(|(a, b)| (a.map(I32F32::to_num), b.map(I32F32::to_num))) + .collect(), + vertices: vertices.into_iter() + .map(|a| a.map(I32F32::to_num)) + .collect() } } pub fn from_stl(stl: &[u8], @@ -480,7 +529,7 @@ impl Bones { let axis = UnitVector::new_normalize(Vector3::y_axis().cross(&d.normalize())); let rot_angle = Vector3::y_axis().dot(&d.normalize()).acos(); let rotation = UnitQuaternion::from_axis_angle(&axis, rot_angle); - let body = bodies.insert(RigidBodyBuilder::new(RigidBodyType::Dynamic).position(Isometry {translation: Translation::from(center), rotation})); + let body = bodies.insert(RigidBodyBuilder::new(RigidBodyType::Dynamic).linear_damping(0.5) .position(Isometry {translation: Translation::from(center), rotation})); colliders.insert_with_parent(ColliderBuilder::round_cylinder(dist / 2.0, 0.01, 0.01).density(10.0), body, bodies); body } @@ -488,7 +537,6 @@ impl Bones { #[allow(clippy::type_complexity)] pub fn insert_for_xpbd( &self, - thresh: f32, xpbd::constraint::Stiffness::YoungPoisson(young, poisson): xpbd::constraint::Stiffness, mesh: &Mesh, bodies: &mut RigidBodySet, @@ -502,27 +550,34 @@ impl Bones { let mut mesh_joints = Vec::<(Vector3, xpbd::constraint::DistanceBuilder)>::new(); - for a in &bones { - for (p, b) in [(a.head, a.head_body), - (a.tail, a.tail_body), - (a.center, a.link_body)] - { - mesh.vertices + let bone_bodies = bones.iter().flat_map(|b| { + [ + (b.head, b.head_body), + (b.tail, b.tail_body), + (b.center, b.link_body), + ] + }).collect::>(); + + let closest_bone_body = |p: Vector3| -> (Vector3, RigidBodyHandle) { + *bone_bodies.iter().min_by_key(|(p_, _)| { + I32F32::from_num((*p_ - p).magnitude()) + }) + .unwrap() + }; + + for v in mesh.vertices .iter() - .copied() - .filter(|p_| (p - p_).magnitude() <= thresh) - .for_each(|p_| { - let dst = (p - p_).magnitude(); - mesh_joints.push((p_, + .copied() { + let (p, body) = closest_bone_body(v); + let dst = (p - v).magnitude(); + mesh_joints.push((v, DistanceBuilder::new().priority(10) - .body_2(b) + .body_2(body) .rest_length(dst) .stiffness(young, poisson))); - }); - } - - dbg!(&mesh_joints); + } + for a in &bones { impulse_joints.insert(a.head_body, a.link_body, SphericalJointBuilder::new().local_anchor2(vector![0.0, diff --git a/src/prefab.rs b/src/prefab.rs index b6c411f81..8ca4b0d54 100644 --- a/src/prefab.rs +++ b/src/prefab.rs @@ -1,6 +1,8 @@ -use std::{collections::VecDeque, sync::LazyLock}; +use std::collections::VecDeque; +use std::sync::LazyLock; -use ::bevy::{prelude::{Cuboid, Meshable}, render::mesh::{Indices, MeshVertexAttribute, VertexAttributeValues}}; +use ::bevy::prelude::{Cuboid, Meshable}; +use ::bevy::render::mesh::{Indices, MeshVertexAttribute, VertexAttributeValues}; use bevy::prelude as bevy; use itertools::Itertools; use nalgebra::{vector, Point, Vector3}; @@ -9,19 +11,27 @@ use rustc_hash::FxHashMap; use crate::mesh::{self, Bones, Mesh}; -pub const HUMAN_MALE_BONES_JSON: &str = include_str!("../prefab/male_base-bones.json"); +pub const ANACONDA_BONES_JSON: &str = include_str!("../prefab/anaconda-bones.json"); +pub static ANACONDA_BONES: LazyLock = + LazyLock::new(|| serde_json::from_str(ANACONDA_BONES_JSON).unwrap()); + +pub const HUMAN_MALE_BONES_JSON: &str = include_str!("../prefab/human-male-bones.json"); pub static HUMAN_MALE_BONES: LazyLock = LazyLock::new(|| serde_json::from_str(HUMAN_MALE_BONES_JSON).unwrap()); -pub const HUMAN_FEMALE_BONES_JSON: &str = include_str!("../prefab/female_base-bones.json"); +pub const HUMAN_FEMALE_BONES_JSON: &str = include_str!("../prefab/human-female-bones.json"); pub static HUMAN_FEMALE_BONES: LazyLock = LazyLock::new(|| serde_json::from_str(HUMAN_FEMALE_BONES_JSON).unwrap()); -pub const HUMAN_MALE_STL: &[u8] = include_bytes!("../prefab/male_base_500f.stl"); +pub const ANACONDA_STL: &[u8] = include_bytes!("../prefab/anaconda.stl"); +pub static ANACONDA_MESH: LazyLock = + LazyLock::new(|| Mesh::from_stl(ANACONDA_STL, Some(0.001), None, None)); + +pub const HUMAN_MALE_STL: &[u8] = include_bytes!("../prefab/human-male.stl"); pub static HUMAN_MALE_MESH: LazyLock = LazyLock::new(|| Mesh::from_stl(HUMAN_MALE_STL, None, None, None)); -pub const HUMAN_FEMALE_STL: &[u8] = include_bytes!("../prefab/female_base_500f.stl"); +pub const HUMAN_FEMALE_STL: &[u8] = include_bytes!("../prefab/human-female.stl"); pub static HUMAN_FEMALE_MESH: LazyLock = LazyLock::new(|| Mesh::from_stl(HUMAN_FEMALE_STL, None, None, None)); @@ -50,6 +60,7 @@ impl Prefab { pub const fn all() -> &'static [Prefab] { &[Self::HumanMale, Self::HumanFemale, + Self::Snake, Self::Cylinder, Self::Cube] } @@ -69,6 +80,16 @@ impl Prefab { pub fn trimesh(self) -> mesh::TriMesh { let (faces, vertices) = match self { + | Prefab::Snake => { + let msh = mesh_loader::stl::from_slice(ANACONDA_STL).unwrap(); + let vertices = msh.meshes[0].vertices + .iter() + .copied() + .map(|[x, y, z]| Vector3::new(x, y, z)) + .collect::>(); + let faces = msh.meshes[0].faces.clone(); + (faces, vertices) + }, | Prefab::HumanMale => { let msh = mesh_loader::stl::from_slice(HUMAN_MALE_STL).unwrap(); let vertices = msh.meshes[0].vertices @@ -90,7 +111,7 @@ impl Prefab { (faces, points) }, | Prefab::Cylinder => { - let (points, faces) = rapier::Cylinder::new(0.5, 0.5).to_trimesh(10); + let (points, faces) = rapier::Cylinder::new(0.5, 0.5).to_trimesh(20); let points = points.into_iter().map(|a| a.coords).collect::>(); (faces, points) }, @@ -101,17 +122,16 @@ impl Prefab { let (points, faces) = subdiv(a); (faces, points) }, - | Prefab::Snake => todo!(), }; - mesh::TriMesh { faces, vertices } + mesh::TriMesh::new(faces, vertices) } pub fn mesh(self) -> (Mesh, Bones) { match self { | Prefab::HumanMale => (HUMAN_MALE_MESH.clone(), HUMAN_MALE_BONES.clone()), | Prefab::HumanFemale => (HUMAN_FEMALE_MESH.clone(), HUMAN_FEMALE_BONES.clone()), - | Prefab::Snake => todo!(), + | Prefab::Snake => (ANACONDA_MESH.clone(), ANACONDA_BONES.clone()), | Prefab::Cylinder => { let tri = self.trimesh(); let mesh = Mesh::from_trimesh(&tri.vertices, &tri.faces, None); @@ -147,11 +167,16 @@ fn bevy_mesh_tris(mesh: &bevy::Mesh) -> (Vec>, Vec<[u32; 3]>) { unreachable!(); }; - let VertexAttributeValues::Float32x3(points) = mesh.attribute(bevy::Mesh::ATTRIBUTE_POSITION).unwrap() else { + let VertexAttributeValues::Float32x3(points) = + mesh.attribute(bevy::Mesh::ATTRIBUTE_POSITION).unwrap() + else { unreachable!(); }; - let points = points.clone().into_iter().map(Vector3::from).collect::>(); + let points = points.clone() + .into_iter() + .map(Vector3::from) + .collect::>(); let mut faces_iter = faces.clone().into_iter(); let mut faces_chunked = vec![]; while let Some(a) = faces_iter.next_array::<3>() { @@ -161,25 +186,24 @@ fn bevy_mesh_tris(mesh: &bevy::Mesh) -> (Vec>, Vec<[u32; 3]>) { (points, faces_chunked) } -fn subdiv((points, faces): (Vec>, Vec<[u32; 3]>)) -> (Vec>, Vec<[u32; 3]>) { +fn subdiv((points, faces): (Vec>, Vec<[u32; 3]>)) + -> (Vec>, Vec<[u32; 3]>) { let mut new_points = points; let mut new_faces = Vec::new(); for ixs in faces { - let [ax,bx,cx] = ixs; - let [a,b,c] = ixs.map(|x| new_points[x as usize]); - let ab = (a + b) / 2.0; - let bc = (b + c) / 2.0; - let ca = (a + c) / 2.0; - let len = new_points.len(); - let [abx,bcx,cax] = [len, len + 1, len + 2].map(|n| n as u32); - new_points.extend([ab,bc,ca]); - new_faces.extend([ - [ax,abx,cax], - [abx,bx,bcx], - [bcx,cx,cax], - [cax,abx,bcx], - ].into_iter()); - } + let [ax, bx, cx] = ixs; + let [a, b, c] = ixs.map(|x| new_points[x as usize]); + let ab = (a + b) / 2.0; + let bc = (b + c) / 2.0; + let ca = (a + c) / 2.0; + let len = new_points.len(); + let [abx, bcx, acx] = [len, len + 1, len + 2].map(|n| n as u32); + new_points.extend([ab, bc, ca]); + new_faces.extend([[ax, abx, acx], + [bx, abx, bcx], + [cx, acx, bcx], + [abx, bcx, acx]].into_iter()); + } (new_points, new_faces) } diff --git a/src/xpbd/builder.rs b/src/xpbd/builder.rs index e9fb597de..d2f09848a 100644 --- a/src/xpbd/builder.rs +++ b/src/xpbd/builder.rs @@ -5,13 +5,23 @@ use fixed::types::I32F32; use graph::ConstraintId; use itertools::Itertools; use nalgebra::Isometry3; +use rapier3d::math::Translation; use rapier3d::na::Vector3; -use rapier3d::prelude::{RigidBodyHandle, *}; +use rapier3d::prelude::{ActiveHooks, + ColliderBuilder, + ColliderHandle, + ColliderSet, + RigidBodyBuilder, + RigidBodyHandle, + RigidBodySet, + RigidBodyType}; use rustc_hash::{FxHashMap, FxHashSet}; use super::constraint::{graph, Constraint, Stiffness}; use super::{constraint, step, Body, Handle, Set}; -use crate::mesh::Mesh; +use crate::either::Either; +use crate::mesh::{Mesh, TriMesh}; +use crate::xpbd::step::Step; fn vec_fixed(a: Vector3) -> Vector3 { a.map(I32F32::from_num) @@ -43,11 +53,13 @@ struct Inserted { pub(self) extra_bodies: FxHashMap>, pub(self) volume_constraints: Vec, pub(self) distance_constraints: Vec, + pub(self) faces: Vec<[RigidBodyHandle; 3]>, } impl Inserted { #[allow(clippy::too_many_arguments)] pub(self) fn insert(mesh: &Mesh, + trimesh: &TriMesh, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, extra_dst: Vec<(Vector3, @@ -56,9 +68,12 @@ impl Inserted { particle_mass: f32, collider_radius: f32, collider_skin: Option, + linear_damping: f32, tet_damping: f32, dst_damping: f32) -> Self { + let TriMesh { faces, vertices } = trimesh; + let mut me = Self::default(); let mut extra_dst = extra_dst.into_iter().map(Some).collect(); me.spawn_points(mesh, @@ -66,12 +81,34 @@ impl Inserted { colliders, particle_mass, collider_radius, - collider_skin, &mut extra_dst); + collider_skin, + linear_damping, + &mut extra_dst); + + me.faces = faces.into_iter() + .map(|a| { + a.map(|x| { + let v = vertices[x as usize].map(I32F32::from_num); + if let Some((body, _)) = me.point2body.get(&v) { + *body + } else { + me.point2body.iter().min_by_key(|a| { + I32F32::from_num((a.0 - v).map(I32F32::to_num::).magnitude()) + }) + .unwrap() + .1 + .0 + } + }) + }) + .collect(); + me.tet_constraints(mesh, tet_damping, stiffness); - me.dst_constraints(bodies, mesh, dst_damping, stiffness); + me.dst_constraints(mesh, dst_damping, stiffness); let dropped_extra_constraints = extra_dst.iter().filter(|a| a.is_some()).count(); if dropped_extra_constraints > 0 { - eprintln!("Dropped {} extra constraints because no matching points could be found!", dropped_extra_constraints); + eprintln!("Dropped {} extra constraints because no matching points could be found!", + dropped_extra_constraints); } me } @@ -83,12 +120,14 @@ impl Inserted { particle_mass: f32, collider_radius: f32, collider_skin: Option, + linear_damping: f32, extra_constraints: &mut Vec, - constraint::DistanceBuilder)>>) { + constraint::DistanceBuilder)>>) { for point in &mesh.vertices { let body = RigidBodyBuilder::new(RigidBodyType::Dynamic).lock_rotations() + .linear_damping(linear_damping) .can_sleep(false) .translation(*point) .build(); @@ -105,26 +144,24 @@ impl Inserted { let coll = colliders.insert_with_parent(coll, body, bodies); self.point2body.insert(vec_fixed(*point), (body, coll)); - let x_dst_dist = extra_constraints.iter().filter_map(|a| a.as_ref()).map(|(p, _)| (*p - point).magnitude()).collect::>(); - dbg!(x_dst_dist); - let cs = - extra_constraints - .iter_mut().filter_map(|opt| { - if let Some((p, _)) = opt { - if (*p - point).magnitude() < 1e-2 { - return opt.take(); - } - } + let cs = extra_constraints.iter_mut() + .filter_map(|opt| { + if let Some((p, _)) = opt { + if (*p - point).magnitude() < 1e-2 { + return opt.take(); + } + } - None - }).collect::>(); + None + }) + .collect::>(); - for (_, c) in cs.into_iter() { - let colls = bodies.get(c.get_body_2()).unwrap().colliders().to_vec(); - self.extra_bodies.insert(c.get_body_2(), colls); - self.distance_constraints.push(c.body_1(body).build()); - } + for (_, c) in cs.into_iter() { + let colls = bodies.get(c.get_body_2()).unwrap().colliders().to_vec(); + self.extra_bodies.insert(c.get_body_2(), colls); + self.distance_constraints.push(c.body_1(body).build()); + } } } @@ -141,12 +178,7 @@ impl Inserted { } } - fn dst_constraints(&mut self, - bodies: &RigidBodySet, - mesh: &Mesh, - damping: f32, - stiffness: Stiffness, -) { + fn dst_constraints(&mut self, mesh: &Mesh, damping: f32, stiffness: Stiffness) { for (p1, p2) in &mesh.edges { let edge = [p1, p2]; let [b1, b2] = edge.map(|p| self.point2body.get(&vec_fixed(*p)).unwrap().0); @@ -160,7 +192,6 @@ impl Inserted { rest_length: dist }; self.distance_constraints.push(c); - } } } @@ -169,6 +200,7 @@ impl Inserted { #[derive(Clone, Debug)] pub struct Builder { pub transform: Isometry3, + pub trimesh: Option, pub mesh: Option, pub collider_radius: Option, pub collider_skin: Option, @@ -176,6 +208,7 @@ pub struct Builder { pub substeps: Option, pub stiffness: Option>, pub extra_constraints: Vec<(Vector3, constraint::DistanceBuilder)>, + pub linear_damping: Option, pub distance_constraint_damping: Option, pub tetrahedra_volume_constraint_damping: Option, } @@ -184,11 +217,13 @@ impl Default for Builder { fn default() -> Self { Self { transform: Isometry3::identity(), mesh: None, + trimesh: None, collider_radius: None, collider_skin: None, mass: None, substeps: None, stiffness: None, + linear_damping: None, distance_constraint_damping: None, tetrahedra_volume_constraint_damping: None, extra_constraints: Default::default() } @@ -229,7 +264,8 @@ impl Builder { } /// Add a tetrahedral mesh (**required**) - pub fn mesh(mut self, mesh: Mesh) -> Self { + pub fn mesh(mut self, trimesh: TriMesh, mesh: Mesh) -> Self { + self.trimesh = Some(trimesh); self.mesh = Some(mesh); self } @@ -248,6 +284,12 @@ impl Builder { self } + /// Set particle linear damping + pub fn linear_damping(mut self, d: f32) -> Self { + self.linear_damping = Some(d); + self + } + /// Set default volume constraint damping (not linear damping ex. air friction) pub fn tetrahedra_volume_constraint_damping(mut self, d: f32) -> Self { self.tetrahedra_volume_constraint_damping = Some(d); @@ -326,8 +368,6 @@ impl Builder { }); } - dbg!("post-packed constraint count", packed.iter().map(|a| a.count()).fold(0, |sum, a| a + sum)); - packed } @@ -339,17 +379,20 @@ impl Builder { -> Result { let Self { transform, mesh, + trimesh, collider_radius, collider_skin, mass, substeps, stiffness, extra_constraints, + linear_damping, tetrahedra_volume_constraint_damping, distance_constraint_damping, } = self; let substeps = substeps.unwrap_or(100); + let trimesh = trimesh.ok_or(Error::MeshRequired)?; let mut mesh = mesh.ok_or(Error::MeshRequired)?; mesh.rotate(&transform.rotation); mesh.translate(&transform.translation.vector); @@ -359,6 +402,7 @@ impl Builder { let stiffness = stiffness.ok_or(Error::StiffnessRequired)?; let Stiffness::YoungPoisson(young, poisson) = stiffness; let collider_radius = collider_radius.unwrap_or(0.02); + let linear_damping = linear_damping.unwrap_or(0.0); let tetrahedra_volume_constraint_damping = tetrahedra_volume_constraint_damping.unwrap_or(0.0); let distance_constraint_damping = distance_constraint_damping.unwrap_or(0.0); @@ -366,6 +410,7 @@ impl Builder { let dts = dt / substeps as f32; let inserted = Inserted::insert(&mesh, + &trimesh, bodies, colliders, extra_constraints, @@ -373,6 +418,7 @@ impl Builder { particle_mass, collider_radius, collider_skin, + linear_damping, tetrahedra_volume_constraint_damping, distance_constraint_damping); @@ -386,29 +432,48 @@ impl Builder { type Handle2Ix = FxHashMap; type Bodies = Vec; - let (handle2ix, bodies): (Handle2Ix, Bodies) = handles.iter() - .copied() - .enumerate() - .map(|(ix, handle)| { - let body = bodies.get(handle).unwrap(); - let body = step::Body::new(dts, body); - ((handle, ix), body) - }) - .collect(); + + let immovable = inserted.extra_bodies + .keys() + .copied() + .collect::>(); + let (handle2ix, bodies): (Handle2Ix, Bodies) = + handles.iter() + .copied() + .enumerate() + .map(|(ix, handle)| { + let body = bodies.get(handle).unwrap(); + let body = step::Body::new(dts, immovable.contains(&handle), body); + ((handle, ix), body) + }) + .collect(); let constraints = Self::build_constraints(inserted.distance_constraints, inserted.volume_constraints, &handle2ix); + let faces = inserted.faces + .iter() + .map(|faces| { + faces.map(|v| { + handles.iter() + .enumerate() + .find_map(|(ix, a)| if *a == v { Some(ix) } else { None }) + .unwrap() + }) + }) + .collect::>(); + let body = Body { dt, + trimesh: faces, dt_substep: dts, substeps, young, poisson, self_collision, handles, - immovable: inserted.extra_bodies.keys().copied().collect(), - step_buf: bodies, + immovable, + step: Step { dt: dts, bodies }, constraints }; Ok(xpbd.insert(body)) diff --git a/src/xpbd/constraint/graph.rs b/src/xpbd/constraint/graph.rs index 9a32e0483..5651f531e 100644 --- a/src/xpbd/constraint/graph.rs +++ b/src/xpbd/constraint/graph.rs @@ -80,7 +80,9 @@ impl Graph { /// divides the constraints into a vec of vecs of constraints, where the inner vecs' /// constraints are guaranteed to be the same priority and share no vertices. pub fn constraints_disj(&self, priorities: &[isize]) -> Vec> { - dbg!("pre-graph constraint count", self.constraints.len(), priorities.len()); + dbg!("pre-graph constraint count", + self.constraints.len(), + priorities.len()); let vertexmask = self.vertices.keys().map(|VertexId(n)| *n).collect::(); let vertexmask0 = vertexmask.clone().zeroed(); @@ -125,7 +127,8 @@ impl Graph { } let sets: Vec> = matchings.into_iter().map(|(_, cs, _)| cs).collect(); - dbg!("post-graph constraint count", sets.iter().fold(0, |sum, a| sum + a.len())); + dbg!("post-graph constraint count", + sets.iter().fold(0, |sum, a| sum + a.len())); sets } } diff --git a/src/xpbd/constraint/impl_.rs b/src/xpbd/constraint/impl_.rs index 7614825db..3521f3cfd 100644 --- a/src/xpbd/constraint/impl_.rs +++ b/src/xpbd/constraint/impl_.rs @@ -28,6 +28,7 @@ pub enum Constraint { Distance(distance::Single), PackedTetrahedronVolume(tetrahedron_volume::Packed), TetrahedronVolume(tetrahedron_volume::Single), + Contact(contact::Contact), } impl Constraint { @@ -96,6 +97,7 @@ impl super::Constraint for Constraint { | Self::Distance(a) => a.apply(step), | Self::PackedTetrahedronVolume(a) => a.apply(step), | Self::TetrahedronVolume(a) => a.apply(step), + | Self::Contact(a) => a.apply(step), } } @@ -105,6 +107,7 @@ impl super::Constraint for Constraint { | Self::Distance(a) => a.priority(), | Self::PackedTetrahedronVolume(a) => a.priority(), | Self::TetrahedronVolume(a) => a.priority(), + | Self::Contact(a) => a.priority(), } } @@ -114,6 +117,7 @@ impl super::Constraint for Constraint { | Self::Distance(a) => a.body_idxs(), | Self::PackedTetrahedronVolume(a) => a.body_idxs(), | Self::TetrahedronVolume(a) => a.body_idxs(), + | Self::Contact(a) => a.body_idxs(), } } @@ -123,6 +127,58 @@ impl super::Constraint for Constraint { | Self::Distance(a) => a.init(handle2ix), | Self::PackedTetrahedronVolume(a) => a.init(handle2ix), | Self::TetrahedronVolume(a) => a.init(handle2ix), + | Self::Contact(a) => a.init(handle2ix), + } + } +} + +/// XPBD <> XPBD collision +pub mod contact { + use nalgebra::{UnitVector3, Vector3}; + + use super::super::Constraint; + + /// A contact between a body and a face on another body + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] + pub struct Contact { + /// Index of particle in [`Step.bodies`] + pub idx: usize, + /// Contact normal + pub normal: UnitVector3, + /// Point nearest to this body on the surface of the other body's mesh + pub contact_point: Vector3, + /// Index of the other body being contacted in the [`crate::xpbd::Set`] + pub other: usize, + /// Index to the face this contact (may) have penetrated on the + /// other body's trimesh + pub face: usize, + } + + impl Constraint for Contact { + fn apply(&mut self, step: &mut crate::xpbd::step::Step) { + let b = &mut step.bodies[self.idx]; + let vmax = 10.0; // m/s + let signed_dist = (b.next.p - self.contact_point).dot(&self.normal.into_inner()); + if signed_dist >= 0.0 { + return; + } + + let depth = (b.next.p - self.contact_point).magnitude(); + let depth = depth.min(vmax * step.dt); + let correction = self.normal.into_inner() * depth; + b.next.p += correction; + } + + fn priority(&self) -> isize { + 100 + } + + fn body_idxs(&self) -> Vec { + vec![] + } + + fn init(&mut self, _: &rustc_hash::FxHashMap) { + () } } } @@ -274,7 +330,8 @@ pub mod distance { bodies: &[step::Body; 2], deltas: [Vector3; 2]) -> [Vector3; 2] - where Vector3: Mul> + where Vector3: Mul>, + ::SimdBool: core::fmt::Debug + Copy { let dt = T::splat(dt); let atilde = compliance / dt.simd_powi(2); @@ -484,7 +541,8 @@ pub mod tetrahedron_volume { volume: T::SimdRealField, bodies: &[step::Body; 4]) -> [Vector3; 4] - where Vector3: Mul> + where Vector3: Mul>, + ::SimdBool: Copy + core::fmt::Debug { let constraint = volume - rest_volume; diff --git a/src/xpbd/handle.rs b/src/xpbd/handle.rs index a0763cd5d..ac3a5ad10 100644 --- a/src/xpbd/handle.rs +++ b/src/xpbd/handle.rs @@ -1,3 +1,3 @@ /// Handle to an xpbd body in an xpbd body set. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct Handle(pub usize); diff --git a/src/xpbd/mod.rs b/src/xpbd/mod.rs index c919e182f..73c1128d3 100644 --- a/src/xpbd/mod.rs +++ b/src/xpbd/mod.rs @@ -1,9 +1,14 @@ use core::f32; use core::fmt::Debug; +use std::collections::BTreeMap; pub use builder::Builder; use constraint::Constraint; pub use handle::Handle; +use nalgebra::Vector3; +use rapier3d::parry::bounding_volume::Aabb; +use rapier3d::parry::partitioning::Qbvh; +use rapier3d::parry::query::details::RayCompositeShapeToiAndNormalBestFirstVisitor; use rapier3d::prelude::{RigidBodyHandle, *}; use rustc_hash::FxHashSet; pub use set::Set; @@ -23,42 +28,43 @@ pub struct Body { pub substeps: usize, pub young: f32, pub poisson: f32, + pub trimesh: Vec<[usize; 3]>, pub self_collision: FxHashSet, pub handles: Vec, pub immovable: FxHashSet, - pub step_buf: Vec, + pub step: Step, pub constraints: Vec, } impl Body { - /// Do a single simulation step - pub fn step(&mut self, bodies: &mut RigidBodySet) { - for ix in 0..self.handles.len() { - self.step_buf[ix] = step::Body::new(self.dt_substep, bodies.get(self.handles[ix]).unwrap()); + /// Do a single simulation substep + pub fn substep(&mut self) { + let Self { constraints, step, .. } = self; + + for c in constraints.iter_mut() { + c.apply(step); } - let mut step = Step { bodies: core::mem::take(&mut self.step_buf), - dt: self.dt_substep }; + step.predict_next(); - for _ in 0..self.substeps { - for c in self.constraints.iter_mut() { - c.apply(&mut step); - } + self.constraints = core::mem::take(&mut self.constraints).into_iter() + .filter(|a| { + !matches!(a, + constraint::impl_::Constraint::Contact(_)) + }) + .collect(); + } - step.bodies.iter_mut().enumerate().for_each(|(ix, b)| { - if self.immovable.contains(&self.handles[ix]) { - *b = b.immovable(); - } else { - *b = b.predict_next(false, self.dt_substep) - } - }); - } - - self.step_buf = step.bodies; - - for (ix, p) in self.step_buf.iter().enumerate() { + /// Apply simulation results to rapier environment + pub fn apply(&mut self, bodies: &mut RigidBodySet) { + for (ix, p) in self.step.bodies.iter().enumerate() { let handle = self.handles[ix]; let rb = bodies.get_mut(handle).unwrap(); + + if !rb.is_dynamic() { + return; + } + let pred = rb.predict_position_using_velocity_and_forces(self.dt); let dx = p.next.p - pred.translation.vector; if rb.is_dynamic() { @@ -72,4 +78,127 @@ impl Body { } } } + + /// Sync step bodies with rapier world positions + pub fn pre_step(&mut self, bodies: &mut RigidBodySet) { + for ix in 0..self.handles.len() { + self.step + .init_body(ix, bodies.get(self.handles[ix]).unwrap()); + } + } + + pub fn generate_parry_trimesh(&self) -> parry3d::shape::TriMesh { + let vertices = self.step + .bodies + .iter() + .map(|b| Point::from(b.next.p)) + .collect::>(); + let faces = self.trimesh + .iter() + .copied() + .map(|a| a.map(|a| a as u32)) + .collect(); + let mut trimesh = rapier3d::prelude::TriMesh::new(vertices, faces).unwrap(); + trimesh.set_flags(TriMeshFlags::ORIENTED).unwrap(); + trimesh + } + + pub fn generate_contacts(&mut self, + thresh: f32, + ix_of_other: usize, + other: &parry3d::shape::TriMesh) { + if !other.aabb(&Isometry::identity()).intersects(&self.aabb()) { + return; + }; + + let points = self.step + .bodies + .iter() + .map(|b| b.next.p) + .collect::>(); + + for (ix, pt) in points.iter().enumerate() { + let Some((proj, (face, _))) = + other.project_local_point_and_get_location_with_max_dist(&Point::from(*pt), true, thresh) + else { + continue; + }; + if !proj.is_inside { + continue; + }; + + let Some(normal) = other.feature_normal(FeatureId::Face(face)) else { + unreachable!() + }; + + self.constraints.push( + constraint::impl_::Constraint::Contact( + constraint::impl_::contact::Contact { + idx: ix, + normal, + contact_point: proj.point.coords, + other: ix_of_other, + face: face as usize, + } + ), + ); + } + } + + pub fn update_contacts(&mut self, bodies: &[Self]) { + self.constraints + .iter_mut() + .filter_map(|c| match c { + | constraint::impl_::Constraint::Contact(c) => Some(c), + | _ => None, + }) + .for_each(|contact| { + let face = bodies[contact.other].trimesh[contact.face]; + let [a, b, c] = face.map(|x| Point::from(bodies[contact.other].step.bodies[x].next.p)); + let tri = parry3d::shape::Triangle::new(a, b, c); + let proj = + tri.project_local_point(&Point::from(self.step.bodies[contact.idx].next.p), false); + contact.normal = tri.normal().unwrap(); + contact.contact_point = proj.point.coords; + }); + } + + pub fn aabb(&self) -> Aabb { + let (Some(mins), Some(maxs)) = self.step + .bodies + .iter() + .fold((None, None), |(mins, maxs), b| { + let p = b.next.p; + match (mins, maxs) { + | (None, None) => (Some(p), Some(p)), + | (Some(mut mins), Some(mut maxs)) => { + if mins.x > p.x { + mins.x = p.x; + } + if mins.y > p.y { + mins.y = p.y; + } + if mins.z > p.z { + mins.z = p.z; + } + if maxs.x < p.x { + maxs.x = p.x; + } + if maxs.y < p.y { + maxs.y = p.y; + } + if maxs.z < p.z { + maxs.z = p.z; + } + (Some(mins), Some(maxs)) + }, + | _ => unreachable!(), + } + }) + else { + unreachable!() + }; + Aabb { mins: Point::from(mins), + maxs: Point::from(maxs) } + } } diff --git a/src/xpbd/set.rs b/src/xpbd/set.rs index 26a4d8c9c..dc343e39e 100644 --- a/src/xpbd/set.rs +++ b/src/xpbd/set.rs @@ -5,6 +5,7 @@ use std::ops::Deref; use std::ptr::NonNull; use std::sync::{Arc, Mutex, MutexGuard}; +use bevy::winit::select_monitor; use rapier3d::prelude::*; use super::handle::Handle; @@ -59,8 +60,74 @@ impl Set { /// Do a single simulation step pub fn step(&self, bodies: &mut RigidBodySet) { let mut bs = self.bodies.lock().unwrap(); - for b in bs.iter_mut() { - b.step(bodies); + if bs.len() == 0 { + return; + } + + // SAFETY + // Each body needs a mutable reference to other bodies + // to do collision detection, index check prevents + // double mutable reference to the same element, and + // elements are not moved or removed within the vec + // from under either mutable reference. + let (bsa, bsb) = unsafe { + let ptr = (&mut bs) as *mut MutexGuard>; + (ptr.as_mut().unwrap(), ptr.as_mut().unwrap()) + }; + + // WANT + // interleaved substeps of all bodies + // + // eg. substeps for [a,b,c] is [10,50,250] where a:b and b:c is 1:5 + // it should look like: + // + // a: . . + // b: . . . . . . + // c: . . . . . . . . . . . . . . . . . . . . . + // + // [7, 70, 200] - substeps (frequency) + // 1400 - least common multiple + // [200, 20, 7] - substep delays + // then for n in 0..1400, for m in delays, if n % m == 0 do substep + let substeps = bsa.iter().map(|b| b.substeps).collect::>(); + let substep_lcm = substeps.iter() + .skip(1) + .fold(substeps[0], |b, a| (a * b) / gcd(*a, b)); + let substep_interval = substeps.iter().map(|a| substep_lcm / a).collect::>(); + + for b in bsa.iter_mut() { + b.pre_step(bodies); + } + + // At the top of the step, regenerate trimeshes and perform collision detection + // within a threshold. For each potential collision, make contact constraints. + let trimesh = bs.iter() + .map(|b| b.generate_parry_trimesh()) + .collect::>(); + let contact_constraint_thresh = 0.1; + for (ax, ab) in bsa.iter_mut().enumerate() { + for (bx, mesh) in trimesh.iter().enumerate() { + if ax == bx { + continue; + } + ab.generate_contacts(contact_constraint_thresh, bx, mesh); + } + } + + for n in 0..substep_lcm { + for (ax, ab) in bsa.iter_mut().enumerate() { + // update contact constraints with update face + // positions + ab.update_contacts(&bsb); + + if n == 0 || n % substep_interval[ax] == 0 { + ab.substep(); + } + } + } + + for b in bsa.iter_mut() { + b.apply(bodies); } } @@ -135,3 +202,28 @@ impl<'a> Iterator for Iter<'a> { iter.next().map(|(ix, a)| (Handle(ix), a)) } } + +// https://docs.rs/num-integer/0.1.46/src/num_integer/lib.rs.html#870 +fn gcd(mut m: usize, mut n: usize) -> usize { + if m == 0 || n == 0 { + return m | n; + } + + // find common factors of 2 + let shift = (m | n).trailing_zeros(); + + // divide n and m by 2 until odd + m >>= m.trailing_zeros(); + n >>= n.trailing_zeros(); + + while m != n { + if m > n { + m -= n; + m >>= m.trailing_zeros(); + } else { + n -= m; + n >>= n.trailing_zeros(); + } + } + m << shift +} diff --git a/src/xpbd/step.rs b/src/xpbd/step.rs index 212d1d0a6..7c11d2f5d 100644 --- a/src/xpbd/step.rs +++ b/src/xpbd/step.rs @@ -1,21 +1,87 @@ use core::fmt::Debug; -use nalgebra::{Scalar, SimdComplexField, SimdValue}; +use nalgebra::{ArrayStorage, Scalar, SimdComplexField, SimdValue, Vector3}; use rapier3d::prelude::RigidBody; -use simba::simd::WideF32x8; +use simba::simd::{WideBoolF32x8, WideF32x8}; use crate::rigidbody_hack; -#[derive(Debug)] +pub const fn wide_bool(b: [bool; 8]) -> WideBoolF32x8 { + pub const fn b2f(b: bool) -> f32 { + if b { + 1.0 + } else { + 0.0 + } + } + + WideBoolF32x8(wide::f32x8::new(crate::map_array!(8; b, b2f))) +} + +pub const fn pack_vector(f: [Vector3; 8]) -> Vector3 { + pub const fn vector_x(f: Vector3) -> f32 { + f.data.0[0][0] + } + + pub const fn vector_y(f: Vector3) -> f32 { + f.data.0[0][1] + } + + pub const fn vector_z(f: Vector3) -> f32 { + f.data.0[0][2] + } + + let x = crate::map_array!(8; f, vector_x); + let y = crate::map_array!(8; f, vector_y); + let z = crate::map_array!(8; f, vector_z); + Vector3::from_array_storage(ArrayStorage([[WideF32x8(wide::f32x8::new(x)), + WideF32x8(wide::f32x8::new(y)), + WideF32x8(wide::f32x8::new(z))]])) +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Step { pub dt: f32, pub bodies: Vec, } +impl Step { + pub fn init_body(&mut self, ix: usize, body: &RigidBody) { + let p = *body.translation(); + let v = *body.linvel(); + + let b = &mut self.bodies[ix]; + let f = unsafe { + let body = core::mem::transmute::<&RigidBody, &rigidbody_hack::RigidBody>(body); + body.forces.force + }; + let init = body::State { p, v, f }; + + b.init = init; + b.prev = init; + b.next = init; + *b = b.predict_next(true, self.dt) + } + + pub fn predict_next(&mut self) { + let Self { bodies, .. } = self; + bodies.iter_mut().for_each(|b| { + if b.immovable { + *b = b.immovable(); + } else { + *b = b.predict_next(false, self.dt); + } + }); + } +} + #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] -pub struct Body { - #[serde(bound = "T: Copy + Scalar")] +pub struct Body + where T::SimdBool: Copy + Debug +{ + #[serde(bound = "T: Copy + Scalar, ::SimdBool: serde::Serialize + for<'a> serde::Deserialize<'a>")] pub mass: T, + pub immovable: ::SimdBool, pub inv_mass: T, pub linear_damping: T, pub init: body::State, @@ -26,17 +92,20 @@ pub struct Body { impl Body { pub const ZERO_F32: Body = Body { mass: 0.0, inv_mass: 0.0, + immovable: false, linear_damping: 0.0, init: body::State::ZERO_F32, prev: body::State::ZERO_F32, next: body::State::ZERO_F32 }; - pub const ZERO_F32X8: Body = Body { mass: WideF32x8::ZERO, - inv_mass: WideF32x8::ZERO, - linear_damping: WideF32x8::ZERO, - init: body::State::ZERO_F32X8, - prev: body::State::ZERO_F32X8, - next: body::State::ZERO_F32X8 }; + pub const ZERO_F32X8: Body = + Body { mass: WideF32x8::ZERO, + immovable: WideBoolF32x8(wide::f32x8::new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])), + inv_mass: WideF32x8::ZERO, + linear_damping: WideF32x8::ZERO, + init: body::State::ZERO_F32X8, + prev: body::State::ZERO_F32X8, + next: body::State::ZERO_F32X8 }; } impl Body { @@ -52,7 +121,7 @@ impl Body { self.next } - pub fn new(dt_substep: f32, body: &RigidBody) -> Self { + pub fn new(dt_substep: f32, immovable: bool, body: &RigidBody) -> Self { let p = *body.translation(); let v = *body.linvel(); let f = unsafe { @@ -65,6 +134,7 @@ impl Body { let init = body::State { p, v, f }; Self { mass, + immovable, inv_mass, init, linear_damping: body.linear_damping(), @@ -72,10 +142,11 @@ impl Body { next: init }.predict_next(true, dt_substep) } - pub fn pack(ps: &[&Self; 8]) -> Body { - let inv_mass: WideF32x8 = WideF32x8::from(crate::map_array!(8; ps, _.inv_mass)); + pub const fn pack(ps: &[&Self; 8]) -> Body { + let inv_mass: WideF32x8 = WideF32x8(wide::f32x8::new(crate::map_array!(8; ps, _.inv_mass))); Body { mass: WideF32x8::ZERO, + immovable: wide_bool(crate::map_array!(8; ps, _.immovable)), inv_mass, linear_damping: WideF32x8::ZERO, init: body::State::ZERO_F32X8, @@ -84,7 +155,9 @@ impl Body { } } -impl> Body { +impl> Body + where ::SimdBool: Copy + core::fmt::Debug +{ pub fn immovable(mut self) -> Self { self.prev = self.init; self.next = self.init; @@ -123,10 +196,11 @@ impl> Body { pub mod body { use nalgebra::{ArrayStorage, Scalar}; - use num_traits::Zero; use rapier3d::na::Vector3; use simba::simd::WideF32x8; + use super::pack_vector; + #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] pub struct State { /// Position @@ -161,12 +235,11 @@ pub mod body { self.p } - pub fn pack(ss: [Self; 8]) -> State { - let p: Vector3 = crate::map_array!(8; ss, _.p).into(); + pub const fn pack(ss: [Self; 8]) -> State { + let p = pack_vector(crate::map_array!(8; ss, _.p)); State { p, - v: Vector3::zero(), - f: Vector3::zero() } + ..Self::ZERO_F32X8 } } } }