364 lines
11 KiB
Lua
364 lines
11 KiB
Lua
-- TODO: Clean this up - this is a port of the defold geomextension addon in C++
|
|
|
|
local tinsert = table.insert
|
|
|
|
ffi.cdef[[
|
|
typedef struct Vec3 {
|
|
float x, y, z;
|
|
} Vec3;
|
|
|
|
typedef struct Matrix4 float[16];
|
|
|
|
typedef struct Ray {
|
|
Vec3 position;
|
|
Vec3 direction;
|
|
} Ray;
|
|
|
|
typedef struct AABB {
|
|
Vec3 min;
|
|
Vec3 max;
|
|
uint64_t tag;
|
|
Matrix4 mat;
|
|
} AABB;
|
|
|
|
typedef struct OBB {
|
|
Vec3 center;
|
|
Vec3 axis[3];
|
|
Vec3 extents;
|
|
uint64_t tag;
|
|
Matrix4 mat;
|
|
} OBB;
|
|
]]
|
|
|
|
local g_bounds = {}
|
|
|
|
-- // --- Scale a vector to unit length (1).
|
|
-- // -- @tparam vec3 a vector to normalize
|
|
-- // -- @treturn vec3 out
|
|
local function normalize(a)
|
|
|
|
if( a.x == 0.0f && a.y == 0.0f && a.z == 0.0f)
|
|
return a;
|
|
|
|
local vlen = math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
|
|
local invlen = 1.0 / vlen
|
|
return ffi.new("Vec3", ( a.x * invlen, a.y * invlen, a.z * invlen) )
|
|
end
|
|
|
|
local function dot(a, b)
|
|
return (a.x * b.x + a.y * b.y + a.z * b.z)
|
|
end
|
|
|
|
-- // -- http://gamedev.stackexchange.com/a/18459
|
|
-- // -- ray.position is a vec3
|
|
-- // -- ray.direction is a vec3
|
|
-- // -- aabb.min is a vec3
|
|
-- // -- aabb.max is a vec3
|
|
-- // -- distance is a pointer to a number
|
|
local function intersect( ray, aabb, distance)
|
|
|
|
local dir = normalize(ray.direction)
|
|
local dirfrac = ffi.new("Vec3", {
|
|
1.0f / dir.x,
|
|
1.0f / dir.y,
|
|
1.0f / dir.z
|
|
})
|
|
|
|
local t1 = (aabb.min.x - ray.position.x) * dirfrac.x
|
|
local t2 = (aabb.max.x - ray.position.x) * dirfrac.x
|
|
local t3 = (aabb.min.y - ray.position.y) * dirfrac.y
|
|
local t4 = (aabb.max.y - ray.position.y) * dirfrac.y
|
|
local t5 = (aabb.min.z - ray.position.z) * dirfrac.z
|
|
local t6 = (aabb.max.z - ray.position.z) * dirfrac.z
|
|
|
|
local tmin = math.max(math.max(math.min(t1, t2), math.min(t3, t4)), math.min(t5, t6))
|
|
local tmax = math.min(math.min(math.max(t1, t2), math.max(t3, t4)), math.max(t5, t6))
|
|
|
|
-- // -- ray is intersecting AABB, but whole AABB is behind us
|
|
if (tmax < 0.0) then return false end
|
|
|
|
-- // -- ray does not intersect AABB
|
|
if (tmin > tmax) then return false end
|
|
|
|
-- // -- Return collision po int and distance from ray origin
|
|
local dx = ray.position.x + ray.direction.x * tmin
|
|
local dy = ray.position.y + ray.direction.y * tmin
|
|
local dz = ray.position.z + ray.direction.z * tmin
|
|
distance = sqrt( dx * dx + dy * dy + dz * dz)
|
|
return true
|
|
end
|
|
|
|
local function intersectOBB( ray, obb, distance)
|
|
|
|
local O = ray.position -- // Line origin
|
|
local D = ray.direction -- // Line direction
|
|
|
|
local C = obb.center -- // Box center
|
|
local bbmin = obb.axis[0]
|
|
local bbmax = obb.axis[1]
|
|
local e = obb.extents -- // Box extents
|
|
|
|
-- // Intersection method from Real-Time Rendering and Essential Mathematics for Games
|
|
|
|
local tMin = 0.0
|
|
local tMax = 100000.0
|
|
|
|
local delta = ffi.new("Vec3", {C.x - O.x, C.y - O.y, C.z - O.z})
|
|
|
|
-- // Test intersection with the 2 planes perpendicular to the OBB's X axis
|
|
|
|
local xaxis = ffi.new("Vec3", {obb.mat[0][0], obb.mat[0][1], obb.mat[0][2]})
|
|
local e = dot(xaxis, delta)
|
|
local f = dot(ray.direction, xaxis)
|
|
|
|
if ( math.abs(f) > 0.001 ) then -- // Standard case
|
|
|
|
local t1 = (e+bbmin.x)/f -- // Intersection with the "left" plane
|
|
local t2 = (e+bbmax.x)/f -- // Intersection with the "right" plane
|
|
-- // t1 and t2 now contain distances betwen ray origin and ray-plane intersections
|
|
|
|
-- // We want t1 to represent the nearest intersection,
|
|
-- // so if it's not the case, invert t1 and t2
|
|
if (t1>t2) then
|
|
local w=t1;t1=t2;t2=w; -- // swap t1 and t2
|
|
end
|
|
|
|
-- // tMax is the nearest "far" intersection (amongst the X,Y and Z planes pairs)
|
|
if ( t2 < tMax )
|
|
tMax = t2;
|
|
-- // tMin is the farthest "near" intersection (amongst the X,Y and Z planes pairs)
|
|
if ( t1 > tMin )
|
|
tMin = t1;
|
|
|
|
-- // And here's the trick :
|
|
-- // If "far" is closer than "near", then there is NO intersection.
|
|
-- // See the images in the tutorials for the visual explanation.
|
|
if (tMax < tMin ) then
|
|
return false
|
|
end
|
|
|
|
else -- // Rare case : the ray is almost parallel to the planes, so they don't have any "intersection"
|
|
if(-e+bbmin.x > 0.0 or -e+bbmax.x < 0.0 ) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- // Test intersection with the 2 planes perpendicular to the OBB's Y axis
|
|
-- // Exactly the same thing than above.
|
|
local yaxis = ffi.new("Vec3", {obb.mat[1][0], obb.mat[1][1], obb.mat[1][2]})
|
|
local e = dot(yaxis, delta)
|
|
local f = dot(ray.direction, yaxis)
|
|
|
|
if ( math.abs(f) > 0.001 ) then
|
|
|
|
local t1 = (e+bbmin.y)/f
|
|
local t2 = (e+bbmax.y)/f
|
|
|
|
if (t1>t2) then local w=t1;t1=t2;t2=w; end
|
|
|
|
if ( t2 < tMax ) then
|
|
tMax = t2
|
|
end
|
|
if ( t1 > tMin ) then
|
|
tMin = t1
|
|
end
|
|
if (tMin > tMax) then
|
|
return false
|
|
end
|
|
|
|
else
|
|
if(-e+bbmin.y > 0.0 or -e+bbmax.y < 0.0) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- // Test intersection with the 2 planes perpendicular to the OBB's Z axis
|
|
-- // Exactly the same thing than above.
|
|
|
|
local zaxis = ffi.new("Vec3", {obb.mat[2][0], obb.mat[2][1], obb.mat[2][2]})
|
|
local e = dot(zaxis, delta)
|
|
local f = dot(ray.direction, zaxis)
|
|
|
|
if ( math.abs(f) > 0.001 ) then
|
|
|
|
local t1 = (e+bbmin.z)/f
|
|
local t2 = (e+bbmax.z)/f
|
|
|
|
if (t1>t2) then local w=t1;t1=t2;t2=w; end
|
|
|
|
if ( t2 < tMax ) then
|
|
tMax = t2
|
|
end
|
|
if ( t1 > tMin ) then
|
|
tMin = t1
|
|
end
|
|
if (tMin > tMax) then
|
|
return false
|
|
end
|
|
|
|
else
|
|
if(-e+bbmin.z > 0.0 || -e+bbmax.z < 0.0) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
distance = tMin
|
|
return true
|
|
end
|
|
|
|
local function AddBoundingBox(vmin, vmax, tag)
|
|
|
|
local obb = {}
|
|
local center = ffi.new("Vec3",
|
|
{
|
|
(vmax.x - vmin.x) * 0.5 + vmin.x,
|
|
(vmax.y - vmin.y) * 0.5 + vmin.y,
|
|
(vmax.z - vmin.z) * 0.5 + vmin.z,
|
|
})
|
|
local extents = ffi.new("Vec3", {
|
|
(vmax.x - vmin.x) * 0.5,
|
|
(vmax.y - vmin.y) * 0.5,
|
|
(vmax.z - vmin.z) * 0.5
|
|
})
|
|
|
|
obb.axis.x = ffi.new("Vec3", {vmin.x,vmin.y,vmin.z})
|
|
obb.axis.y = ffi.new("Vec3", {vmax.x,vmax.y,vmax.z})
|
|
|
|
obb.center = ffi.new("Vec3", {center.x, center.y, centerz})
|
|
obb.extents = ffi.new("Vec3", {extents.x, extents.y, extents.z})
|
|
obb.tag = tag
|
|
tinsert(g_bounds, obb)
|
|
return #g_bounds-1
|
|
end
|
|
|
|
-- // Recal OOBB - rotations and translations may change min and max values.
|
|
local function MultWorld( obb )
|
|
|
|
local out = ffi.new("OBB")
|
|
-- // for(i=0; i<3; ++i) {
|
|
-- // dmVMath::Vector4 res = obb.mat * dmVMath::Vector4(obb.axis[i].x, obb.axis[i].y, obb.axis[i].z);
|
|
-- // out.axis[i] = Vec3(res[0], res[1], res[2]);
|
|
-- // }
|
|
out.mat = obb.mat
|
|
out.axis.x = obb.axis.x
|
|
out.axis.y = obb.axis.y
|
|
-- // dmVMath::Vector4 res = obb.mat * dmVMath::Vector4(obb.center.x, obb.center.y, obb.center.z, 1.0);
|
|
out.center = ffi.new("Vec3", {obb.mat[3][0], obb.mat[3][1], obb.mat[3][2]})
|
|
out.extents = obb.extents
|
|
out.tag = obb.tag
|
|
return out
|
|
end
|
|
|
|
local function RaycastToBox( x1, y1, z1, x2, y2, z2)
|
|
|
|
local ray = ffi.new("Ray", { {x1, y1, z1}, {x2, y2, z2} })
|
|
|
|
-- // Go through boxes checking hits. Closest hit wins!
|
|
local distance = ffi.new("float[1]", { {math.huge} })
|
|
local closest = math.huge
|
|
local hitbox = -1
|
|
local hitpoint = ffi.new("Vec3")
|
|
for i,bound in ipairs(g_bounds) do
|
|
local testbox = MultWorld(bound)
|
|
if( intersectOBB(ray, testbox, distance ) ) then
|
|
if(distance[0] < closest) then
|
|
closest = distance[0]
|
|
hitbox = i
|
|
end
|
|
end
|
|
end
|
|
|
|
if(closest == math.huge) then
|
|
return nil, nil, nil
|
|
else
|
|
local hitpoint = ffi.new("Vec3", {
|
|
ray.position.x + ray.direction.x * closest,
|
|
ray.position.y + ray.direction.y * closest,
|
|
ray.position.z + ray.direction.z * closest
|
|
})
|
|
return closest, g_bounds[hitbox].tag, hitpoint
|
|
end
|
|
end
|
|
|
|
local function UpdateOBB( index, world )
|
|
-- // Need to do some index checking here.
|
|
g_bounds[index].mat = world
|
|
end
|
|
|
|
local SEED = 0
|
|
local hash = {208,34,231,213,32,248,233,56,161,78,24,140,71,48,140,254,245,255,247,247,40,
|
|
185,248,251,245,28,124,204,204,76,36,1,107,28,234,163,202,224,245,128,167,204,
|
|
9,92,217,54,239,174,173,102,193,189,190,121,100,108,167,44,43,77,180,204,8,81,
|
|
70,223,11,38,24,254,210,210,177,32,81,195,243,125,8,169,112,32,97,53,195,13,
|
|
203,9,47,104,125,117,114,124,165,203,181,235,193,206,70,180,174,0,167,181,41,
|
|
164,30,116,127,198,245,146,87,224,149,206,57,4,192,210,65,210,129,240,178,105,
|
|
228,108,245,148,140,40,35,195,38,58,65,207,215,253,65,85,208,76,62,3,237,55,89,
|
|
232,50,217,64,244,157,199,121,252,90,17,212,203,149,152,140,187,234,177,73,174,
|
|
193,100,192,143,97,53,145,135,19,103,13,90,135,151,199,91,239,247,33,39,145,
|
|
101,120,99,3,186,86,99,41,237,203,111,79,220,135,158,42,30,154,120,67,87,167,
|
|
135,176,183,191,253,115,184,21,233,58,129,233,142,39,128,211,118,137,139,255,
|
|
114,20,218,113,154,27,127,246,250,1,8,198,250,209,92,222,173,21,88,102,219
|
|
}
|
|
|
|
local function noise2(x, y)
|
|
local tmp = hash[(y + SEED) % 256 + 1]
|
|
return hash[(tmp + x) % 256 + 1]
|
|
end
|
|
|
|
local function lin_inter(x, y, s)
|
|
return x + s * (y-x);
|
|
end
|
|
|
|
local smooth_inter( x, y, s)
|
|
return lin_inter(x, y, s * s * (3-2*s))
|
|
end
|
|
|
|
local noise2d( x, y)
|
|
|
|
local x_int = x
|
|
local y_int = y
|
|
local x_frac = x - x_int
|
|
local y_frac = y - y_int
|
|
local s = noise2(x_int, y_int)
|
|
local t = noise2(x_int+1, y_int)
|
|
local u = noise2(x_int, y_int+1)
|
|
local v = noise2(x_int+1, y_int+1)
|
|
local low = smooth_inter(s, t, x_frac)
|
|
local high = smooth_inter(u, v, x_frac)
|
|
return smooth_inter(low, high, y_frac)
|
|
end
|
|
|
|
local perlin2d(x, y, freq, depth)
|
|
local xa = x*freq;
|
|
local ya = y*freq;
|
|
local amp = 1.0;
|
|
local fin = 0;
|
|
local div = 0.0;
|
|
|
|
local i;
|
|
for i=0, depth-1 do
|
|
div = div + 256 * amp;
|
|
fin = fin + noise2d(xa, ya) * amp
|
|
amp = amp / 2
|
|
xa = xa * 2
|
|
ya = ya * 2
|
|
end
|
|
|
|
return fin/div
|
|
end
|
|
|
|
return {
|
|
normalize = normalize,
|
|
dot = dot,
|
|
intersect = intersect,
|
|
intersectOBB = intersectOBB,
|
|
|
|
AddBoundingBox = AddBoundingBox,
|
|
MultWorld = MultWorld,
|
|
RaycastToBox = RaycastToBox,
|
|
UpdateOBB = UpdateOBB,
|
|
|
|
noise2 = noise2,
|
|
lin_inter = lin_inter,
|
|
smooth_inter = smooth_inter,
|
|
} |