@series-inc/rundot-syncplay 5.26.0 → 5.28.0-beta.0

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.
@@ -1890,6 +1890,7 @@ struct QueryHit {
1890
1890
  double distance = 0.0;
1891
1891
  Vec3 point{};
1892
1892
  Vec3 normal{};
1893
+ std::int32_t childIndex = -1;
1893
1894
  };
1894
1895
  QueryHit gQueryHits[kQueryCapacity];
1895
1896
  double gQueryResultData[kQueryCapacity * kQueryResultStride];
@@ -3764,6 +3765,7 @@ struct RayHit {
3764
3765
  double distance = 0.0;
3765
3766
  Vec3 point{};
3766
3767
  Vec3 normal{};
3768
+ std::int32_t childIndex = -1;
3767
3769
  };
3768
3770
 
3769
3771
  RayHit rayAabb(
@@ -3859,6 +3861,125 @@ RayHit raySphere(const Body& body, const Vec3& origin, const Vec3& direction, do
3859
3861
  };
3860
3862
  }
3861
3863
 
3864
+ RayHit rayCapsule(const Body& body, const Vec3& origin, const Vec3& direction, double maxDistance) {
3865
+ const Vec3 center{body.x + body.shapeOffsetX, body.y + body.shapeOffsetY, body.z + body.shapeOffsetZ};
3866
+ const Vec3 localOrigin = inverseRotateByOrientation(body, subtractVec3(origin, center));
3867
+ const Vec3 localDirection = inverseRotateByOrientation(body, direction);
3868
+ const double radius = body.shape0;
3869
+ const double halfHeight = body.shape1;
3870
+
3871
+ const double clampedY = std::max(-halfHeight, std::min(halfHeight, localOrigin.y));
3872
+ const double deltaY = localOrigin.y - clampedY;
3873
+ const double distSq = localOrigin.x * localOrigin.x + deltaY * deltaY + localOrigin.z * localOrigin.z;
3874
+ if (distSq <= radius * radius) {
3875
+ return {
3876
+ true,
3877
+ 0.0,
3878
+ origin,
3879
+ scaleVec3(direction, -1.0),
3880
+ -1,
3881
+ };
3882
+ }
3883
+
3884
+ double bestDistance = maxDistance + 1.0;
3885
+ Vec3 bestLocalNormal{};
3886
+ bool hitFound = false;
3887
+
3888
+ // 1. Test vertical cylinder along Y: x^2 + z^2 = radius^2, y in [-halfHeight, halfHeight]
3889
+ const double a = localDirection.x * localDirection.x + localDirection.z * localDirection.z;
3890
+ const double b = 2.0 * (localOrigin.x * localDirection.x + localOrigin.z * localDirection.z);
3891
+ const double c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius;
3892
+
3893
+ if (a > 1e-18) {
3894
+ const double discriminant = b * b - 4.0 * a * c;
3895
+ if (discriminant >= 0.0) {
3896
+ const double root = std::sqrt(discriminant);
3897
+ const double t0 = (-b - root) / (2.0 * a);
3898
+ const double t1 = (-b + root) / (2.0 * a);
3899
+ for (const double t : {t0, t1}) {
3900
+ if (t >= 0.0 && t <= maxDistance) {
3901
+ const double hitY = localOrigin.y + localDirection.y * t;
3902
+ if (hitY >= -halfHeight && hitY <= halfHeight) {
3903
+ if (t < bestDistance) {
3904
+ bestDistance = t;
3905
+ bestLocalNormal = scaleVec3({localOrigin.x + localDirection.x * t, 0.0, localOrigin.z + localDirection.z * t}, 1.0 / radius);
3906
+ hitFound = true;
3907
+ }
3908
+ }
3909
+ }
3910
+ }
3911
+ }
3912
+ }
3913
+
3914
+ // 2. Test bottom sphere: center (0, -halfHeight, 0), radius
3915
+ {
3916
+ const Vec3 capCenter{0.0, -halfHeight, 0.0};
3917
+ const Vec3 offset = subtractVec3(localOrigin, capCenter);
3918
+ const double sb = 2.0 * dotVec3(offset, localDirection);
3919
+ const double sc = dotVec3(offset, offset) - radius * radius;
3920
+ const double sdisc = sb * sb - 4.0 * sc;
3921
+ if (sdisc >= 0.0) {
3922
+ const double root = std::sqrt(sdisc);
3923
+ const double t0 = (-sb - root) / 2.0;
3924
+ const double t1 = (-sb + root) / 2.0;
3925
+ for (const double t : {t0, t1}) {
3926
+ if (t >= 0.0 && t <= maxDistance) {
3927
+ const double hitY = localOrigin.y + localDirection.y * t;
3928
+ if (hitY <= -halfHeight) {
3929
+ if (t < bestDistance) {
3930
+ bestDistance = t;
3931
+ const Vec3 hitPt = addVec3(localOrigin, scaleVec3(localDirection, t));
3932
+ const Vec3 nDelta = subtractVec3(hitPt, capCenter);
3933
+ const double nLen = solverSqrt(dotVec3(nDelta, nDelta));
3934
+ bestLocalNormal = nLen > 1e-18 ? scaleVec3(nDelta, 1.0 / nLen) : Vec3{0.0, -1.0, 0.0};
3935
+ hitFound = true;
3936
+ }
3937
+ }
3938
+ }
3939
+ }
3940
+ }
3941
+ }
3942
+
3943
+ // 3. Test top sphere: center (0, halfHeight, 0), radius
3944
+ {
3945
+ const Vec3 capCenter{0.0, halfHeight, 0.0};
3946
+ const Vec3 offset = subtractVec3(localOrigin, capCenter);
3947
+ const double sb = 2.0 * dotVec3(offset, localDirection);
3948
+ const double sc = dotVec3(offset, offset) - radius * radius;
3949
+ const double sdisc = sb * sb - 4.0 * sc;
3950
+ if (sdisc >= 0.0) {
3951
+ const double root = std::sqrt(sdisc);
3952
+ const double t0 = (-sb - root) / 2.0;
3953
+ const double t1 = (-sb + root) / 2.0;
3954
+ for (const double t : {t0, t1}) {
3955
+ if (t >= 0.0 && t <= maxDistance) {
3956
+ const double hitY = localOrigin.y + localDirection.y * t;
3957
+ if (hitY >= halfHeight) {
3958
+ if (t < bestDistance) {
3959
+ bestDistance = t;
3960
+ const Vec3 hitPt = addVec3(localOrigin, scaleVec3(localDirection, t));
3961
+ const Vec3 nDelta = subtractVec3(hitPt, capCenter);
3962
+ const double nLen = solverSqrt(dotVec3(nDelta, nDelta));
3963
+ bestLocalNormal = nLen > 1e-18 ? scaleVec3(nDelta, 1.0 / nLen) : Vec3{0.0, 1.0, 0.0};
3964
+ hitFound = true;
3965
+ }
3966
+ }
3967
+ }
3968
+ }
3969
+ }
3970
+ }
3971
+
3972
+ if (!hitFound) return {};
3973
+
3974
+ return {
3975
+ true,
3976
+ bestDistance,
3977
+ addVec3(origin, scaleVec3(direction, bestDistance)),
3978
+ rotateByOrientation(body, bestLocalNormal),
3979
+ -1,
3980
+ };
3981
+ }
3982
+
3862
3983
  RayHit rayTriangle(
3863
3984
  const Vec3& origin,
3864
3985
  const Vec3& direction,
@@ -3990,6 +4111,7 @@ BroadphaseBounds queryProxyBounds(const Body& body);
3990
4111
  RayHit rayBody(const Body& body, const Vec3& origin, const Vec3& direction, double maxDistance) {
3991
4112
  if (body.shape == ShapeKind::Sphere) return raySphere(body, origin, direction, maxDistance);
3992
4113
  if (body.shape == ShapeKind::Box) return rayBox(body, origin, direction, maxDistance);
4114
+ if (body.shape == ShapeKind::Capsule) return rayCapsule(body, origin, direction, maxDistance);
3993
4115
  if (body.shape == ShapeKind::Mesh || body.shape == ShapeKind::Heightfield) {
3994
4116
  if (body.shape == ShapeKind::Mesh && body.geometryCount % 3 != 0) {
3995
4117
  return rayAabb(queryProxyBounds(body), origin, direction, maxDistance);
@@ -4000,8 +4122,11 @@ RayHit rayBody(const Body& body, const Vec3& origin, const Vec3& direction, doub
4000
4122
  && (body.geometryFormat == 4 || body.geometryFormat == 5)) {
4001
4123
  RayHit best;
4002
4124
  for (std::uint32_t child = 0; child < compoundChildCount(body); child += 1) {
4003
- const RayHit candidate = rayBody(compoundChildBody(body, child), origin, direction, maxDistance);
4004
- if (candidate.hit && (!best.hit || candidate.distance < best.distance)) best = candidate;
4125
+ RayHit candidate = rayBody(compoundChildBody(body, child), origin, direction, maxDistance);
4126
+ if (candidate.hit && (!best.hit || candidate.distance < best.distance)) {
4127
+ candidate.childIndex = static_cast<std::int32_t>(child);
4128
+ best = candidate;
4129
+ }
4005
4130
  }
4006
4131
  if (best.hit) return best;
4007
4132
  }
@@ -4103,7 +4228,7 @@ bool validQueryInput(const double* queryData, std::uint32_t queryValues) {
4103
4228
 
4104
4229
  void appendQueryHit(std::uint32_t bodyIndex, const RayHit& hit) {
4105
4230
  if (!hit.hit || gQueryResultCount >= kQueryCapacity) return;
4106
- gQueryHits[gQueryResultCount++] = {bodyIndex, hit.distance, hit.point, hit.normal};
4231
+ gQueryHits[gQueryResultCount++] = {bodyIndex, hit.distance, hit.point, hit.normal, hit.childIndex};
4107
4232
  }
4108
4233
 
4109
4234
  void finalizeQueryResults(bool quantizeQueryDistance) {
@@ -4124,6 +4249,7 @@ void finalizeQueryResults(bool quantizeQueryDistance) {
4124
4249
  gQueryResultData[offset + 5] = hit.normal.x == 0.0 ? 0.0 : hit.normal.x;
4125
4250
  gQueryResultData[offset + 6] = hit.normal.y == 0.0 ? 0.0 : hit.normal.y;
4126
4251
  gQueryResultData[offset + 7] = hit.normal.z == 0.0 ? 0.0 : hit.normal.z;
4252
+ gQueryResultData[offset + 8] = static_cast<double>(hit.childIndex);
4127
4253
  }
4128
4254
  }
4129
4255
 
@@ -14,7 +14,7 @@ constexpr std::uint32_t kContactTelemetryStride = 9;
14
14
  constexpr std::uint32_t kJointTelemetryStride = 5;
15
15
  constexpr std::uint32_t kForceStride = 6;
16
16
  constexpr std::uint32_t kQueryInputStride = 21;
17
- constexpr std::uint32_t kQueryResultStride = 8;
17
+ constexpr std::uint32_t kQueryResultStride = 9;
18
18
  constexpr std::uint32_t kMotionKindMask = 0b11;
19
19
  constexpr std::uint32_t kMotionTrigger = 0b100;
20
20
  constexpr std::uint32_t kMotionSensor = 0b1000;
package/core/physics.d.ts CHANGED
@@ -387,6 +387,7 @@ export function raycastPhysicsDetailed(world: KinetixPhysicsWorld, query: Kineti
387
387
  readonly point: KinetixPhysicsVec3;
388
388
  readonly normal: KinetixPhysicsVec3;
389
389
  readonly penetration: number;
390
+ readonly childIndex?: number;
390
391
  })[];
391
392
  export function overlapPhysics(world: KinetixPhysicsWorld, query: KinetixPhysicsOverlapQuery): readonly string[];
392
393
  export function shapeCastPhysics(world: KinetixPhysicsWorld, query: KinetixPhysicsShapeCastQuery): readonly KinetixPhysicsHit[];
@@ -124,7 +124,7 @@ export function buildWasmPhysics({ check = false } = {}) {
124
124
  contactTelemetryStride: 9,
125
125
  jointTelemetryStride: 5,
126
126
  queryInputStride: 21,
127
- queryResultStride: 8,
127
+ queryResultStride: 9,
128
128
  compiler: compiler.command,
129
129
  emccVersion: compiler.version,
130
130
  compilerVersion: compiler.version,
@@ -62,7 +62,7 @@ const PHYSICS_BODY_STRIDE = 44;
62
62
  const PHYSICS_JOINT_STRIDE = 48;
63
63
  const PHYSICS_JOINT_TELEMETRY_STRIDE = 5;
64
64
  const PHYSICS_QUERY_INPUT_STRIDE = 21;
65
- const PHYSICS_QUERY_RESULT_STRIDE = 8;
65
+ const PHYSICS_QUERY_RESULT_STRIDE = 9;
66
66
  const PHYSICS_ARENA_ALIGNMENT = 16;
67
67
  const PHYSICS_ARENA_GUARD_BYTES = 64;
68
68
  const PHYSICS_MIN_PROXY_HALF_EXTENT = 1e-9;
@@ -1848,7 +1848,8 @@ function nativeQuery(world, query, kind) {
1848
1848
  const offset = index * PHYSICS_QUERY_RESULT_STRIDE;
1849
1849
  const body = bodies[values[offset]];
1850
1850
  if (body === undefined) fail('KINETIX_PHYSICS_QUERY_BODY_MISSING');
1851
- return {
1851
+ const childIndexRaw = values[offset + 8];
1852
+ const hitObj = {
1852
1853
  bodyId: body.id,
1853
1854
  distance: kind === 'shapeCast' ? quantizeDistance(values[offset + 1]) : values[offset + 1],
1854
1855
  point: {
@@ -1862,6 +1863,10 @@ function nativeQuery(world, query, kind) {
1862
1863
  z: values[offset + 7],
1863
1864
  },
1864
1865
  };
1866
+ if (Number.isFinite(childIndexRaw) && childIndexRaw >= 0) {
1867
+ hitObj.childIndex = childIndexRaw;
1868
+ }
1869
+ return hitObj;
1865
1870
  });
1866
1871
  }
1867
1872
 
@@ -2628,13 +2633,19 @@ export function raycastPhysics(world, query) {
2628
2633
  export function raycastPhysicsDetailed(world, query) {
2629
2634
  const normalized = normalizedRayQuery(query);
2630
2635
  if (physicsInstance !== null) {
2631
- return deepFreeze(nativeQuery(world, normalized, 'raycast').map((hit) => ({
2632
- bodyId: hit.bodyId,
2633
- distance: hit.distance,
2634
- point: hit.point,
2635
- normal: hit.normal,
2636
- penetration: 0,
2637
- })));
2636
+ return deepFreeze(nativeQuery(world, normalized, 'raycast').map((hit) => {
2637
+ const detailed = {
2638
+ bodyId: hit.bodyId,
2639
+ distance: hit.distance,
2640
+ point: hit.point,
2641
+ normal: hit.normal,
2642
+ penetration: 0,
2643
+ };
2644
+ if (hit.childIndex !== undefined) {
2645
+ detailed.childIndex = hit.childIndex;
2646
+ }
2647
+ return detailed;
2648
+ }));
2638
2649
  }
2639
2650
  return deepFreeze(raycastPhysics(world, normalized).map((hit) => {
2640
2651
  const body = world.bodies.find((candidate) => candidate.id === hit.bodyId);