Использование объекта EllipsoidCollider для определения столкновений
Материал из AlternativaPlatform Wiki
API Alternativa3D 7.7 предоставляет функционал для определения столкновений трехмерных объектов и обработки результатов этих столкновений. В данном уроке мы исследуем эти возможности. Для этого мы используем шаблон и нам потребуются некоторые знания из урока «Пересечение луча».
Итак, берем исходникpackage { import alternativa.engine3d.containers.ConflictContainer; import alternativa.engine3d.controllers.SimpleObjectController; import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.View; import flash.display.Sprite; import flash.events.Event; [SWF(width="640", height="480", backgroundColor="#404060")] public class Template extends Sprite { private var container:ConflictContainer; private var camera:Camera3D; private var controller:SimpleObjectController; public function Template() { super(); container = new ConflictContainer(); camera = new Camera3D(); camera.z = 500; container.addChild(camera); controller = new SimpleObjectController(stage, camera, 200); controller.lookAtXYZ(200, 0, 0); camera.view = new View(640, 480); addChild(camera.view); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { controller.update(); camera.render(); } } }
Добавим диаграмму для контроля состояния нашей системы
addChild(camera.diagram);
что, к слову, вовсе не обязательно. Но так мы сможем видеть насколько эффективно происходят все вычисления. Установим частоту кадров нашего ролика в 60
[SWF(width="640", height="480", frameRate="60", backgroundColor="#000000")]
что тоже весьма опционально. Изменим немного положение нашей камеры, чтобы сцену было видно полнее.
camera.y = 300; camera.z = 300; controller.lookAtXYZ(0, 0, 0);
Все подготовительные действия совершены. Давайте приступим к столкновениям. Перво-наперво определим общую переменную класса private var collider:EllipsoidCollider, в которой будет содержаться ссылка на объект вычисляющий столкновения и в конструкторе корневого класса создадим экземпляр, размером, совпадающий с нашими сферами:
collider = new EllipsoidCollider(RADIUS, RADIUS, RADIUS);
Зададим размер глобально
private const RADIUS:uint = 25;
Создадим необходимую нам геометрию: две сферы и плоскость
private var flat:Plane; private var ball1:Sphere; private var ball2:Sphere;
в определении корневого класса и
flat = new Plane(500, 500, 10, 10, false); flat.setMaterialToAllFaces(new FillMaterial(0x111111, 1, 0, 0x666666)); flat.z = -RADIUS; flat.addEventListener(MouseEvent3D.CLICK, onFlatClick); container.addChild(flat); ball1 = new Sphere(RADIUS, 16); ball1.setMaterialToAllFaces(colors["blue"]); container.addChild(ball1); ball2 = new Sphere(RADIUS, 16); ball2.setMaterialToAllFaces(colors["green"]); ball2.visible = false; container.addChild(ball2);
в конструкторе. Надо отметить, что плоскость flat, на которой располагаются наши сферы мы отпустим вниз на величину радиуса сфер flat.z = -RADIUS; - понятно для чего. Также назначим плоскости обработчик события клика по ней onFlatClick, в котором мы будем фиксировать координаты точки клика мыши по плоскости:
var data:RayIntersectionData = flat.intersectRay(e.localOrigin, e.localDirection); if (!data) return; clickPlace = data.point;
Как это работает и почему - смотрите в уроке «Пересечение луча».
Еще хочу отметить, что в конструкторе мы выключаем вторую сферу для отображения и в определении класса создаем заранее 3 простых материала типа FillMaterial для раскрашивания сфер в нужные цвета:
private var colors:Object = { "red": new FillMaterial(0xff0000), "green": new FillMaterial(0x00ff00), "blue": new FillMaterial(0x0000ff) };
Наше приложение каждый кадр 60 раз в секунду в функции-обработчике события Event.ENTER_FRAME перерисовывает сцену и должно менять положение объектов, требующих анимации. Анимировать мы будем вторую сферу. Алгоритм будет прост. После клика пользователя на плоскости, где расположены сферы, вторая сфера появляется в точке клика (а её мы определили выше в обработчике onFlatClick) и начинает перемещаться в центр сцены, где расположена первая статичная сфера. Если вторая сфера сталкивается с первой, то она останавливается и окрашивается в красный цвет.
if (clickPlace) { ball2.x = clickPlace.x; ball2.y = clickPlace.y; ball2.setMaterialToAllFaces(colors["green"]); ball2.visible = true; clickPlace = null; } var angle:Number = Math.atan2(ball2.y, ball2.x); var delta:Vector3D = new Vector3D(SPEED * Math.cos(angle), SPEED * Math.sin(angle)); var oldPos:Vector3D = new Vector3D(ball2.x, ball2.y, ball2.z); var newPos:Vector3D = collider.calculateDestination(oldPos, delta, ball1); ball2.x = newPos.x; ball2.y = newPos.y; if (collider.getCollision(oldPos, delta, new Vector3D(), new Vector3D(), ball1)) ball2.setMaterialToAllFaces(colors["red"]);
Как видите, если зарегистрирована точка клика clickPlace, то сфера помещается в эту точку, становится видимой и окрашивается в зеленый цвет. Далее мы вычисляем все необходимые нам для перемещения второй сферы параметры: угол сферы относительно центра сферы angle, приращение координат delta при перемещении, старое местоположение oldPos и новое newPos. Причем, новое местоположение сферы вычисляет нам объект collider методом calculateDestination. Этот же объект следит за столкновением геометрии ball1 и ball2 в методе getCollision.
Вот код всего урока:
package { import alternativa.engine3d.containers.ConflictContainer; import alternativa.engine3d.controllers.SimpleObjectController; import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.View; import alternativa.engine3d.core.EllipsoidCollider; import alternativa.engine3d.core.MouseEvent3D; import alternativa.engine3d.core.RayIntersectionData; import alternativa.engine3d.primitives.Plane; import alternativa.engine3d.primitives.Sphere; import alternativa.engine3d.materials.FillMaterial; import flash.display.Sprite; import flash.events.Event; import flash.geom.Vector3D; [SWF(width="640", height="480", frameRate="60", backgroundColor="#000000")] public class EllipsoidColliderTest extends Sprite { private var container:ConflictContainer; private var camera:Camera3D; private var controller:SimpleObjectController; private const SPEED:Number = -2; private const RADIUS:uint = 25; private var flat:Plane; private var ball1:Sphere; private var ball2:Sphere; private var clickPlace:Vector3D; private var collider:EllipsoidCollider; private var colors:Object = { "red": new FillMaterial(0xff0000), "green": new FillMaterial(0x00ff00), "blue": new FillMaterial(0x0000ff) }; public function EllipsoidColliderTest () { container = new ConflictContainer(); camera = new Camera3D(); camera.y = 300; camera.z = 300; camera.view = new View(640, 480); addChild(camera.view); addChild(camera.diagram); container.addChild(camera); collider = new EllipsoidCollider(RADIUS, RADIUS, RADIUS); controller = new SimpleObjectController(stage, camera, 200); controller.lookAtXYZ(0, 0, 0); flat = new Plane(500, 500, 10, 10, false); flat.setMaterialToAllFaces(new FillMaterial(0x111111, 1, 0, 0x666666)); flat.z = -RADIUS; flat.addEventListener(MouseEvent3D.CLICK, onFlatClick); container.addChild(flat); ball1 = new Sphere(RADIUS, 16); ball1.setMaterialToAllFaces(colors["blue"]); container.addChild(ball1); ball2 = new Sphere(RADIUS, 16); ball2.setMaterialToAllFaces(colors["green"]); ball2.visible = false; container.addChild(ball2); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { if (clickPlace) { ball2.x = clickPlace.x; ball2.y = clickPlace.y; ball2.setMaterialToAllFaces(colors["green"]); ball2.visible = true; clickPlace = null; } var angle:Number = Math.atan2(ball2.y, ball2.x); var delta:Vector3D = new Vector3D(SPEED * Math.cos(angle), SPEED * Math.sin(angle)); var oldPos:Vector3D = new Vector3D(ball2.x, ball2.y, ball2.z); var newPos:Vector3D = collider.calculateDestination(oldPos, delta, ball1); ball2.x = newPos.x; ball2.y = newPos.y; if (collider.getCollision(oldPos, delta, new Vector3D(), new Vector3D(), ball1)) ball2.setMaterialToAllFaces(colors["red"]); controller.update(); camera.render(); } private function onFlatClick (e:MouseEvent3D):void { var data:RayIntersectionData = flat.intersectRay(e.localOrigin, e.localDirection); if (!data) return; clickPlace = data.point; } } }