Files
alternativaphysics-archive/0.0.8.0/src/alternativa/physics/collision/colliders/.svn/text-base/BoxBoxCollider.as.svn-base
2024-10-05 12:31:02 +01:00

590 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package alternativa.physics.collision.colliders {
import __AS3__.vec.Vector;
import alternativa.physics.Contact;
import alternativa.physics.ContactPoint;
import alternativa.physics.altphysics;
import alternativa.physics.collision.CollisionPrimitive;
import alternativa.physics.collision.primitives.CollisionBox;
import alternativa.physics.math.Matrix4;
import alternativa.physics.math.Vector3;
use namespace altphysics;
/**
* Расчитывает точки контакта двух боксов. Нормаль контакта направляется в сторону бокса с меньшим ID.
*/
public class BoxBoxCollider extends BoxCollider {
private var epsilon:Number = 0.001;
private var vectorToBox1:Vector3 = new Vector3();
private var axis:Vector3 = new Vector3();
private var axis10:Vector3 = new Vector3();
private var axis11:Vector3 = new Vector3();
private var axis12:Vector3 = new Vector3();
private var axis20:Vector3 = new Vector3();
private var axis21:Vector3 = new Vector3();
private var axis22:Vector3 = new Vector3();
private var bestAxisIndex:int;
private var minOverlap:Number;
private var points1:Vector.<Vector3> = new Vector.<Vector3>(8, true);
private var points2:Vector.<Vector3> = new Vector.<Vector3>(8, true);
private var tmpPoints:Vector.<ContactPoint> = new Vector.<ContactPoint>(8, true);
private var pcount:int;
/**
*
*/
public function BoxBoxCollider() {
for (var i:int = 0; i < 8; i++) {
tmpPoints[i] = new ContactPoint();
points1[i] = new Vector3();
points2[i] = new Vector3();
}
}
/**
* Проверяет наличие пересечения примитивов. Если пересечение существует, заполняется информация о контакте.
*
* @param prim1 первый примитив
* @param prim2 второй примитив
* @param contact переменная, в которую записывается информация о контакте, если пересечение существует
* @return true, если пересечение существует, иначе false
*/
override public function getContact(prim1:CollisionPrimitive, prim2:CollisionPrimitive, contact:Contact):Boolean {
if (!haveCollision(prim1, prim2)) return false;
var box1:CollisionBox;
var box2:CollisionBox;
if (prim1.body != null) {
box1 = prim1 as CollisionBox;
box2 = prim2 as CollisionBox;
} else {
box1 = prim2 as CollisionBox;
box2 = prim1 as CollisionBox;
}
if (bestAxisIndex < 6) {
// Контакт грань-(грань|ребро|вершина)
if (!findFaceContactPoints(box1, box2, vectorToBox1, bestAxisIndex, contact)) return false;
} else {
// Контакт ребро-ребро
bestAxisIndex -= 6;
findEdgesIntersection(box1, box2, vectorToBox1, int(bestAxisIndex/3), bestAxisIndex%3, contact);
}
contact.body1 = box1.body;
contact.body2 = box2.body;
return true;
}
/**
* Выполняет быстрый тест на наличие пересечения двух примитивов.
*
* @param prim1 первый примитив
* @param prim2 второй примитив
* @return true, если пересечение существует, иначе false
*/
override public function haveCollision(prim1:CollisionPrimitive, prim2:CollisionPrimitive):Boolean {
minOverlap = 1e10;
var box1:CollisionBox;
var box2:CollisionBox;
if (prim1.body != null) {
box1 = prim1 as CollisionBox;
box2 = prim2 as CollisionBox;
} else {
box1 = prim2 as CollisionBox;
box2 = prim1 as CollisionBox;
}
var transform1:Matrix4 = box1.transform;
var transform2:Matrix4 = box2.transform;
// Вектор из центра второго бокса в центр первого
vectorToBox1.x = transform1.d - transform2.d;
vectorToBox1.y = transform1.h - transform2.h;
vectorToBox1.z = transform1.l - transform2.l;
// Проверка пересечения по основным осям
axis10.x = transform1.a;
axis10.y = transform1.e;
axis10.z = transform1.i;
if (!testMainAxis(box1, box2, axis10, 0, vectorToBox1)) return false;
axis11.x = transform1.b;
axis11.y = transform1.f;
axis11.z = transform1.j;
if (!testMainAxis(box1, box2, axis11, 1, vectorToBox1)) return false;
axis12.x = transform1.c;
axis12.y = transform1.g;
axis12.z = transform1.k;
if (!testMainAxis(box1, box2, axis12, 2, vectorToBox1)) return false;
axis20.x = transform2.a;
axis20.y = transform2.e;
axis20.z = transform2.i;
if (!testMainAxis(box1, box2, axis20, 3, vectorToBox1)) return false;
axis21.x = transform2.b;
axis21.y = transform2.f;
axis21.z = transform2.j;
if (!testMainAxis(box1, box2, axis21, 4, vectorToBox1)) return false;
axis22.x = transform2.c;
axis22.y = transform2.g;
axis22.z = transform2.k;
if (!testMainAxis(box1, box2, axis22, 5, vectorToBox1)) return false;
// Проверка производных осей
if (!testDerivedAxis(box1, box2, axis10, axis20, 6, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis10, axis21, 7, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis10, axis22, 8, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis11, axis20, 9, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis11, axis21, 10, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis11, axis22, 11, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis12, axis20, 12, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis12, axis21, 13, vectorToBox1)) return false;
if (!testDerivedAxis(box1, box2, axis12, axis22, 14, vectorToBox1)) return false;
return true;
}
/**
* Выполняет поиск точек контакта грани одного бокса с гранью/ребром/вершиной другого.
*
* @param box1 первый бокс
* @param box2 второй бокс
* @param vectorToBox1 вектор, направленный из центра второго бокса в центр первого
* @param faceAxisIdx индекс оси первого бокса, перпендикулярной грани, с которой произошло столкновение
* @param contactInfo структура, в которую записывается информация о точках контакта
*/
private function findFaceContactPoints(box1:CollisionBox, box2:CollisionBox, vectorToBox1:Vector3, faceAxisIdx:int, contact:Contact):Boolean {
var swapNormal:Boolean = false;
if (faceAxisIdx > 2) {
// Столкновение с гранью второго бокса. Для дальнейших расчётов боксы меняются местами,
// но нормаль контакта всё равно должна быть направлена в сторону первоначального box1
var tmpBox:CollisionBox = box1;
box1 = box2;
box2 = tmpBox;
vectorToBox1.x = -vectorToBox1.x;
vectorToBox1.y = -vectorToBox1.y;
vectorToBox1.z = -vectorToBox1.z;
faceAxisIdx -= 3;
swapNormal = true;
}
var transform1:Matrix4 = box1.transform;
var transform2:Matrix4 = box2.transform;
var colAxis:Vector3 = contact.normal;
transform1.getAxis(faceAxisIdx, colAxis);
var negativeFace:Boolean = colAxis.x*vectorToBox1.x + colAxis.y*vectorToBox1.y + colAxis.z*vectorToBox1.z > 0;
var code:int = 1 << (faceAxisIdx << 1);
if (negativeFace) {
code <<= 1;
}
if ((code & box1.excludedFaces) != 0) return false;
if (!negativeFace) {
colAxis.x = -colAxis.x;
colAxis.y = -colAxis.y;
colAxis.z = -colAxis.z;
}
// Ищем ось второго бокса, определяющую наиболее антипараллельную грань
var incidentAxisIdx:int = 0;
var incidentAxisDot:Number;
var maxDot:Number = 0;
for (var axisIdx:int = 0; axisIdx < 3; axisIdx++) {
transform2.getAxis(axisIdx, axis);
var dot:Number = axis.x*colAxis.x + axis.y*colAxis.y + axis.z*colAxis.z;
var absDot:Number = dot < 0 ? -dot : dot;
if (absDot > maxDot) {
maxDot = absDot;
incidentAxisDot = dot;
incidentAxisIdx = axisIdx;
}
}
// Получаем список вершин грани второго бокса, переводим их в систему координат первого бокса и выполняем обрезку
// по грани первого бокса. Таким образом получается список потенциальных точек контакта.
transform2.getAxis(incidentAxisIdx, axis);
getFaceVertsByAxis(box2.hs, incidentAxisIdx, incidentAxisDot < 0, points1);
// TODO: Вычислить результирующую матрицу, затем преобразовать векторы за один заход
transform2.transformVectorsN(points1, points2, 4);
transform1.transformVectorsInverseN(points2, points1, 4);
var pnum:int = clip(box1.hs, faceAxisIdx);
// Проверяем каждую потенциальную точку на принадлежность первому боксу и добавляем такие точки в список контактов
var pen:Number;
pcount = 0;
for (var i:int = 0; i < pnum; i++) {
var v:Vector3 = points1[i];
if ((pen = getPointBoxPenetration(box1.hs, v, faceAxisIdx, negativeFace)) > -epsilon) {
var cp:ContactPoint = tmpPoints[pcount++];
var cpPos:Vector3 = cp.pos;
cpPos.x = transform1.a*v.x + transform1.b*v.y + transform1.c*v.z + transform1.d;
cpPos.y = transform1.e*v.x + transform1.f*v.y + transform1.g*v.z + transform1.h;
cpPos.z = transform1.i*v.x + transform1.j*v.y + transform1.k*v.z + transform1.l;
var r:Vector3 = cp.r1;
r.x = cpPos.x - transform1.d;
r.y = cpPos.y - transform1.h;
r.z = cpPos.z - transform1.l;
r = cp.r2;
r.x = cpPos.x - transform2.d;
r.y = cpPos.y - transform2.h;
r.z = cpPos.z - transform2.l;
cp.penetration = pen;
}
}
if (swapNormal) {
colAxis.x = -colAxis.x;
colAxis.y = -colAxis.y;
colAxis.z = -colAxis.z;
}
if (pcount > 4) {
reducePoints();
}
for (i = 0; i < pcount; i++) {
cp = contact.points[i];
cp.copyFrom(tmpPoints[i]);
}
contact.pcount = pcount;
return true;
}
/**
*
* @param contactInfo
*/
private function reducePoints():void {
var i:int;
var minIdx:int;
var cp1:ContactPoint;
var cp2:ContactPoint;
while (pcount > 4) {
var minLen:Number = 1e10;
var p1:Vector3 = (tmpPoints[int(pcount - 1)] as ContactPoint).pos;
var p2:Vector3;
for (i = 0; i < pcount; i++) {
p2 = (tmpPoints[i] as ContactPoint).pos;
var dx:Number = p2.x - p1.x;
var dy:Number = p2.y - p1.y;
var dz:Number = p2.z - p1.z;
var len:Number = dx*dx + dy*dy + dz*dz;
if (len < minLen) {
minLen = len;
minIdx = i;
}
p1 = p2;
}
var ii:int = minIdx == 0 ? (pcount - 1) : (minIdx - 1);
cp1 = tmpPoints[ii];
cp2 = tmpPoints[minIdx];
p1 = cp1.pos;
p2 = cp2.pos;
p2.x = 0.5*(p1.x + p2.x);
p2.y = 0.5*(p1.y + p2.y);
p2.z = 0.5*(p1.z + p2.z);
cp2.penetration = 0.5*(cp1.penetration + cp2.penetration);
if (minIdx > 0) {
for (i = minIdx; i < pcount; i++) {
tmpPoints[int(i - 1)] = tmpPoints[i];
}
tmpPoints[int(pcount - 1)] = cp1;
}
pcount--;
}
}
/**
*
* @param hs
* @param p
* @param axisIndex
* @param reverse
* @return
*/
private function getPointBoxPenetration(hs:Vector3, p:Vector3, faceAxisIdx:int, reverse:Boolean):Number {
switch (faceAxisIdx) {
case 0:
if (reverse) return p.x + hs.x;
else return hs.x - p.x;
case 1:
if (reverse) return p.y + hs.y;
else return hs.y - p.y;
case 2:
if (reverse) return p.z + hs.z;
else return hs.z - p.z;
}
return 0;
}
/**
* Выполняет обрезку грани, заданной списком вершин в поле объекта verts1. Результат сохраняется в этом же поле.
*
* @param hs вектор половинных размеров бокса, гранью которого обрезается грань второго бокса
* @param faceAxisIdx индекс нормальной оси грани, по которой выполняется обрезка
* @return количество вершин, получившихся в результате обрезки грани, заданной вершинами в поле verts1
*/
private function clip(hs:Vector3, faceAxisIdx:int):int {
var pnum:int = 4;
switch (faceAxisIdx) {
case 0:
if ((pnum = clipLowZ(-hs.z, pnum, points1, points2, epsilon)) == 0) return 0;
if ((pnum = clipHighZ(hs.z, pnum, points2, points1, epsilon)) == 0) return 0;
if ((pnum = clipLowY(-hs.y, pnum, points1, points2, epsilon)) == 0) return 0;
return clipHighY(hs.y, pnum, points2, points1, epsilon);
case 1:
if ((pnum = clipLowZ(-hs.z, pnum, points1, points2, epsilon)) == 0) return 0;
if ((pnum = clipHighZ(hs.z, pnum, points2, points1, epsilon)) == 0) return 0;
if ((pnum = clipLowX(-hs.x, pnum, points1, points2, epsilon)) == 0) return 0;
return clipHighX(hs.x, pnum, points2, points1, epsilon);
case 2:
if ((pnum = clipLowX(-hs.x, pnum, points1, points2, epsilon)) == 0) return 0;
if ((pnum = clipHighX(hs.x, pnum, points2, points1, epsilon)) == 0) return 0;
if ((pnum = clipLowY(-hs.y, pnum, points1, points2, epsilon)) == 0) return 0;
return clipHighY(hs.y, pnum, points2, points1, epsilon);
}
return 0;
}
/**
* Вычисляет точку столкновения рёбер двух боксов.
*
* @param box1 первый бокс
* @param box2 второй бокс
* @param vectorToBox1 вектор, направленный из центра второго бокса в центр первого
* @param axisIdx1 индекс направляющей оси ребра первого бокса
* @param axisIdx2 индекс направляющей оси ребра второго бокса
* @param contactInfo структура, в которую записывается информация о столкновении
*/
private function findEdgesIntersection(box1:CollisionBox, box2:CollisionBox, vectorToBox1:Vector3, axisIdx1:int, axisIdx2:int, contact:Contact):void {
var transform1:Matrix4 = box1.transform;
var transform2:Matrix4 = box2.transform;
transform1.getAxis(axisIdx1, axis10);
transform2.getAxis(axisIdx2, axis20);
var colAxis:Vector3 = contact.normal;
colAxis.x = axis10.y*axis20.z - axis10.z*axis20.y;
colAxis.y = axis10.z*axis20.x - axis10.x*axis20.z;
colAxis.z = axis10.x*axis20.y - axis10.y*axis20.x;
var k:Number = 1/Math.sqrt(colAxis.x*colAxis.x + colAxis.y*colAxis.y + colAxis.z*colAxis.z);
colAxis.x *= k;
colAxis.y *= k;
colAxis.z *= k;
// Разворот оси в сторону первого бокса
if (colAxis.x*vectorToBox1.x + colAxis.y*vectorToBox1.y + colAxis.z*vectorToBox1.z < 0) {
colAxis.x = -colAxis.x;
colAxis.y = -colAxis.y;
colAxis.z = -colAxis.z;
}
// Находим среднюю точку на каждом из пересекающихся рёбер
var halfLen1:Number;
var halfLen2:Number;
var tx:Number = box1.hs.x;
var ty:Number = box1.hs.y;
var tz:Number = box1.hs.z;
var x2:Number = box2.hs.x;
var y2:Number = box2.hs.y;
var z2:Number = box2.hs.z;
// x
if (axisIdx1 == 0) {
tx = 0;
halfLen1 = box1.hs.x;
} else {
if (colAxis.x*transform1.a + colAxis.y*transform1.e + colAxis.z*transform1.i > 0) {
tx = -tx;
}
}
if (axisIdx2 == 0) {
x2 = 0;
halfLen2 = box2.hs.x;
} else {
if (colAxis.x*transform2.a + colAxis.y*transform2.e + colAxis.z*transform2.i < 0) {
x2 = -x2;
}
}
// y
if (axisIdx1 == 1) {
ty = 0;
halfLen1 = box1.hs.y;
} else {
if (colAxis.x*transform1.b + colAxis.y*transform1.f + colAxis.z*transform1.j > 0) {
ty = -ty;
}
}
if (axisIdx2 == 1) {
y2 = 0;
halfLen2 = box2.hs.y;
} else {
if (colAxis.x*transform2.b + colAxis.y*transform2.f + colAxis.z*transform2.j < 0) {
y2 = -y2;
}
}
// z
if (axisIdx1 == 2) {
tz = 0;
halfLen1 = box1.hs.z;
} else {
if (colAxis.x*transform1.c + colAxis.y*transform1.g + colAxis.z*transform1.k > 0) {
tz = -tz;
}
}
if (axisIdx2 == 2) {
z2 = 0;
halfLen2 = box2.hs.z;
} else {
if (colAxis.x*transform2.c + colAxis.y*transform2.g + colAxis.z*transform2.k < 0) {
z2 = -z2;
}
}
// Получаем глобальные координаты средних точек рёбер
var x1:Number = transform1.a*tx + transform1.b*ty + transform1.c*tz + transform1.d;
var y1:Number = transform1.e*tx + transform1.f*ty + transform1.g*tz + transform1.h;
var z1:Number = transform1.i*tx + transform1.j*ty + transform1.k*tz + transform1.l;
tx = x2;
ty = y2;
tz = z2;
x2 = transform2.a*tx + transform2.b*ty + transform2.c*tz + transform2.d;
y2 = transform2.e*tx + transform2.f*ty + transform2.g*tz + transform2.h;
z2 = transform2.i*tx + transform2.j*ty + transform2.k*tz + transform2.l;
// Находим точку пересечения рёбер, решая систему уравнений
k = axis10.x*axis20.x + axis10.y*axis20.y + axis10.z*axis20.z;
var det:Number = k*k - 1;
tx = x2 - x1;
ty = y2 - y1;
tz = z2 - z1;
var c1:Number = axis10.x*tx + axis10.y*ty + axis10.z*tz;
var c2:Number = axis20.x*tx + axis20.y*ty + axis20.z*tz;
var t1:Number = (c2*k - c1)/det;
var t2:Number = (c2 - c1*k)/det;
// Запись данных о столкновении
contact.pcount = 1;
var cp:ContactPoint = contact.points[0];
var cpPos:Vector3 = cp.pos;
// Точка столкновения вычисляется как среднее между ближайшими точками на рёбрах
cp.pos.x = 0.5*(x1 + axis10.x*t1 + x2 + axis20.x*t2);
cp.pos.y = 0.5*(y1 + axis10.y*t1 + y2 + axis20.y*t2);
cp.pos.z = 0.5*(z1 + axis10.z*t1 + z2 + axis20.z*t2);
var r:Vector3 = cp.r1;
r.x = cpPos.x - transform1.d;
r.y = cpPos.y - transform1.h;
r.z = cpPos.z - transform1.l;
r = cp.r2;
r.x = cpPos.x - transform2.d;
r.y = cpPos.y - transform2.h;
r.z = cpPos.z - transform2.l;
cp.penetration = minOverlap;
}
/**
* Проверяет пересечение боксов вдоль заданной оси. При наличии пересечения сохраняется глубина пересечения, если она минимальна.
*
* @param box1
* @param box2
* @param axis
* @param axisIndex
* @param toBox1
* @param bestAxis
* @return true в случае, если проекции боксов на заданную ось пересекаются, иначе false
*/
private function testMainAxis(box1:CollisionBox, box2:CollisionBox, axis:Vector3, axisIndex:int, toBox1:Vector3):Boolean {
var overlap:Number = overlapOnAxis(box1, box2, axis, toBox1);
if (overlap < -epsilon) return false;
if (overlap + epsilon < minOverlap) {
minOverlap = overlap;
bestAxisIndex = axisIndex;
}
return true;
}
/**
*
* @param box1
* @param box2
* @param axis1
* @param axis2
* @param axisIndex
* @param toBox1
* @return
*/
private function testDerivedAxis(box1:CollisionBox, box2:CollisionBox, axis1:Vector3, axis2:Vector3, axisIndex:int, toBox1:Vector3):Boolean {
// axis = axis1 cross axis2
axis.x = axis1.y*axis2.z - axis1.z*axis2.y;
axis.y = axis1.z*axis2.x - axis1.x*axis2.z;
axis.z = axis1.x*axis2.y - axis1.y*axis2.x;
var lenSqr:Number = axis.x*axis.x + axis.y*axis.y + axis.z*axis.z;
if (lenSqr < 0.0001) return true;
var k:Number = 1/Math.sqrt(lenSqr);
axis.x *= k;
axis.y *= k;
axis.z *= k;
var overlap:Number = overlapOnAxis(box1, box2, axis, toBox1);
if (overlap < -epsilon) return false;
if (overlap + epsilon < minOverlap) {
minOverlap = overlap;
bestAxisIndex = axisIndex;
}
return true;
}
/**
* Вычисляет глубину перекрытия двух боксов вдоль заданной оси.
*
* @param box1 первый бокс
* @param box2 второй бокс
* @param axis ось
* @param vectorToBox1 вектор, соединяющий центр второго бокса с центром первого
* @return величина перекрытия боксов вдоль оси
*/
public function overlapOnAxis(box1:CollisionBox, box2:CollisionBox, axis:Vector3, vectorToBox1:Vector3):Number {
var m:Matrix4 = box1.transform;
var d:Number = (m.a*axis.x + m.e*axis.y + m.i*axis.z)*box1.hs.x;
if (d < 0) d = -d;
var projection:Number = d;
d = (m.b*axis.x + m.f*axis.y + m.j*axis.z)*box1.hs.y;
if (d < 0) d = -d;
projection += d;
d = (m.c*axis.x + m.g*axis.y + m.k*axis.z)*box1.hs.z;
if (d < 0) d = -d;
projection += d;
m = box2.transform;
d = (m.a*axis.x + m.e*axis.y + m.i*axis.z)*box2.hs.x;
if (d < 0) d = -d;
projection += d;
d = (m.b*axis.x + m.f*axis.y + m.j*axis.z)*box2.hs.y;
if (d < 0) d = -d;
projection += d;
d = (m.c*axis.x + m.g*axis.y + m.k*axis.z)*box2.hs.z;
if (d < 0) d = -d;
projection += d;
d = vectorToBox1.x*axis.x + vectorToBox1.y*axis.y + vectorToBox1.z*axis.z;
if (d < 0) d = -d;
return projection - d;
}
}
}
import alternativa.physics.math.Vector3;
class CollisionPointTmp {
public var pos:Vector3 = new Vector3();
public var penetration:Number;
public var feature1:int;
public var feature2:int;
}