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 local function throttle(func, tg_throttle) local tg = 0 if not tg_throttle or tg_throttle == 0 then return function(...) local t = time_global() if t ~= tg then tg = t return func(...) end end else return function(...) local t = time_global() if t < tg then return end tg = t + tg_throttle return func(...) end 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 self.visualize_end_pos_only = args.visualize_end_pos_only 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, nil, nil, nil, self.visualize_end_pos_only) 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, draw_end_only) 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.draw_end_only = draw_end_only self.start = function() local begin = self.draw_end_only and 1 or 0 for i = begin, 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(throttle(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