diff --git a/lua/stargate/shared/tracelines.lua b/lua/stargate/shared/tracelines.lua
index 6b517ed1..6c69a46f 100644
--- a/lua/stargate/shared/tracelines.lua
+++ b/lua/stargate/shared/tracelines.lua
@@ -1,5 +1,5 @@
-/*
- Stargate Lib for GarrysMod10
+--[[
+ Stargate Lib for Garry's Mod 10
Copyright (C) 2007 aVoN
This program is free software: you can redistribute it and/or modify
@@ -14,17 +14,29 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-*/
+]]
--#########################################
--- Traclines - To stop them on lua drawn physboxes
+-- Traclines - To stop them on lua drawn physboxes
--#########################################
StarGate.Trace = StarGate.Trace or {};
StarGate.Trace.Entities = StarGate.Trace.Entities or {};
StarGate.Trace.Classes = StarGate.Trace.Classes or {};
+StarGate.Trace.Code = util.TraceLine; -- Trace code to be executed
+StarGate.Trace.Data = {
+ start = Vector(), -- The start position of the trace
+ endpos = Vector(), -- The end position of the trace
+ mins = Vector(), -- The lowest corner of the trace
+ maxs = Vector(), -- The highest corner of the trace
+ filter = nil, -- Things the trace should not hit
+ mask = MASK_SOLID, -- This determines what the trace should hit
+ collisiongroup = COLLISION_GROUP_NONE, -- What the trace should hit collision group regards
+ ignoreworld = false, -- Should the trace ignore world or not
+ output = nil -- Result will be written here instead of returning a new table
+};
---################# Deephook to ents.Create serverside @aVoN
+-- ################# Deephook to ents.Create serverside @aVoN
if SERVER then
hook.Add("OnEntityCreated","StarGate.OnEntityCreated",function(e)
if(not IsValid(e) or not StarGate.Trace) then return end;
@@ -34,25 +46,25 @@ if SERVER then
end)
end
---################# Add a class to check to the tracesline calculation @aVoN
+-- ################# Add a class to check to the tracesline calculation @aVoN
function StarGate.Trace:Add(c,condition)
- self.Classes[c] = {Condition=condition};
- for _,v in pairs(ents.FindByClass(c)) do
+ self.Classes[c] = {Condition = condition};
+ for _, v in pairs(ents.FindByClass(c)) do
self:GetEntityData(v);
end
end
---################# Adds a condition-function to the specific entity. This function decides if the trace goes through the BoundingBox or not @aVoN
+-- ################# Adds a condition-function to the specific entity. This function decides if the trace goes through the BoundingBox or not @aVoN
function StarGate.Trace:AddCondition(c,condition)
if(self.Classes[c]) then
- self.Classes[c] = {Condition=condition};
+ self.Classes[c] = {Condition = condition};
end
end
---################# Remoces such a class @aVoN
+-- ################# Remoces such a class @aVoN
function StarGate.Trace:Remove(c)
self.Classes[c] = nil;
- for k,_ in pairs(self.Entities) do
+ for k, _ in pairs(self.Entities) do
if(k:IsValid()) then
if(k:GetClass() == c) then
self.Entities[k] = nil;
@@ -63,7 +75,7 @@ function StarGate.Trace:Remove(c)
end
end
---################# Is the vector given inside the box or outside of it? @aVoN
+-- ################# Is the vector given inside the box or outside of it? @aVoN
function StarGate.Trace:InBox(pos,Min,Max)
if(
(pos.x >= Min.x and pos.x <= Max.x) and
@@ -77,17 +89,18 @@ end
-- Because this might also be usefull for other scripts, were defininig this global
StarGate.InBox = function(a,b,c) return StarGate.Trace:InBox(a,b,c) end;
---################# Updates the entities OBB Datas @aVoN
+-- ################# Updates the entities OBB Datas @aVoN
function StarGate.Trace:GetEntityData(e)
if(IsValid(e)) then
- local time = CurTime();
- if(not self.Entities[e] or self.Entities[e].Last + 1 < time) then
- local offset = e:OBBCenter(); -- We need the OBB's relatively to the Entiti's position, not to the OBBCenter
+ local now = CurTime();
+ if(not self.Entities[e] or self.Entities[e].Last + 1 < now) then
+ -- We need the OBB relatively to the Entity's position, not to the OBBCenter
+ local offset = e:OBBCenter();
self.Entities[e] = {
- Min = e:OBBMins()+offset,
- Max = e:OBBMaxs()+offset,
- --Radius = e:BoudingRadius(),
- Last = time,
+ Min = e:OBBMins() + offset,
+ Max = e:OBBMaxs() + offset,
+ -- Radius = e:BoudingRadius(),
+ Last = now,
}
end
return true;
@@ -97,80 +110,168 @@ function StarGate.Trace:GetEntityData(e)
end
end
---################# Helper Function: Makes the direction vector longer and checks if the hitpos is within a specific range (== hit wall) @aVoN
+function StarGate.Trace:InSphere(rorg, spos, srad)
+ local rorg = Vector(rorg); rorg:Sub(spos)
+ return (rorg:LengthSqr() <= srad^2)
+end
+
+function StarGate.Trace:AmongRay(bpos, rorg, rdir, full)
+ local sray = Vector(bpos); sray:Sub(rorg);
+ if(sray:Cross(rdir):LengthSqr() < 0.01) then
+ local eray = Vector(rorg); eray:Add(rdir); eray:Sub();
+ eray.x, eray.y, eray.z = -eray.x, -eray.y, -eray.z;
+ sdot, edot = sray:Dot(rdir), eray:Dot(rdir);
+ if(sdot < 0 and edot < 0) then return false; end -- Behind
+ if(not full and sdot > 0 and edot > 0) then return false; end
+ return true; -- Position is on the ray in non-full format
+ end; return false;
+end
+
+--[[
+ * Checks whenever ray hits sphere @dvdvideo1234
+ * This can be used to check collisions for shields and spheres in general
+ * I've explained this here: https://math.stackexchange.com/a/2633290/266012
+ * Returns the nearest and furthest circle intersection point ( when available )
+ * rorg > Ray start origin position. Where are we tracing from.
+ * rdir > Ray direction vector. Trace direction being checked
+ * rlen > Ray length forced value overdrives direction ( not mandatory )
+ * spos > Sphere position vector. The sphere location in 3D space
+ * srad > Sphere radius value. The actual sphere size in 3D space
+ * blen > When enabled considers the ray dot for intersections
+ * This forces the function to produce actual intersections
+ * that check whenever the points belong on the ray or not
+]]
+function StarGate.Trace:HitSphere(rorg, rdir, rlen, spos, srad, blen)
+ local eque = (rlen and tonumber(rlen) or rdir:Length());
+ if(eque <= 0) then return nil end -- No intersection
+ local rdir = rdir:GetNormalized(); rdir:Mul(equa); -- Read length
+ local equr, equa = Vector(rorg), eque^2; equr:Sub(spos); -- Sphere norm
+ local equb, equc = 2 * rdir:Dot(equr), (equr:LengthSqr() - srad^2);
+ local equd = (equb ^ 2 - 4 * equa * equc) -- Check imaginary roots
+ if(equd < 0) then return nil end -- No intersection discriminant
+ local mqua = (1 / (2 * equa)); equd, equb = mqua*math.sqrt(equd), -equb*mqua;
+ local mpos = Vector(rdir); mpos:Mul(equb - equd); mpos:Add(rorg)
+ local ppos = Vector(rdir); ppos:Mul(equb + equd); ppos:Add(rorg)
+ local isin = self:InSphere(rorg, spos, srad) -- Inside check
+ local xpos, ounr = (isin and ppos or mpos), (isin and -1 or 1)
+ -- The position that a bullet will actually hit the circle when fired
+ if(not self:AmongRay(xpos, rorg, rdir, true)) then return nil else
+ local norm = Vector(xpos); norm:Sub(spos); norm:Normalize()
+ local frac = xpos:Distance(rorg) / eque; norm:Mul(ounr)
+ return {HitPos = xpos, Fraction = frac, HitNormal = norm};
+ end
+end
+
+-- ################# Helper Function: Makes the direction vector longer and checks if the hitpos is within a specific range (== hit wall) @aVoN
function StarGate.Trace:HitWall(coordinate,pos,norm,mul,Min,Max,len,hit_normal)
- local old = norm;
- local norm = norm*mul; -- Make the normal hit the wall
+ local norm = norm * mul; -- Make the normal hit the wall
local length = norm:Length(); -- The new normal's length!
- if(length <= len) then -- The necessary normal length is shorter than the trace's length (we haven't hit anything before we hit the actual object!)
+ if(length <= len) then -- The necessary normal length is shorter than the trace's length.
+ -- We haven't hit anything before we hit the actual object!
-- Check, if the remaining two coordinates are within the Min/Max range!
local hit = pos + norm;
- if(
- -- The coordinate == "x,y,z" is because of rounding issues. The checked variable has to get skipped - It is on the wall! Sometimes we have 1.999999999 ~= 2
+ if( -- The coordinate == "x,y,z" is because of rounding issues.
+ -- The checked variable has to get skipped - It is on the wall!
+ -- Sometimes we have 1.999999999 ~= 2
(coordinate == "x" or hit.x >= Min.x and hit.x <= Max.x) and
(coordinate == "y" or hit.y >= Min.y and hit.y <= Max.y) and
(coordinate == "z" or hit.z >= Min.z and hit.z <= Max.z)
- ) then
- return {HitPos=hit,Fraction=length/len,HitNormal=hit_normal}; -- The hitpos and fraction!
+ ) then -- The hitpos and fraction!
+ return {HitPos = hit, Fraction = length / len, HitNormal = hit_normal};
end
end
end
---################# This checks one coordinate of the trace's normal @aVoN
+-- ################# This checks one coordinate of the trace's normal @aVoN
function StarGate.Trace:CheckCoordinate(coordinate,pos,norm,Min,Max,len,in_box)
-- I will not check if the trace start position is exactly on a wall, neither I will check, if the start pos is exactly in the center of this entity.
- -- Doing this would need me to add some more special exeptions where the probability for these cases are < 0.1% (except you are forcing it)
+ -- Doing this would need me to add some more special exceptions where the probability for these cases are < 0.1% (except you are forcing it)
local hit_normal = Vector(0,0,0); hit_normal[coordinate] = 1;
- --################# We are inside the bounding box - Trace to one wall!
- if(in_box) then
+ if(in_box) then -- We are inside the bounding box - Trace to one wall!
local mul = 0;
if(norm[coordinate] > 0) then -- Norm == 0 has been avoided in StarGate.Trace:New
-- The multiplier so the coordinate we're checking is exact on the wall's surface
- mul = math.abs((pos[coordinate] - Max[coordinate])/norm[coordinate]);
- hit_normal = -1*hit_normal;
+ mul = math.abs((pos[coordinate] - Max[coordinate]) / norm[coordinate]);
+ hit_normal = -1 * hit_normal;
else
- mul = math.abs((pos[coordinate] - Min[coordinate])/norm[coordinate]);
+ mul = math.abs((pos[coordinate] - Min[coordinate]) / norm[coordinate]);
end
return self:HitWall(coordinate,pos,norm,mul,Min,Max,len,hit_normal);
- else
- --################# We are outside the bounding box.
+ else -- We are outside the bounding box.
if(pos[coordinate] < Min[coordinate] and norm[coordinate] > 0) then -- We are below the Minimum and the normal goes up => We can hit
- local mul = math.abs((pos[coordinate] - Min[coordinate])/norm[coordinate]); -- The multiplier so the coordinate we're checking is exact on the wall's surface
- return self:HitWall(coordinate,pos,norm,mul,Min,Max,len,-1*hit_normal);
+ local mul = math.abs((pos[coordinate] - Min[coordinate]) / norm[coordinate]); -- The multiplier so the coordinate we're checking is exact on the wall's surface
+ return self:HitWall(coordinate,pos,norm,mul,Min,Max,len,-1 * hit_normal);
elseif(pos[coordinate] > Max[coordinate] and norm[coordinate] < 0) then --Above Max, norm down
- local mul = math.abs((pos[coordinate] - Max[coordinate])/norm[coordinate]);
+ local mul = math.abs((pos[coordinate] - Max[coordinate]) / norm[coordinate]);
return self:HitWall(coordinate,pos,norm,mul,Min,Max,len,hit_normal);
end
end
end
---################# Start a traceline which can hit Lua Drawn BoundingBoxes @aVoN
-function StarGate.Trace:New(start,dir,ignore)
- -- Clients need to add new entities inside this function (Server uses "HookBased" with ents.Create which uses less reouces!)
+--[[
+ * Converts trace ignore argument to quick indexing @dvdvideo1234
+ * Replace this with a dedicated class method in the routine
+ * Function filters are processed by regular traces and CAP specifics are skipped
+]]
+function StarGate.Trace:QuickIgnore(ignore)
+ local quick = {};
+ if(type(ignore) == "table") then
+ for _, v in pairs(ignore) do
+ quick[v] = true;
+ end
+ elseif(ignore) then
+ quick[ignore] = true;
+ end
+ return quick
+end
+
+-- ################# Start a trace line which can hit Lua Drawn BoundingBoxes @aVoN
+function StarGate.Trace:New(start,dir,ignore,mask,cogrp,iworld,width)
+ -- Clients need to add new entities inside this function (Server uses "HookBased" with ents.Create which uses less resources!)
if CLIENT then
- for k,_ in pairs(self.Classes) do
- for _,v in pairs(ents.FindByClass(k)) do
+ for k, _ in pairs(self.Classes) do
+ for _, v in pairs(ents.FindByClass(k)) do
self:GetEntityData(v);
end
end
end
- local trace = util.QuickTrace(start,dir,ignore);
- --This is better and faster than using table.HasValue(ignore,e) (nested for loops)
- local quick_ignore = {};
- if(type(ignore) == "table") then
- for _,v in pairs(ignore) do
- quick_ignore[v] = true;
- end
- elseif(ignore) then
- quick_ignore[ignore] = true;
+
+ -- Setup trace parameters and routine code
+ self.Data.filter = ignore
+ self.Data.start:Set(start)
+ self.Data.ignoreworld = tobool(iworld)
+ self.Data.mask = (tonumber(mask) or MASK_SOLID)
+ self.Data.endpos:Set(dir); Data.endpos:Add(start)
+ self.Data.collisiongroup = (tonumber(cogrp) or COLLISION_GROUP_NONE)
+
+ -- Setup trace width and routine @dvdvideo1234
+ if(width) then -- Trace cube hull with side of width
+ local m = (tonumber(width) or 0) / 2
+ if(m > 0) then -- Width is a valid non-zero number
+ self.Data.mins:SetUnpacked(-m, -m, -m)
+ self.Data.maxs:SetUnpacked( m, m, m)
+ self.Code = util.TraceHull -- Use New trace
+ else -- Margin must be a valid non-zero number
+ self.Code = util.TraceLine -- Use the old trace
+ end -- Otherwise falls back to using the old trace method
+ else -- No width so work as before. Fall back to zero width trace
+ self.Code = util.TraceLine
end
- local len = dir:Length()*trace.Fraction; -- First of all: The length of the trace.
- local norm_world = dir:GetNormal(); -- Get Normal of the dir vector (world coordinates!)
- -- We need to sort all entities first according to their distance to the trace-start, or we hit a prop behind a prop instead of the one infront
+
+ -- Run the trace when setup is ready and code is picked
+ local trace = self.Code(self.Data)
+
+ -- This is better and faster than using table.HasValue(ignore,e) (nested for loops)
+ local quick_ignore = self:QuickIgnore(self.Data.filter)
+
+ local len = dir:Length() * trace.Fraction; -- First of all: The length of the trace.
+ local norm_world = dir:GetNormal(); -- Get Normal of the direction vector (world coordinates!)
+ -- We need to sort all entities first according to their distance to the
+ -- trace-start or we hit a prop behind a prop instead of the one in front
-- Problem noticed by Lynix here: http://img140.imageshack.us/img140/7589/gmflatgrass0017bj9.jpg
- local traced_entities = {} -- Lynix modification
+ local trace_array = {} -- Lynix modification
- for e,_ in pairs(self.Entities) do
+ for e, _ in pairs(self.Entities) do
if(not quick_ignore[e]) then
if(self:GetEntityData(e)) then -- Update dimension data and check, if the ent is valid!
local class = e:GetClass();
@@ -178,37 +279,37 @@ function StarGate.Trace:New(start,dir,ignore)
local pos = e:WorldToLocal(start);
local in_box = false;
- if (class == "shield_core_buble") then
+ if(class == "shield_core_buble") then
in_box = StarGate.IsInShieldCore(e, start);
- elseif (class == "shield") then
- in_box = (e:GetPos():Distance(start) 0) then
- local min = 10000000000; -- Random startvalue
- for _,v in pairs(traced_entities) do
- local dist = trace.StartPos:Distance(v.HitPos);
- if(dist < min) then
- min = dist;
- trace = v;
- end
+
+ --[[ @Lynix @dvdvideo1234
+ * First entry is considered the first minimum
+ * Use for-integer loop as it is way faster than pairs
+ * Store true trace data reference to a local and compare
+ * If margin is not defined will be considered as minimum
+ ]]
+ local anc, mar = self.Data.start;
+ for i = 1, #trace_array do
+ local v = trace_array[i];
+ local m = anc:DistToSqr(v.HitPos);
+ if(not mar or m < mar) then
+ mar, trace = m, v;
end
end
+
-- END OF Lynix modification
return trace;
end