/* * Simbad - Robot Simulator * Copyright (C) 2004 Louis Hugues * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ----------------------------------------------------------------------------- * $Author: sioulseuguh $ * $Date: 2005/08/07 12:24:57 $ * $Revision: 1.3 $ * $Source: /cvsroot/simbad/src/simbad/sim/PhysicalEngine.java,v $ */ package simbad.sim; import java.util.ArrayList; import javax.media.j3d.BoundingBox; import javax.media.j3d.BoundingSphere; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; /** * Centralize resources and algorithms related to physical interactions. * Most of this is experimental for the time being. */ public class PhysicalEngine { /** gravitational acceleration constant in m/s**2 */ protected final static float g= 9.8f; double epsilonContact = 0.00001; // temp 3d resources private BoundingSphere bs1; private BoundingSphere bs2; private Vector3d v1; private Vector3d v2; private Vector3d v3; private Vector3d v4; private Vector3d v5; private Point3d p1; private Point3d p2; private Point3d p3; PhysicalEngine() { // Allocate some permanent resources to avoid gc. p1 = new Point3d(); p2 = new Point3d(); p3 = new Point3d(); v1 = new Vector3d(); v2 = new Vector3d(); v3 = new Vector3d(); v4 = new Vector3d(); v5 = new Vector3d(); bs1 = new BoundingSphere(); bs2 = new BoundingSphere(); } /** * Compute all external force contributions on an agent before any impact. * * @param dt * virtual time elapsed since last call. */ protected void computeForces(double dt, SimpleAgent a) { // Gravity // apply F = mg pointing down . if ( a.distanceToGround() > 0 ){ v1.set(0,-a.mass*g,0); a.linearAcceleration.add(v1); } // Friction // apply friction reaction if velocity>0 if ((a.staticFrictionCoefficient > 0) && (a.linearVelocity.lengthSquared() > 0)) { // Friction reaction is fx|N| - with N = mg float reaction = a.mass * g * a.staticFrictionCoefficient; // It is colinear to Velocity vector and in opposite direction. // Obtain a unit vector oriented like velocity. v1.set(a.linearVelocity); v1.normalize(); // scale it to reaction v1.scale(reaction ); a.linearAcceleration.sub(v1); } } /** * Check all agents/agent pairs and verify physical interactions and/or collision. * * Complexity is O(n^2). */ protected void checkAgentAgentPairs(double dt,ArrayList<SimpleAgent> agents, boolean computeImpact,boolean checkCollision) { int nagents = agents.size(); // At least two agents if (nagents >= 2) { // Check bounds of each couple of agent. for (int i = 0; i < nagents - 1; i++) { SimpleAgent a1 = ((SimpleAgent) agents.get(i)); if (a1.detachedFromSceneGraph) continue; a1.interactionDetected = false; // because there are no other transform above // translation // we can use it directly and not localToVWorld // But this works only on non oriented bounds (spheres). // a1.group.getLocalToVworld(tempt3d); bs1.set(a1.getBounds()); bs1.setRadius(bs1.getRadius()); // bs1.transform(tempt3d); bs1.transform(a1.translation); // add predicted displacement bs1.getCenter(p1); p1.add(a1.instantTranslation); bs1.setCenter(p1); if (checkCollision){ a1.collisionDetected = false; a1.clearVeryNear(); } for (int j = i + 1; j < nagents; j++) { SimpleAgent a2 = ((SimpleAgent) agents.get(j)); if (a2.detachedFromSceneGraph) continue; // at least one of them has moved. if ((a1.positionChanged) || (a2.positionChanged)) { a2.interactionDetected = false; //a2.group.getLocalToVworld(temp2t3d); bs2.set(a2.getBounds()); //bs2.transform(temp2t3d); bs2.transform(a2.translation); // add predicted displacement bs2.getCenter(p1); p1.add(a2.instantTranslation); bs2.setCenter(p1); if (checkCollision) { a2.collisionDetected = false; a2.clearVeryNear(); } if (bs1.intersect(bs2)) { a1.veryNear(a2); a2.veryNear(a1); // Processs collision Indication if (checkCollision) { // collision indicator. // If one of them can be traversed there is no collision. if (!((a1.canBeTraversed)|| (a2.canBeTraversed))){ a1.collisionDetected = true; a2.collisionDetected = true; } } // Process collision impact. if (computeImpact){ computeAgentAgentImpact(a1, a2, bs1, bs2); a1.interactionDetected = true; a2.interactionDetected = true; } } } } } } } /** * Check all agents/static objetc pairs and verify physical interactions and/or collision. */ protected void checkAgentObjectPairs(ArrayList<SimpleAgent> agents, ArrayList<Object> objects, boolean computeInteraction,boolean checkCollision) { int nobjs = objects.size(); int nagents = agents.size(); // At least two agents for (int i = 0; i < nagents; i++) { SimpleAgent a1 = ((SimpleAgent) agents.get(i)); // because there are no other transform above // translation // we can use it directly and not localToVWorld // But this works only on non oriented bounds (spheres). if (checkCollision) a1.collisionDetected = false; bs1.set(a1.getBounds()); bs1.transform(a1.translation); // add predicted displacement bs1.getCenter(p1); p1.add(a1.instantTranslation); bs1.setCenter(p1); // Check each agent / objects couple (once) for (int j = 0; j < nobjs; j++) { Object o = objects.get(j); if (o instanceof StaticObject) { // ensure the object can't be traversed if (!((StaticObject) o).getCanBeTraversed()) { if (intersect(bs1, (StaticObject) o)) { if (checkCollision) a1.collisionDetected = true; if (computeInteraction){ computeAgentObjectImpact(a1,(StaticObject)o,bs1); } } } } } } } /** experimental */ private void computeAgentAgentImpact(SimpleAgent a1, SimpleAgent a2, BoundingSphere bs1, BoundingSphere bs2) { Vector3d n = v1; Vector3d vrel = v2; double coefficientOfRestitution = 1.0; // coefficient of restitution bs1.getCenter(p1); bs2.getCenter(p2); // compute normal of a2 surface (simple 2 spheres case) p3.sub(p1, p2); n.set(p3); n.normalize(); // relative velocity vrel.sub(a1.linearVelocity, a2.linearVelocity); double ndotvrel = n.dot(vrel); if (ndotvrel <= 0) { // Compute impulse magnitude in the normal direction // j = (-(1+epsilon)vrel.n ) / (n.n (1/a1_mass + 1/a2_mass)) double num = -(1 + coefficientOfRestitution) * n.dot(vrel); double denum = 1 / a1.mass + 1 / a2.mass; double j = num / denum; // update a1 velocity // Va1 = Va1 + (j/a1_mass) (n) v2.set(n); v2.scale(j / (a1.mass)); a1.linearVelocity.add(v2); // update a2 velocity // Va1 = Va1 - (j/a2_mass)(n) v2.set(n); v2.scale(j / (a2.mass)); a2.linearVelocity.sub(v2); } } /** experimental */ private void computeAgentObjectImpact(SimpleAgent a, StaticObject o, BoundingSphere bsa ) { Vector3d n = v5; double coefficientOfRestitution = 1.0; // Compute normalized normal and contact point computeContactNormal(bsa,(BoundingBox)o.getBounds(),n); // velocity component in normal direction double ndotva = n.dot(a.linearVelocity); // System.out.println("ndotva " + ndotva ); if (ndotva <= 0) { // Compute impulse magnitude in the normal direction // j = (-(1+epsilon)vrel.n ) / (n.n (1/a_mass + 1/o_mass)) // And Va = Va + (j/a_mass)(n) // but o_mass is infinite and o is static // and a_mass can be canceled thus: // j = (-(1+epsilon)avel.n ) // And Va = Va + jn double j = -(1 + coefficientOfRestitution ) * ndotva ; v2.set(n); v2.scale(j); a.linearVelocity.add(v2); // System.out.println("add " + v2.toString() +" normal "+n.toString()); } } protected boolean intersect(BoundingSphere bs, StaticObject obj) { return obj.intersect(bs); } /** */ protected boolean intersect(BoundingSphere bs, BoundingBox bb) { return (bb.intersect(bs)); /* * double radiussq = bs.getRadius()*bs.getRadius(); bb.getLower(p1); * bb.getUpper(p2); bs.getCenter(p3); double xmin = Math.min(p1.x,p2.x); * double ymin = Math.min(p1.y,p2.y); double zmin = Math.min(p1.z,p2.z); * double xmax = Math.max(p1.x,p2.x); double ymax = Math.max(p1.y,p2.y); * double zmax = Math.max(p1.z,p2.z); * * double dmin = 0; if (p3.x < xmin) dmin += (p3.x - xmin)*(p3.x - * xmin); else if (p3.x >xmax ) dmin += (p3.x - xmax)*(p3.x - xmax); * * if (p3.y < ymin) dmin += (p3.y - ymin)*(p3.y - ymin); else if (p3.y * >ymax ) dmin += (p3.y - ymax)*(p3.y - ymax); * * if (p3.z < zmin) dmin += (p3.z - zmin)*(p3.z - zmin); else if (p3.z * >zmax ) dmin += (p3.z - zmax)*(p3.z - zmax); * * return ( dmin <= radiussq ); */ } protected void computeContactNormal(BoundingSphere bs, BoundingBox bb,Vector3d n){ // NEEDS : BB is Axis Aligned !!!!! bb.getLower(p1); bb.getUpper(p2); bs.getCenter(p3); // pour chaque face // normal de la face // projete centre sphere sur normale // retourne normale ayant longueur proejectio + petite //double sqradius = bs.getRadius() * bs.getRadius(); double p; double min = Double.MAX_VALUE; // six faces p = projNormal(p1.x,p1.y,p1.z, p1.x,p2.y,p1.z, p2.x,p1.y,p1.z); if (p < min) { min = p; n.set(v3);} p = projNormal(p2.x,p1.y,p1.z ,p2.x,p2.y,p1.z, p2.x,p1.y,p2.z); if (p < min) { min = p; n.set(v3);} /* p = projNormal(p2.x,p1.y,p2.z, p2.x,p2.y,p2.z, p1.x,p1.y,p2.z); if (p < min) { min = p; n.set(v3);}*/ /* p = projNormal(p1.x,p1.y,p2.z, p1.x,p2.y,p2.z, p1.x,p1.y,p1.z); if (p < min) { min = p; n.set(v3);}*/ /* p = projNormal(p1.x,p2.y,p1.z, p1.x,p2.y,p2.z, p2.x,p2.y,p1.z); if (p < min) { min = p; n.set(v3);} p = projNormal(p1.x,p1.y,p2.z, p1.x,p1.y,p1.z, p2.x,p1.y,p2.z); if (p < min) { min = p; n.set(v3);} */ } protected double projNormal(double x1,double y1,double z1,double x2,double y2,double z2,double x3,double y3,double z3) { // Normal = (P2 - P1) ^ (P3 -P1) = V3 v1.set(x1,y1,z1); v2.set(x2,y2,z2); v3.set(x3,y3,z3); v2.sub(v1);v3.sub(v1); v4.sub(p3,v1); v3.cross(v2,v3); v3.normalize(); return Math.abs(v3.dot(v4)); } }