329 lines
11 KiB
Plaintext
329 lines
11 KiB
Plaintext
|
local enable_debug = false
|
||
|
local print_tip = function(s, ...)
|
||
|
local f = print_tip or printf
|
||
|
if enable_debug then
|
||
|
return f("Geometry Ray: " .. s, ...)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Check for material (engine edit required)
|
||
|
function lshift(x, by)
|
||
|
return x * 2 ^ by
|
||
|
end
|
||
|
|
||
|
function test(x, mask)
|
||
|
return bit_and(x, mask) == mask
|
||
|
end
|
||
|
|
||
|
local flags_test = {
|
||
|
["flBreakable"] = lshift(1, 0),
|
||
|
["flBounceable"] = lshift(1, 2),
|
||
|
["flSkidmark"] = lshift(1, 3),
|
||
|
["flBloodmark"] = lshift(1, 4),
|
||
|
["flClimable"] = lshift(1, 5),
|
||
|
["flPassable"] = lshift(1, 7),
|
||
|
["flDynamic"] = lshift(1, 8),
|
||
|
["flLiquid"] = lshift(1, 9),
|
||
|
["flSuppressShadows"] = lshift(1, 10),
|
||
|
["flSuppressWallmarks"] = lshift(1, 11),
|
||
|
["flActorObstacle"] = lshift(1, 12),
|
||
|
["flNoRicoshet"] = lshift(1, 13),
|
||
|
["flInjurious"] = lshift(1, 28),
|
||
|
["flShootable"] = lshift(1, 29),
|
||
|
["flTransparent"] = lshift(1, 30),
|
||
|
["flSlowDown"] = lshift(1, 31),
|
||
|
}
|
||
|
|
||
|
--[[
|
||
|
|
||
|
// material exports
|
||
|
.def_readonly( "material_name" , &script_rq_result::pMaterialName )
|
||
|
.def_readonly( "material_flags" , &script_rq_result::pMaterialFlags )
|
||
|
.def_readonly( "material_phfriction" , &script_rq_result::fPHFriction )
|
||
|
.def_readonly( "material_phdamping" , &script_rq_result::fPHDamping )
|
||
|
.def_readonly( "material_phspring" , &script_rq_result::fPHSpring )
|
||
|
.def_readonly( "material_phbounce_start_velocity" , &script_rq_result::fPHBounceStartVelocity )
|
||
|
.def_readonly( "material_phbouncing" , &script_rq_result::fPHBouncing )
|
||
|
.def_readonly( "material_flotation_factor" , &script_rq_result::fFlotationFactor )
|
||
|
.def_readonly( "material_shoot_factor" , &script_rq_result::fShootFactor )
|
||
|
.def_readonly( "material_shoot_factor_mp" , &script_rq_result::fShootFactorMP )
|
||
|
.def_readonly( "material_bounce_damage_factor" , &script_rq_result::fBounceDamageFactor )
|
||
|
.def_readonly( "material_injurious_speed" , &script_rq_result::fInjuriousSpeed )
|
||
|
.def_readonly( "material_vis_transparency_factor" , &script_rq_result::fVisTransparencyFactor )
|
||
|
.def_readonly( "material_snd_occlusion_factor" , &script_rq_result::fSndOcclusionFactor )
|
||
|
.def_readonly( "material_density_factor" , &script_rq_result::fDensityFactor )
|
||
|
|
||
|
]]
|
||
|
|
||
|
-- Geometry Ray class by Thial, edited by demonized
|
||
|
class "geometry_ray"
|
||
|
|
||
|
--[[
|
||
|
(At least one range parameter should be specified)
|
||
|
ray_range:
|
||
|
Defines the total range of the ray. If you want to attach the ray to
|
||
|
a fast moving object it is good to extend the ray so that you can reduce
|
||
|
the polling rate by using the get function.
|
||
|
contact_range:
|
||
|
Defines the distance at which the result will report being in contact.
|
||
|
You can skip it or set it to a value lower than the ray_range to
|
||
|
still be able to get the intersection position from the result while
|
||
|
marking the ray as not being in contact yet
|
||
|
distance_offset:
|
||
|
Defines how much the intersection position is offset.
|
||
|
You can use both positive and negative values or you can leave it blank.
|
||
|
flags (bit map = values can be added together for combined effect):
|
||
|
0 : None
|
||
|
1 : Objects
|
||
|
2 : Statics
|
||
|
4 : Shapes
|
||
|
8 : Obstacles
|
||
|
]]--
|
||
|
function geometry_ray:__init(args)
|
||
|
local args = args or {}
|
||
|
if args.ray_range == nil and args.contact_range == nil then
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
self.ray_range = args.ray_range or args.contact_range
|
||
|
self.contact_range = args.contact_range or args.ray_range
|
||
|
self.distance_offset = args.distance_offset ~= nil and args.distance_offset or 0
|
||
|
self.ray = ray_pick()
|
||
|
self.ray:set_flags(args.flags or 2)
|
||
|
self.ray:set_range(self.ray_range)
|
||
|
self.visualize = args.visualize
|
||
|
if args.ignore_object then
|
||
|
self.ray:set_ignore_object(args.ignore_object)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
position:
|
||
|
position from which the ray will start
|
||
|
direction:
|
||
|
direction in which the ray will be fired
|
||
|
]]--
|
||
|
function geometry_ray:get(position, direction)
|
||
|
if position == nil or direction == nil then
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
local position = vector():set(position)
|
||
|
local direction = vector():set(direction)
|
||
|
|
||
|
self.ray:set_position(position)
|
||
|
self.ray:set_direction(direction)
|
||
|
local res = self.ray:query()
|
||
|
local distance = res and self.ray:get_distance() or self.ray_range
|
||
|
local result = {}
|
||
|
if self.visualize then
|
||
|
local init_pos = vector():set(position)
|
||
|
local end_pos = vector():mad(init_pos, direction, distance)
|
||
|
VisualizeRay(init_pos, end_pos)
|
||
|
end
|
||
|
result.in_contact = distance <= self.contact_range
|
||
|
result.position = position:add(direction:mul(distance + self.distance_offset))
|
||
|
result.distance = distance
|
||
|
result.raw_distance = self.ray:get_distance()
|
||
|
result.success = res
|
||
|
result.object = self.ray:get_object()
|
||
|
result.element = self.ray:get_element()
|
||
|
result.result = self.ray:get_result()
|
||
|
|
||
|
-- Cast to Lua table
|
||
|
-- if result.result then
|
||
|
-- local r = {}
|
||
|
-- r.material_name = result.result.material_name
|
||
|
-- r.material_flags = result.result.material_flags
|
||
|
-- r.material_phfriction = result.result.material_phfriction
|
||
|
-- r.material_phdamping = result.result.material_phdamping
|
||
|
-- r.material_phspring = result.result.material_phspring
|
||
|
-- r.material_phbounce_start_velocity = result.result.material_phbounce_start_velocity
|
||
|
-- r.material_phbouncing = result.result.material_phbouncing
|
||
|
-- r.material_flotation_factor = result.result.material_flotation_factor
|
||
|
-- r.material_shoot_factor = result.result.material_shoot_factor
|
||
|
-- r.material_shoot_factor_mp = result.result.material_shoot_factor_mp
|
||
|
-- r.material_bounce_damage_factor = result.result.material_bounce_damage_factor
|
||
|
-- r.material_injurious_speed = result.result.material_injurious_speed
|
||
|
-- r.material_vis_transparency_factor = result.result.material_vis_transparency_factor
|
||
|
-- r.material_snd_occlusion_factor = result.result.material_snd_occlusion_factor
|
||
|
-- r.material_density_factor = result.result.material_density_factor
|
||
|
|
||
|
-- result.result = r
|
||
|
-- end
|
||
|
|
||
|
return result
|
||
|
end
|
||
|
|
||
|
-- Engine edit required for testing materials
|
||
|
-- If not possible to get material - return nil
|
||
|
function geometry_ray:isMaterialFlag(flag)
|
||
|
local result = self.ray:get_result()
|
||
|
if not result then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if not result.material_flags then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if not flags_test[flag] then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
return test(result.material_flags, flags_test[flag])
|
||
|
end
|
||
|
|
||
|
function geometry_ray:getMaterialFlags()
|
||
|
local result = self.ray:get_result()
|
||
|
if not result then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if not result.material_flags then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local res = {}
|
||
|
for k, v in pairs(flags_test) do
|
||
|
res[k] = test(result.material_flags, v)
|
||
|
end
|
||
|
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
-- Utils
|
||
|
-- Check if values are similar to a precision
|
||
|
function similar(float1, float2, epsilon)
|
||
|
return math.abs(float1 - float2) <= (epsilon or 0.0001)
|
||
|
end
|
||
|
|
||
|
function vec_similar(vec1, vec2, epsilon)
|
||
|
return similar(vec1.x, vec2.x, epsilon) and similar(vec1.y, vec2.y, epsilon) and similar(vec1.z, vec2.z, epsilon)
|
||
|
end
|
||
|
|
||
|
-- Linear inter/extrapolation
|
||
|
function lerp(a, b, f)
|
||
|
if a and b and f then
|
||
|
return a + f * (b - a)
|
||
|
else
|
||
|
return a or b or 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Visualize ray from one point to other with particles playing at setted step
|
||
|
class "VisualizeRay"
|
||
|
function VisualizeRay:__init(init_pos, end_pos, particle_step, visualize_time, force_stop)
|
||
|
self.init_pos = init_pos
|
||
|
self.end_pos = end_pos
|
||
|
self.visualize_time = visualize_time or 3000
|
||
|
self.particle_step = particle_step or 0.02
|
||
|
self.force_stop = force_stop
|
||
|
self.force_stop_default = force_stop
|
||
|
self.time = 0
|
||
|
|
||
|
self.start = function()
|
||
|
for i = 0, 1, self.particle_step do
|
||
|
local p = particles_object("amik\\hit_fx\\metal\\hit_sparks_glow")
|
||
|
local x = lerp(self.init_pos.x, self.end_pos.x, i)
|
||
|
local y = lerp(self.init_pos.y, self.end_pos.y, i)
|
||
|
local z = lerp(self.init_pos.z, self.end_pos.z, i)
|
||
|
p:play_at_pos(vector():set(x, y, z))
|
||
|
local time = 0
|
||
|
local stopped = false
|
||
|
AddUniqueCall(function()
|
||
|
if self.time > self.visualize_time then
|
||
|
if not stopped then
|
||
|
if self.force_stop then p:stop() else p:stop_deffered() end
|
||
|
stopped = true
|
||
|
end
|
||
|
if not p:playing() then
|
||
|
p = nil
|
||
|
return true
|
||
|
end
|
||
|
else
|
||
|
if not p:playing() then
|
||
|
p:play_at_pos(vector():set(x, y, z))
|
||
|
end
|
||
|
time = time + device().time_delta
|
||
|
self.time = time
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
end
|
||
|
self.start()
|
||
|
|
||
|
self.reset_time = function()
|
||
|
self.time = 0
|
||
|
end
|
||
|
|
||
|
self.reset = function()
|
||
|
self.time = 0
|
||
|
self.force_stop = self.force_stop_default
|
||
|
self.start()
|
||
|
end
|
||
|
|
||
|
self.stop = function()
|
||
|
self.time = self.visualize_time + 1
|
||
|
self.force_stop = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Get surface normals by Aoldri, edited by demonized
|
||
|
function get_surface_normal(pos, dir)
|
||
|
local ray = geometry_ray({
|
||
|
ray_range = 1000,
|
||
|
visualize = false,
|
||
|
flags = 1+2,
|
||
|
ignore_object = db.actor,
|
||
|
})
|
||
|
|
||
|
-- Get player's camera position and direction in world space
|
||
|
local pos0 = pos and vector():set(pos) or device().cam_pos
|
||
|
local angle1 = dir and vector():set(dir) or device().cam_dir
|
||
|
|
||
|
-- Generate two positions orthogonal to camera direction and each other
|
||
|
local pos01 = vector():set(0, 1, 0)
|
||
|
pos01 = pos01:sub(vector():set(angle1):mul(pos01:dotproduct(angle1)))
|
||
|
pos01:normalize()
|
||
|
|
||
|
local pos02 = vector_cross(angle1, pos01)
|
||
|
|
||
|
pos01 = pos01:mul(0.01)
|
||
|
pos02 = pos02:mul(0.01)
|
||
|
|
||
|
pos01 = pos01:add(pos0)
|
||
|
pos02 = pos02:add(pos0)
|
||
|
|
||
|
-- Get positions of intersections of rays around pos0
|
||
|
local res = ray:get(pos0, angle1)
|
||
|
local pos1 = res.position
|
||
|
local pos2 = ray:get(pos01, angle1).position
|
||
|
local pos3 = ray:get(pos02, angle1).position
|
||
|
|
||
|
if not res.success then
|
||
|
-- print_tip("cant get normal by pos %s, dir %s", pos0, angle1)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- VisualizeRay(pos0, pos1, nil, 300)
|
||
|
-- VisualizeRay(pos01, pos2, nil, 300)
|
||
|
-- VisualizeRay(pos02, pos3, nil, 300)
|
||
|
|
||
|
-- Get vectors from intersection points from pos1
|
||
|
local vec2 = vec_sub(pos1, pos2)
|
||
|
local vec3 = vec_sub(pos1, pos3)
|
||
|
|
||
|
-- Find normal vector of surface by taking cross product of intersection vectors
|
||
|
local cross = (vector_cross(vec2, vec3)):normalize()
|
||
|
|
||
|
-- If the direction and normal vectors heading in similar direction - invert normal
|
||
|
local deg = angle1:dotproduct(cross)
|
||
|
if deg > 0 then
|
||
|
cross:invert()
|
||
|
end
|
||
|
|
||
|
-- VisualizeRay(pos1, vector():set(pos1):add(cross), nil, 300)
|
||
|
return cross
|
||
|
end
|