package com.alternativagame.engine3d { import com.alternativagame.type.Vector; import flash.geom.Point; public final class Math3D { static private var toRad:Number = Math.PI/180; static private var toDeg:Number = 180/Math.PI; // Перевести в радианы static public function toRadian(n:Number):Number { return n*toRad; } // Перевести в градусы static public function toDegree(n:Number):Number { return n*toDeg; } // Перевести значение градуса в пределы -180..180 static public function limitAngle(n:Number):Number { var res:Number = n % 360; res = (res > 0) ? ((res > 180) ? (res - 360) : res) : ((res < -180) ? (res + 360) : res); return res; } // Кратчайшая разница углов (углы должны быть лимитированы) static public function deltaAngle(a:Number, b:Number):Number { var delta:Number = b - a; if (delta > 180) { return delta - 360; } else { if (delta < -180) { return delta + 360; } else { return delta; } } } static public function random(... args):Number { if (args.length == 0) { return Math.random(); } else { if (args.length == 1) { return Math.random()*args[0]; } else { return Math.random()*(args[1]-args[0])+args[0]; } } } // Длина вектора static public function vectorLength(v:Vector):Number { return Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z); } // Длина вектора static public function vectorLengthSquare(v:Vector):Number { return v.x*v.x + v.y*v.y + v.z*v.z; } // Нормализовать вектор static public function normalize(v:Vector):void { var n:Number = vectorLength(v); if (n !== 0) { v.x /= n; v.y /= n; v.z /= n; } else { v.x = 0; v.y = 0; v.z = 0; } } // Сложение векторов static public function vectorAdd(v1:Vector, v2:Vector):Vector { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); } // Вычитание векторов static public function vectorSub(v1:Vector, v2:Vector):Vector { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } // Умножить вектор на скаляр static public function vectorMultiply(v:Vector, n:Number):Vector { return new Vector(v.x*n, v.y*n, v.z*n); } // Скалярное произведение векторов static public function vectorDot(v1:Vector, v2:Vector):Number { return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); } // Векторное произведение векторов static public function vectorCross(v1:Vector, v2:Vector):Vector { return new Vector(v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x); } // Угол между векторами static public function vectorAngle(v1:Vector, v2:Vector):Number { var len:Number = vectorLength(v1)*vectorLength(v2); // Если один из векторов нулевой, угол - 0 градусов var cos:Number = (len != 0) ? (vectorDot(v1, v2) / len) : 1; return Math.acos(cos); } // Угол между векторами (работает только если векторы нормализованы) static public function vectorAngleFast(v1:Vector, v2:Vector):Number { return Math.acos(vectorDot(v1, v2)); } // Отбрасывает дробную часть у координат вектора static public function vectorFloor(v:Vector):Vector { return new Vector(Math.floor(v.x), Math.floor(v.y), Math.floor(v.z)); } // Сравнение векторов с погрешностью static public function vectorEquals(v1:Vector, v2:Vector, delta:Number = 0):Boolean { var d:Vector = vectorSub(v1, v2); return (Math.abs(d.x) <= delta) && (Math.abs(d.y) <= delta) && (Math.abs(d.z) <= delta); } // Нахождение нормали грани static public function normal(points:Array):Vector { var v1:Vector = new Vector(points[1].x - points[0].x, points[1].y - points[0].y, points[1].z - points[0].z); var v2:Vector = new Vector(points[1].x - points[2].x, points[1].y - points[2].y, points[1].z - points[2].z); var res:Vector = vectorCross(v1, v2); normalize(res); return res; } // Перенос матрицы static public function translateMatrix(m:Matrix3D, x:Number, y:Number, z:Number):void { m.d += x; m.h += y; m.l += z; } // Масштабирование матрицы static public function scaleMatrix(m:Matrix3D, x:Number, y:Number, z:Number):void { m.a *= x; m.b *= x; m.c *= x; m.d *= x; m.e *= y; m.f *= y; m.g *= y; m.h *= y; m.i *= z; m.j *= z; m.k *= z; m.l *= z; } // Масштабирование матрицы по X static public function scaleXMatrix(m:Matrix3D, value:Number):void { m.a *= value; m.b *= value; m.c *= value; m.d *= value; } // Масштабирование матрицы по Y static public function scaleYMatrix(m:Matrix3D, value:Number):void { m.e *= value; m.f *= value; m.g *= value; m.h *= value; } // Масштабирование матрицы по Z static public function scaleZMatrix(m:Matrix3D, value:Number):void { m.i *= value; m.j *= value; m.k *= value; m.l *= value; } // Поворот матрицы static public function rotateMatrix(m:Matrix3D, x:Number, y:Number, z:Number):void { var xRadian:Number = Math3D.toRadian(x); var yRadian:Number = Math3D.toRadian(y); var zRadian:Number = Math3D.toRadian(z); var cosX:Number = Math.cos(xRadian); var sinX:Number = Math.sin(xRadian); var cosY:Number = Math.cos(yRadian); var sinY:Number = Math.sin(yRadian); var cosZ:Number = Math.cos(zRadian); var sinZ:Number = Math.sin(zRadian); var a:Number = m.a; var b:Number = m.b; var c:Number = m.c; var d:Number = m.d; var e:Number = m.e; var f:Number = m.f; var g:Number = m.g; var h:Number = m.h; var i:Number = m.i; var j:Number = m.j; var k:Number = m.k; var l:Number = m.l; var cosZsinY:Number = cosZ*sinY; var sinZsinY:Number = sinZ*sinY; var ra:Number = cosZ*cosY; var rb:Number = cosZsinY*sinX - sinZ*cosX; var rc:Number = cosZsinY*cosX + sinZ*sinX; var re:Number = sinZ*cosY; var rf:Number = sinZsinY*sinX + cosZ*cosX; var rg:Number = sinZsinY*cosX - cosZ*sinX; var ri:Number = -sinY; var rj:Number = cosY*sinX; var rk:Number = cosY*cosX; m.a = ra*a + rb*e + rc*i; m.b = ra*b + rb*f + rc*j; m.c = ra*c + rb*g + rc*k; m.d = ra*d + rb*h + rc*l; m.e = re*a + rf*e + rg*i; m.f = re*b + rf*f + rg*j; m.g = re*c + rf*g + rg*k; m.h = re*d + rf*h + rg*l; m.i = ri*a + rj*e + rk*i; m.j = ri*b + rj*f + rk*j; m.k = ri*c + rj*g + rk*k; m.l = ri*d + rj*h + rk*l; } // Поворот матрицы вдоль X static public function rotateXMatrix(m:Matrix3D, angle:Number):void { var angleRadian:Number = Math3D.toRadian(angle); var sin:Number = Math.sin(angleRadian); var cos:Number = Math.cos(angleRadian); var e:Number = m.e; var f:Number = m.f; var g:Number = m.g; var h:Number = m.h; var i:Number = m.i; var j:Number = m.j; var k:Number = m.k; var l:Number = m.l; m.e = cos*e - sin*i; m.f = cos*f - sin*j; m.g = cos*g - sin*k; m.h = cos*h - sin*l; m.i = sin*e + cos*i; m.j = sin*f + cos*j; m.k = sin*g + cos*k; m.l = sin*h + cos*l; } // Поворот матрицы вдоль Y static public function rotateYMatrix(m:Matrix3D, angle:Number):void { var angleRadian:Number = Math3D.toRadian(angle); var sin:Number = Math.sin(angleRadian); var cos:Number = Math.cos(angleRadian); var a:Number = m.a; var b:Number = m.b; var c:Number = m.c; var d:Number = m.d; var i:Number = m.i; var j:Number = m.j; var k:Number = m.k; var l:Number = m.l; m.a = cos*a + sin*i; m.b = cos*b + sin*j; m.c = cos*c + sin*k; m.d = cos*d + sin*l; m.i = -sin*a + cos*i; m.j = -sin*b + cos*j; m.k = -sin*c + cos*k; m.l = -sin*d + cos*l; } // Поворот матрицы вдоль Z static public function rotateZMatrix(m:Matrix3D, angle:Number):void { var angleRadian:Number = Math3D.toRadian(angle); var sin:Number = Math.sin(angleRadian); var cos:Number = Math.cos(angleRadian); var a:Number = m.a; var b:Number = m.b; var c:Number = m.c; var d:Number = m.d; var e:Number = m.e; var f:Number = m.f; var g:Number = m.g; var h:Number = m.h; m.a = cos*a - sin*e; m.b = cos*b - sin*f; m.c = cos*c - sin*g; m.d = cos*d - sin*h; m.e = sin*a + cos*e; m.f = sin*b + cos*f; m.g = sin*c + cos*g; m.h = sin*d + cos*h; } // Умножение матриц static public function combineMatrix(m1:Matrix3D, m2:Matrix3D):Matrix3D { var res:Matrix3D = new Matrix3D(); res.a = m1.a*m2.a + m1.b*m2.e + m1.c*m2.i; res.b = m1.a*m2.b + m1.b*m2.f + m1.c*m2.j; res.c = m1.a*m2.c + m1.b*m2.g + m1.c*m2.k; res.d = m1.a*m2.d + m1.b*m2.h + m1.c*m2.l + m1.d; res.e = m1.e*m2.a + m1.f*m2.e + m1.g*m2.i; res.f = m1.e*m2.b + m1.f*m2.f + m1.g*m2.j; res.g = m1.e*m2.c + m1.f*m2.g + m1.g*m2.k; res.h = m1.e*m2.d + m1.f*m2.h + m1.g*m2.l + m1.h; res.i = m1.i*m2.a + m1.j*m2.e + m1.k*m2.i; res.j = m1.i*m2.b + m1.j*m2.f + m1.k*m2.j; res.k = m1.i*m2.c + m1.j*m2.g + m1.k*m2.k; res.l = m1.i*m2.d + m1.j*m2.h + m1.k*m2.l + m1.l; return res; } // Трансформация вектора через матрицу static public function vectorTransform(v:Vector, m:Matrix3D):Vector { var res:Vector = new Vector(); res.x = m.a*v.x + m.b*v.y + m.c*v.z + m.d; res.y = m.e*v.x + m.f*v.y + m.g*v.z + m.h; res.z = m.i*v.x + m.j*v.y + m.k*v.z + m.l; return res; } // Проверка на пересечение луча с заданным треугольником static public function tryangleIntersection(a:Vector, b:Vector, c:Vector, n:Vector, v1:Vector, v2:Vector): Vector { var res:Vector; var d:Number = -n.x*a.x - n.y*a.y - n.z*a.z; var v:Vector = new Vector(v2.x-v1.x, v2.y-v1.y, v2.z-v1.z); var nv:Number = (n.x*v.x + n.y*v.y + n.z*v.z); if (nv != 0) { //нахождение точки пересечения луча с плоскостью полигона var t:Number = -(d + n.x*v1.x + n.y*v1.y + n.z*v1.z)/nv; res = new Vector(v1.x + t*v.x,v1.y + t*v.y,v1.z + t*v.z); //проверка на попадание точки пересечения в заданный треугольник var uu:Number = ((res.x - a.x)*(c.y - a.y)-(c.x - a.x)*(res.y - a.y))/((b.x - a.x)*(c.y - a.y)-(c.x - a.x)*(b.y - a.y)); var vv:Number = (res.y - a.y - uu*(b.y - a.y))/(c.y - a.y); if (!(uu > 0 && vv > 0 && (1-uu-vv) > 0)) { //точка не лежит в треугольнике res = null; } } else { //луч параллелен плоскости res = null; } return res; } //проверка на прохождение луча (v1, v2) через сферу с центром (c) заданного радиуса (r) static public function sphereIntersection(c:Vector, r:Number, v1:Vector, v2:Vector):Vector { var res: Vector; var v: Vector = new Vector(v2.x-v1.x, v2.y-v1.y, v2.z-v1.z); var n: Vector = new Vector(-v.x,-v.y,-v.z); var d: Number = -n.x*c.x - n.y*c.y - n.z*c.z; var nv: Number = (n.x*v.x + n.y*v.y + n.z*v.z); // нахождение точки пересечения луча с плоскостью перпендикулярной лучу, // проходящей через центр сферы var t: Number = -(d + n.x*v1.x + n.y*v1.y + n.z*v1.z)/nv; res = new Vector(v1.x + t*v.x,v1.y + t*v.y,v1.z + t*v.z); //определение длины перпендикуляра var l: Number = Math.sqrt((res.x-c.x)*(res.x-c.x) + (res.y-c.y)*(res.y-c.y) + (res.z-c.z)*(res.z-c.z)); if (l > r) { res = null; } return res; } // Нахождение перпендикуляра к отрезку(v1, v2) из произвольной точки(c) static public function perpendicularToSegment(c:Vector, v1:Vector, v2:Vector):Vector { var res:Vector; var v:Vector = vectorSub(v2, v1); var n:Vector = new Vector(-v.x, -v.y, -v.z); var d:Number = -vectorDot(n, c); var nv:Number = vectorDot(n, v); var t:Number = -(d + vectorDot(n, v1))/nv; res = vectorAdd(v1, vectorMultiply(v, t)); return res; } // Расстояние от точки до ребра в плоскости камеры static public function segmentDistance(first:Point, second:Point, point:Point):Number { // Вектор ребра var dx:Number = second.x - first.x; var dy:Number = second.y - first.y; // Вектор точки var px:Number = point.x - first.x; var py:Number = point.y - first.y; // Векторное произведение (площадь параллелограмма) поделить на длину ребра return (dx*py - dy*px)/Math.sqrt(dx*dx + dy*dy); } // Попадает ли точка в 2D-треугольник static public function tryangleHasPoint(a:Point, b:Point, c:Point, point:Point):Boolean { if (vectorCross2D(c.subtract(a), point.subtract(a)) <= 0) { if (vectorCross2D(b.subtract(c), point.subtract(c)) <= 0) { if (vectorCross2D(a.subtract(b), point.subtract(b)) <= 0) { return true; } else { return false; } } else { return false; } } else { return false; } } static public function vectorCross2D(a:Point, b:Point):Number { return a.x*b.y - a.y*b.x; } } }