unit OBB;
(*
Translate from c to pascal by Gunko Vadim
Original c code by Aiman Azees
https://github.com/AimanGameDev/Raylib-OBB-Oriented-Bounding-Box
*)
interface
uses
raylib, raymath, math;
const Infinity = 1.0 / 0.0;
type
POBB = ^TOBB;
TOBB = record
rotation: TQuaternion;
center: TVector3;
halfExtents: TVector3;
end;
procedure OBB_GetAxes(obb: POBB; out right, up, forward: TVector3);
procedure OBB_GetCorners(obb: POBB; out corners: array of TVector3);
procedure OBB_DrawWireframe(obb: POBB; color: TColor);
function OBB_ContainsPoint(obb: POBB; point: TVector3): Boolean;
procedure ProjectBoundingBoxOntoAxis(box: PBoundingBox; axis: TVector3; out outMin, outMax: Single);
procedure ProjectOBBOntoAxis(obb: POBB; axis: TVector3; out outMin, outMax: Single);
function CheckCollisionBoundingBoxVsOBB(box: PBoundingBox; obb: POBB): Boolean;
function CheckCollisionOBBvsOBB(a, b: POBB): Boolean;
function GetRayCollisionOBB(ray: TRay; obb: POBB): TRayCollision;
function CheckCollisionSphereVsOBB(sphereCenter: TVector3; radius: Single; obb: POBB): Boolean;
implementation
procedure OBB_GetAxes(obb: POBB; out right, up, forward: TVector3);
var
rot: TMatrix;
begin
rot := QuaternionToMatrix(obb^.rotation);
right := Vector3Create(rot.m0, rot.m1, rot.m2);
up := Vector3Create(rot.m4, rot.m5, rot.m6);
forward := Vector3Create(rot.m8, rot.m9, rot.m10);
end;
procedure OBB_GetCorners(obb: POBB; out corners: array of TVector3);
var
right, up, forward: TVector3;
begin
OBB_GetAxes(obb, right, up, forward);
right := Vector3Scale(right, obb^.halfExtents.x);
up := Vector3Scale(up, obb^.halfExtents.y);
forward := Vector3Scale(forward, obb^.halfExtents.z);
corners[0] := Vector3Add(Vector3Add(Vector3Add(obb^.center, right), up), forward);
corners[1] := Vector3Add(Vector3Add(Vector3Subtract(obb^.center, right), up), forward);
corners[2] := Vector3Add(Vector3Add(Vector3Subtract(obb^.center, right), up), Vector3Negate(forward));
corners[3] := Vector3Add(Vector3Add(Vector3Add(obb^.center, right), up), Vector3Negate(forward));
corners[4] := Vector3Add(Vector3Add(Vector3Add(obb^.center, right), Vector3Negate(up)), forward);
corners[5] := Vector3Add(Vector3Add(Vector3Subtract(obb^.center, right), Vector3Negate(up)), forward);
corners[6] := Vector3Add(Vector3Add(Vector3Subtract(obb^.center, right), Vector3Negate(up)), Vector3Negate(forward));
corners[7] := Vector3Add(Vector3Add(Vector3Add(obb^.center, right), Vector3Negate(up)), Vector3Negate(forward));
end;
procedure OBB_DrawWireframe(obb: POBB; color: TColor);
var
c: array[0..7] of TVector3;
begin
OBB_GetCorners(obb, c);
DrawLine3D(c[0], c[1], color);
DrawLine3D(c[1], c[2], color);
DrawLine3D(c[2], c[3], color);
DrawLine3D(c[3], c[0], color);
DrawLine3D(c[4], c[5], color);
DrawLine3D(c[5], c[6], color);
DrawLine3D(c[6], c[7], color);
DrawLine3D(c[7], c[4], color);
DrawLine3D(c[0], c[4], color);
DrawLine3D(c[1], c[5], color);
DrawLine3D(c[2], c[6], color);
DrawLine3D(c[3], c[7], color);
end;
function OBB_ContainsPoint(obb: POBB; point: TVector3): Boolean;
var
local: TVector3;
inverseRot: TQuaternion;
begin
local := Vector3Subtract(point, obb^.center);
inverseRot := QuaternionInvert(obb^.rotation);
local := Vector3RotateByQuaternion(local, inverseRot);
Result := (abs(local.x) <= obb^.halfExtents.x) and
(abs(local.y) <= obb^.halfExtents.y) and
(abs(local.z) <= obb^.halfExtents.z);
end;
procedure ProjectBoundingBoxOntoAxis(box: PBoundingBox; axis: TVector3; out outMin, outMax: Single);
var
corners: array[0..7] of TVector3;
i: Integer;
projection, min, max: Single;
begin
corners[0] := Vector3Create(box^.min.x, box^.min.y, box^.min.z);
corners[1] := Vector3Create(box^.max.x, box^.min.y, box^.min.z);
corners[2] := Vector3Create(box^.max.x, box^.max.y, box^.min.z);
corners[3] := Vector3Create(box^.min.x, box^.max.y, box^.min.z);
corners[4] := Vector3Create(box^.min.x, box^.min.y, box^.max.z);
corners[5] := Vector3Create(box^.max.x, box^.min.y, box^.max.z);
corners[6] := Vector3Create(box^.max.x, box^.max.y, box^.max.z);
corners[7] := Vector3Create(box^.min.x, box^.max.y, box^.max.z);
min := Vector3DotProduct(corners[0], axis);
max := min;
for i := 1 to 7 do
begin
projection := Vector3DotProduct(corners[i], axis);
if projection < min then
min := projection;
if projection > max then
max := projection;
end;
outMin := min;
outMax := max;
end;
procedure ProjectOBBOntoAxis(obb: POBB; axis: TVector3; out outMin, outMax: Single);
var
right, up, forward: TVector3;
r, centerProj: Single;
begin
OBB_GetAxes(obb, right, up, forward);
r := abs(Vector3DotProduct(right, axis)) * obb^.halfExtents.x +
abs(Vector3DotProduct(up, axis)) * obb^.halfExtents.y +
abs(Vector3DotProduct(forward, axis)) * obb^.halfExtents.z;
centerProj := Vector3DotProduct(obb^.center, axis);
outMin := centerProj - r;
outMax := centerProj + r;
end;
function CheckCollisionBoundingBoxVsOBB(box: PBoundingBox; obb: POBB): Boolean;
var
aabbAxes: array[0..2] of TVector3;
obbAxes: array[0..2] of TVector3;
testAxes: array[0..14] of TVector3;
axisCount, i, j: Integer;
cross: TVector3;
axis: TVector3;
minA, maxA, minB, maxB: Single;
begin
aabbAxes[0] := Vector3Create(1, 0, 0);
aabbAxes[1] := Vector3Create(0, 1, 0);
aabbAxes[2] := Vector3Create(0, 0, 1);
OBB_GetAxes(obb, obbAxes[0], obbAxes[1], obbAxes[2]);
axisCount := 0;
for i := 0 to 2 do
begin
testAxes[axisCount] := aabbAxes[i];
Inc(axisCount);
end;
for i := 0 to 2 do
begin
testAxes[axisCount] := obbAxes[i];
Inc(axisCount);
end;
for i := 0 to 2 do
begin
for j := 0 to 2 do
begin
cross := Vector3CrossProduct(aabbAxes[i], obbAxes[j]);
if Vector3LengthSqr(cross) > 0.000001 then
begin
testAxes[axisCount] := Vector3Normalize(cross);
Inc(axisCount);
end;
end;
end;
for i := 0 to axisCount - 1 do
begin
axis := testAxes[i];
ProjectBoundingBoxOntoAxis(box, axis, minA, maxA);
ProjectOBBOntoAxis(obb, axis, minB, maxB);
if (maxA < minB) or (maxB < minA) then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
function CheckCollisionOBBvsOBB(a, b: POBB): Boolean;
var
axesA, axesB: array[0..2] of TVector3;
testAxes: array[0..14] of TVector3;
axisCount, i, j: Integer;
cross: TVector3;
len: Single;
axis: TVector3;
minA, maxA, minB, maxB: Single;
begin
OBB_GetAxes(a, axesA[0], axesA[1], axesA[2]);
OBB_GetAxes(b, axesB[0], axesB[1], axesB[2]);
axisCount := 0;
for i := 0 to 2 do
begin
testAxes[axisCount] := axesA[i];
Inc(axisCount);
end;
for i := 0 to 2 do
begin
testAxes[axisCount] := axesB[i];
Inc(axisCount);
end;
for i := 0 to 2 do
begin
for j := 0 to 2 do
begin
cross := Vector3CrossProduct(axesA[i], axesB[j]);
len := Vector3Length(cross);
if len > 0.0001 then
begin
testAxes[axisCount] := Vector3Scale(cross, 1.0 / len);
Inc(axisCount);
end;
end;
end;
for i := 0 to axisCount - 1 do
begin
axis := testAxes[i];
ProjectOBBOntoAxis(a, axis, minA, maxA);
ProjectOBBOntoAxis(b, axis, minB, maxB);
if (maxA < minB) or (maxB < minA) then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
function GetRayCollisionOBB(ray: TRay; obb: POBB): TRayCollision;
var
localOrigin, localRayOrigin, localRayDir: TVector3;
inverseRot: TQuaternion;
boxMin, boxMax: TVector3;
tmin, tmax: Single;
normal: TVector3;
i: Integer;
origin, dir, min, max, ood, t1, t2: Single;
axis: Integer;
temp: Single;
begin
Result.hit := False;
Result.distance := 0;
Result.normal := Vector3Zero();
Result.point := Vector3Zero();
// Move ray into OBB's local space
localOrigin := Vector3Subtract(ray.position, obb^.center);
inverseRot := QuaternionInvert(obb^.rotation);
localRayOrigin := Vector3RotateByQuaternion(localOrigin, inverseRot);
localRayDir := Vector3RotateByQuaternion(ray.direction, inverseRot);
boxMin := Vector3Negate(obb^.halfExtents);
boxMax := obb^.halfExtents;
// Ray vs AABB in OBB-local space
tmin := -Infinity;
tmax := Infinity;
normal := Vector3Zero();
for i := 0 to 2 do
begin
case i of
0: begin origin := localRayOrigin.x; dir := localRayDir.x; min := boxMin.x; max := boxMax.x; end;
1: begin origin := localRayOrigin.y; dir := localRayDir.y; min := boxMin.y; max := boxMax.y; end;
2: begin origin := localRayOrigin.z; dir := localRayDir.z; min := boxMin.z; max := boxMax.z; end;
end;
if abs(dir) < 0.0001 then
begin
if (origin < min) or (origin > max) then
Exit;
end
else
begin
ood := 1.0 / dir;
t1 := (min - origin) * ood;
t2 := (max - origin) * ood;
axis := i;
if t1 > t2 then
begin
temp := t1;
t1 := t2;
t2 := temp;
axis := -axis;
end;
if t1 > tmin then
begin
tmin := t1;
normal := Vector3Zero();
case abs(axis) of
0: normal.x := IfThen(axis >= 0, -1.0, 1.0);
1: normal.y := IfThen(axis >= 0, -1.0, 1.0);
2: normal.z := IfThen(axis >= 0, -1.0, 1.0);
end;
end;
if t2 < tmax then
begin
tmax := t2;
end;
if tmin > tmax then
Exit;
end;
end;
// Convert result to world space
Result.hit := True;
Result.distance := tmin;
Result.point := Vector3Add(ray.position, Vector3Scale(ray.direction, tmin));
Result.normal := Vector3RotateByQuaternion(normal, obb^.rotation);
end;
function CheckCollisionSphereVsOBB(sphereCenter: TVector3; radius: Single; obb: POBB): Boolean;
var
localCenter, clamped, worldClamped: TVector3;
invRot: TQuaternion;
distSq: Single;
begin
localCenter := Vector3Subtract(sphereCenter, obb^.center);
invRot := QuaternionInvert(obb^.rotation);
localCenter := Vector3RotateByQuaternion(localCenter, invRot);
clamped.x := Clamp(localCenter.x, -obb^.halfExtents.x, obb^.halfExtents.x);
clamped.y := Clamp(localCenter.y, -obb^.halfExtents.y, obb^.halfExtents.y);
clamped.z := Clamp(localCenter.z, -obb^.halfExtents.z, obb^.halfExtents.z);
worldClamped := Vector3RotateByQuaternion(clamped, obb^.rotation);
worldClamped := Vector3Add(worldClamped, obb^.center);
distSq := Vector3DistanceSqr(sphereCenter, worldClamped);
Result := distSq <= radius * radius;
end;
end.