TODO: Load anim frame AABBs as DX BoundingBox objects and do regular ray test for gain of 3-5 frames. -- Sezz 2024.11.07 TODO: Try querying collision mesh instead. Get precise floor/ceiling height from object's bounding box. Animated objects are also supported, although horizontal collision shifting is unstable. Method: get accurate bounds in world transform by converting to OBB, then do a ray tes
| 761 | // Method: get accurate bounds in world transform by converting to OBB, then do a ray test |
| 762 | // on top or bottom (depending on test side) to determine if box is present at a particular point. |
| 763 | std::optional<int> GetBridgeItemIntersect(const ItemInfo& item, const Vector3i& pos, bool useBottomHeight) |
| 764 | { |
| 765 | constexpr auto VERTICAL_MARGIN = 4; |
| 766 | |
| 767 | // Check bridge cache for existing entry. |
| 768 | for (const auto& entry : BridgeCache) |
| 769 | { |
| 770 | if (entry.BridgeItemNumber == item.Index && entry.BridgePose == item.Pose && |
| 771 | entry.UseBottomHeight == useBottomHeight && entry.Position == pos) |
| 772 | { |
| 773 | return entry.Height; |
| 774 | } |
| 775 | } |
| 776 | |
| 777 | auto box = GameBoundingBox(&item); |
| 778 | auto extents = box.GetExtents(); |
| 779 | |
| 780 | // Test rough circle intersection to discard bridges not intersecting on XZ plane. |
| 781 | auto circle1 = Vector3(pos.x, pos.z, BLOCK(1)); |
| 782 | auto circle2 = Vector3(item.Pose.Position.x, item.Pose.Position.z, std::hypot(extents.x, extents.z)); |
| 783 | |
| 784 | auto height = std::optional<int>(); |
| 785 | if (Geometry::CircleIntersects(circle1, circle2)) |
| 786 | { |
| 787 | auto origin = Vector3i(pos.x, pos.y + (useBottomHeight ? VERTICAL_MARGIN : -VERTICAL_MARGIN), pos.z) - item.Pose.Position; |
| 788 | |
| 789 | float sinAngle = phd_sin(item.Pose.Orientation.y); |
| 790 | float cosAngle = phd_cos(item.Pose.Orientation.y); |
| 791 | |
| 792 | auto localOrigin = Vector3i( |
| 793 | (origin.x * cosAngle) - (origin.z * sinAngle), |
| 794 | origin.y, |
| 795 | (origin.x * sinAngle) + (origin.z * cosAngle)); |
| 796 | |
| 797 | // Calculate intersection distance. |
| 798 | auto direction = useBottomHeight ? -Vector3::UnitY : Vector3::UnitY; |
| 799 | float targetY = useBottomHeight ? box.Y2 : box.Y1; |
| 800 | float dist = (targetY - localOrigin.y) / direction.y; |
| 801 | |
| 802 | // Compute intersection point. |
| 803 | auto intersectionPoint = Geometry::TranslatePoint(localOrigin, direction, dist); |
| 804 | |
| 805 | // Check if intersection point is within bounding box's X and Z extents. |
| 806 | if (intersectionPoint.x >= box.X1 && intersectionPoint.x <= box.X2 && |
| 807 | intersectionPoint.z >= box.Z1 && intersectionPoint.z <= box.Z2) |
| 808 | { |
| 809 | // Transform intersection point back to world coordinates. |
| 810 | height = item.Pose.Position.y + intersectionPoint.y; |
| 811 | } |
| 812 | } |
| 813 | |
| 814 | // Cache bridge data. |
| 815 | BridgeCache[BridgeCacheSlotID] = BridgeCacheEntry{ item.Index, item.Pose, useBottomHeight, pos, height }; |
| 816 | BridgeCacheSlotID = (BridgeCacheSlotID + 1) % BRIDGE_CACHE_SIZE; // Wrap to next slot ID. |
| 817 | |
| 818 | return height; |
| 819 | } |
| 820 |
no test coverage detected