Source file : globe_3d-collision_detection.adb
-------------------------------------------------------------------------
-- GLOBE_3D.Collision_detection
--
-- Copyright (c) Gautier de Montmollin 1999 .. 2016
-- SWITZERLAND
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
-- NB: this is the MIT License, as found 12-Sep-2007 on the site
-- http://www.opensource.org/licenses/mit-license.php
-------------------------------------------------------------------------
with GL.Math;
with GLOBE_3D.Options;
-- with Ada.Text_IO; use Ada.Text_IO; -- for debugging
package body GLOBE_3D.Collision_detection is
check_normals: constant Boolean:= GLOBE_3D.Options.strict_geometry;
procedure Reaction(
o : Object_3D'Class;
ball : Ball_type;
method : Reaction_method;
step : in out Vector_3D; -- Whole step (in: desired, out: effective)
reacted : out Real -- in proportion to step
)
is
procedure Reaction_internal(o: Object_3D'Class) is
use GL.Math;
P_after_step, P_face: Point_3D;
u,n : Vector_3D;
dist_after, dist_before, nn: Real; -- distance orientee
retour: Real:= 0.0;
lstep0: constant Real:= Norm(step);
so: p_Object_3D_list:= o.sub_objects;
-- This function check whether we are inside the prism above face f
function Dans_prisme_epaissi(f: Positive) return Boolean is
sfp1: Positive;
Ps, Psp1: Point_3D;
u, edge_vector, npa: Vector_3D;
dist_edge, nnpa: Real;
facteur: constant:= 1.05;
begin
-- Cycle through face's vertices
for sf in reverse 1..o.face_internal(f).last_edge loop
sfp1:= 1 + sf mod o.face_internal(f).last_edge;
Ps := o.point( o.face_internal(f).P_compact(sf) );
Psp1:= o.point( o.face_internal(f).P_compact(sfp1) );
edge_vector:= Psp1 - Ps;
npa:= n * edge_vector;
nnpa:= Norm(npa);
if Almost_zero(nnpa) then -- degenerated edge
return False;
end if;
npa:= 1.0/nnpa * npa;
-- npa points towards the prism's interior
u:= P_after_step - (Ps + o.centre);
dist_edge:= u * npa;
if dist_edge < - ball.radius * facteur then
return False;
end if;
end loop;
return True;
end Dans_prisme_epaissi;
begin
if Almost_zero(lstep0) then
return;
end if;
P_after_step:= ball.centre + step;
for face in reverse 1..o.Max_faces loop
n:= o.face_internal(face).normal;
if check_normals then
nn:= Norm(n);
if Almost_zero(nn) then
raise Zero_normal;
elsif abs(nn - 1.0) > 1.0e-7 then
raise Not_one_normal with " norm = " & Real'Image(nn);
end if;
end if;
-- put_line("step=" & step(0)'img & ' ' & step(1)'img & ' ' & step(2)'img);
-- put_line(" n=" & n(0)'img & ' ' & n(1)'img & ' ' & n(2)'img);
if step * n < 0.0 then
P_face:= o.point(o.face_internal(face).P_compact(1)) + o.centre;
-- ^ any point on the face, to measure distance to face's plane.
u:= ball.centre - P_face;
dist_before:= u * n;
if dist_before > 0.0 then
-- ^ Fine, we are on the right side of the face.
-- Test added to Engine_3D's algo, since objects are
-- not always hollow, convex polyhedrons anymore.
u:= P_after_step - P_face;
dist_after:= u * n;
if dist_after < ball.radius
-- Ouch! Collision! React we must!
--
-- This includes negatives values of dist_after, in cases
-- the intended step makes going through the face!
and then
Dans_prisme_epaissi(face)
then
if o.face(face).skin /= invisible then
-- ^ this assumes: invisible <=> can go through
reacted:= reacted + retour / lstep0;
-- !! seems wrong if reactions in different directions
-- should be something like step * step0
case method is
when elastic =>
raise Unsupported with "elastic reaction";
-- should compute the time the "ball" takes from rebound to
-- next face or portal.
when slide =>
retour:= ball.radius - dist_after; -- always > 0
step:= step + retour * n;
-- Since step and n have a negative dot product -checked-
-- and dist(ball.centre+step_old,face) < ball.radius -checked-
-- then:
-- ||step_new|| < ||step_old|| --> decreasing algo :-)
end case;
end if;
end if;
end if;
end if;
end loop;
while so /= null loop
Reaction_internal(so.objc.all);
so:= so.next;
end loop;
end Reaction_internal;
begin
reacted:= 0.0;
Reaction_internal(o);
end Reaction;
end GLOBE_3D.Collision_detection;
GLOBE_3D: Ada library for real-time 3D rendering.
Ada programming.