diff --git a/Alternativa3D5/5.3/alternativa/Alternativa3D.as b/Alternativa3D5/5.3/alternativa/Alternativa3D.as new file mode 100644 index 0000000..757de45 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: версия.подверсия.сборка + */ + public static const version:String = "5.3.0"; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.3/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.3/alternativa/engine3d/controllers/CameraController.as new file mode 100644 index 0000000..d6f8d41 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/controllers/CameraController.as @@ -0,0 +1,888 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.physics.SphereCollider; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.utils.Dictionary; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием + * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены. + */ + public class CameraController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; +// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; +// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения угла тангажа. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш уменьшения угла тангажа. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево). + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо). + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + + // флаги действий + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _up:Boolean; + private var _down:Boolean; + private var _pitchUp:Boolean; + private var _pitchDown:Boolean; + private var _yawLeft:Boolean; + private var _yawRight:Boolean; + private var _accelerate:Boolean; + + private var _moveLocal:Boolean = true; + + // Флаг включения управления камерой + private var _controlsEnabled:Boolean = false; + + // Значение таймера в начале прошлого кадра + private var lastFrameTime:uint; + + // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов. + private var _mouseSensitivity:Number = 1; + // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + private var _mousePitch:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по тангажу + private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch; + // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + private var _mouseYaw:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по расканью + private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw; + + // Вспомогательные переменные для обзора мышью + private var mouseLookActive:Boolean; + private var startDragCoords:Point = new Point(); + private var currentDragCoords:Point = new Point(); + private var prevDragCoords:Point = new Point(); + private var startRotX:Number; + private var startRotZ:Number; + + // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры + private var _pitchSpeed:Number = 1; + // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры + private var _yawSpeed:Number = 1; + // Скорость изменения крена в радианах за секунду при управлении с клавиатуры +// public var bankSpeed:Number = 2; +// private var bankMatrix:Matrix3D = new Matrix3D(); + + // Скорость поступательного движения в единицах за секунду + private var _speed:Number = 100; + // Коэффициент увеличения скорости при соответствующей нажатой клавише + private var _speedMultiplier:Number = 2; + + private var velocity:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + + private var _fovStep:Number = Math.PI / 180; + private var _zoomMultiplier:Number = 0.1; + + // Привязка клавиш к действиям + private var keyBindings:Map = new Map(); + // Привязка действий к обработчикам + private var actionBindings:Map = new Map(); + + // Источник событий клавиатуры и мыши + private var _eventsSource:DisplayObject; + // Управляемая камера + private var _camera:Camera3D; + + // Класс реализации определния столкновений + private var _collider:SphereCollider; + // Флаг необходимости проверки столкновений + private var _checkCollisions:Boolean; + // Радиус сферы для определения столкновений + private var _collisionRadius:Number = 0; + // Набор исключаемых из проверки столкновений объектов + private var _collisionIgnoreSet:Set = new Set(true); + // Флаг движения + private var _isMoving:Boolean; + + private var _onStartMoving:Function; + private var _onStopMoving:Function; + + /** + * Создание экземпляра контроллера. + * + * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры + */ + public function CameraController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = forward; + actionBindings[ACTION_BACK] = back; + actionBindings[ACTION_LEFT] = left; + actionBindings[ACTION_RIGHT] = right; + actionBindings[ACTION_UP] = up; + actionBindings[ACTION_DOWN] = down; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
CONTROLACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
+ */ + public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.CONTROL, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + } + + /** + * Направление камеры на точку. + * + * @param point координаты точки направления камеры + */ + public function lookAt(point:Point3D):void { + if (_camera == null) { + return; + } + var dx:Number = point.x - _camera.x; + var dy:Number = point.y - _camera.y; + var dz:Number = point.z - _camera.z; + _camera.rotationZ = -Math.atan2(dx, dy); + _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90; + } + + /** + * Callback-функция, вызываемая при начале движения камеры. + */ + public function get onStartMoving():Function { + return _onStartMoving; + } + + /** + * @private + */ + public function set onStartMoving(value:Function):void { + _onStartMoving = value; + } + + /** + * Callback-функция, вызываемая при прекращении движения камеры. + */ + public function get onStopMoving():Function { + return _onStopMoving; + } + + /** + * @private + */ + public function set onStopMoving(value:Function):void { + _onStopMoving = value; + } + + /** + * Набор объектов, исключаемых из проверки столкновений. + */ + public function get collisionIgnoreSet():Set { + return _collisionIgnoreSet; + } + + /** + * Источник событий клавиатуры и мыши. + */ + public function get eventsSource():DisplayObject { + return _eventsSource; + } + + /** + * @private + */ + public function set eventsSource(value:DisplayObject):void { + if (_eventsSource != value) { + if (value == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + if (_controlsEnabled) { + unregisterEventsListeners(); + } + _eventsSource = value; + if (_controlsEnabled) { + registerEventListeners(); + } + } + } + + /** + * Ассоциированная камера. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + _camera = value; + if (value == null) { + controlsEnabled = false; + } else { + createCollider(); + } + } + } + + /** + * Режим движения камеры. Если значение равно true, то перемещения камеры происходят относительно + * локальной системы координат, иначе относительно глобальной. + * + * @default true + */ + public function get moveLocal():Boolean { + return _moveLocal; + } + + /** + * @private + */ + public function set moveLocal(value:Boolean):void { + _moveLocal = value; + } + + /** + * Включение режима проверки столкновений. + */ + public function get checkCollisions():Boolean { + return _checkCollisions; + } + + /** + * @private + */ + public function set checkCollisions(value:Boolean):void { + _checkCollisions = value; + } + + /** + * Радиус сферы для определения столкновений. + * + * @default 0 + */ + public function get collisionRadius():Number { + return _collisionRadius; + } + + /** + * @private + */ + public function set collisionRadius(value:Number):void { + _collisionRadius = value; + if (_collider != null) { + _collider.sphereRadius = _collisionRadius; + } + } + + /** + * Привязка клавиши к действию. + * + * @param keyCode код клавиши + * @param action наименование действия + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Активация движения камеры вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function forward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения камеры назад. + * + * @param value true для начала движения, false для окончания + */ + public function back(value:Boolean):void { + _back = value; + } + + /** + * Активация движения камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function left(value:Boolean):void { + _left = value; + } + + /** + * Активация движения камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function right(value:Boolean):void { + _right = value; + } + + /** + * Активация движения камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function up(value:Boolean):void { + _up = value; + } + + /** + * Активация движения камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function down(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * @private + */ + private function createCollider():void { + _collider = new SphereCollider(_camera.scene, _collisionRadius); + _collider.offsetThreshold = 0.01; + _collider.ignoreSet = _collisionIgnoreSet; + } + + /** + * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw. + * + * @default 1 + * + * @see #mousePitch() + * @see #mouseYaw() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mousePitch():Number { + return _mousePitch; + } + + /** + * @private + */ + public function set mousePitch(pitch:Number):void { + _mousePitch = pitch; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + } + + /** + * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mouseYaw():Number { + return _mouseYaw; + } + + /** + * @private + */ + public function set mouseYaw(yaw:Number):void { + _mouseYaw = yaw; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость поступательного движения (единицы в секунду). + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(spd:Number):void { + _speed = spd; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Активность управления камеры. + * + * @default false + */ + public function get controlsEnabled():Boolean { + return _controlsEnabled; + } + + /** + * @private + */ + public function set controlsEnabled(value:Boolean):void { + if (_camera == null || _controlsEnabled == value) return; + if (value) { + lastFrameTime = getTimer(); + registerEventListeners(); + } + else { + unregisterEventsListeners(); + } + _controlsEnabled = value; + } + + /** + * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину + * MouseEvent.delta. + * + * @default Math.PI / 180 + */ + public function get fovStep():Number { + return _fovStep; + } + + /** + * @private + */ + public function set fovStep(value:Number):void { + _fovStep = value; + } + + /** + * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier). + * + * @default 0.1 + */ + public function get zoomMultiplier():Number { + return _zoomMultiplier; + } + + /** + * @private + */ + public function set zoomMultiplier(value:Number):void { + _zoomMultiplier = value; + } + + /** + * @private + */ + private function registerEventListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + */ + private function unregisterEventsListeners():void { + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + * @param e + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * @private + * @param e + */ + private function onMouseDown(e:MouseEvent):void { + mouseLookActive = true; + currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY; + startRotX = _camera.rotationX; + startRotZ = _camera.rotationZ; + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseUp(e:MouseEvent):void { + mouseLookActive = false; + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseWheel(e:MouseEvent):void { + if (_camera.orthographic) { + _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier); + } else { + _camera.fov -= _fovStep * e.delta; + } + } + + /** + * Обработка управляющих воздействий. + * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate(). + * + * @see alternativa.engine3d.core.Scene3D#calculate() + */ + public function processInput(): void { + if (!_controlsEnabled || _camera == null) return; + + // Время в секундах от начала предыдущего кадра + var frameTime:Number = getTimer() - lastFrameTime; + lastFrameTime += frameTime; + frameTime /= 1000; + + // Обработка mouselook + if (mouseLookActive) { + prevDragCoords.x = currentDragCoords.x; + prevDragCoords.y = currentDragCoords.y; + currentDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = _eventsSource.stage.mouseY; + if (!prevDragCoords.equals(currentDragCoords)) { + _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff; + var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawLeft) { + _camera.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _camera.rotationZ -= _yawSpeed * frameTime; + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + rotX = _camera.rotationX + _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } else if (_pitchDown) { + rotX = _camera.rotationX - _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + + // TODO: Поворот относительно продольной оси (крен, roll) + + var frameDistance:Number = _speed * frameTime; + if (_accelerate) { + frameDistance *= _speedMultiplier; + } + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + var transformation:Matrix3D = _camera.transformation; + + if (_moveLocal) { + // Режим относительных пермещений + // Движение вперед-назад + if (_forward) { + velocity.x += frameDistance * transformation.c; + velocity.y += frameDistance * transformation.g; + velocity.z += frameDistance * transformation.k; + } else if (_back) { + velocity.x -= frameDistance * transformation.c; + velocity.y -= frameDistance * transformation.g; + velocity.z -= frameDistance * transformation.k; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * transformation.a; + velocity.y -= frameDistance * transformation.e; + velocity.z -= frameDistance * transformation.i; + } else if (_right) { + velocity.x += frameDistance * transformation.a; + velocity.y += frameDistance * transformation.e; + velocity.z += frameDistance * transformation.i; + } + // Движение вверх-вниз + if (_up) { + velocity.x -= frameDistance * transformation.b; + velocity.y -= frameDistance * transformation.f; + velocity.z -= frameDistance * transformation.j; + } else if (_down) { + velocity.x += frameDistance * transformation.b; + velocity.y += frameDistance * transformation.f; + velocity.z += frameDistance * transformation.j; + } + } + else { + // Режим глобальных перемещений + var cosZ:Number = Math.cos(_camera.rotationZ); + var sinZ:Number = Math.sin(_camera.rotationZ); + // Движение вперед-назад + if (_forward) { + velocity.x -= frameDistance * sinZ; + velocity.y += frameDistance * cosZ; + } else if (_back) { + velocity.x += frameDistance * sinZ; + velocity.y -= frameDistance * cosZ; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * cosZ; + velocity.y -= frameDistance * sinZ; + } else if (_right) { + velocity.x += frameDistance * cosZ; + velocity.y += frameDistance * sinZ; + } + // Движение вверх-вниз + if (_up) { + velocity.z += frameDistance; + } else if (_down) { + velocity.z -= frameDistance; + } + } + + // Коррекция модуля вектора скорости + if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) { + velocity.length = frameDistance; + } + + // Проверка столкновений + if (_checkCollisions) { + _collider.calculateDestination(_camera.coords, velocity, destination); + _camera.x = destination.x; + _camera.y = destination.y; + _camera.z = destination.z; + } else { + _camera.x += velocity.x; + _camera.y += velocity.y; + _camera.z += velocity.z; + } + + // Обработка начала/окончания движения + if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (_onStartMoving != null) { + _onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (_onStopMoving != null) { + _onStopMoving.call(this); + } + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as new file mode 100644 index 0000000..5f3ccf3 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as @@ -0,0 +1,65 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public final class BSPNode { + + // Родительская нода + alternativa3d var parent:BSPNode; + + // Дочерние ветки + alternativa3d var front:BSPNode; + alternativa3d var back:BSPNode; + + // Нормаль плоскости ноды + alternativa3d var normal:Point3D = new Point3D(); + + // Смещение плоскости примитива + alternativa3d var offset:Number; + + // Минимальная мобильность ноды + alternativa3d var mobility:int = int.MAX_VALUE; + + // Набор примитивов в ноде + alternativa3d var primitive:PolyPrimitive; + alternativa3d var backPrimitives:Set; + alternativa3d var frontPrimitives:Set; + + // Хранилище неиспользуемых нод + static private var collector:Array = new Array(); + + // Создать ноду на основе примитива + static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode { + // Достаём ноду из коллектора + var node:BSPNode = collector.pop(); + // Если коллектор пуст, создаём новую ноду + if (node == null) { + node = new BSPNode(); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + // Сохраняем ноду + primitive.node = node; + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + // Сохраняем мобильность + node.mobility = primitive.mobility; + return node; + } + + // Удалить ноду, все ссылки должны быть почищены + static alternativa3d function destroyBSPNode(node:BSPNode):void { + //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives); + collector.push(node); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..66ef3e7 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,902 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + import alternativa.engine3d.display.View; + import alternativa.engine3d.materials.DrawPoint; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + import flash.geom.Point; + import alternativa.utils.MathUtils; + + use namespace alternativa3d; + + /** + * Камера для отображения 3D-сцены на экране. + * + *

Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе + * координат родителя. + * + *

Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода — + * экземпляр класса alternativa.engine3d.display.View. + * + * @see alternativa.engine3d.display.View + */ + public class Camera3D extends Object3D { + + /** + * @private + * Расчёт матрицы пространства камеры + */ + alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX); + /** + * @private + * Расчёт плоскостей отсечения + */ + alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES); + /** + * @private + * Отрисовка + */ + alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Поле зрения + */ + alternativa3d var _fov:Number = Math.PI/2; + /** + * @private + * Фокусное расстояние + */ + alternativa3d var focalLength:Number; + /** + * @private + * Перспективное искажение + */ + alternativa3d var focalDistortion:Number; + + /** + * @private + * Флаги рассчитанности UV-матриц + */ + alternativa3d var uvMatricesCalculated:Set = new Set(true); + + // Всмомогательные точки для расчёта UV-матриц + private var textureA:Point3D = new Point3D(); + private var textureB:Point3D = new Point3D(); + private var textureC:Point3D = new Point3D(); + + /** + * @private + * Вид из камеры + */ + alternativa3d var _view:View; + + /** + * @private + * Режим отрисовки + */ + alternativa3d var _orthographic:Boolean = false; + private var fullDraw:Boolean; + + // Масштаб + private var _zoom:Number = 1; + + // Синус половинчатого угла обзора камеры + private var viewAngle:Number; + + // Направление камеры + private var direction:Point3D = new Point3D(0, 0, 1); + + // Обратная трансформация камеры + private var cameraMatrix:Matrix3D = new Matrix3D(); + + // Скины + private var firstSkin:Skin; + private var prevSkin:Skin; + private var currentSkin:Skin; + + // Плоскости отсечения + private var leftPlane:Point3D = new Point3D(); + private var rightPlane:Point3D = new Point3D(); + private var topPlane:Point3D = new Point3D(); + private var bottomPlane:Point3D = new Point3D(); + private var leftOffset:Number; + private var rightOffset:Number; + private var topOffset:Number; + private var bottomOffset:Number; + + // Вспомогательные массивы точек для отрисовки + private var points1:Array = new Array(); + private var points2:Array = new Array(); + + /** + * Создание нового экземпляра камеры. + * + * @param name имя экземпляра + */ + public function Camera3D(name:String = null) { + super(name); + } + + /** + * @private + */ + private function calculateMatrix():void { + // Расчёт матрицы пространства камеры + cameraMatrix.copy(transformation); + cameraMatrix.invert(); + if (_orthographic) { + cameraMatrix.scale(_zoom, _zoom, _zoom); + } + // Направление камеры + direction.x = transformation.c; + direction.y = transformation.g; + direction.z = transformation.k; + direction.normalize(); + } + + /** + * @private + * Расчёт плоскостей отсечения + */ + private function calculatePlanes():void { + var halfWidth:Number = _view._width*0.5; + var halfHeight:Number = _view._height*0.5; + + var aw:Number = transformation.a*halfWidth; + var ew:Number = transformation.e*halfWidth; + var iw:Number = transformation.i*halfWidth; + var bh:Number = transformation.b*halfHeight; + var fh:Number = transformation.f*halfHeight; + var jh:Number = transformation.j*halfHeight; + if (_orthographic) { + // Расчёт плоскостей отсечения в изометрии + aw /= _zoom; + ew /= _zoom; + iw /= _zoom; + bh /= _zoom; + fh /= _zoom; + jh /= _zoom; + + // Левая плоскость + leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g; + leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k; + leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c; + leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z; + + // Правая плоскость + rightPlane.x = -leftPlane.x; + rightPlane.y = -leftPlane.y; + rightPlane.z = -leftPlane.z; + rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z; + + // Верхняя плоскость + topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e; + topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i; + topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a; + topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = -topPlane.x; + bottomPlane.y = -topPlane.y; + bottomPlane.z = -topPlane.z; + bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z; + } else { + // Вычисляем расстояние фокуса + focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov); + // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции + focalDistortion = 1/(focalLength*focalLength); + + // Расчёт плоскостей отсечения в перспективе + var cl:Number = transformation.c*focalLength; + var gl:Number = transformation.g*focalLength; + var kl:Number = transformation.k*focalLength; + + // Угловые вектора пирамиды видимости + var leftTopX:Number = -aw - bh + cl; + var leftTopY:Number = -ew - fh + gl; + var leftTopZ:Number = -iw - jh + kl; + var rightTopX:Number = aw - bh + cl; + var rightTopY:Number = ew - fh + gl; + var rightTopZ:Number = iw - jh + kl; + var leftBottomX:Number = -aw + bh + cl; + var leftBottomY:Number = -ew + fh + gl; + var leftBottomZ:Number = -iw + jh + kl; + var rightBottomX:Number = aw + bh + cl; + var rightBottomY:Number = ew + fh + gl; + var rightBottomZ:Number = iw + jh + kl; + + // Левая плоскость + leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY; + leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ; + leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX; + leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z; + + // Правая плоскость + rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY; + rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ; + rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX; + rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z; + + // Верхняя плоскость + topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY; + topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ; + topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX; + topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY; + bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ; + bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX; + bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z; + + + // Расчёт угла конуса + var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ); + leftTopX /= length; + leftTopY /= length; + leftTopZ /= length; + length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ); + rightTopX /= length; + rightTopY /= length; + rightTopZ /= length; + length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ); + leftBottomX /= length; + leftBottomY /= length; + leftBottomZ /= length; + length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ); + rightBottomX /= length; + rightBottomY /= length; + rightBottomZ /= length; + + viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z; + var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + + viewAngle = Math.sin(Math.acos(viewAngle)); + } + } + + /** + * @private + */ + private function render():void { + // Режим отрисовки + fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued); + + // Очистка рассчитанных текстурных матриц + uvMatricesCalculated.clear(); + + // Отрисовка + prevSkin = null; + currentSkin = firstSkin; + renderBSPNode(_scene.bsp); + + // Удаление ненужных скинов + while (currentSkin != null) { + removeCurrentSkin(); + } + } + + /** + * @private + */ + private function renderBSPNode(node:BSPNode):void { + if (node != null) { + var primitive:*; + var normal:Point3D = node.normal; + var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z; + var cameraOffset:Number; + if (!_orthographic) { + cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset; + } + if (node.primitive != null) { + // В ноде только базовый примитив + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + drawSkin(node.primitive); + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + } + renderBSPNode(node.back); + } + } else { + // В ноде несколько примитивов + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + for (primitive in node.frontPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + for (primitive in node.backPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.back); + } + } + } + } + + /** + * @private + * Отрисовка скина примитива + */ + private function drawSkin(primitive:PolyPrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Проверка поверхности + var surface:Surface = primitive.face._surface; + if (surface == null) { + return; + } + // Проверка материала + var material:SurfaceMaterial = surface._material; + if (material == null || !material.canDraw(primitive)) { + return; + } + // Отсечение выходящих за окно просмотра частей + var i:uint; + var length:uint = primitive.num; + var primitivePoint:Point3D; + var primitiveUV:Point; + var point:DrawPoint; + var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase; + if (useUV) { + // Формируем список точек и UV-координат полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + primitiveUV = primitive.uvs[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + point.u = primitiveUV.x; + point.v = primitiveUV.y; + } + } + } else { + // Формируем список точек полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + } + } + } + // Отсечение по левой стороне + length = clip(length, points1, points2, leftPlane, leftOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по правой стороне + length = clip(length, points2, points1, rightPlane, rightOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по верхней стороне + length = clip(length, points1, points2, topPlane, topOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по нижней стороне + length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV); + if (length < 3) { + return; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Переводим координаты в систему камеры + var x:Number; + var y:Number; + var z:Number; + for (i = 0; i < length; i++) { + point = points1[i]; + x = point.x; + y = point.y; + z = point.z; + point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin, length, points1); + + // Переключаемся на следующий скин + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + + } else { + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + + } + } + } + + /** + * @private + * Отсечение полигона плоскостью. + */ + private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint { + var i:uint; + var k:Number; + var index:uint = 0; + var point:DrawPoint; + var point1:DrawPoint; + var point2:DrawPoint; + var offset1:Number; + var offset2:Number; + + point1 = points1[length - 1]; + offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset; + + if (calculateUV) { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + point.u = point2.u; + point.v = point2.v; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + + } else { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + } + return index; + } + + /** + * @private + * Добавление текущего скина. + */ + private function addCurrentSkin():void { + currentSkin = Skin.createSkin(); + _view.canvas.addChild(currentSkin); + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + * Вставляем под текущий скин. + */ + private function insertCurrentSkin():void { + var skin:Skin = Skin.createSkin(); + _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin)); + skin.nextSkin = currentSkin; + if (prevSkin == null) { + firstSkin = skin; + } else { + prevSkin.nextSkin = skin; + } + currentSkin = skin; + } + + /** + * @private + * Удаляет текущий скин. + */ + private function removeCurrentSkin():void { + // Сохраняем следующий + var next:Skin = currentSkin.nextSkin; + // Удаляем из канваса + _view.canvas.removeChild(currentSkin); + // Очистка скина + if (currentSkin.material != null) { + currentSkin.material.clear(currentSkin); + } + // Зачищаем ссылки + currentSkin.nextSkin = null; + currentSkin.primitive = null; + currentSkin.material = null; + // Удаляем + Skin.destroySkin(currentSkin); + // Следующий устанавливаем текущим + currentSkin = next; + // Устанавливаем связь от предыдущего скина + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + */ + alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void { + + // Расчёт точек базового примитива в координатах камеры + var point:Point3D = face.primitive.points[0]; + textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[1]; + textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[2]; + textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + + // Находим AB и AC + var abx:Number = textureB.x - textureA.x; + var aby:Number = textureB.y - textureA.y; + var acx:Number = textureC.x - textureA.x; + var acy:Number = textureC.y - textureA.y; + + // Расчёт текстурной матрицы + var uvMatrixBase:Matrix = face.uvMatrixBase; + var uvMatrix:Matrix = face.uvMatrix; + uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width; + uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width; + uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height; + uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height; + uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d; + uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h; + + // Помечаем, как рассчитанную + uvMatricesCalculated[face] = true; + } + + /** + * Поле вывода, в котором происходит отрисовка камеры. + */ + public function get view():View { + return _view; + } + + /** + * @private + */ + public function set view(value:View):void { + if (value != _view) { + if (_view != null) { + _view.camera = null; + } + if (value != null) { + value.camera = this; + } + } + } + + /** + * Включение режима аксонометрической проекции. + * + * @default false + */ + public function get orthographic():Boolean { + return _orthographic; + } + + /** + * @private + */ + public function set orthographic(value:Boolean):void { + if (_orthographic != value) { + // Отправляем сигнал об изменении типа камеры + addOperationToScene(calculateMatrixOperation); + // Сохраняем новое значение + _orthographic = value; + } + } + + /** + * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние + * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода. + * Угол зрения ограничен диапазоном 0-180 градусов. + */ + public function get fov():Number { + return _fov; + } + + /** + * @private + */ + public function set fov(value:Number):void { + value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value); + if (_fov != value) { + // Если перспектива + if (!_orthographic) { + // Отправляем сигнал об изменении плоскостей отсечения + addOperationToScene(calculatePlanesOperation); + } + // Сохраняем новое значение + _fov = value; + } + } + + /** + * Коэффициент увеличения изображения в режиме аксонометрической проекции. + */ + public function get zoom():Number { + return _zoom; + } + + /** + * @private + */ + public function set zoom(value:Number):void { + value = (value < 0) ? 0 : value; + if (_zoom != value) { + // Если изометрия + if (_orthographic) { + // Отправляем сигнал об изменении zoom + addOperationToScene(calculateMatrixOperation); + } + // Сохраняем новое значение + _zoom = value; + } + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + if (_view != null) { + // Отправляем операцию расчёта плоскостей отсечения + scene.addOperation(calculatePlanesOperation); + // Подписываемся на сигналы сцены + scene.changePrimitivesOperation.addSequel(renderOperation); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + super.removeFromScene(scene); + + // Удаляем все операции из очереди + scene.removeOperation(calculateMatrixOperation); + scene.removeOperation(calculatePlanesOperation); + scene.removeOperation(renderOperation); + + if (_view != null) { + // Отписываемся от сигналов сцены + scene.changePrimitivesOperation.removeSequel(renderOperation); + } + } + + /** + * @private + */ + alternativa3d function addToView(view:View):void { + // Сохраняем первый скин + firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null; + + // Подписка на свои операции + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.addSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.addSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.addSequel(renderOperation); + + if (_scene != null) { + // Отправляем сигнал перерисовки + _scene.addOperation(calculateMatrixOperation); + // Подписываемся на сигналы сцены + _scene.changePrimitivesOperation.addSequel(renderOperation); + } + + // Сохраняем вид + _view = view; + } + + /** + * @private + */ + alternativa3d function removeFromView(view:View):void { + // Сброс ссылки на первый скин + firstSkin = null; + + // Отписка от своих операций + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.removeSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.removeSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.removeSequel(renderOperation); + + if (_scene != null) { + // Удаляем все операции из очереди + _scene.removeOperation(calculateMatrixOperation); + _scene.removeOperation(calculatePlanesOperation); + _scene.removeOperation(renderOperation); + // Отписываемся от сигналов сцены + _scene.changePrimitivesOperation.removeSequel(renderOperation); + } + // Удаляем ссылку на вид + _view = null; + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "camera" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Camera3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Camera3D = Camera3D(source); + orthographic = src._orthographic; + zoom = src._zoom; + fov = src._fov; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..f5c0906 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as @@ -0,0 +1,921 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань + * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения + * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается + * матрица трансформации текстуры. + */ + final public class Face { + // Операции + /** + * @private + * Расчёт глобальной нормали плоскости грани. + */ + alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL); + /** + * @private + * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP). + */ + alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV); + /** + * @private + * Обновление примитива в сцене. + */ + alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE); + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL); + /** + * @private + * Расчёт UV для фрагментов (выполняется после трансформации, если её не было). + */ + alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Вершины грани + */ + alternativa3d var _vertices:Array; + /** + * @private + * Количество вершин + */ + alternativa3d var _verticesCount:uint; + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + // UV-координаты + /** + * @private + */ + alternativa3d var _aUV:Point; + /** + * @private + */ + alternativa3d var _bUV:Point; + /** + * @private + */ + alternativa3d var _cUV:Point; + + /** + * @private + * Коэффициенты базовой UV-матрицы + */ + alternativa3d var uvMatrixBase:Matrix; + + /** + * @private + * UV Матрица перевода текстурных координат в изометрическую камеру. + */ + alternativa3d var uvMatrix:Matrix; + /** + * @private + * Нормаль плоскости + */ + alternativa3d var globalNormal:Point3D = new Point3D(); + /** + * @private + * Смещение плоскости + */ + alternativa3d var globalOffset:Number; + + /** + * Создание экземпляра грани. + * + * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в + * порядке обхода лицевой стороны грани против часовой стрелки. + * + * @see Vertex + */ + public function Face(vertices:Array) { + // Сохраняем вершины + _vertices = vertices; + _verticesCount = vertices.length; + + // Создаём оригинальный примитив + primitive = PolyPrimitive.createPolyPrimitive(); + primitive.face = this; + primitive.num = _verticesCount; + + // Обрабатываем вершины + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = vertices[i]; + // Добавляем координаты вершины в примитив + primitive.points.push(vertex.globalCoords); + // Добавляем пустые UV-координаты в примитив + primitive.uvs.push(null); + // Добавляем вершину в грань + vertex.addToFace(this); + } + + // Расчёт нормали + calculateNormalOperation.addSequel(updatePrimitiveOperation); + + // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку + calculateUVOperation.addSequel(calculateFragmentsUVOperation); + calculateUVOperation.addSequel(updateMaterialOperation); + } + + /** + * @private + * Расчёт нормали в глобальных координатах + */ + private function calculateNormal():void { + // Вектор AB + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.globalCoords; + vertex = _vertices[1]; + var bv:Point3D = vertex.globalCoords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + // Вектор AC + vertex = _vertices[2]; + var cv:Point3D = vertex.globalCoords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + // Перпендикуляр к плоскости + globalNormal.x = acz*aby - acy*abz; + globalNormal.y = acx*abz - acz*abx; + globalNormal.z = acy*abx - acx*aby; + // Нормализация перпендикуляра + globalNormal.normalize(); + } + + /** + * @private + * Расчитывает глобальное смещение плоскости грани. + * Помечает конечные примитивы на удаление, а базовый на добавление в сцене. + */ + private function updatePrimitive():void { + // Расчёт смещения + var vertex:Vertex = _vertices[0]; + globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z; + + removePrimitive(primitive); + primitive.mobility = _mesh.inheritedMobility; + _mesh._scene.addPrimitives.push(primitive); + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены + */ + private function removePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + removePrimitive(primitive.backFragment); + removePrimitive(primitive.frontFragment); + primitive.backFragment = null; + primitive.frontFragment = null; + if (primitive != this.primitive) { + primitive.parent = null; + primitive.sibling = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } + } else { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _mesh._scene.removeBSPPrimitive(primitive); + } + } + } + + /** + * @private + * Пометка на перерисовку фрагментов грани. + */ + private function updateMaterial():void { + if (!updatePrimitiveOperation.queued) { + changePrimitive(primitive); + } + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку + */ + private function changePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + changePrimitive(primitive.backFragment); + changePrimitive(primitive.frontFragment); + } else { + _mesh._scene.changedPrimitives[primitive] = true; + } + } + + /** + * @private + * Расчёт UV-матрицы на основании первых трёх UV-координат. + * Расчёт UV-координат для оставшихся точек. + */ + private function calculateUV():void { + var i:uint; + // Расчёт UV-матрицы + if (_aUV != null && _bUV != null && _cUV != null) { + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + var det:Number = abu*acv - abv*acu; + if (det != 0) { + if (uvMatrixBase == null) { + uvMatrixBase = new Matrix(); + uvMatrix = new Matrix(); + } + uvMatrixBase.a = acv/det; + uvMatrixBase.b = -abv/det; + uvMatrixBase.c = -acu/det; + uvMatrixBase.d = abu/det; + uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y); + uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y); + + // Заполняем UV в базовом примитиве + primitive.uvs[0] = _aUV; + primitive.uvs[1] = _bUV; + primitive.uvs[2] = _cUV; + + // Расчёт недостающих UV + if (_verticesCount > 3) { + var a:Point3D = primitive.points[0]; + var b:Point3D = primitive.points[1]; + var c:Point3D = primitive.points[2]; + + var ab1:Number; + var ab2:Number; + var ac1:Number; + var ac2:Number; + var ad1:Number; + var ad2:Number; + var abk:Number; + var ack:Number; + + var uv:Point; + var point:Point3D; + + // Выбор наиболее подходящих осей для расчёта + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) { + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось X + ab1 = b.y - a.y; + ab2 = b.z - a.z; + ac1 = c.y - a.y; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.y - a.y; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } else { + if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось Y + ab1 = b.x - a.x; + ab2 = b.z - a.z; + ac1 = c.x - a.x; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } + } + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + uvMatrix = null; + // Удаляем UV-координаты из базового примитива + for (i = 0; i < _verticesCount; i++) { + primitive.uvs[i] = null; + } + } + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + uvMatrix = null; + // Удаляем UV-координаты из базового примитива + for (i = 0; i < _verticesCount; i++) { + primitive.uvs[i] = null; + } + } + } + + /** + * @private + * Расчёт UV-координат для фрагментов примитива, если не было трансформации + */ + private function calculateFragmentsUV():void { + // Если в этом цикле не было трансформации + if (!updatePrimitiveOperation.queued) { + if (uvMatrixBase != null) { + // Рассчитываем UV в примитиве + calculatePrimitiveUV(primitive); + } else { + // Удаляем UV в примитиве + removePrimitiveUV(primitive); + } + } + } + + /** + * @private + * Расчёт UV для точек базового примитива. + * + * @param primitive + */ + private function calculatePrimitiveUV(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + var points:Array = primitive.points; + var backPoints:Array = primitive.backFragment.points; + var frontPoints:Array = primitive.frontFragment.points; + var uvs:Array = primitive.uvs; + var backUVs:Array = primitive.backFragment.uvs; + var frontUVs:Array = primitive.frontFragment.uvs; + var index1:uint = 0; + var index2:uint = 0; + var point:Point3D; + var uv:Point; + var uv1:Point; + var uv2:Point; + var t:Number; + var firstSplit:Boolean = true; + for (var i:uint = 0; i < primitive.num; i++) { + var split:Boolean = true; + point = points[i]; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + split = false; + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + split = false; + index1++; + } + + if (split) { + uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)]; + uv2 = uvs[i]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + firstSplit = false; + index2++; + index1++; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + index1++; + } + } + } + // Проверяем рассечение последнего ребра + if (index2 < primitive.frontFragment.num) { + uv1 = uvs[primitive.num - 1]; + uv2 = uvs[0]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + } + + calculatePrimitiveUV(primitive.backFragment); + calculatePrimitiveUV(primitive.frontFragment); + } + } + + /** + * @private + * Удаление UV в примитиве и его фрагментах + * @param primitive + */ + private function removePrimitiveUV(primitive:PolyPrimitive):void { + // Очищаем список UV + for (var i:uint = 0; i < primitive.num; i++) { + primitive.uvs[i] = null; + } + // Если есть фрагменты, удаляем UV в них + if (primitive.backFragment != null) { + removePrimitiveUV(primitive.backFragment); + removePrimitiveUV(primitive.frontFragment); + } + } + + /** + * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex. + * + * @see Vertex + */ + public function get vertices():Array { + return new Array().concat(_vertices); + } + + /** + * Количество вершин грани. + */ + public function get verticesCount():uint { + return _verticesCount; + } + + /** + * Полигональный объект, которому принадлежит грань. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Поверхность, которой принадлежит грань. + */ + public function get surface():Surface { + return _surface; + } + + /** + * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор + * имеет значение null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getFaceId(this) : null; + } + + /** + * UV-координаты, соответствующие первой вершине грани. + */ + public function get aUV():Point { + return (_aUV != null) ? _aUV.clone() : null; + } + + /** + * UV-координаты, соответствующие второй вершине грани. + */ + public function get bUV():Point { + return (_bUV != null) ? _bUV.clone() : null; + } + + /** + * UV-координаты, соответствующие третьей вершине грани. + */ + public function get cUV():Point { + return (_cUV != null) ? _cUV.clone() : null; + } + + /** + * @private + */ + public function set aUV(value:Point):void { + if (_aUV != null) { + if (value != null) { + if (!_aUV.equals(value)) { + _aUV.x = value.x; + _aUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _aUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _aUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set bUV(value:Point):void { + if (_bUV != null) { + if (value != null) { + if (!_bUV.equals(value)) { + _bUV.x = value.x; + _bUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _bUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _bUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set cUV(value:Point):void { + if (_cUV != null) { + if (value != null) { + if (!_cUV.equals(value)) { + _cUV.x = value.x; + _cUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _cUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _cUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * Нормаль в локальной системе координат. + */ + public function get normal():Point3D { + var res:Point3D = new Point3D(); + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.coords; + vertex = _vertices[1]; + var bv:Point3D = vertex.coords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + vertex = _vertices[2]; + var cv:Point3D = vertex.coords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + res.x = acz*aby - acy*abz; + res.y = acx*abz - acz*abx; + res.z = acy*abx - acx*aby; + if (res.x != 0 || res.y != 0 || res.z != 0) { + var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z); + res.x /= k; + res.y /= k; + res.z /= k; + } + return res; + } + + /** + * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @return UV-координаты заданной точки + */ + public function getUV(point:Point3D):Point { + return getUVFast(point, normal); + } + + /** + * @private + * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта + * локальной нормали грани. Используется для оптимизации. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @param normal нормаль плоскости грани в локальной системе координат + * @return UV-координаты заданной точки + */ + alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point { + if (_aUV == null || _bUV == null || _cUV == null) { + return null; + } + + // Выбор наиболее длинной оси нормали + var dir:uint; + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) { + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 0; + } else { + dir = 2; + } + } else { + if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 1; + } else { + dir = 2; + } + } + + // Расчёт соотношения по векторам AB и AC + var v:Vertex = _vertices[0]; + var a:Point3D = v._coords; + v = _vertices[1]; + var b:Point3D = v._coords; + v = _vertices[2]; + var c:Point3D = v._coords; + + var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x); + var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z); + var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x); + var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z); + var det:Number = ab1*ac2 - ac1*ab2; + + var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x); + var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z); + var abk:Number = (ad1*ac2 - ac1*ad2)/det; + var ack:Number = (ab1*ad2 - ad1*ab2)/det; + + // Интерполяция по UV первых точек + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + + return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack); + } + + /** + * Множество граней, имеющих общие рёбра с текущей гранью. + */ + public function get edgeJoinedFaces():Set { + var res:Set = new Set(true); + // Перебираем точки грани + for (var i:uint = 0; i < _verticesCount; i++) { + var a:Vertex = _vertices[i]; + var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0]; + + // Перебираем грани текущей точки + for (var key:* in a._faces) { + var face:Face = key; + // Если это другая грань и у неё также есть следующая точка + if (face != this && face._vertices.indexOf(b) >= 0) { + // Значит у граней общее ребро + res[face] = true; + } + } + } + return res; + } + + /** + * @private + * Удаление всех вершин из грани. + * Очистка базового примитива. + */ + alternativa3d function removeVertices():void { + // Удалить вершины + for (var i:uint = 0; i < _verticesCount; i++) { + // Удаляем из списка + var vertex:Vertex = _vertices.pop(); + // Удаляем координаты вершины из примитива + primitive.points.pop(); + // Удаляем вершину из грани + vertex.removeFromFace(this); + } + // Обнуляем количество вершин + _verticesCount = 0; + } + + /** + * @private + * Добавление грани на сцену + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену рассчитываем плоскость и UV + scene.addOperation(calculateNormalOperation); + scene.addOperation(calculateUVOperation); + + // Подписываем сцену на операции + updatePrimitiveOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Удаление грани из сцены + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateUVOperation); + scene.removeOperation(calculateFragmentsUVOperation); + scene.removeOperation(calculateNormalOperation); + scene.removeOperation(updatePrimitiveOperation); + scene.removeOperation(updateMaterialOperation); + + // Удаляем примитивы из сцены + removePrimitive(primitive); + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Добавление грани в меш + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление грани из меша + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation); + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Добавление к поверхности + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Подписка поверхности на операции + surface.changeMaterialOperation.addSequel(updateMaterialOperation); + // Если при смене поверхности изменился материал + if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Сохранить поверхность + _surface = surface; + } + + /** + * @private + * Удаление из поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Отписка поверхности от операций + surface.changeMaterialOperation.removeSequel(updateMaterialOperation); + // Если был материал + if (surface._material != null) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Удалить ссылку на поверхность + _surface = null; + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : ""); + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = _vertices[i]; + res += vertex.id + ((i < _verticesCount - 1) ? ", " : ""); + } + res += "]"; + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as new file mode 100644 index 0000000..0a13b19 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as @@ -0,0 +1,981 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNeedMoreVerticesError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.errors.SurfaceExistsError; + import alternativa.engine3d.errors.SurfaceNotFoundError; + import alternativa.engine3d.errors.VertexExistsError; + import alternativa.engine3d.errors.VertexNotFoundError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Map; + import alternativa.utils.ObjectUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект + * содержит в себе наборы вершин, граней и поверхностей. + */ + public class Mesh extends Object3D { + + // Инкремент количества объектов + private static var counter:uint = 0; + + // Инкременты для идентификаторов вершин, граней и поверхностей + private var vertexIDCounter:uint = 0; + private var faceIDCounter:uint = 0; + private var surfaceIDCounter:uint = 0; + + /** + * @private + * Список вершин + */ + alternativa3d var _vertices:Map = new Map(); + /** + * @private + * Список граней + */ + alternativa3d var _faces:Map = new Map(); + /** + * @private + * Список поверхностей + */ + alternativa3d var _surfaces:Map = new Map(); + + /** + * Создание экземпляра полигонального объекта. + * + * @param name имя экземпляра + */ + public function Mesh(name:String = null) { + super(name); + } + + /** + * Добавление новой вершины к объекту. + * + * @param x координата X в локальной системе координат объекта + * @param y координата Y в локальной системе координат объекта + * @param z координата Z в локальной системе координат объекта + * @param id идентификатор вершины. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной вершины + * + * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex { + // Проверяем ID + if (id != null) { + // Если уже есть вершина с таким ID + if (_vertices[id] != undefined) { + if (_vertices[id] is Vertex) { + throw new VertexExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный + while (_vertices[vertexIDCounter] != undefined) { + vertexIDCounter++; + } + id = vertexIDCounter; + } + + // Создаём вершину + var v:Vertex = new Vertex(x, y, z); + + // Добавляем вершину на сцену + if (_scene != null) { + v.addToScene(_scene); + } + + // Добавляем вершину в меш + v.addToMesh(this); + _vertices[id] = v; + + return v; + } + + /** + * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины + * + * @return экземпляр удалённой вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeVertex(vertex:Object):Vertex { + var byLink:Boolean = vertex is Vertex; + + // Проверяем на null + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + + // Проверяем наличие вершины в меше + if (byLink) { + // Если удаляем по ссылке + if (Vertex(vertex)._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + } else { + // Если удаляем по ID + if (_vertices[vertex] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertex, this); + } else if (!(_vertices[vertex] is Vertex)) { + // По этому id не вершина + throw new InvalidIDError(vertex, this); + } + } + + // Находим вершину и её ID + var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex]; + var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex; + + // Удаляем вершину из сцены + if (_scene != null) { + v.removeFromScene(_scene); + } + + // Удаляем вершину из меша + v.removeFromMesh(this); + delete _vertices[id]; + + return v; + } + + /** + * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная + * ни к одной поверхности. + * + * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой + * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex, + * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину. + * @param id идентификатор грани. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной грани + * + * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан + * null, либо количество вершин в массиве меньше трёх + * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива + * + * @see Vertex + */ + public function createFace(vertices:Array, id:Object = null):Face { + + // Проверяем на null + if (vertices == null) { + throw new FaceNeedMoreVerticesError(this); + } + + // Проверяем ID + if (id != null) { + // Если уже есть грань с таким ID + if (_faces[id] != undefined) { + if (_faces[id] is Face) { + throw new FaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_faces[faceIDCounter] != undefined) { + faceIDCounter++; + } + id = faceIDCounter; + } + + // Проверяем количество точек + var length:uint = vertices.length; + if (length < 3) { + throw new FaceNeedMoreVerticesError(this, length); + } + + // Проверяем и формируем список вершин + var v:Array = new Array(); + var vertex:Vertex; + for (var i:uint = 0; i < length; i++) { + if (vertices[i] is Vertex) { + // Если работаем со ссылками + vertex = vertices[i]; + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertices[i], this); + } + } else { + // Если работаем с ID + if (_vertices[vertices[i]] == null) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertices[i], this); + } else if (!(_vertices[vertices[i]] is Vertex)) { + // Если id зарезервировано + throw new InvalidIDError(vertices[i],this); + } + vertex = _vertices[vertices[i]]; + } + v.push(vertex); + } + + // Создаём грань + var f:Face = new Face(v); + + // Добавляем грань на сцену + if (_scene != null) { + f.addToScene(_scene); + } + + // Добавляем грань в меш + f.addToMesh(this); + _faces[id] = f; + + return f; + } + + /** + * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит. + * + * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани + * + * @return экземпляр удалённой грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeFace(face:Object):Face { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань и её ID + var f:Face = byLink ? Face(face) : _faces[face] ; + var id:Object = byLink ? getFaceId(Face(face)) : face; + + // Удаляем грань из сцены + if (_scene != null) { + f.removeFromScene(_scene); + } + + // Удаляем вершины из грани + f.removeVertices(); + + // Удаляем грань из поверхности + if (f._surface != null) { + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Удаляем грань из меша + f.removeFromMesh(this); + delete _faces[id]; + + return f; + } + + /** + * Добавление новой поверхности к объекту. + * + * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса + * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать + * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если + * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность. + * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function createSurface(faces:Array = null, id:Object = null):Surface { + // Проверяем ID + if (id != null) { + // Если уже есть поверхность с таким ID + if (_surfaces[id] != undefined) { + if (_surfaces[id] is Surface) { + throw new SurfaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_surfaces[surfaceIDCounter] != undefined) { + surfaceIDCounter++; + } + id = surfaceIDCounter; + } + + // Создаём поверхность + var s:Surface = new Surface(); + + // Добавляем поверхность на сцену + if (_scene != null) { + s.addToScene(_scene); + } + + // Добавляем поверхность в меш + s.addToMesh(this); + _surfaces[id] = s; + + // Добавляем грани, если есть + if (faces != null) { + var length:uint = faces.length; + for (var i:uint = 0; i < length; i++) { + s.addFace(faces[i]); + } + } + + return s; + } + + /** + * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани. + * + * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности + * + * @return экземпляр удалённой поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeSurface(surface:Object):Surface { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если удаляем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если удаляем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность и её ID + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface; + + // Удаляем поверхность из сцены + if (_scene != null) { + s.removeFromScene(_scene); + } + + // Удаляем грани из поверхности + s.removeFaces(); + + // Удаляем поверхность из меша + s.removeFromMesh(this); + delete _surfaces[id]; + + return s; + } + + /** + * Добавление всех граней объекта в указанную поверхность. + * + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в + * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором, + * будет создана новая поверхность. + * + * @param removeSurfaces удалять или нет пустые поверхности после переноса граней + * + * @return экземпляр поверхности, в которую перенесены грани + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface { + var returnSurface:Surface; + var returnSurfaceId:Object; + if (surface is Surface) { + // Работаем с экземпляром Surface + if (surface._mesh == this) { + returnSurface = Surface(surface); + } else { + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Работаем с идентификатором + if (_surfaces[surface] == undefined) { + // Поверхности еще нет + returnSurface = createSurface(null, surface); + returnSurfaceId = surface; + } else { + if (_surfaces[surface] is Surface) { + returnSurface = _surfaces[surface]; + } else { + // _surfaces[surface] по идентификатору возвращает не Surface + throw new InvalidIDError(surface, this); + } + } + } + // Перемещаем все грани + for each (var face:Face in _faces) { + if (face._surface != returnSurface) { + returnSurface.addFace(face); + } + } + if (removeSurfaces) { + // Удаляем старые, теперь вручную - меньше проверок, но рискованно + if (returnSurfaceId == null) { + returnSurfaceId = getSurfaceId(returnSurface); + } + var newSurfaces:Map = new Map(); + newSurfaces[returnSurfaceId] = returnSurface; + delete _surfaces[returnSurfaceId]; + // Удаляем оставшиеся + for (var currentSurfaceId:* in _surfaces) { + // Удаляем поверхность из сцены + var currentSurface:Surface = _surfaces[currentSurfaceId]; + if (_scene != null) { + currentSurface.removeFromScene(_scene); + } + // Удаляем поверхность из меша + currentSurface.removeFromMesh(this); + delete _surfaces[currentSurfaceId]; + } + // Новый список граней + _surfaces = newSurfaces; + } + return returnSurface; + } + + /** + * Установка материала для указанной поверхности. + * + * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности. + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Surface + */ + public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если назначаем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если назначаем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + + // Назначаем материал + s.material = material; + } + + /** + * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала. + * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей. + * + * @param material устанавливаемый материал + */ + public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void { + for each (var surface:Surface in _surfaces) { + surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null; + } + } + + /** + * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по + * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать + * невырожденный треугольник в UV-пространстве. + * + * @param aUV UV-координаты, соответствующие первой вершине грани + * @param bUV UV-координаты, соответствующие второй вершине грани + * @param cUV UV-координаты, соответствующие третьей вершине грани + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если назначаем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если назначаем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _faces[face]; + + // Назначаем UV-координаты + f.aUV = aUV; + f.bUV = bUV; + f.cUV = cUV; + } + + /** + * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин. + */ + public function get vertices():Map { + return _vertices.clone(); + } + + /** + * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней. + */ + public function get faces():Map { + return _faces.clone(); + } + + /** + * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей. + */ + public function get surfaces():Map { + return _surfaces.clone(); + } + + /** + * Получение вершины объекта по её идентификатору. + * + * @param id идентификатор вершины + * + * @return экземпляр вершины с указанным идентификатором + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getVertexById(id:Object):Vertex { + if (id == null) { + throw new VertexNotFoundError(null, this); + } + if (_vertices[id] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(id, this); + } else { + if (_vertices[id] is Vertex) { + return _vertices[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора вершины объекта. + * + * @param экземпляр вершины + * + * @return идентификатор указанной вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + */ + public function getVertexId(vertex:Vertex):Object { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + for (var i:Object in _vertices) { + if (_vertices[i] == vertex) { + return i; + } + } + throw new VertexNotFoundError(vertex, this); + } + + /** + * Проверка наличия вершины в объекте. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины + * + * @return true, если объект содержит указанную вершину, иначе false + * + * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Vertex + */ + public function hasVertex(vertex:Object):Boolean { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex is Vertex) { + // Проверка вершины + return vertex._mesh == this; + } else { + // Проверка ID вершины + if (_vertices[vertex] != undefined) { + // По этому ID есть объект + if (_vertices[vertex] is Vertex) { + // Объект является вершиной + return true; + } else { + // ID некорректный + throw new InvalidIDError(vertex, this); + } + } else { + return false; + } + } + } + + /** + * Получение грани объекта по ее идентификатору. + * + * @param id идентификатор грани + * + * @return экземпляр грани с указанным идентификатором + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getFaceById(id:Object):Face { + if (id == null) { + throw new FaceNotFoundError(null, this); + } + if (_faces[id] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(id, this); + } else { + if (_faces[id] is Face) { + return _faces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора грани объекта. + * + * @param face экземпляр грани + * + * @return идентификатор указанной грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + */ + public function getFaceId(face:Face):Object { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + for (var i:Object in _faces) { + if (_faces[i] == face) { + return i; + } + } + throw new FaceNotFoundError(face, this); + } + + /** + * Проверка наличия грани в объекте. + * + * @param face экземпляр класса Face или идентификатор грани + * + * @return true, если объект содержит указанную грань, иначе false + * + * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasFace(face:Object):Boolean { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face is Face) { + // Проверка грани + return face._mesh == this; + } else { + // Проверка ID грани + if (_faces[face] != undefined) { + // По этому ID есть объект + if (_faces[face] is Face) { + // Объект является гранью + return true; + } else { + // ID некорректный + throw new InvalidIDError(face, this); + } + } else { + return false; + } + } + } + + /** + * Получение поверхности объекта по ее идентификатору + * + * @param id идентификатор поверхности + * + * @return экземпляр поверхности с указанным идентификатором + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getSurfaceById(id:Object):Surface { + if (id == null) { + throw new SurfaceNotFoundError(null, this); + } + if (_surfaces[id] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(id, this); + } else { + if (_surfaces[id] is Surface) { + return _surfaces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора поверхности объекта. + * + * @param surface экземпляр поверхности + * + * @return идентификатор указанной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + */ + public function getSurfaceId(surface:Surface):Object { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + for (var i:Object in _surfaces) { + if (_surfaces[i] == surface) { + return i; + } + } + return null; + } + + /** + * Проверка наличия поверхности в объекте. + * + * @param surface экземпляр класса Surface или идентификатор поверхности + * + * @return true, если объект содержит указанную поверхность, иначе false + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasSurface(surface:Object):Boolean { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface is Surface) { + // Проверка поверхности + return surface._mesh == this; + } else { + // Проверка ID поверхности + if (_surfaces[surface] != undefined) { + // По этому ID есть объект + if (_surfaces[surface] is Surface) { + // Объект является поверхностью + return true; + } else { + // ID некорректный + throw new InvalidIDError(surface, this); + } + } else { + return false; + } + } + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + var vertex:Vertex; + var face:Face; + var surface:Surface; + if (value != null) { + // Добавить вершины на сцену + for each (vertex in _vertices) { + vertex.addToScene(value); + } + // Добавить грани на сцену + for each (face in _faces) { + face.addToScene(value); + } + // Добавить поверхности на сцену + for each (surface in _surfaces) { + surface.addToScene(value); + } + } else { + // Удалить вершины из сцены + for each (vertex in _vertices) { + vertex.removeFromScene(_scene); + } + // Удалить грани из сцены + for each (face in _faces) { + face.removeFromScene(_scene); + } + // Удалить поверхности из сцены + for each (surface in _surfaces) { + surface.removeFromScene(_scene); + } + } + } + super.setScene(value); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "mesh" + ++counter; + } + + /** + * @inheritDoc + */ + override public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]"; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Mesh(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Mesh = Mesh(source); + + var id:*; + var len:int; + var i:int; + // Копирование вершин + var vertexMap:Map = new Map(true); + for (id in src._vertices) { + var sourceVertex:Vertex = src._vertices[id]; + vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id); + } + + // Копирование граней + var faceMap:Map = new Map(true); + for (id in src._faces) { + var sourceFace:Face = src._faces[id]; + len = sourceFace._vertices.length; + var faceVertices:Array = new Array(len); + for (i = 0; i < len; i++) { + faceVertices[i] = vertexMap[sourceFace._vertices[i]]; + } + var newFace:Face = createFace(faceVertices, id); + newFace.aUV = sourceFace._aUV; + newFace.bUV = sourceFace._bUV; + newFace.cUV = sourceFace._cUV; + faceMap[sourceFace] = newFace; + } + + // Копирование поверхностей + for (id in src._surfaces) { + var sourceSurface:Surface = src._surfaces[id]; + var surfaceFaces:Array = sourceSurface._faces.toArray(); + len = surfaceFaces.length; + for (i = 0; i < len; i++) { + surfaceFaces[i] = faceMap[surfaceFaces[i]]; + } + createSurface(surfaceFaces, id).material = SurfaceMaterial(sourceSurface.material.clone()); + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..d2133da --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,708 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.Object3DHierarchyError; + import alternativa.engine3d.errors.Object3DNotFoundError; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + use namespace alternativa3d; + + /** + * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения + * о трансформации объекта как единого целого. + * + *

Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая + * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей + * X, Y, Z и параллельного переноса центра объекта из начала координат. + * Операции применяются в порядке их перечисления. + * + *

Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций + * самого объекта и всех его предков по иерархии объектов сцены. + */ + public class Object3D { + // Операции + /** + * @private + * Поворот или масштабирование + */ + alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this); + /** + * @private + * Перемещение + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт матрицы трансформации + */ + alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION); + /** + * @private + * Изменение уровеня мобильности + */ + alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Наименование + */ + alternativa3d var _name:String; + /** + * @private + * Сцена + */ + alternativa3d var _scene:Scene3D; + /** + * @private + * Родительский объект + */ + alternativa3d var _parent:Object3D; + /** + * @private + * Дочерние объекты + */ + alternativa3d var _children:Set = new Set(); + /** + * @private + * Уровень мобильности + */ + alternativa3d var _mobility:int = 0; + /** + * @private + */ + alternativa3d var inheritedMobility:int; + /** + * @private + * Координаты объекта относительно родителя + */ + alternativa3d var _coords:Point3D = new Point3D(); + /** + * @private + * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationX:Number = 0; + /** + * @private + * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationY:Number = 0; + /** + * @private + * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationZ:Number = 0; + /** + * @private + * Мастшаб объекта по оси X относительно родителя + */ + alternativa3d var _scaleX:Number = 1; + /** + * @private + * Мастшаб объекта по оси Y относительно родителя + */ + alternativa3d var _scaleY:Number = 1; + /** + * @private + * Мастшаб объекта по оси Z относительно родителя + */ + alternativa3d var _scaleZ:Number = 1; + /** + * @private + * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены + */ + alternativa3d var transformation:Matrix3D = new Matrix3D(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра класса. + * + * @param name имя экземпляра + */ + public function Object3D(name:String = null) { + // Имя по-умолчанию + _name = (name != null) ? name : defaultName(); + + // Последствия операций + changeRotationOrScaleOperation.addSequel(calculateTransformationOperation); + changeCoordsOperation.addSequel(calculateTransformationOperation); + } + + /** + * @private + * Расчёт трансформации + */ + private function calculateTransformation():void { + if (changeRotationOrScaleOperation.queued) { + // Если полная трансформация + transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + if (_parent != null) { + transformation.combine(_parent.transformation); + } + // Сохраняем глобальные координаты объекта + globalCoords.x = transformation.d; + globalCoords.y = transformation.h; + globalCoords.z = transformation.l; + } else { + // Если только перемещение + globalCoords.copy(_coords); + if (_parent != null) { + globalCoords.transform(_parent.transformation); + } + transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z); + } + } + + /** + * @private + * Расчёт общей мобильности + */ + private function calculateMobility():void { + inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility; + } + + /** + * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя. + * Новой сценой дочернего объекта становится сцена родителя. + * + * @param child добавляемый объект + * + * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены + */ + public function addChild(child:Object3D):void { + + // Проверка на null + if (child == null) { + throw new Object3DHierarchyError(null, this); + } + + // Проверка на наличие + if (child._parent == this) { + return; + } + + // Проверка на добавление к самому себе + if (child == this) { + throw new Object3DHierarchyError(this, this); + } + + // Проверка на добавление родительского объекта + if (child._scene == _scene) { + // Если объект был в той же сцене, либо оба не были в сцене + var parentObject:Object3D = _parent; + while (parentObject != null) { + if (child == parentObject) { + throw new Object3DHierarchyError(child, this); + return; + } + parentObject = parentObject._parent; + } + } + + // Если объект был в другом объекте + if (child._parent != null) { + // Удалить его оттуда + child._parent._children.remove(child); + } else { + // Если объект был корневым в сцене + if (child._scene != null && child._scene._root == child) { + child._scene.root = null; + } + } + + // Добавляем в список + _children.add(child); + // Указываем себя как родителя + child.setParent(this); + // Устанавливаем уровни + child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1); + // Указываем сцену + child.setScene(_scene); + } + + /** + * Удаление дочернего объекта. + * + * @param child удаляемый дочерний объект + * + * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта + */ + public function removeChild(child:Object3D):void { + // Проверка на null + if (child == null) { + throw new Object3DNotFoundError(null, this); + } + // Проверка на наличие + if (child._parent != this) { + throw new Object3DNotFoundError(child, this); + } + // Убираем из списка + _children.remove(child); + // Удаляем ссылку на родителя + child.setParent(null); + // Удаляем ссылку на сцену + child.setScene(null); + } + + /** + * @private + * Установка родительского объекта. + * + * @param value родительский объект + */ + alternativa3d function setParent(value:Object3D):void { + // Отписываемся от сигналов старого родителя + if (_parent != null) { + removeParentSequels(); + } + // Сохранить родителя + _parent = value; + // Если устанавливаем родителя + if (value != null) { + // Подписка на сигналы родителя + addParentSequels(); + } + } + + /** + * @private + * Установка новой сцены для объекта. + * + * @param value сцена + */ + alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + // Если была сцена + if (_scene != null) { + // Удалиться из сцены + removeFromScene(_scene); + } + // Если новая сцена + if (value != null) { + // Добавиться на сцену + addToScene(value); + } + // Сохранить сцену + _scene = value; + } else { + // Посылаем операцию трансформации + addOperationToScene(changeRotationOrScaleOperation); + // Посылаем операцию пересчёта мобильности + addOperationToScene(calculateMobilityOperation); + } + // Установить эту сцену у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setScene(_scene); + } + } + + /** + * @private + * Установка уровня операции трансформации. + * + * @param value уровень операции трансформации + */ + alternativa3d function setLevel(value:uint):void { + // Установить уровень операции трансформации и расчёта мобильности + calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value; + calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value; + // Установить уровни у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setLevel(value + 1); + } + } + + /** + * @private + * Подписка на сигналы родителя. + */ + private function addParentSequels():void { + _parent.changeCoordsOperation.addSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation); + } + + /** + * @private + * Удаление подписки на сигналы родителя. + */ + private function removeParentSequels():void { + _parent.changeCoordsOperation.removeSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation); + } + + /** + * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, в которую добавляется объект + */ + protected function addToScene(scene:Scene3D):void { + // При добавлении на сцену полная трансформация и расчёт мобильности + scene.addOperation(changeRotationOrScaleOperation); + scene.addOperation(calculateMobilityOperation); + } + + /** + * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, из которой удаляется объект + */ + protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeRotationOrScaleOperation); + scene.removeOperation(changeCoordsOperation); + scene.removeOperation(calculateMobilityOperation); + } + + /** + * Имя объекта. + */ + public function get name():String { + return _name; + } + + /** + * @private + */ + public function set name(value:String):void { + _name = value; + } + + /** + * Сцена, которой принадлежит объект. + */ + public function get scene():Scene3D { + return _scene; + } + + /** + * Родительский объект. + */ + public function get parent():Object3D { + return _parent; + } + + /** + * Набор дочерних объектов. + */ + public function get children():Set { + return _children.clone(); + } + + /** + * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков + * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные + * объекты находятся ближе к корню дерева, чем более мобильные. + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + if (_mobility != value) { + _mobility = value; + addOperationToScene(calculateMobilityOperation); + } + } + + /** + * Координата X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * Координата Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * Координата Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Координаты объекта. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Угол поворота вокруг оси X, заданный в радианах. + */ + public function get rotationX():Number { + return _rotationX; + } + + /** + * Угол поворота вокруг оси Y, заданный в радианах. + */ + public function get rotationY():Number { + return _rotationY; + } + + /** + * Угол поворота вокруг оси Z, заданный в радианах. + */ + public function get rotationZ():Number { + return _rotationZ; + } + + /** + * @private + */ + public function set rotationX(value:Number):void { + if (_rotationX != value) { + _rotationX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationY(value:Number):void { + if (_rotationY != value) { + _rotationY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationZ(value:Number):void { + if (_rotationZ != value) { + _rotationZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Коэффициент масштабирования вдоль оси X. + */ + public function get scaleX():Number { + return _scaleX; + } + + /** + * Коэффициент масштабирования вдоль оси Y. + */ + public function get scaleY():Number { + return _scaleY; + } + + /** + * Коэффициент масштабирования вдоль оси Z. + */ + public function get scaleZ():Number { + return _scaleZ; + } + + /** + * @private + */ + public function set scaleX(value:Number):void { + if (_scaleX != value) { + _scaleX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleY(value:Number):void { + if (_scaleY != value) { + _scaleY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleZ(value:Number):void { + if (_scaleZ != value) { + _scaleZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + "]"; + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + protected function defaultName():String { + return "object" + ++counter; + } + + /** + * @private + * Добавление операции в очередь. + * + * @param operation добавляемая операция + */ + alternativa3d function addOperationToScene(operation:Operation):void { + if (_scene != null) { + _scene.addOperation(operation); + } + } + + /** + * @private + * Удаление операции из очереди. + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperationFromScene(operation:Operation):void { + if (_scene != null) { + _scene.removeOperation(operation); + } + } + + /** + * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при + * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и + * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения + * правильного объекта. + * + * @return новый пустой объект + */ + protected function createEmptyObject():Object3D { + return new Object3D(); + } + + /** + * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в + * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те + * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется + * вызовом super.clonePropertiesFrom(source). + * + * @param source объект, свойства которого копируются + */ + protected function clonePropertiesFrom(source:Object3D):void { + _name = source._name; + _mobility = source._mobility; + _coords.x = source._coords.x; + _coords.y = source._coords.y; + _coords.z = source._coords.z; + _rotationX = source._rotationX; + _rotationY = source._rotationY; + _rotationZ = source._rotationZ; + _scaleX = source._scaleX; + _scaleY = source._scaleY; + _scaleZ = source._scaleZ; + } + + /** + * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы + * createEmptyObject() и clonePropertiesFrom(). + * + * @return клонированный экземпляр объекта + * + * @see #createEmptyObject() + * @see #clonePropertiesFrom() + */ + public function clone():Object3D { + var copy:Object3D = createEmptyObject(); + copy.clonePropertiesFrom(this); + + // Клонирование детей + for (var key:* in _children) { + var child:Object3D = key; + copy.addChild(child.clone()); + } + + return copy; + } + + /** + * Получение дочернего объекта с заданным именем. + * + * @param name имя дочернего объекта + * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов + */ + public function getChildByName(name:String):Object3D { + for (var key:* in _children) { + var child:Object3D = key; + if (child._name == name) { + return child; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as new file mode 100644 index 0000000..ca430ac --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as @@ -0,0 +1,125 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public class Operation { + + alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000; + alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000; + alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000; + alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000; + alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000; + alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000; + alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000; + alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000; + alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000; + alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000; + alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000; + alternativa3d static const CAMERA_RENDER:uint = 0x0C000000; + alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000; + + // Объект + alternativa3d var object:Object; + + // Метод + alternativa3d var method:Function; + + // Название метода + alternativa3d var name:String; + + // Последствия + private var sequel:Operation; + private var sequels:Set; + + // Приоритет операции + alternativa3d var priority:uint; + + // Находится ли операция в очереди + alternativa3d var queued:Boolean = false; + + public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) { + this.object = object; + this.method = method; + this.name = name; + this.priority = priority; + } + + // Добавить последствие + alternativa3d function addSequel(operation:Operation):void { + if (sequel == null) { + if (sequels == null) { + sequel = operation; + } else { + sequels[operation] = true; + } + } else { + if (sequel != operation) { + sequels = new Set(true); + sequels[sequel] = true; + sequels[operation] = true; + sequel = null; + } + } + } + + // Удалить последствие + alternativa3d function removeSequel(operation:Operation):void { + if (sequel == null) { + if (sequels != null) { + delete sequels[operation]; + var key:*; + var single:Boolean = false; + for (key in sequels) { + if (single) { + single = false; + break; + } + single = true; + } + if (single) { + sequel = key; + sequels = null; + } + } + } else { + if (sequel == operation) { + sequel = null; + } + } + } + + alternativa3d function collectSequels(collector:Array):void { + if (sequel == null) { + // Проверяем последствия + for (var key:* in sequels) { + var operation:Operation = key; + // Если операция ещё не в очереди + if (!operation.queued) { + // Добавляем её в очередь + collector.push(operation); + // Устанавливаем флаг очереди + operation.queued = true; + // Вызываем добавление в очередь её последствий + operation.collectSequels(collector); + } + } + } else { + if (!sequel.queued) { + collector.push(sequel); + sequel.queued = true; + sequel.collectSequels(collector); + } + } + } + + public function toString():String { + return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as new file mode 100644 index 0000000..5fa4143 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as @@ -0,0 +1,83 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + */ + public class PolyPrimitive { + + // Количество точек + alternativa3d var num:uint; + // Точки + alternativa3d var points:Array = new Array(); + // UV-координаты + alternativa3d var uvs:Array = new Array(); + + // Грань + alternativa3d var face:Face; + // Родительский примитив + alternativa3d var parent:PolyPrimitive; + // Соседний примитив (при наличии родительского) + alternativa3d var sibling:PolyPrimitive; + + // Фрагменты + alternativa3d var backFragment:PolyPrimitive; + alternativa3d var frontFragment:PolyPrimitive; + // Рассечения + alternativa3d var splitTime1:Number; + alternativa3d var splitTime2:Number; + + // BSP-нода, в которой находится примитив + alternativa3d var node:BSPNode; + + // Значения для расчёта качества сплиттера + alternativa3d var splits:uint; + alternativa3d var disbalance:int; + // Качество примитива как сплиттера (меньше - лучше) + public var splitQuality:Number; + + // Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве. + public var mobility:int; + + // Хранилище неиспользуемых примитивов + static private var collector:Array = new Array(); + + // Создать примитив + static alternativa3d function createPolyPrimitive():PolyPrimitive { + // Достаём примитив из коллектора + var primitive:PolyPrimitive = collector.pop(); + // Если коллектор пуст, создаём новый примитив + if (primitive == null) { + primitive = new PolyPrimitive(); + } + //trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node); + return primitive; + } + + /** + * Кладёт примитив в коллектор для последующего реиспользования. + * Ссылка на грань и массивы точек зачищаются в этом методе. + * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода. + * + * Исключение: + * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода. + * + * @param primitive примитив на реиспользование + */ + static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void { + primitive.face = null; + for (var i:uint = 0; i < primitive.num; i++) { + primitive.points.pop(); + primitive.uvs.pop(); + } + collector.push(primitive); + } + + public function toString():String { + return "[Primitive " + face._mesh._name + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as new file mode 100644 index 0000000..58702ff --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as @@ -0,0 +1,1236 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов + * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения + * BSP-дерева для содержимого сцены. + */ + public class Scene3D { + // Операции + /** + * @private + * Полное обновление BSP-дерева + */ + alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this); + /** + * @private + * Изменение примитивов + */ + alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this); + /** + * @private + * Расчёт BSP-дерева + */ + alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP); + /** + * @private + * Очистка списков изменений + */ + alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES); + + /** + * @private + * Корневой объект + */ + alternativa3d var _root:Object3D; + + /** + * @private + * Список операций на выполнение + */ + alternativa3d var operations:Array = new Array(); + //protected var operationSort:Array = ["priority"]; + //protected var operationSortOptions:Array = [Array.NUMERIC]; + /** + * @private + * Вспомогательная пустая операция, используется при удалении операций из списка + */ + alternativa3d var dummyOperation:Operation = new Operation("removed", this); + + /** + * @private + * Флаг анализа сплиттеров + */ + alternativa3d var _splitAnalysis:Boolean = true; + /** + * @private + * Cбалансированность дерева + */ + alternativa3d var _splitBalance:Number = 0; + /** + * @private + * Список изменённых примитивов + */ + alternativa3d var changedPrimitives:Set = new Set(); + + // Вспомогательный список для сборки дочерних примитивов + private var childPrimitives:Set = new Set(); + /** + * @private + * Список примитивов на добавление/удаление + */ + alternativa3d var addPrimitives:Array = new Array(); + +// alternativa3d var addSort:Array = ["mobility"]; +// alternativa3d var addSortOptions:Array = [Array.NUMERIC | Array.DESCENDING]; +// alternativa3d var addSplitQualitySort:Array = ["mobility", "splitQuality"]; +// alternativa3d var addSplitQualitySortOptions:Array = [Array.NUMERIC | Array.DESCENDING, Array.NUMERIC | Array.DESCENDING]; + /** + * @private + * Погрешность при определении точек на плоскости + */ + alternativa3d const planeOffsetThreshold:Number = 0.01; + /** + * @private + * BSP-дерево + */ + alternativa3d var bsp:BSPNode; + + /** + * @private + * Список нод на удаление + */ + alternativa3d var removeNodes:Set = new Set(); + /** + * @private + * Вспомогательная пустая нода, используется при удалении нод из дерева + */ + alternativa3d var dummyNode:BSPNode = new BSPNode(); + + /** + * Создание экземпляра сцены. + */ + public function Scene3D() { + // Обновление BSP-дерева требует его пересчёта + updateBSPOperation.addSequel(calculateBSPOperation); + // После пересчёта дерева необходимо очистить списки изменений + calculateBSPOperation.addSequel(clearPrimitivesOperation); + // Изменение примитивов в случае пересчёта дерева + calculateBSPOperation.addSequel(changePrimitivesOperation); + } + + /** + * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список + * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех + * областях вывода, к которым подключены находящиеся в сцене камеры. + */ + public function calculate():void { + if (operations[0] != undefined) { + // Формируем последствия + var operation:Operation; + var length:uint = operations.length; + var i:uint; + for (i = 0; i < length; i++) { + operation = operations[i]; + operation.collectSequels(operations); + } + // Сортируем операции + length = operations.length; + //operations.sortOn(operationSort, operationSortOptions); + sortOperations(0, length - 1); + // Запускаем операции + //trace("----------------------------------------"); + for (i = 0; i < length; i++) { + operation = operations[i]; + if (operation.method != null) { + //trace("EXECUTE:", operation); + operation.method(); + } else { + /*if (operation == dummyOperation) { + trace("REMOVED"); + } else { + trace(operation); + }*/ + } + } + // Очищаем список операций + for (i = 0; i < length; i++) { + operation = operations.pop(); + operation.queued = false; + } + } + } + + /** + * @private + * Сортировка операций, если массив operations пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortOperations(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:Operation; + var mid:uint = operations[(r + l) >>> 1].priority; + var right:Operation; + do { + while ((left = operations[i]).priority < mid) {i++}; + while (mid < (right = operations[j]).priority) {j--}; + if (i <= j) { + operations[i++] = right; + operations[j--] = left; + } + } while (i <= j) + if (l < j) { + sortOperations(l, j); + } + if (i < r) { + sortOperations(i, r); + } + } + + /** + * @private + * Добавление операции в список + * + * @param operation добавляемая операция + */ + alternativa3d function addOperation(operation:Operation):void { + if (!operation.queued) { + operations.push(operation); + operation.queued = true; + } + } + + /** + * @private + * удаление операции из списка + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperation(operation:Operation):void { + if (operation.queued) { + operations[operations.indexOf(operation)] = dummyOperation; + operation.queued = false; + } + } + + /** + * @private + * Расчёт изменений в BSP-дереве. + * Обработка удалённых и добавленных примитивов. + */ + protected function calculateBSP():void { + if (updateBSPOperation.queued) { + + // Удаление списка нод, помеченных на удаление + removeNodes.clear(); + + // Удаление BSP-дерева, перенос примитивов в список дочерних + childBSP(bsp); + bsp = null; + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + } else { + + var key:*; + var primitive:PolyPrimitive; + + // Удаляем ноды из дерева + if (!removeNodes.isEmpty()) { + var node:BSPNode; + while ((node = removeNodes.peek()) != null) { + + // Ищем верхнюю удаляемую ноду + var removeNode:BSPNode = node; + while ((node = node.parent) != null) { + if (removeNodes[node]) { + removeNode = node; + } + } + + // Удаляем ветку + var parent:BSPNode = removeNode.parent; + var replace:BSPNode = removeBSPNode(removeNode); + + // Если вернулась вспомогательная нода, игнорируем её + if (replace == dummyNode) { + replace = null; + } + + // Если есть родительская нода + if (parent != null) { + // Заменяем себя на указанную ноду + if (parent.front == removeNode) { + parent.front = replace; + } else { + parent.back = replace; + } + } else { + // Если нет родительской ноды, значит заменяем корень на указанную ноду + bsp = replace; + } + + // Устанавливаем связь с родителем для заменённой ноды + if (replace != null) { + replace.parent = parent; + } + } + + // Собираем дочерние примитивы в список на добавление + assembleChildPrimitives(); + } + } + + // Если есть примитивы на добавление + if (addPrimitives[0] != undefined) { + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + splitQualityAnalise(); + // Сортируем массив примитивов c учётом качества + //addPrimitives.sortOn(addSplitQualitySort, addSplitQualitySortOptions); + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + ///addPrimitives.sortOn(addSort, addSortOptions); + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Если корневого нода ещё нет, создаём + if (bsp == null) { + primitive = addPrimitives.pop(); + bsp = BSPNode.createBSPNode(primitive); + changedPrimitives[primitive] = true; + } + + // Встраиваем примитивы в дерево + while ((primitive = addPrimitives.pop()) != null) { + addBSP(bsp, primitive); + } + } + } + + /** + * @private + * Сортировка граней, если массив addPrimitives пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:PolyPrimitive = addPrimitives[(r + l)>>>1]; + var midMobility:Number = mid.mobility; + var midSplitQuality:Number = mid.splitQuality; + var right:PolyPrimitive; + do { + while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++}; + while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitives(l, j); + } + if (i < r) { + sortPrimitives(i, r); + } + } + + /** + * @private + * Сортировка только по мобильности + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitivesByMobility(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:int = addPrimitives[(r + l)>>>1].mobility; + var right:PolyPrimitive; + do { + while ((left = addPrimitives[i]).mobility > mid) {i++}; + while (mid > (right = addPrimitives[j]).mobility) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitivesByMobility(l, j); + } + if (i < r) { + sortPrimitivesByMobility(i, r); + } + } + + + /** + * @private + * Анализ качества сплиттеров + */ + private function splitQualityAnalise():void { + // Перебираем примитивы на добавление + var i:uint; + var length:uint = addPrimitives.length; + var maxSplits:uint = 0; + var maxDisbalance:uint = 0; + var splitter:PolyPrimitive; + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splits = 0; + splitter.disbalance = 0; + var normal:Point3D = splitter.face.globalNormal; + var offset:Number = splitter.face.globalOffset; + // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление + for (var j:uint = 0; j < length; j++) { + if (i != j) { + var primitive:PolyPrimitive = addPrimitives[j]; + if (splitter.mobility <= primitive.mobility) { + // Проверяем наличие точек спереди и сзади сплиттера + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + for (var k:uint = 0; k < primitive.num; k++) { + var point:Point3D = primitive.points[k]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset; + if (pointOffset > planeOffsetThreshold) { + if (!pointsFront) { + splitter.disbalance++; + pointsFront = true; + } + if (pointsBack) { + splitter.splits++; + break; + } + } else { + if (pointOffset < -planeOffsetThreshold) { + if (!pointsBack) { + splitter.disbalance--; + pointsBack = true; + } + if (pointsFront) { + splitter.splits++; + break; + } + } + } + } + } + } + } + // Абсолютное значение дисбаланса + splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance; + // Ищем максимальное количество рассечений и значение дисбаланса + maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits; + maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance; + } + // Расчитываем качество сплиттеров + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance; + } + } + + /** + * @private + * Добавление примитива в BSP-дерево + * + * @param node текущий узел дерева, в который добавляется примитив + * @param primitive добавляемый примитив + */ + protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void { + var point:Point3D; + var normal:Point3D; + var key:*; + + // Сравниваем мобильности ноды и примитива + if (primitive.mobility < node.mobility) { + + // Формируем список содержимого ноды и всех примитивов ниже + if (node.primitive != null) { + childPrimitives[node.primitive] = true; + changedPrimitives[node.primitive] = true; + node.primitive.node = null; + } else { + var p:PolyPrimitive; + for (key in node.backPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + for (key in node.frontPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + } + childBSP(node.back); + childBSP(node.front); + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + splitQualityAnalise(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Сохраняем ноду + primitive.node = node; + + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + + // Сохраняем мобильность + node.mobility = primitive.mobility; + + // Чистим списки примитивов + node.backPrimitives = null; + node.frontPrimitives = null; + + // Удаляем дочерние ноды + node.back = null; + node.front = null; + + } else { + // Получаем нормаль из ноды + normal = node.normal; + + var points:Array = primitive.points; + var uvs:Array = primitive.uvs; + + // Собирательные флаги + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + + // Собираем расстояния точек до плоскости + for (var i:uint = 0; i < primitive.num; i++) { + point = points[i]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + if (pointOffset > planeOffsetThreshold) { + pointsFront = true; + if (pointsBack) { + break; + } + } else { + if (pointOffset < -planeOffsetThreshold) { + pointsBack = true; + if (pointsFront) { + break; + } + } + } + } + + if (!pointsFront && !pointsBack) { + // Сохраняем ноду + primitive.node = node; + + // Если был только базовый примитив, переносим его в список + if (node.primitive != null) { + node.frontPrimitives = new Set(true); + node.frontPrimitives[node.primitive] = true; + node.primitive = null; + } + + // Примитив находится в плоскости ноды + if (Point3D.dot(primitive.face.globalNormal, normal) > 0) { + node.frontPrimitives[primitive] = true; + } else { + if (node.backPrimitives == null) { + node.backPrimitives = new Set(true); + } + node.backPrimitives[primitive] = true; + } + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + } else { + if (!pointsBack) { + // Примитив спереди плоскости ноды + if (node.front == null) { + // Создаём переднюю ноду + node.front = BSPNode.createBSPNode(primitive); + node.front.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в переднюю ноду + addBSP(node.front, primitive); + } + } else { + if (!pointsFront) { + // Примитив сзади плоскости ноды + if (node.back == null) { + // Создаём заднюю ноду + node.back = BSPNode.createBSPNode(primitive); + node.back.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в заднюю ноду + addBSP(node.back, primitive); + } + } else { + // Рассечение + var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + + var firstSplit:Boolean = true; + + point = points[0]; + var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + var offset1:Number = offset0; + var offset2:Number; + for (i = 0; i < primitive.num; i++) { + var j:uint; + if (i < primitive.num - 1) { + j = i + 1; + point = points[j]; + offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + } else { + j = 0; + offset2 = offset0; + } + + if (offset1 > planeOffsetThreshold) { + // Точка спереди плоскости ноды + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } else { + if (offset1 < -planeOffsetThreshold) { + // Точка сзади плоскости ноды + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + } else { + // Рассечение по точке примитива + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } + } + + // Рассечение ребра + if (offset1 > planeOffsetThreshold && offset2 < -planeOffsetThreshold || offset1 < -planeOffsetThreshold && offset2 > planeOffsetThreshold) { + // Находим точку рассечения + var t:Number = offset1/(offset1 - offset2); + point = Point3D.interpolate(points[i], points[j], t); + backFragment.points.push(point); + frontFragment.points.push(point); + // Находим UV в точке рассечения + var uv:Point; + if (primitive.face.uvMatrixBase != null) { + uv = Point.interpolate(uvs[j], uvs[i], t); + } else { + uv = null; + } + backFragment.uvs.push(uv); + frontFragment.uvs.push(uv); + // Отмечаем рассечённое ребро + if (firstSplit) { + primitive.splitTime1 = t; + firstSplit = false; + } else { + primitive.splitTime2 = t; + } + } + + offset1 = offset2; + } + backFragment.num = backFragment.points.length; + frontFragment.num = frontFragment.points.length; + + // Назначаем мобильность + backFragment.mobility = primitive.mobility; + frontFragment.mobility = primitive.mobility; + + // Устанавливаем связи рассечённых примитивов + backFragment.face = primitive.face; + frontFragment.face = primitive.face; + backFragment.parent = primitive; + frontFragment.parent = primitive; + backFragment.sibling = frontFragment; + frontFragment.sibling = backFragment; + primitive.backFragment = backFragment; + primitive.frontFragment = frontFragment; + + // Добавляем фрагменты в дочерние ноды + if (node.back == null) { + node.back = BSPNode.createBSPNode(backFragment); + node.back.parent = node; + changedPrimitives[backFragment] = true; + } else { + addBSP(node.back, backFragment); + } + if (node.front == null) { + node.front = BSPNode.createBSPNode(frontFragment); + node.front.parent = node; + changedPrimitives[frontFragment] = true; + } else { + addBSP(node.front, frontFragment); + } + } + } + } + } + } + + /** + * @private + * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления. + * + * @param node удаляемый узел + * @return корневой узел поддерева, оставшегося после операции удаления + */ + protected function removeBSPNode(node:BSPNode):BSPNode { + var replaceNode:BSPNode; + if (node != null) { + // Удаляем дочерние + node.back = removeBSPNode(node.back); + node.front = removeBSPNode(node.front); + + if (!removeNodes[node]) { + // Если нода не удаляется, возвращает себя + replaceNode = node; + + // Проверяем дочерние ноды + if (node.back != null) { + if (node.back != dummyNode) { + node.back.parent = node; + } else { + node.back = null; + } + } + if (node.front != null) { + if (node.front != dummyNode) { + node.front.parent = node; + } else { + node.front = null; + } + } + } else { + // Проверяем дочерние ветки + if (node.back == null) { + if (node.front != null) { + // Есть только передняя ветка + replaceNode = node.front; + node.front = null; + } + } else { + if (node.front == null) { + // Есть только задняя ветка + replaceNode = node.back; + node.back = null; + } else { + // Есть обе ветки - собираем дочерние примитивы + childBSP(node.back); + childBSP(node.front); + // Используем вспомогательную ноду + replaceNode = dummyNode; + // Удаляем связи с дочерними нодами + node.back = null; + node.front = null; + } + } + + // Удаляем ноду из списка на удаление + delete removeNodes[node]; + // Удаляем ноду + node.parent = null; + BSPNode.destroyBSPNode(node); + } + } + return replaceNode; + } + + /** + * @private + * Удаление примитива из узла дерева + * + * @param primitive удаляемый примитив + */ + alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void { + var node:BSPNode = primitive.node; + primitive.node = null; + + var single:Boolean = false; + var key:*; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Если нода единичная + if (node.primitive == primitive) { + removeNodes[node] = true; + node.primitive = null; + } else { + // Есть передние примитивы + if (node.frontPrimitives[primitive]) { + // Удаляем примитив спереди + delete node.frontPrimitives[primitive]; + + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + if (key == null) { + // Передняя пуста, значит сзади кто-то есть + + // Переворачиваем дочерние ноды + var t:BSPNode = node.back; + node.back = node.front; + node.front = t; + + // Переворачиваем плоскость ноды + node.normal.invert(); + node.offset = -node.offset; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если сзади один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Если сзади несколько примитивов, переносим их в передние + node.frontPrimitives = node.backPrimitives; + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + + } else { + // Если остался один примитив и сзади примитивов нет + if (single && node.backPrimitives == null) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } else { + // Удаляем примитив сзади + delete node.backPrimitives[primitive]; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + break; + } + + // Если сзади примитивов больше нет + if (key == null) { + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если спереди один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } + } + + /** + * @private + * Удаление и перевставка ветки + * + * @param node + */ + protected function childBSP(node:BSPNode):void { + if (node != null && node != dummyNode) { + var primitive:PolyPrimitive = node.primitive; + if (primitive != null) { + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + node.primitive = null; + primitive.node = null; + } else { + for (var key:* in node.backPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + for (key in node.frontPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + node.backPrimitives = null; + node.frontPrimitives = null; + } + childBSP(node.back); + childBSP(node.front); + // Удаляем ноду + node.parent = null; + node.back = null; + node.front = null; + BSPNode.destroyBSPNode(node); + } + } + + /** + * @private + * Сборка списка дочерних примитивов в коллектор + */ + protected function assembleChildPrimitives():void { + var primitive:PolyPrimitive; + while ((primitive = childPrimitives.take()) != null) { + assemblePrimitive(primitive); + } + } + + /** + * @private + * Сборка примитивов и разделение на добавленные и удалённые + * + * @param primitive + */ + private function assemblePrimitive(primitive:PolyPrimitive):void { + // Если есть соседний примитив и он может быть собран + if (primitive.sibling != null && canAssemble(primitive.sibling)) { + // Собираем их в родительский + assemblePrimitive(primitive.parent); + // Зачищаем связи между примитивами + primitive.sibling.sibling = null; + primitive.sibling.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive.sibling); + primitive.sibling = null; + primitive.parent.backFragment = null; + primitive.parent.frontFragment = null; + primitive.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } else { + // Если собраться не получилось или родительский + addPrimitives.push(primitive); + } + } + + /** + * @private + * Проверка, может ли примитив в списке дочерних быть собран + * + * @param primitive + * @return + */ + private function canAssemble(primitive:PolyPrimitive):Boolean { + if (childPrimitives[primitive]) { + delete childPrimitives[primitive]; + return true; + } else { + var backFragment:PolyPrimitive = primitive.backFragment; + var frontFragment:PolyPrimitive = primitive.frontFragment; + if (backFragment != null) { + var assembleBack:Boolean = canAssemble(backFragment); + var assembleFront:Boolean = canAssemble(frontFragment); + if (assembleBack && assembleFront) { + backFragment.parent = null; + frontFragment.parent = null; + backFragment.sibling = null; + frontFragment.sibling = null; + primitive.backFragment = null; + primitive.frontFragment = null; + PolyPrimitive.destroyPolyPrimitive(backFragment); + PolyPrimitive.destroyPolyPrimitive(frontFragment); + return true; + } else { + if (assembleBack) { + addPrimitives.push(backFragment); + } + if (assembleFront) { + addPrimitives.push(frontFragment); + } + } + } + } + return false; + } + + /** + * @private + * Очистка списков + */ + private function clearPrimitives():void { + changedPrimitives.clear(); + } + + /** + * Корневой объект сцены. + */ + public function get root():Object3D { + return _root; + } + + /** + * @private + */ + public function set root(value:Object3D):void { + // Если ещё не является корневым объектом + if (_root != value) { + // Если устанавливаем не пустой объект + if (value != null) { + // Если объект был в другом объекте + if (value._parent != null) { + // Удалить его оттуда + value._parent._children.remove(value); + } else { + // Если объект был корневым в сцене + if (value._scene != null && value._scene._root == value) { + value._scene.root = null; + } + } + // Удаляем ссылку на родителя + value.setParent(null); + // Указываем сцену + value.setScene(this); + // Устанавливаем уровни + value.setLevel(0); + } + + // Если был корневой объект + if (_root != null) { + // Удаляем ссылку на родителя + _root.setParent(null); + // Удаляем ссылку на камеру + _root.setScene(null); + } + + // Сохраняем корневой объект + _root = value; + } + } + + /** + * Флаг активности анализа сплиттеров. + * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей + * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми. + * + *

Изменением свойства splitBalance можно влиять на конечный вид BSP-дерева. + * + * @see #splitBalance + * @default true + */ + public function get splitAnalysis():Boolean { + return _splitAnalysis; + } + + /** + * @private + */ + public function set splitAnalysis(value:Boolean):void { + if (_splitAnalysis != value) { + _splitAnalysis = value; + addOperation(updateBSPOperation); + } + } + + /** + * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров. + * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева). + * + * @see #splitAnalysis + * @default 0 + */ + public function get splitBalance():Number { + return _splitBalance; + } + + /** + * @private + */ + public function set splitBalance(value:Number):void { + value = (value < 0) ? 0 : ((value > 1) ? 1 : value); + if (_splitBalance != value) { + _splitBalance = value; + if (_splitAnalysis) { + addOperation(updateBSPOperation); + } + } + } + + /** + * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей + * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого + * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу. + * + * @param container контейнер для отрисовки дерева + */ + public function drawBSP(container:Sprite):void { + + container.graphics.clear(); + while (container.numChildren > 0) { + container.removeChildAt(0); + } + if (bsp != null) { + drawBSPNode(bsp, container, 0, 0, 1); + } + } + + /** + * @private + * Отрисовка узла BSP-дерева при визуализации + * + * @param node + * @param container + * @param x + * @param y + * @param size + */ + private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void { + var s:Shape = new Shape(); + container.addChild(s); + s.x = x; + s.y = y; + var color:uint = 0xFF0000; + var primitive:PolyPrimitive; + if (node.primitive != null) { + primitive = node.primitive; + } else { + if (node.frontPrimitives != null) { + primitive = node.frontPrimitives.peek(); + } + } + if (primitive != null) { + if (primitive.face._surface != null && primitive.face._surface._material != null) { + if (primitive.face._surface._material is FillMaterial) { + color = FillMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is WireMaterial) { + color = WireMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is TextureMaterial) { + color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0); + } + } + } + + if (node == dummyNode) { + color = 0xFF00FF; + } + + s.graphics.beginFill(color); + s.graphics.drawCircle(0, 0, 3); + s.graphics.endFill(); + + var xOffset:Number = 100; + var yOffset:Number = 20; + if (node.back != null) { + container.graphics.lineStyle(0, 0x660000); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x - xOffset*size, y + yOffset); + drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8); + } + if (node.front != null) { + container.graphics.lineStyle(0, 0x006600); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x + xOffset*size, y + yOffset); + drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as new file mode 100644 index 0000000..6e69daa --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as @@ -0,0 +1,350 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов, + * визуализирующих грани объекта. + */ + public class Surface { + // Операции + /** + * @private + * Изменение набора граней + */ + alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this); + /** + * @private + * Изменение материала + */ + alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Материал + */ + alternativa3d var _material:SurfaceMaterial; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + + /** + * Создание экземпляра поверхности. + */ + public function Surface() {} + + /** + * Добавление грани в поверхность. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань + * + * @see Face + */ + public function addFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (_faces.has(f)) { + // Если грань уже в поверхности + throw new FaceExistsError(f, this); + } + + // Проверяем грань на нахождение в другой поверхности + if (f._surface != null) { + // Удаляем её из той поверхности + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Добавляем грань в поверхность + _faces.add(f); + f.addToSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Удаление грани из поверхности. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function removeFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (!_faces.has(f)) { + // Если грань не в поверхности + throw new FaceNotFoundError(f, this); + } + + // Удаляем грань из поверхности + _faces.remove(f); + f.removeFromSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности. + */ + public function get material():SurfaceMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SurfaceMaterial):void { + if (_material != value) { + // Если был материал + if (_material != null) { + // Удалить материал из поверхности + _material.removeFromSurface(this); + // Удалить материал из меша + if (_mesh != null) { + _material.removeFromMesh(_mesh); + // Удалить материал из сцены + if (_mesh._scene != null) { + _material.removeFromScene(_mesh._scene); + } + } + } + // Если новый материал + if (value != null) { + // Если материал был в другой поверхности + if (value._surface != null) { + // Удалить его оттуда + value._surface.material = null; + } + // Добавить материал в поверхность + value.addToSurface(this); + // Добавить материал в меш + if (_mesh != null) { + value.addToMesh(_mesh); + // Добавить материал в сцену + if (_mesh._scene != null) { + value.addToScene(_mesh._scene); + } + } + } + // Сохраняем материал + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Набор граней поверхности. + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Полигональный объект, которому принадлежит поверхность. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту, + * значение идентификатора равно null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getSurfaceId(this) : null; + } + + /** + * @private + * Добавление в сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeFacesOperation); + scene.removeOperation(changeMaterialOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + } + + /** + * @private + * Добавление к мешу + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + + // Добавляем в меш материал + if (_material != null) { + _material.addToMesh(mesh); + } + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + + // Удаляем из меша материал + if (_material != null) { + _material.removeFromMesh(mesh); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Удаление граней + */ + alternativa3d function removeFaces():void { + for (var key:* in _faces) { + var face:Face = key; + _faces.remove(face); + face.removeFromSurface(this); + } + } + + /** + * @private + * Изменение материала + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_mesh != null) { + _mesh.addOperationToScene(changeMaterialOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var length:uint = _faces.length; + var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : ""); + var i:uint = 0; + for (var key:* in _faces) { + var face:Face = key; + res += face.id + ((i < length - 1) ? ", " : ""); + i++; + } + res += "]"; + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..cceb91e --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,250 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на + * полигональный объект и грани этого объекта, которым она принадлежит. + */ + final public class Vertex { + // Операции + /** + * @private + * Изменение локальных координат + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт глобальных координат + */ + alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Координаты точки + */ + alternativa3d var _coords:Point3D; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра вершины. + * + * @param x координата вершины по оси X + * @param y координата вершины по оси Y + * @param z координата вершины по оси Z + */ + public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) { + _coords = new Point3D(x, y, z); + + // Изменение координат инициирует пересчёт глобальных координат + changeCoordsOperation.addSequel(calculateCoordsOperation); + } + + /** + * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины + */ + private function calculateCoords():void { + globalCoords.copy(_coords); + globalCoords.transform(_mesh.transformation); + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * координата вершины по оси X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * координата вершины по оси Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * координата вершины по оси Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * Координаты вершины. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * Полигональный объект, которому принадлежит вершина. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса + * altertnativa.engine3d.core.Face. + * + * @see Face + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getVertexId(this) : null; + } + + /** + * @private + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену расчитать глобальные координаты + scene.addOperation(calculateCoordsOperation); + } + + /** + * @private + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateCoordsOperation); + scene.removeOperation(changeCoordsOperation); + } + + /** + * @private + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation); + // Удалить зависимые грани + for (var key:* in _faces) { + var face:Face = key; + mesh.removeFace(face); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * @param face + */ + alternativa3d function addToFace(face:Face):void { + // Подписка грани на операции + changeCoordsOperation.addSequel(face.calculateUVOperation); + changeCoordsOperation.addSequel(face.calculateNormalOperation); + // Добавить грань в список + _faces.add(face); + } + + /** + * @private + * @param face + */ + alternativa3d function removeFromFace(face:Face):void { + // Отписка грани от операций + changeCoordsOperation.removeSequel(face.calculateUVOperation); + changeCoordsOperation.removeSequel(face.calculateNormalOperation); + // Удалить грань из списка + _faces.remove(face); + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as new file mode 100644 index 0000000..928f8ae --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as @@ -0,0 +1,68 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Operation; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.materials.SurfaceMaterial; + + import flash.display.Graphics; + import flash.display.Sprite; + + use namespace alternativa3d; + + /** + * @private + */ + public class Skin extends Sprite { + + /** + * @private + * Графика скина (для быстрого доступа) + */ + alternativa3d var gfx:Graphics = graphics; + + /** + * @private + * Ссылка на следующий скин + */ + alternativa3d var nextSkin:Skin; + + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + /** + * @private + * Материал + */ + alternativa3d var material:SurfaceMaterial; + + // Хранилище неиспользуемых скинов + static private var collector:Array = new Array(); + + /** + * @private + * Создание скина. + */ + static alternativa3d function createSkin():Skin { + // Достаём скин из коллектора + var skin:Skin = collector.pop(); + // Если коллектор пуст, создаём новый скин + if (skin == null) { + skin = new Skin(); + } + return skin; + } + + /** + * @private + * Удаление скина, все ссылки должны быть почищены. + */ + static alternativa3d function destroySkin(skin:Skin):void { + collector.push(skin); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/display/View.as b/Alternativa3D5/5.3/alternativa/engine3d/display/View.as new file mode 100644 index 0000000..b13659a --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/display/View.as @@ -0,0 +1,149 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + + import flash.display.Sprite; + import flash.geom.Rectangle; + import alternativa.engine3d.core.Camera3D; + + use namespace alternativa3d; + + /** + * Область для вывода изображения с камеры. + */ + public class View extends Sprite { + + /** + * @private + * Область отрисовки спрайтов + */ + alternativa3d var canvas:Sprite; + + private var _camera:Camera3D; + + /** + * @private + * Ширина области вывода + */ + alternativa3d var _width:Number; + /** + * @private + * Высота области вывода + */ + alternativa3d var _height:Number; + + /** + * Создание экземпляра области вывода. + * + * @param camera камера, изображение с которой должно выводиться + * @param width ширина области вывода + * @param height высота области вывода + */ + public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) { + canvas = new Sprite(); + canvas.mouseEnabled = false; + canvas.mouseChildren = false; + canvas.tabEnabled = false; + canvas.tabChildren = false; + addChild(canvas); + + this.camera = camera; + this.width = width; + this.height = height; + } + + /** + * Камера с которой ведётся отображение. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + // Если была камера + if (_camera != null) { + // Удалить камеру + _camera.removeFromView(this); + } + // Если новая камера + if (value != null) { + // Если камера была в другом вьюпорте + if (value._view != null) { + // Удалить её оттуда + value._view.camera = null; + } + // Добавить камеру + value.addToView(this); + } else { + // Зачистка скинов + if (canvas.numChildren > 0) { + var skin:Skin = Skin(canvas.getChildAt(0)); + while (skin != null) { + // Сохраняем следующий + var next:Skin = skin.nextSkin; + // Удаляем из канваса + canvas.removeChild(skin); + // Очистка скина + if (skin.material != null) { + skin.material.clear(skin); + } + // Зачищаем ссылки + skin.nextSkin = null; + skin.primitive = null; + skin.material = null; + // Удаляем + Skin.destroySkin(skin); + // Следующий устанавливаем текущим + skin = next; + } + } + } + // Сохраняем камеру + _camera = value; + } + } + + /** + * Ширина области вывода в пикселях. + */ + override public function get width():Number { + return _width; + } + + /** + * @private + */ + override public function set width(value:Number):void { + if (_width != value) { + _width = value; + canvas.x = _width*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Высота области вывода в пикселях. + */ + override public function get height():Number { + return _height; + } + + /** + * @private + */ + override public function set height(value:Number):void { + if (_height != value) { + _height = value; + canvas.y = _height*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as new file mode 100644 index 0000000..bcf6397 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.errors { + + import alternativa.utils.TextUtils; + + /** + * Базовый класс для ошибок 3d-engine. + */ + public class Engine3DError extends Error { + + /** + * Источник ошибки - объект в котором произошла ошибка. + */ + public var source:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param source источник ошибки + */ + public function Engine3DError(message:String = "", source:Object = null) { + super(message); + this.source = source; + this.name = "Engine3DError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as new file mode 100644 index 0000000..ecc7f59 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as @@ -0,0 +1,35 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Surface; + + /** + * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте. + */ + public class FaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани, которая уже содержится в объекте + * @param source источник ошибки + */ + public function FaceExistsError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else if (source is Surface) { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 already exists."; + } else { + message += "%1. Face with ID '%2' already exists."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as new file mode 100644 index 0000000..3341f63 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая недостаточное количество вершин для создания грани. + * Для создания грани должно быть указано не менее трех вершин. + */ + public class FaceNeedMoreVerticesError extends Engine3DError { + + /** + * Количество переданных для создания грани вершин + */ + public var count:uint; + + /** + * Создание экземпляра класса. + * + * @param mesh объект, в котором произошла ошибка + * @param count количество вершин, переданное для создания грани + */ + public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) { + super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh); + this.count = count; + this.name = "FaceNeedMoreVerticesError"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as new file mode 100644 index 0000000..912d6a4 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, если грань не найдена в объекте. + */ + public class FaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани + * @param source объект, в котором произошла ошибка + */ + public function FaceNotFoundError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 not found."; + } else { + message += "%1. Face with ID '%2' not found."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as new file mode 100644 index 0000000..fbf4666 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + + + /** + * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован. + */ + public class InvalidIDError extends Engine3DError { + /** + * Зарезервированный идентификатор + */ + public var id:Object; + + /** + * Создание экземпляра класса. + * + * @param id идентификатор + * @param source объект, в котором произошла ошибка + */ + public function InvalidIDError(id:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh %2. "; + } else if (source is Surface) { + message = "Surface %2. "; + } + super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source); + this.id = id; + this.name = "InvalidIDError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as new file mode 100644 index 0000000..8d61bd0 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, связанная с нарушением иерархии объектов сцены. + */ + public class Object3DHierarchyError extends Engine3DError + { + + /** + * Объект сцены, нарушающий иерархию + */ + public var object:Object3D; + + /** + * Создание экземпляра класса. + * + * @param object объект, нарушающий иерархию + * @param source источник ошибки + */ + public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source); + this.object = object; + this.name = "Object3DHierarchyError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as new file mode 100644 index 0000000..d1ddd1e --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены. + */ + public class Object3DNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param object ненайденный объект сцены + * @param source объект сцены, в котором произошла ошибка + */ + public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source); + this.name = "Object3DNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as new file mode 100644 index 0000000..7743a53 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Ошибка, обозначающая, что объект уже присутствует в контейнере. + */ + public class ObjectExistsError extends Engine3DError { + + /** + * Экземпляр или идентификатор объекта, который уже присутствует в контейнере + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object объект, который уже присутствует в контейнере + * @param source объект, вызвавший ошибку + */ + public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectExistsError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as new file mode 100644 index 0000000..87b3d14 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Необходимый объект не был найден в контейнере. + */ + public class ObjectNotFoundError extends Engine3DError { + + /** + * Объект, который отсутствует в контейнере. + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object отсутствующий объект + * @param source объект, вызвавший ошибку + */ + public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as new file mode 100644 index 0000000..e13bee7 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность уже присутствует в контейнере. + */ + public class SurfaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая уже присутствует в контейнере + * @param mesh объект, вызвавший ошибку + */ + public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh); + this.name = "SurfaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as new file mode 100644 index 0000000..05457bf --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as @@ -0,0 +1,31 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность не найдена в контейнере. + */ + public class SurfaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая отсутствует в объекте + * @param mesh объект, который вызвал ошибку + */ + public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) { + if (mesh == null) { + + } + if (surface is Surface) { + message = "Mesh %1. Surface %2 not found."; + } else { + message = "Mesh %1. Surface with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, surface), surface, mesh); + this.name = "SurfaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as new file mode 100644 index 0000000..9f0fff5 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина уже содержится в объекте. + */ + public class VertexExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая уже есть в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh); + this.name = "VertexExistsError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as new file mode 100644 index 0000000..edf80aa --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина не найдена в объекте. + */ + public class VertexNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая не найдена в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) { + if (vertex is Vertex) { + message = "Mesh %1. Vertex %2 not found."; + } else { + message = "Mesh %1. Vertex with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh); + this.name = "VertexNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..565a84f --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,1053 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Texture; + import alternativa.utils.ColorUtils; + import alternativa.utils.MathUtils; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.errors.IOError; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + import alternativa.types.Map; + import flash.events.SecurityErrorEvent; + + use namespace alternativa3d; + + /** + * Загрузчик моделей в формате 3DS. + * + *

Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если + * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели. + * + *

Из файла модели загружаются: + *

+ * + * Порядок загрузки каждого объекта из 3DS-файла: + * + * + * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы. + */ + public class Loader3DS extends EventDispatcher { + + /** + * Коэффициент пересчёта в дюймы. + */ + public static const INCHES:Number = 1; + + /** + * Коэффициент пересчёта в футы. + */ + public static const FEET:Number = 0.0833333334; + /** + * Коэффициент пересчёта в мили. + */ + public static const MILES:Number = 0.0000157828; + /** + * Коэффициент пересчёта в миллиметры. + */ + public static const MILLIMETERS:Number = 25.4000000259; + /** + * Коэффициент пересчёта в сантиметры. + */ + public static const CENTIMETERS:Number = 2.5400000025; + /** + * Коэффициент пересчёта в метры. + */ + public static const METERS:Number = 0.0254; + /** + * Коэффициент пересчёта в километры. + */ + public static const KILOMETERS:Number = 0.0000254; + + private static var stubBitmapData:BitmapData; + + private var _content:Object3D; + private var version:uint; + private var objectDatas:Map; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var urlLoader:URLLoader; + private var data:ByteArray; + + private var counter:uint; + private var sequence:Array; + private var loader:Loader; + private var context:LoaderContext; + private var path:String; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = INCHES; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + urlLoader.close(); + } catch (e:Error) { + } + try { + loader.close(); + } catch (e:Error) { + } + } + + /** + * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL 3DS-файла + * @param context LoaderContext для загрузки файлов текстур + */ + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + _content = null; + version = 0; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + sequence = null; + + if (urlLoader == null) { + urlLoader = new URLLoader(); + urlLoader.dataFormat = URLLoaderDataFormat.BINARY; + urlLoader.addEventListener(Event.COMPLETE, on3DSLoad); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + urlLoader.load(new URLRequest(url)); + } + + private function on3DSLoad(e:Event):void { + data = urlLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + private function on3DSError(e:Event):void { + dispatchEvent(e); + } + + private function loadBitmaps():void { + if (bitmaps != null) { + counter = 0; + sequence = new Array(); + for (var filename:String in bitmaps) { + sequence.push(filename); + } + if (loader == null) { + loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNextBitmap); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNextBitmap); + } + loader.load(new URLRequest(path + sequence[counter]), context); + } else { + buildContent(); + } + } + + private function loadNextBitmap(e:Event = null):void { + if (!(e is IOErrorEvent)) { + bitmaps[sequence[counter]] = Bitmap(loader.content).bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[sequence[counter]] = stubBitmapData; + } + counter++; + if (counter < sequence.length) { + loader.load(new URLRequest(path + sequence[counter]), context); + } else { + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Object3D(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = MathUtils.toRadian(mapData.rotation); + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Array().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Array().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + } else { + var object:Object3D = new Object3D(objectName); + animationData.object = object; + buildObject(animationData); + } + } + + buildHierarchy(_content, 0, length - 1); + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + buildMesh(mesh, objectData, null); + _content.addChild(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Object3D = animationData.object; + if (animationData.position != null) { + object.x = animationData.position.x * units; + object.y = animationData.position.y * units; + object.z = animationData.position.z * units; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + object.mobility = mobility; + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var key:*; + var vertex:Vertex; + var face:Face; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Point3D = objectData.vertices[i]; + objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i); + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + objectData.matrix.d -= animationData.pivot.x; + objectData.matrix.h -= animationData.pivot.y; + objectData.matrix.l -= animationData.pivot.z; + + // Трансформируем вершины + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.transform(objectData.matrix); + } + } + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.multiply(units); + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i); + if (objectData.uvs != null) { + face.aUV = objectData.uvs[faceData.a]; + face.bUV = objectData.uvs[faceData.b]; + face.cUV = objectData.uvs[faceData.c]; + } + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + var surfaceData:SurfaceData = objectData.surfaces[surfaceId]; + var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId); + if (materialData.diffuseMap != null) { + var bmp:BitmapData = bitmaps[materialData.diffuseMap.filename]; + surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + length = surfaceData.faces.length; + for (i = 0; i < length; i++) { + face = mesh.getFaceById(surfaceData.faces[i]); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var m:Matrix = materialData.matrix; + var x:Number = face.aUV.x; + var y:Number = face.aUV.y; + face._aUV.x = m.a*x + m.b*y + m.tx; + face._aUV.y = m.c*x + m.d*y + m.ty; + x = face._bUV.x; + y = face._bUV.y; + face._bUV.x = m.a*x + m.b*y + m.tx; + face._bUV.y = m.c*x + m.d*y + m.ty; + x = face._cUV.x; + y = face._cUV.y; + face._cUV.x = m.a*x + m.b*y + m.tx; + face._cUV.y = m.c*x + m.d*y + m.ty; + } + } + } else { + surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100); + } + } + } else { + // Поверхность по умолчанию + var defaultSurface:Surface = mesh.createSurface(); + // Добавляем грани + for (var faceId:String in mesh._faces) { + defaultSurface.addFace(mesh._faces[faceId]); + } + defaultSurface.material = new WireMaterial(0, 0x7F7F7F); + } + } + + private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void { + if (begin <= end) { + var animation:AnimationData = animationDatas[begin]; + var object:Object3D = animation.object; + parent.addChild(object); + + var parentIndex:uint = animation.parentIndex; + for (var i:uint = begin + 1; i <= end; i++) { + animation = animationDatas[i]; + if (parentIndex == animation.parentIndex) { + buildHierarchy(object, begin + 1, i - 1); + buildHierarchy(parent, i, end); + return; + } + } + buildHierarchy(object, begin + 1, end); + } + } + + + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte()); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + if (bitmaps == null) { + bitmaps = new Array(); + } + bitmaps[map.filename] = null; + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Map(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Array(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Array(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(); + object.matrix.a = data.readFloat(); + object.matrix.e = data.readFloat(); + object.matrix.i = data.readFloat(); + object.matrix.b = data.readFloat(); + object.matrix.f = data.readFloat(); + object.matrix.j = data.readFloat(); + object.matrix.c = data.readFloat(); + object.matrix.g = data.readFloat(); + object.matrix.k = data.readFloat(); + object.matrix.d = data.readFloat(); + object.matrix.h = data.readFloat(); + object.matrix.l = data.readFloat(); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Map(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Object3D { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D { + var res:Point3D = new Point3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.types.Matrix3D; +import alternativa.types.Point3D; + +import flash.geom.Matrix; +import alternativa.types.Map; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Array; + public var uvs:Array; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Map; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:uint; + public var pivot:Point3D; + public var position:Point3D; + public var rotation:Point3D; + public var scale:Point3D; +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as new file mode 100644 index 0000000..622eedb --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as @@ -0,0 +1,285 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.types.Map; + import alternativa.utils.ColorUtils; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * @private + * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ). + *

+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала. + */ + internal class LoaderMTL extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + private static const CMD_NEW_MATERIAL:String = "newmtl"; + private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd"; + private static const CMD_DISSOLVE:String = "d"; + private static const CMD_MAP_DIFFUSE:String = "map_Kd"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + // Загрузчик файла MTL + private var fileLoader:URLLoader; + // Загрузчик файлов текстур + private var bitmapLoader:Loader; + // Контекст загрузки для bitmapLoader + private var loaderContext:LoaderContext; + // Базовый URL файла MTL + private var baseUrl:String; + + // Библиотека загруженных материалов + private var _library:Map; + // Список материалов, имеющих диффузные текстуры + private var diffuseMaps:Map; + // Имя текущего материала + private var materialName:String; + // параметры текущего материала + private var currentMaterialInfo:MaterialInfo = new MaterialInfo(); + + alternativa3d static var stubBitmapData:BitmapData; + + /** + * Создаёт новый экземпляр класса. + */ + public function LoaderMTL() { + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + fileLoader.close(); + } catch (e:Error) { + } + } + + /** + * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса + * alternativa.engine3d.loaders.MaterialInfo. + * @see alternativa.engine3d.loaders.MaterialInfo + */ + public function get library():Map { + return _library; + } + + /** + * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и + * формирование библиотеки материалов. После окончания работы метода посылается сообщение + * Event.COMPLETE и становится доступна библиотека материалов через свойство library. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку. + *

+ * @param url URL MTL-файла + * @param loaderContext LoaderContext для загрузки файлов текстур + * + * @see #library + */ + public function load(url:String, loaderContext:LoaderContext = null):void { + this.loaderContext = loaderContext; + baseUrl = url.substring(0, url.lastIndexOf("/") + 1); + + if (fileLoader == null) { + fileLoader = new URLLoader(); + fileLoader.addEventListener(Event.COMPLETE, parseMTLFile); + fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError); + fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + + bitmapLoader = new Loader(); + bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete); + bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete); + } + + try { + fileLoader.close(); + bitmapLoader.close(); + } catch (e:Error) { + // Пропуск ошибки при попытке закрытия неактивных загрузчиков + } + + fileLoader.load(new URLRequest(url)); + } + + /** + * Разбор содержимого загруженного файла материалов. + */ + private function parseMTLFile(e:Event = null):void { + var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE); + _library = new Map(); + diffuseMaps = new Map(); + for each (var line:String in lines) { + parseLine(line); + } + defineMaterial(); + + if (diffuseMaps.isEmpty()) { + // Текстур нет, загрузка окончена + dispatchEvent(new Event(Event.COMPLETE)); + } else { + // Загрузка файлов текстур + loadNextBitmap(); + } + } + + /** + * Разбор строки файла. + * + * @param line строка файла + */ + private function parseLine(line:String):void { + line = line.replace(REGEXP_TRIM,"$1") + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + case CMD_NEW_MATERIAL: + defineMaterial(parts); + break; + case CMD_DIFFUSE_REFLECTIVITY: + readDiffuseReflectivity(parts); + break; + case CMD_DISSOLVE: + readAlpha(parts); + break; + case CMD_MAP_DIFFUSE: + parseDiffuseMapLine(parts); + break; + } + } + + /** + * Определение нового материала. + */ + private function defineMaterial(parts:Array = null):void { + if (materialName != null) { + _library[materialName] = currentMaterialInfo; + } + if (parts != null) { + materialName = parts[1]; + currentMaterialInfo = new MaterialInfo(); + } + } + + /** + * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей + * версии движка данные коэффициенты преобразуются в цвет материала. + */ + private function readDiffuseReflectivity(parts:Array):void { + var r:Number = Number(parts[1]); + // Проверка, заданы ли коэффициенты в виде r g b + if (!isNaN(r)) { + var g:Number = Number(parts[2]); + var b:Number = Number(parts[3]); + currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b); + } + } + + /** + * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом + * (не поддерживается параметр -halo). + */ + private function readAlpha(parts:Array):void { + var alpha:Number = Number(parts[1]); + if (!isNaN(alpha)) { + currentMaterialInfo.alpha = alpha; + } + } + + /** + * Разбор строки, задающей текстурную карту для диффузного отражения. + */ + private function parseDiffuseMapLine(parts:Array):void { + var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts); + diffuseMaps[materialName] = info; + } + + /** + * Загрузка файла следующей текстуры. + */ + private function loadNextBitmap():void { + // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры + for (materialName in diffuseMaps) { + break; + } + bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext); + } + + /** + * + */ + private function createStubBitmap():void { + if (stubBitmapData == null) { + var size:uint = 10; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + } + + /** + * Обработка результата загрузки файла текстуры. + */ + private function onBitmapLoadComplete(e:Event):void { + var bmd:BitmapData; + + if (e is ErrorEvent) { + if (stubBitmapData == null) { + createStubBitmap(); + } + bmd = stubBitmapData; + } else { + bmd = Bitmap(bitmapLoader.content).bitmapData; + } + + var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName]; + delete diffuseMaps[materialName]; + var info:MaterialInfo = _library[materialName]; + + info.bitmapData = bmd; + info.repeat = mtlInfo.repeat; + info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV); + info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV); + info.textureFileName = mtlInfo.fileName; + + if (diffuseMaps.isEmpty()) { + dispatchEvent(new Event(Event.COMPLETE)); + } else { + loadNextBitmap(); + } + } + + /** + * + * @param e + */ + private function onError(e:IOErrorEvent):void { + dispatchEvent(e); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as new file mode 100644 index 0000000..a70b200 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as @@ -0,0 +1,506 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.types.Texture; + + import flash.display.BlendMode; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные + * модели помещаются в один контейнер Object3D. + *

+ * Поддерживаюся следующие команды формата OBJ: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КомандаОписаниеДействие
o object_nameОбъявление нового объекта с именем object_nameЕсли для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем, + * иначе у текущего объекта просто меняется имя на указанное.
v x y zОбъявление вершины с координатами x y zВершина помещается в общий список вершин сцены для дальнейшего использования
vt u [v]Объявление текстурной вершины с координатами u vВершина помещается в общий список текстурных вершин сцены для дальнейшего использования
f v0[/vt0] v1[/vt1] ... vN[/vtN]Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность + * текущего объекта, соответствующую текущему материалу.
usemtl material_nameУстановка текущего материала с именем material_nameС момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность, + * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
mtllib file1 file2 ...Объявление файлов, содержащих определения материаловВыполняется загрузка файлов и формирование библиотеки материалов
+ * + *

+ * Пример использования: + *

+	 * var loader:LoaderOBJ = new LoaderOBJ();
+	 * loader.addEventListener(Event.COMPLETE, onLoadingComplete);
+	 * loader.load("foo.obj");
+	 * 
+	 * function onLoadingComplete(e:Event):void {
+	 *   scene.root.addChild(e.target.content);
+	 * }
+	 * 
+ */ + public class LoaderOBJ extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + + private static const CMD_OBJECT_NAME:String = "o"; + private static const CMD_VERTEX:String = "v"; + private static const CMD_TEXTURE_VERTEX:String = "vt"; + private static const CMD_FACE:String = "f"; + private static const CMD_MATERIAL_LIB:String = "mtllib"; + private static const CMD_USE_MATERIAL:String = "usemtl"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private var basePath:String; + private var objLoader:URLLoader; + private var mtlLoader:LoaderMTL; + private var loaderContext:LoaderContext; + private var loadMaterials:Boolean; + // Объект, содержащий все определённые в obj файле объекты + private var _content:Object3D; + // Текущий конструируемый объект + private var currentObject:Mesh; + // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта + private var vIndexStart:int = 0; + // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта + private var vtIndexStart:int = 0; + // Глобальный массив вершин, определённых во входном файле + private var globalVertices:Array; + // Глобальный массив текстурных вершин, определённых во входном файле + private var globalTextureVertices:Array; + // Имя текущего активного материала. Если значение равно null, то активного материала нет. + private var currentMaterialName:String; + // Массив граней текущего объекта, которым назначен текущий материал + private var materialFaces:Array; + // Массив имён файлов, содержащих определения материалов + private var materialFileNames:Array; + private var currentMaterialFileIndex:int; + private var materialLibrary:Map; + + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * При установленном значении true выполняется преобразование координат геометрических вершин посредством + * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена + * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z). + */ + public var rotateModel:Boolean; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function LoaderOBJ() { + } + + /** + * Контейнер, содержащий все загруженные из OBJ-файла модели. + */ + public function get content():Object3D { + return _content; + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + objLoader.close(); + } catch (e:Error) { + } + mtlLoader.close(); + } + + /** + * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL OBJ-файла + * @param loadMaterials флаг загрузки материалов. Если указано значение true, будут обработаны все файлы + * материалов, указанные в исходном OBJ-файле. + * @param context LoaderContext для загрузки файлов текстур + * + * @see #content + */ + public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void { + _content = null; + this.loadMaterials = loadMaterials; + this.loaderContext = context; + basePath = url.substring(0, url.lastIndexOf("/") + 1); + if (objLoader == null) { + objLoader = new URLLoader(); + objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete); + objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } else { + close(); + } + objLoader.load(new URLRequest(url)); + } + + /** + * Обработка окончания загрузки obj файла. + * + * @param e + */ + private function onObjLoadComplete(e:Event):void { + parse(objLoader.data); + } + + /** + * Обработка ошибки при загрузке. + * + * @param e + */ + private function onObjLoadError(e:ErrorEvent):void { + dispatchEvent(e); + } + + /** + * Метод выполняет разбор данных, полученных из obj файла. + * + * @param s содержимое obj файла + * @param materialLibrary библиотека материалов + * @return объект, содержащий все трёхмерные объекты, определённые в obj файле + */ + private function parse(data:String):void { + _content = new Object3D(); + currentObject = new Mesh(); + currentObject.mobility = mobility; + _content.addChild(currentObject); + + globalVertices = new Array(); + globalTextureVertices = new Array(); + materialFileNames = new Array(); + + var lines:Array = data.split(REGEXP_SPLIT_FILE); + for each (var line:String in lines) { + parseLine(line); + } + moveFacesToSurface(); + // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах. + if (loadMaterials && materialFileNames.length > 0) { + loadMaterialsLibrary(); + } else { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * + */ + private function parseLine(line:String):void { + line = line.replace(REGEXP_TRIM,"$1"); + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + // Объявление нового объекта + case CMD_OBJECT_NAME: + defineObject(parts[1]); + break; + // Объявление вершины + case CMD_VERTEX: + globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление текстурной вершины + case CMD_TEXTURE_VERTEX: + globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление грани + case CMD_FACE: + createFace(parts); + break; + case CMD_MATERIAL_LIB: + storeMaterialFileNames(parts); + break; + case CMD_USE_MATERIAL: + setNewMaterial(parts); + break; + } + } + + /** + * Объявление нового объекта. + * + * @param objectName имя объекта + */ + private function defineObject(objectName:String):void { + if (currentObject.faces.length == 0) { + // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя + currentObject.name = objectName; + } else { + // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект + moveFacesToSurface(); + currentObject = new Mesh(objectName); + currentObject.mobility = mobility; + _content.addChild(currentObject); + } + vIndexStart = globalVertices.length; + vtIndexStart = globalTextureVertices.length; + } + + /** + * Создание грани в текущем объекте. + * + * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1 + */ + private function createFace(parts:Array):void { + // Стартовый индекс вершины в объекте для добавляемой грани + var startVertexIndex:int = currentObject.vertices.length; + // Создание вершин в объекте + var faceVertexCount:int = parts.length - 1; + var vtIndices:Array = new Array(3); + // Массив идентификаторов вершин грани + var faceVertices:Array = new Array(faceVertexCount); + for (var i:int = 0; i < faceVertexCount; i++) { + var indices:Array = parts[i + 1].split("/"); + // Создание вершины + var vIdx:int = int(indices[0]); + // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1. + // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта. + var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx; + + var vertex:Vertex = currentObject.vertices[actualIndex]; + // Если вершины нет в объекте, она добавляется + if (vertex == null) { + var p:Point3D = globalVertices[actualIndex]; + if (rotateModel) { + // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X + vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex); + } else { + vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex); + } + } + faceVertices[i] = vertex; + + // Запись индекса текстурной вершины + if (i < 3) { + vtIndices[i] = int(indices[1]); + } + } + // Создание грани + var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length); + // Установка uv координат + if (vtIndices[0] != 0) { + p = globalTextureVertices[vtIndices[0] - 1]; + face.aUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[1] - 1]; + face.bUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[2] - 1]; + face.cUV = new Point(p.x, p.y); + } + // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте + if (currentMaterialName != null) { + materialFaces.push(face); + } + } + + /** + * Загрузка библиотек материалов. + * + * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1 + */ + private function storeMaterialFileNames(parts:Array):void { + for (var i:int = 1; i < parts.length; i++) { + materialFileNames.push(parts[i]); + } + } + + /** + * Установка нового текущего материала. + * + * @param parts массив, во втором элементе которого содержится имя материала + */ + private function setNewMaterial(parts:Array):void { + // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта + moveFacesToSurface(); + // Установка нового текущего материала + currentMaterialName = parts[1]; + } + + /** + * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала. + */ + private function moveFacesToSurface():void { + if (currentMaterialName != null && materialFaces.length > 0) { + if (currentObject.hasSurface(currentMaterialName)) { + // При наличии поверхности с таким идентификатором, грани добавляются в неё + var surface:Surface = currentObject.getSurfaceById(currentMaterialName); + for each (var face:* in materialFaces) { + surface.addFace(face); + } + } else { + // При отсутствии поверхности с таким идентификатором, создатся новая поверхность + currentObject.createSurface(materialFaces, currentMaterialName); + } + } + materialFaces = []; + } + + /** + * Загрузка материалов. + */ + private function loadMaterialsLibrary():void { + if (mtlLoader == null) { + mtlLoader = new LoaderMTL(); + mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete); + mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } + materialLibrary = new Map(); + + currentMaterialFileIndex = -1; + loadNextMaterialFile(); + } + + /** + * Обработка успешной загрузки библиотеки материалов. + */ + private function onMaterialFileLoadComplete(e:Event):void { + materialLibrary.concat(mtlLoader.library); + // Загрузка следующего файла материалов + loadNextMaterialFile(); + } + + /** + * + */ + private function loadNextMaterialFile():void { + currentMaterialFileIndex++; + if (currentMaterialFileIndex == materialFileNames.length) { + setMaterials(); + dispatchEvent(new Event(Event.COMPLETE)); + } else { + mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext); + } + } + + /** + * Установка материалов. + */ + private function setMaterials():void { + if (materialLibrary != null) { + for (var objectKey:* in _content.children) { + var object:Mesh = objectKey; + for (var surfaceKey:* in object.surfaces) { + var surface:Surface = object.surfaces[surfaceKey]; + // Поверхности имеют идентификаторы, соответствующие именам материалов + var materialInfo:MaterialInfo = materialLibrary[surfaceKey]; + if (materialInfo != null) { + if (materialInfo.bitmapData == null) { + surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode); + } else { + surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize); + } + } + } + } + } + } + + /** + * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено + * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо + * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения, + * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину + * смещения. + * + * @param surface поверхность, грани которой обрабатываюся + * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y + * указывает смещение по V. + * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования + * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V. + */ + private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void { + for (var key:* in surface.faces) { + var face:Face = key; + var uv:Point = face.aUV; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.aUV = uv; + uv = face.bUV; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.bUV = uv; + uv = face.cUV; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.cUV = uv; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as new file mode 100644 index 0000000..4bf9e57 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.loaders { + /** + * @private + * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора + * описания текстуры. + * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/ + */ + internal class MTLTextureMapInfo { + + // Ассоциация параметров команды объявления текстуры и методов для их чтения + private static const optionReaders:Object = { + "-clamp": clampReader, + "-o": offsetReader, + "-s": sizeReader, + + "-blendu": stubReader, + "-blendv": stubReader, + "-bm": stubReader, + "-boost": stubReader, + "-cc": stubReader, + "-imfchan": stubReader, + "-mm": stubReader, + "-t": stubReader, + "-texres": stubReader + }; + + // Смещение в текстурном пространстве + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var offsetW:Number = 0; + + // Масштабирование текстурного пространства + public var sizeU:Number = 1; + public var sizeV:Number = 1; + public var sizeW:Number = 1; + + // Флаг повторения текстуры + public var repeat:Boolean = true; + // Имя файла текстуры + public var fileName:String; + + /** + * Метод выполняет разбор данных о текстуре. + * + * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла. + * @return объект, содержащий данные о текстуре + */ + public static function parse(parts:Array):MTLTextureMapInfo { + var info:MTLTextureMapInfo = new MTLTextureMapInfo(); + // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры + var index:int = 1; + var reader:Function; + // Чтение параметров текстуры + while ((reader = optionReaders[parts[index]]) != null) { + index = reader(index, parts, info); + } + // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры + info.fileName = parts[index]; + return info; + } + + /** + * Читатель-заглушка. Пропускает все неподдерживаемые параметры. + */ + private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + index++; + var maxIndex:int = parts.length - 1; + while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) { + index++; + } + return index; + } + + /** + * Метод чтения параметров масштабирования текстурного пространства. + */ + private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.sizeU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.sizeV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.sizeW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметров смещения текстуры. + */ + private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.offsetU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.offsetV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.offsetW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметра повторения текстуры. + */ + private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.repeat = parts[index + 1] == "off"; + return index + 2; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as new file mode 100644 index 0000000..0a9a9ee --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders { + import flash.display.BitmapData; + import flash.geom.Point; + + /** + * @private + * Класс содержит обобщённую информацию о материале. + */ + internal class MaterialInfo { + public var color:uint; + public var alpha:Number; + + public var textureFileName:String; + public var bitmapData:BitmapData; + public var repeat:Boolean; + + public var mapOffset:Point; + public var mapSize:Point; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as new file mode 100644 index 0000000..0d3d0c3 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as @@ -0,0 +1,198 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + import alternativa.utils.ColorUtils; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + * Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу + * полигона линией заданной толщины и цвета. + */ + public class DevMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha прозрачность + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * + * @param camera + * @param skin + * @param length + * @param points + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + /* + //Мобильность + var param:int = skin.primitive.mobility*10; + */ + + /* + // Уровень распиленности + var param:int = 0; + var prm:PolyPrimitive = skin.primitive; + while (prm != null) { + prm = prm.parent; + param++; + } + param *= 10; + */ + + // Уровень в BSP-дереве + var param:int = 0; + var node:BSPNode = skin.primitive.node; + while (node != null) { + node = node.parent; + param++; + } + param *= 5; + + var c:uint = ColorUtils.rgb(param, param, param); + + if (camera._orthographic) { + gfx.beginFill(c); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(c); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as new file mode 100644 index 0000000..b661756 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.materials { + /** + * @private + */ + public final class DrawPoint { + + public var x:Number; + public var y:Number; + public var z:Number; + public var u:Number; + public var v:Number; + + public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) { + this.x = x; + this.y = y; + this.z = z; + this.u = u; + this.v = v; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..6372fac --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,167 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом материал может рисовать границу + * полигона линией заданной толщины и цвета. + */ + public class FillMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha прозрачность + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * + * @param camera + * @param skin + * @param length + * @param points + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..399304f --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов. + */ + public class Material { + + /** + * Создание клона материала. + * + * @return клон материала + */ + public function clone():Material { + return new Material(); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as new file mode 100644 index 0000000..96bc64f --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as @@ -0,0 +1,178 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов поверхности. + */ + public class SurfaceMaterial extends Material { + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Альфа + */ + alternativa3d var _alpha:Number; + /** + * @private + * Режим наложения + */ + alternativa3d var _blendMode:String = BlendMode.NORMAL; + /** + * @private + * Материал использует информация об UV-координатах + */ + alternativa3d var useUV:Boolean = false; + + /** + * Создание экземпляра класса. + * + * @param alpha прозрачность материала + * @param blendMode режим наложения цвета + */ + public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + _alpha = alpha; + _blendMode = blendMode; + } + + /** + * Поверхность материала. + */ + public function get surface():Surface { + return _surface; + } + + /** + * @private + * Добавление на сцену + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Добавление к мешу + * + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void {} + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void {} + + /** + * @private + * Добавление на поверхность + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Сохраняем поверхность + _surface = surface; + } + + /** + * @private + * Удаление с поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Удаляем ссылку на поверхность + _surface = null; + } + + /** + * Прозрачность материала. + */ + public function get alpha():Number { + return _alpha; + } + + /** + * @private + */ + public function set alpha(value:Number):void { + if (_alpha != value) { + _alpha = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Режим наложения цвета. + */ + public function get blendMode():String { + return _blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + if (_blendMode != value) { + _blendMode = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @private + */ + alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return true; + } + + /** + * @private + */ + alternativa3d function clear(skin:Skin):void { + skin.gfx.clear(); + } + + /** + * @private + */ + alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SurfaceMaterial(_alpha, _blendMode); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..b4b81c2 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,368 @@ +package alternativa.engine3d.materials { + import __AS3__.vec.Vector; + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.display.BitmapData; + import flash.display.Graphics; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, заполняющий полигон текстурой. Помимо наложения текстуры материал может рисовать границу полигона линией + * заданной толщины и цвета. + */ + public class TextureMaterial extends SurfaceMaterial { + + private static var stubBitmapData:BitmapData; + private static var stubMatrix:Matrix; + + private var gfx:Graphics; + private var textureMatrix:Matrix = new Matrix(); + private var focalLength:Number; + private var distortion:Number; + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + /** + * @private + * Повтор текстуры + */ + alternativa3d var _repeat:Boolean; + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + /** + * @private + * Точность перспективной коррекции + */ + alternativa3d var _precision:Number; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра текстурного материала. + * + * @param texture текстура материала + * @param alpha прозрачность материала + * @param repeat повтор текстуры при заполнении + * @param smooth сглаживание текстуры при увеличении масштаба + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + * @param precision точность перспективной коррекции. Может быть задана одной из констант класса + * TextureMaterialPrecision или числом типа Number. Во втором случае, чем меньшее значение будет + * установлено, тем более качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено + * на расчёт кадра. + */ + public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) { + super(alpha, blendMode); + _texture = texture; + _repeat = repeat; + _smooth = smooth; + _wireThickness = wireThickness; + _wireColor = wireColor; + _precision = precision; + useUV = true; + } + + /** + * @private + * @param primitive + * @return + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _texture != null; + } + + /** + * @private + * @param camera + * @param skin + * @param length + * @param points + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + gfx = skin.gfx; + + // Проверка на нулевую UV-матрицу + if (skin.primitive.face.uvMatrixBase == null) { + if (stubBitmapData == null) { + // Создание текстуры-заглушки + stubBitmapData = new BitmapData(2, 2, false, 0); + stubBitmapData.setPixel(0, 0, 0xFF00FF); + stubBitmapData.setPixel(1, 1, 0xFF00FF); + stubMatrix = new Matrix(10, 0, 0, 10, 0, 0); + } + gfx.beginBitmapFill(stubBitmapData, stubMatrix); + if (camera._orthographic) { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + return; + } + + if (camera._orthographic) { + // Расчитываем матрицу наложения текстуры + var face:Face = skin.primitive.face; + // Если матрица не расчитана, считаем + if (!camera.uvMatricesCalculated[face]) { + camera.calculateUVMatrix(face, _texture._width, _texture._height); + } + gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + focalLength = camera.focalLength; + //distortion = camera.focalDistortion*_precision; + + var front:int = 0; + var back:int = length - 1; + + var newFront:int = 1; + var newBack:int = (back > 0) ? (back - 1) : (length - 1); + var direction:Boolean = true; + + var a:DrawPoint = points[back]; + var b:DrawPoint; + var c:DrawPoint = points[front]; + + var drawVertices:Vector. = new Vector.(); + var drawUVTs:Vector. = new Vector.(); + + for (i = 0; i < length; i++) { + var p:DrawPoint = points[i]; + var t:Number = focalLength/p.z; + drawVertices[i << 1] = p.x*t; + drawVertices[(i << 1) + 1] = p.y*t; + drawUVTs.push(p.u, 1 - p.v, t); + } + + var drawIndices:Vector. = new Vector.(); + + while (front != newBack) { + if (direction) { +/* a = points[front]; + b = points[newFront]; + c = points[back]; + */ + drawIndices.push(front, newFront, back); + + front = newFront; + newFront = (front < length - 1) ? (front + 1) : 0; + } else { +/* a = points[newBack]; + b = points[back]; + c = points[front]; + */ + drawIndices.push(newBack, back, front); + + back = newBack; + newBack = (back > 0) ? (back - 1) : (length - 1); + } + + direction = !direction; + } + gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + gfx.drawTriangles(drawVertices, drawIndices, drawUVTs); + + } + } + + + /** + * Текстура материала. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get repeat():Boolean { + return _repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + if (_repeat != value) { + _repeat = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки полигона. Если значение отрицательное, то отрисовка линий не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки полигона. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Точность перспективной коррекции. + */ + public function get precision():Number { + return _precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + if (_precision != value) { + _precision = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as new file mode 100644 index 0000000..f5b2509 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as @@ -0,0 +1,37 @@ +package alternativa.engine3d.materials { + + /** + * Класс содержит константы точности перспективной коррекции текстурного материала. + */ + public class TextureMaterialPrecision { + + /** + * Адаптивная триангуляция не будет выполняться, только простая триангуляция + */ + public static const NONE:Number = -1; + /** + * Очень низкое качество адаптивной триангуляции + */ + public static const VERY_LOW:Number = 50; + /** + * Низкое качество адаптивной триангуляции + */ + public static const LOW:Number = 25; + /** + * Среднее качество адаптивной триангуляции + */ + public static const MEDIUM:Number = 10; + /** + * Высокое качество адаптивной триангуляции + */ + public static const HIGH:Number = 6; + /** + * Очень высокое качество адаптивной триангуляции + */ + public static const VERY_HIGH:Number = 3; + /** + * Адаптивная триангуляция выполняется в максимальном качестве для выбранного расширения. + */ + public static const BEST:Number = 1; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as new file mode 100644 index 0000000..61bbe19 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as @@ -0,0 +1,132 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + import alternativa.engine3d.core.PolyPrimitive; + + use namespace alternativa3d; + + /** + * Материал для рисования рёбер полигонов. + */ + public class WireMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + /** + * @private + * Толщина линий + */ + alternativa3d var _thickness:Number; + + /** + * Создание экземпляра класса. + * + * @param thickness толщина линий + * @param color цвет линий + * @param alpha прозрачность линий + * @param blendMode режим наложения цвета + */ + public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + _color = color; + _thickness = thickness; + } + + /** + * @private + * @param primitive + * @return + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _thickness >= 0; + } + + /** + * @private + * @param camera + * @param skin + * @param length + * @param points + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + gfx.moveTo(point.x, point.y); + for (i = 0; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 0; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет линий. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линий. Если толщина отрицательная, то отрисовка не происходит. + */ + public function get thickness():Number { + return _thickness; + } + + /** + * @private + */ + public function set thickness(value:Number):void { + if (_thickness != value) { + _thickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new WireMaterial(_thickness, _color, _alpha, _blendMode); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as new file mode 100644 index 0000000..8ad22a0 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as @@ -0,0 +1,17 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class Collision { + public var face:Face; + public var normal:Point3D; + public var offset:Number; + public var point:Point3D; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as new file mode 100644 index 0000000..d0dc0a5 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as @@ -0,0 +1,44 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + */ + public class CollisionPlane { + public var node:BSPNode; + public var infront:Boolean; + public var sourceOffset:Number; + public var destinationOffset:Number; + + // Хранилище неиспользуемых плоскостей + static private var collector:Array = new Array(); + + // Создать плоскость + static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane { + + // Достаём плоскость из коллектора + var plane:CollisionPlane = collector.pop(); + // Если коллектор пуст, создаём новую плоскость + if (plane == null) { + plane = new CollisionPlane(); + } + + plane.node = node; + plane.infront = infront; + plane.sourceOffset = sourceOffset; + plane.destinationOffset = destinationOffset; + + return plane; + } + + // Удалить плоскость, все ссылки должны быть почищены + static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void { + collector.push(plane); + } + + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as new file mode 100644 index 0000000..cef6dc6 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as @@ -0,0 +1,470 @@ +package alternativa.engine3d.physics { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + * Класс реализует алгоритм определения столкновений сферы с полигонами сцены. + */ + public class SphereCollider { + + // Максимальное кол-во попыток найти свободное от столкновения со сценой направление + private static const maxCollisions:uint = 5000; + + /** + * @private + * Радиус сферы + */ + public var sphereRadius:Number; + + /** + * @private + * Список объектов, с которыми не будут проверяться столкновения + */ + public var ignoreSet:Set; + + /** + * @private + * Установка погрешности определения расстояний. + */ + public var offsetThreshold:Number = 0.01; + + private var scene:Scene3D; + + private var collisionSource:Point3D; + private var collisionVector:Point3D; + private var collisionVelocity:Point3D = new Point3D(); + private var collisionDestination:Point3D = new Point3D(); + private var collisionPlanes:Array = new Array(); + private var collisionPlanePoint:Point3D = new Point3D(); + private var collisionPrimitive:PolyPrimitive; + private var collisionPrimitivePoint:Point3D = new Point3D(); + private var collisionPrimitiveNearest:PolyPrimitive; + private var collisionPrimitiveNearestLengthSqr:Number; + private var collisionPoint:Point3D = new Point3D(); + private var collisionNormal:Point3D = new Point3D(); + private var collisionOffset:Number; + private var coords:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + + /** + * @private + * Создание экземпляра класса. + * + * @param scene сцена, в которой определяется столкновение + */ + public function SphereCollider(scene:Scene3D, radius:Number = 0) { + if (scene == null) { + throw new Error("SphereCollider: scene is null"); + } + this.scene = scene; + sphereRadius = radius; + } + + /** + * @private + * Считает конечное положения сферы по направлению вектора перемещения, с учетом столкновений + * её с объектами сцены, кроме объектов из списка ignoreSet + * + * @param source начальное положение сферы + * @param velocity вектор перемещения сферы + * @param destination результат выполнения метода - конечное положение сферы + */ + public function calculateDestination(source:Point3D, velocity:Point3D, destination:Point3D):void { + + // Сохраняем вектор перемещения во внутренний вектор + collisionVelocity.x = velocity.x; + collisionVelocity.y = velocity.y; + collisionVelocity.z = velocity.z; + + // Расчеты не производятся, если скорость мала + if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold && + collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold && + collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) { + destination.x = source.x; + destination.y = source.y; + destination.z = source.z; + return; + } + + coords.x = source.x; + coords.y = source.y; + coords.z = source.z; + + destination.x = source.x + collisionVelocity.x; + destination.y = source.y + collisionVelocity.y; + destination.z = source.z + collisionVelocity.z; + var collisions:uint = 0; + do { + var collision:Collision = calculateCollision(coords, collisionVelocity, this.collision); + if (collision != null) { + // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали + var offset:Number = sphereRadius + offsetThreshold + collision.offset - destination.x*collision.normal.x - destination.y*collision.normal.y - destination.z*collision.normal.z; + destination.x += collision.normal.x * offset; + destination.y += collision.normal.y * offset; + destination.z += collision.normal.z * offset; + // Коррекция текущих кординат центра сферы для следующей итерации + coords.x = collision.point.x + collision.normal.x * (sphereRadius + offsetThreshold); + coords.y = collision.point.y + collision.normal.y * (sphereRadius + offsetThreshold); + coords.z = collision.point.z + collision.normal.z * (sphereRadius + offsetThreshold); + // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения. + collisionVelocity.x = destination.x - coords.x; + collisionVelocity.y = destination.y - coords.y; + collisionVelocity.z = destination.z - coords.z; + + // Если смещение слишком мало, останавливаемся + if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold && + collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold && + collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) { + break; + } + } + } while ((collision != null) && (++collisions < maxCollisions)); + + // Если после maxCollisions ходов выход не найден, то остаемся на старом месте + if (collisions == maxCollisions) { + destination.x = source.x; + destination.y = source.y; + destination.z = source.z; + } + } + + /** + * @private + * Определение столкновения сферы с полигонами сцены. + * + * @param source исходная точка положения сферы в сцене + * @param vector вектор перемещения сферы в сцене + * @param collision экземпляр класса для возврата в него результата + * + * @return объект, содержащий параметры столкновения или null в случае отсутствия столкновений + */ + public function calculateCollision(source:Point3D, vector:Point3D, collision:Collision = null):Collision { + collisionSource = source; + collisionVector = vector; + collisionDestination.x = collisionSource.x + collisionVector.x; + collisionDestination.y = collisionSource.y + collisionVector.y; + collisionDestination.z = collisionSource.z + collisionVector.z; + + // Собираем потенциальные плоскости столкновения + collectCollisionPlanes(scene.bsp); + + // Перебираем плоскости по мере удалённости + collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING); + var plane:CollisionPlane; + // Пока не найдём столкновение с примитивом или плоскости не кончатся + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlane(plane); + } + plane.node = null; + CollisionPlane.destroyCollisionPlane(plane); + } + + if (collisionPrimitive != null) { + if (collision == null) { + collision = new Collision(); + } + collision.face = collisionPrimitive.face; + collision.normal = collisionNormal.clone(); + collision.point = collisionPoint.clone(); + collision.offset = collisionOffset; + } else { + collision = null; + } + + collisionSource = null; + collisionVector = null; + collisionPrimitive = null; + return collision; + } + + /** + * @private + * Сбор потенциальных плоскостей столкновения. + * + * @param node текущий узел BSP-дерева + */ + private function collectCollisionPlanes(node:BSPNode):void { + if (node != null) { + var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset; + var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset; + var plane:CollisionPlane; + + if (sourceOffset >= 0) { + // Перед нодой + + // Проверяем передние ноды + collectCollisionPlanes(node.front); + + if (destinationOffset < sphereRadius) { + + // Нашли пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset); + collisionPlanes.push(plane); + + // Проверяем задние ноды + collectCollisionPlanes(node.back); + } + } else { + // За нодой + + // Проверяем задние ноды + collectCollisionPlanes(node.back); + + if (destinationOffset > -sphereRadius) { + + // Если в ноде есть сзади примитивы + if (node.backPrimitives != null) { + // Нашли пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset); + collisionPlanes.push(plane); + } + + // Проверяем передние ноды + collectCollisionPlanes(node.front); + } + } + } + } + + /** + * @private + * Определение пересечения сферы с примитивами, лежащими в заданной плоскости. + * + * @param plane плоскость, содержащая примитивы для проверки + */ + private function calculateCollisionWithPlane(plane:CollisionPlane):void { + collisionPlanePoint.copy(collisionSource); + + var normal:Point3D = plane.node.normal; + // Если сфера врезана в плоскость + if (plane.sourceOffset <= sphereRadius) { + if (plane.infront) { + collisionPlanePoint.x -= normal.x * plane.sourceOffset; + collisionPlanePoint.y -= normal.y * plane.sourceOffset; + collisionPlanePoint.z -= normal.z * plane.sourceOffset; + } else { + collisionPlanePoint.x += normal.x * plane.sourceOffset; + collisionPlanePoint.y += normal.y * plane.sourceOffset; + collisionPlanePoint.z += normal.z * plane.sourceOffset; + } + } else { + // Находим центр сферы во время столкновения с плоскостью + var time:Number = (plane.sourceOffset - sphereRadius) / (plane.sourceOffset - plane.destinationOffset); + collisionPlanePoint.x = collisionSource.x + collisionVector.x * time; + collisionPlanePoint.y = collisionSource.y + collisionVector.y * time; + collisionPlanePoint.z = collisionSource.z + collisionVector.z * time; + + // Устанавливаем точку пересечения cферы с плоскостью + if (plane.infront) { + collisionPlanePoint.x -= normal.x * sphereRadius; + collisionPlanePoint.y -= normal.y * sphereRadius; + collisionPlanePoint.z -= normal.z * sphereRadius; + } else { + collisionPlanePoint.x += normal.x * sphereRadius; + collisionPlanePoint.y += normal.y * sphereRadius; + collisionPlanePoint.z += normal.z * sphereRadius; + } + } + + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if (plane.node.primitive != null) { + if (ignoreSet == null || ignoreSet[plane.node.primitive.face._mesh] == undefined) { + calculateCollisionWithPrimitive(plane.node.primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) { + calculateCollisionWithPrimitive(primitive); + if (collisionPrimitive != null) break; + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) { + calculateCollisionWithPrimitive(primitive); + if (collisionPrimitive != null) break; + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + + // Нормаль плоскости при столкновении - нормаль плоскости + if (plane.infront) { + collisionNormal.x = normal.x; + collisionNormal.y = normal.y; + collisionNormal.z = normal.z; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normal.x; + collisionNormal.y = -normal.y; + collisionNormal.z = -normal.z; + collisionOffset = -plane.node.offset; + } + + // Точка столкновения в точке столкновения с плоскостью + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + + } else { + // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей + + // Вектор из ближайшей точки в центр сферы + var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x; + var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y; + var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z; + + // Если движение в сторону точки + if (nearestPointToSourceX * collisionVector.x + nearestPointToSourceY * collisionVector.y + nearestPointToSourceZ * collisionVector.z <= 0) { + + // Ищем нормализованный вектор обратного направления + var vectorLength:Number = Math.sqrt(collisionVector.x * collisionVector.x + collisionVector.y * collisionVector.y + collisionVector.z * collisionVector.z); + var vectorX:Number = -collisionVector.x / vectorLength; + var vectorY:Number = -collisionVector.y / vectorLength; + var vectorZ:Number = -collisionVector.z / vectorLength; + + // Длина вектора из ближайшей точки в центр сферы + var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ; + + // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления + var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ; + + var projectionInsideSphereLengthSqr:Number = sphereRadius * sphereRadius - nearestPointToSourceLengthSqr + projectionLength * projectionLength; + + if (projectionInsideSphereLengthSqr > 0) { + // Находим расстояние из ближайшей точки до сферы + var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr); + + if (distance < vectorLength) { + // Столкновение сферы с ближайшей точкой произошло + + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x; + collisionPoint.y = collisionPrimitivePoint.y; + collisionPoint.z = collisionPrimitivePoint.z; + + // Находим нормаль плоскости столкновения + var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr); + collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength; + collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength; + collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength; + + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + } + } + + /** + * @private + * Определение столкновения с примитивом. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitive(primitive:PolyPrimitive):void { + + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + for (var i:uint = 0; i < length; i++) { + + var p1:Point3D = points[i]; + var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0]; + + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = collisionPlanePoint.x - p1.x; + var vectorY:Number = collisionPlanePoint.y - p1.y; + var vectorZ:Number = collisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = collisionPlanePoint.x - p2.x; + vectorY = collisionPlanePoint.y - p2.y; + vectorZ = collisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..5e730b5 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,316 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Прямоугольный параллелепипед. + */ + public class Box extends Mesh { + + /** + * Создание нового параллелепипеда. + *

Параллелепипед после создания будет содержать в себе шесть поверхностей. + * "front", "back", "left", "right", "top", "bottom" + * на каждую из которых может быть установлен свой материал.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param height высота. Размерность по оси Z. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param heightSegments количество сегментов по по высоте + * @param reverse задает направление нормалей граней. Если указано значение true, то нормали будут направлены внутрь фигуры. + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы. + */ + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + height = (height < 0)? 0 : height; + + var wh:Number = width/2; + var lh:Number = length/2; + var hh:Number = height/2; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + // Создание точек + for (x = 0; x <= widthSegments; x++) { + for (y = 0; y <= lengthSegments; y++) { + for (z = 0; z <= heightSegments; z++) { + if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) { + createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z); + } + } + } + } + + // Создание поверхностей + var front:Surface = createSurface(null, "front"); + var back:Surface = createSurface(null, "back"); + var left:Surface = createSurface(null, "left"); + var right:Surface = createSurface(null, "right"); + var top:Surface = createSurface(null, "top"); + var bottom:Surface = createSurface(null, "bottom"); + + // Создание граней + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var faceId:String; + + // Для оптимизаций UV при триангуляции + var aUV:Point; + var cUV:Point; + + // Построение верхней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "top_"+x+"_"+y; + if (reverse) { + if (triangulate) { + aUV = new Point(x*wd, (lengthSegments - y)*ld); + cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1"); + setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0"); + setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, y*ld); + cUV = new Point((x + 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + top.addFace(faceId + ":0"); + top.addFace(faceId + ":1"); + } else { + top.addFace(faceId); + } + } + } + + // Построение нижней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "bottom_" + x + "_" + y; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld); + cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1"); + setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, y*ld); + cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + bottom.addFace(faceId + ":0"); + bottom.addFace(faceId + ":1"); + } else { + bottom.addFace(faceId); + } + } + } + + // Построение фронтальной грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "front_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, z*hd); + cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0"); + } else { + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, z*hd); + cUV = new Point((x + 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + front.addFace(faceId + ":0"); + front.addFace(faceId + ":1"); + } else { + front.addFace(faceId); + } + } + } + + // Построение задней грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "back_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point(x * wd, (z + 1) * hd); + cUV = new Point((x + 1) * wd, z * hd); + createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (z + 1)*hd); + cUV = new Point((widthSegments - x - 1)*wd, z*hd); + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0"); + createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + back.addFace(faceId + ":0"); + back.addFace(faceId + ":1"); + } else { + back.addFace(faceId); + } + } + } + + // Построение левой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "left_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point(y*ld, (z + 1)*hd); + cUV = new Point((y + 1)*ld, z*hd); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((lengthSegments - y - 1)*ld, z*hd); + cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + left.addFace(faceId + ":0"); + left.addFace(faceId + ":1"); + } else { + left.addFace(faceId); + } + } + } + + // Построение правой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "right_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point((lengthSegments - y)*ld, z*hd); + cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(y*ld, z*hd); + cUV = new Point((y + 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + right.addFace(faceId + ":0"); + right.addFace(faceId + ":1"); + } else { + right.addFace(faceId); + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Box(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as new file mode 100644 index 0000000..f98c012 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as @@ -0,0 +1,264 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Усеченный конус или цилиндр. + */ + public class Cone extends Mesh { + + /** + * Создает примитив усеченный конус или цилиндр. + *

Различные значения параметров позволяют создавать различные примитивы. + * Так при установленном параметре topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном bottomRadius = topRadius будет построен цилиндр.

+ *

По умолчанию параметр triangulate установлен в false и на примитив не может быть наложена текстура. + * Только при установленном параметре triangulate в true это возможно.

+ *

После создания примитив всегда содержит в себе поверхность "side". + * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom", + * при установленном параметре topRadius в примитиве создается поверхность "top". + * На каждую из поверхностей может быть наложен свой материал

+ * + * @param height высота примтива. Размерность по оси Z. Не может быть меньше нуля. + * @param bottomRadius нижний радиус примитива + * @param topRadius верхний радиус примтива + * @param heightSegments количество сегментов по высоте примитива + * @param radialSegments количество сегментов по радиусу примтива + * @param reverse задает направление нормалей. При значении true нормли будут направлены внутрь примитива. + * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы + * и появится возможность наложить на примитив текстуру. + */ + public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) { + + if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) { + return; + } + height = (height < 0)? 0 : height; + bottomRadius = (bottomRadius < 0)? 0 : bottomRadius; + topRadius = (topRadius < 0)? 0 : topRadius; + + const radialSegment:Number = MathUtils.DEG360/radialSegments; + const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments; + const heightSegment:Number = height/heightSegments; + const halfHeight:Number = height*0.5 + const uSegment:Number = 1/radialSegments; + const vSegment:Number = 1/heightSegments; + + // Создание вершин + if (topRadius == 0 || triangulate) { + var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp"); + } + if (bottomRadius == 0 || triangulate) { + var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown"); + } + + var radial:uint; + var segment:uint; + + var topSegment:uint = heightSegments - int(topRadius == 0); + var bottomSegment:uint = int(bottomRadius == 0) ; + for (segment = bottomSegment; segment <= topSegment; segment++) { + for (radial = 0; radial < radialSegments; radial++) { + var currentAngle:Number = radialSegment*radial; + var currentRadius:Number = bottomRadius - (radiusSegment*segment); + createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var face:Face; + + var points:Array; + + var side:Surface = createSurface(null, "side"); + + if (topRadius == 0) { + // Создание граней у верхнего полюса + var prevRadial:uint = radialSegments - 1; + var centerUV:Point = new Point(0.5, 1); + var v:Number = topSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней верхней крышки + var top:Surface = createSurface(null, "top"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + var UV:Point; + var prevUV:Point; + if (reverse) { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + topSegment); + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + topSegment); + } + } + top.addFace(createFace(points, "top")); + } + } + // Создание боковых граней + var face2:Face; + var aUV:Point; + var cUV:Point; + for (segment = bottomSegment; segment < topSegment; segment++) { + prevRadial = radialSegments - 1; + v = segment * vSegment; + for (radial = 0; radial < radialSegments; radial++) { + if (triangulate) { + if (reverse) { + face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"); + face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"); + aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment) + cUV = new Point(1 - prevRadial*uSegment, v); + setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2); + } else { + face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"); + face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"); + aUV = new Point(prevRadial*uSegment, v) + cUV = new Point((prevRadial + 1)*uSegment, v + vSegment); + setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2); + } + side.addFace(face); + side.addFace(face2); + } else { + if (reverse) { + side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } else { + side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment)); + } + } + prevRadial = radial; + } + } + + if (bottomRadius == 0) { + // Создание граней у нижнего полюса + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0); + v = bottomSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней нижней крышки + var bottom:Surface = createSurface(null, "bottom"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + if (reverse) { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + bottomSegment); + } + } else { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + bottomSegment); + } + } + bottom.addFace(createFace(points, "bottom")); + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Cone(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as new file mode 100644 index 0000000..ea681b4 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as @@ -0,0 +1,197 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Геоплоскость. + */ + public class GeoPlane extends Mesh { + + /** + * Создает геоплоскость. + *

Геоплоскость это плоскость с сетчатой структурой граней.

+ *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значение параметра равно true, то создаётся двусторонняя поверхность + * @param reverse флаг инвертирования нормалей + */ + public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var hh:Number = length/2; + // Размеры сегмента + var ws:Number = width/widthSegments; + var hs:Number = length/lengthSegments; + + // Размеры UV-сегмента + var us:Number = 1/widthSegments; + var vs:Number = 1/lengthSegments; + + // Создание точек + var x:uint; + var y:uint; + var frontUV:Array = new Array(); + var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array(); + for (y = 0; y <= lengthSegments; y++) { + frontUV[y] = new Array(); + if (backUV != null) { + backUV[y] = new Array(); + } + for (x = 0; x <= widthSegments; x++) { + if ((y & 1) == 0) { + // Если чётный ряд + createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(x*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(x*us, 1 - y*vs); + } + } else { + // Если нечётный ряд + if (x == 0) { + // Первая точка + createVertex(-wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(0, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(0, 1 - y*vs); + } + } else { + createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point((x - 0.5)*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs); + } + if (x == widthSegments) { + // Последняя точка + createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1)); + frontUV[y][x + 1] = new Point(1, y*vs); + if (backUV != null) { + backUV[y][x + 1] = new Point(1, 1 - y*vs); + } + } + } + } + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + var face:Face; + for (y = 0; y < lengthSegments; y++) { + for (var n:uint = 0; n <= (widthSegments << 1); n++) { + x = n >> 1; + if ((y & 1) == 0) { + // Если чётный ряд + if ((n & 1) == 0) { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } + } else { + // Если нечётный ряд + if ((n & 1) == 0) { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } + } + } + } + + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoPlane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..7ba3bf2 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,321 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Геосфера. + */ + public class GeoSphere extends Mesh { + + /** + * Создает геосферу. + *

Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

Текстурные координаты у геосферы не находятся в промежутке [0, 1], + * поэтому для материала с текстурой необходимо устанавливать флаг repeat. + * + * @param radius радиус геосферы. Не может быть меньше нуля. + * @param segments количество сегментов геосферы + * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы. + */ + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + if (segments == 0) { + return; + } + radius = (radius < 0)? 0 : radius; + + const sections:uint = 20; + + //var nfaces:uint = sections*segments*segments; + //var nverts:Number = nfaces/2 + 2; + var points:Array = new Array(); + + var i:uint; + var f:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + points.push(createVertex(0, 0, radius, "poleUp")); + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG360*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, subz)); + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG180*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, -subz)); + } + points.push(createVertex(0, 0, -radius, "poleDown")); + + for (i = 1; i < 6; i++) { + interpolate(0, i, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i % 5 + 1, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i + 5, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, (i + 3) % 5 + 6, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i + 5, i % 5 + 6, segments, points); + } + for (i = 6; i < 11; i++) { + interpolate(11, i, segments, points); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points); + } + } + // Создание граней + var face:Face; + var surface:Surface = createSurface(); + for (f = 0; f < sections; f++) { + for (var row:uint = 0; row < segments; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + var va:Vertex = points[a]; + var vb:Vertex = points[b]; + var vc:Vertex = points[c]; + var aUV:Point; + var bUV:Point; + var cUV:Point; + var coordA:Point3D = va._coords; + var coordB:Point3D = vb._coords; + var coordC:Point3D = vc._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + // полюс + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, cUV, bUV, face); + } else { + face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f); + setUVsToFace(aUV, bUV, cUV, face); + } + surface.addFace(face); + //trace(a + "_" + b + "_" + c); + if (column < row) { + b = findVertices(segments, f, row, column + 1); + var vd:Vertex = points[b]; + coordB = vd._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, bUV, cUV, face); + } else { + face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f); + setUVsToFace(aUV, cUV, bUV, face); + } + surface.addFace(face); + } + } + } + } + } + +/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point { + if (radius == 0) { + radius = point.length; + } + if (reverse) { + var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360; + } else { + u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5; + } + return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5); + } + */ + private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void { + if (num < 2) { + return; + } + var a:Vertex = Vertex(points[v1]); + var b:Vertex = Vertex(points[v2]); + var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin)); + } + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoSphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..d346048 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,117 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Плоскость. + */ + public class Plane extends Mesh { + + /** + * Создает плоскость. + *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси Х. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значении параметра равно true, то формируется двусторонняя плоскость + * @param reverse инвертирование нормалей + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы. + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var lh:Number = length/2; + + // Размеры сегмента + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + + // Размеры UV-сегмента + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + + // Создание точек и UV + var x:int; + var y:int; + var uv:Array = new Array(); + for (y = 0; y <= lengthSegments; y++) { + uv[y] = new Array(); + for (x = 0; x <= widthSegments; x++) { + uv[y][x] = new Point(x*wd, y*ld); + createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y); + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + if (twoSided || !reverse) { + if (triangulate) { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0"); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1"); + setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1"); + front.addFace("front" + x + "_" + y + ":0"); + front.addFace("front" + x + "_" + y + ":1"); + } else { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y); + front.addFace("front" + x + "_" + y); + } + } + if (twoSided || reverse) { + if (triangulate) { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0"); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1"); + setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1"); + back.addFace("back" + x + "_" + y + ":0"); + back.addFace("back"+x+"_"+y + ":1"); + } else { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y); + back.addFace("back" + x + "_" + y); + } + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Plane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as new file mode 100644 index 0000000..994d7f7 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as @@ -0,0 +1,144 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Сфера. + */ + public class Sphere extends Mesh { + + /** + * Создает сферу. + *

После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

По умолчанию параметр triangulate установлен в false и на сферу нельзя наложить текстуру. + * Только при установленном triangulate в true это возможно.

+ * + * @param radius Радиус сферы. Не может быть меньше нуля. + * @param radialSegments количество сегментов по экватору сферы + * @param heightSegments количество сегментов по высоте + * @param reverse флаг инвертирования нормалей. При параметре установленном в true нормали направлены внутрь сферы. + * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы, + * и будет возможно наложить на примитив текстуру. + */ + public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) { + if ((radialSegments < 3) || (heightSegments < 2)) { + return; + } + radius = (radius < 0)? 0 : radius; + + var poleUp:Vertex = createVertex(0, 0, radius, "poleUp"); + var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown"); + + const radialAngle:Number = MathUtils.DEG360/radialSegments; + const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1); + + var radial:uint; + var segment:uint; + + // Создание вершин + for (segment = 1; segment < heightSegments; segment++) { + var currentHeightAngle:Number = heightAngle*segment; + var segmentRadius:Number = Math.sin(currentHeightAngle)*radius; + var segmentZ:Number = Math.cos(currentHeightAngle)*radius; + for (radial = 0; radial < radialSegments; radial++) { + var currentRadialAngle:Number = radialAngle*radial; + createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var surface:Surface = createSurface(); + + var prevRadial:uint = radialSegments - 1; + var lastSegmentString:String = "_" + (heightSegments - 1); + + var uStep:Number = 1/radialSegments; + var vStep:Number = 1/heightSegments; + + var face:Face; + + // Для триангуляции + var aUV:Point; + var cUV:Point; + + var u:Number; + + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString)); + + // Если включена триангуляция + if (triangulate) { + // Триангулируем середину и просчитываем маппинг + u = uStep*prevRadial; + setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1))); + cUV = new Point(1 - u, 1 - vStep*segment); + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString); + + } else { + // Просто создаем середину + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString)); + + if (triangulate) { + u = uStep*prevRadial; + setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(u, 1 - (vStep*segment)); + cUV = new Point(u + uStep, 1 - vStep * (segment + 1)); + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString); + } else { + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as new file mode 100644 index 0000000..d6cc103 --- /dev/null +++ b/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as @@ -0,0 +1,861 @@ +package alternativa.utils { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Утилиты для работы с Mesh-объектами. + */ + public class MeshUtils { + + static private var verticesSort:Array = ["x", "y", "z"]; + static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC]; + + /** + * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются. + * + * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh + * + * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов + */ + static public function uniteMeshes(... meshes):Mesh { + var res:Mesh = new Mesh(); + + var length:uint = meshes.length; + var key:*; + var vertex:Vertex; + var face:Face; + var j:uint; + for (var i:uint = 0; i < length; i++) { + var mesh:Mesh = meshes[i]; + var vertices:Map = mesh._vertices.clone(); + for (key in vertices) { + vertex = vertices[key]; + vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z); + } + var faces:Map = mesh._faces.clone(); + for (key in faces) { + face = faces[key]; + var faceVertices:Array = new Array().concat(face._vertices); + for (j = 0; j < face._verticesCount; j++) { + vertex = faceVertices[j]; + faceVertices[j] = vertices[vertex.id]; + } + faces[key] = res.createFace(faceVertices); + res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]); + } + for (key in mesh._surfaces) { + var surface:Surface = mesh._surfaces[key]; + var surfaceFaces:Array = surface._faces.toArray(); + var numFaces:uint = surfaceFaces.length; + for (j = 0; j < numFaces; j++) { + face = surfaceFaces[j]; + surfaceFaces[j] = faces[face.id]; + } + var newSurface:Surface = res.createSurface(surfaceFaces); + newSurface.material = SurfaceMaterial(surface.material.clone()); + } + } + return res; + } + + /** + * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности. + * + * @param mesh объект, вершины которого объединяются + * @param threshold погрешность измерения расстояний + */ + static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void { + // Получаем список вершин меша и сортируем по координатам + var vertices:Array = mesh._vertices.toArray(true); + vertices.sortOn(verticesSort, verticesSortOptions); + + // Поиск вершин с одинаковыми координатами + var weld:Map = new Map(true); + var vertex:Vertex; + var currentVertex:Vertex = vertices[0]; + var length:uint = vertices.length; + var i:uint; + for (i = 1; i < length; i++) { + vertex = vertices[i]; + if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) { + weld[vertex] = currentVertex; + } else { + currentVertex = vertex; + } + } + + // Собираем грани объединяемых вершин + var faces:Set = new Set(true); + var keyVertex:*; + var keyFace:*; + for (keyVertex in weld) { + vertex = keyVertex; + for (keyFace in vertex._faces) { + faces[keyFace] = true; + } + } + + // Заменяем грани + for (keyFace in faces) { + var face:Face = keyFace; + var id:Object = mesh.getFaceId(face); + var surface:Surface = face._surface; + var aUV:Point = face._aUV; + var bUV:Point = face._bUV; + var cUV:Point = face._cUV; + vertices = new Array().concat(face._vertices); + length = vertices.length; + for (i = 0; i < length; i++) { + vertex = weld[vertices[i]]; + if (vertex != null) { + vertices[i] = vertex; + } + } + mesh.removeFace(face); + face = mesh.createFace(vertices, id); + if (surface != null) { + surface.addFace(face); + } + face.aUV = aUV; + face.bUV = bUV; + face.cUV = cUV; + } + + // Удаляем вершины + for (keyVertex in weld) { + mesh.removeVertex(keyVertex); + } + } + + /** + * Объединение соседних граней, образующих плоский выпуклый многоугольник. + * + * @param mesh объект, грани которого объединяются + * @param angleThreshold погрешность измерения углов + * @param uvThreshold погрешность измерения UV-координат + */ + static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void { + angleThreshold = Math.cos(angleThreshold); + + var face:Face; + var sibling:Face; + var key:*; + var i:uint; + + // Формируем списки граней + var faces1:Set = new Set(true); + var faces2:Set = new Set(true); + + // Формируем список нормалей + var normals:Map = new Map(true); + for each (face in mesh._faces.clone()) { + var faceNormal:Point3D = face.normal; + if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) { + faces1[face] = true; + normals[face] = faceNormal; + } else { + mesh.removeFace(face); + } + } + + // Объединение + do { + // Флаг объединения + var weld:Boolean = false; + // Объединяем грани + while ((face = faces1.take()) != null) { + //var num:uint = face.num; + //var vertices:Array = face.vertices; + var currentWeld:Boolean = false; + + // Проверка общих граней по точкам + + + // Проверка общих граней по рёбрам + + // Перебираем точки грани + for (i = 0; (i < face._verticesCount) && !currentWeld; i++) { + var faceIndex1:uint = i; + var faceIndex2:uint; + var siblingIndex1:int; + var siblingIndex2:uint; + + // Перебираем грани текущей точки + var vertex:Vertex = face._vertices[faceIndex1]; + var vertexFaces:Set = vertex.faces; + for (key in vertexFaces) { + sibling = key; + // Если грань в списке на объединение и в одной поверхности + if (faces1[sibling] && face._surface == sibling._surface) { + faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0; + siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]); + // Если общее ребро + if (siblingIndex1 >= 0) { + // Если грани сонаправлены + var normal:Point3D = normals[face]; + if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) { + // Если в точках объединения нет перегибов + siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0; + + // Расширяем грани объединения + var i1:uint; + var i2:uint; + while (true) { + i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1); + i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0; + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex1 = i1; + siblingIndex2 = i2; + } else { + break; + } + } + + while (true) { + i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0; + i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1); + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex2 = i1; + siblingIndex1 = i2; + } else { + break; + } + } + + vertex = face._vertices[faceIndex1]; + var a:Point3D = vertex.coords; + vertex = face._vertices[faceIndex2]; + var b:Point3D = vertex.coords; + + // Считаем первый перегиб + vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0]; + var c:Point3D = vertex.coords; + vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)]; + var d:Point3D = vertex.coords; + + var cx:Number = c.x - a.x; + var cy:Number = c.y - a.y; + var cz:Number = c.z - a.z; + var dx:Number = d.x - a.x; + var dy:Number = d.y - a.y; + var dz:Number = d.z - a.z; + + var crossX:Number = cy*dz - cz*dy; + var crossY:Number = cz*dx - cx*dz; + var crossZ:Number = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если в первой точке перегиба нет + if (dot >= 0) { + + // Считаем второй перегиб + vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0]; + c = vertex.coords; + vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)]; + d = vertex.coords; + + cx = c.x - b.x; + cy = c.y - b.y; + cz = c.z - b.z; + dx = d.x - b.x; + dy = d.y - b.y; + dz = d.z - b.z; + + crossX = cy*dz - cz*dy; + crossY = cz*dx - cx*dz; + crossZ = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если во второй точке перегиба нет + if (dot >= 0) { + + // Флаг наличия UV у обеих граней + var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null); + + if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) { + + // Если грани имеют UV, проверяем совместимость + if (hasUV) { + vertex = sibling._vertices[0]; + var uv:Point = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[1]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[2]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) { + break; + } + } + + // Формируем новую грань + var newVertices:Array = new Array(); + var n:uint = faceIndex2; + do { + newVertices.push(face._vertices[n]); + n = (n < face._verticesCount - 1) ? (n + 1) : 0; + } while (n != faceIndex1); + n = siblingIndex2; + do { + newVertices.push(sibling._vertices[n]); + n = (n < sibling._verticesCount - 1) ? (n + 1) : 0; + } while (n != siblingIndex1); + + // Выбираем начальную точку + n = getBestBeginVertexIndex(newVertices); + for (var m:uint = 0; m < n; m++) { + newVertices.push(newVertices.shift()); + } + + // Заменяем грани новой + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (hasUV) { + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + mesh.removeFace(sibling); + + // Обновляем список нормалей + delete normals[sibling]; + delete normals[face]; + normals[newFace] = newFace.normal; + + // Обновляем списки расчётов + delete faces1[sibling]; + faces2[newFace] = true; + + // Помечаем объединение + weld = true; + currentWeld = true; + break; + } + } + } + } + } + } + } + } + + + + // Если не удалось объединить, переносим грань + faces2[face] = true; + } + + // Меняем списки + var fs:Set = faces1; + faces1 = faces2; + faces2 = fs; + } while (weld); + + removeIsolatedVertices(mesh); + removeUselessVertices(mesh); + } + + /** + * Удаление вершин объекта, не принадлежащим ни одной грани. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeIsolatedVertices(mesh:Mesh):void { + for each (var vertex:Vertex in mesh._vertices.clone()) { + if (vertex._faces.isEmpty()) { + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeUselessVertices(mesh:Mesh):void { + var v:Vertex; + var key:*; + var face:Face; + var index:uint; + var length:uint; + for each (var vertex:Vertex in mesh._vertices.clone()) { + var unless:Boolean = true; + var indexes:Map = new Map(true); + for (key in vertex._faces) { + face = key; + length = face._vertices.length; + index = face._vertices.indexOf(vertex); + v = face._vertices[index]; + var a:Point3D = v.coords; + v = face._vertices[(index < length - 1) ? (index + 1) : 0]; + var b:Point3D = v.coords; + v = face._vertices[(index > 0) ? (index - 1) : (length - 1)]; + var c:Point3D = v.coords; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) { + indexes[face] = index; + } else { + unless = false; + break; + } + } + if (unless && !indexes.isEmpty()) { + // Удаляем + for (key in indexes) { + var i:uint; + face = key; + index = indexes[face]; + length = face._vertices.length; + var newVertices:Array = new Array(); + for (i = 0; i < length; i++) { + if (i != index) { + newVertices.push(face._vertices[i]); + } + } + var n:uint = getBestBeginVertexIndex(newVertices); + for (i = 0; i < n; i++) { + newVertices.push(newVertices.shift()); + } + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var normal:Point3D = face.normal; + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + } + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вырожденных граней. + * + * @param mesh объект, грани которого удаляются + */ + static public function removeSingularFaces(mesh:Mesh):void { + for each (var face:Face in mesh._faces.clone()) { + var normal:Point3D = face.normal; + if (normal.x == 0 && normal.y == 0 && normal.z == 0) { + mesh.removeFace(face); + } + } + } + + /** + * @private + * Находит наиболее подходящую первую точку. + * @param vertices + * @return + */ + static public function getBestBeginVertexIndex(vertices:Array):uint { + var bestIndex:uint = 0; + var num:uint = vertices.length; + if (num > 3) { + var maxCrossLength:Number = 0; + var v:Vertex = vertices[num - 1]; + var c1:Point3D = v.coords; + v = vertices[0]; + var c2:Point3D = v.coords; + + var prevX:Number = c2.x - c1.x; + var prevY:Number = c2.y - c1.y; + var prevZ:Number = c2.z - c1.z; + + for (var i:uint = 0; i < num; i++) { + c1 = c2; + v = vertices[(i < num - 1) ? (i + 1) : 0]; + c2 = v.coords; + + var nextX:Number = c2.x - c1.x; + var nextY:Number = c2.y - c1.y; + var nextZ:Number = c2.z - c1.z; + + var crossX:Number = prevY*nextZ - prevZ*nextY; + var crossY:Number = prevZ*nextX - prevX*nextZ; + var crossZ:Number = prevX*nextY - prevY*nextX; + + + var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ; + if (crossLength > maxCrossLength) { + maxCrossLength = crossLength; + bestIndex = i; + } + + prevX = nextX; + prevY = nextY; + prevZ = nextZ; + } + // Берём предыдущий + bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1); + } + + return bestIndex; + } + + /** + * Генерация AS-класса. + * + * @param mesh объект, на базе которого генерируется класс + * @param packageName имя пакета для генерируемого класса + * @return AS-класс в текстовом виде + */ + static public function generateClass(mesh:Mesh, packageName:String = ""):String { + + var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1); + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["alternativa.engine3d.core.Mesh"] = true; + + var materialSet:Map = new Map(true); + var materialName:String; + var materialNum:uint = 1; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r"; + + + var newLine:Boolean = false; + if (mesh.mobility != 0) { + constructor += "\t\t\tmobility = " + mesh.mobility +";\r"; + newLine = true; + } + + if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) { + importSet["alternativa.types.Point3D"] = true; + constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r"; + newLine = true; + } else { + if (mesh.x != 0) { + constructor += "\t\t\tx = " + mesh.x + ";\r"; + newLine = true; + } + if (mesh.y != 0) { + constructor += "\t\t\ty = " + mesh.y + ";\r"; + newLine = true; + } + if (mesh.z != 0) { + constructor += "\t\t\tz = " + mesh.z + ";\r"; + newLine = true; + } + } + if (mesh.rotationX != 0) { + constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r"; + newLine = true; + } + if (mesh.rotationY != 0) { + constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r"; + newLine = true; + } + if (mesh.rotationZ != 0) { + constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r"; + newLine = true; + } + if (mesh.scaleX != 1) { + constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r"; + newLine = true; + } + if (mesh.scaleY != 1) { + constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r"; + newLine = true; + } + if (mesh.scaleZ != 1) { + constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + function idToString(value:*):String { + return isNaN(value) ? ("\"" + value + "\"") : value; + } + + function blendModeToString(value:String):String { + switch (value) { + case BlendMode.ADD: return "BlendMode.ADD"; + case BlendMode.ALPHA: return "BlendMode.ALPHA"; + case BlendMode.DARKEN: return "BlendMode.DARKEN"; + case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE"; + case BlendMode.ERASE: return "BlendMode.ERASE"; + case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT"; + case BlendMode.INVERT: return "BlendMode.INVERT"; + case BlendMode.LAYER: return "BlendMode.LAYER"; + case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN"; + case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY"; + case BlendMode.NORMAL: return "BlendMode.NORMAL"; + case BlendMode.OVERLAY: return "BlendMode.OVERLAY"; + case BlendMode.SCREEN: return "BlendMode.SCREEN"; + case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT"; + default: return "BlendMode.NORMAL"; + } + } + + function colorToString(value:uint):String { + var hex:String = value.toString(16).toUpperCase(); + var res:String = "0x"; + var len:uint = 6 - hex.length; + for (var j:uint = 0; j < len; j++) { + res += "0"; + } + res += hex; + return res; + } + + var i:uint; + var length:uint; + var key:*; + var id:String; + var face:Face; + var surface:Surface; + + newLine = false; + for (id in mesh._vertices) { + var vertex:Vertex = mesh._vertices[id]; + var coords:Point3D = vertex.coords; + constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + newLine = false; + for (id in mesh._faces) { + face = mesh._faces[id]; + length = face._verticesCount; + constructor += "\t\t\tcreateFace([" + for (i = 0; i < length - 1; i++) { + constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", "; + } + constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r"; + + if (face._aUV != null || face._bUV != null || face._cUV != null) { + importSet["flash.geom.Point"] = true; + constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r"; + } + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + for (id in mesh._surfaces) { + surface = mesh._surfaces[id]; + var facesStr:String = ""; + for (key in surface._faces) { + facesStr += idToString(mesh.getFaceId(key)) + ", "; + } + constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r"; + + if (surface.material != null) { + var material:String; + var defaultAlpha:Boolean = surface.material.alpha == 1; + var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL; + if (surface.material is WireMaterial) { + importSet["alternativa.engine3d.materials.WireMaterial"] = true; + var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0; + var defaultColor:Boolean = WireMaterial(surface.material).color == 0; + material = "new WireMaterial("; + if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) { + material += WireMaterial(surface.material).thickness; + if (!defaultColor || !defaultAlpha || !defaultBlendMode) { + material += ", " + colorToString(WireMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode) { + material += ", " + surface.material.alpha ; + if (!defaultBlendMode) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + } + } + } + } + } + var defaultWireThickness:Boolean; + var defaultWireColor:Boolean; + if (surface.material is FillMaterial) { + importSet["alternativa.engine3d.materials.FillMaterial"] = true; + defaultWireThickness = FillMaterial(surface.material).wireThickness < 0; + defaultWireColor = FillMaterial(surface.material).wireColor == 0; + material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + material += ", " + surface.material.alpha; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor) { + material += ", " + FillMaterial(surface.material).wireThickness; + if (!defaultWireColor) { + material += ", " + colorToString(FillMaterial(surface.material).wireColor); + } + } + } + } + } + if (surface.material is TextureMaterial) { + importSet["alternativa.engine3d.materials.TextureMaterial"] = true; + var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat; + var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth; + defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0; + defaultWireColor = TextureMaterial(surface.material).wireColor == 0; + var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM; + + if (TextureMaterial(surface.material).texture == null) { + materialName = "null"; + } else { + importSet["alternativa.types.Texture"] = true; + if (materialSet[TextureMaterial(surface.material).texture] == undefined) { + materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++; + materialSet[TextureMaterial(surface.material).texture] = materialName; + } else { + materialName = materialSet[TextureMaterial(surface.material).texture]; + } + materialName = materialName.split(".")[0]; + } + material = "new TextureMaterial(" + materialName; + if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).alpha; + if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).repeat; + if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).smooth; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).wireThickness; + if (!defaultWireColor || !defaultPrecision) { + material += ", " + colorToString(TextureMaterial(surface.material).wireColor); + if (!defaultPrecision) { + material += ", " + TextureMaterial(surface.material).precision; + } + } + } + } + } + } + } + } + + constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r"; + } + } + + var imports:String = ""; + newLine = false; + + var importArray:Array = new Array(); + for (key in importSet) { + importArray.push(key); + } + importArray.sort(); + + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + var embeds:String = ""; + newLine = false; + for each (materialName in materialSet) { + var materialClassName:String = materialName.split(".")[0]; + var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1); + embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r"; + newLine = true; + } + embeds += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + +// /** +// * @private +// * Убирает лишние точки на ребрах и добавляет точки в месте распилов на соседних гранях +// * +// * @param object объект +// * @param recursive если установлена в true, метод будет вызываться рекурсивно на потомках потомков потомков объекта тоже. +// * +// * @return количество добавленных точек с вычетом убранных +// */ +// p function fixGaps(object:Object3D, recursive:Boolean = false):int { +// var result:int = 0; +// if (object is Mesh) { +// var mesh:Mesh = Mesh(object); +// var faces:Map = mesh._faces; +// for each (var face:Face in faces) { +// //face. +// } +// } +// if (recursive) { +// var children:Set = object._children; +// for (var child:* in children) { +// result += fixGaps(child, true); +// } +// } +// return result; +// } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/Alternativa3D.as b/Alternativa3D5/5.4/alternativa/Alternativa3D.as new file mode 100644 index 0000000..ac916ba --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "5.4.1"; + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as new file mode 100644 index 0000000..0bc89a7 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as @@ -0,0 +1,889 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием + * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены. + */ + public class CameraController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; +// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; +// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения угла тангажа. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш уменьшения угла тангажа. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево). + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо). + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + + // флаги действий + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _up:Boolean; + private var _down:Boolean; + private var _pitchUp:Boolean; + private var _pitchDown:Boolean; + private var _yawLeft:Boolean; + private var _yawRight:Boolean; + private var _accelerate:Boolean; + + private var _moveLocal:Boolean = true; + + // Флаг включения управления камерой + private var _controlsEnabled:Boolean = false; + + // Значение таймера в начале прошлого кадра + private var lastFrameTime:uint; + + // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов. + private var _mouseSensitivity:Number = 1; + // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + private var _mousePitch:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по тангажу + private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch; + // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + private var _mouseYaw:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по расканью + private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw; + + // Вспомогательные переменные для обзора мышью + private var mouseLookActive:Boolean; + private var startDragCoords:Point = new Point(); + private var currentDragCoords:Point = new Point(); + private var prevDragCoords:Point = new Point(); + private var startRotX:Number; + private var startRotZ:Number; + + // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры + private var _pitchSpeed:Number = 1; + // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры + private var _yawSpeed:Number = 1; + // Скорость изменения крена в радианах за секунду при управлении с клавиатуры +// public var bankSpeed:Number = 2; +// private var bankMatrix:Matrix3D = new Matrix3D(); + + // Скорость поступательного движения в единицах за секунду + private var _speed:Number = 100; + // Коэффициент увеличения скорости при соответствующей нажатой клавише + private var _speedMultiplier:Number = 2; + + private var velocity:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + + private var _fovStep:Number = Math.PI / 180; + private var _zoomMultiplier:Number = 0.1; + + // Привязка клавиш к действиям + private var keyBindings:Map = new Map(); + // Привязка действий к обработчикам + private var actionBindings:Map = new Map(); + + // Источник событий клавиатуры и мыши + private var _eventsSource:DisplayObject; + // Управляемая камера + private var _camera:Camera3D; + + // Класс реализации определния столкновений + private var _collider:EllipsoidCollider; + // Флаг необходимости проверки столкновений + private var _checkCollisions:Boolean; + // Радиус сферы для определения столкновений + private var _collisionRadius:Number = 0; + // Набор исключаемых из проверки столкновений объектов + private var _collisionIgnoreSet:Set = new Set(true); + // Флаг движения + private var _isMoving:Boolean; + + private var _onStartMoving:Function; + private var _onStopMoving:Function; + + /** + * Создание экземпляра контроллера. + * + * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры + */ + public function CameraController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = forward; + actionBindings[ACTION_BACK] = back; + actionBindings[ACTION_LEFT] = left; + actionBindings[ACTION_RIGHT] = right; + actionBindings[ACTION_UP] = up; + actionBindings[ACTION_DOWN] = down; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
CONTROLACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
+ */ + public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.CONTROL, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + } + + /** + * Направление камеры на точку. + * + * @param point координаты точки направления камеры + */ + public function lookAt(point:Point3D):void { + if (_camera == null) { + return; + } + var dx:Number = point.x - _camera.x; + var dy:Number = point.y - _camera.y; + var dz:Number = point.z - _camera.z; + _camera.rotationZ = -Math.atan2(dx, dy); + _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90; + } + + /** + * Callback-функция, вызываемая при начале движения камеры. + */ + public function get onStartMoving():Function { + return _onStartMoving; + } + + /** + * @private + */ + public function set onStartMoving(value:Function):void { + _onStartMoving = value; + } + + /** + * Callback-функция, вызываемая при прекращении движения камеры. + */ + public function get onStopMoving():Function { + return _onStopMoving; + } + + /** + * @private + */ + public function set onStopMoving(value:Function):void { + _onStopMoving = value; + } + + /** + * Набор объектов, исключаемых из проверки столкновений. + */ + public function get collisionIgnoreSet():Set { + return _collisionIgnoreSet; + } + + /** + * Источник событий клавиатуры и мыши. + */ + public function get eventsSource():DisplayObject { + return _eventsSource; + } + + /** + * @private + */ + public function set eventsSource(value:DisplayObject):void { + if (_eventsSource != value) { + if (value == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + if (_controlsEnabled) { + unregisterEventsListeners(); + } + _eventsSource = value; + if (_controlsEnabled) { + registerEventListeners(); + } + } + } + + /** + * Ассоциированная камера. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + _camera = value; + if (value == null) { + controlsEnabled = false; + } else { + createCollider(); + } + } + } + + /** + * Режим движения камеры. Если значение равно true, то перемещения камеры происходят относительно + * локальной системы координат, иначе относительно глобальной. + * + * @default true + */ + public function get moveLocal():Boolean { + return _moveLocal; + } + + /** + * @private + */ + public function set moveLocal(value:Boolean):void { + _moveLocal = value; + } + + /** + * Включение режима проверки столкновений. + */ + public function get checkCollisions():Boolean { + return _checkCollisions; + } + + /** + * @private + */ + public function set checkCollisions(value:Boolean):void { + _checkCollisions = value; + } + + /** + * Радиус сферы для определения столкновений. + * + * @default 0 + */ + public function get collisionRadius():Number { + return _collisionRadius; + } + + /** + * @private + */ + public function set collisionRadius(value:Number):void { + _collisionRadius = value; + if (_collider != null) { + _collider.radiusX = _collisionRadius; + _collider.radiusY = _collisionRadius; + _collider.radiusZ = _collisionRadius; + } + } + + /** + * Привязка клавиши к действию. + * + * @param keyCode код клавиши + * @param action наименование действия + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Активация движения камеры вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function forward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения камеры назад. + * + * @param value true для начала движения, false для окончания + */ + public function back(value:Boolean):void { + _back = value; + } + + /** + * Активация движения камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function left(value:Boolean):void { + _left = value; + } + + /** + * Активация движения камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function right(value:Boolean):void { + _right = value; + } + + /** + * Активация движения камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function up(value:Boolean):void { + _up = value; + } + + /** + * Активация движения камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function down(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * @private + */ + private function createCollider():void { + _collider = new EllipsoidCollider(_camera.scene, _collisionRadius); + _collider.offsetThreshold = 0.01; + _collider.collisionSet = _collisionIgnoreSet; + } + + /** + * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw. + * + * @default 1 + * + * @see #mousePitch() + * @see #mouseYaw() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mousePitch():Number { + return _mousePitch; + } + + /** + * @private + */ + public function set mousePitch(pitch:Number):void { + _mousePitch = pitch; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + } + + /** + * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mouseYaw():Number { + return _mouseYaw; + } + + /** + * @private + */ + public function set mouseYaw(yaw:Number):void { + _mouseYaw = yaw; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость поступательного движения (единицы в секунду). + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(spd:Number):void { + _speed = spd; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Активность управления камеры. + * + * @default false + */ + public function get controlsEnabled():Boolean { + return _controlsEnabled; + } + + /** + * @private + */ + public function set controlsEnabled(value:Boolean):void { + if (_camera == null || _controlsEnabled == value) return; + if (value) { + lastFrameTime = getTimer(); + registerEventListeners(); + } + else { + unregisterEventsListeners(); + } + _controlsEnabled = value; + } + + /** + * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину + * MouseEvent.delta. + * + * @default Math.PI / 180 + */ + public function get fovStep():Number { + return _fovStep; + } + + /** + * @private + */ + public function set fovStep(value:Number):void { + _fovStep = value; + } + + /** + * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier). + * + * @default 0.1 + */ + public function get zoomMultiplier():Number { + return _zoomMultiplier; + } + + /** + * @private + */ + public function set zoomMultiplier(value:Number):void { + _zoomMultiplier = value; + } + + /** + * @private + */ + private function registerEventListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + */ + private function unregisterEventsListeners():void { + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + * @param e + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * @private + * @param e + */ + private function onMouseDown(e:MouseEvent):void { + mouseLookActive = true; + currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY; + startRotX = _camera.rotationX; + startRotZ = _camera.rotationZ; + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseUp(e:MouseEvent):void { + mouseLookActive = false; + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseWheel(e:MouseEvent):void { + if (_camera.orthographic) { + _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier); + } else { + _camera.fov -= _fovStep * e.delta; + } + } + + /** + * Обработка управляющих воздействий. + * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate(). + * + * @see alternativa.engine3d.core.Scene3D#calculate() + */ + public function processInput(): void { + if (!_controlsEnabled || _camera == null) return; + + // Время в секундах от начала предыдущего кадра + var frameTime:Number = getTimer() - lastFrameTime; + lastFrameTime += frameTime; + frameTime /= 1000; + + // Обработка mouselook + if (mouseLookActive) { + prevDragCoords.x = currentDragCoords.x; + prevDragCoords.y = currentDragCoords.y; + currentDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = _eventsSource.stage.mouseY; + if (!prevDragCoords.equals(currentDragCoords)) { + _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff; + var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawLeft) { + _camera.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _camera.rotationZ -= _yawSpeed * frameTime; + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + rotX = _camera.rotationX + _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } else if (_pitchDown) { + rotX = _camera.rotationX - _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + + // TODO: Поворот относительно продольной оси (крен, roll) + + var frameDistance:Number = _speed * frameTime; + if (_accelerate) { + frameDistance *= _speedMultiplier; + } + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + var transformation:Matrix3D = _camera.transformation; + + if (_moveLocal) { + // Режим относительных пермещений + // Движение вперед-назад + if (_forward) { + velocity.x += frameDistance * transformation.c; + velocity.y += frameDistance * transformation.g; + velocity.z += frameDistance * transformation.k; + } else if (_back) { + velocity.x -= frameDistance * transformation.c; + velocity.y -= frameDistance * transformation.g; + velocity.z -= frameDistance * transformation.k; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * transformation.a; + velocity.y -= frameDistance * transformation.e; + velocity.z -= frameDistance * transformation.i; + } else if (_right) { + velocity.x += frameDistance * transformation.a; + velocity.y += frameDistance * transformation.e; + velocity.z += frameDistance * transformation.i; + } + // Движение вверх-вниз + if (_up) { + velocity.x -= frameDistance * transformation.b; + velocity.y -= frameDistance * transformation.f; + velocity.z -= frameDistance * transformation.j; + } else if (_down) { + velocity.x += frameDistance * transformation.b; + velocity.y += frameDistance * transformation.f; + velocity.z += frameDistance * transformation.j; + } + } + else { + // Режим глобальных перемещений + var cosZ:Number = Math.cos(_camera.rotationZ); + var sinZ:Number = Math.sin(_camera.rotationZ); + // Движение вперед-назад + if (_forward) { + velocity.x -= frameDistance * sinZ; + velocity.y += frameDistance * cosZ; + } else if (_back) { + velocity.x += frameDistance * sinZ; + velocity.y -= frameDistance * cosZ; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * cosZ; + velocity.y -= frameDistance * sinZ; + } else if (_right) { + velocity.x += frameDistance * cosZ; + velocity.y += frameDistance * sinZ; + } + // Движение вверх-вниз + if (_up) { + velocity.z += frameDistance; + } else if (_down) { + velocity.z -= frameDistance; + } + } + + // Коррекция модуля вектора скорости + if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) { + velocity.length = frameDistance; + } + + // Проверка столкновений + if (_checkCollisions) { + _collider.calculateDestination(_camera.coords, velocity, destination); + _camera.x = destination.x; + _camera.y = destination.y; + _camera.z = destination.z; + } else { + _camera.x += velocity.x; + _camera.y += velocity.y; + _camera.z += velocity.z; + } + + // Обработка начала/окончания движения + if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (_onStartMoving != null) { + _onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (_onStopMoving != null) { + _onStopMoving.call(this); + } + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as new file mode 100644 index 0000000..7f18af5 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as @@ -0,0 +1,450 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе + * координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения + * действуют вдоль локальных осей. + * + *

Соответствия локальных осей для объектов, не являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВперёдКрен
ZВверхРысканье
+ * + *

Соответствия локальных осей для объектов, являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВнизРысканье
ZВперёдКрен
+ * + * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или + * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются + * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши + * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент + * чувствительности мыши по горизонтали даёт угловую скорость по крену. + */ + public class FlyController extends ObjectController { + /** + * Имя действия для привязки клавиш поворота по крену влево. + */ + public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; + /** + * Имя действия для привязки клавиш поворота по крену вправо. + */ + public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + + private var _rollLeft:Boolean; + private var _rollRight:Boolean; + + private var _rollSpeed:Number = 1; + + private var rotations:Point3D; + private var rollMatrix:Matrix3D = new Matrix3D(); + private var transformation:Matrix3D = new Matrix3D(); + private var axis:Point3D = new Point3D(); + + private var velocity:Point3D = new Point3D(); + private var displacement:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + private var deltaVelocity:Point3D = new Point3D(); + private var accelerationVector:Point3D = new Point3D(); + private var currentTransform:Matrix3D = new Matrix3D(); + + private var _currentSpeed:Number = 1; + /** + * Текущие координаты мышиного курсора в режиме mouse look. + */ + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * Модуль вектора ускорния, получаемого от команд движения. + */ + public var acceleration:Number = 1000; + /** + * Модуль вектора замедляющего ускорения. + */ + public var deceleration:Number = 50; + /** + * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + /** + * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор + * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту + * прикладывается замедляющее ускорение. + */ + public var inertialMode:Boolean; + + /** + * @inheritDoc + */ + public function FlyController(eventsSourceObject:DisplayObject) { + super(eventsSourceObject); + + actionBindings[ACTION_ROLL_LEFT] = rollLeft; + actionBindings[ACTION_ROLL_RIGHT] = rollRight; + } + + /** + * Текущая скорость движения. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Активация вращения по крену влево. + */ + public function rollLeft(value:Boolean):void { + _rollLeft = value; + } + + /** + * Активация вращения по крену вправо. + */ + public function rollRight(value:Boolean):void { + _rollRight = value; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
CONTROLACTION_DOWN
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_ROLL_LEFT
RIGHTACTION_ROLL_RIGHT
QACTION_YAW_LEFT
EACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.CONTROL, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT); + bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями. + * + * @param frameTime длительность текущего кадра в секундах + */ + override protected function rotateObject(frameTime:Number):void { + var transformation:Matrix3D = _object.transformation; + if (_mouseLookActive) { + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!currentMouseCoords.equals(startMouseCoords)) { + var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX; + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + + rotateObjectAroundAxis(axis, deltaYaw * frameTime); + + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY; + axis.x = currentTransform.a; + axis.y = currentTransform.e; + axis.z = currentTransform.i; + + rotateObjectAroundAxis(axis, deltaPitch * frameTime); + } + } + + // Поворот относительно продольной оси (крен, roll) + if (_rollLeft) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, -_rollSpeed * frameTime); + } else if (_rollRight) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, _rollSpeed * frameTime); + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, _pitchSpeed * frameTime); + } else if (_pitchDown) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime); + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawRight) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } + } else if (_yawLeft) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + // Движение вперед-назад + accelerationVector.x = 0; + accelerationVector.y = 0; + accelerationVector.z = 0; + if (_forward) { + accelerationVector.y = 1; + } else if (_back) { + accelerationVector.y = -1; + } + // Движение влево-вправо + if (_right) { + accelerationVector.x = 1; + } else if (_left) { + accelerationVector.x = -1; + } + // Движение ввверх-вниз + if (_up) { + accelerationVector.z = 1; + } else if (_down) { + accelerationVector.z = -1; + } + + var speedLoss:Number; + var len:Number; + + if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) { + // Управление активно + if (_object is Camera3D) { + var tmp:Number = accelerationVector.z; + accelerationVector.z = accelerationVector.y; + accelerationVector.y = -tmp; + } + accelerationVector.normalize(); + accelerationVector.x *= acceleration; + accelerationVector.y *= acceleration; + accelerationVector.z *= acceleration; + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + accelerationVector.transform(currentTransform); + deltaVelocity.x = accelerationVector.x; + deltaVelocity.y = accelerationVector.y; + deltaVelocity.z = accelerationVector.z; + deltaVelocity.x *= frameTime; + deltaVelocity.y *= frameTime; + deltaVelocity.z *= frameTime; + + if (!inertialMode) { + speedLoss = deceleration * frameTime; + var dot:Number = Point3D.dot(velocity, accelerationVector); + if (dot > 0) { + len = accelerationVector.length; + var x:Number = accelerationVector.x / len; + var y:Number = accelerationVector.y / len; + var z:Number = accelerationVector.z / len; + len = dot / len; + x = velocity.x - len * x; + y = velocity.y - len * y; + z = velocity.z - len * z; + len = Math.sqrt(x*x + y*y + z*z); + if (len > speedLoss) { + x *= speedLoss / len; + y *= speedLoss / len; + z *= speedLoss / len; + } + velocity.x -= x; + velocity.y -= y; + velocity.z -= z; + } else { + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + velocity.x += deltaVelocity.x; + velocity.y += deltaVelocity.y; + velocity.z += deltaVelocity.z; + + if (velocity.length > _speed) { + velocity.length = _speed; + } + } else { + // Управление неактивно + if (!inertialMode) { + speedLoss = deceleration * frameTime; + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + displacement.x = velocity.x * frameTime; + displacement.y = velocity.y * frameTime; + displacement.z = velocity.z * frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x / frameTime; + velocity.y = displacement.y / frameTime; + velocity.z = displacement.z / frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Поворот объекта вокруг заданной оси. + * + * @param axis + * @param angle + */ + private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void { + transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + rollMatrix.fromAxisAngle(axis, angle); + rollMatrix.inverseCombine(transformation); + rotations = rollMatrix.getRotations(rotations); + _object.rotationX = rotations.x; + _object.rotationY = rotations.y; + _object.rotationZ = rotations.z; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as new file mode 100644 index 0000000..330c337 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as @@ -0,0 +1,839 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.utils.MathUtils; + import alternativa.utils.ObjectUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе + * реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд + * перемещения. + */ + public class ObjectController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + /** + * Флаг движения вперёд. + */ + protected var _forward:Boolean; + /** + * Флаг движения назад. + */ + protected var _back:Boolean; + /** + * Флаг движения влево. + */ + protected var _left:Boolean; + /** + * Флаг движения вправо. + */ + protected var _right:Boolean; + /** + * Флаг движения вверх. + */ + protected var _up:Boolean; + /** + * Флаг движения вниз. + */ + protected var _down:Boolean; + /** + * Флаг поворота относительно оси X в положительном направлении (взгляд вверх). + */ + protected var _pitchUp:Boolean; + /** + * Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз). + */ + protected var _pitchDown:Boolean; + /** + * Флаг поворота относительно оси Z в положительном направлении (взгляд налево). + */ + protected var _yawLeft:Boolean; + /** + * Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо). + */ + protected var _yawRight:Boolean; + /** + * Флаг активности режима ускорения. + */ + protected var _accelerate:Boolean; + /** + * Флаг активности режима поворотов мышью. + */ + protected var _mouseLookActive:Boolean; + /** + * Начальные координаты мышиного курсора в режиме mouse look. + */ + protected var startMouseCoords:Point3D = new Point3D(); + /** + * Флаг активности контроллера. + */ + protected var _enabled:Boolean = true; + /** + * Источник событий клавиатуры и мыши + */ + protected var _eventsSource:DisplayObject; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Map = new Map(); + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + protected var actionBindings:Map = new Map(); + /** + * Флаг активности клавиатуры. + */ + protected var _keyboardEnabled:Boolean; + /** + * Флаг активности мыши. + */ + protected var _mouseEnabled:Boolean; + /** + * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали. + */ + protected var _mouseSensitivity:Number = 1; + /** + * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseSensitivityY:Number = Math.PI / 360; + /** + * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseSensitivityX:Number = Math.PI / 360; + /** + * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY; + /** + * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX; + /** + * Угловая скорость поворота вокруг поперечной оси в радианах за секунду. + */ + protected var _pitchSpeed:Number = 1; + /** + * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду. + */ + protected var _yawSpeed:Number = 1; + /** + * Скорость поступательного движения в единицах за секунду. + */ + protected var _speed:Number = 100; + /** + * Коэффициент увеличения скорости при соответствующей активной команде. + */ + protected var _speedMultiplier:Number = 2; + /** + * Управляемый объект. + */ + protected var _object:Object3D; + /** + * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра). + */ + protected var lastFrameTime:uint; + /** + * Текущие координаты контроллера. + */ + protected var _coords:Point3D = new Point3D(); + /** + * Индикатор движения объекта (перемещения или поворота) в текущем кадре. + */ + protected var _isMoving:Boolean; + /** + * Объект для определения столкновений. + */ + protected var _collider:EllipsoidCollider = new EllipsoidCollider(); + + /** + * Включение и выключение режима проверки столкновений. + */ + public var checkCollisions:Boolean; + /** + * Функция вида function():void, вызываемая при начале движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStartMoving:Function; + /** + * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStopMoving:Function; + + // Вектор смещения + private var _displacement:Point3D = new Point3D(); + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + */ + public function ObjectController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + actionBindings[ACTION_MOUSE_LOOK] = setMouseLook; + + keyboardEnabled = true; + mouseEnabled = true; + } + + /** + * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput(). + * + * @default true + * + * @see #processInput() + */ + public function get enabled():Boolean { + return _enabled; + } + + /** + * @private + */ + public function set enabled(value:Boolean):void { + _enabled = value; + } + + /** + * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения + * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера. + * + * @see #setObjectCoords() + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + _coords.copy(value); + setObjectCoords(); + } + + /** + * Чтение координат контроллера в заданную переменную. + * + * @param point переменная, в которую записываются координаты контроллера + */ + public function readCoords(point:Point3D):void { + point.copy(_coords); + } + + /** + * Управляемый объект. + */ + public function get object():Object3D { + return _object; + } + + /** + * @private + * При установке объекта устанавливается сцена для коллайдера. + */ + public function set object(value:Object3D):void { + _object = value; + _collider.scene = _object == null ? null : _object.scene; + } + + /** + * Объект, реализующий проверку столкновений для эллипсоида. + */ + public function get collider():EllipsoidCollider { + return _collider; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Угловая скорость поворота вокруг поперечной оси (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(value:Number):void { + _speed = value < 0 ? -value : value; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY. + * + * @default 1 + * + * @see #mouseSensitivityY() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Чувтсвительность мыши по вертикали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivityY():Number { + return _mouseSensitivityY; + } + + /** + * @private + */ + public function set mouseSensitivityY(value:Number):void { + _mouseSensitivityY = value; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + } + + /** + * Чувтсвительность мыши по горизонтали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityY() + */ + public function get mouseSensitivityX():Number { + return _mouseSensitivityX; + } + + /** + * @private + */ + public function set mouseSensitivityX(value:Number):void { + _mouseSensitivityX = value; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(), + * при выключении — stoptMouseLook(). + * + * @see #startMouseLook() + * @see #stopMouseLook() + */ + public function setMouseLook(value:Boolean):void { + if (_mouseLookActive != value) { + _mouseLookActive = value; + if (_mouseLookActive) { + startMouseLook(); + } else { + stopMouseLook(); + } + } + } + + /** + * Метод выполняет необходимые действия при включении режима вращения объекта мышью. + * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords. + * + * @see #startMouseCoords + * @see #setMouseLook() + * @see #stopMouseLook() + */ + protected function startMouseLook():void { + startMouseCoords.x = _eventsSource.stage.mouseX; + startMouseCoords.y = _eventsSource.stage.mouseY; + } + + /** + * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает + * ничего. + * + * @see #setMouseLook() + * @see #startMouseLook() + */ + protected function stopMouseLook():void { + } + + /** + * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled + * равно false, метод не выполняется. + *

+ * Алгоритм работы метода следующий: + *

+ */ + public function processInput():void { + if (!_enabled || _object == null) { + return; + } + var frameTime:Number = getTimer() - lastFrameTime; + lastFrameTime += frameTime; + if (frameTime > 100) { + frameTime = 100; + } + frameTime /= 1000; + + rotateObject(frameTime); + getDisplacement(frameTime, _displacement); + applyDisplacement(frameTime, _displacement); + + // Обработка начала/окончания движения + if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (onStartMoving != null) { + onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (onStopMoving != null) { + onStopMoving.call(this); + } + } + } + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + */ + protected function rotateObject(frameTime:Number):void { + } + + /** + * Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement смещение объекта, которое нужно обработать + */ + protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + } + + /** + * Включение и выключение обработки клавиатурных событий. При включении выполняется метод registerKeyboardListeners, + * при выключении — unregisterKeyboardListeners. + * + * @see #registerKeyboardListeners() + * @see #unregisterKeyboardListeners() + */ + public function get keyboardEnabled():Boolean { + return _keyboardEnabled; + } + + /** + * @private + */ + public function set keyboardEnabled(value:Boolean):void { + if (_keyboardEnabled != value) { + _keyboardEnabled = value; + if (_keyboardEnabled) { + registerKeyboardListeners(); + } else { + unregisterKeyboardListeners(); + } + } + } + + /** + * @private + * Запуск обработчиков клавиатурных команд. + */ + private function onKeyboardEvent(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки клавиатурных событий. + * + * @see #unregisterKeyboardListeners() + */ + protected function registerKeyboardListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Удаление обработчиков при выключении обработки клавиатурных событий. + * + * @see #registerKeyboardListeners() + */ + protected function unregisterKeyboardListeners():void { + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners, + * при выключении — unregisterMouseListeners. + * + * @see #registerMouseListeners() + * @see #unregisterMouseListeners() + */ + public function get mouseEnabled():Boolean { + return _mouseEnabled; + } + + /** + * @private + */ + public function set mouseEnabled(value:Boolean):void { + if (_mouseEnabled != value) { + _mouseEnabled = value; + if (_mouseEnabled) { + registerMouseListeners(); + } else { + unregisterMouseListeners(); + } + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки мышиных событий. + * + * @see #unregisterMouseListeners() + */ + protected function registerMouseListeners():void { + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + } + + /** + * Удаление используемых обработчиков при выключении обработки мышиных событий. + * + * @see #registerMouseListeners() + */ + protected function unregisterMouseListeners():void { + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Активация mouselook + */ + private function onMouseDown(e:MouseEvent):void { + setMouseLook(true); + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Отключение mouselook + */ + private function onMouseUp(e:MouseEvent):void { + setMouseLook(false); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Установка координат управляемого объекта в зависимости от текущих координат контроллера. + */ + protected function setObjectCoords():void { + _object.coords = _coords; + } + + /** + * Индикатор режима увеличенной скорости. + */ + public function get accelerated():Boolean { + return _accelerate; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as new file mode 100644 index 0000000..0dd11fc --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as @@ -0,0 +1,508 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.physics.Collision; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены. + * + *

Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила + * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка + * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность + * движения по вертикали. + * + *

Для всех объектов, за исключением Camera3D, направлением "вперёд" считается направление его оси + * Y, направлением "вверх" — направление оси Z. Для объектов класса + * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление + * "вверх" противоположно направлению локальной оси Y. + * + *

Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для + * эллипсоида, параметры которого устанавливаются через свойство collider. Координаты управляемого + * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида, + * задаваемого параметром objectZPosition. + * + *

Команда ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях + * происходит движение вверх. + */ + public class WalkController extends ObjectController { + /** + * Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z, + * при отрицательном — по оси Z. + */ + public var gravity:Number = 0; + /** + * Вертикальная скорость прыжка. + */ + public var jumpSpeed:Number = 0; + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + private var _groundMesh:Mesh; + /** + * Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности + * скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + + // Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации. + private var _airControlCoefficient:Number = 1; + + private var _currentSpeed:Number = 0; + + private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70)); + + private var destination:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + + private var _objectZPosition:Number = 0.5; + private var _flyMode:Boolean; + private var _onGround:Boolean; + + private var velocity:Point3D = new Point3D(); + private var tmpVelocity:Point3D = new Point3D(); + + private var controlsActive:Boolean; + + private var inJump:Boolean; + private var startRotX:Number; + private var startRotZ:Number; + + // Координаты мышиного курсора в режиме mouse look в предыдущем кадре. + private var prevMouseCoords:Point3D = new Point3D(); + // Текущие координаты мышиного курсора в режиме mouse look. + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * @inheritDoc + */ + public function WalkController(eventSourceObject:DisplayObject) { + super(eventSourceObject); + } + + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + public function get groundMesh():Mesh { + return _groundMesh; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + + /** + * Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации. + * Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при + * нахождении на поверхности. + * + * @default 1 + */ + public function get airControlCoefficient():Number { + return _airControlCoefficient; + } + + /** + * @private + */ + public function set airControlCoefficient(value:Number):void { + _airControlCoefficient = value > 0 ? value : -value; + } + + /** + * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте + * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство + * onGround будет иметь значение false. + * + * @see #onGround + */ + public function get maxGroundAngle():Number { + return Math.acos(minGroundCos); + } + + /** + * @private + */ + public function set maxGroundAngle(value:Number):void { + minGroundCos = Math.cos(value); + } + + /** + * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида, + * значение 1 -- положение в верхней точке эллипсоида. + */ + public function get objectZPosition():Number { + return _objectZPosition; + } + + /** + * @private + */ + public function set objectZPosition(value:Number):void { + _objectZPosition = value; + setObjectCoords(); + } + + /** + * Включене и выключение режима полёта. + * + * @default false + */ + public function get flyMode():Boolean { + return _flyMode; + } + + /** + * @private + */ + public function set flyMode(value:Boolean):void { + _flyMode = value; + } + + /** + * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности, + * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения. + * + * @see #maxGroundAngle + */ + public function get onGround():Boolean { + return _onGround; + } + + /** + * Модуль текущей скорости. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
CONTROLACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.CONTROL, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен + * отклонением в 90 градусов от горизонтали. + * + * @param frameTime длительность текущего кадра в секундах + */ + override protected function rotateObject(frameTime:Number):void { + // Mouse look + var rotX:Number; + if (_mouseLookActive) { + prevMouseCoords.copy(currentMouseCoords); + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!prevMouseCoords.equals(currentMouseCoords)) { + _object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX; + rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY; + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90; + } else { + _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX; + } + } + } + + // Повороты влево-вправо + if (_yawLeft) { + _object.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _object.rotationZ -= _yawSpeed * frameTime; + } + // Взгляд вверх-вниз + rotX = NaN; + if (_pitchUp) { + rotX = _object.rotationX + _pitchSpeed * frameTime; + } else if (_pitchDown) { + rotX = _object.rotationX - _pitchSpeed * frameTime; + } + if (!isNaN(rotX)) { + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX; + } else { + _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX; + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + var cos:Number = 0; + if (checkCollisions && !_flyMode) { + // Проверка наличия под ногами поверхности + displacement.x = 0; + displacement.y = 0; + displacement.z = - 0.5 * gravity * frameTime * frameTime; + if (_collider.getCollision(_coords, displacement, collision)) { + cos = collision.normal.z; + _groundMesh = collision.face._mesh; + } else { + _groundMesh = null; + } + } + _onGround = cos > minGroundCos; + + if (_onGround && inJump) { + inJump = false; + } + + var len:Number; + var x:Number; + var y:Number; + var z:Number; + + // При наличии управляющих воздействий расчитывается приращение скорости + controlsActive = _forward || _back || _right || _left || _up || _down; + if (controlsActive) { + if (_flyMode) { + tmpVelocity.x = 0; + tmpVelocity.y = 0; + tmpVelocity.z = 0; + // Режим полёта, ускорения направлены вдоль локальных осей + // Ускорение вперёд-назад + if (_forward) { + tmpVelocity.y = 1; + } else if (_back) { + tmpVelocity.y = -1; + } + // Ускорение влево-вправо + if (_right) { + tmpVelocity.x = 1; + } else if (_left) { + tmpVelocity.x = -1; + } + // Ускорение вверх-вниз + if (_up) { + tmpVelocity.z = 1; + } else if (_down) { + tmpVelocity.z = -1; + } + var matrix:Matrix3D = _object.transformation; + x = tmpVelocity.x; + if (_object is Camera3D) { + y = -tmpVelocity.z; + z = tmpVelocity.y; + } else { + y = tmpVelocity.y; + z = tmpVelocity.z; + } + // Поворот вектора из локальной системы координат объекта в глобальную + velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed; + velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed; + velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed; + } else { + + // Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z + var heading:Number = _object.rotationZ; + var headingCos:Number = Math.cos(heading); + var headingSin:Number = Math.sin(heading); + + var spd:Number = _speed; + if (gravity != 0 && !_onGround) { + spd *= _airControlCoefficient; + } + + // Вперёд-назад + if (_forward) { + velocity.x -= spd * headingSin; + velocity.y += spd * headingCos; + } else if (_back) { + velocity.x += spd * headingSin; + velocity.y -= spd * headingCos; + } + // Влево-вправо + if (_right) { + velocity.x += spd * headingCos; + velocity.y += spd * headingSin; + } else if (_left) { + velocity.x -= spd * headingCos; + velocity.y -= spd * headingSin; + } + if (gravity == 0) { + // Ускорение вверх-вниз + if (_up) { + velocity.z += _speed; + } else if (_down) { + velocity.z -= _speed; + } + } + } + } else { + // Управление неактивно, замедление движения + len = 1 / Math.pow(3, frameTime * 10); + if (_flyMode || gravity == 0) { + velocity.x *= len; + velocity.y *= len; + velocity.z *= len; + } else { + if (_onGround) { + velocity.x *= len; + velocity.y *= len; + if (velocity.z < 0) { + velocity.z *= len; + } + } else { + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + } + } + // Прыжок + if (_onGround && _up && !inJump) { + velocity.z = jumpSpeed; + inJump = true; + _onGround = false; + cos = 0; + } + // В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности + if (!(_flyMode || _onGround)) { + velocity.z -= gravity * frameTime; + } + + // Ограничение скорости + var max:Number = _accelerate ? _speed * _speedMultiplier : _speed; + if (_flyMode || gravity == 0) { + len = velocity.length; + if (len > max) { + velocity.length = max; + } + } else { + len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y); + if (len > max) { + velocity.x *= max / len; + velocity.y *= max / len; + } + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + + // Cмещение за кадр + displacement.x = velocity.x * frameTime; + displacement.y = velocity.y * frameTime; + displacement.z = velocity.z * frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x / frameTime; + velocity.y = displacement.y / frameTime; + velocity.z = displacement.z / frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Метод устанавливает координаты управляемого объекта в зависимости от параметра objectZPosition. + * + * @see #objectZPosition + */ + override protected function setObjectCoords():void { + _object.x = _coords.x; + _object.y = _coords.y; + _object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ; + } + + /** + * Метод выполняет необходимые действия при включении вращения объекта мышью. + */ + override protected function startMouseLook():void { + super.startMouseLook(); + startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX; + startRotZ = _object.rotationZ; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as new file mode 100644 index 0000000..5f3ccf3 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as @@ -0,0 +1,65 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public final class BSPNode { + + // Родительская нода + alternativa3d var parent:BSPNode; + + // Дочерние ветки + alternativa3d var front:BSPNode; + alternativa3d var back:BSPNode; + + // Нормаль плоскости ноды + alternativa3d var normal:Point3D = new Point3D(); + + // Смещение плоскости примитива + alternativa3d var offset:Number; + + // Минимальная мобильность ноды + alternativa3d var mobility:int = int.MAX_VALUE; + + // Набор примитивов в ноде + alternativa3d var primitive:PolyPrimitive; + alternativa3d var backPrimitives:Set; + alternativa3d var frontPrimitives:Set; + + // Хранилище неиспользуемых нод + static private var collector:Array = new Array(); + + // Создать ноду на основе примитива + static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode { + // Достаём ноду из коллектора + var node:BSPNode = collector.pop(); + // Если коллектор пуст, создаём новую ноду + if (node == null) { + node = new BSPNode(); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + // Сохраняем ноду + primitive.node = node; + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + // Сохраняем мобильность + node.mobility = primitive.mobility; + return node; + } + + // Удалить ноду, все ссылки должны быть почищены + static alternativa3d function destroyBSPNode(node:BSPNode):void { + //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives); + collector.push(node); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..66ef3e7 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,902 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + import alternativa.engine3d.display.View; + import alternativa.engine3d.materials.DrawPoint; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + import flash.geom.Point; + import alternativa.utils.MathUtils; + + use namespace alternativa3d; + + /** + * Камера для отображения 3D-сцены на экране. + * + *

Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе + * координат родителя. + * + *

Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода — + * экземпляр класса alternativa.engine3d.display.View. + * + * @see alternativa.engine3d.display.View + */ + public class Camera3D extends Object3D { + + /** + * @private + * Расчёт матрицы пространства камеры + */ + alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX); + /** + * @private + * Расчёт плоскостей отсечения + */ + alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES); + /** + * @private + * Отрисовка + */ + alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Поле зрения + */ + alternativa3d var _fov:Number = Math.PI/2; + /** + * @private + * Фокусное расстояние + */ + alternativa3d var focalLength:Number; + /** + * @private + * Перспективное искажение + */ + alternativa3d var focalDistortion:Number; + + /** + * @private + * Флаги рассчитанности UV-матриц + */ + alternativa3d var uvMatricesCalculated:Set = new Set(true); + + // Всмомогательные точки для расчёта UV-матриц + private var textureA:Point3D = new Point3D(); + private var textureB:Point3D = new Point3D(); + private var textureC:Point3D = new Point3D(); + + /** + * @private + * Вид из камеры + */ + alternativa3d var _view:View; + + /** + * @private + * Режим отрисовки + */ + alternativa3d var _orthographic:Boolean = false; + private var fullDraw:Boolean; + + // Масштаб + private var _zoom:Number = 1; + + // Синус половинчатого угла обзора камеры + private var viewAngle:Number; + + // Направление камеры + private var direction:Point3D = new Point3D(0, 0, 1); + + // Обратная трансформация камеры + private var cameraMatrix:Matrix3D = new Matrix3D(); + + // Скины + private var firstSkin:Skin; + private var prevSkin:Skin; + private var currentSkin:Skin; + + // Плоскости отсечения + private var leftPlane:Point3D = new Point3D(); + private var rightPlane:Point3D = new Point3D(); + private var topPlane:Point3D = new Point3D(); + private var bottomPlane:Point3D = new Point3D(); + private var leftOffset:Number; + private var rightOffset:Number; + private var topOffset:Number; + private var bottomOffset:Number; + + // Вспомогательные массивы точек для отрисовки + private var points1:Array = new Array(); + private var points2:Array = new Array(); + + /** + * Создание нового экземпляра камеры. + * + * @param name имя экземпляра + */ + public function Camera3D(name:String = null) { + super(name); + } + + /** + * @private + */ + private function calculateMatrix():void { + // Расчёт матрицы пространства камеры + cameraMatrix.copy(transformation); + cameraMatrix.invert(); + if (_orthographic) { + cameraMatrix.scale(_zoom, _zoom, _zoom); + } + // Направление камеры + direction.x = transformation.c; + direction.y = transformation.g; + direction.z = transformation.k; + direction.normalize(); + } + + /** + * @private + * Расчёт плоскостей отсечения + */ + private function calculatePlanes():void { + var halfWidth:Number = _view._width*0.5; + var halfHeight:Number = _view._height*0.5; + + var aw:Number = transformation.a*halfWidth; + var ew:Number = transformation.e*halfWidth; + var iw:Number = transformation.i*halfWidth; + var bh:Number = transformation.b*halfHeight; + var fh:Number = transformation.f*halfHeight; + var jh:Number = transformation.j*halfHeight; + if (_orthographic) { + // Расчёт плоскостей отсечения в изометрии + aw /= _zoom; + ew /= _zoom; + iw /= _zoom; + bh /= _zoom; + fh /= _zoom; + jh /= _zoom; + + // Левая плоскость + leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g; + leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k; + leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c; + leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z; + + // Правая плоскость + rightPlane.x = -leftPlane.x; + rightPlane.y = -leftPlane.y; + rightPlane.z = -leftPlane.z; + rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z; + + // Верхняя плоскость + topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e; + topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i; + topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a; + topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = -topPlane.x; + bottomPlane.y = -topPlane.y; + bottomPlane.z = -topPlane.z; + bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z; + } else { + // Вычисляем расстояние фокуса + focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov); + // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции + focalDistortion = 1/(focalLength*focalLength); + + // Расчёт плоскостей отсечения в перспективе + var cl:Number = transformation.c*focalLength; + var gl:Number = transformation.g*focalLength; + var kl:Number = transformation.k*focalLength; + + // Угловые вектора пирамиды видимости + var leftTopX:Number = -aw - bh + cl; + var leftTopY:Number = -ew - fh + gl; + var leftTopZ:Number = -iw - jh + kl; + var rightTopX:Number = aw - bh + cl; + var rightTopY:Number = ew - fh + gl; + var rightTopZ:Number = iw - jh + kl; + var leftBottomX:Number = -aw + bh + cl; + var leftBottomY:Number = -ew + fh + gl; + var leftBottomZ:Number = -iw + jh + kl; + var rightBottomX:Number = aw + bh + cl; + var rightBottomY:Number = ew + fh + gl; + var rightBottomZ:Number = iw + jh + kl; + + // Левая плоскость + leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY; + leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ; + leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX; + leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z; + + // Правая плоскость + rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY; + rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ; + rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX; + rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z; + + // Верхняя плоскость + topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY; + topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ; + topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX; + topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY; + bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ; + bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX; + bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z; + + + // Расчёт угла конуса + var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ); + leftTopX /= length; + leftTopY /= length; + leftTopZ /= length; + length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ); + rightTopX /= length; + rightTopY /= length; + rightTopZ /= length; + length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ); + leftBottomX /= length; + leftBottomY /= length; + leftBottomZ /= length; + length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ); + rightBottomX /= length; + rightBottomY /= length; + rightBottomZ /= length; + + viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z; + var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + + viewAngle = Math.sin(Math.acos(viewAngle)); + } + } + + /** + * @private + */ + private function render():void { + // Режим отрисовки + fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued); + + // Очистка рассчитанных текстурных матриц + uvMatricesCalculated.clear(); + + // Отрисовка + prevSkin = null; + currentSkin = firstSkin; + renderBSPNode(_scene.bsp); + + // Удаление ненужных скинов + while (currentSkin != null) { + removeCurrentSkin(); + } + } + + /** + * @private + */ + private function renderBSPNode(node:BSPNode):void { + if (node != null) { + var primitive:*; + var normal:Point3D = node.normal; + var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z; + var cameraOffset:Number; + if (!_orthographic) { + cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset; + } + if (node.primitive != null) { + // В ноде только базовый примитив + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + drawSkin(node.primitive); + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + } + renderBSPNode(node.back); + } + } else { + // В ноде несколько примитивов + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + for (primitive in node.frontPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + for (primitive in node.backPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.back); + } + } + } + } + + /** + * @private + * Отрисовка скина примитива + */ + private function drawSkin(primitive:PolyPrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Проверка поверхности + var surface:Surface = primitive.face._surface; + if (surface == null) { + return; + } + // Проверка материала + var material:SurfaceMaterial = surface._material; + if (material == null || !material.canDraw(primitive)) { + return; + } + // Отсечение выходящих за окно просмотра частей + var i:uint; + var length:uint = primitive.num; + var primitivePoint:Point3D; + var primitiveUV:Point; + var point:DrawPoint; + var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase; + if (useUV) { + // Формируем список точек и UV-координат полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + primitiveUV = primitive.uvs[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + point.u = primitiveUV.x; + point.v = primitiveUV.y; + } + } + } else { + // Формируем список точек полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + } + } + } + // Отсечение по левой стороне + length = clip(length, points1, points2, leftPlane, leftOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по правой стороне + length = clip(length, points2, points1, rightPlane, rightOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по верхней стороне + length = clip(length, points1, points2, topPlane, topOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по нижней стороне + length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV); + if (length < 3) { + return; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Переводим координаты в систему камеры + var x:Number; + var y:Number; + var z:Number; + for (i = 0; i < length; i++) { + point = points1[i]; + x = point.x; + y = point.y; + z = point.z; + point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin, length, points1); + + // Переключаемся на следующий скин + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + + } else { + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + + } + } + } + + /** + * @private + * Отсечение полигона плоскостью. + */ + private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint { + var i:uint; + var k:Number; + var index:uint = 0; + var point:DrawPoint; + var point1:DrawPoint; + var point2:DrawPoint; + var offset1:Number; + var offset2:Number; + + point1 = points1[length - 1]; + offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset; + + if (calculateUV) { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + point.u = point2.u; + point.v = point2.v; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + + } else { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + } + return index; + } + + /** + * @private + * Добавление текущего скина. + */ + private function addCurrentSkin():void { + currentSkin = Skin.createSkin(); + _view.canvas.addChild(currentSkin); + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + * Вставляем под текущий скин. + */ + private function insertCurrentSkin():void { + var skin:Skin = Skin.createSkin(); + _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin)); + skin.nextSkin = currentSkin; + if (prevSkin == null) { + firstSkin = skin; + } else { + prevSkin.nextSkin = skin; + } + currentSkin = skin; + } + + /** + * @private + * Удаляет текущий скин. + */ + private function removeCurrentSkin():void { + // Сохраняем следующий + var next:Skin = currentSkin.nextSkin; + // Удаляем из канваса + _view.canvas.removeChild(currentSkin); + // Очистка скина + if (currentSkin.material != null) { + currentSkin.material.clear(currentSkin); + } + // Зачищаем ссылки + currentSkin.nextSkin = null; + currentSkin.primitive = null; + currentSkin.material = null; + // Удаляем + Skin.destroySkin(currentSkin); + // Следующий устанавливаем текущим + currentSkin = next; + // Устанавливаем связь от предыдущего скина + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + */ + alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void { + + // Расчёт точек базового примитива в координатах камеры + var point:Point3D = face.primitive.points[0]; + textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[1]; + textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[2]; + textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + + // Находим AB и AC + var abx:Number = textureB.x - textureA.x; + var aby:Number = textureB.y - textureA.y; + var acx:Number = textureC.x - textureA.x; + var acy:Number = textureC.y - textureA.y; + + // Расчёт текстурной матрицы + var uvMatrixBase:Matrix = face.uvMatrixBase; + var uvMatrix:Matrix = face.uvMatrix; + uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width; + uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width; + uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height; + uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height; + uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d; + uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h; + + // Помечаем, как рассчитанную + uvMatricesCalculated[face] = true; + } + + /** + * Поле вывода, в котором происходит отрисовка камеры. + */ + public function get view():View { + return _view; + } + + /** + * @private + */ + public function set view(value:View):void { + if (value != _view) { + if (_view != null) { + _view.camera = null; + } + if (value != null) { + value.camera = this; + } + } + } + + /** + * Включение режима аксонометрической проекции. + * + * @default false + */ + public function get orthographic():Boolean { + return _orthographic; + } + + /** + * @private + */ + public function set orthographic(value:Boolean):void { + if (_orthographic != value) { + // Отправляем сигнал об изменении типа камеры + addOperationToScene(calculateMatrixOperation); + // Сохраняем новое значение + _orthographic = value; + } + } + + /** + * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние + * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода. + * Угол зрения ограничен диапазоном 0-180 градусов. + */ + public function get fov():Number { + return _fov; + } + + /** + * @private + */ + public function set fov(value:Number):void { + value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value); + if (_fov != value) { + // Если перспектива + if (!_orthographic) { + // Отправляем сигнал об изменении плоскостей отсечения + addOperationToScene(calculatePlanesOperation); + } + // Сохраняем новое значение + _fov = value; + } + } + + /** + * Коэффициент увеличения изображения в режиме аксонометрической проекции. + */ + public function get zoom():Number { + return _zoom; + } + + /** + * @private + */ + public function set zoom(value:Number):void { + value = (value < 0) ? 0 : value; + if (_zoom != value) { + // Если изометрия + if (_orthographic) { + // Отправляем сигнал об изменении zoom + addOperationToScene(calculateMatrixOperation); + } + // Сохраняем новое значение + _zoom = value; + } + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + if (_view != null) { + // Отправляем операцию расчёта плоскостей отсечения + scene.addOperation(calculatePlanesOperation); + // Подписываемся на сигналы сцены + scene.changePrimitivesOperation.addSequel(renderOperation); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + super.removeFromScene(scene); + + // Удаляем все операции из очереди + scene.removeOperation(calculateMatrixOperation); + scene.removeOperation(calculatePlanesOperation); + scene.removeOperation(renderOperation); + + if (_view != null) { + // Отписываемся от сигналов сцены + scene.changePrimitivesOperation.removeSequel(renderOperation); + } + } + + /** + * @private + */ + alternativa3d function addToView(view:View):void { + // Сохраняем первый скин + firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null; + + // Подписка на свои операции + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.addSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.addSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.addSequel(renderOperation); + + if (_scene != null) { + // Отправляем сигнал перерисовки + _scene.addOperation(calculateMatrixOperation); + // Подписываемся на сигналы сцены + _scene.changePrimitivesOperation.addSequel(renderOperation); + } + + // Сохраняем вид + _view = view; + } + + /** + * @private + */ + alternativa3d function removeFromView(view:View):void { + // Сброс ссылки на первый скин + firstSkin = null; + + // Отписка от своих операций + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.removeSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.removeSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.removeSequel(renderOperation); + + if (_scene != null) { + // Удаляем все операции из очереди + _scene.removeOperation(calculateMatrixOperation); + _scene.removeOperation(calculatePlanesOperation); + _scene.removeOperation(renderOperation); + // Отписываемся от сигналов сцены + _scene.changePrimitivesOperation.removeSequel(renderOperation); + } + // Удаляем ссылку на вид + _view = null; + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "camera" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Camera3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Camera3D = Camera3D(source); + orthographic = src._orthographic; + zoom = src._zoom; + fov = src._fov; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..f5c0906 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as @@ -0,0 +1,921 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань + * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения + * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается + * матрица трансформации текстуры. + */ + final public class Face { + // Операции + /** + * @private + * Расчёт глобальной нормали плоскости грани. + */ + alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL); + /** + * @private + * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP). + */ + alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV); + /** + * @private + * Обновление примитива в сцене. + */ + alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE); + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL); + /** + * @private + * Расчёт UV для фрагментов (выполняется после трансформации, если её не было). + */ + alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Вершины грани + */ + alternativa3d var _vertices:Array; + /** + * @private + * Количество вершин + */ + alternativa3d var _verticesCount:uint; + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + // UV-координаты + /** + * @private + */ + alternativa3d var _aUV:Point; + /** + * @private + */ + alternativa3d var _bUV:Point; + /** + * @private + */ + alternativa3d var _cUV:Point; + + /** + * @private + * Коэффициенты базовой UV-матрицы + */ + alternativa3d var uvMatrixBase:Matrix; + + /** + * @private + * UV Матрица перевода текстурных координат в изометрическую камеру. + */ + alternativa3d var uvMatrix:Matrix; + /** + * @private + * Нормаль плоскости + */ + alternativa3d var globalNormal:Point3D = new Point3D(); + /** + * @private + * Смещение плоскости + */ + alternativa3d var globalOffset:Number; + + /** + * Создание экземпляра грани. + * + * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в + * порядке обхода лицевой стороны грани против часовой стрелки. + * + * @see Vertex + */ + public function Face(vertices:Array) { + // Сохраняем вершины + _vertices = vertices; + _verticesCount = vertices.length; + + // Создаём оригинальный примитив + primitive = PolyPrimitive.createPolyPrimitive(); + primitive.face = this; + primitive.num = _verticesCount; + + // Обрабатываем вершины + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = vertices[i]; + // Добавляем координаты вершины в примитив + primitive.points.push(vertex.globalCoords); + // Добавляем пустые UV-координаты в примитив + primitive.uvs.push(null); + // Добавляем вершину в грань + vertex.addToFace(this); + } + + // Расчёт нормали + calculateNormalOperation.addSequel(updatePrimitiveOperation); + + // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку + calculateUVOperation.addSequel(calculateFragmentsUVOperation); + calculateUVOperation.addSequel(updateMaterialOperation); + } + + /** + * @private + * Расчёт нормали в глобальных координатах + */ + private function calculateNormal():void { + // Вектор AB + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.globalCoords; + vertex = _vertices[1]; + var bv:Point3D = vertex.globalCoords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + // Вектор AC + vertex = _vertices[2]; + var cv:Point3D = vertex.globalCoords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + // Перпендикуляр к плоскости + globalNormal.x = acz*aby - acy*abz; + globalNormal.y = acx*abz - acz*abx; + globalNormal.z = acy*abx - acx*aby; + // Нормализация перпендикуляра + globalNormal.normalize(); + } + + /** + * @private + * Расчитывает глобальное смещение плоскости грани. + * Помечает конечные примитивы на удаление, а базовый на добавление в сцене. + */ + private function updatePrimitive():void { + // Расчёт смещения + var vertex:Vertex = _vertices[0]; + globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z; + + removePrimitive(primitive); + primitive.mobility = _mesh.inheritedMobility; + _mesh._scene.addPrimitives.push(primitive); + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены + */ + private function removePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + removePrimitive(primitive.backFragment); + removePrimitive(primitive.frontFragment); + primitive.backFragment = null; + primitive.frontFragment = null; + if (primitive != this.primitive) { + primitive.parent = null; + primitive.sibling = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } + } else { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _mesh._scene.removeBSPPrimitive(primitive); + } + } + } + + /** + * @private + * Пометка на перерисовку фрагментов грани. + */ + private function updateMaterial():void { + if (!updatePrimitiveOperation.queued) { + changePrimitive(primitive); + } + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку + */ + private function changePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + changePrimitive(primitive.backFragment); + changePrimitive(primitive.frontFragment); + } else { + _mesh._scene.changedPrimitives[primitive] = true; + } + } + + /** + * @private + * Расчёт UV-матрицы на основании первых трёх UV-координат. + * Расчёт UV-координат для оставшихся точек. + */ + private function calculateUV():void { + var i:uint; + // Расчёт UV-матрицы + if (_aUV != null && _bUV != null && _cUV != null) { + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + var det:Number = abu*acv - abv*acu; + if (det != 0) { + if (uvMatrixBase == null) { + uvMatrixBase = new Matrix(); + uvMatrix = new Matrix(); + } + uvMatrixBase.a = acv/det; + uvMatrixBase.b = -abv/det; + uvMatrixBase.c = -acu/det; + uvMatrixBase.d = abu/det; + uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y); + uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y); + + // Заполняем UV в базовом примитиве + primitive.uvs[0] = _aUV; + primitive.uvs[1] = _bUV; + primitive.uvs[2] = _cUV; + + // Расчёт недостающих UV + if (_verticesCount > 3) { + var a:Point3D = primitive.points[0]; + var b:Point3D = primitive.points[1]; + var c:Point3D = primitive.points[2]; + + var ab1:Number; + var ab2:Number; + var ac1:Number; + var ac2:Number; + var ad1:Number; + var ad2:Number; + var abk:Number; + var ack:Number; + + var uv:Point; + var point:Point3D; + + // Выбор наиболее подходящих осей для расчёта + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) { + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось X + ab1 = b.y - a.y; + ab2 = b.z - a.z; + ac1 = c.y - a.y; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.y - a.y; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } else { + if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось Y + ab1 = b.x - a.x; + ab2 = b.z - a.z; + ac1 = c.x - a.x; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } + } + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + uvMatrix = null; + // Удаляем UV-координаты из базового примитива + for (i = 0; i < _verticesCount; i++) { + primitive.uvs[i] = null; + } + } + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + uvMatrix = null; + // Удаляем UV-координаты из базового примитива + for (i = 0; i < _verticesCount; i++) { + primitive.uvs[i] = null; + } + } + } + + /** + * @private + * Расчёт UV-координат для фрагментов примитива, если не было трансформации + */ + private function calculateFragmentsUV():void { + // Если в этом цикле не было трансформации + if (!updatePrimitiveOperation.queued) { + if (uvMatrixBase != null) { + // Рассчитываем UV в примитиве + calculatePrimitiveUV(primitive); + } else { + // Удаляем UV в примитиве + removePrimitiveUV(primitive); + } + } + } + + /** + * @private + * Расчёт UV для точек базового примитива. + * + * @param primitive + */ + private function calculatePrimitiveUV(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + var points:Array = primitive.points; + var backPoints:Array = primitive.backFragment.points; + var frontPoints:Array = primitive.frontFragment.points; + var uvs:Array = primitive.uvs; + var backUVs:Array = primitive.backFragment.uvs; + var frontUVs:Array = primitive.frontFragment.uvs; + var index1:uint = 0; + var index2:uint = 0; + var point:Point3D; + var uv:Point; + var uv1:Point; + var uv2:Point; + var t:Number; + var firstSplit:Boolean = true; + for (var i:uint = 0; i < primitive.num; i++) { + var split:Boolean = true; + point = points[i]; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + split = false; + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + split = false; + index1++; + } + + if (split) { + uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)]; + uv2 = uvs[i]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + firstSplit = false; + index2++; + index1++; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + index1++; + } + } + } + // Проверяем рассечение последнего ребра + if (index2 < primitive.frontFragment.num) { + uv1 = uvs[primitive.num - 1]; + uv2 = uvs[0]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + } + + calculatePrimitiveUV(primitive.backFragment); + calculatePrimitiveUV(primitive.frontFragment); + } + } + + /** + * @private + * Удаление UV в примитиве и его фрагментах + * @param primitive + */ + private function removePrimitiveUV(primitive:PolyPrimitive):void { + // Очищаем список UV + for (var i:uint = 0; i < primitive.num; i++) { + primitive.uvs[i] = null; + } + // Если есть фрагменты, удаляем UV в них + if (primitive.backFragment != null) { + removePrimitiveUV(primitive.backFragment); + removePrimitiveUV(primitive.frontFragment); + } + } + + /** + * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex. + * + * @see Vertex + */ + public function get vertices():Array { + return new Array().concat(_vertices); + } + + /** + * Количество вершин грани. + */ + public function get verticesCount():uint { + return _verticesCount; + } + + /** + * Полигональный объект, которому принадлежит грань. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Поверхность, которой принадлежит грань. + */ + public function get surface():Surface { + return _surface; + } + + /** + * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор + * имеет значение null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getFaceId(this) : null; + } + + /** + * UV-координаты, соответствующие первой вершине грани. + */ + public function get aUV():Point { + return (_aUV != null) ? _aUV.clone() : null; + } + + /** + * UV-координаты, соответствующие второй вершине грани. + */ + public function get bUV():Point { + return (_bUV != null) ? _bUV.clone() : null; + } + + /** + * UV-координаты, соответствующие третьей вершине грани. + */ + public function get cUV():Point { + return (_cUV != null) ? _cUV.clone() : null; + } + + /** + * @private + */ + public function set aUV(value:Point):void { + if (_aUV != null) { + if (value != null) { + if (!_aUV.equals(value)) { + _aUV.x = value.x; + _aUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _aUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _aUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set bUV(value:Point):void { + if (_bUV != null) { + if (value != null) { + if (!_bUV.equals(value)) { + _bUV.x = value.x; + _bUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _bUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _bUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set cUV(value:Point):void { + if (_cUV != null) { + if (value != null) { + if (!_cUV.equals(value)) { + _cUV.x = value.x; + _cUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _cUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _cUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * Нормаль в локальной системе координат. + */ + public function get normal():Point3D { + var res:Point3D = new Point3D(); + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.coords; + vertex = _vertices[1]; + var bv:Point3D = vertex.coords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + vertex = _vertices[2]; + var cv:Point3D = vertex.coords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + res.x = acz*aby - acy*abz; + res.y = acx*abz - acz*abx; + res.z = acy*abx - acx*aby; + if (res.x != 0 || res.y != 0 || res.z != 0) { + var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z); + res.x /= k; + res.y /= k; + res.z /= k; + } + return res; + } + + /** + * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @return UV-координаты заданной точки + */ + public function getUV(point:Point3D):Point { + return getUVFast(point, normal); + } + + /** + * @private + * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта + * локальной нормали грани. Используется для оптимизации. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @param normal нормаль плоскости грани в локальной системе координат + * @return UV-координаты заданной точки + */ + alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point { + if (_aUV == null || _bUV == null || _cUV == null) { + return null; + } + + // Выбор наиболее длинной оси нормали + var dir:uint; + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) { + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 0; + } else { + dir = 2; + } + } else { + if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 1; + } else { + dir = 2; + } + } + + // Расчёт соотношения по векторам AB и AC + var v:Vertex = _vertices[0]; + var a:Point3D = v._coords; + v = _vertices[1]; + var b:Point3D = v._coords; + v = _vertices[2]; + var c:Point3D = v._coords; + + var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x); + var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z); + var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x); + var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z); + var det:Number = ab1*ac2 - ac1*ab2; + + var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x); + var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z); + var abk:Number = (ad1*ac2 - ac1*ad2)/det; + var ack:Number = (ab1*ad2 - ad1*ab2)/det; + + // Интерполяция по UV первых точек + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + + return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack); + } + + /** + * Множество граней, имеющих общие рёбра с текущей гранью. + */ + public function get edgeJoinedFaces():Set { + var res:Set = new Set(true); + // Перебираем точки грани + for (var i:uint = 0; i < _verticesCount; i++) { + var a:Vertex = _vertices[i]; + var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0]; + + // Перебираем грани текущей точки + for (var key:* in a._faces) { + var face:Face = key; + // Если это другая грань и у неё также есть следующая точка + if (face != this && face._vertices.indexOf(b) >= 0) { + // Значит у граней общее ребро + res[face] = true; + } + } + } + return res; + } + + /** + * @private + * Удаление всех вершин из грани. + * Очистка базового примитива. + */ + alternativa3d function removeVertices():void { + // Удалить вершины + for (var i:uint = 0; i < _verticesCount; i++) { + // Удаляем из списка + var vertex:Vertex = _vertices.pop(); + // Удаляем координаты вершины из примитива + primitive.points.pop(); + // Удаляем вершину из грани + vertex.removeFromFace(this); + } + // Обнуляем количество вершин + _verticesCount = 0; + } + + /** + * @private + * Добавление грани на сцену + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену рассчитываем плоскость и UV + scene.addOperation(calculateNormalOperation); + scene.addOperation(calculateUVOperation); + + // Подписываем сцену на операции + updatePrimitiveOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Удаление грани из сцены + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateUVOperation); + scene.removeOperation(calculateFragmentsUVOperation); + scene.removeOperation(calculateNormalOperation); + scene.removeOperation(updatePrimitiveOperation); + scene.removeOperation(updateMaterialOperation); + + // Удаляем примитивы из сцены + removePrimitive(primitive); + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Добавление грани в меш + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление грани из меша + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation); + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Добавление к поверхности + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Подписка поверхности на операции + surface.changeMaterialOperation.addSequel(updateMaterialOperation); + // Если при смене поверхности изменился материал + if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Сохранить поверхность + _surface = surface; + } + + /** + * @private + * Удаление из поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Отписка поверхности от операций + surface.changeMaterialOperation.removeSequel(updateMaterialOperation); + // Если был материал + if (surface._material != null) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Удалить ссылку на поверхность + _surface = null; + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : ""); + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = _vertices[i]; + res += vertex.id + ((i < _verticesCount - 1) ? ", " : ""); + } + res += "]"; + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as new file mode 100644 index 0000000..4f38fb9 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as @@ -0,0 +1,985 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNeedMoreVerticesError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.errors.SurfaceExistsError; + import alternativa.engine3d.errors.SurfaceNotFoundError; + import alternativa.engine3d.errors.VertexExistsError; + import alternativa.engine3d.errors.VertexNotFoundError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Map; + import alternativa.utils.ObjectUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект + * содержит в себе наборы вершин, граней и поверхностей. + */ + public class Mesh extends Object3D { + + // Инкремент количества объектов + private static var counter:uint = 0; + + // Инкременты для идентификаторов вершин, граней и поверхностей + private var vertexIDCounter:uint = 0; + private var faceIDCounter:uint = 0; + private var surfaceIDCounter:uint = 0; + + /** + * @private + * Список вершин + */ + alternativa3d var _vertices:Map = new Map(); + /** + * @private + * Список граней + */ + alternativa3d var _faces:Map = new Map(); + /** + * @private + * Список поверхностей + */ + alternativa3d var _surfaces:Map = new Map(); + + /** + * Создание экземпляра полигонального объекта. + * + * @param name имя экземпляра + */ + public function Mesh(name:String = null) { + super(name); + } + + /** + * Добавление новой вершины к объекту. + * + * @param x координата X в локальной системе координат объекта + * @param y координата Y в локальной системе координат объекта + * @param z координата Z в локальной системе координат объекта + * @param id идентификатор вершины. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной вершины + * + * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex { + // Проверяем ID + if (id != null) { + // Если уже есть вершина с таким ID + if (_vertices[id] != undefined) { + if (_vertices[id] is Vertex) { + throw new VertexExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный + while (_vertices[vertexIDCounter] != undefined) { + vertexIDCounter++; + } + id = vertexIDCounter; + } + + // Создаём вершину + var v:Vertex = new Vertex(x, y, z); + + // Добавляем вершину на сцену + if (_scene != null) { + v.addToScene(_scene); + } + + // Добавляем вершину в меш + v.addToMesh(this); + _vertices[id] = v; + + return v; + } + + /** + * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины + * + * @return экземпляр удалённой вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeVertex(vertex:Object):Vertex { + var byLink:Boolean = vertex is Vertex; + + // Проверяем на null + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + + // Проверяем наличие вершины в меше + if (byLink) { + // Если удаляем по ссылке + if (Vertex(vertex)._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + } else { + // Если удаляем по ID + if (_vertices[vertex] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertex, this); + } else if (!(_vertices[vertex] is Vertex)) { + // По этому id не вершина + throw new InvalidIDError(vertex, this); + } + } + + // Находим вершину и её ID + var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex]; + var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex; + + // Удаляем вершину из сцены + if (_scene != null) { + v.removeFromScene(_scene); + } + + // Удаляем вершину из меша + v.removeFromMesh(this); + delete _vertices[id]; + + return v; + } + + /** + * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная + * ни к одной поверхности. + * + * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой + * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex, + * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину. + * @param id идентификатор грани. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной грани + * + * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан + * null, либо количество вершин в массиве меньше трёх + * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива + * + * @see Vertex + */ + public function createFace(vertices:Array, id:Object = null):Face { + + // Проверяем на null + if (vertices == null) { + throw new FaceNeedMoreVerticesError(this); + } + + // Проверяем ID + if (id != null) { + // Если уже есть грань с таким ID + if (_faces[id] != undefined) { + if (_faces[id] is Face) { + throw new FaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_faces[faceIDCounter] != undefined) { + faceIDCounter++; + } + id = faceIDCounter; + } + + // Проверяем количество точек + var length:uint = vertices.length; + if (length < 3) { + throw new FaceNeedMoreVerticesError(this, length); + } + + // Проверяем и формируем список вершин + var v:Array = new Array(); + var vertex:Vertex; + for (var i:uint = 0; i < length; i++) { + if (vertices[i] is Vertex) { + // Если работаем со ссылками + vertex = vertices[i]; + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertices[i], this); + } + } else { + // Если работаем с ID + if (_vertices[vertices[i]] == null) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertices[i], this); + } else if (!(_vertices[vertices[i]] is Vertex)) { + // Если id зарезервировано + throw new InvalidIDError(vertices[i],this); + } + vertex = _vertices[vertices[i]]; + } + v.push(vertex); + } + + // Создаём грань + var f:Face = new Face(v); + + // Добавляем грань на сцену + if (_scene != null) { + f.addToScene(_scene); + } + + // Добавляем грань в меш + f.addToMesh(this); + _faces[id] = f; + + return f; + } + + /** + * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит. + * + * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани + * + * @return экземпляр удалённой грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeFace(face:Object):Face { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань и её ID + var f:Face = byLink ? Face(face) : _faces[face] ; + var id:Object = byLink ? getFaceId(Face(face)) : face; + + // Удаляем грань из сцены + if (_scene != null) { + f.removeFromScene(_scene); + } + + // Удаляем вершины из грани + f.removeVertices(); + + // Удаляем грань из поверхности + if (f._surface != null) { + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Удаляем грань из меша + f.removeFromMesh(this); + delete _faces[id]; + + return f; + } + + /** + * Добавление новой поверхности к объекту. + * + * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса + * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать + * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если + * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность. + * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function createSurface(faces:Array = null, id:Object = null):Surface { + // Проверяем ID + if (id != null) { + // Если уже есть поверхность с таким ID + if (_surfaces[id] != undefined) { + if (_surfaces[id] is Surface) { + throw new SurfaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_surfaces[surfaceIDCounter] != undefined) { + surfaceIDCounter++; + } + id = surfaceIDCounter; + } + + // Создаём поверхность + var s:Surface = new Surface(); + + // Добавляем поверхность на сцену + if (_scene != null) { + s.addToScene(_scene); + } + + // Добавляем поверхность в меш + s.addToMesh(this); + _surfaces[id] = s; + + // Добавляем грани, если есть + if (faces != null) { + var length:uint = faces.length; + for (var i:uint = 0; i < length; i++) { + s.addFace(faces[i]); + } + } + + return s; + } + + /** + * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани. + * + * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности + * + * @return экземпляр удалённой поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeSurface(surface:Object):Surface { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если удаляем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если удаляем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность и её ID + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface; + + // Удаляем поверхность из сцены + if (_scene != null) { + s.removeFromScene(_scene); + } + + // Удаляем грани из поверхности + s.removeFaces(); + + // Удаляем поверхность из меша + s.removeFromMesh(this); + delete _surfaces[id]; + + return s; + } + + /** + * Добавление всех граней объекта в указанную поверхность. + * + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в + * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором, + * будет создана новая поверхность. + * + * @param removeSurfaces удалять или нет пустые поверхности после переноса граней + * + * @return экземпляр поверхности, в которую перенесены грани + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface { + var returnSurface:Surface; + var returnSurfaceId:Object; + if (surface is Surface) { + // Работаем с экземпляром Surface + if (surface._mesh == this) { + returnSurface = Surface(surface); + } else { + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Работаем с идентификатором + if (_surfaces[surface] == undefined) { + // Поверхности еще нет + returnSurface = createSurface(null, surface); + returnSurfaceId = surface; + } else { + if (_surfaces[surface] is Surface) { + returnSurface = _surfaces[surface]; + } else { + // _surfaces[surface] по идентификатору возвращает не Surface + throw new InvalidIDError(surface, this); + } + } + } + // Перемещаем все грани + for each (var face:Face in _faces) { + if (face._surface != returnSurface) { + returnSurface.addFace(face); + } + } + if (removeSurfaces) { + // Удаляем старые, теперь вручную - меньше проверок, но рискованно + if (returnSurfaceId == null) { + returnSurfaceId = getSurfaceId(returnSurface); + } + var newSurfaces:Map = new Map(); + newSurfaces[returnSurfaceId] = returnSurface; + delete _surfaces[returnSurfaceId]; + // Удаляем оставшиеся + for (var currentSurfaceId:* in _surfaces) { + // Удаляем поверхность из сцены + var currentSurface:Surface = _surfaces[currentSurfaceId]; + if (_scene != null) { + currentSurface.removeFromScene(_scene); + } + // Удаляем поверхность из меша + currentSurface.removeFromMesh(this); + delete _surfaces[currentSurfaceId]; + } + // Новый список граней + _surfaces = newSurfaces; + } + return returnSurface; + } + + /** + * Установка материала для указанной поверхности. + * + * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности. + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Surface + */ + public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если назначаем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если назначаем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + + // Назначаем материал + s.material = material; + } + + /** + * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала. + * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей. + * + * @param material устанавливаемый материал + */ + public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void { + for each (var surface:Surface in _surfaces) { + surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null; + } + } + + /** + * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по + * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать + * невырожденный треугольник в UV-пространстве. + * + * @param aUV UV-координаты, соответствующие первой вершине грани + * @param bUV UV-координаты, соответствующие второй вершине грани + * @param cUV UV-координаты, соответствующие третьей вершине грани + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если назначаем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если назначаем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _faces[face]; + + // Назначаем UV-координаты + f.aUV = aUV; + f.bUV = bUV; + f.cUV = cUV; + } + + /** + * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин. + */ + public function get vertices():Map { + return _vertices.clone(); + } + + /** + * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней. + */ + public function get faces():Map { + return _faces.clone(); + } + + /** + * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей. + */ + public function get surfaces():Map { + return _surfaces.clone(); + } + + /** + * Получение вершины объекта по её идентификатору. + * + * @param id идентификатор вершины + * + * @return экземпляр вершины с указанным идентификатором + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getVertexById(id:Object):Vertex { + if (id == null) { + throw new VertexNotFoundError(null, this); + } + if (_vertices[id] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(id, this); + } else { + if (_vertices[id] is Vertex) { + return _vertices[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора вершины объекта. + * + * @param экземпляр вершины + * + * @return идентификатор указанной вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + */ + public function getVertexId(vertex:Vertex):Object { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + for (var i:Object in _vertices) { + if (_vertices[i] == vertex) { + return i; + } + } + throw new VertexNotFoundError(vertex, this); + } + + /** + * Проверка наличия вершины в объекте. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины + * + * @return true, если объект содержит указанную вершину, иначе false + * + * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Vertex + */ + public function hasVertex(vertex:Object):Boolean { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex is Vertex) { + // Проверка вершины + return vertex._mesh == this; + } else { + // Проверка ID вершины + if (_vertices[vertex] != undefined) { + // По этому ID есть объект + if (_vertices[vertex] is Vertex) { + // Объект является вершиной + return true; + } else { + // ID некорректный + throw new InvalidIDError(vertex, this); + } + } else { + return false; + } + } + } + + /** + * Получение грани объекта по ее идентификатору. + * + * @param id идентификатор грани + * + * @return экземпляр грани с указанным идентификатором + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getFaceById(id:Object):Face { + if (id == null) { + throw new FaceNotFoundError(null, this); + } + if (_faces[id] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(id, this); + } else { + if (_faces[id] is Face) { + return _faces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора грани объекта. + * + * @param face экземпляр грани + * + * @return идентификатор указанной грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + */ + public function getFaceId(face:Face):Object { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + for (var i:Object in _faces) { + if (_faces[i] == face) { + return i; + } + } + throw new FaceNotFoundError(face, this); + } + + /** + * Проверка наличия грани в объекте. + * + * @param face экземпляр класса Face или идентификатор грани + * + * @return true, если объект содержит указанную грань, иначе false + * + * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasFace(face:Object):Boolean { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face is Face) { + // Проверка грани + return face._mesh == this; + } else { + // Проверка ID грани + if (_faces[face] != undefined) { + // По этому ID есть объект + if (_faces[face] is Face) { + // Объект является гранью + return true; + } else { + // ID некорректный + throw new InvalidIDError(face, this); + } + } else { + return false; + } + } + } + + /** + * Получение поверхности объекта по ее идентификатору + * + * @param id идентификатор поверхности + * + * @return экземпляр поверхности с указанным идентификатором + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getSurfaceById(id:Object):Surface { + if (id == null) { + throw new SurfaceNotFoundError(null, this); + } + if (_surfaces[id] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(id, this); + } else { + if (_surfaces[id] is Surface) { + return _surfaces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора поверхности объекта. + * + * @param surface экземпляр поверхности + * + * @return идентификатор указанной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + */ + public function getSurfaceId(surface:Surface):Object { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + for (var i:Object in _surfaces) { + if (_surfaces[i] == surface) { + return i; + } + } + return null; + } + + /** + * Проверка наличия поверхности в объекте. + * + * @param surface экземпляр класса Surface или идентификатор поверхности + * + * @return true, если объект содержит указанную поверхность, иначе false + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasSurface(surface:Object):Boolean { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface is Surface) { + // Проверка поверхности + return surface._mesh == this; + } else { + // Проверка ID поверхности + if (_surfaces[surface] != undefined) { + // По этому ID есть объект + if (_surfaces[surface] is Surface) { + // Объект является поверхностью + return true; + } else { + // ID некорректный + throw new InvalidIDError(surface, this); + } + } else { + return false; + } + } + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + var vertex:Vertex; + var face:Face; + var surface:Surface; + if (value != null) { + // Добавить вершины на сцену + for each (vertex in _vertices) { + vertex.addToScene(value); + } + // Добавить грани на сцену + for each (face in _faces) { + face.addToScene(value); + } + // Добавить поверхности на сцену + for each (surface in _surfaces) { + surface.addToScene(value); + } + } else { + // Удалить вершины из сцены + for each (vertex in _vertices) { + vertex.removeFromScene(_scene); + } + // Удалить грани из сцены + for each (face in _faces) { + face.removeFromScene(_scene); + } + // Удалить поверхности из сцены + for each (surface in _surfaces) { + surface.removeFromScene(_scene); + } + } + } + super.setScene(value); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "mesh" + ++counter; + } + + /** + * @inheritDoc + */ + override public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]"; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Mesh(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Mesh = Mesh(source); + + var id:*; + var len:int; + var i:int; + // Копирование вершин + var vertexMap:Map = new Map(true); + for (id in src._vertices) { + var sourceVertex:Vertex = src._vertices[id]; + vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id); + } + + // Копирование граней + var faceMap:Map = new Map(true); + for (id in src._faces) { + var sourceFace:Face = src._faces[id]; + len = sourceFace._vertices.length; + var faceVertices:Array = new Array(len); + for (i = 0; i < len; i++) { + faceVertices[i] = vertexMap[sourceFace._vertices[i]]; + } + var newFace:Face = createFace(faceVertices, id); + newFace.aUV = sourceFace._aUV; + newFace.bUV = sourceFace._bUV; + newFace.cUV = sourceFace._cUV; + faceMap[sourceFace] = newFace; + } + + // Копирование поверхностей + for (id in src._surfaces) { + var sourceSurface:Surface = src._surfaces[id]; + var surfaceFaces:Array = sourceSurface._faces.toArray(); + len = surfaceFaces.length; + for (i = 0; i < len; i++) { + surfaceFaces[i] = faceMap[surfaceFaces[i]]; + } + var surface:Surface = createSurface(surfaceFaces, id); + var sourceMaterial:SurfaceMaterial = sourceSurface.material; + if (sourceMaterial != null) { + surface.material = SurfaceMaterial(sourceMaterial.clone()); + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..d2133da --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,708 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.Object3DHierarchyError; + import alternativa.engine3d.errors.Object3DNotFoundError; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + use namespace alternativa3d; + + /** + * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения + * о трансформации объекта как единого целого. + * + *

Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая + * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей + * X, Y, Z и параллельного переноса центра объекта из начала координат. + * Операции применяются в порядке их перечисления. + * + *

Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций + * самого объекта и всех его предков по иерархии объектов сцены. + */ + public class Object3D { + // Операции + /** + * @private + * Поворот или масштабирование + */ + alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this); + /** + * @private + * Перемещение + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт матрицы трансформации + */ + alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION); + /** + * @private + * Изменение уровеня мобильности + */ + alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Наименование + */ + alternativa3d var _name:String; + /** + * @private + * Сцена + */ + alternativa3d var _scene:Scene3D; + /** + * @private + * Родительский объект + */ + alternativa3d var _parent:Object3D; + /** + * @private + * Дочерние объекты + */ + alternativa3d var _children:Set = new Set(); + /** + * @private + * Уровень мобильности + */ + alternativa3d var _mobility:int = 0; + /** + * @private + */ + alternativa3d var inheritedMobility:int; + /** + * @private + * Координаты объекта относительно родителя + */ + alternativa3d var _coords:Point3D = new Point3D(); + /** + * @private + * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationX:Number = 0; + /** + * @private + * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationY:Number = 0; + /** + * @private + * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationZ:Number = 0; + /** + * @private + * Мастшаб объекта по оси X относительно родителя + */ + alternativa3d var _scaleX:Number = 1; + /** + * @private + * Мастшаб объекта по оси Y относительно родителя + */ + alternativa3d var _scaleY:Number = 1; + /** + * @private + * Мастшаб объекта по оси Z относительно родителя + */ + alternativa3d var _scaleZ:Number = 1; + /** + * @private + * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены + */ + alternativa3d var transformation:Matrix3D = new Matrix3D(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра класса. + * + * @param name имя экземпляра + */ + public function Object3D(name:String = null) { + // Имя по-умолчанию + _name = (name != null) ? name : defaultName(); + + // Последствия операций + changeRotationOrScaleOperation.addSequel(calculateTransformationOperation); + changeCoordsOperation.addSequel(calculateTransformationOperation); + } + + /** + * @private + * Расчёт трансформации + */ + private function calculateTransformation():void { + if (changeRotationOrScaleOperation.queued) { + // Если полная трансформация + transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + if (_parent != null) { + transformation.combine(_parent.transformation); + } + // Сохраняем глобальные координаты объекта + globalCoords.x = transformation.d; + globalCoords.y = transformation.h; + globalCoords.z = transformation.l; + } else { + // Если только перемещение + globalCoords.copy(_coords); + if (_parent != null) { + globalCoords.transform(_parent.transformation); + } + transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z); + } + } + + /** + * @private + * Расчёт общей мобильности + */ + private function calculateMobility():void { + inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility; + } + + /** + * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя. + * Новой сценой дочернего объекта становится сцена родителя. + * + * @param child добавляемый объект + * + * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены + */ + public function addChild(child:Object3D):void { + + // Проверка на null + if (child == null) { + throw new Object3DHierarchyError(null, this); + } + + // Проверка на наличие + if (child._parent == this) { + return; + } + + // Проверка на добавление к самому себе + if (child == this) { + throw new Object3DHierarchyError(this, this); + } + + // Проверка на добавление родительского объекта + if (child._scene == _scene) { + // Если объект был в той же сцене, либо оба не были в сцене + var parentObject:Object3D = _parent; + while (parentObject != null) { + if (child == parentObject) { + throw new Object3DHierarchyError(child, this); + return; + } + parentObject = parentObject._parent; + } + } + + // Если объект был в другом объекте + if (child._parent != null) { + // Удалить его оттуда + child._parent._children.remove(child); + } else { + // Если объект был корневым в сцене + if (child._scene != null && child._scene._root == child) { + child._scene.root = null; + } + } + + // Добавляем в список + _children.add(child); + // Указываем себя как родителя + child.setParent(this); + // Устанавливаем уровни + child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1); + // Указываем сцену + child.setScene(_scene); + } + + /** + * Удаление дочернего объекта. + * + * @param child удаляемый дочерний объект + * + * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта + */ + public function removeChild(child:Object3D):void { + // Проверка на null + if (child == null) { + throw new Object3DNotFoundError(null, this); + } + // Проверка на наличие + if (child._parent != this) { + throw new Object3DNotFoundError(child, this); + } + // Убираем из списка + _children.remove(child); + // Удаляем ссылку на родителя + child.setParent(null); + // Удаляем ссылку на сцену + child.setScene(null); + } + + /** + * @private + * Установка родительского объекта. + * + * @param value родительский объект + */ + alternativa3d function setParent(value:Object3D):void { + // Отписываемся от сигналов старого родителя + if (_parent != null) { + removeParentSequels(); + } + // Сохранить родителя + _parent = value; + // Если устанавливаем родителя + if (value != null) { + // Подписка на сигналы родителя + addParentSequels(); + } + } + + /** + * @private + * Установка новой сцены для объекта. + * + * @param value сцена + */ + alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + // Если была сцена + if (_scene != null) { + // Удалиться из сцены + removeFromScene(_scene); + } + // Если новая сцена + if (value != null) { + // Добавиться на сцену + addToScene(value); + } + // Сохранить сцену + _scene = value; + } else { + // Посылаем операцию трансформации + addOperationToScene(changeRotationOrScaleOperation); + // Посылаем операцию пересчёта мобильности + addOperationToScene(calculateMobilityOperation); + } + // Установить эту сцену у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setScene(_scene); + } + } + + /** + * @private + * Установка уровня операции трансформации. + * + * @param value уровень операции трансформации + */ + alternativa3d function setLevel(value:uint):void { + // Установить уровень операции трансформации и расчёта мобильности + calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value; + calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value; + // Установить уровни у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setLevel(value + 1); + } + } + + /** + * @private + * Подписка на сигналы родителя. + */ + private function addParentSequels():void { + _parent.changeCoordsOperation.addSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation); + } + + /** + * @private + * Удаление подписки на сигналы родителя. + */ + private function removeParentSequels():void { + _parent.changeCoordsOperation.removeSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation); + } + + /** + * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, в которую добавляется объект + */ + protected function addToScene(scene:Scene3D):void { + // При добавлении на сцену полная трансформация и расчёт мобильности + scene.addOperation(changeRotationOrScaleOperation); + scene.addOperation(calculateMobilityOperation); + } + + /** + * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, из которой удаляется объект + */ + protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeRotationOrScaleOperation); + scene.removeOperation(changeCoordsOperation); + scene.removeOperation(calculateMobilityOperation); + } + + /** + * Имя объекта. + */ + public function get name():String { + return _name; + } + + /** + * @private + */ + public function set name(value:String):void { + _name = value; + } + + /** + * Сцена, которой принадлежит объект. + */ + public function get scene():Scene3D { + return _scene; + } + + /** + * Родительский объект. + */ + public function get parent():Object3D { + return _parent; + } + + /** + * Набор дочерних объектов. + */ + public function get children():Set { + return _children.clone(); + } + + /** + * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков + * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные + * объекты находятся ближе к корню дерева, чем более мобильные. + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + if (_mobility != value) { + _mobility = value; + addOperationToScene(calculateMobilityOperation); + } + } + + /** + * Координата X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * Координата Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * Координата Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Координаты объекта. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Угол поворота вокруг оси X, заданный в радианах. + */ + public function get rotationX():Number { + return _rotationX; + } + + /** + * Угол поворота вокруг оси Y, заданный в радианах. + */ + public function get rotationY():Number { + return _rotationY; + } + + /** + * Угол поворота вокруг оси Z, заданный в радианах. + */ + public function get rotationZ():Number { + return _rotationZ; + } + + /** + * @private + */ + public function set rotationX(value:Number):void { + if (_rotationX != value) { + _rotationX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationY(value:Number):void { + if (_rotationY != value) { + _rotationY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationZ(value:Number):void { + if (_rotationZ != value) { + _rotationZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Коэффициент масштабирования вдоль оси X. + */ + public function get scaleX():Number { + return _scaleX; + } + + /** + * Коэффициент масштабирования вдоль оси Y. + */ + public function get scaleY():Number { + return _scaleY; + } + + /** + * Коэффициент масштабирования вдоль оси Z. + */ + public function get scaleZ():Number { + return _scaleZ; + } + + /** + * @private + */ + public function set scaleX(value:Number):void { + if (_scaleX != value) { + _scaleX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleY(value:Number):void { + if (_scaleY != value) { + _scaleY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleZ(value:Number):void { + if (_scaleZ != value) { + _scaleZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + "]"; + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + protected function defaultName():String { + return "object" + ++counter; + } + + /** + * @private + * Добавление операции в очередь. + * + * @param operation добавляемая операция + */ + alternativa3d function addOperationToScene(operation:Operation):void { + if (_scene != null) { + _scene.addOperation(operation); + } + } + + /** + * @private + * Удаление операции из очереди. + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperationFromScene(operation:Operation):void { + if (_scene != null) { + _scene.removeOperation(operation); + } + } + + /** + * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при + * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и + * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения + * правильного объекта. + * + * @return новый пустой объект + */ + protected function createEmptyObject():Object3D { + return new Object3D(); + } + + /** + * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в + * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те + * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется + * вызовом super.clonePropertiesFrom(source). + * + * @param source объект, свойства которого копируются + */ + protected function clonePropertiesFrom(source:Object3D):void { + _name = source._name; + _mobility = source._mobility; + _coords.x = source._coords.x; + _coords.y = source._coords.y; + _coords.z = source._coords.z; + _rotationX = source._rotationX; + _rotationY = source._rotationY; + _rotationZ = source._rotationZ; + _scaleX = source._scaleX; + _scaleY = source._scaleY; + _scaleZ = source._scaleZ; + } + + /** + * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы + * createEmptyObject() и clonePropertiesFrom(). + * + * @return клонированный экземпляр объекта + * + * @see #createEmptyObject() + * @see #clonePropertiesFrom() + */ + public function clone():Object3D { + var copy:Object3D = createEmptyObject(); + copy.clonePropertiesFrom(this); + + // Клонирование детей + for (var key:* in _children) { + var child:Object3D = key; + copy.addChild(child.clone()); + } + + return copy; + } + + /** + * Получение дочернего объекта с заданным именем. + * + * @param name имя дочернего объекта + * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов + */ + public function getChildByName(name:String):Object3D { + for (var key:* in _children) { + var child:Object3D = key; + if (child._name == name) { + return child; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as new file mode 100644 index 0000000..ca430ac --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as @@ -0,0 +1,125 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public class Operation { + + alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000; + alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000; + alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000; + alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000; + alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000; + alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000; + alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000; + alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000; + alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000; + alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000; + alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000; + alternativa3d static const CAMERA_RENDER:uint = 0x0C000000; + alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000; + + // Объект + alternativa3d var object:Object; + + // Метод + alternativa3d var method:Function; + + // Название метода + alternativa3d var name:String; + + // Последствия + private var sequel:Operation; + private var sequels:Set; + + // Приоритет операции + alternativa3d var priority:uint; + + // Находится ли операция в очереди + alternativa3d var queued:Boolean = false; + + public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) { + this.object = object; + this.method = method; + this.name = name; + this.priority = priority; + } + + // Добавить последствие + alternativa3d function addSequel(operation:Operation):void { + if (sequel == null) { + if (sequels == null) { + sequel = operation; + } else { + sequels[operation] = true; + } + } else { + if (sequel != operation) { + sequels = new Set(true); + sequels[sequel] = true; + sequels[operation] = true; + sequel = null; + } + } + } + + // Удалить последствие + alternativa3d function removeSequel(operation:Operation):void { + if (sequel == null) { + if (sequels != null) { + delete sequels[operation]; + var key:*; + var single:Boolean = false; + for (key in sequels) { + if (single) { + single = false; + break; + } + single = true; + } + if (single) { + sequel = key; + sequels = null; + } + } + } else { + if (sequel == operation) { + sequel = null; + } + } + } + + alternativa3d function collectSequels(collector:Array):void { + if (sequel == null) { + // Проверяем последствия + for (var key:* in sequels) { + var operation:Operation = key; + // Если операция ещё не в очереди + if (!operation.queued) { + // Добавляем её в очередь + collector.push(operation); + // Устанавливаем флаг очереди + operation.queued = true; + // Вызываем добавление в очередь её последствий + operation.collectSequels(collector); + } + } + } else { + if (!sequel.queued) { + collector.push(sequel); + sequel.queued = true; + sequel.collectSequels(collector); + } + } + } + + public function toString():String { + return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as new file mode 100644 index 0000000..9ad7335 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as @@ -0,0 +1,130 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + * Примитивный полигон (примитив), хранящийся в узле BSP-дерева. + */ + public class PolyPrimitive { + + /** + * @private + * Количество точек + */ + alternativa3d var num:uint; + /** + * @private + * Точки + */ + alternativa3d var points:Array = new Array(); + /** + * @private + * UV-координаты + */ + alternativa3d var uvs:Array = new Array(); + /** + * @private + * Грань + */ + alternativa3d var face:Face; + /** + * @private + * Родительский примитив + */ + alternativa3d var parent:PolyPrimitive; + /** + * @private + * Соседний примитив (при наличии родительского) + */ + alternativa3d var sibling:PolyPrimitive; + /** + * @private + * Фрагменты + */ + alternativa3d var backFragment:PolyPrimitive; + /** + * @private + */ + alternativa3d var frontFragment:PolyPrimitive; + /** + * @private + * Рассечения + */ + alternativa3d var splitTime1:Number; + /** + * @private + */ + alternativa3d var splitTime2:Number; + /** + * @private + * BSP-нода, в которой находится примитив + */ + alternativa3d var node:BSPNode; + /** + * @private + * Значения для расчёта качества сплиттера + */ + alternativa3d var splits:uint; + /** + * @private + */ + alternativa3d var disbalance:int; + /** + * @private + * Качество примитива как сплиттера (меньше - лучше) + */ + public var splitQuality:Number; + /** + * @private + * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве. + */ + public var mobility:int; + + // Хранилище неиспользуемых примитивов + static private var collector:Array = new Array(); + + /** + * @private + * Создать примитив + */ + static alternativa3d function createPolyPrimitive():PolyPrimitive { + // Достаём примитив из коллектора + var primitive:PolyPrimitive = collector.pop(); + // Если коллектор пуст, создаём новый примитив + if (primitive == null) { + primitive = new PolyPrimitive(); + } + //trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node); + return primitive; + } + + /** + * @private + * Кладёт примитив в коллектор для последующего реиспользования. + * Ссылка на грань и массивы точек зачищаются в этом методе. + * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода. + * + * Исключение: + * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода. + * + * @param primitive примитив на реиспользование + */ + static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void { + primitive.face = null; + for (var i:uint = 0; i < primitive.num; i++) { + primitive.points.pop(); + primitive.uvs.pop(); + } + collector.push(primitive); + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[Primitive " + face._mesh._name + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as new file mode 100644 index 0000000..757a2c2 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as @@ -0,0 +1,1256 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов + * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения + * BSP-дерева для содержимого сцены. + */ + public class Scene3D { + // Операции + /** + * @private + * Полное обновление BSP-дерева + */ + alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this); + /** + * @private + * Изменение примитивов + */ + alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this); + /** + * @private + * Расчёт BSP-дерева + */ + alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP); + /** + * @private + * Очистка списков изменений + */ + alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES); + + /** + * @private + * Корневой объект + */ + alternativa3d var _root:Object3D; + + /** + * @private + * Список операций на выполнение + */ + alternativa3d var operations:Array = new Array(); + //protected var operationSort:Array = ["priority"]; + //protected var operationSortOptions:Array = [Array.NUMERIC]; + /** + * @private + * Вспомогательная пустая операция, используется при удалении операций из списка + */ + alternativa3d var dummyOperation:Operation = new Operation("removed", this); + + /** + * @private + * Флаг анализа сплиттеров + */ + alternativa3d var _splitAnalysis:Boolean = true; + /** + * @private + * Cбалансированность дерева + */ + alternativa3d var _splitBalance:Number = 0; + /** + * @private + * Список изменённых примитивов + */ + alternativa3d var changedPrimitives:Set = new Set(); + + // Вспомогательный список для сборки дочерних примитивов + private var childPrimitives:Set = new Set(); + /** + * @private + * Список примитивов на добавление/удаление + */ + alternativa3d var addPrimitives:Array = new Array(); + +// alternativa3d var addSort:Array = ["mobility"]; +// alternativa3d var addSortOptions:Array = [Array.NUMERIC | Array.DESCENDING]; +// alternativa3d var addSplitQualitySort:Array = ["mobility", "splitQuality"]; +// alternativa3d var addSplitQualitySortOptions:Array = [Array.NUMERIC | Array.DESCENDING, Array.NUMERIC | Array.DESCENDING]; + /** + * @private + * Погрешность при определении точек на плоскости + */ + private var _planeOffsetThreshold:Number = 0.01; + /** + * @private + * BSP-дерево + */ + alternativa3d var bsp:BSPNode; + + /** + * @private + * Список нод на удаление + */ + alternativa3d var removeNodes:Set = new Set(); + /** + * @private + * Вспомогательная пустая нода, используется при удалении нод из дерева + */ + alternativa3d var dummyNode:BSPNode = new BSPNode(); + + /** + * Создание экземпляра сцены. + */ + public function Scene3D() { + // Обновление BSP-дерева требует его пересчёта + updateBSPOperation.addSequel(calculateBSPOperation); + // Изменение примитивов в случае пересчёта дерева + calculateBSPOperation.addSequel(changePrimitivesOperation); + // При изменении примитивов необходимо очистить списки изменений + changePrimitivesOperation.addSequel(clearPrimitivesOperation); + } + + /** + * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список + * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех + * областях вывода, к которым подключены находящиеся в сцене камеры. + */ + public function calculate():void { + if (operations[0] != undefined) { + // Формируем последствия + var operation:Operation; + var length:uint = operations.length; + var i:uint; + for (i = 0; i < length; i++) { + operation = operations[i]; + operation.collectSequels(operations); + } + // Сортируем операции + length = operations.length; + //operations.sortOn(operationSort, operationSortOptions); + sortOperations(0, length - 1); + // Запускаем операции + //trace("----------------------------------------"); + for (i = 0; i < length; i++) { + operation = operations[i]; + if (operation.method != null) { + //trace("EXECUTE:", operation); + operation.method(); + } else { + /*if (operation == dummyOperation) { + trace("REMOVED"); + } else { + trace(operation); + }*/ + } + } + // Очищаем список операций + for (i = 0; i < length; i++) { + operation = operations.pop(); + operation.queued = false; + } + } + } + + /** + * @private + * Сортировка операций, если массив operations пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortOperations(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:Operation; + var mid:uint = operations[(r + l) >>> 1].priority; + var right:Operation; + do { + while ((left = operations[i]).priority < mid) {i++}; + while (mid < (right = operations[j]).priority) {j--}; + if (i <= j) { + operations[i++] = right; + operations[j--] = left; + } + } while (i <= j) + if (l < j) { + sortOperations(l, j); + } + if (i < r) { + sortOperations(i, r); + } + } + + /** + * @private + * Добавление операции в список + * + * @param operation добавляемая операция + */ + alternativa3d function addOperation(operation:Operation):void { + if (!operation.queued) { + operations.push(operation); + operation.queued = true; + } + } + + /** + * @private + * удаление операции из списка + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperation(operation:Operation):void { + if (operation.queued) { + operations[operations.indexOf(operation)] = dummyOperation; + operation.queued = false; + } + } + + /** + * @private + * Расчёт изменений в BSP-дереве. + * Обработка удалённых и добавленных примитивов. + */ + protected function calculateBSP():void { + if (updateBSPOperation.queued) { + + // Удаление списка нод, помеченных на удаление + removeNodes.clear(); + + // Удаление BSP-дерева, перенос примитивов в список дочерних + childBSP(bsp); + bsp = null; + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + } else { + + var key:*; + var primitive:PolyPrimitive; + + // Удаляем ноды из дерева + if (!removeNodes.isEmpty()) { + var node:BSPNode; + while ((node = removeNodes.peek()) != null) { + + // Ищем верхнюю удаляемую ноду + var removeNode:BSPNode = node; + while ((node = node.parent) != null) { + if (removeNodes[node]) { + removeNode = node; + } + } + + // Удаляем ветку + var parent:BSPNode = removeNode.parent; + var replace:BSPNode = removeBSPNode(removeNode); + + // Если вернулась вспомогательная нода, игнорируем её + if (replace == dummyNode) { + replace = null; + } + + // Если есть родительская нода + if (parent != null) { + // Заменяем себя на указанную ноду + if (parent.front == removeNode) { + parent.front = replace; + } else { + parent.back = replace; + } + } else { + // Если нет родительской ноды, значит заменяем корень на указанную ноду + bsp = replace; + } + + // Устанавливаем связь с родителем для заменённой ноды + if (replace != null) { + replace.parent = parent; + } + } + + // Собираем дочерние примитивы в список на добавление + assembleChildPrimitives(); + } + } + + // Если есть примитивы на добавление + if (addPrimitives[0] != undefined) { + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + //addPrimitives.sortOn(addSplitQualitySort, addSplitQualitySortOptions); + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + ///addPrimitives.sortOn(addSort, addSortOptions); + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Если корневого нода ещё нет, создаём + if (bsp == null) { + primitive = addPrimitives.pop(); + bsp = BSPNode.createBSPNode(primitive); + changedPrimitives[primitive] = true; + } + + // Встраиваем примитивы в дерево + while ((primitive = addPrimitives.pop()) != null) { + addBSP(bsp, primitive); + } + } + } + + /** + * @private + * Сортировка граней, если массив addPrimitives пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:PolyPrimitive = addPrimitives[(r + l)>>>1]; + var midMobility:Number = mid.mobility; + var midSplitQuality:Number = mid.splitQuality; + var right:PolyPrimitive; + do { + while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++}; + while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitives(l, j); + } + if (i < r) { + sortPrimitives(i, r); + } + } + + /** + * @private + * Сортировка только по мобильности + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitivesByMobility(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:int = addPrimitives[(r + l)>>>1].mobility; + var right:PolyPrimitive; + do { + while ((left = addPrimitives[i]).mobility > mid) {i++}; + while (mid > (right = addPrimitives[j]).mobility) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitivesByMobility(l, j); + } + if (i < r) { + sortPrimitivesByMobility(i, r); + } + } + + + /** + * @private + * Анализ качества сплиттеров + */ + private function analyseSplitQuality():void { + // Перебираем примитивы на добавление + var i:uint; + var length:uint = addPrimitives.length; + var maxSplits:uint = 0; + var maxDisbalance:uint = 0; + var splitter:PolyPrimitive; + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splits = 0; + splitter.disbalance = 0; + var normal:Point3D = splitter.face.globalNormal; + var offset:Number = splitter.face.globalOffset; + // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление + for (var j:uint = 0; j < length; j++) { + if (i != j) { + var primitive:PolyPrimitive = addPrimitives[j]; + if (splitter.mobility <= primitive.mobility) { + // Проверяем наличие точек спереди и сзади сплиттера + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + for (var k:uint = 0; k < primitive.num; k++) { + var point:Point3D = primitive.points[k]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset; + if (pointOffset > _planeOffsetThreshold) { + if (!pointsFront) { + splitter.disbalance++; + pointsFront = true; + } + if (pointsBack) { + splitter.splits++; + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + if (!pointsBack) { + splitter.disbalance--; + pointsBack = true; + } + if (pointsFront) { + splitter.splits++; + break; + } + } + } + } + } + } + } + // Абсолютное значение дисбаланса + splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance; + // Ищем максимальное количество рассечений и значение дисбаланса + maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits; + maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance; + } + // Расчитываем качество сплиттеров + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance; + } + } + + /** + * @private + * Добавление примитива в BSP-дерево + * + * @param node текущий узел дерева, в который добавляется примитив + * @param primitive добавляемый примитив + */ + protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void { + var point:Point3D; + var normal:Point3D; + var key:*; + + // Сравниваем мобильности ноды и примитива + if (primitive.mobility < node.mobility) { + + // Формируем список содержимого ноды и всех примитивов ниже + if (node.primitive != null) { + childPrimitives[node.primitive] = true; + changedPrimitives[node.primitive] = true; + node.primitive.node = null; + } else { + var p:PolyPrimitive; + for (key in node.backPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + for (key in node.frontPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + } + childBSP(node.back); + childBSP(node.front); + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Сохраняем ноду + primitive.node = node; + + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + + // Сохраняем мобильность + node.mobility = primitive.mobility; + + // Чистим списки примитивов + node.backPrimitives = null; + node.frontPrimitives = null; + + // Удаляем дочерние ноды + node.back = null; + node.front = null; + + } else { + // Получаем нормаль из ноды + normal = node.normal; + + var points:Array = primitive.points; + var uvs:Array = primitive.uvs; + + // Собирательные флаги + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + + // Собираем расстояния точек до плоскости + for (var i:uint = 0; i < primitive.num; i++) { + point = points[i]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + if (pointOffset > _planeOffsetThreshold) { + pointsFront = true; + if (pointsBack) { + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + pointsBack = true; + if (pointsFront) { + break; + } + } + } + } + + if (!pointsFront && !pointsBack) { + // Сохраняем ноду + primitive.node = node; + + // Если был только базовый примитив, переносим его в список + if (node.primitive != null) { + node.frontPrimitives = new Set(true); + node.frontPrimitives[node.primitive] = true; + node.primitive = null; + } + + // Примитив находится в плоскости ноды + if (Point3D.dot(primitive.face.globalNormal, normal) > 0) { + node.frontPrimitives[primitive] = true; + } else { + if (node.backPrimitives == null) { + node.backPrimitives = new Set(true); + } + node.backPrimitives[primitive] = true; + } + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + } else { + if (!pointsBack) { + // Примитив спереди плоскости ноды + if (node.front == null) { + // Создаём переднюю ноду + node.front = BSPNode.createBSPNode(primitive); + node.front.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в переднюю ноду + addBSP(node.front, primitive); + } + } else { + if (!pointsFront) { + // Примитив сзади плоскости ноды + if (node.back == null) { + // Создаём заднюю ноду + node.back = BSPNode.createBSPNode(primitive); + node.back.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в заднюю ноду + addBSP(node.back, primitive); + } + } else { + // Рассечение + var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + + var firstSplit:Boolean = true; + + point = points[0]; + var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + var offset1:Number = offset0; + var offset2:Number; + for (i = 0; i < primitive.num; i++) { + var j:uint; + if (i < primitive.num - 1) { + j = i + 1; + point = points[j]; + offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + } else { + j = 0; + offset2 = offset0; + } + + if (offset1 > _planeOffsetThreshold) { + // Точка спереди плоскости ноды + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } else { + if (offset1 < -_planeOffsetThreshold) { + // Точка сзади плоскости ноды + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + } else { + // Рассечение по точке примитива + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } + } + + // Рассечение ребра + if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) { + // Находим точку рассечения + var t:Number = offset1/(offset1 - offset2); + point = Point3D.interpolate(points[i], points[j], t); + backFragment.points.push(point); + frontFragment.points.push(point); + // Находим UV в точке рассечения + var uv:Point; + if (primitive.face.uvMatrixBase != null) { + uv = Point.interpolate(uvs[j], uvs[i], t); + } else { + uv = null; + } + backFragment.uvs.push(uv); + frontFragment.uvs.push(uv); + // Отмечаем рассечённое ребро + if (firstSplit) { + primitive.splitTime1 = t; + firstSplit = false; + } else { + primitive.splitTime2 = t; + } + } + + offset1 = offset2; + } + backFragment.num = backFragment.points.length; + frontFragment.num = frontFragment.points.length; + + // Назначаем мобильность + backFragment.mobility = primitive.mobility; + frontFragment.mobility = primitive.mobility; + + // Устанавливаем связи рассечённых примитивов + backFragment.face = primitive.face; + frontFragment.face = primitive.face; + backFragment.parent = primitive; + frontFragment.parent = primitive; + backFragment.sibling = frontFragment; + frontFragment.sibling = backFragment; + primitive.backFragment = backFragment; + primitive.frontFragment = frontFragment; + + // Добавляем фрагменты в дочерние ноды + if (node.back == null) { + node.back = BSPNode.createBSPNode(backFragment); + node.back.parent = node; + changedPrimitives[backFragment] = true; + } else { + addBSP(node.back, backFragment); + } + if (node.front == null) { + node.front = BSPNode.createBSPNode(frontFragment); + node.front.parent = node; + changedPrimitives[frontFragment] = true; + } else { + addBSP(node.front, frontFragment); + } + } + } + } + } + } + + /** + * @private + * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления. + * + * @param node удаляемый узел + * @return корневой узел поддерева, оставшегося после операции удаления + */ + protected function removeBSPNode(node:BSPNode):BSPNode { + var replaceNode:BSPNode; + if (node != null) { + // Удаляем дочерние + node.back = removeBSPNode(node.back); + node.front = removeBSPNode(node.front); + + if (!removeNodes[node]) { + // Если нода не удаляется, возвращает себя + replaceNode = node; + + // Проверяем дочерние ноды + if (node.back != null) { + if (node.back != dummyNode) { + node.back.parent = node; + } else { + node.back = null; + } + } + if (node.front != null) { + if (node.front != dummyNode) { + node.front.parent = node; + } else { + node.front = null; + } + } + } else { + // Проверяем дочерние ветки + if (node.back == null) { + if (node.front != null) { + // Есть только передняя ветка + replaceNode = node.front; + node.front = null; + } + } else { + if (node.front == null) { + // Есть только задняя ветка + replaceNode = node.back; + node.back = null; + } else { + // Есть обе ветки - собираем дочерние примитивы + childBSP(node.back); + childBSP(node.front); + // Используем вспомогательную ноду + replaceNode = dummyNode; + // Удаляем связи с дочерними нодами + node.back = null; + node.front = null; + } + } + + // Удаляем ноду из списка на удаление + delete removeNodes[node]; + // Удаляем ноду + node.parent = null; + BSPNode.destroyBSPNode(node); + } + } + return replaceNode; + } + + /** + * @private + * Удаление примитива из узла дерева + * + * @param primitive удаляемый примитив + */ + alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void { + var node:BSPNode = primitive.node; + primitive.node = null; + + var single:Boolean = false; + var key:*; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Если нода единичная + if (node.primitive == primitive) { + removeNodes[node] = true; + node.primitive = null; + } else { + // Есть передние примитивы + if (node.frontPrimitives[primitive]) { + // Удаляем примитив спереди + delete node.frontPrimitives[primitive]; + + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + if (key == null) { + // Передняя пуста, значит сзади кто-то есть + + // Переворачиваем дочерние ноды + var t:BSPNode = node.back; + node.back = node.front; + node.front = t; + + // Переворачиваем плоскость ноды + node.normal.invert(); + node.offset = -node.offset; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если сзади один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Если сзади несколько примитивов, переносим их в передние + node.frontPrimitives = node.backPrimitives; + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + + } else { + // Если остался один примитив и сзади примитивов нет + if (single && node.backPrimitives == null) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } else { + // Удаляем примитив сзади + delete node.backPrimitives[primitive]; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + break; + } + + // Если сзади примитивов больше нет + if (key == null) { + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если спереди один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } + } + + /** + * @private + * Удаление и перевставка ветки + * + * @param node + */ + protected function childBSP(node:BSPNode):void { + if (node != null && node != dummyNode) { + var primitive:PolyPrimitive = node.primitive; + if (primitive != null) { + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + node.primitive = null; + primitive.node = null; + } else { + for (var key:* in node.backPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + for (key in node.frontPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + node.backPrimitives = null; + node.frontPrimitives = null; + } + childBSP(node.back); + childBSP(node.front); + // Удаляем ноду + node.parent = null; + node.back = null; + node.front = null; + BSPNode.destroyBSPNode(node); + } + } + + /** + * @private + * Сборка списка дочерних примитивов в коллектор + */ + protected function assembleChildPrimitives():void { + var primitive:PolyPrimitive; + while ((primitive = childPrimitives.take()) != null) { + assemblePrimitive(primitive); + } + } + + /** + * @private + * Сборка примитивов и разделение на добавленные и удалённые + * + * @param primitive + */ + private function assemblePrimitive(primitive:PolyPrimitive):void { + // Если есть соседний примитив и он может быть собран + if (primitive.sibling != null && canAssemble(primitive.sibling)) { + // Собираем их в родительский + assemblePrimitive(primitive.parent); + // Зачищаем связи между примитивами + primitive.sibling.sibling = null; + primitive.sibling.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive.sibling); + primitive.sibling = null; + primitive.parent.backFragment = null; + primitive.parent.frontFragment = null; + primitive.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } else { + // Если собраться не получилось или родительский + addPrimitives.push(primitive); + } + } + + /** + * @private + * Проверка, может ли примитив в списке дочерних быть собран + * + * @param primitive + * @return + */ + private function canAssemble(primitive:PolyPrimitive):Boolean { + if (childPrimitives[primitive]) { + delete childPrimitives[primitive]; + return true; + } else { + var backFragment:PolyPrimitive = primitive.backFragment; + var frontFragment:PolyPrimitive = primitive.frontFragment; + if (backFragment != null) { + var assembleBack:Boolean = canAssemble(backFragment); + var assembleFront:Boolean = canAssemble(frontFragment); + if (assembleBack && assembleFront) { + backFragment.parent = null; + frontFragment.parent = null; + backFragment.sibling = null; + frontFragment.sibling = null; + primitive.backFragment = null; + primitive.frontFragment = null; + PolyPrimitive.destroyPolyPrimitive(backFragment); + PolyPrimitive.destroyPolyPrimitive(frontFragment); + return true; + } else { + if (assembleBack) { + addPrimitives.push(backFragment); + } + if (assembleFront) { + addPrimitives.push(frontFragment); + } + } + } + } + return false; + } + + /** + * @private + * Очистка списков + */ + private function clearPrimitives():void { + changedPrimitives.clear(); + } + + /** + * Корневой объект сцены. + */ + public function get root():Object3D { + return _root; + } + + /** + * @private + */ + public function set root(value:Object3D):void { + // Если ещё не является корневым объектом + if (_root != value) { + // Если устанавливаем не пустой объект + if (value != null) { + // Если объект был в другом объекте + if (value._parent != null) { + // Удалить его оттуда + value._parent._children.remove(value); + } else { + // Если объект был корневым в сцене + if (value._scene != null && value._scene._root == value) { + value._scene.root = null; + } + } + // Удаляем ссылку на родителя + value.setParent(null); + // Указываем сцену + value.setScene(this); + // Устанавливаем уровни + value.setLevel(0); + } + + // Если был корневой объект + if (_root != null) { + // Удаляем ссылку на родителя + _root.setParent(null); + // Удаляем ссылку на камеру + _root.setScene(null); + } + + // Сохраняем корневой объект + _root = value; + } + } + + /** + * Флаг активности анализа сплиттеров. + * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей + * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми. + * + *

Изменением свойства splitBalance можно влиять на конечный вид BSP-дерева. + * + * @see #splitBalance + * @default true + */ + public function get splitAnalysis():Boolean { + return _splitAnalysis; + } + + /** + * @private + */ + public function set splitAnalysis(value:Boolean):void { + if (_splitAnalysis != value) { + _splitAnalysis = value; + addOperation(updateBSPOperation); + } + } + + /** + * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров. + * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева). + * + * @see #splitAnalysis + * @default 0 + */ + public function get splitBalance():Number { + return _splitBalance; + } + + /** + * @private + */ + public function set splitBalance(value:Number):void { + value = (value < 0) ? 0 : ((value > 1) ? 1 : value); + if (_splitBalance != value) { + _splitBalance = value; + if (_splitAnalysis) { + addOperation(updateBSPOperation); + } + } + } + + /** + * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold. + * + * @default 0.01 + */ + public function get planeOffsetThreshold():Number { + return _planeOffsetThreshold; + } + + /** + * @private + */ + public function set planeOffsetThreshold(value:Number):void { + value = (value < 0) ? 0 : value; + if (_planeOffsetThreshold != value) { + _planeOffsetThreshold = value; + addOperation(updateBSPOperation); + } + } + + /** + * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей + * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого + * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу. + * + * @param container контейнер для отрисовки дерева + */ + public function drawBSP(container:Sprite):void { + + container.graphics.clear(); + while (container.numChildren > 0) { + container.removeChildAt(0); + } + if (bsp != null) { + drawBSPNode(bsp, container, 0, 0, 1); + } + } + + /** + * @private + * Отрисовка узла BSP-дерева при визуализации + * + * @param node + * @param container + * @param x + * @param y + * @param size + */ + private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void { + var s:Shape = new Shape(); + container.addChild(s); + s.x = x; + s.y = y; + var color:uint = 0xFF0000; + var primitive:PolyPrimitive; + if (node.primitive != null) { + primitive = node.primitive; + } else { + if (node.frontPrimitives != null) { + primitive = node.frontPrimitives.peek(); + } + } + if (primitive != null) { + if (primitive.face._surface != null && primitive.face._surface._material != null) { + if (primitive.face._surface._material is FillMaterial) { + color = FillMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is WireMaterial) { + color = WireMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is TextureMaterial) { + color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0); + } + } + } + + if (node == dummyNode) { + color = 0xFF00FF; + } + + s.graphics.beginFill(color); + s.graphics.drawCircle(0, 0, 3); + s.graphics.endFill(); + + var xOffset:Number = 100; + var yOffset:Number = 20; + if (node.back != null) { + container.graphics.lineStyle(0, 0x660000); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x - xOffset*size, y + yOffset); + drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8); + } + if (node.front != null) { + container.graphics.lineStyle(0, 0x006600); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x + xOffset*size, y + yOffset); + drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as new file mode 100644 index 0000000..6e69daa --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as @@ -0,0 +1,350 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов, + * визуализирующих грани объекта. + */ + public class Surface { + // Операции + /** + * @private + * Изменение набора граней + */ + alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this); + /** + * @private + * Изменение материала + */ + alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Материал + */ + alternativa3d var _material:SurfaceMaterial; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + + /** + * Создание экземпляра поверхности. + */ + public function Surface() {} + + /** + * Добавление грани в поверхность. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань + * + * @see Face + */ + public function addFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (_faces.has(f)) { + // Если грань уже в поверхности + throw new FaceExistsError(f, this); + } + + // Проверяем грань на нахождение в другой поверхности + if (f._surface != null) { + // Удаляем её из той поверхности + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Добавляем грань в поверхность + _faces.add(f); + f.addToSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Удаление грани из поверхности. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function removeFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (!_faces.has(f)) { + // Если грань не в поверхности + throw new FaceNotFoundError(f, this); + } + + // Удаляем грань из поверхности + _faces.remove(f); + f.removeFromSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности. + */ + public function get material():SurfaceMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SurfaceMaterial):void { + if (_material != value) { + // Если был материал + if (_material != null) { + // Удалить материал из поверхности + _material.removeFromSurface(this); + // Удалить материал из меша + if (_mesh != null) { + _material.removeFromMesh(_mesh); + // Удалить материал из сцены + if (_mesh._scene != null) { + _material.removeFromScene(_mesh._scene); + } + } + } + // Если новый материал + if (value != null) { + // Если материал был в другой поверхности + if (value._surface != null) { + // Удалить его оттуда + value._surface.material = null; + } + // Добавить материал в поверхность + value.addToSurface(this); + // Добавить материал в меш + if (_mesh != null) { + value.addToMesh(_mesh); + // Добавить материал в сцену + if (_mesh._scene != null) { + value.addToScene(_mesh._scene); + } + } + } + // Сохраняем материал + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Набор граней поверхности. + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Полигональный объект, которому принадлежит поверхность. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту, + * значение идентификатора равно null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getSurfaceId(this) : null; + } + + /** + * @private + * Добавление в сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeFacesOperation); + scene.removeOperation(changeMaterialOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + } + + /** + * @private + * Добавление к мешу + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + + // Добавляем в меш материал + if (_material != null) { + _material.addToMesh(mesh); + } + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + + // Удаляем из меша материал + if (_material != null) { + _material.removeFromMesh(mesh); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Удаление граней + */ + alternativa3d function removeFaces():void { + for (var key:* in _faces) { + var face:Face = key; + _faces.remove(face); + face.removeFromSurface(this); + } + } + + /** + * @private + * Изменение материала + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_mesh != null) { + _mesh.addOperationToScene(changeMaterialOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var length:uint = _faces.length; + var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : ""); + var i:uint = 0; + for (var key:* in _faces) { + var face:Face = key; + res += face.id + ((i < length - 1) ? ", " : ""); + i++; + } + res += "]"; + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..cceb91e --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,250 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на + * полигональный объект и грани этого объекта, которым она принадлежит. + */ + final public class Vertex { + // Операции + /** + * @private + * Изменение локальных координат + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт глобальных координат + */ + alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Координаты точки + */ + alternativa3d var _coords:Point3D; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра вершины. + * + * @param x координата вершины по оси X + * @param y координата вершины по оси Y + * @param z координата вершины по оси Z + */ + public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) { + _coords = new Point3D(x, y, z); + + // Изменение координат инициирует пересчёт глобальных координат + changeCoordsOperation.addSequel(calculateCoordsOperation); + } + + /** + * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины + */ + private function calculateCoords():void { + globalCoords.copy(_coords); + globalCoords.transform(_mesh.transformation); + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * координата вершины по оси X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * координата вершины по оси Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * координата вершины по оси Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * Координаты вершины. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * Полигональный объект, которому принадлежит вершина. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса + * altertnativa.engine3d.core.Face. + * + * @see Face + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getVertexId(this) : null; + } + + /** + * @private + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену расчитать глобальные координаты + scene.addOperation(calculateCoordsOperation); + } + + /** + * @private + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateCoordsOperation); + scene.removeOperation(changeCoordsOperation); + } + + /** + * @private + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation); + // Удалить зависимые грани + for (var key:* in _faces) { + var face:Face = key; + mesh.removeFace(face); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * @param face + */ + alternativa3d function addToFace(face:Face):void { + // Подписка грани на операции + changeCoordsOperation.addSequel(face.calculateUVOperation); + changeCoordsOperation.addSequel(face.calculateNormalOperation); + // Добавить грань в список + _faces.add(face); + } + + /** + * @private + * @param face + */ + alternativa3d function removeFromFace(face:Face):void { + // Отписка грани от операций + changeCoordsOperation.removeSequel(face.calculateUVOperation); + changeCoordsOperation.removeSequel(face.calculateNormalOperation); + // Удалить грань из списка + _faces.remove(face); + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as new file mode 100644 index 0000000..cdcd378 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as @@ -0,0 +1,66 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.materials.SurfaceMaterial; + + import flash.display.Graphics; + import flash.display.Sprite; + + use namespace alternativa3d; + + /** + * @private + * Контейнер, исползуемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере. + */ + public class Skin extends Sprite { + + /** + * @private + * Графика скина (для быстрого доступа) + */ + alternativa3d var gfx:Graphics = graphics; + + /** + * @private + * Ссылка на следующий скин + */ + alternativa3d var nextSkin:Skin; + + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + /** + * @private + * Материал, связанный со скином. + */ + alternativa3d var material:SurfaceMaterial; + + // Хранилище неиспользуемых скинов + static private var collector:Array = new Array(); + + /** + * @private + * Создание скина. + */ + static alternativa3d function createSkin():Skin { + // Достаём скин из коллектора + var skin:Skin = collector.pop(); + // Если коллектор пуст, создаём новый скин + if (skin == null) { + skin = new Skin(); + } + return skin; + } + + /** + * @private + * Удаление скина, все ссылки должны быть почищены. + */ + static alternativa3d function destroySkin(skin:Skin):void { + collector.push(skin); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/display/View.as b/Alternativa3D5/5.4/alternativa/engine3d/display/View.as new file mode 100644 index 0000000..5ac3e5d --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/display/View.as @@ -0,0 +1,186 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + + import flash.display.Sprite; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Область для вывода изображения с камеры. + */ + public class View extends Sprite { + + /** + * @private + * Область отрисовки спрайтов + */ + alternativa3d var canvas:Sprite; + + private var _camera:Camera3D; + + /** + * @private + * Ширина области вывода + */ + alternativa3d var _width:Number; + /** + * @private + * Высота области вывода + */ + alternativa3d var _height:Number; + + /** + * Создание экземпляра области вывода. + * + * @param camera камера, изображение с которой должно выводиться + * @param width ширина области вывода + * @param height высота области вывода + */ + public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) { + canvas = new Sprite(); + canvas.mouseEnabled = false; + canvas.mouseChildren = false; + canvas.tabEnabled = false; + canvas.tabChildren = false; + addChild(canvas); + + this.camera = camera; + this.width = width; + this.height = height; + } + + /** + * Камера с которой ведётся отображение. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + // Если была камера + if (_camera != null) { + // Удалить камеру + _camera.removeFromView(this); + } + // Если новая камера + if (value != null) { + // Если камера была в другом вьюпорте + if (value._view != null) { + // Удалить её оттуда + value._view.camera = null; + } + // Добавить камеру + value.addToView(this); + } else { + // Зачистка скинов + if (canvas.numChildren > 0) { + var skin:Skin = Skin(canvas.getChildAt(0)); + while (skin != null) { + // Сохраняем следующий + var next:Skin = skin.nextSkin; + // Удаляем из канваса + canvas.removeChild(skin); + // Очистка скина + if (skin.material != null) { + skin.material.clear(skin); + } + // Зачищаем ссылки + skin.nextSkin = null; + skin.primitive = null; + skin.material = null; + // Удаляем + Skin.destroySkin(skin); + // Следующий устанавливаем текущим + skin = next; + } + } + } + // Сохраняем камеру + _camera = value; + } + } + + /** + * Ширина области вывода в пикселях. + */ + override public function get width():Number { + return _width; + } + + /** + * @private + */ + override public function set width(value:Number):void { + if (_width != value) { + _width = value; + canvas.x = _width*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Высота области вывода в пикселях. + */ + override public function get height():Number { + return _height; + } + + /** + * @private + */ + override public function set height(value:Number):void { + if (_height != value) { + _height = value; + canvas.y = _height*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Метод возвращает грань, находящуюся под указанной точкой в области вывода. + * + * @param viewPoint координаты точки относительно области вывода + * + * @return ближайшая к камере грань под заданной точкой области вывода + */ + public function getFaceUnderPoint(viewPoint:Point):Face { + var p:Point = localToGlobal(viewPoint); + var objects:Array = canvas.getObjectsUnderPoint(p); + var skin:Skin = objects.pop() as Skin; + if (skin != null) { + return skin.primitive.face; + } + return null; + } + + /** + * Метод возвращает грани, находящиеся под указанной точкой в области вывода. + * + * @param viewPoint координаты точки относительно области вывода + * + * @return массив граней, расположенных под заданной точкой области вывода. Первым элементом массива является самая дальняя грань. + */ + public function getFacesUnderPoint(viewPoint:Point):Array { + var p:Point = localToGlobal(viewPoint); + var objects:Array = canvas.getObjectsUnderPoint(p); + var res:Array = new Array(); + var length:uint = objects.length; + for (var i:uint = 0; i < length; i++) { + var skin:Skin = objects[i]; + res.push(skin.primitive.face); + } + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as new file mode 100644 index 0000000..bcf6397 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.errors { + + import alternativa.utils.TextUtils; + + /** + * Базовый класс для ошибок 3d-engine. + */ + public class Engine3DError extends Error { + + /** + * Источник ошибки - объект в котором произошла ошибка. + */ + public var source:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param source источник ошибки + */ + public function Engine3DError(message:String = "", source:Object = null) { + super(message); + this.source = source; + this.name = "Engine3DError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as new file mode 100644 index 0000000..ecc7f59 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as @@ -0,0 +1,35 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Surface; + + /** + * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте. + */ + public class FaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани, которая уже содержится в объекте + * @param source источник ошибки + */ + public function FaceExistsError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else if (source is Surface) { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 already exists."; + } else { + message += "%1. Face with ID '%2' already exists."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as new file mode 100644 index 0000000..3341f63 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая недостаточное количество вершин для создания грани. + * Для создания грани должно быть указано не менее трех вершин. + */ + public class FaceNeedMoreVerticesError extends Engine3DError { + + /** + * Количество переданных для создания грани вершин + */ + public var count:uint; + + /** + * Создание экземпляра класса. + * + * @param mesh объект, в котором произошла ошибка + * @param count количество вершин, переданное для создания грани + */ + public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) { + super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh); + this.count = count; + this.name = "FaceNeedMoreVerticesError"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as new file mode 100644 index 0000000..912d6a4 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, если грань не найдена в объекте. + */ + public class FaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани + * @param source объект, в котором произошла ошибка + */ + public function FaceNotFoundError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 not found."; + } else { + message += "%1. Face with ID '%2' not found."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as new file mode 100644 index 0000000..fbf4666 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + + + /** + * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован. + */ + public class InvalidIDError extends Engine3DError { + /** + * Зарезервированный идентификатор + */ + public var id:Object; + + /** + * Создание экземпляра класса. + * + * @param id идентификатор + * @param source объект, в котором произошла ошибка + */ + public function InvalidIDError(id:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh %2. "; + } else if (source is Surface) { + message = "Surface %2. "; + } + super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source); + this.id = id; + this.name = "InvalidIDError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as new file mode 100644 index 0000000..8d61bd0 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, связанная с нарушением иерархии объектов сцены. + */ + public class Object3DHierarchyError extends Engine3DError + { + + /** + * Объект сцены, нарушающий иерархию + */ + public var object:Object3D; + + /** + * Создание экземпляра класса. + * + * @param object объект, нарушающий иерархию + * @param source источник ошибки + */ + public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source); + this.object = object; + this.name = "Object3DHierarchyError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as new file mode 100644 index 0000000..d1ddd1e --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены. + */ + public class Object3DNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param object ненайденный объект сцены + * @param source объект сцены, в котором произошла ошибка + */ + public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source); + this.name = "Object3DNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as new file mode 100644 index 0000000..7743a53 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Ошибка, обозначающая, что объект уже присутствует в контейнере. + */ + public class ObjectExistsError extends Engine3DError { + + /** + * Экземпляр или идентификатор объекта, который уже присутствует в контейнере + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object объект, который уже присутствует в контейнере + * @param source объект, вызвавший ошибку + */ + public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectExistsError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as new file mode 100644 index 0000000..87b3d14 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Необходимый объект не был найден в контейнере. + */ + public class ObjectNotFoundError extends Engine3DError { + + /** + * Объект, который отсутствует в контейнере. + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object отсутствующий объект + * @param source объект, вызвавший ошибку + */ + public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as new file mode 100644 index 0000000..e13bee7 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность уже присутствует в контейнере. + */ + public class SurfaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая уже присутствует в контейнере + * @param mesh объект, вызвавший ошибку + */ + public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh); + this.name = "SurfaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as new file mode 100644 index 0000000..05457bf --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as @@ -0,0 +1,31 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность не найдена в контейнере. + */ + public class SurfaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая отсутствует в объекте + * @param mesh объект, который вызвал ошибку + */ + public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) { + if (mesh == null) { + + } + if (surface is Surface) { + message = "Mesh %1. Surface %2 not found."; + } else { + message = "Mesh %1. Surface with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, surface), surface, mesh); + this.name = "SurfaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as new file mode 100644 index 0000000..9f0fff5 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина уже содержится в объекте. + */ + public class VertexExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая уже есть в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh); + this.name = "VertexExistsError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as new file mode 100644 index 0000000..edf80aa --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина не найдена в объекте. + */ + public class VertexNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая не найдена в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) { + if (vertex is Vertex) { + message = "Mesh %1. Vertex %2 not found."; + } else { + message = "Mesh %1. Vertex with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh); + this.name = "VertexNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..098f91b --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,1055 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Texture; + import alternativa.utils.ColorUtils; + import alternativa.utils.MathUtils; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.errors.IOError; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + import alternativa.types.Map; + import flash.events.SecurityErrorEvent; + + use namespace alternativa3d; + + /** + * Загрузчик моделей в формате 3DS. + * + *

Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если + * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели. + * + *

Из файла модели загружаются: + *

+ * + * Порядок загрузки каждого объекта из 3DS-файла: + * + * + * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы. + */ + public class Loader3DS extends EventDispatcher { + + /** + * Коэффициент пересчёта в дюймы. + */ + public static const INCHES:Number = 1; + + /** + * Коэффициент пересчёта в футы. + */ + public static const FEET:Number = 0.0833333334; + /** + * Коэффициент пересчёта в мили. + */ + public static const MILES:Number = 0.0000157828; + /** + * Коэффициент пересчёта в миллиметры. + */ + public static const MILLIMETERS:Number = 25.4000000259; + /** + * Коэффициент пересчёта в сантиметры. + */ + public static const CENTIMETERS:Number = 2.5400000025; + /** + * Коэффициент пересчёта в метры. + */ + public static const METERS:Number = 0.0254; + /** + * Коэффициент пересчёта в километры. + */ + public static const KILOMETERS:Number = 0.0000254; + + private static var stubBitmapData:BitmapData; + + private var _content:Object3D; + private var version:uint; + private var objectDatas:Map; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var urlLoader:URLLoader; + private var data:ByteArray; + + private var counter:uint; + private var sequence:Array; + private var loader:Loader; + private var context:LoaderContext; + private var path:String; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = INCHES; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + urlLoader.close(); + } catch (e:Error) { + } + try { + loader.close(); + } catch (e:Error) { + } + } + + /** + * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL 3DS-файла + * @param context LoaderContext для загрузки файлов текстур + */ + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + _content = null; + version = 0; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + sequence = null; + + if (urlLoader == null) { + urlLoader = new URLLoader(); + urlLoader.dataFormat = URLLoaderDataFormat.BINARY; + urlLoader.addEventListener(Event.COMPLETE, on3DSLoad); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + urlLoader.load(new URLRequest(url)); + } + + private function on3DSLoad(e:Event):void { + data = urlLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + private function on3DSError(e:Event):void { + dispatchEvent(e); + } + + private function loadBitmaps():void { + if (bitmaps != null) { + counter = 0; + sequence = new Array(); + for (var filename:String in bitmaps) { + sequence.push(filename); + } + if (loader == null) { + loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNextBitmap); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNextBitmap); + } + loader.load(new URLRequest(path + sequence[counter]), context); + } else { + buildContent(); + } + } + + private function loadNextBitmap(e:Event = null):void { + if (!(e is IOErrorEvent)) { + bitmaps[sequence[counter]] = Bitmap(loader.content).bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[sequence[counter]] = stubBitmapData; + } + counter++; + if (counter < sequence.length) { + loader.load(new URLRequest(path + sequence[counter]), context); + } else { + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Object3D(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = MathUtils.toRadian(mapData.rotation); + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Array().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Array().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + } else { + var object:Object3D = new Object3D(objectName); + animationData.object = object; + buildObject(animationData); + } + } + + buildHierarchy(_content, 0, length - 1); + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + buildMesh(mesh, objectData, null); + _content.addChild(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Object3D = animationData.object; + if (animationData.position != null) { + object.x = animationData.position.x * units; + object.y = animationData.position.y * units; + object.z = animationData.position.z * units; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + object.mobility = mobility; + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var key:*; + var vertex:Vertex; + var face:Face; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Point3D = objectData.vertices[i]; + objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i); + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.d -= animationData.pivot.x; + objectData.matrix.h -= animationData.pivot.y; + objectData.matrix.l -= animationData.pivot.z; + } + + // Трансформируем вершины + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.transform(objectData.matrix); + } + } + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.multiply(units); + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i); + if (objectData.uvs != null) { + face.aUV = objectData.uvs[faceData.a]; + face.bUV = objectData.uvs[faceData.b]; + face.cUV = objectData.uvs[faceData.c]; + } + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + var surfaceData:SurfaceData = objectData.surfaces[surfaceId]; + var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId); + if (materialData.diffuseMap != null) { + var bmp:BitmapData = bitmaps[materialData.diffuseMap.filename]; + surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + length = surfaceData.faces.length; + for (i = 0; i < length; i++) { + face = mesh.getFaceById(surfaceData.faces[i]); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var m:Matrix = materialData.matrix; + var x:Number = face.aUV.x; + var y:Number = face.aUV.y; + face._aUV.x = m.a*x + m.b*y + m.tx; + face._aUV.y = m.c*x + m.d*y + m.ty; + x = face._bUV.x; + y = face._bUV.y; + face._bUV.x = m.a*x + m.b*y + m.tx; + face._bUV.y = m.c*x + m.d*y + m.ty; + x = face._cUV.x; + y = face._cUV.y; + face._cUV.x = m.a*x + m.b*y + m.tx; + face._cUV.y = m.c*x + m.d*y + m.ty; + } + } + } else { + surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100); + } + } + } else { + // Поверхность по умолчанию + var defaultSurface:Surface = mesh.createSurface(); + // Добавляем грани + for (var faceId:String in mesh._faces) { + defaultSurface.addFace(mesh._faces[faceId]); + } + defaultSurface.material = new WireMaterial(0, 0x7F7F7F); + } + } + + private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void { + if (begin <= end) { + var animation:AnimationData = animationDatas[begin]; + var object:Object3D = animation.object; + parent.addChild(object); + + var parentIndex:uint = animation.parentIndex; + for (var i:uint = begin + 1; i <= end; i++) { + animation = animationDatas[i]; + if (parentIndex == animation.parentIndex) { + buildHierarchy(object, begin + 1, i - 1); + buildHierarchy(parent, i, end); + return; + } + } + buildHierarchy(object, begin + 1, end); + } + } + + + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte()); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + if (bitmaps == null) { + bitmaps = new Array(); + } + bitmaps[map.filename] = null; + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Map(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Array(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Array(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(); + object.matrix.a = data.readFloat(); + object.matrix.e = data.readFloat(); + object.matrix.i = data.readFloat(); + object.matrix.b = data.readFloat(); + object.matrix.f = data.readFloat(); + object.matrix.j = data.readFloat(); + object.matrix.c = data.readFloat(); + object.matrix.g = data.readFloat(); + object.matrix.k = data.readFloat(); + object.matrix.d = data.readFloat(); + object.matrix.h = data.readFloat(); + object.matrix.l = data.readFloat(); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Map(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Object3D { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D { + var res:Point3D = new Point3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.types.Matrix3D; +import alternativa.types.Point3D; + +import flash.geom.Matrix; +import alternativa.types.Map; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Array; + public var uvs:Array; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Map; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:uint; + public var pivot:Point3D; + public var position:Point3D; + public var rotation:Point3D; + public var scale:Point3D; +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as new file mode 100644 index 0000000..2a7d6d5 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as @@ -0,0 +1,285 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.types.Map; + import alternativa.utils.ColorUtils; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * @private + * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ). + *

+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала. + */ + internal class LoaderMTL extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + private static const CMD_NEW_MATERIAL:String = "newmtl"; + private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd"; + private static const CMD_DISSOLVE:String = "d"; + private static const CMD_MAP_DIFFUSE:String = "map_Kd"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + // Загрузчик файла MTL + private var fileLoader:URLLoader; + // Загрузчик файлов текстур + private var bitmapLoader:Loader; + // Контекст загрузки для bitmapLoader + private var loaderContext:LoaderContext; + // Базовый URL файла MTL + private var baseUrl:String; + + // Библиотека загруженных материалов + private var _library:Map; + // Список материалов, имеющих диффузные текстуры + private var diffuseMaps:Map; + // Имя текущего материала + private var materialName:String; + // параметры текущего материала + private var currentMaterialInfo:MaterialInfo = new MaterialInfo(); + + alternativa3d static var stubBitmapData:BitmapData; + + /** + * Создаёт новый экземпляр класса. + */ + public function LoaderMTL() { + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + fileLoader.close(); + } catch (e:Error) { + } + } + + /** + * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса + * alternativa.engine3d.loaders.MaterialInfo. + * @see alternativa.engine3d.loaders.MaterialInfo + */ + public function get library():Map { + return _library; + } + + /** + * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и + * формирование библиотеки материалов. После окончания работы метода посылается сообщение + * Event.COMPLETE и становится доступна библиотека материалов через свойство library. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку. + *

+ * @param url URL MTL-файла + * @param loaderContext LoaderContext для загрузки файлов текстур + * + * @see #library + */ + public function load(url:String, loaderContext:LoaderContext = null):void { + this.loaderContext = loaderContext; + baseUrl = url.substring(0, url.lastIndexOf("/") + 1); + + if (fileLoader == null) { + fileLoader = new URLLoader(); + fileLoader.addEventListener(Event.COMPLETE, parseMTLFile); + fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError); + fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + + bitmapLoader = new Loader(); + bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete); + bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete); + } + + try { + fileLoader.close(); + bitmapLoader.close(); + } catch (e:Error) { + // Пропуск ошибки при попытке закрытия неактивных загрузчиков + } + + fileLoader.load(new URLRequest(url)); + } + + /** + * Разбор содержимого загруженного файла материалов. + */ + private function parseMTLFile(e:Event = null):void { + var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE); + _library = new Map(); + diffuseMaps = new Map(); + for each (var line:String in lines) { + parseLine(line); + } + defineMaterial(); + + if (diffuseMaps.isEmpty()) { + // Текстур нет, загрузка окончена + dispatchEvent(new Event(Event.COMPLETE)); + } else { + // Загрузка файлов текстур + loadNextBitmap(); + } + } + + /** + * Разбор строки файла. + * + * @param line строка файла + */ + private function parseLine(line:String):void { + line = line.replace(REGEXP_TRIM,"$1") + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + case CMD_NEW_MATERIAL: + defineMaterial(parts); + break; + case CMD_DIFFUSE_REFLECTIVITY: + readDiffuseReflectivity(parts); + break; + case CMD_DISSOLVE: + readAlpha(parts); + break; + case CMD_MAP_DIFFUSE: + parseDiffuseMapLine(parts); + break; + } + } + + /** + * Определение нового материала. + */ + private function defineMaterial(parts:Array = null):void { + if (materialName != null) { + _library[materialName] = currentMaterialInfo; + } + if (parts != null) { + materialName = parts[1]; + currentMaterialInfo = new MaterialInfo(); + } + } + + /** + * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей + * версии движка данные коэффициенты преобразуются в цвет материала. + */ + private function readDiffuseReflectivity(parts:Array):void { + var r:Number = Number(parts[1]); + // Проверка, заданы ли коэффициенты в виде r g b + if (!isNaN(r)) { + var g:Number = Number(parts[2]); + var b:Number = Number(parts[3]); + currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b); + } + } + + /** + * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом + * (не поддерживается параметр -halo). + */ + private function readAlpha(parts:Array):void { + var alpha:Number = Number(parts[1]); + if (!isNaN(alpha)) { + currentMaterialInfo.alpha = alpha; + } + } + + /** + * Разбор строки, задающей текстурную карту для диффузного отражения. + */ + private function parseDiffuseMapLine(parts:Array):void { + var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts); + diffuseMaps[materialName] = info; + } + + /** + * Загрузка файла следующей текстуры. + */ + private function loadNextBitmap():void { + // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры + for (materialName in diffuseMaps) { + break; + } + bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext); + } + + /** + * + */ + private function createStubBitmap():void { + if (stubBitmapData == null) { + var size:uint = 10; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + } + + /** + * Обработка результата загрузки файла текстуры. + */ + private function onBitmapLoadComplete(e:Event):void { + var bmd:BitmapData; + + if (e is ErrorEvent) { + if (stubBitmapData == null) { + createStubBitmap(); + } + bmd = stubBitmapData; + } else { + bmd = Bitmap(bitmapLoader.content).bitmapData; + } + + var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName]; + delete diffuseMaps[materialName]; + var info:MaterialInfo = _library[materialName]; + + info.bitmapData = bmd; + info.repeat = mtlInfo.repeat; + info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV); + info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV); + info.textureFileName = mtlInfo.fileName; + + if (diffuseMaps.isEmpty()) { + dispatchEvent(new Event(Event.COMPLETE)); + } else { + loadNextBitmap(); + } + } + + /** + * + * @param e + */ + private function onError(e:IOErrorEvent):void { + dispatchEvent(e); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as new file mode 100644 index 0000000..5c05896 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as @@ -0,0 +1,509 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.types.Texture; + + import flash.display.BlendMode; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные + * модели помещаются в один контейнер Object3D. + *

+ * Поддерживаюся следующие команды формата OBJ: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КомандаОписаниеДействие
o object_nameОбъявление нового объекта с именем object_nameЕсли для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем, + * иначе у текущего объекта просто меняется имя на указанное.
v x y zОбъявление вершины с координатами x y zВершина помещается в общий список вершин сцены для дальнейшего использования
vt u [v]Объявление текстурной вершины с координатами u vВершина помещается в общий список текстурных вершин сцены для дальнейшего использования
f v0[/vt0] v1[/vt1] ... vN[/vtN]Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность + * текущего объекта, соответствующую текущему материалу.
usemtl material_nameУстановка текущего материала с именем material_nameС момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность, + * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
mtllib file1 file2 ...Объявление файлов, содержащих определения материаловВыполняется загрузка файлов и формирование библиотеки материалов
+ * + *

+ * Пример использования: + *

+	 * var loader:LoaderOBJ = new LoaderOBJ();
+	 * loader.addEventListener(Event.COMPLETE, onLoadingComplete);
+	 * loader.load("foo.obj");
+	 * 
+	 * function onLoadingComplete(e:Event):void {
+	 *   scene.root.addChild(e.target.content);
+	 * }
+	 * 
+ */ + public class LoaderOBJ extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + + private static const CMD_OBJECT_NAME:String = "o"; + private static const CMD_GROUP_NAME:String = "g"; + private static const CMD_VERTEX:String = "v"; + private static const CMD_TEXTURE_VERTEX:String = "vt"; + private static const CMD_FACE:String = "f"; + private static const CMD_MATERIAL_LIB:String = "mtllib"; + private static const CMD_USE_MATERIAL:String = "usemtl"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private var basePath:String; + private var objLoader:URLLoader; + private var mtlLoader:LoaderMTL; + private var loaderContext:LoaderContext; + private var loadMaterials:Boolean; + // Объект, содержащий все определённые в obj файле объекты + private var _content:Object3D; + // Текущий конструируемый объект + private var currentObject:Mesh; + // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта + private var vIndexStart:int = 0; + // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта + private var vtIndexStart:int = 0; + // Глобальный массив вершин, определённых во входном файле + private var globalVertices:Array; + // Глобальный массив текстурных вершин, определённых во входном файле + private var globalTextureVertices:Array; + // Имя текущего активного материала. Если значение равно null, то активного материала нет. + private var currentMaterialName:String; + // Массив граней текущего объекта, которым назначен текущий материал + private var materialFaces:Array; + // Массив имён файлов, содержащих определения материалов + private var materialFileNames:Array; + private var currentMaterialFileIndex:int; + private var materialLibrary:Map; + + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * При установленном значении true выполняется преобразование координат геометрических вершин посредством + * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена + * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z). + */ + public var rotateModel:Boolean; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function LoaderOBJ() { + } + + /** + * Контейнер, содержащий все загруженные из OBJ-файла модели. + */ + public function get content():Object3D { + return _content; + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + try { + objLoader.close(); + } catch (e:Error) { + } + mtlLoader.close(); + } + + /** + * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL OBJ-файла + * @param loadMaterials флаг загрузки материалов. Если указано значение true, будут обработаны все файлы + * материалов, указанные в исходном OBJ-файле. + * @param context LoaderContext для загрузки файлов текстур + * + * @see #content + */ + public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void { + _content = null; + this.loadMaterials = loadMaterials; + this.loaderContext = context; + basePath = url.substring(0, url.lastIndexOf("/") + 1); + if (objLoader == null) { + objLoader = new URLLoader(); + objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete); + objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } else { + close(); + } + objLoader.load(new URLRequest(url)); + } + + /** + * Обработка окончания загрузки obj файла. + * + * @param e + */ + private function onObjLoadComplete(e:Event):void { + parse(objLoader.data); + } + + /** + * Обработка ошибки при загрузке. + * + * @param e + */ + private function onObjLoadError(e:ErrorEvent):void { + dispatchEvent(e); + } + + /** + * Метод выполняет разбор данных, полученных из obj файла. + * + * @param s содержимое obj файла + * @param materialLibrary библиотека материалов + * @return объект, содержащий все трёхмерные объекты, определённые в obj файле + */ + private function parse(data:String):void { + _content = new Object3D(); + currentObject = new Mesh(); + currentObject.mobility = mobility; + _content.addChild(currentObject); + + globalVertices = new Array(); + globalTextureVertices = new Array(); + materialFileNames = new Array(); + + var lines:Array = data.split(REGEXP_SPLIT_FILE); + for each (var line:String in lines) { + parseLine(line); + } + moveFacesToSurface(); + // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах. + if (loadMaterials && materialFileNames.length > 0) { + loadMaterialsLibrary(); + } else { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * + */ + private function parseLine(line:String):void { + line = line.replace(REGEXP_TRIM,"$1"); + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + // Объявление нового объекта + case CMD_OBJECT_NAME: + defineObject(parts[1]); + break; + // Объявление вершины + case CMD_VERTEX: + globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление текстурной вершины + case CMD_TEXTURE_VERTEX: + globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление грани + case CMD_FACE: + createFace(parts); + break; + case CMD_MATERIAL_LIB: + storeMaterialFileNames(parts); + break; + case CMD_USE_MATERIAL: + setNewMaterial(parts); + break; + } + } + + /** + * Объявление нового объекта. + * + * @param objectName имя объекта + */ + private function defineObject(objectName:String):void { + if (currentObject.faces.length == 0) { + // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя + currentObject.name = objectName; + } else { + // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект + moveFacesToSurface(); + currentObject = new Mesh(objectName); + currentObject.mobility = mobility; + _content.addChild(currentObject); + } + vIndexStart = globalVertices.length; + vtIndexStart = globalTextureVertices.length; + } + + /** + * Создание грани в текущем объекте. + * + * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1 + */ + private function createFace(parts:Array):void { + // Стартовый индекс вершины в объекте для добавляемой грани + var startVertexIndex:int = currentObject.vertices.length; + // Создание вершин в объекте + var faceVertexCount:int = parts.length - 1; + var vtIndices:Array = new Array(3); + // Массив идентификаторов вершин грани + var faceVertices:Array = new Array(faceVertexCount); + for (var i:int = 0; i < faceVertexCount; i++) { + var indices:Array = parts[i + 1].split("/"); + // Создание вершины + var vIdx:int = int(indices[0]); + // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1. + // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта. + var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx; + + var vertex:Vertex = currentObject.vertices[actualIndex]; + // Если вершины нет в объекте, она добавляется + if (vertex == null) { + var p:Point3D = globalVertices[actualIndex]; + if (rotateModel) { + // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X + vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex); + } else { + vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex); + } + } + faceVertices[i] = vertex; + + // Запись индекса текстурной вершины + if (i < 3) { + vtIndices[i] = int(indices[1]); + } + } + // Создание грани + var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length); + // Установка uv координат + if (vtIndices[0] != 0) { + p = globalTextureVertices[vtIndices[0] - 1]; + face.aUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[1] - 1]; + face.bUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[2] - 1]; + face.cUV = new Point(p.x, p.y); + } + // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте + if (currentMaterialName != null) { + materialFaces.push(face); + } + } + + /** + * Загрузка библиотек материалов. + * + * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1 + */ + private function storeMaterialFileNames(parts:Array):void { + for (var i:int = 1; i < parts.length; i++) { + materialFileNames.push(parts[i]); + } + } + + /** + * Установка нового текущего материала. + * + * @param parts массив, во втором элементе которого содержится имя материала + */ + private function setNewMaterial(parts:Array):void { + // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта + moveFacesToSurface(); + // Установка нового текущего материала + currentMaterialName = parts[1]; + } + + /** + * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала. + */ + private function moveFacesToSurface():void { + if (currentMaterialName != null && materialFaces.length > 0) { + if (currentObject.hasSurface(currentMaterialName)) { + // При наличии поверхности с таким идентификатором, грани добавляются в неё + var surface:Surface = currentObject.getSurfaceById(currentMaterialName); + for each (var face:* in materialFaces) { + surface.addFace(face); + } + } else { + // При отсутствии поверхности с таким идентификатором, создатся новая поверхность + currentObject.createSurface(materialFaces, currentMaterialName); + } + } + materialFaces = []; + } + + /** + * Загрузка материалов. + */ + private function loadMaterialsLibrary():void { + if (mtlLoader == null) { + mtlLoader = new LoaderMTL(); + mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete); + mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } + materialLibrary = new Map(); + + currentMaterialFileIndex = -1; + loadNextMaterialFile(); + } + + /** + * Обработка успешной загрузки библиотеки материалов. + */ + private function onMaterialFileLoadComplete(e:Event):void { + materialLibrary.concat(mtlLoader.library); + // Загрузка следующего файла материалов + loadNextMaterialFile(); + } + + /** + * + */ + private function loadNextMaterialFile():void { + currentMaterialFileIndex++; + if (currentMaterialFileIndex == materialFileNames.length) { + setMaterials(); + dispatchEvent(new Event(Event.COMPLETE)); + } else { + mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext); + } + } + + /** + * Установка материалов. + */ + private function setMaterials():void { + if (materialLibrary != null) { + for (var objectKey:* in _content.children) { + var object:Mesh = objectKey; + for (var surfaceKey:* in object.surfaces) { + var surface:Surface = object.surfaces[surfaceKey]; + // Поверхности имеют идентификаторы, соответствующие именам материалов + var materialInfo:MaterialInfo = materialLibrary[surfaceKey]; + if (materialInfo != null) { + if (materialInfo.bitmapData == null) { + surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode); + } else { + surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize); + } + } + } + } + } + } + + /** + * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено + * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо + * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения, + * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину + * смещения. + * + * @param surface поверхность, грани которой обрабатываюся + * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y + * указывает смещение по V. + * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования + * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V. + */ + private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void { + for (var key:* in surface.faces) { + var face:Face = key; + var uv:Point = face.aUV; + if (uv != null) { + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.aUV = uv; + uv = face.bUV; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.bUV = uv; + uv = face.cUV; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.cUV = uv; + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as new file mode 100644 index 0000000..4bf9e57 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.loaders { + /** + * @private + * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора + * описания текстуры. + * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/ + */ + internal class MTLTextureMapInfo { + + // Ассоциация параметров команды объявления текстуры и методов для их чтения + private static const optionReaders:Object = { + "-clamp": clampReader, + "-o": offsetReader, + "-s": sizeReader, + + "-blendu": stubReader, + "-blendv": stubReader, + "-bm": stubReader, + "-boost": stubReader, + "-cc": stubReader, + "-imfchan": stubReader, + "-mm": stubReader, + "-t": stubReader, + "-texres": stubReader + }; + + // Смещение в текстурном пространстве + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var offsetW:Number = 0; + + // Масштабирование текстурного пространства + public var sizeU:Number = 1; + public var sizeV:Number = 1; + public var sizeW:Number = 1; + + // Флаг повторения текстуры + public var repeat:Boolean = true; + // Имя файла текстуры + public var fileName:String; + + /** + * Метод выполняет разбор данных о текстуре. + * + * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла. + * @return объект, содержащий данные о текстуре + */ + public static function parse(parts:Array):MTLTextureMapInfo { + var info:MTLTextureMapInfo = new MTLTextureMapInfo(); + // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры + var index:int = 1; + var reader:Function; + // Чтение параметров текстуры + while ((reader = optionReaders[parts[index]]) != null) { + index = reader(index, parts, info); + } + // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры + info.fileName = parts[index]; + return info; + } + + /** + * Читатель-заглушка. Пропускает все неподдерживаемые параметры. + */ + private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + index++; + var maxIndex:int = parts.length - 1; + while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) { + index++; + } + return index; + } + + /** + * Метод чтения параметров масштабирования текстурного пространства. + */ + private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.sizeU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.sizeV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.sizeW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметров смещения текстуры. + */ + private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.offsetU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.offsetV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.offsetW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметра повторения текстуры. + */ + private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.repeat = parts[index + 1] == "off"; + return index + 2; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as new file mode 100644 index 0000000..0a9a9ee --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders { + import flash.display.BitmapData; + import flash.geom.Point; + + /** + * @private + * Класс содержит обобщённую информацию о материале. + */ + internal class MaterialInfo { + public var color:uint; + public var alpha:Number; + + public var textureFileName:String; + public var bitmapData:BitmapData; + public var repeat:Boolean; + + public var mapOffset:Point; + public var mapSize:Point; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as new file mode 100644 index 0000000..0d3d0c3 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as @@ -0,0 +1,198 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + import alternativa.utils.ColorUtils; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + * Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу + * полигона линией заданной толщины и цвета. + */ + public class DevMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha прозрачность + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * + * @param camera + * @param skin + * @param length + * @param points + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + /* + //Мобильность + var param:int = skin.primitive.mobility*10; + */ + + /* + // Уровень распиленности + var param:int = 0; + var prm:PolyPrimitive = skin.primitive; + while (prm != null) { + prm = prm.parent; + param++; + } + param *= 10; + */ + + // Уровень в BSP-дереве + var param:int = 0; + var node:BSPNode = skin.primitive.node; + while (node != null) { + node = node.parent; + param++; + } + param *= 5; + + var c:uint = ColorUtils.rgb(param, param, param); + + if (camera._orthographic) { + gfx.beginFill(c); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(c); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as new file mode 100644 index 0000000..3a76932 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as @@ -0,0 +1,45 @@ +package alternativa.engine3d.materials { + /** + * @private + * Точка, подготовленная к отрисовке. + */ + public final class DrawPoint { + /** + * Координата X в системе координат камеры. + */ + public var x:Number; + /** + * Координата Y в системе координат камеры. + */ + public var y:Number; + /** + * Координата Z в системе координат камеры. + */ + public var z:Number; + /** + * Координата U в текстурном пространстве. + */ + public var u:Number; + /** + * Координата V в текстурном пространстве. + */ + public var v:Number; + + /** + * Создаёт новый экземпляр класса. + * + * @param x координата X в системе координат камеры + * @param y координата Y в системе координат камеры + * @param z координата Z в системе координат камеры + * @param u координата U в текстурном пространстве + * @param v координата V в текстурном пространстве + */ + public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) { + this.x = x; + this.y = y; + this.z = z; + this.u = u; + this.v = v; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..795bdb7 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,163 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу + * грани линией заданной толщины и цвета. + */ + public class FillMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..399304f --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов. + */ + public class Material { + + /** + * Создание клона материала. + * + * @return клон материала + */ + public function clone():Material { + return new Material(); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as new file mode 100644 index 0000000..8052c58 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as @@ -0,0 +1,197 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов полигональных поверхностей. + */ + public class SurfaceMaterial extends Material { + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Альфа + */ + alternativa3d var _alpha:Number; + /** + * @private + * Режим наложения цвета + */ + alternativa3d var _blendMode:String = BlendMode.NORMAL; + /** + * @private + * Материал использует информация об UV-координатах + */ + alternativa3d var useUV:Boolean = false; + + /** + * Создание экземпляра класса. + * + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + _alpha = alpha; + _blendMode = blendMode; + } + + /** + * Поверхность, которой назначен материал. + */ + public function get surface():Surface { + return _surface; + } + + /** + * @private + * Добавление на сцену + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Добавление к мешу + * + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void {} + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void {} + + /** + * @private + * Добавление на поверхность + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Сохраняем поверхность + _surface = surface; + } + + /** + * @private + * Удаление с поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Удаляем ссылку на поверхность + _surface = null; + } + + /** + * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + */ + public function get alpha():Number { + return _alpha; + } + + /** + * @private + */ + public function set alpha(value:Number):void { + if (_alpha != value) { + _alpha = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Режим наложения цвета. + */ + public function get blendMode():String { + return _blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + if (_blendMode != value) { + _blendMode = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @private + * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает + * true. + * + * @param primitive примитив для проверки + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return true; + } + + /** + * @private + * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.). + * + * @param skin скин для очистки + */ + alternativa3d function clear(skin:Skin):void { + skin.gfx.clear(); + } + + /** + * @private + * Метод выполняет отрисовку в заданный скин. + * + * @param camera камера, вызвавшая метод + * @param skin скин, в котором нужно рисовать + * @param length длина массива points + * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса + * alternativa.engine3d.materials.DrawPoint + * + * @see DrawPoint + */ + alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SurfaceMaterial(_alpha, _blendMode); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..d41d4cd --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,370 @@ +package alternativa.engine3d.materials { + import __AS3__.vec.Vector; + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.display.BitmapData; + import flash.display.Graphics; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, заполняющий грань текстурой. Помимо наложения текстуры, материал может рисовать границу грани линией + * заданной толщины и цвета. + */ + public class TextureMaterial extends SurfaceMaterial { + + private static var stubBitmapData:BitmapData; + private static var stubMatrix:Matrix; + + private var gfx:Graphics; + private var textureMatrix:Matrix = new Matrix(); + private var focalLength:Number; + private var distortion:Number; + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + /** + * @private + * Повтор текстуры + */ + alternativa3d var _repeat:Boolean; + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + /** + * @private + * Точность перспективной коррекции + */ + alternativa3d var _precision:Number; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра текстурного материала. + * + * @param texture текстура материала + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param repeat повтор текстуры при заполнении + * @param smooth сглаживание текстуры при увеличении масштаба + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + * @param precision точность перспективной коррекции. Может быть задана одной из констант класса + * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более + * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра. + * + * @see TextureMaterialPrecision + */ + public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) { + super(alpha, blendMode); + _texture = texture; + _repeat = repeat; + _smooth = smooth; + _wireThickness = wireThickness; + _wireColor = wireColor; + _precision = precision; + useUV = true; + } + + /** + * @private + * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. + * + * @param primitive примитив для проверки + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _texture != null; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + gfx = skin.gfx; + + // Проверка на нулевую UV-матрицу + if (skin.primitive.face.uvMatrixBase == null) { + if (stubBitmapData == null) { + // Создание текстуры-заглушки + stubBitmapData = new BitmapData(2, 2, false, 0); + stubBitmapData.setPixel(0, 0, 0xFF00FF); + stubBitmapData.setPixel(1, 1, 0xFF00FF); + stubMatrix = new Matrix(10, 0, 0, 10, 0, 0); + } + gfx.beginBitmapFill(stubBitmapData, stubMatrix); + if (camera._orthographic) { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + return; + } + + if (camera._orthographic) { + // Расчитываем матрицу наложения текстуры + var face:Face = skin.primitive.face; + // Если матрица не расчитана, считаем + if (!camera.uvMatricesCalculated[face]) { + camera.calculateUVMatrix(face, _texture._width, _texture._height); + } + gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + focalLength = camera.focalLength; + //distortion = camera.focalDistortion*_precision; + + var front:int = 0; + var back:int = length - 1; + + var newFront:int = 1; + var newBack:int = (back > 0) ? (back - 1) : (length - 1); + var direction:Boolean = true; + + var a:DrawPoint = points[back]; + var b:DrawPoint; + var c:DrawPoint = points[front]; + + var drawVertices:Vector. = new Vector.(); + var drawUVTs:Vector. = new Vector.(); + + for (i = 0; i < length; i++) { + var p:DrawPoint = points[i]; + var t:Number = focalLength/p.z; + drawVertices[i << 1] = p.x*t; + drawVertices[(i << 1) + 1] = p.y*t; + drawUVTs.push(p.u, 1 - p.v, t); + } + + var drawIndices:Vector. = new Vector.(); + + while (front != newBack) { + if (direction) { +/* a = points[front]; + b = points[newFront]; + c = points[back]; + */ + drawIndices.push(front, newFront, back); + + front = newFront; + newFront = (front < length - 1) ? (front + 1) : 0; + } else { +/* a = points[newBack]; + b = points[back]; + c = points[front]; + */ + drawIndices.push(newBack, back, front); + + back = newBack; + newBack = (back > 0) ? (back - 1) : (length - 1); + } + + direction = !direction; + } + gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + gfx.drawTriangles(drawVertices, drawIndices, drawUVTs); + + } + } + + + /** + * Текстура материала. Материал не выполняет никаких действий по отрисовке, если не задана текстура. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get repeat():Boolean { + return _repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + if (_repeat != value) { + _repeat = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Цвет линии обводки полигона. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Точность перспективной коррекции. + */ + public function get precision():Number { + return _precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + if (_precision != value) { + _precision = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision); + return res; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as new file mode 100644 index 0000000..ca955e5 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as @@ -0,0 +1,39 @@ +package alternativa.engine3d.materials { + + /** + * Класс содержит константы точности перспективной коррекции текстурного материала. + * + * @see TextureMaterial + */ + public class TextureMaterialPrecision { + + /** + * Адаптивная триангуляция не будет выполняться, только простая триангуляция. + */ + public static const NONE:Number = -1; + /** + * Очень низкое качество адаптивной триангуляции. + */ + public static const VERY_LOW:Number = 50; + /** + * Низкое качество адаптивной триангуляции. + */ + public static const LOW:Number = 25; + /** + * Среднее качество адаптивной триангуляции. + */ + public static const MEDIUM:Number = 10; + /** + * Высокое качество адаптивной триангуляции. + */ + public static const HIGH:Number = 6; + /** + * Очень высокое качество адаптивной триангуляции. + */ + public static const VERY_HIGH:Number = 3; + /** + * Максимальное качество адаптивной триангуляции. + */ + public static const BEST:Number = 1; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as new file mode 100644 index 0000000..c9c6d4e --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as @@ -0,0 +1,128 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + import alternativa.engine3d.core.PolyPrimitive; + + use namespace alternativa3d; + + /** + * Материал для рисования рёбер граней. + */ + public class WireMaterial extends SurfaceMaterial { + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + /** + * @private + * Толщина линий + */ + alternativa3d var _thickness:Number; + + /** + * Создание экземпляра класса. + * + * @param thickness толщина линий + * @param color цвет линий + * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + _color = color; + _thickness = thickness; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _thickness >= 0; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + gfx.moveTo(point.x, point.y); + for (i = 0; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 0; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет линий. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется. + */ + public function get thickness():Number { + return _thickness; + } + + /** + * @private + */ + public function set thickness(value:Number):void { + if (_thickness != value) { + _thickness = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new WireMaterial(_thickness, _color, _alpha, _blendMode); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as new file mode 100644 index 0000000..5e0e560 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as @@ -0,0 +1,30 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к + * эллипсоиду плоскость, проходящая через точку столкновения с гранью. + */ + public class Collision { + /** + * Грань, с которой произошло столкновение. + */ + public var face:Face; + /** + * Нормаль плоскости столкновения. + */ + public var normal:Point3D; + /** + * Смещение плоскости столкновения. + */ + public var offset:Number; + /** + * Координаты точки столкновения. + */ + public var point:Point3D; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as new file mode 100644 index 0000000..65c3634 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as @@ -0,0 +1,60 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + */ + public class CollisionPlane { + // Узел BSP дерева, который содержит плоскость + public var node:BSPNode; + // Индикатор положения объекта относительно плоскости (спереди или сзади) + public var infront:Boolean; + // Расстояние до плоскости в начальной точке (всегда положительное) + public var sourceOffset:Number; + // Расстояние до плоскости в конечной точке + public var destinationOffset:Number; + + // Хранилище неиспользуемых плоскостей + static private var collector:Array = new Array(); + + + /** + * Создание плоскости + * + * @param node + * @param infront + * @param sourceOffset + * @param destinationOffset + * @return + */ + static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane { + + // Достаём плоскость из коллектора + var plane:CollisionPlane = collector.pop(); + // Если коллектор пуст, создаём новую плоскость + if (plane == null) { + plane = new CollisionPlane(); + } + + plane.node = node; + plane.infront = infront; + plane.sourceOffset = sourceOffset; + plane.destinationOffset = destinationOffset; + + return plane; + } + + /** + * Удаление плоскости, все ссылки должны быть почищены + * + * @param plane + */ + static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void { + plane.node = null; + collector.push(plane); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as new file mode 100644 index 0000000..7dd776e --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.physics { + /** + * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet + * при определении столкновений. + * + * @see EllipsoidCollider#collisionSet + */ + public class CollisionSetMode { + /** + * Грани объектов игнорируются при определении столкновений. + */ + static public const EXCLUDE:int = 1; + /** + * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам. + */ + static public const INCLUDE:int = 2; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as new file mode 100644 index 0000000..6f0732f --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as @@ -0,0 +1,993 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + use namespace alternativa3d; + + /** + * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками. + */ + public class EllipsoidCollider { + // Максимальное количество попыток найти свободное от столкновения со сценой направление + private static const MAX_COLLISIONS:uint = 50; + // Радиус наибольшей сферы + private var _radius:Number = 100; + private var _radius2:Number = _radius * _radius; + private var _radiusX:Number = _radius; + private var _radiusY:Number = _radius; + private var _radiusZ:Number = _radius; + private var _radiusX2:Number = _radiusX * _radiusX; + private var _radiusY2:Number = _radiusY * _radiusY; + private var _radiusZ2:Number = _radiusZ * _radiusZ; + // Коэффициенты масштабирования осей + private var _scaleX:Number = 1; + private var _scaleY:Number = 1; + private var _scaleZ:Number = 1; + // Квадраты коэффициентов масштабирования осей + private var _scaleX2:Number = 1; + private var _scaleY2:Number = 1; + private var _scaleZ2:Number = 1; + + private var collisionSource:Point3D; + private var currentDisplacement:Point3D = new Point3D(); + private var collisionDestination:Point3D = new Point3D(); + + private var collisionPlanes:Array = new Array(); + private var collisionPrimitive:PolyPrimitive; + private var collisionPrimitiveNearest:PolyPrimitive; + private var collisionPlanePoint:Point3D = new Point3D(); + private var collisionPrimitiveNearestLengthSqr:Number; + private var collisionPrimitivePoint:Point3D = new Point3D(); + + private var collisionNormal:Point3D = new Point3D(); + private var collisionPoint:Point3D = new Point3D(); + private var collisionOffset:Number; + + private var currentCoords:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + private var collisionRadius:Number; + private var radiusVector:Point3D = new Point3D(); + private var p1:Point3D = new Point3D(); + private var p2:Point3D = new Point3D();; + private var localCollisionPlanePoint:Point3D = new Point3D(); + + // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу. + private var useSimpleAlgorithm:Boolean = true; + + /** + * Сцена, в которой определяются столкновения. + */ + public var scene:Scene3D; + /** + * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих + * координат меньше указанной погрешности. + */ + public var offsetThreshold:Number = 0.01; + /** + * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры + * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит + * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества. + * + * @see #collisionSetMode + * @see alternativa.engine3d.core.Mesh + * @see alternativa.engine3d.core.Surface + */ + public var collisionSet:Set; + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если + * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений. + * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным + * в множестве объектам. + * + * @default true + * @see #collisionSet + */ + private var _collisionSetMode:int = CollisionSetMode.EXCLUDE; + + /** + * Создаёт новый экземпляр класса. + * + * @param scene сцена, в которой определяются столкновения + * @param scaleX радиус эллипсоида по оси X + * @param scaleY радиус эллипсоида по оси Y + * @param scaleZ радиус эллипсоида по оси Z + */ + public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) { + this.scene = scene; + this.radiusX = radiusX; + this.radiusY = radiusY; + this.radiusZ = radiusZ; + } + + /** + * @private + */ + public function get collisionSetMode():int { + return _collisionSetMode; + } + + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. + * + * @default CollisionSetMode.EXCLUDE + * @see #collisionSet + * @see CollisionSetMode + */ + public function set collisionSetMode(value:int):void { + if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) { + throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value"); + } + _collisionSetMode = value; + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusX():Number { + return _radiusX; + } + + /** + * @private + */ + public function set radiusX(value:Number):void { + _radiusX = value >= 0 ? value : -value; + _radiusX2 = _radiusX * _radiusX; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusY():Number { + return _radiusY; + } + + /** + * @private + */ + public function set radiusY(value:Number):void { + _radiusY = value >= 0 ? value : -value; + _radiusY2 = _radiusY * _radiusY; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusZ():Number { + return _radiusZ; + } + + /** + * @private + */ + public function set radiusZ(value:Number):void { + _radiusZ = value >= 0 ? value : -value; + _radiusZ2 = _radiusZ * _radiusZ; + calculateScales(); + } + + /** + * Расчёт коэффициентов масштабирования осей. + */ + private function calculateScales():void { + _radius = _radiusX; + if (_radiusY > _radius) { + _radius = _radiusY; + } + if (_radiusZ > _radius) { + _radius = _radiusZ; + } + _radius2 = _radius * _radius; + _scaleX = _radiusX / _radius; + _scaleY = _radiusY / _radius; + _scaleZ = _radiusZ / _radius; + _scaleX2 = _scaleX * _scaleX; + _scaleY2 = _scaleY * _scaleY; + _scaleZ2 = _scaleZ * _scaleZ; + + useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ); + } + + /** + * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение + * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены, + * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если + * значение поля scene равно null, то результат работы метода будет простой суммой двух + * входных векторов. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль + * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке. + * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат + * корневого объекта сцены + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + * @see #offsetThreshold + */ + public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void { + // Расчеты не производятся, если перемещение мало + if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold && + displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold && + displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + return; + } + + // Начальные координаты + currentCoords.x = sourcePoint.x; + currentCoords.y = sourcePoint.y; + currentCoords.z = sourcePoint.z; + // Начальный вектор перемещения + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + // Начальная точка назначения + destinationPoint.x = sourcePoint.x + currentDisplacement.x; + destinationPoint.y = sourcePoint.y + currentDisplacement.y; + destinationPoint.z = sourcePoint.z + currentDisplacement.z; + + if (useSimpleAlgorithm) { + calculateDestinationS(sourcePoint, destinationPoint); + } else { + calculateDestinationE(sourcePoint, destinationPoint); + } + } + + /** + * Вычисление точки назначения для сферы. + * @param sourcePoint + * @param destinationPoint + */ + private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision ) { + // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали + var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра сферы для следующей итерации + currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold); + currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold); + currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold); + // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Вычисление точки назначения для эллипсоида. + * @param destinationPoint + * @return + */ + private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не + // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и + // необходимость принудительного выхода. + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision) { + // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали + var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра эллипсоида для следующей итерации + collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius; + currentCoords.x = collision.point.x - collisionRadius * radiusVector.x; + currentCoords.y = collision.point.y - collisionRadius * radiusVector.y; + currentCoords.z = collision.point.z - collisionRadius * radiusVector.z; + // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + destinationPoint.x = currentCoords.x; + destinationPoint.y = currentCoords.y; + destinationPoint.z = currentCoords.z; + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора + * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены + * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат + * корневого объекта сцены + * + * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены, + * false если столкновений нет или не задано значение поля scene. + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + */ + public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean { + if (scene == null) { + return false; + } + + collisionSource = sourcePoint; + + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + + collisionDestination.x = collisionSource.x + currentDisplacement.x; + collisionDestination.y = collisionSource.y + currentDisplacement.y; + collisionDestination.z = collisionSource.z + currentDisplacement.z; + + collectPotentialCollisionPlanes(scene.bsp); + collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING); + + var plane:CollisionPlane; + // Пока не найдём столкновение с примитивом или плоскости не кончатся + if (useSimpleAlgorithm) { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneS(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } else { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneE(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } + + var collisionFound:Boolean = collisionPrimitive != null; + if (collisionFound) { + collision.face = collisionPrimitive.face; + collision.normal = collisionNormal; + collision.offset = collisionOffset; + collision.point = collisionPoint; + } + + collisionPrimitive = null; + collisionSource = null; + + return collisionFound; + } + + /** + * Сбор потенциальных плоскостей столкновения. + * + * @param node текущий узел BSP-дерева + */ + private function collectPotentialCollisionPlanes(node:BSPNode):void { + if (node == null) { + return; + } + + var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset; + var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset; + var plane:CollisionPlane; + + if (sourceOffset >= 0) { + // Исходное положение центра перед плоскостью ноды + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + if (destinationOffset < _radius) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset); + collisionPlanes.push(plane); + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + } + } else { + // Исходное положение центра за плоскостью ноды + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + if (destinationOffset > -_radius) { + // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад + if (node.backPrimitives != null) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset); + collisionPlanes.push(plane); + } + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + } + } + } + + /** + * @private + * Определение пересечения сферы с примитивами, лежащими в заданной плоскости. + * + * @param plane плоскость, содержащая примитивы для проверки + */ + private function calculateCollisionWithPlaneS(plane:CollisionPlane):void { + collisionPlanePoint.copy(collisionSource); + + var normal:Point3D = plane.node.normal; + // Если сфера врезана в плоскость + if (plane.sourceOffset <= _radius) { + if (plane.infront) { + collisionPlanePoint.x -= normal.x * plane.sourceOffset; + collisionPlanePoint.y -= normal.y * plane.sourceOffset; + collisionPlanePoint.z -= normal.z * plane.sourceOffset; + } else { + collisionPlanePoint.x += normal.x * plane.sourceOffset; + collisionPlanePoint.y += normal.y * plane.sourceOffset; + collisionPlanePoint.z += normal.z * plane.sourceOffset; + } + } else { + // Находим центр сферы во время столкновения с плоскостью + var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset); + collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time; + collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time; + collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time; + + // Устанавливаем точку пересечения cферы с плоскостью + if (plane.infront) { + collisionPlanePoint.x -= normal.x * _radius; + collisionPlanePoint.y -= normal.y * _radius; + collisionPlanePoint.z -= normal.z * _radius; + } else { + collisionPlanePoint.x += normal.x * _radius; + collisionPlanePoint.y += normal.y * _radius; + collisionPlanePoint.z += normal.z * _radius; + } + } + + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(plane.node.primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + + // Нормаль плоскости при столкновении - нормаль плоскости + if (plane.infront) { + collisionNormal.x = normal.x; + collisionNormal.y = normal.y; + collisionNormal.z = normal.z; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normal.x; + collisionNormal.y = -normal.y; + collisionNormal.z = -normal.z; + collisionOffset = -plane.node.offset; + } + + // Точка столкновения в точке столкновения с плоскостью + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + + } else { + // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей + + // Вектор из ближайшей точки в центр сферы + var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x; + var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y; + var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z; + + // Если движение в сторону точки + if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) { + + // Ищем нормализованный вектор обратного направления + var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z); + var vectorX:Number = -currentDisplacement.x / vectorLength; + var vectorY:Number = -currentDisplacement.y / vectorLength; + var vectorZ:Number = -currentDisplacement.z / vectorLength; + + // Длина вектора из ближайшей точки в центр сферы + var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ; + + // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления + var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ; + + var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength; + + if (projectionInsideSphereLengthSqr > 0) { + // Находим расстояние из ближайшей точки до сферы + var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr); + + if (distance < vectorLength) { + // Столкновение сферы с ближайшей точкой произошло + + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x; + collisionPoint.y = collisionPrimitivePoint.y; + collisionPoint.z = collisionPrimitivePoint.z; + + // Находим нормаль плоскости столкновения + var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr); + collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength; + collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength; + collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength; + + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + } + } + + /** + * @private + * Определение столкновения сферы с примитивом. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void { + + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + for (var i:uint = 0; i < length; i++) { + + var p1:Point3D = points[i]; + var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0]; + + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = collisionPlanePoint.x - p1.x; + var vectorY:Number = collisionPlanePoint.y - p1.y; + var vectorZ:Number = collisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = collisionPlanePoint.x - p2.x; + vectorY = collisionPlanePoint.y - p2.y; + vectorZ = collisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + + /** + * Проверка на действительное столкновение эллипсоида с плоскостью. + */ + private function calculateCollisionWithPlaneE(plane:CollisionPlane):void { + var normalX:Number = plane.node.normal.x; + var normalY:Number = plane.node.normal.y; + var normalZ:Number = plane.node.normal.z; + // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление + // от плоскости, в этом случае столкновения не происходит. + var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ; + if (plane.infront) { + displacementAlongNormal = -displacementAlongNormal; + } + // Выходим из функции в случае удаления от плоскости + if (displacementAlongNormal < 0) { + return; + } + // Определение ближайшей к плоскости точки эллипсоида + var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2); + // Положение точки в локальной системе координат эллипсоида + var localClosestX:Number = k * normalX * _scaleX2; + var localClosestY:Number = k * normalY * _scaleY2; + var localClosestZ:Number = k * normalZ * _scaleZ2; + // Глобальные координаты точки + var px:Number = collisionSource.x + localClosestX; + var py:Number = collisionSource.y + localClosestY; + var pz:Number = collisionSource.z + localClosestZ; + // Растояние от найденной точки эллипсоида до плоскости + var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + if (closestPointDistance > plane.sourceOffset) { + // Найдена наиболее удалённая точка, расчитываем вторую + px = collisionSource.x - localClosestX; + py = collisionSource.y - localClosestY; + pz = collisionSource.z - localClosestZ; + closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + } + // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости, + // то столкновения не произошло и нужно завершить выполнение функции + if (closestPointDistance > displacementAlongNormal) { + return; + } + // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения + // с ближайшим полигоном, лежащим в этой плоскости + if (closestPointDistance <= 0 ) { + // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость + if (plane.infront) { + collisionPlanePoint.x = px - normalX * closestPointDistance; + collisionPlanePoint.y = py - normalY * closestPointDistance; + collisionPlanePoint.z = pz - normalZ * closestPointDistance; + } else { + collisionPlanePoint.x = px + normalX * closestPointDistance; + collisionPlanePoint.y = py + normalY * closestPointDistance; + collisionPlanePoint.z = pz + normalZ * closestPointDistance; + } + } else { + // Эллипсоид не пересекается с плоскостью, ищем точку контакта + var t:Number = closestPointDistance / displacementAlongNormal; + collisionPlanePoint.x = px + currentDisplacement.x * t; + collisionPlanePoint.y = py + currentDisplacement.y * t; + collisionPlanePoint.z = pz + currentDisplacement.z * t; + } + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + // Нормаль плоскости при столкновении - нормаль плоскости примитива + if (plane.infront) { + collisionNormal.x = normalX; + collisionNormal.y = normalY; + collisionNormal.z = normalZ; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normalX; + collisionNormal.y = -normalY; + collisionNormal.z = -normalZ; + collisionOffset = -plane.node.offset; + } + // Радиус эллипсоида в точке столкновения + collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px - collisionSource.x; + radiusVector.y = py - collisionSource.y; + radiusVector.z = pz - collisionSource.z; + // Точка столкновения совпадает с точкой столкновения с плоскостью примитива + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + } else { + // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива + // Трансформированная в пространство эллипсоида ближайшая точка на примитиве + px = collisionPrimitivePoint.x; + py = collisionPrimitivePoint.y; + pz = collisionPrimitivePoint.z; + + var collisionExists:Boolean; + // Квадрат расстояния из центра эллипсоида до точки примитива + var r2:Number = px*px + py*py + pz*pz; + if (r2 < _radius2) { + // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе + k = _radius / Math.sqrt(r2); + px *= k * _scaleX; + py *= k * _scaleY; + pz *= k * _scaleZ; + + collisionExists = true; + } else { + // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки + // примитива, с поверхностью эллипсоида + // Трансформированный в пространство эллипсоида противоположный вектор скорости + var vx:Number = - currentDisplacement.x / _scaleX; + var vy:Number = - currentDisplacement.y / _scaleY; + var vz:Number = - currentDisplacement.z / _scaleZ; + // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости + var a:Number = vx*vx + vy*vy + vz*vz; + var b:Number = 2 * (px*vx + py*vy + pz*vz); + var c:Number = r2 - _radius2; + var d:Number = b*b - 4*a*c; + // Решение есть только при действительном дискриминанте квадратного уравнения + if (d >=0) { + // Выбирается минимальное время, т.к. нужна первая точка пересечения + t = -0.5 * (b + Math.sqrt(d)) / a; + // Точка лежит на луче только если время положительное + if (t >= 0 && t <= 1) { + // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство + px = (px + t * vx) * _scaleX; + py = (py + t * vy) * _scaleY; + pz = (pz + t * vz) * _scaleZ; + + collisionExists = true; + } + } + } + if (collisionExists) { + // Противоположная нормаль к эллипсоиду в точке пересечения + collisionNormal.x = - px / _scaleX2; + collisionNormal.y = - py / _scaleY2; + collisionNormal.z = - pz / _scaleZ2; + collisionNormal.normalize(); + // Радиус эллипсоида в точке столкновения + collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px; + radiusVector.y = py; + radiusVector.z = pz; + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x; + collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y; + collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z; + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + + /** + * @private + * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит + * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки + * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через + * минимальное время. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void { + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + var point1:Point3D; + var point2:Point3D = points[length - 1]; + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX; + localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY; + localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ; + // Обход всех рёбер примитива + for (var i:uint = 0; i < length; i++) { + point1 = point2; + point2 = points[i]; + + p1.x = p2.x; + p1.y = p2.y; + p1.z = p2.z; + + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра + // с целью определения положения точки столкновения относительно полигона + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = localCollisionPlanePoint.x - p1.x; + var vectorY:Number = localCollisionPlanePoint.y - p1.y; + var vectorZ:Number = localCollisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = localCollisionPlanePoint.x - p2.x; + vectorY = localCollisionPlanePoint.y - p2.y; + vectorZ = localCollisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..5e730b5 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,316 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Прямоугольный параллелепипед. + */ + public class Box extends Mesh { + + /** + * Создание нового параллелепипеда. + *

Параллелепипед после создания будет содержать в себе шесть поверхностей. + * "front", "back", "left", "right", "top", "bottom" + * на каждую из которых может быть установлен свой материал.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param height высота. Размерность по оси Z. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param heightSegments количество сегментов по по высоте + * @param reverse задает направление нормалей граней. Если указано значение true, то нормали будут направлены внутрь фигуры. + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы. + */ + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + height = (height < 0)? 0 : height; + + var wh:Number = width/2; + var lh:Number = length/2; + var hh:Number = height/2; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + // Создание точек + for (x = 0; x <= widthSegments; x++) { + for (y = 0; y <= lengthSegments; y++) { + for (z = 0; z <= heightSegments; z++) { + if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) { + createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z); + } + } + } + } + + // Создание поверхностей + var front:Surface = createSurface(null, "front"); + var back:Surface = createSurface(null, "back"); + var left:Surface = createSurface(null, "left"); + var right:Surface = createSurface(null, "right"); + var top:Surface = createSurface(null, "top"); + var bottom:Surface = createSurface(null, "bottom"); + + // Создание граней + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var faceId:String; + + // Для оптимизаций UV при триангуляции + var aUV:Point; + var cUV:Point; + + // Построение верхней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "top_"+x+"_"+y; + if (reverse) { + if (triangulate) { + aUV = new Point(x*wd, (lengthSegments - y)*ld); + cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1"); + setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0"); + setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, y*ld); + cUV = new Point((x + 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + top.addFace(faceId + ":0"); + top.addFace(faceId + ":1"); + } else { + top.addFace(faceId); + } + } + } + + // Построение нижней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "bottom_" + x + "_" + y; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld); + cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1"); + setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, y*ld); + cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + bottom.addFace(faceId + ":0"); + bottom.addFace(faceId + ":1"); + } else { + bottom.addFace(faceId); + } + } + } + + // Построение фронтальной грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "front_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, z*hd); + cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0"); + } else { + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, z*hd); + cUV = new Point((x + 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + front.addFace(faceId + ":0"); + front.addFace(faceId + ":1"); + } else { + front.addFace(faceId); + } + } + } + + // Построение задней грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "back_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point(x * wd, (z + 1) * hd); + cUV = new Point((x + 1) * wd, z * hd); + createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (z + 1)*hd); + cUV = new Point((widthSegments - x - 1)*wd, z*hd); + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0"); + createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + back.addFace(faceId + ":0"); + back.addFace(faceId + ":1"); + } else { + back.addFace(faceId); + } + } + } + + // Построение левой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "left_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point(y*ld, (z + 1)*hd); + cUV = new Point((y + 1)*ld, z*hd); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((lengthSegments - y - 1)*ld, z*hd); + cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + left.addFace(faceId + ":0"); + left.addFace(faceId + ":1"); + } else { + left.addFace(faceId); + } + } + } + + // Построение правой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "right_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point((lengthSegments - y)*ld, z*hd); + cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(y*ld, z*hd); + cUV = new Point((y + 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + right.addFace(faceId + ":0"); + right.addFace(faceId + ":1"); + } else { + right.addFace(faceId); + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Box(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as new file mode 100644 index 0000000..f98c012 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as @@ -0,0 +1,264 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Усеченный конус или цилиндр. + */ + public class Cone extends Mesh { + + /** + * Создает примитив усеченный конус или цилиндр. + *

Различные значения параметров позволяют создавать различные примитивы. + * Так при установленном параметре topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном bottomRadius = topRadius будет построен цилиндр.

+ *

По умолчанию параметр triangulate установлен в false и на примитив не может быть наложена текстура. + * Только при установленном параметре triangulate в true это возможно.

+ *

После создания примитив всегда содержит в себе поверхность "side". + * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom", + * при установленном параметре topRadius в примитиве создается поверхность "top". + * На каждую из поверхностей может быть наложен свой материал

+ * + * @param height высота примтива. Размерность по оси Z. Не может быть меньше нуля. + * @param bottomRadius нижний радиус примитива + * @param topRadius верхний радиус примтива + * @param heightSegments количество сегментов по высоте примитива + * @param radialSegments количество сегментов по радиусу примтива + * @param reverse задает направление нормалей. При значении true нормли будут направлены внутрь примитива. + * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы + * и появится возможность наложить на примитив текстуру. + */ + public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) { + + if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) { + return; + } + height = (height < 0)? 0 : height; + bottomRadius = (bottomRadius < 0)? 0 : bottomRadius; + topRadius = (topRadius < 0)? 0 : topRadius; + + const radialSegment:Number = MathUtils.DEG360/radialSegments; + const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments; + const heightSegment:Number = height/heightSegments; + const halfHeight:Number = height*0.5 + const uSegment:Number = 1/radialSegments; + const vSegment:Number = 1/heightSegments; + + // Создание вершин + if (topRadius == 0 || triangulate) { + var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp"); + } + if (bottomRadius == 0 || triangulate) { + var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown"); + } + + var radial:uint; + var segment:uint; + + var topSegment:uint = heightSegments - int(topRadius == 0); + var bottomSegment:uint = int(bottomRadius == 0) ; + for (segment = bottomSegment; segment <= topSegment; segment++) { + for (radial = 0; radial < radialSegments; radial++) { + var currentAngle:Number = radialSegment*radial; + var currentRadius:Number = bottomRadius - (radiusSegment*segment); + createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var face:Face; + + var points:Array; + + var side:Surface = createSurface(null, "side"); + + if (topRadius == 0) { + // Создание граней у верхнего полюса + var prevRadial:uint = radialSegments - 1; + var centerUV:Point = new Point(0.5, 1); + var v:Number = topSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней верхней крышки + var top:Surface = createSurface(null, "top"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + var UV:Point; + var prevUV:Point; + if (reverse) { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + topSegment); + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + topSegment); + } + } + top.addFace(createFace(points, "top")); + } + } + // Создание боковых граней + var face2:Face; + var aUV:Point; + var cUV:Point; + for (segment = bottomSegment; segment < topSegment; segment++) { + prevRadial = radialSegments - 1; + v = segment * vSegment; + for (radial = 0; radial < radialSegments; radial++) { + if (triangulate) { + if (reverse) { + face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"); + face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"); + aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment) + cUV = new Point(1 - prevRadial*uSegment, v); + setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2); + } else { + face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"); + face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"); + aUV = new Point(prevRadial*uSegment, v) + cUV = new Point((prevRadial + 1)*uSegment, v + vSegment); + setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2); + } + side.addFace(face); + side.addFace(face2); + } else { + if (reverse) { + side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } else { + side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment)); + } + } + prevRadial = radial; + } + } + + if (bottomRadius == 0) { + // Создание граней у нижнего полюса + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0); + v = bottomSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней нижней крышки + var bottom:Surface = createSurface(null, "bottom"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + if (reverse) { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + bottomSegment); + } + } else { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + bottomSegment); + } + } + bottom.addFace(createFace(points, "bottom")); + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Cone(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as new file mode 100644 index 0000000..ea681b4 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as @@ -0,0 +1,197 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Геоплоскость. + */ + public class GeoPlane extends Mesh { + + /** + * Создает геоплоскость. + *

Геоплоскость это плоскость с сетчатой структурой граней.

+ *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значение параметра равно true, то создаётся двусторонняя поверхность + * @param reverse флаг инвертирования нормалей + */ + public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var hh:Number = length/2; + // Размеры сегмента + var ws:Number = width/widthSegments; + var hs:Number = length/lengthSegments; + + // Размеры UV-сегмента + var us:Number = 1/widthSegments; + var vs:Number = 1/lengthSegments; + + // Создание точек + var x:uint; + var y:uint; + var frontUV:Array = new Array(); + var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array(); + for (y = 0; y <= lengthSegments; y++) { + frontUV[y] = new Array(); + if (backUV != null) { + backUV[y] = new Array(); + } + for (x = 0; x <= widthSegments; x++) { + if ((y & 1) == 0) { + // Если чётный ряд + createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(x*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(x*us, 1 - y*vs); + } + } else { + // Если нечётный ряд + if (x == 0) { + // Первая точка + createVertex(-wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(0, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(0, 1 - y*vs); + } + } else { + createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point((x - 0.5)*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs); + } + if (x == widthSegments) { + // Последняя точка + createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1)); + frontUV[y][x + 1] = new Point(1, y*vs); + if (backUV != null) { + backUV[y][x + 1] = new Point(1, 1 - y*vs); + } + } + } + } + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + var face:Face; + for (y = 0; y < lengthSegments; y++) { + for (var n:uint = 0; n <= (widthSegments << 1); n++) { + x = n >> 1; + if ((y & 1) == 0) { + // Если чётный ряд + if ((n & 1) == 0) { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } + } else { + // Если нечётный ряд + if ((n & 1) == 0) { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } + } + } + } + + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoPlane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..7ba3bf2 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,321 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Геосфера. + */ + public class GeoSphere extends Mesh { + + /** + * Создает геосферу. + *

Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

Текстурные координаты у геосферы не находятся в промежутке [0, 1], + * поэтому для материала с текстурой необходимо устанавливать флаг repeat. + * + * @param radius радиус геосферы. Не может быть меньше нуля. + * @param segments количество сегментов геосферы + * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы. + */ + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + if (segments == 0) { + return; + } + radius = (radius < 0)? 0 : radius; + + const sections:uint = 20; + + //var nfaces:uint = sections*segments*segments; + //var nverts:Number = nfaces/2 + 2; + var points:Array = new Array(); + + var i:uint; + var f:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + points.push(createVertex(0, 0, radius, "poleUp")); + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG360*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, subz)); + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG180*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, -subz)); + } + points.push(createVertex(0, 0, -radius, "poleDown")); + + for (i = 1; i < 6; i++) { + interpolate(0, i, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i % 5 + 1, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i + 5, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, (i + 3) % 5 + 6, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i + 5, i % 5 + 6, segments, points); + } + for (i = 6; i < 11; i++) { + interpolate(11, i, segments, points); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points); + } + } + // Создание граней + var face:Face; + var surface:Surface = createSurface(); + for (f = 0; f < sections; f++) { + for (var row:uint = 0; row < segments; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + var va:Vertex = points[a]; + var vb:Vertex = points[b]; + var vc:Vertex = points[c]; + var aUV:Point; + var bUV:Point; + var cUV:Point; + var coordA:Point3D = va._coords; + var coordB:Point3D = vb._coords; + var coordC:Point3D = vc._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + // полюс + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, cUV, bUV, face); + } else { + face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f); + setUVsToFace(aUV, bUV, cUV, face); + } + surface.addFace(face); + //trace(a + "_" + b + "_" + c); + if (column < row) { + b = findVertices(segments, f, row, column + 1); + var vd:Vertex = points[b]; + coordB = vd._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, bUV, cUV, face); + } else { + face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f); + setUVsToFace(aUV, cUV, bUV, face); + } + surface.addFace(face); + } + } + } + } + } + +/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point { + if (radius == 0) { + radius = point.length; + } + if (reverse) { + var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360; + } else { + u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5; + } + return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5); + } + */ + private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void { + if (num < 2) { + return; + } + var a:Vertex = Vertex(points[v1]); + var b:Vertex = Vertex(points[v2]); + var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin)); + } + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoSphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..d346048 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,117 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Плоскость. + */ + public class Plane extends Mesh { + + /** + * Создает плоскость. + *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси Х. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значении параметра равно true, то формируется двусторонняя плоскость + * @param reverse инвертирование нормалей + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы. + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var lh:Number = length/2; + + // Размеры сегмента + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + + // Размеры UV-сегмента + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + + // Создание точек и UV + var x:int; + var y:int; + var uv:Array = new Array(); + for (y = 0; y <= lengthSegments; y++) { + uv[y] = new Array(); + for (x = 0; x <= widthSegments; x++) { + uv[y][x] = new Point(x*wd, y*ld); + createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y); + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + if (twoSided || !reverse) { + if (triangulate) { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0"); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1"); + setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1"); + front.addFace("front" + x + "_" + y + ":0"); + front.addFace("front" + x + "_" + y + ":1"); + } else { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y); + front.addFace("front" + x + "_" + y); + } + } + if (twoSided || reverse) { + if (triangulate) { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0"); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1"); + setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1"); + back.addFace("back" + x + "_" + y + ":0"); + back.addFace("back"+x+"_"+y + ":1"); + } else { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y); + back.addFace("back" + x + "_" + y); + } + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Plane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as new file mode 100644 index 0000000..994d7f7 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as @@ -0,0 +1,144 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Сфера. + */ + public class Sphere extends Mesh { + + /** + * Создает сферу. + *

После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

По умолчанию параметр triangulate установлен в false и на сферу нельзя наложить текстуру. + * Только при установленном triangulate в true это возможно.

+ * + * @param radius Радиус сферы. Не может быть меньше нуля. + * @param radialSegments количество сегментов по экватору сферы + * @param heightSegments количество сегментов по высоте + * @param reverse флаг инвертирования нормалей. При параметре установленном в true нормали направлены внутрь сферы. + * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы, + * и будет возможно наложить на примитив текстуру. + */ + public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) { + if ((radialSegments < 3) || (heightSegments < 2)) { + return; + } + radius = (radius < 0)? 0 : radius; + + var poleUp:Vertex = createVertex(0, 0, radius, "poleUp"); + var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown"); + + const radialAngle:Number = MathUtils.DEG360/radialSegments; + const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1); + + var radial:uint; + var segment:uint; + + // Создание вершин + for (segment = 1; segment < heightSegments; segment++) { + var currentHeightAngle:Number = heightAngle*segment; + var segmentRadius:Number = Math.sin(currentHeightAngle)*radius; + var segmentZ:Number = Math.cos(currentHeightAngle)*radius; + for (radial = 0; radial < radialSegments; radial++) { + var currentRadialAngle:Number = radialAngle*radial; + createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var surface:Surface = createSurface(); + + var prevRadial:uint = radialSegments - 1; + var lastSegmentString:String = "_" + (heightSegments - 1); + + var uStep:Number = 1/radialSegments; + var vStep:Number = 1/heightSegments; + + var face:Face; + + // Для триангуляции + var aUV:Point; + var cUV:Point; + + var u:Number; + + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString)); + + // Если включена триангуляция + if (triangulate) { + // Триангулируем середину и просчитываем маппинг + u = uStep*prevRadial; + setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1))); + cUV = new Point(1 - u, 1 - vStep*segment); + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString); + + } else { + // Просто создаем середину + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString)); + + if (triangulate) { + u = uStep*prevRadial; + setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(u, 1 - (vStep*segment)); + cUV = new Point(u + uStep, 1 - vStep * (segment + 1)); + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString); + } else { + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as new file mode 100644 index 0000000..5def643 --- /dev/null +++ b/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as @@ -0,0 +1,834 @@ +package alternativa.utils { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Утилиты для работы с Mesh-объектами. + */ + public class MeshUtils { + + static private var verticesSort:Array = ["x", "y", "z"]; + static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC]; + + /** + * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются. + * + * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh + * + * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов + */ + static public function uniteMeshes(... meshes):Mesh { + var res:Mesh = new Mesh(); + + var length:uint = meshes.length; + var key:*; + var vertex:Vertex; + var face:Face; + var j:uint; + for (var i:uint = 0; i < length; i++) { + var mesh:Mesh = meshes[i]; + var vertices:Map = mesh._vertices.clone(); + for (key in vertices) { + vertex = vertices[key]; + vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z); + } + var faces:Map = mesh._faces.clone(); + for (key in faces) { + face = faces[key]; + var faceVertices:Array = new Array().concat(face._vertices); + for (j = 0; j < face._verticesCount; j++) { + vertex = faceVertices[j]; + faceVertices[j] = vertices[vertex.id]; + } + faces[key] = res.createFace(faceVertices); + res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]); + } + for (key in mesh._surfaces) { + var surface:Surface = mesh._surfaces[key]; + var surfaceFaces:Array = surface._faces.toArray(); + var numFaces:uint = surfaceFaces.length; + for (j = 0; j < numFaces; j++) { + face = surfaceFaces[j]; + surfaceFaces[j] = faces[face.id]; + } + var newSurface:Surface = res.createSurface(surfaceFaces); + newSurface.material = SurfaceMaterial(surface.material.clone()); + } + } + return res; + } + + /** + * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности. + * + * @param mesh объект, вершины которого объединяются + * @param threshold погрешность измерения расстояний + */ + static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void { + // Получаем список вершин меша и сортируем по координатам + var vertices:Array = mesh._vertices.toArray(true); + vertices.sortOn(verticesSort, verticesSortOptions); + + // Поиск вершин с одинаковыми координатами + var weld:Map = new Map(true); + var vertex:Vertex; + var currentVertex:Vertex = vertices[0]; + var length:uint = vertices.length; + var i:uint; + for (i = 1; i < length; i++) { + vertex = vertices[i]; + if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) { + weld[vertex] = currentVertex; + } else { + currentVertex = vertex; + } + } + + // Собираем грани объединяемых вершин + var faces:Set = new Set(true); + var keyVertex:*; + var keyFace:*; + for (keyVertex in weld) { + vertex = keyVertex; + for (keyFace in vertex._faces) { + faces[keyFace] = true; + } + } + + // Заменяем грани + for (keyFace in faces) { + var face:Face = keyFace; + var id:Object = mesh.getFaceId(face); + var surface:Surface = face._surface; + var aUV:Point = face._aUV; + var bUV:Point = face._bUV; + var cUV:Point = face._cUV; + vertices = new Array().concat(face._vertices); + length = vertices.length; + for (i = 0; i < length; i++) { + vertex = weld[vertices[i]]; + if (vertex != null) { + vertices[i] = vertex; + } + } + mesh.removeFace(face); + face = mesh.createFace(vertices, id); + if (surface != null) { + surface.addFace(face); + } + face.aUV = aUV; + face.bUV = bUV; + face.cUV = cUV; + } + + // Удаляем вершины + for (keyVertex in weld) { + mesh.removeVertex(keyVertex); + } + } + + /** + * Объединение соседних граней, образующих плоский выпуклый многоугольник. + * + * @param mesh объект, грани которого объединяются + * @param angleThreshold погрешность измерения углов + * @param uvThreshold погрешность измерения UV-координат + */ + static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void { + angleThreshold = Math.cos(angleThreshold); + + var face:Face; + var sibling:Face; + var key:*; + var i:uint; + + // Формируем списки граней + var faces1:Set = new Set(true); + var faces2:Set = new Set(true); + + // Формируем список нормалей + var normals:Map = new Map(true); + for each (face in mesh._faces.clone()) { + var faceNormal:Point3D = face.normal; + if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) { + faces1[face] = true; + normals[face] = faceNormal; + } else { + mesh.removeFace(face); + } + } + + // Объединение + do { + // Флаг объединения + var weld:Boolean = false; + // Объединяем грани + while ((face = faces1.take()) != null) { + //var num:uint = face.num; + //var vertices:Array = face.vertices; + var currentWeld:Boolean = false; + + // Проверка общих граней по точкам + + + // Проверка общих граней по рёбрам + + // Перебираем точки грани + for (i = 0; (i < face._verticesCount) && !currentWeld; i++) { + var faceIndex1:uint = i; + var faceIndex2:uint; + var siblingIndex1:int; + var siblingIndex2:uint; + + // Перебираем грани текущей точки + var vertex:Vertex = face._vertices[faceIndex1]; + var vertexFaces:Set = vertex.faces; + for (key in vertexFaces) { + sibling = key; + // Если грань в списке на объединение и в одной поверхности + if (faces1[sibling] && face._surface == sibling._surface) { + faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0; + siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]); + // Если общее ребро + if (siblingIndex1 >= 0) { + // Если грани сонаправлены + var normal:Point3D = normals[face]; + if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) { + // Если в точках объединения нет перегибов + siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0; + + // Расширяем грани объединения + var i1:uint; + var i2:uint; + while (true) { + i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1); + i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0; + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex1 = i1; + siblingIndex2 = i2; + } else { + break; + } + } + + while (true) { + i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0; + i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1); + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex2 = i1; + siblingIndex1 = i2; + } else { + break; + } + } + + vertex = face._vertices[faceIndex1]; + var a:Point3D = vertex.coords; + vertex = face._vertices[faceIndex2]; + var b:Point3D = vertex.coords; + + // Считаем первый перегиб + vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0]; + var c:Point3D = vertex.coords; + vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)]; + var d:Point3D = vertex.coords; + + var cx:Number = c.x - a.x; + var cy:Number = c.y - a.y; + var cz:Number = c.z - a.z; + var dx:Number = d.x - a.x; + var dy:Number = d.y - a.y; + var dz:Number = d.z - a.z; + + var crossX:Number = cy*dz - cz*dy; + var crossY:Number = cz*dx - cx*dz; + var crossZ:Number = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если в первой точке перегиба нет + if (dot >= 0) { + + // Считаем второй перегиб + vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0]; + c = vertex.coords; + vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)]; + d = vertex.coords; + + cx = c.x - b.x; + cy = c.y - b.y; + cz = c.z - b.z; + dx = d.x - b.x; + dy = d.y - b.y; + dz = d.z - b.z; + + crossX = cy*dz - cz*dy; + crossY = cz*dx - cx*dz; + crossZ = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если во второй точке перегиба нет + if (dot >= 0) { + + // Флаг наличия UV у обеих граней + var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null); + + if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) { + + // Если грани имеют UV, проверяем совместимость + if (hasUV) { + vertex = sibling._vertices[0]; + var uv:Point = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[1]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[2]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) { + break; + } + } + + // Формируем новую грань + var newVertices:Array = new Array(); + var n:uint = faceIndex2; + do { + newVertices.push(face._vertices[n]); + n = (n < face._verticesCount - 1) ? (n + 1) : 0; + } while (n != faceIndex1); + n = siblingIndex2; + do { + newVertices.push(sibling._vertices[n]); + n = (n < sibling._verticesCount - 1) ? (n + 1) : 0; + } while (n != siblingIndex1); + + // Выбираем начальную точку + n = getBestBeginVertexIndex(newVertices); + for (var m:uint = 0; m < n; m++) { + newVertices.push(newVertices.shift()); + } + + // Заменяем грани новой + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (hasUV) { + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + mesh.removeFace(sibling); + + // Обновляем список нормалей + delete normals[sibling]; + delete normals[face]; + normals[newFace] = newFace.normal; + + // Обновляем списки расчётов + delete faces1[sibling]; + faces2[newFace] = true; + + // Помечаем объединение + weld = true; + currentWeld = true; + break; + } + } + } + } + } + } + } + } + + + + // Если не удалось объединить, переносим грань + faces2[face] = true; + } + + // Меняем списки + var fs:Set = faces1; + faces1 = faces2; + faces2 = fs; + } while (weld); + + removeIsolatedVertices(mesh); + removeUselessVertices(mesh); + } + + /** + * Удаление вершин объекта, не принадлежащим ни одной грани. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeIsolatedVertices(mesh:Mesh):void { + for each (var vertex:Vertex in mesh._vertices.clone()) { + if (vertex._faces.isEmpty()) { + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeUselessVertices(mesh:Mesh):void { + var v:Vertex; + var key:*; + var face:Face; + var index:uint; + var length:uint; + for each (var vertex:Vertex in mesh._vertices.clone()) { + var unless:Boolean = true; + var indexes:Map = new Map(true); + for (key in vertex._faces) { + face = key; + length = face._vertices.length; + index = face._vertices.indexOf(vertex); + v = face._vertices[index]; + var a:Point3D = v.coords; + v = face._vertices[(index < length - 1) ? (index + 1) : 0]; + var b:Point3D = v.coords; + v = face._vertices[(index > 0) ? (index - 1) : (length - 1)]; + var c:Point3D = v.coords; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) { + indexes[face] = index; + } else { + unless = false; + break; + } + } + if (unless && !indexes.isEmpty()) { + // Удаляем + for (key in indexes) { + var i:uint; + face = key; + index = indexes[face]; + length = face._vertices.length; + var newVertices:Array = new Array(); + for (i = 0; i < length; i++) { + if (i != index) { + newVertices.push(face._vertices[i]); + } + } + var n:uint = getBestBeginVertexIndex(newVertices); + for (i = 0; i < n; i++) { + newVertices.push(newVertices.shift()); + } + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var normal:Point3D = face.normal; + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + } + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вырожденных граней. + * + * @param mesh объект, грани которого удаляются + */ + static public function removeSingularFaces(mesh:Mesh):void { + for each (var face:Face in mesh._faces.clone()) { + var normal:Point3D = face.normal; + if (normal.x == 0 && normal.y == 0 && normal.z == 0) { + mesh.removeFace(face); + } + } + } + + /** + * @private + * Находит наиболее подходящую первую точку. + * @param vertices + * @return + */ + static public function getBestBeginVertexIndex(vertices:Array):uint { + var bestIndex:uint = 0; + var num:uint = vertices.length; + if (num > 3) { + var maxCrossLength:Number = 0; + var v:Vertex = vertices[num - 1]; + var c1:Point3D = v.coords; + v = vertices[0]; + var c2:Point3D = v.coords; + + var prevX:Number = c2.x - c1.x; + var prevY:Number = c2.y - c1.y; + var prevZ:Number = c2.z - c1.z; + + for (var i:uint = 0; i < num; i++) { + c1 = c2; + v = vertices[(i < num - 1) ? (i + 1) : 0]; + c2 = v.coords; + + var nextX:Number = c2.x - c1.x; + var nextY:Number = c2.y - c1.y; + var nextZ:Number = c2.z - c1.z; + + var crossX:Number = prevY*nextZ - prevZ*nextY; + var crossY:Number = prevZ*nextX - prevX*nextZ; + var crossZ:Number = prevX*nextY - prevY*nextX; + + + var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ; + if (crossLength > maxCrossLength) { + maxCrossLength = crossLength; + bestIndex = i; + } + + prevX = nextX; + prevY = nextY; + prevZ = nextZ; + } + // Берём предыдущий + bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1); + } + + return bestIndex; + } + + /** + * Генерация AS-класса. + * + * @param mesh объект, на базе которого генерируется класс + * @param packageName имя пакета для генерируемого класса + * @return AS-класс в текстовом виде + */ + static public function generateClass(mesh:Mesh, packageName:String = ""):String { + + var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1); + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["alternativa.engine3d.core.Mesh"] = true; + + var materialSet:Map = new Map(true); + var materialName:String; + var materialNum:uint = 1; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r"; + + + var newLine:Boolean = false; + if (mesh.mobility != 0) { + constructor += "\t\t\tmobility = " + mesh.mobility +";\r"; + newLine = true; + } + + if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) { + importSet["alternativa.types.Point3D"] = true; + constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r"; + newLine = true; + } else { + if (mesh.x != 0) { + constructor += "\t\t\tx = " + mesh.x + ";\r"; + newLine = true; + } + if (mesh.y != 0) { + constructor += "\t\t\ty = " + mesh.y + ";\r"; + newLine = true; + } + if (mesh.z != 0) { + constructor += "\t\t\tz = " + mesh.z + ";\r"; + newLine = true; + } + } + if (mesh.rotationX != 0) { + constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r"; + newLine = true; + } + if (mesh.rotationY != 0) { + constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r"; + newLine = true; + } + if (mesh.rotationZ != 0) { + constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r"; + newLine = true; + } + if (mesh.scaleX != 1) { + constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r"; + newLine = true; + } + if (mesh.scaleY != 1) { + constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r"; + newLine = true; + } + if (mesh.scaleZ != 1) { + constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + function idToString(value:*):String { + return isNaN(value) ? ("\"" + value + "\"") : value; + } + + function blendModeToString(value:String):String { + switch (value) { + case BlendMode.ADD: return "BlendMode.ADD"; + case BlendMode.ALPHA: return "BlendMode.ALPHA"; + case BlendMode.DARKEN: return "BlendMode.DARKEN"; + case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE"; + case BlendMode.ERASE: return "BlendMode.ERASE"; + case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT"; + case BlendMode.INVERT: return "BlendMode.INVERT"; + case BlendMode.LAYER: return "BlendMode.LAYER"; + case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN"; + case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY"; + case BlendMode.NORMAL: return "BlendMode.NORMAL"; + case BlendMode.OVERLAY: return "BlendMode.OVERLAY"; + case BlendMode.SCREEN: return "BlendMode.SCREEN"; + case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT"; + default: return "BlendMode.NORMAL"; + } + } + + function colorToString(value:uint):String { + var hex:String = value.toString(16).toUpperCase(); + var res:String = "0x"; + var len:uint = 6 - hex.length; + for (var j:uint = 0; j < len; j++) { + res += "0"; + } + res += hex; + return res; + } + + var i:uint; + var length:uint; + var key:*; + var id:String; + var face:Face; + var surface:Surface; + + newLine = false; + for (id in mesh._vertices) { + var vertex:Vertex = mesh._vertices[id]; + var coords:Point3D = vertex.coords; + constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + newLine = false; + for (id in mesh._faces) { + face = mesh._faces[id]; + length = face._verticesCount; + constructor += "\t\t\tcreateFace([" + for (i = 0; i < length - 1; i++) { + constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", "; + } + constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r"; + + if (face._aUV != null || face._bUV != null || face._cUV != null) { + importSet["flash.geom.Point"] = true; + constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r"; + } + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + for (id in mesh._surfaces) { + surface = mesh._surfaces[id]; + var facesStr:String = ""; + for (key in surface._faces) { + facesStr += idToString(mesh.getFaceId(key)) + ", "; + } + constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r"; + + if (surface.material != null) { + var material:String; + var defaultAlpha:Boolean = surface.material.alpha == 1; + var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL; + if (surface.material is WireMaterial) { + importSet["alternativa.engine3d.materials.WireMaterial"] = true; + var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0; + var defaultColor:Boolean = WireMaterial(surface.material).color == 0; + material = "new WireMaterial("; + if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) { + material += WireMaterial(surface.material).thickness; + if (!defaultColor || !defaultAlpha || !defaultBlendMode) { + material += ", " + colorToString(WireMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode) { + material += ", " + surface.material.alpha ; + if (!defaultBlendMode) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + } + } + } + } + } + var defaultWireThickness:Boolean; + var defaultWireColor:Boolean; + if (surface.material is FillMaterial) { + importSet["alternativa.engine3d.materials.FillMaterial"] = true; + defaultWireThickness = FillMaterial(surface.material).wireThickness < 0; + defaultWireColor = FillMaterial(surface.material).wireColor == 0; + material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + material += ", " + surface.material.alpha; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor) { + material += ", " + FillMaterial(surface.material).wireThickness; + if (!defaultWireColor) { + material += ", " + colorToString(FillMaterial(surface.material).wireColor); + } + } + } + } + } + if (surface.material is TextureMaterial) { + importSet["alternativa.engine3d.materials.TextureMaterial"] = true; + var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat; + var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth; + defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0; + defaultWireColor = TextureMaterial(surface.material).wireColor == 0; + var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM; + + if (TextureMaterial(surface.material).texture == null) { + materialName = "null"; + } else { + importSet["alternativa.types.Texture"] = true; + if (materialSet[TextureMaterial(surface.material).texture] == undefined) { + materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++; + materialSet[TextureMaterial(surface.material).texture] = materialName; + } else { + materialName = materialSet[TextureMaterial(surface.material).texture]; + } + materialName = materialName.split(".")[0]; + } + material = "new TextureMaterial(" + materialName; + if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).alpha; + if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).repeat; + if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).smooth; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).wireThickness; + if (!defaultWireColor || !defaultPrecision) { + material += ", " + colorToString(TextureMaterial(surface.material).wireColor); + if (!defaultPrecision) { + material += ", " + TextureMaterial(surface.material).precision; + } + } + } + } + } + } + } + } + + constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r"; + } + } + + var imports:String = ""; + newLine = false; + + var importArray:Array = new Array(); + for (key in importSet) { + importArray.push(key); + } + importArray.sort(); + + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + var embeds:String = ""; + newLine = false; + for each (materialName in materialSet) { + var materialClassName:String = materialName.split(".")[0]; + var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1); + embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r"; + newLine = true; + } + embeds += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/Alternativa3D.as b/Alternativa3D5/5.5/alternativa/Alternativa3D.as new file mode 100644 index 0000000..a8d2ba3 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "5.5.0"; + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as new file mode 100644 index 0000000..44102e2 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as @@ -0,0 +1,889 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием + * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены. + */ + public class CameraController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; +// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; +// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения угла тангажа. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш уменьшения угла тангажа. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево). + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо). + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + + // флаги действий + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _up:Boolean; + private var _down:Boolean; + private var _pitchUp:Boolean; + private var _pitchDown:Boolean; + private var _yawLeft:Boolean; + private var _yawRight:Boolean; + private var _accelerate:Boolean; + + private var _moveLocal:Boolean = true; + + // Флаг включения управления камерой + private var _controlsEnabled:Boolean = false; + + // Значение таймера в начале прошлого кадра + private var lastFrameTime:uint; + + // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов. + private var _mouseSensitivity:Number = 1; + // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + private var _mousePitch:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по тангажу + private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch; + // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + private var _mouseYaw:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по расканью + private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw; + + // Вспомогательные переменные для обзора мышью + private var mouseLookActive:Boolean; + private var startDragCoords:Point = new Point(); + private var currentDragCoords:Point = new Point(); + private var prevDragCoords:Point = new Point(); + private var startRotX:Number; + private var startRotZ:Number; + + // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры + private var _pitchSpeed:Number = 1; + // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры + private var _yawSpeed:Number = 1; + // Скорость изменения крена в радианах за секунду при управлении с клавиатуры +// public var bankSpeed:Number = 2; +// private var bankMatrix:Matrix3D = new Matrix3D(); + + // Скорость поступательного движения в единицах за секунду + private var _speed:Number = 100; + // Коэффициент увеличения скорости при соответствующей нажатой клавише + private var _speedMultiplier:Number = 2; + + private var velocity:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + + private var _fovStep:Number = Math.PI / 180; + private var _zoomMultiplier:Number = 0.1; + + // Привязка клавиш к действиям + private var keyBindings:Map = new Map(); + // Привязка действий к обработчикам + private var actionBindings:Map = new Map(); + + // Источник событий клавиатуры и мыши + private var _eventsSource:DisplayObject; + // Управляемая камера + private var _camera:Camera3D; + + // Класс реализации определния столкновений + private var _collider:EllipsoidCollider; + // Флаг необходимости проверки столкновений + private var _checkCollisions:Boolean; + // Радиус сферы для определения столкновений + private var _collisionRadius:Number = 0; + // Набор исключаемых из проверки столкновений объектов + private var _collisionIgnoreSet:Set = new Set(true); + // Флаг движения + private var _isMoving:Boolean; + + private var _onStartMoving:Function; + private var _onStopMoving:Function; + + /** + * Создание экземпляра контроллера. + * + * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры + */ + public function CameraController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = forward; + actionBindings[ACTION_BACK] = back; + actionBindings[ACTION_LEFT] = left; + actionBindings[ACTION_RIGHT] = right; + actionBindings[ACTION_UP] = up; + actionBindings[ACTION_DOWN] = down; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
+ */ + public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + } + + /** + * Направление камеры на точку. + * + * @param point координаты точки направления камеры + */ + public function lookAt(point:Point3D):void { + if (_camera == null) { + return; + } + var dx:Number = point.x - _camera.x; + var dy:Number = point.y - _camera.y; + var dz:Number = point.z - _camera.z; + _camera.rotationZ = -Math.atan2(dx, dy); + _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90; + } + + /** + * Callback-функция, вызываемая при начале движения камеры. + */ + public function get onStartMoving():Function { + return _onStartMoving; + } + + /** + * @private + */ + public function set onStartMoving(value:Function):void { + _onStartMoving = value; + } + + /** + * Callback-функция, вызываемая при прекращении движения камеры. + */ + public function get onStopMoving():Function { + return _onStopMoving; + } + + /** + * @private + */ + public function set onStopMoving(value:Function):void { + _onStopMoving = value; + } + + /** + * Набор объектов, исключаемых из проверки столкновений. + */ + public function get collisionIgnoreSet():Set { + return _collisionIgnoreSet; + } + + /** + * Источник событий клавиатуры и мыши. + */ + public function get eventsSource():DisplayObject { + return _eventsSource; + } + + /** + * @private + */ + public function set eventsSource(value:DisplayObject):void { + if (_eventsSource != value) { + if (value == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + if (_controlsEnabled) { + unregisterEventsListeners(); + } + _eventsSource = value; + if (_controlsEnabled) { + registerEventListeners(); + } + } + } + + /** + * Ассоциированная камера. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + _camera = value; + if (value == null) { + controlsEnabled = false; + } else { + createCollider(); + } + } + } + + /** + * Режим движения камеры. Если значение равно true, то перемещения камеры происходят относительно + * локальной системы координат, иначе относительно глобальной. + * + * @default true + */ + public function get moveLocal():Boolean { + return _moveLocal; + } + + /** + * @private + */ + public function set moveLocal(value:Boolean):void { + _moveLocal = value; + } + + /** + * Включение режима проверки столкновений. + */ + public function get checkCollisions():Boolean { + return _checkCollisions; + } + + /** + * @private + */ + public function set checkCollisions(value:Boolean):void { + _checkCollisions = value; + } + + /** + * Радиус сферы для определения столкновений. + * + * @default 0 + */ + public function get collisionRadius():Number { + return _collisionRadius; + } + + /** + * @private + */ + public function set collisionRadius(value:Number):void { + _collisionRadius = value; + if (_collider != null) { + _collider.radiusX = _collisionRadius; + _collider.radiusY = _collisionRadius; + _collider.radiusZ = _collisionRadius; + } + } + + /** + * Привязка клавиши к действию. + * + * @param keyCode код клавиши + * @param action наименование действия + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Активация движения камеры вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function forward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения камеры назад. + * + * @param value true для начала движения, false для окончания + */ + public function back(value:Boolean):void { + _back = value; + } + + /** + * Активация движения камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function left(value:Boolean):void { + _left = value; + } + + /** + * Активация движения камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function right(value:Boolean):void { + _right = value; + } + + /** + * Активация движения камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function up(value:Boolean):void { + _up = value; + } + + /** + * Активация движения камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function down(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * @private + */ + private function createCollider():void { + _collider = new EllipsoidCollider(_camera.scene, _collisionRadius); + _collider.offsetThreshold = 0.01; + _collider.collisionSet = _collisionIgnoreSet; + } + + /** + * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw. + * + * @default 1 + * + * @see #mousePitch() + * @see #mouseYaw() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mousePitch():Number { + return _mousePitch; + } + + /** + * @private + */ + public function set mousePitch(pitch:Number):void { + _mousePitch = pitch; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + } + + /** + * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mouseYaw():Number { + return _mouseYaw; + } + + /** + * @private + */ + public function set mouseYaw(yaw:Number):void { + _mouseYaw = yaw; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость поступательного движения (единицы в секунду). + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(spd:Number):void { + _speed = spd; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Активность управления камеры. + * + * @default false + */ + public function get controlsEnabled():Boolean { + return _controlsEnabled; + } + + /** + * @private + */ + public function set controlsEnabled(value:Boolean):void { + if (_camera == null || _controlsEnabled == value) return; + if (value) { + lastFrameTime = getTimer(); + registerEventListeners(); + } + else { + unregisterEventsListeners(); + } + _controlsEnabled = value; + } + + /** + * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину + * MouseEvent.delta. + * + * @default Math.PI / 180 + */ + public function get fovStep():Number { + return _fovStep; + } + + /** + * @private + */ + public function set fovStep(value:Number):void { + _fovStep = value; + } + + /** + * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier). + * + * @default 0.1 + */ + public function get zoomMultiplier():Number { + return _zoomMultiplier; + } + + /** + * @private + */ + public function set zoomMultiplier(value:Number):void { + _zoomMultiplier = value; + } + + /** + * @private + */ + private function registerEventListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + */ + private function unregisterEventsListeners():void { + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + * @param e + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * @private + * @param e + */ + private function onMouseDown(e:MouseEvent):void { + mouseLookActive = true; + currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY; + startRotX = _camera.rotationX; + startRotZ = _camera.rotationZ; + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseUp(e:MouseEvent):void { + mouseLookActive = false; + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseWheel(e:MouseEvent):void { + if (_camera.orthographic) { + _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier); + } else { + _camera.fov -= _fovStep * e.delta; + } + } + + /** + * Обработка управляющих воздействий. + * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate(). + * + * @see alternativa.engine3d.core.Scene3D#calculate() + */ + public function processInput(): void { + if (!_controlsEnabled || _camera == null) return; + + // Время в секундах от начала предыдущего кадра + var frameTime:Number = getTimer() - lastFrameTime; + lastFrameTime += frameTime; + frameTime /= 1000; + + // Обработка mouselook + if (mouseLookActive) { + prevDragCoords.x = currentDragCoords.x; + prevDragCoords.y = currentDragCoords.y; + currentDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = _eventsSource.stage.mouseY; + if (!prevDragCoords.equals(currentDragCoords)) { + _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff; + var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawLeft) { + _camera.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _camera.rotationZ -= _yawSpeed * frameTime; + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + rotX = _camera.rotationX + _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } else if (_pitchDown) { + rotX = _camera.rotationX - _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + + // TODO: Поворот относительно продольной оси (крен, roll) + + var frameDistance:Number = _speed * frameTime; + if (_accelerate) { + frameDistance *= _speedMultiplier; + } + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + var transformation:Matrix3D = _camera._transformation; + + if (_moveLocal) { + // Режим относительных пермещений + // Движение вперед-назад + if (_forward) { + velocity.x += frameDistance * transformation.c; + velocity.y += frameDistance * transformation.g; + velocity.z += frameDistance * transformation.k; + } else if (_back) { + velocity.x -= frameDistance * transformation.c; + velocity.y -= frameDistance * transformation.g; + velocity.z -= frameDistance * transformation.k; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * transformation.a; + velocity.y -= frameDistance * transformation.e; + velocity.z -= frameDistance * transformation.i; + } else if (_right) { + velocity.x += frameDistance * transformation.a; + velocity.y += frameDistance * transformation.e; + velocity.z += frameDistance * transformation.i; + } + // Движение вверх-вниз + if (_up) { + velocity.x -= frameDistance * transformation.b; + velocity.y -= frameDistance * transformation.f; + velocity.z -= frameDistance * transformation.j; + } else if (_down) { + velocity.x += frameDistance * transformation.b; + velocity.y += frameDistance * transformation.f; + velocity.z += frameDistance * transformation.j; + } + } + else { + // Режим глобальных перемещений + var cosZ:Number = Math.cos(_camera.rotationZ); + var sinZ:Number = Math.sin(_camera.rotationZ); + // Движение вперед-назад + if (_forward) { + velocity.x -= frameDistance * sinZ; + velocity.y += frameDistance * cosZ; + } else if (_back) { + velocity.x += frameDistance * sinZ; + velocity.y -= frameDistance * cosZ; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * cosZ; + velocity.y -= frameDistance * sinZ; + } else if (_right) { + velocity.x += frameDistance * cosZ; + velocity.y += frameDistance * sinZ; + } + // Движение вверх-вниз + if (_up) { + velocity.z += frameDistance; + } else if (_down) { + velocity.z -= frameDistance; + } + } + + // Коррекция модуля вектора скорости + if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) { + velocity.length = frameDistance; + } + + // Проверка столкновений + if (_checkCollisions) { + _collider.calculateDestination(_camera.coords, velocity, destination); + _camera.x = destination.x; + _camera.y = destination.y; + _camera.z = destination.z; + } else { + _camera.x += velocity.x; + _camera.y += velocity.y; + _camera.z += velocity.z; + } + + // Обработка начала/окончания движения + if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (_onStartMoving != null) { + _onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (_onStopMoving != null) { + _onStopMoving.call(this); + } + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as new file mode 100644 index 0000000..3c325d6 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as @@ -0,0 +1,469 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление объектом, находящимся в корневом объекте сцены, подобно управлению летательным аппаратом. + * Повороты выполняются вокруг локальных осей объекта, собственные ускорения действуют вдоль локальных осей. + * + *

Соответствия локальных осей для объектов, не являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВперёдКрен
ZВверхРысканье
+ * + *

Соответствия локальных осей для объектов, являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВнизРысканье
ZВперёдКрен
+ *

+ * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или + * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются + * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши + * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент + * чувствительности мыши по горизонтали даёт угловую скорость по крену. + */ + public class FlyController extends ObjectController { + /** + * Имя действия для привязки клавиш поворота по крену влево. + */ + public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; + /** + * Имя действия для привязки клавиш поворота по крену вправо. + */ + public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + + private var _rollLeft:Boolean; + private var _rollRight:Boolean; + + private var _rollSpeed:Number = 1; + + private var rotations:Point3D; + private var rollMatrix:Matrix3D = new Matrix3D(); + private var transformation:Matrix3D = new Matrix3D(); + private var axis:Point3D = new Point3D(); + + private var velocity:Point3D = new Point3D(); + private var displacement:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + private var deltaVelocity:Point3D = new Point3D(); + private var accelerationVector:Point3D = new Point3D(); + private var currentTransform:Matrix3D = new Matrix3D(); + + private var _currentSpeed:Number = 1; + /** + * Текущие координаты мышиного курсора в режиме mouse look. + */ + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * Модуль вектора ускорния, получаемого от команд движения. + */ + public var acceleration:Number = 1000; + /** + * Модуль вектора замедляющего ускорения. + */ + public var deceleration:Number = 50; + /** + * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + /** + * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор + * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту + * прикладывается замедляющее ускорение. + */ + public var inertialMode:Boolean; + + /** + * @inheritDoc + */ + public function FlyController(eventsSourceObject:DisplayObject) { + super(eventsSourceObject); + + actionBindings[ACTION_ROLL_LEFT] = rollLeft; + actionBindings[ACTION_ROLL_RIGHT] = rollRight; + } + + /** + * Текущая скорость движения. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Активация вращения по крену влево. + */ + public function rollLeft(value:Boolean):void { + _rollLeft = value; + } + + /** + * Активация вращения по крену вправо. + */ + public function rollRight(value:Boolean):void { + _rollRight = value; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_ROLL_LEFT
RIGHTACTION_ROLL_RIGHT
QACTION_YAW_LEFT
EACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT); + bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями. + * + * @param frameTime длительность текущего кадра в секундах + */ + override protected function rotateObject(frameTime:Number):void { + var transformation:Matrix3D = _object._transformation; + if (_mouseLookActive) { + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!currentMouseCoords.equals(startMouseCoords)) { + var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX; + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + + rotateObjectAroundAxis(axis, deltaYaw * frameTime); + + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY; + axis.x = currentTransform.a; + axis.y = currentTransform.e; + axis.z = currentTransform.i; + + rotateObjectAroundAxis(axis, deltaPitch * frameTime); + } + } + + // Поворот относительно продольной оси (крен, roll) + if (_rollLeft) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, -_rollSpeed * frameTime); + } else if (_rollRight) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, _rollSpeed * frameTime); + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, _pitchSpeed * frameTime); + } else if (_pitchDown) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime); + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawRight) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } + } else if (_yawLeft) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + // Движение вперед-назад + accelerationVector.x = 0; + accelerationVector.y = 0; + accelerationVector.z = 0; + if (_forward) { + accelerationVector.y = 1; + } else if (_back) { + accelerationVector.y = -1; + } + // Движение влево-вправо + if (_right) { + accelerationVector.x = 1; + } else if (_left) { + accelerationVector.x = -1; + } + // Движение ввверх-вниз + if (_up) { + accelerationVector.z = 1; + } else if (_down) { + accelerationVector.z = -1; + } + + var speedLoss:Number; + var len:Number; + + if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) { + // Управление активно + if (_object is Camera3D) { + var tmp:Number = accelerationVector.z; + accelerationVector.z = accelerationVector.y; + accelerationVector.y = -tmp; + } + accelerationVector.normalize(); + accelerationVector.x *= acceleration; + accelerationVector.y *= acceleration; + accelerationVector.z *= acceleration; + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + accelerationVector.transform(currentTransform); + deltaVelocity.x = accelerationVector.x; + deltaVelocity.y = accelerationVector.y; + deltaVelocity.z = accelerationVector.z; + deltaVelocity.x *= frameTime; + deltaVelocity.y *= frameTime; + deltaVelocity.z *= frameTime; + + if (!inertialMode) { + speedLoss = deceleration * frameTime; + var dot:Number = Point3D.dot(velocity, accelerationVector); + if (dot > 0) { + len = accelerationVector.length; + var x:Number = accelerationVector.x / len; + var y:Number = accelerationVector.y / len; + var z:Number = accelerationVector.z / len; + len = dot / len; + x = velocity.x - len * x; + y = velocity.y - len * y; + z = velocity.z - len * z; + len = Math.sqrt(x*x + y*y + z*z); + if (len > speedLoss) { + x *= speedLoss / len; + y *= speedLoss / len; + z *= speedLoss / len; + } + velocity.x -= x; + velocity.y -= y; + velocity.z -= z; + } else { + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + velocity.x += deltaVelocity.x; + velocity.y += deltaVelocity.y; + velocity.z += deltaVelocity.z; + + if (velocity.length > _speed) { + velocity.length = _speed; + } + } else { + // Управление неактивно + if (!inertialMode) { + speedLoss = deceleration * frameTime; + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + displacement.x = velocity.x * frameTime; + displacement.y = velocity.y * frameTime; + displacement.z = velocity.z * frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x / frameTime; + velocity.y = displacement.y / frameTime; + velocity.z = displacement.z / frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Поворот объекта вокруг заданной оси. + * + * @param axis + * @param angle + */ + private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void { + transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + rollMatrix.fromAxisAngle(axis, angle); + rollMatrix.inverseCombine(transformation); + rotations = rollMatrix.getRotations(rotations); + _object.rotationX = rotations.x; + _object.rotationY = rotations.y; + _object.rotationZ = rotations.z; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + + /** + * @inheritDoc + */ + override public function set enabled(value:Boolean):void { + super.enabled = value; + if (!value) { + velocity.reset(); + _currentSpeed = 0; + } + } + + /** + * @inheritDoc + */ + override protected function clearCommandFlags():void { + super.clearCommandFlags(); + _rollLeft = false; + _rollRight = false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as new file mode 100644 index 0000000..343df38 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as @@ -0,0 +1,886 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.utils.MathUtils; + import alternativa.utils.ObjectUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе + * реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд + * перемещения. + */ + public class ObjectController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + /** + * Флаг движения вперёд. + */ + protected var _forward:Boolean; + /** + * Флаг движения назад. + */ + protected var _back:Boolean; + /** + * Флаг движения влево. + */ + protected var _left:Boolean; + /** + * Флаг движения вправо. + */ + protected var _right:Boolean; + /** + * Флаг движения вверх. + */ + protected var _up:Boolean; + /** + * Флаг движения вниз. + */ + protected var _down:Boolean; + /** + * Флаг поворота относительно оси X в положительном направлении (взгляд вверх). + */ + protected var _pitchUp:Boolean; + /** + * Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз). + */ + protected var _pitchDown:Boolean; + /** + * Флаг поворота относительно оси Z в положительном направлении (взгляд налево). + */ + protected var _yawLeft:Boolean; + /** + * Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо). + */ + protected var _yawRight:Boolean; + /** + * Флаг активности режима ускорения. + */ + protected var _accelerate:Boolean; + /** + * Флаг активности режима поворотов мышью. + */ + protected var _mouseLookActive:Boolean; + /** + * Начальные координаты мышиного курсора в режиме mouse look. + */ + protected var startMouseCoords:Point3D = new Point3D(); + /** + * Флаг активности контроллера. + */ + protected var _enabled:Boolean = true; + /** + * Источник событий клавиатуры и мыши + */ + protected var _eventsSource:DisplayObject; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Map = new Map(); + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + protected var actionBindings:Map = new Map(); + /** + * Флаг активности клавиатуры. + */ + protected var _keyboardEnabled:Boolean; + /** + * Флаг активности мыши. + */ + protected var _mouseEnabled:Boolean; + /** + * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали. + */ + protected var _mouseSensitivity:Number = 1; + /** + * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseSensitivityY:Number = Math.PI / 360; + /** + * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseSensitivityX:Number = Math.PI / 360; + /** + * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY; + /** + * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX; + /** + * Угловая скорость поворота вокруг поперечной оси в радианах за секунду. + */ + protected var _pitchSpeed:Number = 1; + /** + * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду. + */ + protected var _yawSpeed:Number = 1; + /** + * Скорость поступательного движения в единицах за секунду. + */ + protected var _speed:Number = 100; + /** + * Коэффициент увеличения скорости при соответствующей активной команде. + */ + protected var _speedMultiplier:Number = 2; + /** + * Управляемый объект. + */ + protected var _object:Object3D; + /** + * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра). + */ + protected var lastFrameTime:uint; + /** + * Текущие координаты контроллера. + */ + protected var _coords:Point3D = new Point3D(); + /** + * Индикатор движения объекта (перемещения или поворота) в текущем кадре. + */ + protected var _isMoving:Boolean; + /** + * Объект для определения столкновений. + */ + protected var _collider:EllipsoidCollider = new EllipsoidCollider(); + + /** + * Включение и выключение режима проверки столкновений. + */ + public var checkCollisions:Boolean; + /** + * Функция вида function():void, вызываемая при начале движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStartMoving:Function; + /** + * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStopMoving:Function; + + // Вектор смещения + private var _displacement:Point3D = new Point3D(); + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + */ + public function ObjectController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + actionBindings[ACTION_MOUSE_LOOK] = setMouseLook; + + keyboardEnabled = true; + mouseEnabled = true; + } + + /** + * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput(). + * + * @default true + * + * @see #processInput() + */ + public function get enabled():Boolean { + return _enabled; + } + + /** + * @private + */ + public function set enabled(value:Boolean):void { + _enabled = value; + if (_enabled) { + if (_mouseEnabled) { + registerMouseListeners(); + } + if (_keyboardEnabled) { + registerKeyboardListeners(); + } + } else { + if (_mouseEnabled) { + unregisterMouseListeners(); + setMouseLook(false); + } + if (_keyboardEnabled) { + unregisterKeyboardListeners(); + } + } + } + + /** + * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения + * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера. + * + * @see #setObjectCoords() + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + _coords.copy(value); + setObjectCoords(); + } + + /** + * Чтение координат контроллера в заданную переменную. + * + * @param point переменная, в которую записываются координаты контроллера + */ + public function readCoords(point:Point3D):void { + point.copy(_coords); + } + + /** + * Управляемый объект. + */ + public function get object():Object3D { + return _object; + } + + /** + * @private + * При установке объекта устанавливается сцена для коллайдера. + */ + public function set object(value:Object3D):void { + _object = value; + _collider.scene = _object == null ? null : _object.scene; + setObjectCoords(); + } + + /** + * Объект, реализующий проверку столкновений для эллипсоида. + */ + public function get collider():EllipsoidCollider { + return _collider; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Угловая скорость поворота вокруг поперечной оси (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(value:Number):void { + _speed = value < 0 ? -value : value; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY. + * + * @default 1 + * + * @see #mouseSensitivityY() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Чувтсвительность мыши по вертикали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivityY():Number { + return _mouseSensitivityY; + } + + /** + * @private + */ + public function set mouseSensitivityY(value:Number):void { + _mouseSensitivityY = value; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + } + + /** + * Чувтсвительность мыши по горизонтали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityY() + */ + public function get mouseSensitivityX():Number { + return _mouseSensitivityX; + } + + /** + * @private + */ + public function set mouseSensitivityX(value:Number):void { + _mouseSensitivityX = value; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(), + * при выключении — stoptMouseLook(). + * + * @see #startMouseLook() + * @see #stopMouseLook() + */ + public function setMouseLook(value:Boolean):void { + if (_mouseLookActive != value) { + _mouseLookActive = value; + if (_mouseLookActive) { + startMouseLook(); + } else { + stopMouseLook(); + } + } + } + + /** + * Метод выполняет необходимые действия при включении режима вращения объекта мышью. + * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords. + * + * @see #startMouseCoords + * @see #setMouseLook() + * @see #stopMouseLook() + */ + protected function startMouseLook():void { + startMouseCoords.x = _eventsSource.stage.mouseX; + startMouseCoords.y = _eventsSource.stage.mouseY; + } + + /** + * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает + * ничего. + * + * @see #setMouseLook() + * @see #startMouseLook() + */ + protected function stopMouseLook():void { + } + + /** + * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled + * равно false, метод не выполняется. + *

+ * Алгоритм работы метода следующий: + *

+ */ + public function processInput():void { + if (!_enabled || _object == null) { + return; + } + var frameTime:Number = getTimer() - lastFrameTime; + // Проверка в связи с возможным багом десятого плеера, ну и вообще на всякий случай + if (frameTime == 0) { + return; + } + + lastFrameTime += frameTime; + if (frameTime > 100) { + frameTime = 100; + } + frameTime /= 1000; + + rotateObject(frameTime); + getDisplacement(frameTime, _displacement); + applyDisplacement(frameTime, _displacement); + + // Обработка начала/окончания движения + if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (onStartMoving != null) { + onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (onStopMoving != null) { + onStopMoving.call(this); + } + } + } + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + */ + protected function rotateObject(frameTime:Number):void { + } + + /** + * Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement смещение объекта, которое нужно обработать + */ + protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + } + + /** + * Включение и выключение обработки клавиатурных событий. При включении выполняется метод registerKeyboardListeners, + * при выключении — unregisterKeyboardListeners. + * + * @see #registerKeyboardListeners() + * @see #unregisterKeyboardListeners() + */ + public function get keyboardEnabled():Boolean { + return _keyboardEnabled; + } + + /** + * @private + */ + public function set keyboardEnabled(value:Boolean):void { + if (_keyboardEnabled != value) { + _keyboardEnabled = value; + if (_keyboardEnabled) { + if (_enabled) { + registerKeyboardListeners(); + } + } else { + unregisterKeyboardListeners(); + } + } + } + + /** + * @private + * Запуск обработчиков клавиатурных команд. + */ + private function onKeyboardEvent(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки клавиатурных событий. + * + * @see #unregisterKeyboardListeners() + */ + protected function registerKeyboardListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Удаление обработчиков при выключении обработки клавиатурных событий. + * + * @see #registerKeyboardListeners() + */ + protected function unregisterKeyboardListeners():void { + clearCommandFlags(); + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners, + * при выключении — unregisterMouseListeners. + * + * @see #registerMouseListeners() + * @see #unregisterMouseListeners() + */ + public function get mouseEnabled():Boolean { + return _mouseEnabled; + } + + /** + * @private + */ + public function set mouseEnabled(value:Boolean):void { + if (_mouseEnabled != value) { + _mouseEnabled = value; + if (_mouseEnabled) { + if (_enabled) { + registerMouseListeners(); + } + } else { + unregisterMouseListeners(); + } + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки мышиных событий. + * + * @see #unregisterMouseListeners() + */ + protected function registerMouseListeners():void { + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + } + + /** + * Удаление используемых обработчиков при выключении обработки мышиных событий. + * + * @see #registerMouseListeners() + */ + protected function unregisterMouseListeners():void { + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Активация mouselook + */ + private function onMouseDown(e:MouseEvent):void { + setMouseLook(true); + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Отключение mouselook + */ + private function onMouseUp(e:MouseEvent):void { + setMouseLook(false); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Установка координат управляемого объекта в зависимости от текущих координат контроллера. + */ + protected function setObjectCoords():void { + if (_object != null) { + _object.coords = _coords; + } + } + + /** + * Индикатор режима увеличенной скорости. + */ + public function get accelerated():Boolean { + return _accelerate; + } + + /** + * Метод сбрасывает флаги активных команд. + */ + protected function clearCommandFlags():void { + _forward = false; + _back = false; + _left = false; + _right = false; + _up = false; + _down = false; + _pitchUp = false; + _pitchDown = false; + _yawLeft = false; + _yawRight = false; + _accelerate = false; + _mouseLookActive = false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as new file mode 100644 index 0000000..edc7a0a --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as @@ -0,0 +1,520 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.physics.Collision; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены. + * + *

Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила + * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка + * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность + * движения по вертикали. + * + *

Для всех объектов, за исключением Camera3D, направлением "вперёд" считается направление его оси + * Y, направлением "вверх" — направление оси Z. Для объектов класса + * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление + * "вверх" противоположно направлению локальной оси Y. + * + *

Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для + * эллипсоида, параметры которого устанавливаются через свойство collider. Координаты управляемого + * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида, + * задаваемого параметром objectZPosition. + * + *

Команда ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях + * происходит движение вверх. + */ + public class WalkController extends ObjectController { + /** + * Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z, + * при отрицательном — по оси Z. + */ + public var gravity:Number = 0; + /** + * Вертикальная скорость прыжка. + */ + public var jumpSpeed:Number = 0; + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + private var _groundMesh:Mesh; + /** + * Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности + * скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + + // Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации. + private var _airControlCoefficient:Number = 1; + + private var _currentSpeed:Number = 0; + + private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70)); + + private var destination:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + + private var _objectZPosition:Number = 0.5; + private var _flyMode:Boolean; + private var _onGround:Boolean; + + private var velocity:Point3D = new Point3D(); + private var tmpVelocity:Point3D = new Point3D(); + + private var controlsActive:Boolean; + + private var inJump:Boolean; + private var startRotX:Number; + private var startRotZ:Number; + + // Координаты мышиного курсора в режиме mouse look в предыдущем кадре. + private var prevMouseCoords:Point3D = new Point3D(); + // Текущие координаты мышиного курсора в режиме mouse look. + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * @inheritDoc + */ + public function WalkController(eventSourceObject:DisplayObject) { + super(eventSourceObject); + } + + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + public function get groundMesh():Mesh { + return _groundMesh; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + + /** + * Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации. + * Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при + * нахождении на поверхности. + * + * @default 1 + */ + public function get airControlCoefficient():Number { + return _airControlCoefficient; + } + + /** + * @private + */ + public function set airControlCoefficient(value:Number):void { + _airControlCoefficient = value > 0 ? value : -value; + } + + /** + * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте + * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство + * onGround будет иметь значение false. + * + * @see #onGround + */ + public function get maxGroundAngle():Number { + return Math.acos(minGroundCos); + } + + /** + * @private + */ + public function set maxGroundAngle(value:Number):void { + minGroundCos = Math.cos(value); + } + + /** + * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида, + * значение 1 -- положение в верхней точке эллипсоида. + */ + public function get objectZPosition():Number { + return _objectZPosition; + } + + /** + * @private + */ + public function set objectZPosition(value:Number):void { + _objectZPosition = value; + setObjectCoords(); + } + + /** + * Включене и выключение режима полёта. + * + * @default false + */ + public function get flyMode():Boolean { + return _flyMode; + } + + /** + * @private + */ + public function set flyMode(value:Boolean):void { + _flyMode = value; + } + + /** + * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности, + * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения. + * + * @see #maxGroundAngle + */ + public function get onGround():Boolean { + return _onGround; + } + + /** + * Модуль текущей скорости. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен + * отклонением на 90 градусов от горизонтали. + * + * @param frameTime длительность текущего кадра в секундах + */ + override protected function rotateObject(frameTime:Number):void { + // Mouse look + var rotX:Number; + if (_mouseLookActive) { + prevMouseCoords.copy(currentMouseCoords); + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!prevMouseCoords.equals(currentMouseCoords)) { + _object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX; + rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY; + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90; + } else { + _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX; + } + } + } + + // Повороты влево-вправо + if (_yawLeft) { + _object.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _object.rotationZ -= _yawSpeed * frameTime; + } + // Взгляд вверх-вниз + rotX = NaN; + if (_pitchUp) { + rotX = _object.rotationX + _pitchSpeed * frameTime; + } else if (_pitchDown) { + rotX = _object.rotationX - _pitchSpeed * frameTime; + } + if (!isNaN(rotX)) { + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX; + } else { + _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX; + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + var cos:Number = 0; + if (checkCollisions && !_flyMode) { + // Проверка наличия под ногами поверхности + displacement.x = 0; + displacement.y = 0; + displacement.z = - 0.5 * gravity * frameTime * frameTime; + if (_collider.getCollision(_coords, displacement, collision)) { + cos = collision.normal.z; + _groundMesh = collision.face._mesh; + } else { + _groundMesh = null; + } + } + _onGround = cos > minGroundCos; + + if (_onGround && inJump) { + inJump = false; + } + + var len:Number; + var x:Number; + var y:Number; + var z:Number; + + // При наличии управляющих воздействий расчитывается приращение скорости + controlsActive = _forward || _back || _right || _left || _up || _down; + if (controlsActive) { + if (_flyMode) { + tmpVelocity.x = 0; + tmpVelocity.y = 0; + tmpVelocity.z = 0; + // Режим полёта, ускорения направлены вдоль локальных осей + // Ускорение вперёд-назад + if (_forward) { + tmpVelocity.y = 1; + } else if (_back) { + tmpVelocity.y = -1; + } + // Ускорение влево-вправо + if (_right) { + tmpVelocity.x = 1; + } else if (_left) { + tmpVelocity.x = -1; + } + // Ускорение вверх-вниз + if (_up) { + tmpVelocity.z = 1; + } else if (_down) { + tmpVelocity.z = -1; + } + var matrix:Matrix3D = _object._transformation; + x = tmpVelocity.x; + if (_object is Camera3D) { + y = -tmpVelocity.z; + z = tmpVelocity.y; + } else { + y = tmpVelocity.y; + z = tmpVelocity.z; + } + // Поворот вектора из локальной системы координат объекта в глобальную + velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed; + velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed; + velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed; + } else { + + // Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z + var heading:Number = _object.rotationZ; + var headingCos:Number = Math.cos(heading); + var headingSin:Number = Math.sin(heading); + + var spd:Number = _speed; + if (gravity != 0 && !_onGround) { + spd *= _airControlCoefficient; + } + + // Вперёд-назад + if (_forward) { + velocity.x -= spd * headingSin; + velocity.y += spd * headingCos; + } else if (_back) { + velocity.x += spd * headingSin; + velocity.y -= spd * headingCos; + } + // Влево-вправо + if (_right) { + velocity.x += spd * headingCos; + velocity.y += spd * headingSin; + } else if (_left) { + velocity.x -= spd * headingCos; + velocity.y -= spd * headingSin; + } + if (gravity == 0) { + // Ускорение вверх-вниз + if (_up) { + velocity.z += _speed; + } else if (_down) { + velocity.z -= _speed; + } + } + } + } else { + // Управление неактивно, замедление движения + len = 1 / Math.pow(3, frameTime * 10); + if (_flyMode || gravity == 0) { + velocity.x *= len; + velocity.y *= len; + velocity.z *= len; + } else { + if (_onGround) { + velocity.x *= len; + velocity.y *= len; + if (velocity.z < 0) { + velocity.z *= len; + } + } else { + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + } + } + // Прыжок + if (_onGround && _up && !inJump) { + velocity.z = jumpSpeed; + inJump = true; + _onGround = false; + cos = 0; + } + // В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности + if (!(_flyMode || _onGround)) { + velocity.z -= gravity * frameTime; + } + + // Ограничение скорости + var max:Number = _accelerate ? _speed * _speedMultiplier : _speed; + if (_flyMode || gravity == 0) { + len = velocity.length; + if (len > max) { + velocity.length = max; + } + } else { + len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y); + if (len > max) { + velocity.x *= max / len; + velocity.y *= max / len; + } + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + + // Cмещение за кадр + displacement.x = velocity.x * frameTime; + displacement.y = velocity.y * frameTime; + displacement.z = velocity.z * frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x / frameTime; + velocity.y = displacement.y / frameTime; + velocity.z = displacement.z / frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Метод устанавливает координаты управляемого объекта в зависимости от параметра objectZPosition. + * + * @see #objectZPosition + */ + override protected function setObjectCoords():void { + if (_object != null) { + _object.x = _coords.x; + _object.y = _coords.y; + _object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ; + } + } + + /** + * Метод выполняет необходимые действия при включении вращения объекта мышью. + */ + override protected function startMouseLook():void { + super.startMouseLook(); + startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX; + startRotZ = _object.rotationZ; + } + + /** + * @inheritDoc + */ + override public function set enabled(value:Boolean):void { + super.enabled = value; + if (!value) { + velocity.reset(); + _currentSpeed = 0; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as new file mode 100644 index 0000000..1032a99 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as @@ -0,0 +1,77 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public final class BSPNode { + + // Тип примитива + alternativa3d var sprited:Boolean; + + // Родительская нода + alternativa3d var parent:BSPNode; + + // Дочерние ветки + alternativa3d var front:BSPNode; + alternativa3d var back:BSPNode; + + // Нормаль плоскости ноды + alternativa3d var normal:Point3D = new Point3D(); + + // Смещение плоскости примитива + alternativa3d var offset:Number; + + // Минимальная мобильность ноды + alternativa3d var mobility:int = int.MAX_VALUE; + + // Набор примитивов в ноде + alternativa3d var primitive:PolyPrimitive; + alternativa3d var backPrimitives:Set; + alternativa3d var frontPrimitives:Set; + + // Хранилище неиспользуемых нод + static private var collector:Array = new Array(); + + // Создать ноду на основе примитива + static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode { + var node:BSPNode; + if ((node = collector.pop()) == null) { + node = new BSPNode(); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + // Сохраняем ноду + primitive.node = node; + // Если это спрайтовый примитив + if (primitive.face == null) { + node.normal.x = 0; + node.normal.y = 0; + node.normal.z = 0; + node.offset = 0; + node.sprited = true; + } else { + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + node.sprited = false; + } + // Сохраняем мобильность + node.mobility = primitive.mobility; + return node; + } + + // Удалить ноду, все ссылки должны быть почищены + static alternativa3d function destroyBSPNode(node:BSPNode):void { + //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives); + collector.push(node); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..65fac09 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,1269 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + import alternativa.engine3d.display.View; + import alternativa.engine3d.materials.DrawPoint; + import alternativa.engine3d.materials.SpriteMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Камера для отображения 3D-сцены на экране. + * + *

Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе + * координат родителя. + * + *

Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода — + * экземпляр класса alternativa.engine3d.display.View. + * + * @see alternativa.engine3d.display.View + */ + public class Camera3D extends Object3D { + + /** + * @private + * Расчёт матрицы пространства камеры + */ + alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX); + /** + * @private + * Расчёт плоскостей отсечения + */ + alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES); + /** + * @private + * Отрисовка + */ + alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Поле зрения + */ + alternativa3d var _fov:Number = Math.PI/2; + /** + * @private + * Фокусное расстояние + */ + alternativa3d var focalLength:Number; + /** + * @private + * Перспективное искажение + */ + alternativa3d var focalDistortion:Number; + + /** + * @private + * Флаги рассчитанности UV-матриц + */ + alternativa3d var uvMatricesCalculated:Set = new Set(true); + + // Всмомогательные точки для расчёта UV-матриц + private var textureA:Point3D = new Point3D(); + private var textureB:Point3D = new Point3D(); + private var textureC:Point3D = new Point3D(); + + /** + * @private + * Вид из камеры + */ + alternativa3d var _view:View; + + /** + * @private + * Режим отрисовки + */ + alternativa3d var _orthographic:Boolean = false; + private var fullDraw:Boolean; + + /** + * @private + * Масштаб + */ + alternativa3d var _zoom:Number = 1; + + // Синус половинчатого угла обзора камеры + private var viewAngle:Number; + + /** + * @private + * Направление камеры + */ + private var direction:Point3D = new Point3D(0, 0, 1); + + /** + * @private + * Обратная трансформация камеры + */ + alternativa3d var cameraMatrix:Matrix3D = new Matrix3D(); + + // Скины + private var firstSkin:Skin; + private var prevSkin:Skin; + private var currentSkin:Skin; + + // Плоскости отсечения + private var leftPlane:Point3D = new Point3D(); + private var rightPlane:Point3D = new Point3D(); + private var topPlane:Point3D = new Point3D(); + private var bottomPlane:Point3D = new Point3D(); + private var farPlane:Point3D = new Point3D(); + private var leftOffset:Number; + private var rightOffset:Number; + private var topOffset:Number; + private var bottomOffset:Number; + private var nearOffset:Number; + private var farOffset:Number; + + // Вспомогательные массивы точек для отрисовки + private var points1:Array = new Array(); + private var points2:Array = new Array(); + private var spritePoint:Point3D = new Point3D(); + // Массив для сортировки спрайтов + private var spritePrimitives:Array = new Array(); + + /** + * @private + */ + alternativa3d var _nearClippingDistance:Number = 10; + /** + * @private + */ + alternativa3d var _farClippingDistance:Number = 1000; + /** + * @private + */ + alternativa3d var _nearClipping:Boolean = false; + /** + * @private + */ + alternativa3d var _farClipping:Boolean = false; + /** + * @private + */ + alternativa3d var _viewClipping:Boolean = true; + + /** + * Создание нового экземпляра камеры. + * + * @param name имя экземпляра + */ + public function Camera3D(name:String = null) { + super(name); + } + + /** + * @private + */ + private function calculateMatrix():void { + // Расчёт матрицы пространства камеры + cameraMatrix.copy(_transformation); + cameraMatrix.invert(); + if (_orthographic) { + cameraMatrix.scale(_zoom, _zoom, _zoom); + } + // Направление камеры + direction.x = _transformation.c; + direction.y = _transformation.g; + direction.z = _transformation.k; + direction.normalize(); + } + + /** + * @private + * Расчёт плоскостей отсечения + */ + private function calculatePlanes():void { + var halfWidth:Number = _view._width*0.5; + var halfHeight:Number = _view._height*0.5; + + var aw:Number = _transformation.a*halfWidth; + var ew:Number = _transformation.e*halfWidth; + var iw:Number = _transformation.i*halfWidth; + var bh:Number = _transformation.b*halfHeight; + var fh:Number = _transformation.f*halfHeight; + var jh:Number = _transformation.j*halfHeight; + if (_orthographic) { + if (_viewClipping) { + // Расчёт плоскостей отсечения в изометрии + aw /= _zoom; + ew /= _zoom; + iw /= _zoom; + bh /= _zoom; + fh /= _zoom; + jh /= _zoom; + + // Левая плоскость + leftPlane.x = _transformation.f*_transformation.k - _transformation.j*_transformation.g; + leftPlane.y = _transformation.j*_transformation.c - _transformation.b*_transformation.k; + leftPlane.z = _transformation.b*_transformation.g - _transformation.f*_transformation.c; + leftOffset = (_transformation.d - aw)*leftPlane.x + (_transformation.h - ew)*leftPlane.y + (_transformation.l - iw)*leftPlane.z; + + // Правая плоскость + rightPlane.x = -leftPlane.x; + rightPlane.y = -leftPlane.y; + rightPlane.z = -leftPlane.z; + rightOffset = (_transformation.d + aw)*rightPlane.x + (_transformation.h + ew)*rightPlane.y + (_transformation.l + iw)*rightPlane.z; + + // Верхняя плоскость + topPlane.x = _transformation.g*_transformation.i - _transformation.k*_transformation.e; + topPlane.y = _transformation.k*_transformation.a - _transformation.c*_transformation.i; + topPlane.z = _transformation.c*_transformation.e - _transformation.g*_transformation.a; + topOffset = (_transformation.d - bh)*topPlane.x + (_transformation.h - fh)*topPlane.y + (_transformation.l - jh)*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = -topPlane.x; + bottomPlane.y = -topPlane.y; + bottomPlane.z = -topPlane.z; + bottomOffset = (_transformation.d + bh)*bottomPlane.x + (_transformation.h + fh)*bottomPlane.y + (_transformation.l + jh)*bottomPlane.z; + } + } else { + // Вычисляем расстояние фокуса + focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov); + // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции + focalDistortion = 1/(focalLength*focalLength); + + if (_viewClipping) { + // Расчёт плоскостей отсечения в перспективе + var cl:Number = _transformation.c*focalLength; + var gl:Number = _transformation.g*focalLength; + var kl:Number = _transformation.k*focalLength; + + // Угловые вектора пирамиды видимости + var leftTopX:Number = -aw - bh + cl; + var leftTopY:Number = -ew - fh + gl; + var leftTopZ:Number = -iw - jh + kl; + var rightTopX:Number = aw - bh + cl; + var rightTopY:Number = ew - fh + gl; + var rightTopZ:Number = iw - jh + kl; + var leftBottomX:Number = -aw + bh + cl; + var leftBottomY:Number = -ew + fh + gl; + var leftBottomZ:Number = -iw + jh + kl; + var rightBottomX:Number = aw + bh + cl; + var rightBottomY:Number = ew + fh + gl; + var rightBottomZ:Number = iw + jh + kl; + + // Левая плоскость + leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY; + leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ; + leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX; + leftOffset = _transformation.d*leftPlane.x + _transformation.h*leftPlane.y + _transformation.l*leftPlane.z; + + // Правая плоскость + rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY; + rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ; + rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX; + rightOffset = _transformation.d*rightPlane.x + _transformation.h*rightPlane.y + _transformation.l*rightPlane.z; + + // Верхняя плоскость + topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY; + topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ; + topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX; + topOffset = _transformation.d*topPlane.x + _transformation.h*topPlane.y + _transformation.l*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY; + bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ; + bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX; + bottomOffset = _transformation.d*bottomPlane.x + _transformation.h*bottomPlane.y + _transformation.l*bottomPlane.z; + + + // Расчёт угла конуса + var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ); + leftTopX /= length; + leftTopY /= length; + leftTopZ /= length; + length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ); + rightTopX /= length; + rightTopY /= length; + rightTopZ /= length; + length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ); + leftBottomX /= length; + leftBottomY /= length; + leftBottomZ /= length; + length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ); + rightBottomX /= length; + rightBottomY /= length; + rightBottomZ /= length; + + viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z; + var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + + viewAngle = Math.sin(Math.acos(viewAngle)); + } else { + viewAngle = 1; + } + } + + var x:Number; + var y:Number; + var z:Number; + var k:Number; + + if (_nearClipping) { + if (_orthographic) { + k = _nearClippingDistance / _zoom; + x = _transformation.c*k + _transformation.d; + y = _transformation.g*k + _transformation.h; + z = _transformation.k*k + _transformation.l; + } else { + x = _transformation.c*_nearClippingDistance + _transformation.d; + y = _transformation.g*_nearClippingDistance + _transformation.h; + z = _transformation.k*_nearClippingDistance + _transformation.l; + } + nearOffset = direction.x*x + direction.y*y + direction.z*z; + } + + if (_farClipping) { + if (_orthographic) { + k = _farClippingDistance / _zoom; + x = _transformation.c*k + _transformation.d; + y = _transformation.g*k + _transformation.h; + z = _transformation.k*k + _transformation.l; + } else { + x = _transformation.c*_farClippingDistance + _transformation.d; + y = _transformation.g*_farClippingDistance + _transformation.h; + z = _transformation.k*_farClippingDistance + _transformation.l; + } + + farPlane.x = -direction.x; + farPlane.y = -direction.y; + farPlane.z = -direction.z; + farOffset = farPlane.x*x + farPlane.y*y + farPlane.z*z; + } + } + + /** + * @private + */ + private function render():void { + // Режим отрисовки + fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued); + + // Очистка рассчитанных текстурных матриц + uvMatricesCalculated.clear(); + + // Отрисовка + prevSkin = null; + currentSkin = firstSkin; + renderBSPNode(_scene.bsp); + + // Удаление ненужных скинов + while (currentSkin != null) { + removeCurrentSkin(); + } + + if (_view._interactive) { + _view.checkMouseOverOut(true); + } + } + + /** + * @private + */ + private function renderBSPNode(node:BSPNode):void { + if (node != null) { + if (node.sprited) { + // Спрайтовая нода + if (node.primitive != null) { + drawSpriteSkin(node.primitive as SpritePrimitive); + } else { + drawSpritePrimitives(node.frontPrimitives); + } + } else { + var primitive:*; + var normal:Point3D = node.normal; + var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z; + var cameraOffset:Number; + if (!_orthographic) { + cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset; + } + if (node.primitive != null) { + // В ноде только базовый примитив + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + drawSkin(node.primitive); + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + } + renderBSPNode(node.back); + } + } else { + // В ноде несколько примитивов + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + for (primitive in node.frontPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.front); + } else { + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + for (primitive in node.backPrimitives) { + drawSkin(primitive); + } + } + renderBSPNode(node.back); + } + } + } + } + } + + /** + * @private + * Функция сортирует список спрайтовых примитивов по удаленности и отправляет на отрисовку. + * + * @param primitives список примитивов для отрисовки + */ + private function drawSpritePrimitives(primitives:Set):void { + var primitive:SpritePrimitive; + var counter:int = -1; + for (var p:* in primitives) { + primitive = p; + var point:Point3D = primitive.sprite.globalCoords; + var z:Number = cameraMatrix.i*point.x + cameraMatrix.j*point.y + cameraMatrix.k*point.z + cameraMatrix.l; + primitive.screenDepth = z; + //if (z > 0) { + spritePrimitives[++counter] = primitive; + //} + } + if (counter > 0) { + sortSpritePrimitives(0, counter); + } + for (var i:int = counter; i >= 0; i--) { + drawSpriteSkin(spritePrimitives[i]); + } + } + + /** + * @private + * Сортировка примитивов по удаленности. + * + * @param l начальный индекс + * @param r конечный индекс + */ + private function sortSpritePrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:SpritePrimitive; + var mid:Number = spritePrimitives[(r + l) >> 1].screenDepth; + var right:SpritePrimitive; + do { + while ((left = spritePrimitives[i]).screenDepth < mid) {i++}; + while (mid < (right = spritePrimitives[j]).screenDepth) {j--}; + if (i <= j) { + spritePrimitives[i++] = right; + spritePrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortSpritePrimitives(l, j); + } + if (i < r) { + sortSpritePrimitives(i, r); + } + } + + /** + * @private + * Отрисовка скина спрайтового примитива. + * + * @param primitive спрайтовый примитив + */ + private function drawSpriteSkin(primitive:SpritePrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + var sprite:Sprite3D = primitive.sprite; + + var material:SpriteMaterial = sprite._material; + if (material == null) { + return; + } + + if (!material.canDraw(this)) { + return; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin); + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Скин текущего примитива дальше по списку скинов + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + + } + } + } + + /** + * @private + * Отрисовка скина примитива + */ + private function drawSkin(primitive:PolyPrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Проверка поверхности + var surface:Surface = primitive.face._surface; + if (surface == null) { + return; + } + // Проверка материала + var material:SurfaceMaterial = surface._material; + if (material == null || !material.canDraw(primitive)) { + return; + } + // Отсечение выходящих за окно просмотра частей + var i:uint; + var length:uint = primitive.num; + var primitivePoint:Point3D; + var primitiveUV:Point; + var point:DrawPoint; + var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase; + if (useUV) { + // Формируем список точек и UV-координат полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + primitiveUV = primitive.uvs[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + point.u = primitiveUV.x; + point.v = primitiveUV.y; + } + } + } else { + // Формируем список точек полигона + for (i = 0; i < length; i++) { + primitivePoint = primitive.points[i]; + point = points1[i]; + if (point == null) { + points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z); + } else { + point.x = primitivePoint.x; + point.y = primitivePoint.y; + point.z = primitivePoint.z; + } + } + } + + if (_viewClipping) { + // Отсечение по левой стороне + length = clip(length, points1, points2, leftPlane, leftOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по правой стороне + length = clip(length, points2, points1, rightPlane, rightOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по верхней стороне + length = clip(length, points1, points2, topPlane, topOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по нижней стороне + length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV); + if (length < 3) { + return; + } + } + + var tmp:Array; + if (_farClipping && _nearClipping) { + // Отсечение по дальней плоскости + length = clip(length, points1, points2, farPlane, farOffset, useUV); + if (length < 3) { + return; + } + // Отсечение по ближней плоскости + length = clip(length, points2, points1, direction, nearOffset, useUV); + if (length < 3) { + return; + } + } else if (_farClipping) { + // Отсечение по дальней плоскости + length = clip(length, points1, points2, farPlane, farOffset, useUV); + if (length < 3) { + return; + } + tmp = points1; + points1 = points2; + points2 = tmp; + } else if (_nearClipping) { + // Отсечение по ближней плоскости + length = clip(length, points1, points2, direction, nearOffset, useUV); + if (length < 3) { + return; + } + tmp = points1; + points1 = points2; + points2 = tmp; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Переводим координаты в систему камеры + var x:Number; + var y:Number; + var z:Number; + for (i = 0; i < length; i++) { + point = points1[i]; + x = point.x; + y = point.y; + z = point.z; + point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin, length, points1); + + // Переключаемся на следующий скин + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + + } else { + // Скин текущего примитива дальше по списку скинов + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + + } + } + } + + /** + * @private + * Отсечение полигона плоскостью. + */ + private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint { + var i:uint; + var k:Number; + var index:uint = 0; + var point:DrawPoint; + var point1:DrawPoint; + var point2:DrawPoint; + var offset1:Number; + var offset2:Number; + + point1 = points1[length - 1]; + offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset; + + if (calculateUV) { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + point.u = point2.u; + point.v = point2.v; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + point.u = point2.u - (point2.u - point1.u)*k; + point.v = point2.v - (point2.v - point1.v)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + + } else { + + for (i = 0; i < length; i++) { + + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x, point2.y, point2.z); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + } + return index; + } + + /** + * @private + * Добавление текущего скина. + */ + private function addCurrentSkin():void { + currentSkin = Skin.createSkin(); + _view.canvas.addChild(currentSkin); + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + * Вставляем под текущий скин. + */ + private function insertCurrentSkin():void { + var skin:Skin = Skin.createSkin(); + _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin)); + skin.nextSkin = currentSkin; + if (prevSkin == null) { + firstSkin = skin; + } else { + prevSkin.nextSkin = skin; + } + currentSkin = skin; + } + + /** + * @private + * Удаляет текущий скин. + */ + private function removeCurrentSkin():void { + // Сохраняем следующий + var next:Skin = currentSkin.nextSkin; + // Удаляем из канваса + _view.canvas.removeChild(currentSkin); + // Очистка скина + if (currentSkin.material != null) { + currentSkin.material.clear(currentSkin); + } + // Зачищаем ссылки + currentSkin.nextSkin = null; + currentSkin.primitive = null; + currentSkin.material = null; + // Удаляем + Skin.destroySkin(currentSkin); + // Следующий устанавливаем текущим + currentSkin = next; + // Устанавливаем связь от предыдущего скина + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + */ + alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void { + + // Расчёт точек базового примитива в координатах камеры + var point:Point3D = face.primitive.points[0]; + textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[1]; + textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[2]; + textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + + // Находим AB и AC + var abx:Number = textureB.x - textureA.x; + var aby:Number = textureB.y - textureA.y; + var acx:Number = textureC.x - textureA.x; + var acy:Number = textureC.y - textureA.y; + + // Расчёт текстурной матрицы + var uvMatrixBase:Matrix = face.uvMatrixBase; + var uvMatrix:Matrix = face.uvMatrix; + uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width; + uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width; + uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height; + uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height; + uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d; + uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h; + + // Помечаем, как рассчитанную + uvMatricesCalculated[face] = true; + } + + /** + * Поле вывода, в котором происходит отрисовка камеры. + */ + public function get view():View { + return _view; + } + + /** + * @private + */ + public function set view(value:View):void { + if (value != _view) { + if (_view != null) { + _view.camera = null; + } + if (value != null) { + value.camera = this; + } + } + } + + /** + * Включение режима аксонометрической проекции. + * + * @default false + */ + public function get orthographic():Boolean { + return _orthographic; + } + + /** + * @private + */ + public function set orthographic(value:Boolean):void { + if (_orthographic != value) { + // Отправляем сигнал об изменении типа камеры + addOperationToScene(calculateMatrixOperation); + // Сохраняем новое значение + _orthographic = value; + } + } + + /** + * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние + * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода. + * Угол зрения ограничен диапазоном 0-180 градусов. + */ + public function get fov():Number { + return _fov; + } + + /** + * @private + */ + public function set fov(value:Number):void { + value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value); + if (_fov != value) { + // Если перспектива + if (!_orthographic) { + // Отправляем сигнал об изменении плоскостей отсечения + addOperationToScene(calculatePlanesOperation); + } + // Сохраняем новое значение + _fov = value; + } + } + + /** + * Коэффициент увеличения изображения в режиме аксонометрической проекции. + */ + public function get zoom():Number { + return _zoom; + } + + /** + * @private + */ + public function set zoom(value:Number):void { + value = (value < 0) ? 0 : value; + if (_zoom != value) { + // Если изометрия + if (_orthographic) { + // Отправляем сигнал об изменении zoom + addOperationToScene(calculateMatrixOperation); + } + // Сохраняем новое значение + _zoom = value; + } + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + if (_view != null) { + // Отправляем операцию расчёта плоскостей отсечения + scene.addOperation(calculatePlanesOperation); + // Подписываемся на сигналы сцены + scene.changePrimitivesOperation.addSequel(renderOperation); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + super.removeFromScene(scene); + + // Удаляем все операции из очереди + scene.removeOperation(calculateMatrixOperation); + scene.removeOperation(calculatePlanesOperation); + scene.removeOperation(renderOperation); + + if (_view != null) { + // Отписываемся от сигналов сцены + scene.changePrimitivesOperation.removeSequel(renderOperation); + } + } + + /** + * @private + */ + alternativa3d function addToView(view:View):void { + // Сохраняем первый скин + firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null; + + // Подписка на свои операции + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.addSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.addSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.addSequel(renderOperation); + + if (_scene != null) { + // Отправляем сигнал перерисовки + _scene.addOperation(calculateMatrixOperation); + // Подписываемся на сигналы сцены + _scene.changePrimitivesOperation.addSequel(renderOperation); + } + + // Сохраняем вид + _view = view; + } + + /** + * @private + */ + alternativa3d function removeFromView(view:View):void { + // Сброс ссылки на первый скин + firstSkin = null; + + // Отписка от своих операций + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.removeSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.removeSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.removeSequel(renderOperation); + + if (_scene != null) { + // Удаляем все операции из очереди + _scene.removeOperation(calculateMatrixOperation); + _scene.removeOperation(calculatePlanesOperation); + _scene.removeOperation(renderOperation); + // Отписываемся от сигналов сцены + _scene.changePrimitivesOperation.removeSequel(renderOperation); + } + // Удаляем ссылку на вид + _view = null; + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "camera" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Camera3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Camera3D = Camera3D(source); + orthographic = src._orthographic; + zoom = src._zoom; + fov = src._fov; + } + + /** + * Определяет, выполняется ли отсечение по ближней плоскости. Если задано значение true, объекты или их части, имеющие в камере + * координату Z меньше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется. + * + * @see #farClipping + * @see #viewClipping + * @see #nearClippingDistance + * + * @default false + */ + public function get nearClipping():Boolean { + return _nearClipping; + } + + /** + * @private + */ + public function set nearClipping(value:Boolean):void { + if (_nearClipping != value) { + _nearClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Расстояние до ближней плоскости отсечения в системе координат камеры. При задании отрицательного значения свойству присваивается ноль. + * + * @see #nearClipping + * + * @default 10 + */ + public function get nearClippingDistance():Number { + return _nearClippingDistance; + } + + /** + * @private + */ + public function set nearClippingDistance(value:Number):void { + if (value < 0) { + value = 0; + } + if (_nearClippingDistance != value) { + _nearClippingDistance = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Определяет, выполняется ли отсечение по дальней плоскости. Если задано значение true, объекты или их части, имеющие в камере + * координату Z больше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется. + * + * @see #nearClipping + * @see #viewClipping + * @see #farClippingDistance + * + * @default false + */ + public function get farClipping():Boolean { + return _farClipping; + } + + /** + * @private + */ + public function set farClipping(value:Boolean):void { + if (_farClipping != value) { + _farClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Расстояние до дальней плоскости отсечения в системе координат камеры. При задании отрицательного значения свойству присваивается ноль. + * + * @see #farClipping + * + * @default 1000 + */ + public function get farClippingDistance():Number { + return _farClippingDistance; + } + + /** + * @private + */ + public function set farClippingDistance(value:Number):void { + if (value < 0) { + value = 0; + } + if (_farClippingDistance != value) { + _farClippingDistance = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Определяет, выполняется ли отсечение по пирамиде видимости. Если задано значение true, выполняется отсечение объектов сцены по + * пирамиде видимости. Если задано значение false, отсечение не выполняется, тем самым ускоряя отрисовку. Такой режим полезен, когда + * заранее известно, что вся сцена находится в пирамиде видимости. Не следует отключать отсечение по пирамиде видимости одновременно с + * отсечением по ближней плоскости, если позади камеры находятся объекты или части объектов, т.к. это приведёт к артефактам отрисовки, + * а при использовании текстурных материалов и к зацикливанию адаптивной триангуляции. + * + * @see #nearClipping + * @see #farClipping + * + * @default true + */ + public function get viewClipping():Boolean { + return _viewClipping; + } + + /** + * @private + */ + public function set viewClipping(value:Boolean):void { + if (_viewClipping != value) { + _viewClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..de8516d --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as @@ -0,0 +1,1078 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же гранью. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на грань. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с грани. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань + * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения + * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается + * матрица трансформации текстуры. + * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие. + */ + final public class Face implements IEventDispatcher { + // Погрешность определения вырожденной UV матрицы + private static const uvThreshold:Number = 1.0/2880; + // Операции + /** + * @private + * Расчёт глобальной нормали плоскости грани. + */ + alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL); + /** + * @private + * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP). + */ + alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV); + /** + * @private + * Обновление примитива в сцене. + */ + alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE); + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL); + /** + * @private + * Расчёт UV для фрагментов (выполняется после трансформации, если её не было). + */ + alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Вершины грани + */ + alternativa3d var _vertices:Array; + /** + * @private + * Количество вершин + */ + alternativa3d var _verticesCount:uint; + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + // UV-координаты + /** + * @private + */ + alternativa3d var _aUV:Point; + /** + * @private + */ + alternativa3d var _bUV:Point; + /** + * @private + */ + alternativa3d var _cUV:Point; + + /** + * @private + * Коэффициенты базовой UV-матрицы + */ + alternativa3d var uvMatrixBase:Matrix; + + /** + * @private + * UV Матрица перевода текстурных координат в изометрическую камеру. + */ + alternativa3d var uvMatrix:Matrix; + /** + * @private + * Нормаль плоскости + */ + alternativa3d var globalNormal:Point3D = new Point3D(); + /** + * @private + * Смещение плоскости + */ + alternativa3d var globalOffset:Number; + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра грани. + * + * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в + * порядке обхода лицевой стороны грани против часовой стрелки. + * + * @see Vertex + */ + public function Face(vertices:Array) { + // Сохраняем вершины + _vertices = vertices; + _verticesCount = vertices.length; + + // Создаём оригинальный примитив + primitive = PolyPrimitive.createPolyPrimitive(); + primitive.face = this; + primitive.num = _verticesCount; + + // Обрабатываем вершины + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = vertices[i]; + // Добавляем координаты вершины в примитив + primitive.points.push(vertex.globalCoords); + // Добавляем пустые UV-координаты в примитив + primitive.uvs.push(null); + // Добавляем вершину в грань + vertex.addToFace(this); + } + + // Расчёт нормали + calculateNormalOperation.addSequel(updatePrimitiveOperation); + + // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку + calculateUVOperation.addSequel(calculateFragmentsUVOperation); + calculateUVOperation.addSequel(updateMaterialOperation); + } + + /** + * @private + * Расчёт нормали в глобальных координатах + */ + private function calculateNormal():void { + // Вектор AB + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.globalCoords; + vertex = _vertices[1]; + var bv:Point3D = vertex.globalCoords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + // Вектор AC + vertex = _vertices[2]; + var cv:Point3D = vertex.globalCoords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + // Перпендикуляр к плоскости + globalNormal.x = acz*aby - acy*abz; + globalNormal.y = acx*abz - acz*abx; + globalNormal.z = acy*abx - acx*aby; + // Нормализация перпендикуляра + globalNormal.normalize(); + } + + /** + * @private + * Расчитывает глобальное смещение плоскости грани. + * Помечает конечные примитивы на удаление, а базовый на добавление в сцене. + */ + private function updatePrimitive():void { + // Расчёт смещения + var vertex:Vertex = _vertices[0]; + globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z; + + removePrimitive(primitive); + primitive.mobility = _mesh.inheritedMobility; + _mesh._scene.addPrimitives.push(primitive); + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены + */ + private function removePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + removePrimitive(primitive.backFragment); + removePrimitive(primitive.frontFragment); + primitive.backFragment = null; + primitive.frontFragment = null; + if (primitive != this.primitive) { + primitive.parent = null; + primitive.sibling = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } + } else { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _mesh._scene.removeBSPPrimitive(primitive); + } + } + } + + /** + * @private + * Пометка на перерисовку фрагментов грани. + */ + private function updateMaterial():void { + if (!updatePrimitiveOperation.queued) { + changePrimitive(primitive); + } + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку + */ + private function changePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + changePrimitive(primitive.backFragment); + changePrimitive(primitive.frontFragment); + } else { + _mesh._scene.changedPrimitives[primitive] = true; + } + } + + /** + * @private + * Расчёт UV-матрицы на основании первых трёх UV-координат. + * Расчёт UV-координат для оставшихся точек. + */ + private function calculateUV():void { + var i:uint; + var uv:Point; + // Расчёт UV-матрицы + if (_aUV != null && _bUV != null && _cUV != null) { + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + var det:Number = abu*acv - abv*acu; + + // Проверка на нулевой определитель + if (det < uvThreshold && det > -uvThreshold) { + var len:Number; + if (abu < uvThreshold && abu > -uvThreshold && abv < uvThreshold && abv > -uvThreshold) { + if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) { + // Оба вырождены + abu = uvThreshold; + acv = uvThreshold; + } else { + // Вырожден AB + len = Math.sqrt(acu*acu + acv*acv); + abu = uvThreshold*acv/len; + abv = -uvThreshold*acu/len; + } + } else { + if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) { + //Вырожден AC + len = Math.sqrt(abu*abu + abv*abv); + acu = -uvThreshold*abv/len; + acv = uvThreshold*abu/len; + } else { + // Сонаправлены + len = Math.sqrt(abu*abu + abv*abv); + acu += uvThreshold*abv/len; + acv -= uvThreshold*abu/len; + } + } + + // Пересчитываем определитель + det = abu*acv - abv*acu; + + // Заполняем UV в базовом примитиве + primitive.uvs[0] = _aUV; + primitive.uvs[1] = new Point(_aUV.x + abu, _aUV.y + abv); + primitive.uvs[2] = new Point(_aUV.x + acu, _aUV.y + acv); + } else { + // Заполняем UV в базовом примитиве + primitive.uvs[0] = _aUV; + primitive.uvs[1] = _bUV; + primitive.uvs[2] = _cUV; + } + + // Создаём матрицу + if (uvMatrixBase == null) { + uvMatrixBase = new Matrix(); + uvMatrix = new Matrix(); + } + uvMatrixBase.a = acv/det; + uvMatrixBase.b = -abv/det; + uvMatrixBase.c = -acu/det; + uvMatrixBase.d = abu/det; + uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y); + uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y); + + // Расчёт недостающих UV + if (_verticesCount > 3) { + var a:Point3D = primitive.points[0]; + var b:Point3D = primitive.points[1]; + var c:Point3D = primitive.points[2]; + + var ab1:Number; + var ab2:Number; + var ac1:Number; + var ac2:Number; + var ad1:Number; + var ad2:Number; + var abk:Number; + var ack:Number; + + var point:Point3D; + + // Выбор наиболее подходящих осей для расчёта + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) { + if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось X + ab1 = b.y - a.y; + ab2 = b.z - a.z; + ac1 = c.y - a.y; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.y - a.y; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } else { + if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) { + // Ось Y + ab1 = b.x - a.x; + ab2 = b.z - a.z; + ac1 = c.x - a.x; + ac2 = c.z - a.z; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.z - a.z; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } else { + // Ось Z + ab1 = b.x - a.x; + ab2 = b.y - a.y; + ac1 = c.x - a.x; + ac2 = c.y - a.y; + det = ab1*ac2 - ac1*ab2; + for (i = 3; i < _verticesCount; i++) { + point = primitive.points[i]; + ad1 = point.x - a.x; + ad2 = point.y - a.y; + abk = (ad1*ac2 - ac1*ad2)/det; + ack = (ab1*ad2 - ad1*ab2)/det; + uv = primitive.uvs[i]; + if (uv == null) { + uv = new Point(); + primitive.uvs[i] = uv; + } + uv.x = _aUV.x + abu*abk + acu*ack; + uv.y = _aUV.y + abv*abk + acv*ack; + } + } + } + } + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + uvMatrix = null; + // Удаляем UV-координаты из базового примитива + for (i = 0; i < _verticesCount; i++) { + primitive.uvs[i] = null; + } + } + } + + /** + * @private + * Расчёт UV-координат для фрагментов примитива, если не было трансформации + */ + private function calculateFragmentsUV():void { + // Если в этом цикле не было трансформации + if (!updatePrimitiveOperation.queued) { + if (uvMatrixBase != null) { + // Рассчитываем UV в примитиве + calculatePrimitiveUV(primitive); + } else { + // Удаляем UV в примитиве + removePrimitiveUV(primitive); + } + } + } + + /** + * @private + * Расчёт UV для точек базового примитива. + * + * @param primitive + */ + private function calculatePrimitiveUV(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + var points:Array = primitive.points; + var backPoints:Array = primitive.backFragment.points; + var frontPoints:Array = primitive.frontFragment.points; + var uvs:Array = primitive.uvs; + var backUVs:Array = primitive.backFragment.uvs; + var frontUVs:Array = primitive.frontFragment.uvs; + var index1:uint = 0; + var index2:uint = 0; + var point:Point3D; + var uv:Point; + var uv1:Point; + var uv2:Point; + var t:Number; + var firstSplit:Boolean = true; + for (var i:uint = 0; i < primitive.num; i++) { + var split:Boolean = true; + point = points[i]; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + split = false; + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + split = false; + index1++; + } + + if (split) { + uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)]; + uv2 = uvs[i]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + firstSplit = false; + index2++; + index1++; + if (point == frontPoints[index2]) { + if (frontUVs[index2] == null) { + frontUVs[index2] = uvs[i]; + } + index2++; + } + if (point == backPoints[index1]) { + if (backUVs[index1] == null) { + backUVs[index1] = uvs[i]; + } + index1++; + } + } + } + // Проверяем рассечение последнего ребра + if (index2 < primitive.frontFragment.num) { + uv1 = uvs[primitive.num - 1]; + uv2 = uvs[0]; + t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2; + uv = frontUVs[index2]; + if (uv == null) { + uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t); + frontUVs[index2] = uv; + backUVs[index1] = uv; + } else { + uv.x = uv1.x + (uv2.x - uv1.x)*t; + uv.y = uv1.y + (uv2.y - uv1.y)*t; + } + } + + calculatePrimitiveUV(primitive.backFragment); + calculatePrimitiveUV(primitive.frontFragment); + } + } + + /** + * @private + * Удаление UV в примитиве и его фрагментах + * @param primitive + */ + private function removePrimitiveUV(primitive:PolyPrimitive):void { + // Очищаем список UV + for (var i:uint = 0; i < primitive.num; i++) { + primitive.uvs[i] = null; + } + // Если есть фрагменты, удаляем UV в них + if (primitive.backFragment != null) { + removePrimitiveUV(primitive.backFragment); + removePrimitiveUV(primitive.frontFragment); + } + } + + /** + * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex. + * + * @see Vertex + */ + public function get vertices():Array { + return new Array().concat(_vertices); + } + + /** + * Количество вершин грани. + */ + public function get verticesCount():uint { + return _verticesCount; + } + + /** + * Полигональный объект, которому принадлежит грань. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Поверхность, которой принадлежит грань. + */ + public function get surface():Surface { + return _surface; + } + + /** + * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор + * имеет значение null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getFaceId(this) : null; + } + + /** + * UV-координаты, соответствующие первой вершине грани. + */ + public function get aUV():Point { + return (_aUV != null) ? _aUV.clone() : null; + } + + /** + * UV-координаты, соответствующие второй вершине грани. + */ + public function get bUV():Point { + return (_bUV != null) ? _bUV.clone() : null; + } + + /** + * UV-координаты, соответствующие третьей вершине грани. + */ + public function get cUV():Point { + return (_cUV != null) ? _cUV.clone() : null; + } + + /** + * @private + */ + public function set aUV(value:Point):void { + if (_aUV != null) { + if (value != null) { + if (!_aUV.equals(value)) { + _aUV.x = value.x; + _aUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _aUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _aUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set bUV(value:Point):void { + if (_bUV != null) { + if (value != null) { + if (!_bUV.equals(value)) { + _bUV.x = value.x; + _bUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _bUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _bUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * @private + */ + public function set cUV(value:Point):void { + if (_cUV != null) { + if (value != null) { + if (!_cUV.equals(value)) { + _cUV.x = value.x; + _cUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + _cUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } else { + if (value != null) { + _cUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateUVOperation); + } + } + } + } + + /** + * Нормаль в локальной системе координат. + */ + public function get normal():Point3D { + var res:Point3D = new Point3D(); + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.coords; + vertex = _vertices[1]; + var bv:Point3D = vertex.coords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + vertex = _vertices[2]; + var cv:Point3D = vertex.coords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + res.x = acz*aby - acy*abz; + res.y = acx*abz - acz*abx; + res.z = acy*abx - acx*aby; + if (res.x != 0 || res.y != 0 || res.z != 0) { + var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z); + res.x /= k; + res.y /= k; + res.z /= k; + } + return res; + } + + /** + * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @return UV-координаты заданной точки + */ + public function getUV(point:Point3D):Point { + return getUVFast(point, normal); + } + + /** + * @private + * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта + * локальной нормали грани. Используется для оптимизации. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @param normal нормаль плоскости грани в локальной системе координат + * @return UV-координаты заданной точки + */ + alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point { + if (_aUV == null || _bUV == null || _cUV == null) { + return null; + } + + // Выбор наиболее длинной оси нормали + var dir:uint; + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) { + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 0; + } else { + dir = 2; + } + } else { + if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 1; + } else { + dir = 2; + } + } + + // Расчёт соотношения по векторам AB и AC + var v:Vertex = _vertices[0]; + var a:Point3D = v._coords; + v = _vertices[1]; + var b:Point3D = v._coords; + v = _vertices[2]; + var c:Point3D = v._coords; + + var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x); + var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z); + var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x); + var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z); + var det:Number = ab1*ac2 - ac1*ab2; + + var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x); + var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z); + var abk:Number = (ad1*ac2 - ac1*ad2)/det; + var ack:Number = (ab1*ad2 - ad1*ab2)/det; + + // Интерполяция по UV первых точек + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + + return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack); + } + + /** + * Множество граней, имеющих общие рёбра с текущей гранью. + */ + public function get edgeJoinedFaces():Set { + var res:Set = new Set(true); + // Перебираем точки грани + for (var i:uint = 0; i < _verticesCount; i++) { + var a:Vertex = _vertices[i]; + var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0]; + + // Перебираем грани текущей точки + for (var key:* in a._faces) { + var face:Face = key; + // Если это другая грань и у неё также есть следующая точка + if (face != this && face._vertices.indexOf(b) >= 0) { + // Значит у граней общее ребро + res[face] = true; + } + } + } + return res; + } + + /** + * @private + * Удаление всех вершин из грани. + * Очистка базового примитива. + */ + alternativa3d function removeVertices():void { + // Удалить вершины + for (var i:uint = 0; i < _verticesCount; i++) { + // Удаляем из списка + var vertex:Vertex = _vertices.pop(); + // Удаляем координаты вершины из примитива + primitive.points.pop(); + // Удаляем вершину из грани + vertex.removeFromFace(this); + } + // Обнуляем количество вершин + _verticesCount = 0; + } + + /** + * @private + * Добавление грани на сцену + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену рассчитываем плоскость и UV + scene.addOperation(calculateNormalOperation); + scene.addOperation(calculateUVOperation); + + // Подписываем сцену на операции + updatePrimitiveOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Удаление грани из сцены + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateUVOperation); + scene.removeOperation(calculateFragmentsUVOperation); + scene.removeOperation(calculateNormalOperation); + scene.removeOperation(updatePrimitiveOperation); + scene.removeOperation(updateMaterialOperation); + + // Удаляем примитивы из сцены + removePrimitive(primitive); + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Добавление грани в меш + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление грани из меша + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation); + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Добавление к поверхности + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Подписка поверхности на операции + surface.changeMaterialOperation.addSequel(updateMaterialOperation); + // Если при смене поверхности изменился материал + if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Сохранить поверхность + _surface = surface; + } + + /** + * @private + * Удаление из поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Отписка поверхности от операций + surface.changeMaterialOperation.removeSequel(updateMaterialOperation); + // Если был материал + if (surface._material != null) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Удалить ссылку на поверхность + _surface = null; + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : ""); + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = _vertices[i]; + res += vertex.id + ((i < _verticesCount - 1) ? ", " : ""); + } + res += "]"; + return res; + } + + /** + * Добавление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылка события. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверка наличия зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as new file mode 100644 index 0000000..b17d63d --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as @@ -0,0 +1,986 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNeedMoreVerticesError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.errors.SurfaceExistsError; + import alternativa.engine3d.errors.SurfaceNotFoundError; + import alternativa.engine3d.errors.VertexExistsError; + import alternativa.engine3d.errors.VertexNotFoundError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Map; + import alternativa.utils.ObjectUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект + * содержит в себе наборы вершин, граней и поверхностей. + */ + public class Mesh extends Object3D { + + // Инкремент количества объектов + private static var counter:uint = 0; + + // Инкременты для идентификаторов вершин, граней и поверхностей + private var vertexIDCounter:uint = 0; + private var faceIDCounter:uint = 0; + private var surfaceIDCounter:uint = 0; + + /** + * @private + * Список вершин + */ + alternativa3d var _vertices:Map = new Map(); + /** + * @private + * Список граней + */ + alternativa3d var _faces:Map = new Map(); + /** + * @private + * Список поверхностей + */ + alternativa3d var _surfaces:Map = new Map(); + + /** + * Создание экземпляра полигонального объекта. + * + * @param name имя экземпляра + */ + public function Mesh(name:String = null) { + super(name); + } + + /** + * Добавление новой вершины к объекту. + * + * @param x координата X в локальной системе координат объекта + * @param y координата Y в локальной системе координат объекта + * @param z координата Z в локальной системе координат объекта + * @param id идентификатор вершины. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной вершины + * + * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex { + // Проверяем ID + if (id != null) { + // Если уже есть вершина с таким ID + if (_vertices[id] != undefined) { + if (_vertices[id] is Vertex) { + throw new VertexExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный + while (_vertices[vertexIDCounter] != undefined) { + vertexIDCounter++; + } + id = vertexIDCounter; + } + + // Создаём вершину + var v:Vertex = new Vertex(x, y, z); + + // Добавляем вершину на сцену + if (_scene != null) { + v.addToScene(_scene); + } + + // Добавляем вершину в меш + v.addToMesh(this); + _vertices[id] = v; + + return v; + } + + /** + * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины + * + * @return экземпляр удалённой вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeVertex(vertex:Object):Vertex { + var byLink:Boolean = vertex is Vertex; + + // Проверяем на null + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + + // Проверяем наличие вершины в меше + if (byLink) { + // Если удаляем по ссылке + if (Vertex(vertex)._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + } else { + // Если удаляем по ID + if (_vertices[vertex] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertex, this); + } else if (!(_vertices[vertex] is Vertex)) { + // По этому id не вершина + throw new InvalidIDError(vertex, this); + } + } + + // Находим вершину и её ID + var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex]; + var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex; + + // Удаляем вершину из сцены + if (_scene != null) { + v.removeFromScene(_scene); + } + + // Удаляем вершину из меша + v.removeFromMesh(this); + delete _vertices[id]; + + return v; + } + + /** + * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная + * ни к одной поверхности. + * + * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой + * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex, + * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину. + * @param id идентификатор грани. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной грани + * + * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан + * null, либо количество вершин в массиве меньше трёх + * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива + * + * @see Vertex + */ + public function createFace(vertices:Array, id:Object = null):Face { + + // Проверяем на null + if (vertices == null) { + throw new FaceNeedMoreVerticesError(this); + } + + // Проверяем ID + if (id != null) { + // Если уже есть грань с таким ID + if (_faces[id] != undefined) { + if (_faces[id] is Face) { + throw new FaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_faces[faceIDCounter] != undefined) { + faceIDCounter++; + } + id = faceIDCounter; + } + + // Проверяем количество точек + var length:uint = vertices.length; + if (length < 3) { + throw new FaceNeedMoreVerticesError(this, length); + } + + // Проверяем и формируем список вершин + var v:Array = new Array(); + var vertex:Vertex; + for (var i:uint = 0; i < length; i++) { + if (vertices[i] is Vertex) { + // Если работаем со ссылками + vertex = vertices[i]; + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertices[i], this); + } + } else { + // Если работаем с ID + if (_vertices[vertices[i]] == null) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertices[i], this); + } else if (!(_vertices[vertices[i]] is Vertex)) { + // Если id зарезервировано + throw new InvalidIDError(vertices[i],this); + } + vertex = _vertices[vertices[i]]; + } + v.push(vertex); + } + + // Создаём грань + var f:Face = new Face(v); + + // Добавляем грань на сцену + if (_scene != null) { + f.addToScene(_scene); + } + + // Добавляем грань в меш + f.addToMesh(this); + _faces[id] = f; + + return f; + } + + /** + * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит. + * + * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани + * + * @return экземпляр удалённой грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeFace(face:Object):Face { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань и её ID + var f:Face = byLink ? Face(face) : _faces[face] ; + var id:Object = byLink ? getFaceId(Face(face)) : face; + + // Удаляем вершины из грани + f.removeVertices(); + + // Удаляем грань из поверхности + if (f._surface != null) { + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Удаляем грань из сцены + if (_scene != null) { + f.removeFromScene(_scene); + } + + // Удаляем грань из меша + f.removeFromMesh(this); + + delete _faces[id]; + + return f; + } + + /** + * Добавление новой поверхности к объекту. + * + * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса + * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать + * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если + * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность. + * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function createSurface(faces:Array = null, id:Object = null):Surface { + // Проверяем ID + if (id != null) { + // Если уже есть поверхность с таким ID + if (_surfaces[id] != undefined) { + if (_surfaces[id] is Surface) { + throw new SurfaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_surfaces[surfaceIDCounter] != undefined) { + surfaceIDCounter++; + } + id = surfaceIDCounter; + } + + // Создаём поверхность + var s:Surface = new Surface(); + + // Добавляем поверхность на сцену + if (_scene != null) { + s.addToScene(_scene); + } + + // Добавляем поверхность в меш + s.addToMesh(this); + _surfaces[id] = s; + + // Добавляем грани, если есть + if (faces != null) { + var length:uint = faces.length; + for (var i:uint = 0; i < length; i++) { + s.addFace(faces[i]); + } + } + + return s; + } + + /** + * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани. + * + * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности + * + * @return экземпляр удалённой поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeSurface(surface:Object):Surface { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если удаляем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если удаляем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность и её ID + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface; + + // Удаляем поверхность из сцены + if (_scene != null) { + s.removeFromScene(_scene); + } + + // Удаляем грани из поверхности + s.removeFaces(); + + // Удаляем поверхность из меша + s.removeFromMesh(this); + delete _surfaces[id]; + + return s; + } + + /** + * Добавление всех граней объекта в указанную поверхность. + * + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в + * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором, + * будет создана новая поверхность. + * + * @param removeSurfaces удалять или нет пустые поверхности после переноса граней + * + * @return экземпляр поверхности, в которую перенесены грани + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface { + var returnSurface:Surface; + var returnSurfaceId:Object; + if (surface is Surface) { + // Работаем с экземпляром Surface + if (surface._mesh == this) { + returnSurface = Surface(surface); + } else { + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Работаем с идентификатором + if (_surfaces[surface] == undefined) { + // Поверхности еще нет + returnSurface = createSurface(null, surface); + returnSurfaceId = surface; + } else { + if (_surfaces[surface] is Surface) { + returnSurface = _surfaces[surface]; + } else { + // _surfaces[surface] по идентификатору возвращает не Surface + throw new InvalidIDError(surface, this); + } + } + } + // Перемещаем все грани + for each (var face:Face in _faces) { + if (face._surface != returnSurface) { + returnSurface.addFace(face); + } + } + if (removeSurfaces) { + // Удаляем старые, теперь вручную - меньше проверок, но рискованно + if (returnSurfaceId == null) { + returnSurfaceId = getSurfaceId(returnSurface); + } + var newSurfaces:Map = new Map(); + newSurfaces[returnSurfaceId] = returnSurface; + delete _surfaces[returnSurfaceId]; + // Удаляем оставшиеся + for (var currentSurfaceId:* in _surfaces) { + // Удаляем поверхность из сцены + var currentSurface:Surface = _surfaces[currentSurfaceId]; + if (_scene != null) { + currentSurface.removeFromScene(_scene); + } + // Удаляем поверхность из меша + currentSurface.removeFromMesh(this); + delete _surfaces[currentSurfaceId]; + } + // Новый список граней + _surfaces = newSurfaces; + } + return returnSurface; + } + + /** + * Установка материала для указанной поверхности. + * + * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности. + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Surface + */ + public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если назначаем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если назначаем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + + // Назначаем материал + s.material = material; + } + + /** + * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала. + * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей. + * + * @param material устанавливаемый материал + */ + public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void { + for each (var surface:Surface in _surfaces) { + surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null; + } + } + + /** + * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по + * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать + * невырожденный треугольник в UV-пространстве. + * + * @param aUV UV-координаты, соответствующие первой вершине грани + * @param bUV UV-координаты, соответствующие второй вершине грани + * @param cUV UV-координаты, соответствующие третьей вершине грани + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если назначаем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если назначаем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _faces[face]; + + // Назначаем UV-координаты + f.aUV = aUV; + f.bUV = bUV; + f.cUV = cUV; + } + + /** + * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин. + */ + public function get vertices():Map { + return _vertices.clone(); + } + + /** + * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней. + */ + public function get faces():Map { + return _faces.clone(); + } + + /** + * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей. + */ + public function get surfaces():Map { + return _surfaces.clone(); + } + + /** + * Получение вершины объекта по её идентификатору. + * + * @param id идентификатор вершины + * + * @return экземпляр вершины с указанным идентификатором + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getVertexById(id:Object):Vertex { + if (id == null) { + throw new VertexNotFoundError(null, this); + } + if (_vertices[id] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(id, this); + } else { + if (_vertices[id] is Vertex) { + return _vertices[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора вершины объекта. + * + * @param экземпляр вершины + * + * @return идентификатор указанной вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + */ + public function getVertexId(vertex:Vertex):Object { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + for (var i:Object in _vertices) { + if (_vertices[i] == vertex) { + return i; + } + } + throw new VertexNotFoundError(vertex, this); + } + + /** + * Проверка наличия вершины в объекте. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины + * + * @return true, если объект содержит указанную вершину, иначе false + * + * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Vertex + */ + public function hasVertex(vertex:Object):Boolean { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex is Vertex) { + // Проверка вершины + return vertex._mesh == this; + } else { + // Проверка ID вершины + if (_vertices[vertex] != undefined) { + // По этому ID есть объект + if (_vertices[vertex] is Vertex) { + // Объект является вершиной + return true; + } else { + // ID некорректный + throw new InvalidIDError(vertex, this); + } + } else { + return false; + } + } + } + + /** + * Получение грани объекта по ее идентификатору. + * + * @param id идентификатор грани + * + * @return экземпляр грани с указанным идентификатором + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getFaceById(id:Object):Face { + if (id == null) { + throw new FaceNotFoundError(null, this); + } + if (_faces[id] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(id, this); + } else { + if (_faces[id] is Face) { + return _faces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора грани объекта. + * + * @param face экземпляр грани + * + * @return идентификатор указанной грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + */ + public function getFaceId(face:Face):Object { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + for (var i:Object in _faces) { + if (_faces[i] == face) { + return i; + } + } + throw new FaceNotFoundError(face, this); + } + + /** + * Проверка наличия грани в объекте. + * + * @param face экземпляр класса Face или идентификатор грани + * + * @return true, если объект содержит указанную грань, иначе false + * + * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasFace(face:Object):Boolean { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face is Face) { + // Проверка грани + return face._mesh == this; + } else { + // Проверка ID грани + if (_faces[face] != undefined) { + // По этому ID есть объект + if (_faces[face] is Face) { + // Объект является гранью + return true; + } else { + // ID некорректный + throw new InvalidIDError(face, this); + } + } else { + return false; + } + } + } + + /** + * Получение поверхности объекта по ее идентификатору + * + * @param id идентификатор поверхности + * + * @return экземпляр поверхности с указанным идентификатором + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getSurfaceById(id:Object):Surface { + if (id == null) { + throw new SurfaceNotFoundError(null, this); + } + if (_surfaces[id] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(id, this); + } else { + if (_surfaces[id] is Surface) { + return _surfaces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора поверхности объекта. + * + * @param surface экземпляр поверхности + * + * @return идентификатор указанной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + */ + public function getSurfaceId(surface:Surface):Object { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + for (var i:Object in _surfaces) { + if (_surfaces[i] == surface) { + return i; + } + } + return null; + } + + /** + * Проверка наличия поверхности в объекте. + * + * @param surface экземпляр класса Surface или идентификатор поверхности + * + * @return true, если объект содержит указанную поверхность, иначе false + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasSurface(surface:Object):Boolean { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface is Surface) { + // Проверка поверхности + return surface._mesh == this; + } else { + // Проверка ID поверхности + if (_surfaces[surface] != undefined) { + // По этому ID есть объект + if (_surfaces[surface] is Surface) { + // Объект является поверхностью + return true; + } else { + // ID некорректный + throw new InvalidIDError(surface, this); + } + } else { + return false; + } + } + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + var vertex:Vertex; + var face:Face; + var surface:Surface; + if (value != null) { + // Добавить вершины на сцену + for each (vertex in _vertices) { + vertex.addToScene(value); + } + // Добавить грани на сцену + for each (face in _faces) { + face.addToScene(value); + } + // Добавить поверхности на сцену + for each (surface in _surfaces) { + surface.addToScene(value); + } + } else { + // Удалить вершины из сцены + for each (vertex in _vertices) { + vertex.removeFromScene(_scene); + } + // Удалить грани из сцены + for each (face in _faces) { + face.removeFromScene(_scene); + } + // Удалить поверхности из сцены + for each (surface in _surfaces) { + surface.removeFromScene(_scene); + } + } + } + super.setScene(value); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "mesh" + ++counter; + } + + /** + * @inheritDoc + */ + override public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]"; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Mesh(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Mesh = Mesh(source); + + var id:*; + var len:int; + var i:int; + // Копирование вершин + var vertexMap:Map = new Map(true); + for (id in src._vertices) { + var sourceVertex:Vertex = src._vertices[id]; + vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id); + } + + // Копирование граней + var faceMap:Map = new Map(true); + for (id in src._faces) { + var sourceFace:Face = src._faces[id]; + len = sourceFace._vertices.length; + var faceVertices:Array = new Array(len); + for (i = 0; i < len; i++) { + faceVertices[i] = vertexMap[sourceFace._vertices[i]]; + } + var newFace:Face = createFace(faceVertices, id); + newFace.aUV = sourceFace._aUV; + newFace.bUV = sourceFace._bUV; + newFace.cUV = sourceFace._cUV; + faceMap[sourceFace] = newFace; + } + + // Копирование поверхностей + for (id in src._surfaces) { + var sourceSurface:Surface = src._surfaces[id]; + var surfaceFaces:Array = sourceSurface._faces.toArray(); + len = surfaceFaces.length; + for (i = 0; i < len; i++) { + surfaceFaces[i] = faceMap[surfaceFaces[i]]; + } + var surface:Surface = createSurface(surfaceFaces, id); + var sourceMaterial:SurfaceMaterial = sourceSurface.material; + if (sourceMaterial != null) { + surface.material = SurfaceMaterial(sourceMaterial.clone()); + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..bd6ad51 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,957 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.errors.Object3DHierarchyError; + import alternativa.engine3d.errors.Object3DNotFoundError; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения + * о трансформации объекта как единого целого. + * + *

Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая + * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей + * X, Y, Z и параллельного переноса центра объекта из начала координат. + * Операции применяются в порядке их перечисления. + * + *

Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций + * самого объекта и всех его предков по иерархии объектов сцены. + * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие. На данный момент не реализованы capture и bubbling фазы рассылки + * событий. + */ + public class Object3D implements IEventDispatcher { + /** + * @private + * Вспомогательная матрица + */ + alternativa3d static var matrix1:Matrix3D = new Matrix3D(); + /** + * @private + * Вспомогательная матрица + */ + alternativa3d static var matrix2:Matrix3D = new Matrix3D(); + + /** + * @private + * Поворот или масштабирование + */ + alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this); + /** + * @private + * Перемещение + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт матрицы трансформации + */ + alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION); + /** + * @private + * Изменение уровеня мобильности + */ + alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Наименование + */ + alternativa3d var _name:String; + /** + * @private + * Сцена + */ + alternativa3d var _scene:Scene3D; + /** + * @private + * Родительский объект + */ + alternativa3d var _parent:Object3D; + /** + * @private + * Дочерние объекты + */ + alternativa3d var _children:Set = new Set(); + /** + * @private + * Уровень мобильности + */ + alternativa3d var _mobility:int = 0; + /** + * @private + */ + alternativa3d var inheritedMobility:int; + /** + * @private + * Координаты объекта относительно родителя + */ + alternativa3d var _coords:Point3D = new Point3D(); + /** + * @private + * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationX:Number = 0; + /** + * @private + * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationY:Number = 0; + /** + * @private + * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationZ:Number = 0; + /** + * @private + * Мастшаб объекта по оси X относительно родителя + */ + alternativa3d var _scaleX:Number = 1; + /** + * @private + * Мастшаб объекта по оси Y относительно родителя + */ + alternativa3d var _scaleY:Number = 1; + /** + * @private + * Мастшаб объекта по оси Z относительно родителя + */ + alternativa3d var _scaleZ:Number = 1; + /** + * @private + * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены + */ + alternativa3d var _transformation:Matrix3D = new Matrix3D(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра класса. + * + * @param name имя экземпляра + */ + public function Object3D(name:String = null) { + // Имя по-умолчанию + _name = (name != null) ? name : defaultName(); + + // Последствия операций + changeRotationOrScaleOperation.addSequel(calculateTransformationOperation); + changeCoordsOperation.addSequel(calculateTransformationOperation); + } + + /** + * @private + * Расчёт трансформации + */ + alternativa3d function calculateTransformation():void { + if (changeRotationOrScaleOperation.queued) { + // Если полная трансформация + _transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + if (_parent != null) { + _transformation.combine(_parent._transformation); + } + // Сохраняем глобальные координаты объекта + globalCoords.x = _transformation.d; + globalCoords.y = _transformation.h; + globalCoords.z = _transformation.l; + } else { + // Если только перемещение + globalCoords.copy(_coords); + if (_parent != null) { + globalCoords.transform(_parent._transformation); + } + _transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z); + } + } + + /** + * @private + * Расчёт общей мобильности + */ + private function calculateMobility():void { + inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility; + } + + /** + * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя. + * Новой сценой дочернего объекта становится сцена родителя. + * + * @param child добавляемый объект + * + * @return добавленный объект + * + * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены + */ + public function addChild(child:Object3D):Object3D { + + // Проверка на null + if (child == null) { + throw new Object3DHierarchyError(null, this); + } + + // Проверка на наличие + if (child._parent == this) { + return child; + } + + // Проверка на добавление к самому себе + if (child == this) { + throw new Object3DHierarchyError(this, this); + } + + // Проверка на добавление родительского объекта + if (child._scene == _scene) { + // Если объект был в той же сцене, либо оба не были в сцене + var parentObject:Object3D = _parent; + while (parentObject != null) { + if (child == parentObject) { + throw new Object3DHierarchyError(child, this); + return; + } + parentObject = parentObject._parent; + } + } + + // Если объект был в другом объекте + if (child._parent != null) { + // Удалить его оттуда + child._parent._children.remove(child); + } else { + // Если объект был корневым в сцене + if (child._scene != null && child._scene._root == child) { + child._scene.root = null; + } + } + + // Добавляем в список + _children.add(child); + // Указываем себя как родителя + child.setParent(this); + // Устанавливаем уровни + child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1); + // Указываем сцену + child.setScene(_scene); + + return child; + } + + /** + * Удаление дочернего объекта. + * + * @param child удаляемый дочерний объект + * + * @return удаленный объект + * + * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта + */ + public function removeChild(child:Object3D):Object3D { + // Проверка на null + if (child == null) { + throw new Object3DNotFoundError(null, this); + } + // Проверка на наличие + if (child._parent != this) { + throw new Object3DNotFoundError(child, this); + } + // Убираем из списка + _children.remove(child); + // Удаляем ссылку на родителя + child.setParent(null); + // Удаляем ссылку на сцену + child.setScene(null); + + return child; + } + + /** + * @private + * Установка родительского объекта. + * + * @param value родительский объект + */ + alternativa3d function setParent(value:Object3D):void { + // Отписываемся от сигналов старого родителя + if (_parent != null) { + removeParentSequels(); + } + // Сохранить родителя + _parent = value; + // Если устанавливаем родителя + if (value != null) { + // Подписка на сигналы родителя + addParentSequels(); + } + } + + /** + * @private + * Установка новой сцены для объекта. + * + * @param value сцена + */ + alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + // Если была сцена + if (_scene != null) { + // Удалиться из сцены + removeFromScene(_scene); + } + // Если новая сцена + if (value != null) { + // Добавиться на сцену + addToScene(value); + } + // Сохранить сцену + _scene = value; + } else { + // Посылаем операцию трансформации + addOperationToScene(changeRotationOrScaleOperation); + // Посылаем операцию пересчёта мобильности + addOperationToScene(calculateMobilityOperation); + } + // Установить эту сцену у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setScene(_scene); + } + } + + /** + * @private + * Установка уровня операции трансформации. + * + * @param value уровень операции трансформации + */ + alternativa3d function setLevel(value:uint):void { + // Установить уровень операции трансформации и расчёта мобильности + calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value; + calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value; + // Установить уровни у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setLevel(value + 1); + } + } + + /** + * @private + * Подписка на сигналы родителя. + */ + private function addParentSequels():void { + _parent.changeCoordsOperation.addSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation); + } + + /** + * @private + * Удаление подписки на сигналы родителя. + */ + private function removeParentSequels():void { + _parent.changeCoordsOperation.removeSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation); + } + + /** + * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, в которую добавляется объект + */ + protected function addToScene(scene:Scene3D):void { + // При добавлении на сцену полная трансформация и расчёт мобильности + scene.addOperation(changeRotationOrScaleOperation); + scene.addOperation(calculateMobilityOperation); + } + + /** + * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, из которой удаляется объект + */ + protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeRotationOrScaleOperation); + scene.removeOperation(changeCoordsOperation); + scene.removeOperation(calculateMobilityOperation); + } + + /** + * Имя объекта. + */ + public function get name():String { + return _name; + } + + /** + * @private + */ + public function set name(value:String):void { + _name = value; + } + + /** + * Сцена, которой принадлежит объект. + */ + public function get scene():Scene3D { + return _scene; + } + + /** + * Родительский объект. + */ + public function get parent():Object3D { + return _parent; + } + + /** + * Набор дочерних объектов. + */ + public function get children():Set { + return _children.clone(); + } + + /** + * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков + * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные + * объекты находятся ближе к корню дерева, чем более мобильные. + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + if (_mobility != value) { + _mobility = value; + addOperationToScene(calculateMobilityOperation); + } + } + + /** + * Координата X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * Координата Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * Координата Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Координаты объекта. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Угол поворота вокруг оси X, заданный в радианах. + */ + public function get rotationX():Number { + return _rotationX; + } + + /** + * Угол поворота вокруг оси Y, заданный в радианах. + */ + public function get rotationY():Number { + return _rotationY; + } + + /** + * Угол поворота вокруг оси Z, заданный в радианах. + */ + public function get rotationZ():Number { + return _rotationZ; + } + + /** + * @private + */ + public function set rotationX(value:Number):void { + if (_rotationX != value) { + _rotationX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationY(value:Number):void { + if (_rotationY != value) { + _rotationY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationZ(value:Number):void { + if (_rotationZ != value) { + _rotationZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Коэффициент масштабирования вдоль оси X. + */ + public function get scaleX():Number { + return _scaleX; + } + + /** + * Коэффициент масштабирования вдоль оси Y. + */ + public function get scaleY():Number { + return _scaleY; + } + + /** + * Коэффициент масштабирования вдоль оси Z. + */ + public function get scaleZ():Number { + return _scaleZ; + } + + /** + * @private + */ + public function set scaleX(value:Number):void { + if (_scaleX != value) { + _scaleX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleY(value:Number):void { + if (_scaleY != value) { + _scaleY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleZ(value:Number):void { + if (_scaleZ != value) { + _scaleZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + "]"; + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + protected function defaultName():String { + return "object" + ++counter; + } + + /** + * @private + * Добавление операции в очередь. + * + * @param operation добавляемая операция + */ + alternativa3d function addOperationToScene(operation:Operation):void { + if (_scene != null) { + _scene.addOperation(operation); + } + } + + /** + * @private + * Удаление операции из очереди. + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperationFromScene(operation:Operation):void { + if (_scene != null) { + _scene.removeOperation(operation); + } + } + + /** + * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при + * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и + * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения + * правильного объекта. + * + * @return новый пустой объект + */ + protected function createEmptyObject():Object3D { + return new Object3D(); + } + + /** + * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в + * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те + * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется + * вызовом super.clonePropertiesFrom(source). + * + * @param source объект, свойства которого копируются + */ + protected function clonePropertiesFrom(source:Object3D):void { + _name = source._name; + _mobility = source._mobility; + _coords.x = source._coords.x; + _coords.y = source._coords.y; + _coords.z = source._coords.z; + _rotationX = source._rotationX; + _rotationY = source._rotationY; + _rotationZ = source._rotationZ; + _scaleX = source._scaleX; + _scaleY = source._scaleY; + _scaleZ = source._scaleZ; + } + + /** + * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы + * createEmptyObject() и clonePropertiesFrom(). + * + * @return клонированный экземпляр объекта + * + * @see #createEmptyObject() + * @see #clonePropertiesFrom() + */ + public function clone():Object3D { + var copy:Object3D = createEmptyObject(); + copy.clonePropertiesFrom(this); + + // Клонирование детей + for (var key:* in _children) { + var child:Object3D = key; + copy.addChild(child.clone()); + } + + return copy; + } + + /** + * Получение дочернего объекта с заданным именем. + * + * @param name имя дочернего объекта + * @param deep флаг углублённого поиска. Если задано значение false, поиск будет осуществляться только среди непосредственных + * дочерних объектов, иначе поиск будет выполняться по всему дереву дочерних объектов. + * + * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов + */ + public function getChildByName(name:String, deep:Boolean = false):Object3D { + var key:*; + var child:Object3D; + for (key in _children) { + child = key; + if (child._name == name) { + return child; + } + } + if (deep) { + for (key in _children) { + child = key; + child = child.getChildByName(name, true); + if (child != null) { + return child; + } + } + } + return null; + } + + /** + * Трансформация точки из локальной системы координат объекта в систему координат сцены. Матрица трансформации корневого объекта сцены + * не учитывается, т.к. его система координат является системой координат сцены. + * + * @param point локальные координаты точки + * + * @return координаты точки в сцене или null в случае, если объект не находится в сцене + */ + public function localToGlobal(point:Point3D):Point3D { + if (_scene == null) { + return null; + } + var globalPoint:Point3D = point.clone(); + + if (_parent == null) { + // Для корневого объекта трансформация единичная вне зависимости от матрицы + return globalPoint; + } + + getTransformation(matrix2); + globalPoint.transform(matrix2); + + return globalPoint; + } + + /** + * Преобразование точки из глобальной в локальную систему координат. + * + * @param point точка, заданная в глобальной системе координат. + * + * @return точка, трансформированная в локальную систему координат объекта. + */ + public function globalToLocal(point:Point3D):Point3D { + var result:Point3D = point.clone(); + getTransformation(matrix2); + matrix2.invert(); + result.transform(matrix2); + return result; + } + + /** + * Получение матрицы полной трансформации объекта. + * + * @return матрица полной трансформации, переводящая координаты из локальной системы объекта в систему координат сцены + */ + public function get transformation():Matrix3D { + var result:Matrix3D = new Matrix3D(); + getTransformation(result); + return result; + } + + /** + * Добавление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылка события. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверка наличия зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + + /** + * @private + * Получение матрицы трансформации объекта. Перед расчётом выполняется проверка необходимости пересчёта матрицы. + * + * @param matrix матрица, в которую записывается результат + * + * @return true, если был произведён пересчёт матрицы трансформации, false, если пересчёт не понадобился + */ + alternativa3d function getTransformation(matrix:Matrix3D):Boolean { + var rootObject:Object3D = _scene._root; + var topNonTransformedObject:Object3D; + var currentObject:Object3D = this; + + // Поиск первого нетрансформированного объекта в дереве объектов + do { + if (currentObject.changeCoordsOperation.queued || currentObject.changeRotationOrScaleOperation.queued) { + topNonTransformedObject = currentObject._parent; + } + } while ((currentObject = currentObject._parent) != rootObject); + + if (topNonTransformedObject != null) { + // Расчёт матрицы трансформации + matrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + currentObject = this; + while ((currentObject = currentObject._parent) != topNonTransformedObject) { + matrix1.toTransform(currentObject._coords.x, currentObject._coords.y, currentObject._coords.z, currentObject._rotationX, currentObject._rotationY, currentObject._rotationZ, currentObject._scaleX, currentObject._scaleY, currentObject._scaleZ); + matrix.combine(matrix1); + } + if (topNonTransformedObject != rootObject) { + matrix.combine(topNonTransformedObject._transformation); + } + return true; + } + matrix.copy(_transformation); + return false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as new file mode 100644 index 0000000..ae0236c --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as @@ -0,0 +1,126 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public class Operation { + + alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000; + alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000; + alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000; + alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000; + alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000; + alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000; + alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000; + alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000; + alternativa3d static const SPRITE_UPDATE_MATERIAL:uint = 0x09000000; + alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x0A000000; + alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0B000000; + alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0C000000; + alternativa3d static const CAMERA_RENDER:uint = 0x0D000000; + alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0E000000; + + // Объект + alternativa3d var object:Object; + + // Метод + alternativa3d var method:Function; + + // Название метода + alternativa3d var name:String; + + // Последствия + private var sequel:Operation; + private var sequels:Set; + + // Приоритет операции + alternativa3d var priority:uint; + + // Находится ли операция в очереди + alternativa3d var queued:Boolean = false; + + public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) { + this.object = object; + this.method = method; + this.name = name; + this.priority = priority; + } + + // Добавить последствие + alternativa3d function addSequel(operation:Operation):void { + if (sequel == null) { + if (sequels == null) { + sequel = operation; + } else { + sequels[operation] = true; + } + } else { + if (sequel != operation) { + sequels = new Set(true); + sequels[sequel] = true; + sequels[operation] = true; + sequel = null; + } + } + } + + // Удалить последствие + alternativa3d function removeSequel(operation:Operation):void { + if (sequel == null) { + if (sequels != null) { + delete sequels[operation]; + var key:*; + var single:Boolean = false; + for (key in sequels) { + if (single) { + single = false; + break; + } + single = true; + } + if (single) { + sequel = key; + sequels = null; + } + } + } else { + if (sequel == operation) { + sequel = null; + } + } + } + + alternativa3d function collectSequels(collector:Array):void { + if (sequel == null) { + // Проверяем последствия + for (var key:* in sequels) { + var operation:Operation = key; + // Если операция ещё не в очереди + if (!operation.queued) { + // Добавляем её в очередь + collector.push(operation); + // Устанавливаем флаг очереди + operation.queued = true; + // Вызываем добавление в очередь её последствий + operation.collectSequels(collector); + } + } + } else { + if (!sequel.queued) { + collector.push(sequel); + sequel.queued = true; + sequel.collectSequels(collector); + } + } + } + + public function toString():String { + return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as new file mode 100644 index 0000000..6e5d645 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as @@ -0,0 +1,128 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + * Примитивный полигон (примитив), хранящийся в узле BSP-дерева. + */ + public class PolyPrimitive { + + /** + * @private + * Количество точек + */ + alternativa3d var num:uint; + /** + * @private + * Точки + */ + alternativa3d var points:Array = new Array(); + /** + * @private + * UV-координаты + */ + alternativa3d var uvs:Array = new Array(); + /** + * @private + * Грань + */ + alternativa3d var face:Face; + /** + * @private + * Родительский примитив + */ + alternativa3d var parent:PolyPrimitive; + /** + * @private + * Соседний примитив (при наличии родительского) + */ + alternativa3d var sibling:PolyPrimitive; + /** + * @private + * Фрагменты + */ + alternativa3d var backFragment:PolyPrimitive; + /** + * @private + */ + alternativa3d var frontFragment:PolyPrimitive; + /** + * @private + * Рассечения + */ + alternativa3d var splitTime1:Number; + /** + * @private + */ + alternativa3d var splitTime2:Number; + /** + * @private + * BSP-нода, в которой находится примитив + */ + alternativa3d var node:BSPNode; + /** + * @private + * Значения для расчёта качества сплиттера + */ + alternativa3d var splits:uint; + /** + * @private + */ + alternativa3d var disbalance:int; + /** + * @private + * Качество примитива как сплиттера (меньше - лучше) + */ + public var splitQuality:Number; + /** + * @private + * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве. + */ + public var mobility:int; + + // Хранилище неиспользуемых примитивов + static private var collector:Array = new Array(); + + /** + * @private + * Создать примитив + */ + static alternativa3d function createPolyPrimitive():PolyPrimitive { + var primitive:PolyPrimitive; + if ((primitive = collector.pop()) != null) { + return primitive; + } + return new PolyPrimitive(); + } + + /** + * @private + * Кладёт примитив в коллектор для последующего реиспользования. + * Ссылка на грань и массивы точек зачищаются в этом методе. + * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода. + * + * Исключение: + * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода. + * + * @param primitive примитив на реиспользование + */ + static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void { + primitive.face = null; + for (var i:uint = 0; i < primitive.num; i++) { + primitive.points.pop(); + primitive.uvs.pop(); + } + collector.push(primitive); + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[Primitive " + face._mesh._name + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as new file mode 100644 index 0000000..bed9480 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as @@ -0,0 +1,1287 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SpriteTextureMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов + * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения + * BSP-дерева для содержимого сцены. + */ + public class Scene3D { + // Операции + /** + * @private + * Полное обновление BSP-дерева + */ + alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this); + /** + * @private + * Изменение примитивов + */ + alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this); + /** + * @private + * Расчёт BSP-дерева + */ + alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP); + /** + * @private + * Очистка списков изменений + */ + alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES); + + /** + * @private + * Корневой объект + */ + alternativa3d var _root:Object3D; + + /** + * @private + * Список операций на выполнение + */ + alternativa3d var operations:Array = new Array(); + //protected var operationSort:Array = ["priority"]; + //protected var operationSortOptions:Array = [Array.NUMERIC]; + /** + * @private + * Вспомогательная пустая операция, используется при удалении операций из списка + */ + alternativa3d var dummyOperation:Operation = new Operation("removed", this); + + /** + * @private + * Флаг анализа сплиттеров + */ + alternativa3d var _splitAnalysis:Boolean = true; + /** + * @private + * Cбалансированность дерева + */ + alternativa3d var _splitBalance:Number = 0; + /** + * @private + * Список изменённых примитивов + */ + alternativa3d var changedPrimitives:Set = new Set(); + + // Вспомогательный список для сборки дочерних примитивов + private var childPrimitives:Set = new Set(); + /** + * @private + * Список примитивов на добавление/удаление + */ + alternativa3d var addPrimitives:Array = new Array(); + + /** + * @private + * Погрешность при определении точек на плоскости + */ + private var _planeOffsetThreshold:Number = 0.01; + /** + * @private + * BSP-дерево + */ + alternativa3d var bsp:BSPNode; + + /** + * @private + * Список нод на удаление + */ + alternativa3d var removeNodes:Set = new Set(); + /** + * @private + * Вспомогательная пустая нода, используется при удалении нод из дерева + */ + alternativa3d var dummyNode:BSPNode = new BSPNode(); + + /** + * Создание экземпляра сцены. + */ + public function Scene3D() { + // Обновление BSP-дерева требует его пересчёта + updateBSPOperation.addSequel(calculateBSPOperation); + // Изменение примитивов в случае пересчёта дерева + calculateBSPOperation.addSequel(changePrimitivesOperation); + // При изменении примитивов необходимо очистить списки изменений + changePrimitivesOperation.addSequel(clearPrimitivesOperation); + } + + /** + * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список + * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех + * областях вывода, к которым подключены находящиеся в сцене камеры. + */ + public function calculate():void { + if (operations[0] != undefined) { + // Формируем последствия + var operation:Operation; + var length:uint = operations.length; + var i:uint; + for (i = 0; i < length; i++) { + operation = operations[i]; + operation.collectSequels(operations); + } + // Сортируем операции + length = operations.length; + sortOperations(0, length - 1); + // Запускаем операции + //trace("----------------------------------------"); + for (i = 0; i < length; i++) { + operation = operations[i]; + if (operation.method != null) { + //trace("EXECUTE:", operation); + operation.method(); + } else { + /*if (operation == dummyOperation) { + trace("REMOVED"); + } else { + trace(operation); + }*/ + } + } + // Очищаем список операций + for (i = 0; i < length; i++) { + operation = operations.pop(); + operation.queued = false; + } + } + } + + /** + * @private + * Сортировка операций, если массив operations пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortOperations(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:Operation; + var mid:uint = operations[(r + l) >> 1].priority; + var right:Operation; + do { + while ((left = operations[i]).priority < mid) {i++}; + while (mid < (right = operations[j]).priority) {j--}; + if (i <= j) { + operations[i++] = right; + operations[j--] = left; + } + } while (i <= j) + if (l < j) { + sortOperations(l, j); + } + if (i < r) { + sortOperations(i, r); + } + } + + /** + * @private + * Добавление операции в список + * + * @param operation добавляемая операция + */ + alternativa3d function addOperation(operation:Operation):void { + if (!operation.queued) { + operations.push(operation); + operation.queued = true; + } + } + + /** + * @private + * удаление операции из списка + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperation(operation:Operation):void { + if (operation.queued) { + operations[operations.indexOf(operation)] = dummyOperation; + operation.queued = false; + } + } + + /** + * @private + * Расчёт изменений в BSP-дереве. + * Обработка удалённых и добавленных примитивов. + */ + protected function calculateBSP():void { + if (updateBSPOperation.queued) { + + // Удаление списка нод, помеченных на удаление + removeNodes.clear(); + + // Удаление BSP-дерева, перенос примитивов в список дочерних + childBSP(bsp); + bsp = null; + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + } else { + + var key:*; + var primitive:PolyPrimitive; + + // Удаляем ноды из дерева + if (!removeNodes.isEmpty()) { + var node:BSPNode; + while ((node = removeNodes.peek()) != null) { + + // Ищем верхнюю удаляемую ноду + var removeNode:BSPNode = node; + while ((node = node.parent) != null) { + if (removeNodes[node]) { + removeNode = node; + } + } + + // Удаляем ветку + var parent:BSPNode = removeNode.parent; + var replace:BSPNode = removeBSPNode(removeNode); + + // Если вернулась вспомогательная нода, игнорируем её + if (replace == dummyNode) { + replace = null; + } + + // Если есть родительская нода + if (parent != null) { + // Заменяем себя на указанную ноду + if (parent.front == removeNode) { + parent.front = replace; + } else { + parent.back = replace; + } + } else { + // Если нет родительской ноды, значит заменяем корень на указанную ноду + bsp = replace; + } + + // Устанавливаем связь с родителем для заменённой ноды + if (replace != null) { + replace.parent = parent; + } + } + + // Собираем дочерние примитивы в список на добавление + assembleChildPrimitives(); + } + } + + // Если есть примитивы на добавление + if (addPrimitives[0] != undefined) { + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Если корневого нода ещё нет, создаём + if (bsp == null) { + primitive = addPrimitives.pop(); + bsp = BSPNode.createBSPNode(primitive); + changedPrimitives[primitive] = true; + } + + // Встраиваем примитивы в дерево + while ((primitive = addPrimitives.pop()) != null) { + addBSP(bsp, primitive); + } + } + } + + /** + * @private + * Сортировка граней, если массив addPrimitives пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:PolyPrimitive = addPrimitives[(r + l) >> 1]; + var midMobility:int = mid.mobility; + var midSplitQuality:Number = mid.splitQuality; + var right:PolyPrimitive; + do { + while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++}; + while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitives(l, j); + } + if (i < r) { + sortPrimitives(i, r); + } + } + + /** + * @private + * Сортировка только по мобильности + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitivesByMobility(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:int = addPrimitives[(r + l) >> 1].mobility; + var right:PolyPrimitive; + do { + while ((left = addPrimitives[i]).mobility > mid) {i++}; + while (mid > (right = addPrimitives[j]).mobility) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitivesByMobility(l, j); + } + if (i < r) { + sortPrimitivesByMobility(i, r); + } + } + + + /** + * @private + * Анализ качества сплиттеров + */ + private function analyseSplitQuality():void { + // Перебираем примитивы на добавление + var i:uint; + var length:uint = addPrimitives.length; + var maxSplits:uint = 0; + var maxDisbalance:uint = 0; + var splitter:PolyPrimitive; + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + if (splitter.face == null) { + // Пропускаем спрайтовые примитивы + continue; + } + splitter.splits = 0; + splitter.disbalance = 0; + var normal:Point3D = splitter.face.globalNormal; + var offset:Number = splitter.face.globalOffset; + // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление + for (var j:uint = 0; j < length; j++) { + if (i != j) { + var primitive:PolyPrimitive = addPrimitives[j]; + if (primitive.face == null) { + // Пропускаем спрайтовые примитивы + continue; + } + if (splitter.mobility <= primitive.mobility) { + // Проверяем наличие точек спереди и сзади сплиттера + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + for (var k:uint = 0; k < primitive.num; k++) { + var point:Point3D = primitive.points[k]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset; + if (pointOffset > _planeOffsetThreshold) { + if (!pointsFront) { + splitter.disbalance++; + pointsFront = true; + } + if (pointsBack) { + splitter.splits++; + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + if (!pointsBack) { + splitter.disbalance--; + pointsBack = true; + } + if (pointsFront) { + splitter.splits++; + break; + } + } + } + } + } + } + } + // Абсолютное значение дисбаланса + splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance; + // Ищем максимальное количество рассечений и значение дисбаланса + maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits; + maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance; + } + // Расчитываем качество сплиттеров + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance; + } + } + + /** + * @private + * Добавление примитива в BSP-дерево + * + * @param node текущий узел дерева, в который добавляется примитив + * @param primitive добавляемый примитив + */ + protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void { + var point:Point3D; + var normal:Point3D; + var key:*; + + // Сравниваем мобильности ноды и примитива + if (primitive.mobility < node.mobility || (node.sprited && primitive.face != null)) { + // Мобильность примитива ниже мобильности ноды + // Или мобильность примитива равна мобильности спрайтовой ноды + + // Формируем список содержимого ноды и всех примитивов ниже + if (node.primitive != null) { + childPrimitives[node.primitive] = true; + changedPrimitives[node.primitive] = true; + node.primitive.node = null; + } else { + var p:PolyPrimitive; + for (key in node.backPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + for (key in node.frontPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + } + childBSP(node.back); + childBSP(node.front); + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Сохраняем ноду + primitive.node = node; + + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + node.sprited = false; + + // Сохраняем мобильность + node.mobility = primitive.mobility; + + // Чистим списки примитивов + node.backPrimitives = null; + node.frontPrimitives = null; + + // Удаляем дочерние ноды + node.back = null; + node.front = null; + + } else { + // Получаем нормаль из ноды + normal = node.normal; + + var points:Array = primitive.points; + var uvs:Array = primitive.uvs; + + // Собирательные флаги + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + + // Собираем расстояния точек до плоскости + for (var i:uint = 0; i < primitive.num; i++) { + point = points[i]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + if (pointOffset > _planeOffsetThreshold) { + pointsFront = true; + if (pointsBack) { + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + pointsBack = true; + if (pointsFront) { + break; + } + } + } + } + + // Если все точки в плоскости или это добавление спрайтовой точки в спрайтовую ноду + if (!pointsFront && !pointsBack && (primitive.face != null || node.sprited)) { + // Сохраняем ноду + primitive.node = node; + + // Если был только базовый примитив, переносим его в список + if (node.primitive != null) { + node.frontPrimitives = new Set(true); + node.frontPrimitives[node.primitive] = true; + node.primitive = null; + } + + // Если примитив спрайтовый или нормаль полигона сонаправлена с нормалью ноды + if (primitive.face == null || Point3D.dot(primitive.face.globalNormal, normal) > 0) { + node.frontPrimitives[primitive] = true; + } else { + if (node.backPrimitives == null) { + node.backPrimitives = new Set(true); + } + node.backPrimitives[primitive] = true; + } + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + } else { + if (!pointsBack) { + // Примитив спереди плоскости ноды + if (node.front == null) { + // Создаём переднюю ноду + node.front = BSPNode.createBSPNode(primitive); + node.front.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в переднюю ноду + addBSP(node.front, primitive); + } + } else { + if (!pointsFront) { + // Примитив сзади плоскости ноды + if (node.back == null) { + // Создаём заднюю ноду + node.back = BSPNode.createBSPNode(primitive); + node.back.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в заднюю ноду + addBSP(node.back, primitive); + } + } else { + // Рассечение + var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive(); + + var firstSplit:Boolean = true; + + point = points[0]; + var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + var offset1:Number = offset0; + var offset2:Number; + for (i = 0; i < primitive.num; i++) { + var j:uint; + if (i < primitive.num - 1) { + j = i + 1; + point = points[j]; + offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + } else { + j = 0; + offset2 = offset0; + } + + if (offset1 > _planeOffsetThreshold) { + // Точка спереди плоскости ноды + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } else { + if (offset1 < -_planeOffsetThreshold) { + // Точка сзади плоскости ноды + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + } else { + // Рассечение по точке примитива + backFragment.points.push(points[i]); + backFragment.uvs.push(primitive.uvs[i]); + frontFragment.points.push(points[i]); + frontFragment.uvs.push(primitive.uvs[i]); + } + } + + // Рассечение ребра + if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) { + // Находим точку рассечения + var t:Number = offset1/(offset1 - offset2); + point = Point3D.interpolate(points[i], points[j], t); + backFragment.points.push(point); + frontFragment.points.push(point); + // Находим UV в точке рассечения + var uv:Point; + if (primitive.face.uvMatrixBase != null) { + uv = Point.interpolate(uvs[j], uvs[i], t); + } else { + uv = null; + } + backFragment.uvs.push(uv); + frontFragment.uvs.push(uv); + // Отмечаем рассечённое ребро + if (firstSplit) { + primitive.splitTime1 = t; + firstSplit = false; + } else { + primitive.splitTime2 = t; + } + } + + offset1 = offset2; + } + backFragment.num = backFragment.points.length; + frontFragment.num = frontFragment.points.length; + + // Назначаем мобильность + backFragment.mobility = primitive.mobility; + frontFragment.mobility = primitive.mobility; + + // Устанавливаем связи рассечённых примитивов + backFragment.face = primitive.face; + frontFragment.face = primitive.face; + backFragment.parent = primitive; + frontFragment.parent = primitive; + backFragment.sibling = frontFragment; + frontFragment.sibling = backFragment; + primitive.backFragment = backFragment; + primitive.frontFragment = frontFragment; + + // Добавляем фрагменты в дочерние ноды + if (node.back == null) { + node.back = BSPNode.createBSPNode(backFragment); + node.back.parent = node; + changedPrimitives[backFragment] = true; + } else { + addBSP(node.back, backFragment); + } + if (node.front == null) { + node.front = BSPNode.createBSPNode(frontFragment); + node.front.parent = node; + changedPrimitives[frontFragment] = true; + } else { + addBSP(node.front, frontFragment); + } + } + } + } + } + } + + /** + * @private + * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления. + * + * @param node удаляемый узел + * @return корневой узел поддерева, оставшегося после операции удаления + */ + protected function removeBSPNode(node:BSPNode):BSPNode { + var replaceNode:BSPNode; + if (node != null) { + // Удаляем дочерние + node.back = removeBSPNode(node.back); + node.front = removeBSPNode(node.front); + + if (!removeNodes[node]) { + // Если нода не удаляется, возвращает себя + replaceNode = node; + + // Проверяем дочерние ноды + if (node.back != null) { + if (node.back != dummyNode) { + node.back.parent = node; + } else { + node.back = null; + } + } + if (node.front != null) { + if (node.front != dummyNode) { + node.front.parent = node; + } else { + node.front = null; + } + } + } else { + // Проверяем дочерние ветки + if (node.back == null) { + if (node.front != null) { + // Есть только передняя ветка + replaceNode = node.front; + node.front = null; + } + } else { + if (node.front == null) { + // Есть только задняя ветка + replaceNode = node.back; + node.back = null; + } else { + // Есть обе ветки - собираем дочерние примитивы + childBSP(node.back); + childBSP(node.front); + // Используем вспомогательную ноду + replaceNode = dummyNode; + // Удаляем связи с дочерними нодами + node.back = null; + node.front = null; + } + } + + // Удаляем ноду из списка на удаление + delete removeNodes[node]; + // Удаляем ноду + node.parent = null; + BSPNode.destroyBSPNode(node); + } + } + return replaceNode; + } + + /** + * @private + * Удаление примитива из узла дерева + * + * @param primitive удаляемый примитив + */ + alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void { + var node:BSPNode = primitive.node; + primitive.node = null; + + var single:Boolean = false; + var key:*; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Если нода единичная + if (node.primitive == primitive) { + removeNodes[node] = true; + node.primitive = null; + } else { + // Есть передние примитивы + if (node.frontPrimitives[primitive]) { + // Удаляем примитив спереди + delete node.frontPrimitives[primitive]; + + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + if (key == null) { + // Передняя пуста или не спрайтовая нода, значит сзади кто-то есть + + // Переворачиваем дочерние ноды + var t:BSPNode = node.back; + node.back = node.front; + node.front = t; + + // Переворачиваем плоскость ноды + node.normal.invert(); + node.offset = -node.offset; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если сзади один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Если сзади несколько примитивов, переносим их в передние + node.frontPrimitives = node.backPrimitives; + // Пересчитываем мобильность ноды по передним примитивам + // Присваивается наименьшая мобильность из всех примитивов + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + + } else { + // Если остался один примитив и сзади примитивов нет + if (single && node.backPrimitives == null) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } else { + // Удаляем примитив сзади + delete node.backPrimitives[primitive]; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + break; + } + + // Если сзади примитивов больше нет + if (key == null) { + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если спереди один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } + } + + /** + * @private + * Удаление и перевставка ветки + * + * @param node + */ + protected function childBSP(node:BSPNode):void { + if (node != null && node != dummyNode) { + var primitive:PolyPrimitive = node.primitive; + if (primitive != null) { + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + node.primitive = null; + primitive.node = null; + } else { + for (var key:* in node.backPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + for (key in node.frontPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + node.backPrimitives = null; + node.frontPrimitives = null; + } + childBSP(node.back); + childBSP(node.front); + // Удаляем ноду + node.parent = null; + node.back = null; + node.front = null; + BSPNode.destroyBSPNode(node); + } + } + + /** + * @private + * Сборка списка дочерних примитивов в коллектор + */ + protected function assembleChildPrimitives():void { + var primitive:PolyPrimitive; + while ((primitive = childPrimitives.take()) != null) { + assemblePrimitive(primitive); + } + } + + /** + * @private + * Сборка примитивов и разделение на добавленные и удалённые + * + * @param primitive + */ + private function assemblePrimitive(primitive:PolyPrimitive):void { + // Если есть соседний примитив и он может быть собран + if (primitive.sibling != null && canAssemble(primitive.sibling)) { + // Собираем их в родительский + assemblePrimitive(primitive.parent); + // Зачищаем связи между примитивами + primitive.sibling.sibling = null; + primitive.sibling.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive.sibling); + primitive.sibling = null; + primitive.parent.backFragment = null; + primitive.parent.frontFragment = null; + primitive.parent = null; + PolyPrimitive.destroyPolyPrimitive(primitive); + } else { + // Если собраться не получилось или родительский + addPrimitives.push(primitive); + } + } + + /** + * @private + * Проверка, может ли примитив в списке дочерних быть собран + * + * @param primitive + * @return + */ + private function canAssemble(primitive:PolyPrimitive):Boolean { + if (childPrimitives[primitive]) { + delete childPrimitives[primitive]; + return true; + } else { + var backFragment:PolyPrimitive = primitive.backFragment; + var frontFragment:PolyPrimitive = primitive.frontFragment; + if (backFragment != null) { + var assembleBack:Boolean = canAssemble(backFragment); + var assembleFront:Boolean = canAssemble(frontFragment); + if (assembleBack && assembleFront) { + backFragment.parent = null; + frontFragment.parent = null; + backFragment.sibling = null; + frontFragment.sibling = null; + primitive.backFragment = null; + primitive.frontFragment = null; + PolyPrimitive.destroyPolyPrimitive(backFragment); + PolyPrimitive.destroyPolyPrimitive(frontFragment); + return true; + } else { + if (assembleBack) { + addPrimitives.push(backFragment); + } + if (assembleFront) { + addPrimitives.push(frontFragment); + } + } + } + } + return false; + } + + /** + * @private + * Очистка списков + */ + private function clearPrimitives():void { + changedPrimitives.clear(); + } + + /** + * Проверка наличия изменений в сцене. + * + * @return true, если в сцене были изменения с предыдущего вызова метода calculate + * + * @see #calculate() + */ + public function hasChanges():Boolean { + var len:int = operations.length; + for (var i:int = 0; i < len; i++) { + if (operations[i] != dummyOperation) { + return true; + } + } + return false; + } + + /** + * Корневой объект сцены. + */ + public function get root():Object3D { + return _root; + } + + /** + * @private + */ + public function set root(value:Object3D):void { + // Если ещё не является корневым объектом + if (_root != value) { + // Если устанавливаем не пустой объект + if (value != null) { + // Если объект был в другом объекте + if (value._parent != null) { + // Удалить его оттуда + value._parent._children.remove(value); + } else { + // Если объект был корневым в сцене + if (value._scene != null && value._scene._root == value) { + value._scene.root = null; + } + } + // Удаляем ссылку на родителя + value.setParent(null); + // Указываем сцену + value.setScene(this); + // Устанавливаем уровни + value.setLevel(0); + } + + // Если был корневой объект + if (_root != null) { + // Удаляем ссылку на родителя + _root.setParent(null); + // Удаляем ссылку на камеру + _root.setScene(null); + } + + // Сохраняем корневой объект + _root = value; + } + } + + /** + * Флаг активности анализа сплиттеров. + * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей + * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми. + * + *

Изменением свойства splitBalance можно влиять на конечный вид BSP-дерева. + * + * @see #splitBalance + * @default true + */ + public function get splitAnalysis():Boolean { + return _splitAnalysis; + } + + /** + * @private + */ + public function set splitAnalysis(value:Boolean):void { + if (_splitAnalysis != value) { + _splitAnalysis = value; + addOperation(updateBSPOperation); + } + } + + /** + * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров. + * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева). + * + * @see #splitAnalysis + * @default 0 + */ + public function get splitBalance():Number { + return _splitBalance; + } + + /** + * @private + */ + public function set splitBalance(value:Number):void { + value = (value < 0) ? 0 : ((value > 1) ? 1 : value); + if (_splitBalance != value) { + _splitBalance = value; + if (_splitAnalysis) { + addOperation(updateBSPOperation); + } + } + } + + /** + * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold. + * + * @default 0.01 + */ + public function get planeOffsetThreshold():Number { + return _planeOffsetThreshold; + } + + /** + * @private + */ + public function set planeOffsetThreshold(value:Number):void { + value = (value < 0) ? 0 : value; + if (_planeOffsetThreshold != value) { + _planeOffsetThreshold = value; + addOperation(updateBSPOperation); + } + } + + /** + * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей + * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого + * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу. + * + * @param container контейнер для отрисовки дерева + */ + public function drawBSP(container:Sprite):void { + + container.graphics.clear(); + while (container.numChildren > 0) { + container.removeChildAt(0); + } + if (bsp != null) { + drawBSPNode(bsp, container, 0, 0, 1); + } + } + + /** + * @private + * Отрисовка узла BSP-дерева при визуализации + * + * @param node + * @param container + * @param x + * @param y + * @param size + */ + private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void { + var s:Shape = new Shape(); + container.addChild(s); + s.x = x; + s.y = y; + var color:uint = 0xFF0000; + var primitive:PolyPrimitive; + if (node.primitive != null) { + primitive = node.primitive; + } else { + if (node.frontPrimitives != null) { + primitive = node.frontPrimitives.peek(); + } + } + if (primitive != null) { + if (primitive.face == null) { + // Спрайтовый примитив + var material:SpriteTextureMaterial = (primitive as SpritePrimitive).sprite._material as SpriteTextureMaterial; + if (material != null && material._texture != null) { + color = material._texture._bitmapData.getPixel(0, 0); + } + } else { + if (primitive.face._surface != null && primitive.face._surface._material != null) { + if (primitive.face._surface._material is FillMaterial) { + color = FillMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is WireMaterial) { + color = WireMaterial(primitive.face._surface._material)._color; + } + if ((primitive.face._surface._material is TextureMaterial) && TextureMaterial(primitive.face._surface._material)._texture != null) { + color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0); + } + } + } + } + + if (node == dummyNode) { + color = 0xFF00FF; + } + + s.graphics.beginFill(color); + s.graphics.drawCircle(0, 0, 3); + s.graphics.endFill(); + + var xOffset:Number = 100; + var yOffset:Number = 20; + if (node.back != null) { + container.graphics.lineStyle(0, 0x660000); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x - xOffset*size, y + yOffset); + drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8); + } + if (node.front != null) { + container.graphics.lineStyle(0, 0x006600); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x + xOffset*size, y + yOffset); + drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as new file mode 100644 index 0000000..e9ebba0 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as @@ -0,0 +1,207 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.materials.SpriteMaterial; + + use namespace alternativa3d; + + /** + * Объект представляет собой точку в трёхмерном пространстве. Объекту может быть назначен материал для вывода различных изображений в + * месте его нахождения. + */ + public class Sprite3D extends Object3D { + /** + * Счетчик имен объектов + */ + private static var counter:uint = 0; + + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateSpriteMaterial", this, updateMaterial, Operation.SPRITE_UPDATE_MATERIAL); + /** + * @private + * Примитив. + */ + alternativa3d var primitive:SpritePrimitive; + /** + * @private + * Материал. + */ + alternativa3d var _material:SpriteMaterial; + /** + * @private + * Размер материала. + */ + alternativa3d var _materialScale:Number; + + /** + * Создание экземпляра спрайта. + * + * @param name имя спрайта + */ + public function Sprite3D(name:String = null) { + super(name); + // Создаем примитив спрайта + primitive = new SpritePrimitive(); + primitive.sprite = this; + // В примитиве одна точка - координата спрайта + primitive.points = [this.globalCoords]; + primitive.num = 1; + primitive.mobility = int.MAX_VALUE; + } + + /** + * @private + * Расчет перемещения точки спрайта. + */ + override alternativa3d function calculateTransformation():void { + super.calculateTransformation(); + // Произошло перемещение спрайта, необходимо перевставить точку в БСП + updatePrimitive(); + if (changeRotationOrScaleOperation.queued) { + // Считаем размер материала + // Считается для любого материала, без отдельных операций + var a:Number = _transformation.a; + var b:Number = _transformation.b; + var c:Number = _transformation.c; + var e:Number = _transformation.e; + var f:Number = _transformation.f; + var g:Number = _transformation.g; + var i:Number = _transformation.i; + var j:Number = _transformation.j; + var k:Number = _transformation.k; + _materialScale = (Math.sqrt(a*a + e*e + i*i) + Math.sqrt(b*b + f*f + j*j) + Math.sqrt(c*c + g*g + k*k))/3; + } + } + + /** + * Перевставка точки спрайта в БСП дереве. + */ + private function updatePrimitive():void { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _scene.removeBSPPrimitive(primitive); + } + _scene.addPrimitives.push(primitive); + } + + /** + * Перерисовка скинов спрайта. + */ + private function updateMaterial():void { + if (!calculateTransformationOperation.queued) { + _scene.changedPrimitives[primitive] = true; + } + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + // Подписываем сцену на операции + calculateTransformationOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(updateMaterialOperation); + + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаляем примитив из сцены + scene.removeBSPPrimitive(primitive); + } + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + calculateTransformationOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + super.removeFromScene(scene); + } + + /** + * @private + * Изменение материала. + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_scene != null) { + _scene.addOperation(updateMaterialOperation); + } + } + + /** + * Материал для отображения спрайта. + */ + public function get material():SpriteMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SpriteMaterial):void { + if (_material != value) { + if (_material != null) { + _material.removeFromSprite(this); + if (_scene != null) { + _material.removeFromScene(_scene); + } + } + if (value != null) { + if (value._sprite != null) { + value._sprite.material = null; + } + value.addToSprite(this); + if (_scene != null) { + value.addToScene(_scene); + } + } + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + override protected function defaultName():String { + return "sprite" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sprite3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + material = (source as Sprite3D).material.clone() as SpriteMaterial; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as b/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as new file mode 100644 index 0000000..52c315b --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as @@ -0,0 +1,42 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + * Спрайтовый примитив. + */ + public class SpritePrimitive extends PolyPrimitive { + + /** + * @private + * Спрайт, которому принадлежит примитив. + */ + alternativa3d var sprite:Sprite3D; + /** + * @private + * Параметр используется для сортировки примитивов в камере. + */ + alternativa3d var screenDepth:Number; + + /** + * @private + * Создает экземпляр класса. + */ + public function SpritePrimitive() { + super(); + uvs = null; + } + + /** + * @private + * Строковое представление объекта. + */ + override public function toString():String { + return "[SpritePrimitive " + sprite.toString() + "]"; + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as new file mode 100644 index 0000000..d0a8501 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as @@ -0,0 +1,477 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Set; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же поверхностью. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на поверхность. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с поверхности. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов, + * визуализирующих грани объекта. + * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие. + */ + public class Surface implements IEventDispatcher { + // Операции + /** + * @private + * Изменение набора граней + */ + alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this); + /** + * @private + * Изменение материала + */ + alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Материал + */ + alternativa3d var _material:SurfaceMaterial; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра поверхности. + */ + public function Surface() {} + + /** + * Добавление грани в поверхность. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань + * + * @see Face + */ + public function addFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (_faces.has(f)) { + // Если грань уже в поверхности + throw new FaceExistsError(f, this); + } + + // Проверяем грань на нахождение в другой поверхности + if (f._surface != null) { + // Удаляем её из той поверхности + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Добавляем грань в поверхность + _faces.add(f); + f.addToSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Удаление грани из поверхности. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function removeFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (!_faces.has(f)) { + // Если грань не в поверхности + throw new FaceNotFoundError(f, this); + } + + // Удаляем грань из поверхности + _faces.remove(f); + f.removeFromSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности. + */ + public function get material():SurfaceMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SurfaceMaterial):void { + if (_material != value) { + // Если был материал + if (_material != null) { + // Удалить материал из поверхности + _material.removeFromSurface(this); + // Удалить материал из меша + if (_mesh != null) { + _material.removeFromMesh(_mesh); + // Удалить материал из сцены + if (_mesh._scene != null) { + _material.removeFromScene(_mesh._scene); + } + } + } + // Если новый материал + if (value != null) { + // Если материал был в другой поверхности + if (value._surface != null) { + // Удалить его оттуда + value._surface.material = null; + } + // Добавить материал в поверхность + value.addToSurface(this); + // Добавить материал в меш + if (_mesh != null) { + value.addToMesh(_mesh); + // Добавить материал в сцену + if (_mesh._scene != null) { + value.addToScene(_mesh._scene); + } + } + } + // Сохраняем материал + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Набор граней поверхности. + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Полигональный объект, которому принадлежит поверхность. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту, + * значение идентификатора равно null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getSurfaceId(this) : null; + } + + /** + * @private + * Добавление в сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeFacesOperation); + scene.removeOperation(changeMaterialOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + } + + /** + * @private + * Добавление к мешу + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + + // Добавляем в меш материал + if (_material != null) { + _material.addToMesh(mesh); + } + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + + // Удаляем из меша материал + if (_material != null) { + _material.removeFromMesh(mesh); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Удаление граней + */ + alternativa3d function removeFaces():void { + for (var key:* in _faces) { + var face:Face = key; + _faces.remove(face); + face.removeFromSurface(this); + } + } + + /** + * @private + * Изменение материала + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_mesh != null) { + _mesh.addOperationToScene(changeMaterialOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var length:uint = _faces.length; + var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : ""); + var i:uint = 0; + for (var key:* in _faces) { + var face:Face = key; + res += face.id + ((i < length - 1) ? ", " : ""); + i++; + } + res += "]"; + return res; + } + + /** + * Добавление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылка события. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверка наличия зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..d7f5be5 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,250 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на + * полигональный объект и грани этого объекта, которым она принадлежит. + */ + final public class Vertex { + // Операции + /** + * @private + * Изменение локальных координат + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт глобальных координат + */ + alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Координаты точки + */ + alternativa3d var _coords:Point3D; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра вершины. + * + * @param x координата вершины по оси X + * @param y координата вершины по оси Y + * @param z координата вершины по оси Z + */ + public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) { + _coords = new Point3D(x, y, z); + + // Изменение координат инициирует пересчёт глобальных координат + changeCoordsOperation.addSequel(calculateCoordsOperation); + } + + /** + * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины + */ + private function calculateCoords():void { + globalCoords.copy(_coords); + globalCoords.transform(_mesh._transformation); + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * координата вершины по оси X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * координата вершины по оси Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * координата вершины по оси Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * Координаты вершины. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * Полигональный объект, которому принадлежит вершина. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса + * altertnativa.engine3d.core.Face. + * + * @see Face + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getVertexId(this) : null; + } + + /** + * @private + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену расчитать глобальные координаты + scene.addOperation(calculateCoordsOperation); + } + + /** + * @private + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateCoordsOperation); + scene.removeOperation(changeCoordsOperation); + } + + /** + * @private + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation); + // Удалить зависимые грани + for (var key:* in _faces) { + var face:Face = key; + mesh.removeFace(face); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * @param face + */ + alternativa3d function addToFace(face:Face):void { + // Подписка грани на операции + changeCoordsOperation.addSequel(face.calculateUVOperation); + changeCoordsOperation.addSequel(face.calculateNormalOperation); + // Добавить грань в список + _faces.add(face); + } + + /** + * @private + * @param face + */ + alternativa3d function removeFromFace(face:Face):void { + // Отписка грани от операций + changeCoordsOperation.removeSequel(face.calculateUVOperation); + changeCoordsOperation.removeSequel(face.calculateNormalOperation); + // Удалить грань из списка + _faces.remove(face); + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as new file mode 100644 index 0000000..0bb7f21 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as @@ -0,0 +1,65 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.SpritePrimitive; + import alternativa.engine3d.materials.Material; + + import flash.display.Graphics; + import flash.display.Sprite; + + use namespace alternativa3d; + + /** + * @private + * Контейнер, используемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере. + */ + public class Skin extends Sprite { + + /** + * @private + * Графика скина (для быстрого доступа) + */ + alternativa3d var gfx:Graphics = graphics; + + /** + * @private + * Ссылка на следующий скин + */ + alternativa3d var nextSkin:Skin; + + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + /** + * @private + * Материал, связанный со скином. + */ + alternativa3d var material:Material; + + // Хранилище неиспользуемых скинов + static private var collector:Array = new Array(); + + /** + * @private + * Создание скина. + */ + static alternativa3d function createSkin():Skin { + var skin:Skin; + if ((skin = collector.pop()) != null) { + return skin; + } + return new Skin(); + } + + /** + * @private + * Удаление скина, все ссылки должны быть почищены. + */ + static alternativa3d function destroySkin(skin:Skin):void { + collector.push(skin); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/display/View.as b/Alternativa3D5/5.5/alternativa/engine3d/display/View.as new file mode 100644 index 0000000..1566aac --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/display/View.as @@ -0,0 +1,820 @@ +package alternativa.engine3d.display { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Sprite3D; + import alternativa.engine3d.core.SpritePrimitive; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.events.MouseEvent3D; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + + import flash.display.Sprite; + import flash.events.MouseEvent; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Область для вывода изображения с камеры. + */ + public class View extends Sprite { + + /** + * @private + * Область отрисовки спрайтов + */ + alternativa3d var canvas:Sprite; + + private var _camera:Camera3D; + + /** + * @private + * Ширина области вывода + */ + alternativa3d var _width:Number; + /** + * @private + * Высота области вывода + */ + alternativa3d var _height:Number; + + /** + * @private + * Флаг интерактивности вьюпорта. В интерактивном режиме вьюпорт принимает события мыши и транслирует их + * в подсистему трёхмерных событий, которая, в свою очередь, преобразует двумерный клик в трёхмерный и рассылает + * события всем подписчикам. + */ + alternativa3d var _interactive:Boolean; + + // Грань под курсором + private var faceUnderPoint:Face; + private var objectUnderPoint:Object3D; + + private var lastMouseEvent:MouseEvent; + private var stagePoint:Point = new Point(); + + // Текущая грань + private var currentFace:Face; + // Текущая поверхность + private var currentSurface:Surface; + // Текущий объект + private var currentObject:Object3D; + // Грань, на которой было событие MOUSE_DOWN + private var pressedFace:Face; + // Поверхность, на которой было событие MOUSE_DOWN + private var pressedSurface:Surface; + // Объект, на которой было событие MOUSE_DOWN + private var pressedObject:Object3D; + + // Направляющий вектор проецирующей прямой в камере + private var lineVector:Point3D = new Point3D(); + // Вспомогательная переменная для хранения точки проецирующей прямой в ортографической камере + private var linePoint:Point3D = new Point3D(); + // Точка на объекте под курсором в глобальной системе координат. + private var globalCursor3DCoords:Point3D = new Point3D(); + // Координаты курсора в системе координат объекта + private var localCursor3DCoords:Point3D = new Point3D(); + // UV-координаты в грани под курсором + private var uvPoint:Point = new Point(); + // Вспомогательная матрица + private var inverseMatrix:Matrix3D = new Matrix3D(); + + /** + * Создание экземпляра области вывода. + * + * @param camera камера, изображение с которой должно выводиться + * @param width ширина области вывода + * @param height высота области вывода + */ + public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) { + canvas = new Sprite(); + canvas.mouseEnabled = false; + canvas.mouseChildren = false; + canvas.tabEnabled = false; + canvas.tabChildren = false; + addChild(canvas); + + this.camera = camera; + this.width = width; + this.height = height; + } + + /** + * Камера с которой ведётся отображение. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + // Если была камера + if (_camera != null) { + // Удалить камеру + _camera.removeFromView(this); + } + // Если новая камера + if (value != null) { + // Если камера была в другом вьюпорте + if (value._view != null) { + // Удалить её оттуда + value._view.camera = null; + } + // Добавить камеру + value.addToView(this); + } else { + // Зачистка скинов + if (canvas.numChildren > 0) { + var skin:Skin = Skin(canvas.getChildAt(0)); + while (skin != null) { + // Сохраняем следующий + var next:Skin = skin.nextSkin; + // Удаляем из канваса + canvas.removeChild(skin); + // Очистка скина + if (skin.material != null) { + skin.material.clear(skin); + } + // Зачищаем ссылки + skin.nextSkin = null; + skin.primitive = null; + skin.material = null; + // Удаляем + Skin.destroySkin(skin); + // Следующий устанавливаем текущим + skin = next; + } + } + } + // Сохраняем камеру + _camera = value; + } + } + + /** + * Ширина области вывода в пикселях. + */ + override public function get width():Number { + return _width; + } + + /** + * @private + */ + override public function set width(value:Number):void { + if (_width != value) { + _width = value; + canvas.x = _width*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Высота области вывода в пикселях. + */ + override public function get height():Number { + return _height; + } + + /** + * @private + */ + override public function set height(value:Number):void { + if (_height != value) { + _height = value; + canvas.y = _height*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Метод возвращает объект, находящийся под указанной точкой в области вывода. + * + * @param viewPoint координаты точки относительно области вывода + * + * @return ближайший к камере объект под заданной точкой области вывода. Объект может быть гранью (Face) или спрайтом (Sprite3D). + */ + public function getObjectUnderPoint(viewPoint:Point):Object { + var stagePoint:Point = localToGlobal(viewPoint); + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var skin:Skin; + for (var i:int = objects.length - 1; i >= 0; i--) { + skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + return skin.primitive.face != null ? skin.primitive.face : (skin.primitive as SpritePrimitive).sprite; + } + } + return null; + } + + /** + * Метод возвращает объекты, находящиеся под указанной точкой в области вывода. + * + * @param viewPoint координаты точки относительно области вывода + * + * @return массив объектов, расположенных под заданной точкой области вывода. Первым элементом массива является самый дальний объект. + * Объектами могут быть грани (Face) или спрайты (Sprite3D). + */ + override public function getObjectsUnderPoint(viewPoint:Point):Array { + var stagePoint:Point = localToGlobal(viewPoint); + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var res:Array = new Array(); + var length:uint = objects.length; + for (var i:uint = 0; i < length; i++) { + var skin:Skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + if (skin.primitive.face != null) { + res.push(skin.primitive.face); + } else { + res.push((skin.primitive as SpritePrimitive).sprite); + } + } + } + return res; + } + + /** + * Проецирование точки, заданной глобальными координатами, на плоскость области вывода. + * + * @param point глобальные координаты проецируемой точки + * + * @return объект Point3D, содержащий координаты проекции точки относительно левого верхнего угла области вывода и z-координату + * точки в системе камеры. В случае, если области вывода не назначена камера или камера не находится в сцене, возвращается null. + */ + public function projectPoint(point:Point3D):Point3D { + if (_camera == null || _camera._scene == null) { + return null; + } + + var cameraMatrix:Matrix3D = Object3D.matrix2; + var focalLength:Number = _camera.focalLength;; + var zoom:Number; + + // Вычисление матрицы трансформации камеры + if (camera.getTransformation(cameraMatrix)) { + // Матрица была пересчитана заново + cameraMatrix.invert(); + if (_camera._orthographic) { + // Учёт масштабирования в ортографической камере + zoom = _camera.zoom; + cameraMatrix.scale(zoom, zoom, zoom); + } + } else { + // Пересчёта не потребовалось, проверяем изменение зума + if (_camera._orthographic && _camera.calculateMatrixOperation.queued) { + cameraMatrix.invert(); + zoom = _camera.zoom; + cameraMatrix.scale(zoom, zoom, zoom); + } else { + // Зум не менялся или перспективный режим, просто копируем обратную матрицу + cameraMatrix = _camera.cameraMatrix; + } + } + // Расчёт фокусного расстояния + if (!_camera._orthographic && _camera.calculatePlanesOperation.queued) { + focalLength = 0.5 * Math.sqrt(_height * _height + _width * _width) / Math.tan(0.5 * _camera._fov); + } + // Координаты точки в системе координат камеры + var x:Number = cameraMatrix.a * point.x + cameraMatrix.b * point.y + cameraMatrix.c * point.z + cameraMatrix.d; + var y:Number = cameraMatrix.e * point.x + cameraMatrix.f * point.y + cameraMatrix.g * point.z + cameraMatrix.h; + var z:Number = cameraMatrix.i * point.x + cameraMatrix.j * point.y + cameraMatrix.k * point.z + cameraMatrix.l; + // Проекция точки на вьюпорт + if (_camera._orthographic) { + return new Point3D(x + (_width >> 1), y + (_height >> 1), z); + } else { + return new Point3D(x * focalLength / z + (_width >> 1), y * focalLength / z + (_height >> 1), z); + } + } + + /** + * Интерактивность области вывода. При включённой интерактивности возможно взаимодействие мыши с отрисованными объектами. + */ + public function get interactive():Boolean { + return _interactive; + } + + /** + * @private + */ + public function set interactive(value:Boolean):void { + if (_interactive == value) { + return; + } + _interactive = value; + + if (_interactive) { + addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + addEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent); + addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent); + addEventListener(MouseEvent.MOUSE_OUT, onMouseEvent); + } else { + removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_OUT, onMouseEvent); + pressedFace = currentFace = null; + pressedSurface = currentSurface = null; + pressedObject = currentObject = null; + } + } + + /** + * Сброс нажатых объектов при отпускании кнопки мыши вне вьюпорта. + */ + private function stageMouseUp(e:MouseEvent):void { + pressedFace = null; + pressedSurface = null; + pressedObject = null; + stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp); + } + + /** + * Метод находит интерактивный объект (Object3D) и интерактивную грань, если возможно, находящиеся под указанной точкой в области вывода. + * + * @param pointX X-координата точки относительно области вывода + * @param pointY Y-координата точки относительно области вывода + */ + private function getInteractiveObjectUnderPoint(pointX:Number, pointY:Number):void { + faceUnderPoint = null; + objectUnderPoint = null; + stagePoint.x = pointX; + stagePoint.y = pointY; + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var skin:Skin; + for (var i:int = objects.length - 1; i >= 0; i--) { + skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + if (skin.primitive.face != null) { + // Скин, содержащий PolyPrimitive + if (skin.primitive.face._mesh.mouseEnabled) { + faceUnderPoint = skin.primitive.face; + objectUnderPoint = faceUnderPoint._mesh; + return; + } + } else { + // Скин, содержащий SpritePrimitive + var sprite:Sprite3D = (skin.primitive as SpritePrimitive).sprite; + if (sprite.mouseEnabled) { + objectUnderPoint = sprite; + return; + } + } + } + } + } + + /** + * Вычисление свойств точки объекта, находящегося под указанной точкой фокусной плоскости камеры. Метод расчитывает глобальные и локальные + * 3D-координаты точки, а также её UV-координаты. + * + * @param canvasX + * @param canvasY + */ + private function getInteractiveObjectPointProperties(canvasX:Number, canvasY:Number):void { + if (objectUnderPoint == null) { + return; + } + // Координаты локального направляющего вектора + var x:Number; + var y:Number; + var z:Number; + // Вычисление направляющего вектора и точки проецирующей прямой в глобальном пространстве + var m:Matrix3D = _camera._transformation; + if (_camera._orthographic) { + x = y = 0; + z = 1; + linePoint.x = canvasX / _camera.zoom; + linePoint.y = canvasY / _camera.zoom; + linePoint.z = 0; + linePoint.transform(m); + } else { + x = canvasX; + y = canvasY; + z = _camera.focalLength; + linePoint.copy(_camera.globalCoords); + } + // Направляющий вектор в глобальном пространстве + lineVector.x = x * m.a + y * m.b + z * m.c; + lineVector.y = x * m.e + y * m.f + z * m.g; + lineVector.z = x * m.i + y * m.j + z * m.k; + // Вычисление глобальных координат точки пересечения проецирующей прямой и плоскости объекта + var normal:Point3D; + var offset:Number; + if (faceUnderPoint != null) { + // Работаем с гранью + normal = faceUnderPoint.globalNormal; + offset = faceUnderPoint.globalOffset; + } else { + // Работаем со спрайтом + normal = lineVector.clone(); + normal.multiply(-1); + globalCursor3DCoords.copy(objectUnderPoint._coords); + globalCursor3DCoords.transform(objectUnderPoint._transformation); + offset = Point3D.dot(globalCursor3DCoords, normal); + } + getLineAndPlaneIntersection(linePoint, lineVector, normal, offset, globalCursor3DCoords); + // Вычисление локальных координат точки пересечения + inverseMatrix.copy((faceUnderPoint != null ? faceUnderPoint._mesh : objectUnderPoint)._transformation); + inverseMatrix.invert(); + localCursor3DCoords.copy(globalCursor3DCoords); + localCursor3DCoords.transform(inverseMatrix); + // Вычисление UV-координат + if (faceUnderPoint != null) { + var uv:Point = faceUnderPoint.getUV(localCursor3DCoords); + if (uv != null) { + uvPoint.x = uv.x; + uvPoint.y = uv.y; + } else { + uvPoint.x = NaN; + uvPoint.y = NaN; + } + } + } + + /** + * + */ + private function createSimpleMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D { + var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey; + var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey; + var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey; + var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta; + return new MouseEvent3D(type, this, object, surface, face, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + altKey, ctrlKey, shiftKey, delta); + } + + /** + * + */ + private function createFullMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D { + var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey; + var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey; + var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey; + var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta; + return new MouseEvent3D(type, this, object, surface, face, + globalCursor3DCoords.x, globalCursor3DCoords.y, globalCursor3DCoords.z, localCursor3DCoords.x, localCursor3DCoords.y, localCursor3DCoords.z, uvPoint.x, uvPoint.y, + altKey, ctrlKey, shiftKey, delta); + } + + /** + * Обработка мышиного события на вьюпорте и передача его в систему трёхмерных событий. + */ + private function onMouseEvent(e:MouseEvent):void { + // Сохранение события для использования в функциях создания MouseEvent3D + lastMouseEvent = e; + // Получение объекта под курсором и свойств точки на этом объекте + getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY); + getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1)); + // Обработка события + switch (e.type) { + case MouseEvent.MOUSE_MOVE: + processMouseMove(); + break; + case MouseEvent.MOUSE_OUT: + stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUp); + checkMouseOverOut(); + break; + case MouseEvent.MOUSE_DOWN: + processMouseDown(); + break; + case MouseEvent.MOUSE_UP: + processMouseUp(); + break; + case MouseEvent.MOUSE_WHEEL: + processMouseWheel(); + break; + } + lastMouseEvent = null; + } + + /** + * Обработка нажатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_DOWN. + */ + private function processMouseDown():void { + if (objectUnderPoint == null) { + return; + } + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + } else { + currentFace = null; + currentSurface = null; + } + currentObject = pressedObject = objectUnderPoint; + + var evt:MouseEvent3D; + if (currentFace != null && currentFace.mouseEnabled) { + pressedFace = currentFace; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface.mouseEnabled) { + pressedSurface = currentSurface; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + /** + * Обработка отжатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_UP, MouseEvent3D.CLICK. + */ + private function processMouseUp():void { + if (objectUnderPoint == null) { + pressedFace = null; + pressedSurface = null; + pressedObject = null; + return; + } + + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + } else { + currentFace = null; + currentSurface = null; + } + currentObject = objectUnderPoint; + + var evt:MouseEvent3D; + // MouseEvent3D.MOUSE_UP + if (currentFace != null && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + + // MouseEvent3D.CLICK + if (currentFace != null && currentFace == pressedFace && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface == pressedSurface && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + if (currentObject == pressedObject) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + pressedFace = null; + pressedSurface = null; + pressedObject = null; + } + + /** + * Обработка вращения колеса мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_WHEEL. + */ + private function processMouseWheel():void { + if (objectUnderPoint == null) { + return; + } + + var evt:MouseEvent3D; + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + + if (currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } else { + currentFace = null; + currentSurface = null; + } + currentObject = objectUnderPoint; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + /** + * @private + * Метод проверяет наличиче событий MOUSE_OVER, MOUSE_OUT для объектов сцены, их поверхностей и граней. + * + * @param checkObject флаг необходимости предварительно получить объект под курсором. Используется при вызове метода из функции отрисовки камеры. + */ + alternativa3d function checkMouseOverOut(checkObject:Boolean = false):void { + if (checkObject) { + getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY); + getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1)); + } + var evt:MouseEvent3D; + if (objectUnderPoint == null) { + // Мышь ушла с объекта, генерируются события MOUSE_OUT + if (currentFace != null) { + // MOUSE_OUT для грани + if (currentFace.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + // MOUSE_OUT для поверхности + if (currentSurface.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + + if (currentObject != null) { + // MOUSE_OUT для объекта + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + currentFace = null; + currentSurface = null; + currentObject = null; + } else { + // Мышь на каком-то объекте + var surface:Surface; + var faceChanged:Boolean; + var surfaceChanged:Boolean; + var objectChanged:Boolean; + + if (faceUnderPoint != null) { + surface = faceUnderPoint._surface; + } + // + if (faceUnderPoint != currentFace) { + // MOUSE_OUT для грани + if (currentFace != null && currentFace.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + faceChanged = true; + // MOUSE_OUT для поверхности + if (surface != currentSurface) { + if (currentSurface != null && currentSurface.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + surfaceChanged = true; + } + } + // MOUSE_OUT для объекта + if (objectUnderPoint != currentObject) { + if (currentObject != null) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + objectChanged = true; + } + + currentFace = faceUnderPoint; + currentSurface = surface; + currentObject = objectUnderPoint; + if (currentFace != null) { + // MOUSE_OVER для грани + if (faceChanged && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + // MOUSE_OVER для поверхности + if (surfaceChanged && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + // MOUSE_OVER для объекта + if (objectChanged) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + } + } + + /** + * Обработчик движения мыши. + */ + private function processMouseMove():void { + // Запуск проверки на наличие событий MOUSE_OVER и MOUSE_OUT + checkMouseOverOut(); + // Генерация событий MOUSE_MOVE + var evt:MouseEvent3D; + if (currentFace != null) { + // Мышь на каком-то объекте + if (currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + + if (currentObject != null) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + } + + /** + * Вычисление точки пересечения прямой и плоскости. + * + * @param linePoint точка, лежащая на прямой + * @param lineVector направляющий вектор прямой + * @param planeNormal нормаль плоскости + * @param planeOffset смещение плоскости + * @param result переменная для сохранения координат точки пересечения. Если прямая и плоскость параллельны, то будут записаны значения NaN. + */ + private function getLineAndPlaneIntersection(linePoint:Point3D, lineVector:Point3D, planeNormal:Point3D, planeOffset:Number, result:Point3D):void { + var dot:Number = planeNormal.x * lineVector.x + planeNormal.y * lineVector.y + planeNormal.z * lineVector.z; + if (dot == 0) { + // Прямая и плосоксть параллельны + result.x = NaN; + result.y = NaN; + result.z = NaN; + return; + } + // Выбор оси с максимальной по модулю координатой + var axis:int = 0; + var max:Number = lineVector.x; + if (max < lineVector.y && max > -lineVector.y) { + max = lineVector.y; + axis = 1; + } + if (max < lineVector.z && max > -lineVector.z) { + axis = 2; + } + + var x:Number; + var y:Number; + var z:Number; + switch (axis) { + case 0: + x = (planeOffset * lineVector.x + planeNormal.y * (linePoint.x * lineVector.y - linePoint.y * lineVector.x) + planeNormal.z * (linePoint.x * lineVector.z - linePoint.z * lineVector.x)) / dot; + result.x = x; + result.y = (x - linePoint.x) * lineVector.y / lineVector.x + linePoint.y; + result.z = (x - linePoint.x) * lineVector.z / lineVector.x + linePoint.z; + break; + case 1: + y = (planeOffset * lineVector.y + planeNormal.x * (linePoint.y * lineVector.x - linePoint.x * lineVector.y) + planeNormal.z * (linePoint.y * lineVector.z - linePoint.z * lineVector.y)) / dot; + result.x = (y - linePoint.y) * lineVector.x / lineVector.y + linePoint.x; + result.y = y; + result.z = (y - linePoint.y) * lineVector.z / lineVector.y + linePoint.z; + break; + case 2: + z = (planeOffset * lineVector.z + planeNormal.x * (linePoint.z * lineVector.x - linePoint.x * lineVector.z) + planeNormal.y * (linePoint.z * lineVector.y - linePoint.y * lineVector.z)) / dot; + result.x = (z - linePoint.z) * lineVector.x / lineVector.z + linePoint.x; + result.y = (z - linePoint.z) * lineVector.y / lineVector.z + linePoint.y; + result.z = z; + break; + } + } + + /** + * Получение координат точки в системе координат камеры, связанной с областью вывода. Если камера в режиме перспективной + * проекции, то метод вернёт координаты точки, лежащей на прямой, проходящей через начало координат камеры и указанную точку + * области вывода. Если камера в режиме ортографической проекции, то метод вернёт координаты точки, лежащей на прямой, + * перпендикулярной фокальной плоскости камеры и проходящей через указанную точку области вывода. + * + * @param viewPoint координаты точки в области вывода + * @param depth глубина точки в камере — координата Z в системе координат камеры + * @return координаты точки в системе координат камеры или null, если с областью вывода не связана камера + */ + public function get3DCoords(viewPoint:Point, depth:Number):Point3D { + if (_camera == null) { + return null; + } + if (_camera._orthographic) { + return new Point3D((viewPoint.x - (_width >> 1)), (viewPoint.y - (_height >> 1)), depth); + } else { + var k:Number = depth / _camera.focalLength; + return new Point3D((viewPoint.x - (_width >> 1))* k, (viewPoint.y - (_height >> 1)) * k, depth); + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as new file mode 100644 index 0000000..7c3edd0 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as @@ -0,0 +1,25 @@ +package alternativa.engine3d.errors { + + /** + * Базовый класс для ошибок 3d-engine. + */ + public class Engine3DError extends Error { + + /** + * Источник ошибки - объект в котором произошла ошибка. + */ + public var source:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param source источник ошибки + */ + public function Engine3DError(message:String = "", source:Object = null) { + super(message); + this.source = source; + this.name = "Engine3DError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as new file mode 100644 index 0000000..ecc7f59 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as @@ -0,0 +1,35 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Surface; + + /** + * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте. + */ + public class FaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани, которая уже содержится в объекте + * @param source источник ошибки + */ + public function FaceExistsError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else if (source is Surface) { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 already exists."; + } else { + message += "%1. Face with ID '%2' already exists."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as new file mode 100644 index 0000000..3341f63 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая недостаточное количество вершин для создания грани. + * Для создания грани должно быть указано не менее трех вершин. + */ + public class FaceNeedMoreVerticesError extends Engine3DError { + + /** + * Количество переданных для создания грани вершин + */ + public var count:uint; + + /** + * Создание экземпляра класса. + * + * @param mesh объект, в котором произошла ошибка + * @param count количество вершин, переданное для создания грани + */ + public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) { + super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh); + this.count = count; + this.name = "FaceNeedMoreVerticesError"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as new file mode 100644 index 0000000..912d6a4 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, если грань не найдена в объекте. + */ + public class FaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани + * @param source объект, в котором произошла ошибка + */ + public function FaceNotFoundError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 not found."; + } else { + message += "%1. Face with ID '%2' not found."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as new file mode 100644 index 0000000..fbf4666 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + + + /** + * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован. + */ + public class InvalidIDError extends Engine3DError { + /** + * Зарезервированный идентификатор + */ + public var id:Object; + + /** + * Создание экземпляра класса. + * + * @param id идентификатор + * @param source объект, в котором произошла ошибка + */ + public function InvalidIDError(id:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh %2. "; + } else if (source is Surface) { + message = "Surface %2. "; + } + super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source); + this.id = id; + this.name = "InvalidIDError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as new file mode 100644 index 0000000..8d61bd0 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, связанная с нарушением иерархии объектов сцены. + */ + public class Object3DHierarchyError extends Engine3DError + { + + /** + * Объект сцены, нарушающий иерархию + */ + public var object:Object3D; + + /** + * Создание экземпляра класса. + * + * @param object объект, нарушающий иерархию + * @param source источник ошибки + */ + public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source); + this.object = object; + this.name = "Object3DHierarchyError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as new file mode 100644 index 0000000..d1ddd1e --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены. + */ + public class Object3DNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param object ненайденный объект сцены + * @param source объект сцены, в котором произошла ошибка + */ + public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source); + this.name = "Object3DNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as new file mode 100644 index 0000000..7743a53 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Ошибка, обозначающая, что объект уже присутствует в контейнере. + */ + public class ObjectExistsError extends Engine3DError { + + /** + * Экземпляр или идентификатор объекта, который уже присутствует в контейнере + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object объект, который уже присутствует в контейнере + * @param source объект, вызвавший ошибку + */ + public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectExistsError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as new file mode 100644 index 0000000..87b3d14 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Необходимый объект не был найден в контейнере. + */ + public class ObjectNotFoundError extends Engine3DError { + + /** + * Объект, который отсутствует в контейнере. + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object отсутствующий объект + * @param source объект, вызвавший ошибку + */ + public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as new file mode 100644 index 0000000..e13bee7 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность уже присутствует в контейнере. + */ + public class SurfaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая уже присутствует в контейнере + * @param mesh объект, вызвавший ошибку + */ + public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh); + this.name = "SurfaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as new file mode 100644 index 0000000..05457bf --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as @@ -0,0 +1,31 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность не найдена в контейнере. + */ + public class SurfaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая отсутствует в объекте + * @param mesh объект, который вызвал ошибку + */ + public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) { + if (mesh == null) { + + } + if (surface is Surface) { + message = "Mesh %1. Surface %2 not found."; + } else { + message = "Mesh %1. Surface with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, surface), surface, mesh); + this.name = "SurfaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as new file mode 100644 index 0000000..9f0fff5 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина уже содержится в объекте. + */ + public class VertexExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая уже есть в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh); + this.name = "VertexExistsError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as new file mode 100644 index 0000000..edf80aa --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина не найдена в объекте. + */ + public class VertexNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая не найдена в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) { + if (vertex is Vertex) { + message = "Mesh %1. Vertex %2 not found."; + } else { + message = "Mesh %1. Vertex with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh); + this.name = "VertexNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as b/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as new file mode 100644 index 0000000..283b83d --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as @@ -0,0 +1,171 @@ +package alternativa.engine3d.events { + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.View; + + import flash.events.Event; + + /** + * Событие, возникающее при взаимодействии мыши с объектами сцены. + */ + public class MouseEvent3D extends Event { + /** + * Значение свойства type для объекта события click. + * @eventType click + */ + public static const CLICK:String = "click"; + /** + * Значение свойства type для объекта события mouseDown. + * @eventType mouseDown + */ + public static const MOUSE_DOWN:String = "mouseDown"; + /** + * Значение свойства type для объекта события mouseUp. + * @eventType mouseUp + */ + public static const MOUSE_UP:String = "mouseUp"; + /** + * Значение свойства type для объекта события mouseOver. + * @eventType mouseOver + */ + public static const MOUSE_OVER:String = "mouseOver"; + /** + * Значение свойства type для объекта события mouseOut. + * @eventType mouseOut + */ + public static const MOUSE_OUT:String = "mouseOut"; + /** + * Значение свойства type для объекта события mouseMove. + * @eventType mouseMove + */ + public static const MOUSE_MOVE:String = "mouseMove"; + /** + * Значение свойства type для объекта события mouseWheel. + * @eventType mouseWheel + */ + public static const MOUSE_WHEEL:String = "mouseWheel"; + + /** + * Объект сцены, с которым связано событие. + */ + public var object:Object3D; + /** + * Поверхность объекта сцены, с которой связано событие. + */ + public var surface:Surface; + /** + * Грань объекта сцены, с которой связано событие. + */ + public var face:Face; + /** + * Область вывода, в которой произошло событие. + */ + public var view:View; + + /** + * X-координата мышиного курсора в сцене. + */ + public var globalX:Number; + /** + * Y-координата мышиного курсора в сцене. + */ + public var globalY:Number; + /** + * Z-координата мышиного курсора в сцене. + */ + public var globalZ:Number; + + /** + * X-координата мышиного курсора в системе координат объекта. + */ + public var localX:Number; + /** + * Y-координата мышиного курсора в системе координат объекта. + */ + public var localY:Number; + /** + * Z-координата мышиного курсора в системе координат объекта. + */ + public var localZ:Number; + + /** + * Текстурная координата U в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN. + */ + public var u:Number; + /** + * Текстурная координата V в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN. + */ + public var v:Number; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Alt. + */ + public var altKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Control. + */ + public var ctrlKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Shift. + */ + public var shiftKey:Boolean; + /** + * Количество линий прокрутки при вращении колеса мыши. + */ + public var delta:int; + + /** + * Создаёт новый экземпляр события. + * + * @param type тип события + * @param view область вывода, в которой произошло событие + * @param object объект сцены, с которым связано событие + * @param surface поверхность, с которой связано событие + * @param face грань, с которой связано событие + * @param globalX X-координата мышиного курсора в сцене + * @param globalY Y-координата мышиного курсора в сцене + * @param globalZ Z-координата мышиного курсора в сцене + * @param localX X-координата мышиного курсора в системе координат объекта + * @param localY Y-координата мышиного курсора в системе координат объекта + * @param localZ Z-координата мышиного курсора в системе координат объекта + * @param u текстурная координата U в точке нахождения мышиного курсора + * @param v текстурная координата V в точке нахождения мышиного курсора + */ + public function MouseEvent3D(type:String, view:View, object:Object3D, surface:Surface, face:Face, globalX:Number = NaN, globalY:Number = NaN, globalZ:Number = NaN, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, u:Number = NaN, v:Number = NaN, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) { + super(type); + this.object = object; + this.surface = surface; + this.face = face; + this.globalX = globalX; + this.globalY = globalY; + this.globalZ = globalZ; + this.localX = localX; + this.localY = localY; + this.localZ = localZ; + this.u = u; + this.v = v; + this.altKey = altKey; + this.ctrlKey = ctrlKey; + this.shiftKey = shiftKey; + this.delta = delta; + } + + /** + * Получение строкового представления объекта. + * + * @return строковое представление объекта + */ + override public function toString():String { + return formatToString("MouseEvent3D", "object", "surface", "face", "globalX", "globalY", "globalZ", "localX", "localY", "localZ", "u", "v", "delta", "altKey", "ctrlKey", "shiftKey"); + } + + /** + * Возвращает клон объекта. + * + * @return клон события + */ + override public function clone():Event { + return new MouseEvent3D(type, view, object, surface, face, globalX, globalY, globalZ, localX, localY, localZ, u, v, altKey, ctrlKey, shiftKey, delta); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..197ed5d --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,1114 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Texture; + import alternativa.utils.ColorUtils; + import alternativa.utils.MathUtils; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * Событие посылается после завершения загрузки модели. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки, связанной с вводом-выводом. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие посылается при нарушении правил безопасности при загрузке файла модели. + * + * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR + */ + [Event (name="securityError", type="flash.events.SecurityErrorEvent")] + /** + * Загрузчик моделей в формате 3DS. + * + *

Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если + * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели. + * + *

Из файла модели загружаются: + *

+ * + * Порядок загрузки каждого объекта из 3DS-файла: + * + * + * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы. + */ + public class Loader3DS extends EventDispatcher { + + /** + * Коэффициент пересчёта в дюймы. + */ + public static const INCHES:Number = 1; + + /** + * Коэффициент пересчёта в футы. + */ + public static const FEET:Number = 0.0833333334; + /** + * Коэффициент пересчёта в мили. + */ + public static const MILES:Number = 0.0000157828; + /** + * Коэффициент пересчёта в миллиметры. + */ + public static const MILLIMETERS:Number = 25.4000000259; + /** + * Коэффициент пересчёта в сантиметры. + */ + public static const CENTIMETERS:Number = 2.5400000025; + /** + * Коэффициент пересчёта в метры. + */ + public static const METERS:Number = 0.0254; + /** + * Коэффициент пересчёта в километры. + */ + public static const KILOMETERS:Number = 0.0000254; + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_MODEL:int = 0; + private static const STATE_LOADING_TEXTURES:int = 1; + + private static var stubBitmapData:BitmapData; + + private var _content:Object3D; + private var version:uint; + private var objectDatas:Map; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureMapsLoader; + private var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + private var loaderState:int = STATE_IDLE; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = INCHES; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_MODEL) { + modelLoader.close(); + } + if (loaderState == STATE_LOADING_TEXTURES) { + textureLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + } + } + + /** + * Очистка сылок. + */ + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + /** + * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL 3DS-файла + * @param context LoaderContext для загрузки файлов текстур + */ + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + version = 0; + clean(); + + if (modelLoader == null) { + modelLoader = new URLLoader(); + modelLoader.dataFormat = URLLoaderDataFormat.BINARY; + modelLoader.addEventListener(Event.COMPLETE, on3DSLoad); + modelLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + modelLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + loaderState = STATE_LOADING_MODEL; + modelLoader.load(new URLRequest(url)); + } + + /** + * Запуск разбора загруженного 3DS-файла. + */ + private function on3DSLoad(e:Event):void { + loaderState = STATE_IDLE; + data = modelLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + /** + * Обработка ошибки при загрузке 3DS-файла. + */ + private function on3DSError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + /** + * Запуск загрузки файлов текстур. + */ + private function loadBitmaps():void { + if (textureLoader == null) { + textureLoader = new TextureMapsLoader(); + textureLoader.addEventListener(Event.COMPLETE, loadNextBitmap); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + } + + // Имена материалов с диффузными текстурами собираются в массив textureMaterialNames + bitmaps = new Array(); + textureMaterialNames = new Array(); + for each (var materialData:MaterialData in materialDatas) { + if (materialData.diffuseMap != null) { + textureMaterialNames.push(materialData.name); + } + } + + loaderState = STATE_LOADING_TEXTURES; + loadNextBitmap(); + } + + /** + * Запуск загрузки следующей текстуры в очереди. + */ + private function loadNextBitmap(e:Event = null):void { + if (e != null) { + if (!(e is IOErrorEvent)) { + bitmaps[textureMaterialNames[counter]] = textureLoader.bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[textureMaterialNames[counter]] = stubBitmapData; + } + } else { + counter = -1; + } + counter++; + if (counter < textureMaterialNames.length) { + var materialData:MaterialData = materialDatas[textureMaterialNames[counter]]; + textureLoader.load(path + materialData.diffuseMap.filename, materialData.opacityMap == null ? null : path + materialData.opacityMap.filename, context); + } else { + loaderState = STATE_IDLE; + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Object3D(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = MathUtils.toRadian(mapData.rotation); + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Array().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Array().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + } else { + var object:Object3D = new Object3D(objectName); + animationData.object = object; + buildObject(animationData); + } + } + + buildHierarchy(_content, 0, length - 1); + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + buildMesh(mesh, objectData, null); + _content.addChild(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Object3D = animationData.object; + if (animationData.position != null) { + object.x = animationData.position.x * units; + object.y = animationData.position.y * units; + object.z = animationData.position.z * units; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + object.mobility = mobility; + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var key:*; + var vertex:Vertex; + var face:Face; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Point3D = objectData.vertices[i]; + objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i); + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.d -= animationData.pivot.x; + objectData.matrix.h -= animationData.pivot.y; + objectData.matrix.l -= animationData.pivot.z; + } + + // Трансформируем вершины + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.transform(objectData.matrix); + } + } + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.multiply(units); + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i); + if (objectData.uvs != null) { + face.aUV = objectData.uvs[faceData.a]; + face.bUV = objectData.uvs[faceData.b]; + face.cUV = objectData.uvs[faceData.c]; + } + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + var surfaceData:SurfaceData = objectData.surfaces[surfaceId]; + var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId); + if (materialData.diffuseMap != null) { + var bmp:BitmapData = bitmaps[materialData.name]; + surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + length = surfaceData.faces.length; + for (i = 0; i < length; i++) { + face = mesh.getFaceById(surfaceData.faces[i]); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var m:Matrix = materialData.matrix; + var x:Number = face.aUV.x; + var y:Number = face.aUV.y; + face._aUV.x = m.a*x + m.b*y + m.tx; + face._aUV.y = m.c*x + m.d*y + m.ty; + x = face._bUV.x; + y = face._bUV.y; + face._bUV.x = m.a*x + m.b*y + m.tx; + face._bUV.y = m.c*x + m.d*y + m.ty; + x = face._cUV.x; + y = face._cUV.y; + face._cUV.x = m.a*x + m.b*y + m.tx; + face._cUV.y = m.c*x + m.d*y + m.ty; + } + } + } else { + surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100); + } + } + } else { + // Поверхность по умолчанию + var defaultSurface:Surface = mesh.createSurface(); + // Добавляем грани + for (var faceId:String in mesh._faces) { + defaultSurface.addFace(mesh._faces[faceId]); + } + defaultSurface.material = new WireMaterial(0, 0x7F7F7F); + } + } + + private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void { + if (begin <= end) { + var animation:AnimationData = animationDatas[begin]; + var object:Object3D = animation.object; + parent.addChild(object); + + var parentIndex:uint = animation.parentIndex; + for (var i:uint = begin + 1; i <= end; i++) { + animation = animationDatas[i]; + if (parentIndex == animation.parentIndex) { + buildHierarchy(object, begin + 1, i - 1); + buildHierarchy(parent, i, end); + return; + } + } + buildHierarchy(object, begin + 1, end); + } + } + + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte()); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Map(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Array(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Array(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(); + object.matrix.a = data.readFloat(); + object.matrix.e = data.readFloat(); + object.matrix.i = data.readFloat(); + object.matrix.b = data.readFloat(); + object.matrix.f = data.readFloat(); + object.matrix.j = data.readFloat(); + object.matrix.c = data.readFloat(); + object.matrix.g = data.readFloat(); + object.matrix.k = data.readFloat(); + object.matrix.d = data.readFloat(); + object.matrix.h = data.readFloat(); + object.matrix.l = data.readFloat(); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Map(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Object3D { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D { + var res:Point3D = new Point3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.types.Matrix3D; +import alternativa.types.Point3D; + +import flash.geom.Matrix; +import alternativa.types.Map; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Array; + public var uvs:Array; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Map; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:uint; + public var pivot:Point3D; + public var position:Point3D; + public var rotation:Point3D; + public var scale:Point3D; +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as new file mode 100644 index 0000000..74922a7 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as @@ -0,0 +1,346 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.types.Map; + import alternativa.utils.ColorUtils; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * Тип события, возникающего при завершении загрузки модели. + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Тип события, возникающего при ошибке загрузки, связанной с вводом-выводом. + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Тип события, возникающего при нарушении безопасности. + */ + [Event (name="securityError", type="flash.events.SecurityErrorEvent")] + /** + * @private + * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ). + *

+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала. + */ + internal class LoaderMTL extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + private static const CMD_NEW_MATERIAL:String = "newmtl"; + private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd"; + private static const CMD_DISSOLVE:String = "d"; + private static const CMD_MAP_DIFFUSE:String = "map_Kd"; + private static const CMD_MAP_DISSOLVE:String = "map_d"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_LIBRARY:int = 0; + private static const STATE_LOADING_TEXTURES:int = 1; + + // Загрузчик файла MTL + private var mtlFileLoader:URLLoader; + // Загрузчик файлов текстур + private var textureLoader:TextureMapsLoader; + // Контекст загрузки для bitmapLoader + private var loaderContext:LoaderContext; + // Базовый URL файла MTL + private var baseUrl:String; + + // Библиотека загруженных материалов + private var _library:Map; + // Список диффузные текстур. Ключи -- имена материалов, значения -- информация о текстурах. + private var diffuseMaps:Map; + // Список карт прозрачности. Ключи -- имена материалов, значения -- информация о картах прозрачности. + private var dissolveMaps:Map; + // Имя текущего материала + private var materialName:String; + // параметры текущего материала + private var currentMaterialInfo:MaterialInfo = new MaterialInfo(); + + private var loaderState:int = STATE_IDLE; + + alternativa3d static var stubBitmapData:BitmapData; + + /** + * Создаёт новый экземпляр класса. + */ + public function LoaderMTL() { + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_LIBRARY) { + mtlFileLoader.close(); + } + if (loaderState == STATE_LOADING_TEXTURES) { + textureLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + _library = null; + } + } + + /** + * + */ + private function clean():void { + diffuseMaps = null; + dissolveMaps = null; + loaderContext = null; + } + + /** + * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса + * alternativa.engine3d.loaders.MaterialInfo. + * @see alternativa.engine3d.loaders.MaterialInfo + */ + public function get library():Map { + return _library; + } + + /** + * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и + * формирование библиотеки материалов. После окончания работы метода посылается сообщение + * Event.COMPLETE и становится доступна библиотека материалов через свойство library. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку. + *

+ * @param url URL MTL-файла + * @param loaderContext LoaderContext для загрузки файлов текстур + * + * @see #library + */ + public function load(url:String, loaderContext:LoaderContext = null):void { + this.loaderContext = loaderContext; + baseUrl = url.substring(0, url.lastIndexOf("/") + 1); + + if (mtlFileLoader == null) { + mtlFileLoader = new URLLoader(); + mtlFileLoader.addEventListener(Event.COMPLETE, parseMTLFile); + mtlFileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError); + mtlFileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + } + close(); + loaderState = STATE_LOADING_LIBRARY; + mtlFileLoader.load(new URLRequest(url)); + } + + /** + * Разбор содержимого загруженного файла материалов. + */ + private function parseMTLFile(e:Event = null):void { + loaderState = STATE_IDLE; + var lines:Array = mtlFileLoader.data.split(REGEXP_SPLIT_FILE); + _library = new Map(); + diffuseMaps = new Map(); + dissolveMaps = new Map(); + lines.forEach(parseLine); + defineMaterial(); + + if (diffuseMaps.isEmpty()) { + // Текстур нет, загрузка окончена + complete(); + } else { + // Загрузка файлов текстур + loaderState = STATE_LOADING_TEXTURES; + loadNextBitmap(); + } + } + + /** + * Разбор строки файла. + * + * @param line строка файла + */ + private function parseLine(line:String, index:int, lines:Array):void { + line = line.replace(REGEXP_TRIM,"$1") + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + case CMD_NEW_MATERIAL: + defineMaterial(parts); + break; + case CMD_DIFFUSE_REFLECTIVITY: + readDiffuseReflectivity(parts); + break; + case CMD_DISSOLVE: + readAlpha(parts); + break; + case CMD_MAP_DIFFUSE: + parseTextureMapLine(diffuseMaps, parts); + break; + case CMD_MAP_DISSOLVE: + parseTextureMapLine(dissolveMaps, parts); + break; + } + } + + /** + * Определение нового материала. + */ + private function defineMaterial(parts:Array = null):void { + if (materialName != null) { + _library[materialName] = currentMaterialInfo; + } + if (parts != null) { + materialName = parts[1]; + currentMaterialInfo = new MaterialInfo(); + } + } + + /** + * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей + * версии движка данные коэффициенты преобразуются в цвет материала. + */ + private function readDiffuseReflectivity(parts:Array):void { + var r:Number = Number(parts[1]); + // Проверка, заданы ли коэффициенты в виде r g b + if (!isNaN(r)) { + var g:Number = Number(parts[2]); + var b:Number = Number(parts[3]); + currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b); + } + } + + /** + * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом + * (не поддерживается параметр -halo). + */ + private function readAlpha(parts:Array):void { + var alpha:Number = Number(parts[1]); + if (!isNaN(alpha)) { + currentMaterialInfo.alpha = alpha; + } + } + + /** + * Разбор строки, задающей текстурную карту. + */ + private function parseTextureMapLine(map:Map, parts:Array):void { + var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts); + map[materialName] = info; + } + + /** + * Загрузка файла следующей текстуры. + */ + private function loadNextBitmap():void { + if (textureLoader == null) { + textureLoader = new TextureMapsLoader(); + textureLoader.addEventListener(Event.COMPLETE, onBitmapLoadComplete); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete); + } + + // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры + for (materialName in diffuseMaps) { + break; + } + + var diffuseName:String = baseUrl + diffuseMaps[materialName].fileName; + var dissolveName:String; + var dissolveTextureInfo:MTLTextureMapInfo = dissolveMaps[materialName]; + if (dissolveTextureInfo != null) { + dissolveName = dissolveTextureInfo.fileName; + } + + textureLoader.load(diffuseName, dissolveName, loaderContext); + } + + /** + * + */ + private function createStubBitmap():void { + if (stubBitmapData == null) { + var size:uint = 10; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + } + + /** + * Обработка результата загрузки файла текстуры. + */ + private function onBitmapLoadComplete(e:Event):void { + var bmd:BitmapData; + + if (e is ErrorEvent) { + if (stubBitmapData == null) { + createStubBitmap(); + } + bmd = stubBitmapData; + } else { + bmd = textureLoader.bitmapData; + } + + var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName]; + delete diffuseMaps[materialName]; + var materialInfo:MaterialInfo = _library[materialName]; + + materialInfo.bitmapData = bmd; + materialInfo.repeat = mtlInfo.repeat; + materialInfo.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV); + materialInfo.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV); + materialInfo.textureFileName = mtlInfo.fileName; + + if (diffuseMaps.isEmpty()) { + complete(); + } else { + loadNextBitmap(); + } + } + + /** + * Обработка успешного завершения загрузки. + */ + private function complete():void { + loaderState = STATE_IDLE; + if (textureLoader != null) { + textureLoader.unload(); + } + clean(); + dispatchEvent(new Event(Event.COMPLETE)); + } + + /** + * Обработка ошибки загрузки MTL-файла. + */ + private function onError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as new file mode 100644 index 0000000..70710f5 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as @@ -0,0 +1,567 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.types.Texture; + + import flash.display.BlendMode; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * Событие посылается после завершения загрузки модели. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки, связанной с вводом-выводом. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие посылается при нарушении правил безопасности при загрузке файла модели. + * + * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR + */ + [Event (name="securityError", type="flash.events.SecurityErrorEvent")] + /** + * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные + * модели помещаются в один контейнер Object3D. Отдельные модели в файле должны определяться командой + * o object_name. + *

+ * Распознаются следующие ключевые слова формата OBJ: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КомандаОписаниеДействие
o object_nameОбъявление нового объекта с именем object_nameЕсли для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем, + * иначе у текущего объекта просто меняется имя на указанное.
v x y zОбъявление вершины с координатами x y zВершина помещается в общий список вершин сцены для дальнейшего использования
vt u [v]Объявление текстурной вершины с координатами u vВершина помещается в общий список текстурных вершин сцены для дальнейшего использования
f v0[/vt0] v1[/vt1] ... vN[/vtN]Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность + * текущего объекта, соответствующую текущему материалу.
usemtl material_nameУстановка текущего материала с именем material_nameС момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность, + * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
mtllib file1 file2 ...Объявление файлов, содержащих определения материаловВыполняется загрузка файлов и формирование библиотеки материалов. Для текстурных материалов может быть указана + * карта прозрачности, на основе которой формируется канал прозрачности диффузной текстуры. Если размеры карты прозрачности отличаются от + * размеров диффузной текстуры, то карта прозрачности сжимается или растягивается до размеров диффузной текстуры.
+ */ + public class LoaderOBJ extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + + private static const CMD_OBJECT_NAME:String = "o"; + private static const CMD_GROUP_NAME:String = "g"; + private static const CMD_VERTEX:String = "v"; + private static const CMD_TEXTURE_VERTEX:String = "vt"; + private static const CMD_FACE:String = "f"; + private static const CMD_MATERIAL_LIB:String = "mtllib"; + private static const CMD_USE_MATERIAL:String = "usemtl"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_MODEL:int = 0; + private static const STATE_LOADING_LIBRARY:int = 1; + + // Текущее состояние загрузчика + private var loaderState:int = STATE_IDLE; + + private var basePath:String; + private var objLoader:URLLoader; + private var mtlLoader:LoaderMTL; + private var loaderContext:LoaderContext; + private var loadMaterials:Boolean; + // Объект, содержащий все определённые в obj файле объекты + private var _content:Object3D; + // Текущий конструируемый объект + private var currentObject:Mesh; + // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта + private var vIndexStart:int = 0; + // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта + private var vtIndexStart:int = 0; + // Глобальный массив вершин, определённых во входном файле + private var globalVertices:Array; + // Глобальный массив текстурных вершин, определённых во входном файле + private var globalTextureVertices:Array; + // Имя текущего активного материала. Если значение равно null, то активного материала нет. + private var currentMaterialName:String; + // Массив граней текущего объекта, которым назначен текущий материал + private var materialFaces:Array; + // Массив имён файлов, содержащих определения материалов + private var materialFileNames:Array; + private var currentMaterialFileIndex:int; + private var materialLibrary:Map; + private var uv:Point = new Point(); + + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * При установленном значении true выполняется преобразование координат геометрических вершин посредством + * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена + * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z). + */ + public var rotateModel:Boolean; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function LoaderOBJ() { + } + + /** + * Контейнер, содержащий все загруженные из OBJ-файла модели. + */ + public function get content():Object3D { + return _content; + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_MODEL) { + objLoader.close(); + } + if (loaderState == STATE_LOADING_LIBRARY) { + mtlLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + _content = null; + } + } + + /** + * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE, + * после чего контейнер с загруженными объектами становится доступным через свойство content. При вызове метода текущая + * активная загрузка прекращается. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * @param url URL OBJ-файла + * @param loadMaterials флаг загрузки материалов. Если указано значение true, будут обработаны все файлы + * материалов, указанные в исходном OBJ-файле. + * @param context LoaderContext для загрузки файлов текстур + * + * @see #content + */ + public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void { + if (objLoader == null) { + objLoader = new URLLoader(); + objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete); + objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } else { + close(); + } + loaderState = STATE_LOADING_MODEL; + _content = null; + this.loadMaterials = loadMaterials; + this.loaderContext = context; + basePath = url.substring(0, url.lastIndexOf("/") + 1); + objLoader.load(new URLRequest(url)); + } + + /** + * Обработка окончания загрузки obj файла. + * + * @param e + */ + private function onObjLoadComplete(e:Event):void { + parse(objLoader.data); + } + + /** + * Обработка ошибки при загрузке. + * + * @param e + */ + private function onObjLoadError(e:ErrorEvent):void { + close(); + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + loaderState = STATE_IDLE; + clean(); + dispatchEvent(new Event(Event.COMPLETE)); + } + + /** + * + */ + private function clean():void { + globalVertices = null; + globalTextureVertices = null; + materialFileNames = null; + currentObject = null; + loaderContext = null; + } + + /** + * Метод выполняет разбор данных, полученных из obj файла. + * + * @param s содержимое obj файла + * @param materialLibrary библиотека материалов + * @return объект, содержащий все трёхмерные объекты, определённые в obj файле + */ + private function parse(data:String):void { + _content = new Object3D(); + currentObject = new Mesh(); + currentObject.mobility = mobility; + _content.addChild(currentObject); + + globalVertices = new Array(); + globalTextureVertices = new Array(); + materialFileNames = new Array(); + + var lines:Array = data.split(REGEXP_SPLIT_FILE); + lines.forEach(parseLine); + moveFacesToSurface(); + // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах. + if (loadMaterials && materialFileNames.length > 0) { + loadMaterialsLibrary(); + } else { + complete(); + } + } + + /** + * + */ + private function parseLine(line:String, index:int, lines:Array):void { + line = line.replace(REGEXP_TRIM,"$1"); + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + // Объявление нового объекта + case CMD_OBJECT_NAME: + defineObject(parts[1]); + break; + // Объявление вершины + case CMD_VERTEX: + globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление текстурной вершины + case CMD_TEXTURE_VERTEX: + globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление грани + case CMD_FACE: + createFace(parts); + break; + case CMD_MATERIAL_LIB: + storeMaterialFileNames(parts); + break; + case CMD_USE_MATERIAL: + setNewMaterial(parts); + break; + } + } + + /** + * Объявление нового объекта. + * + * @param objectName имя объекта + */ + private function defineObject(objectName:String):void { + if (currentObject._faces.length == 0) { + // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя + currentObject.name = objectName; + } else { + // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект + moveFacesToSurface(); + currentObject = new Mesh(objectName); + currentObject.mobility = mobility; + _content.addChild(currentObject); + } + vIndexStart = globalVertices.length; + vtIndexStart = globalTextureVertices.length; + } + + /** + * Создание грани в текущем объекте. + * + * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1 + */ + private function createFace(parts:Array):void { + // Стартовый индекс вершины в объекте для добавляемой грани + var startVertexIndex:int = currentObject._vertices.length; + // Создание вершин в объекте + var faceVertexCount:int = parts.length - 1; + var vtIndices:Array = new Array(3); + // Массив идентификаторов вершин грани + var faceVertices:Array = new Array(faceVertexCount); + for (var i:int = 0; i < faceVertexCount; i++) { + var indices:Array = parts[i + 1].split("/"); + // Создание вершины + var vIdx:int = int(indices[0]); + // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1. + // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта. + var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx; + + var vertex:Vertex = currentObject._vertices[actualIndex]; + // Если вершины нет в объекте, она добавляется + if (vertex == null) { + var p:Point3D = globalVertices[actualIndex]; + if (rotateModel) { + // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X + vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex); + } else { + vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex); + } + } + faceVertices[i] = vertex; + + // Запись индекса текстурной вершины + if (i < 3) { + vtIndices[i] = int(indices[1]); + } + } + // Создание грани + var face:Face = currentObject.createFace(faceVertices, currentObject._faces.length); + // Установка uv координат + if (vtIndices[0] != 0) { + p = globalTextureVertices[vtIndices[0] - 1]; + face.aUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[1] - 1]; + face.bUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[2] - 1]; + face.cUV = new Point(p.x, p.y); + } + // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте + if (currentMaterialName != null) { + materialFaces.push(face); + } + } + + /** + * Загрузка библиотек материалов. + * + * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1 + */ + private function storeMaterialFileNames(parts:Array):void { + for (var i:int = 1; i < parts.length; i++) { + materialFileNames.push(parts[i]); + } + } + + /** + * Установка нового текущего материала. + * + * @param parts массив, во втором элементе которого содержится имя материала + */ + private function setNewMaterial(parts:Array):void { + // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта + moveFacesToSurface(); + // Установка нового текущего материала + currentMaterialName = parts[1]; + } + + /** + * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала. + */ + private function moveFacesToSurface():void { + if (currentMaterialName != null && materialFaces.length > 0) { + if (currentObject.hasSurface(currentMaterialName)) { + // При наличии поверхности с таким идентификатором, грани добавляются в неё + var surface:Surface = currentObject.getSurfaceById(currentMaterialName); + for each (var face:* in materialFaces) { + surface.addFace(face); + } + } else { + // При отсутствии поверхности с таким идентификатором, создатся новая поверхность + currentObject.createSurface(materialFaces, currentMaterialName); + } + } + materialFaces = []; + } + + /** + * Загрузка материалов. + */ + private function loadMaterialsLibrary():void { + loaderState = STATE_LOADING_LIBRARY; + if (mtlLoader == null) { + mtlLoader = new LoaderMTL(); + mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete); + mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError); + mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError); + } + materialLibrary = new Map(); + + currentMaterialFileIndex = -1; + loadNextMaterialFile(); + } + + /** + * Обработка успешной загрузки библиотеки материалов. + */ + private function onMaterialFileLoadComplete(e:Event):void { + materialLibrary.concat(mtlLoader.library); + // Загрузка следующего файла материалов + loadNextMaterialFile(); + } + + /** + * Загрузка и разбор очередного файла материалов. + */ + private function loadNextMaterialFile():void { + currentMaterialFileIndex++; + if (currentMaterialFileIndex == materialFileNames.length) { + // Все материалы загружены + setMaterials(); + complete(); + } else { + mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext); + } + } + + /** + * Установка материалов. + */ + private function setMaterials():void { + if (materialLibrary != null) { + for (var objectKey:* in _content.children) { + var object:Mesh = objectKey; + for (var surfaceKey:* in object._surfaces) { + var surface:Surface = object._surfaces[surfaceKey]; + // Поверхности имеют идентификаторы, соответствующие именам материалов + var materialInfo:MaterialInfo = materialLibrary[surfaceKey]; + if (materialInfo != null) { + if (materialInfo.bitmapData == null) { + surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode); + } else { + surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision); + transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize); + } + } + } + } + } + } + + /** + * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MTL предусмотрено + * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо + * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения, + * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину + * смещения. + * + * @param surface поверхность, грани которой обрабатываюся + * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y + * указывает смещение по V. + * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования + * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V. + */ + private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void { + for (var key:* in surface._faces) { + var face:Face = key; + if (face._aUV) { + uv.x = face._aUV.x; + uv.y = face._aUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.aUV = uv; + + uv.x = face._bUV.x; + uv.y = face._bUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.bUV = uv; + + uv.x = face._cUV.x; + uv.y = face._cUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y; + face.cUV = uv; + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as new file mode 100644 index 0000000..4bf9e57 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.loaders { + /** + * @private + * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора + * описания текстуры. + * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/ + */ + internal class MTLTextureMapInfo { + + // Ассоциация параметров команды объявления текстуры и методов для их чтения + private static const optionReaders:Object = { + "-clamp": clampReader, + "-o": offsetReader, + "-s": sizeReader, + + "-blendu": stubReader, + "-blendv": stubReader, + "-bm": stubReader, + "-boost": stubReader, + "-cc": stubReader, + "-imfchan": stubReader, + "-mm": stubReader, + "-t": stubReader, + "-texres": stubReader + }; + + // Смещение в текстурном пространстве + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var offsetW:Number = 0; + + // Масштабирование текстурного пространства + public var sizeU:Number = 1; + public var sizeV:Number = 1; + public var sizeW:Number = 1; + + // Флаг повторения текстуры + public var repeat:Boolean = true; + // Имя файла текстуры + public var fileName:String; + + /** + * Метод выполняет разбор данных о текстуре. + * + * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла. + * @return объект, содержащий данные о текстуре + */ + public static function parse(parts:Array):MTLTextureMapInfo { + var info:MTLTextureMapInfo = new MTLTextureMapInfo(); + // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры + var index:int = 1; + var reader:Function; + // Чтение параметров текстуры + while ((reader = optionReaders[parts[index]]) != null) { + index = reader(index, parts, info); + } + // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры + info.fileName = parts[index]; + return info; + } + + /** + * Читатель-заглушка. Пропускает все неподдерживаемые параметры. + */ + private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + index++; + var maxIndex:int = parts.length - 1; + while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) { + index++; + } + return index; + } + + /** + * Метод чтения параметров масштабирования текстурного пространства. + */ + private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.sizeU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.sizeV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.sizeW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметров смещения текстуры. + */ + private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.offsetU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.offsetV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.offsetW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметра повторения текстуры. + */ + private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.repeat = parts[index + 1] == "off"; + return index + 2; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as new file mode 100644 index 0000000..0a9a9ee --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders { + import flash.display.BitmapData; + import flash.geom.Point; + + /** + * @private + * Класс содержит обобщённую информацию о материале. + */ + internal class MaterialInfo { + public var color:uint; + public var alpha:Number; + + public var textureFileName:String; + public var bitmapData:BitmapData; + public var repeat:Boolean; + + public var mapOffset:Point; + public var mapSize:Point; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as new file mode 100644 index 0000000..6fc4865 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as @@ -0,0 +1,157 @@ +package alternativa.engine3d.loaders { + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.display.Stage; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Тип события, возникающего при завершении загрузки. + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Тип события, возникающего при ошибке загрузки, связанной с вводом-выводом. + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * @private + * Класс для загрузки битмапов диффузной текустуры и карты прозрачности. + */ + public class TextureMapsLoader extends EventDispatcher { + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_DIFFUSE_MAP:int = 0; + private static const STATE_LOADING_ALPHA_MAP:int = 1; + + private var bitmapLoader:Loader; + private var _bitmapData:BitmapData; + private var alphaTextureUrl:String; + private var loaderContext:LoaderContext; + + private var loaderState:int = STATE_IDLE; + + /** + * + */ + public function TextureMapsLoader() { + } + + /** + * Загрузка текстурных карт. При успешной загрузке посылается сообщение Event.COMPLETE. + * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext + */ + public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void { + this.alphaTextureUrl = alphaTextureUrl; + this.loaderContext = loaderContext; + if (bitmapLoader == null) { + bitmapLoader = new Loader(); + bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } else { + close(); + } + startLoading(STATE_LOADING_DIFFUSE_MAP, diffuseTextureUrl); + } + + /** + * Запуск загрузки файла текстуры. + * + * @param state фаза загрузки + * @param url URL загружаемого файла + */ + private function startLoading(state:int, url:String):void { + loaderState = state; + bitmapLoader.load(new URLRequest(url), loaderContext); + } + + /** + * + */ + private function onLoadComplete(e:Event):void { + switch (loaderState) { + case STATE_LOADING_DIFFUSE_MAP: + // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности. + _bitmapData = Bitmap(bitmapLoader.content).bitmapData; + if (alphaTextureUrl != null) { + startLoading(STATE_LOADING_ALPHA_MAP, alphaTextureUrl); + } else { + complete(); + } + break; + case STATE_LOADING_ALPHA_MAP: + // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры. + var tmpBmp:BitmapData = _bitmapData; + _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height); + _bitmapData.copyPixels(tmpBmp, tmpBmp.rect, new Point()); + + var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData; + if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) { + tmpBmp.draw(alpha, new Matrix(_bitmapData.width / alpha.width, 0, 0, _bitmapData.height / alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = tmpBmp; + } + _bitmapData.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + complete(); + break; + } + } + + /** + * + */ + private function onLoadError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + loaderState = STATE_IDLE; + loaderContext = null; + bitmapLoader.unload(); + dispatchEvent(new Event(Event.COMPLETE)); + } + + /** + * Загруженная текстура. + */ + public function get bitmapData():BitmapData { + return _bitmapData; + } + + /** + * Прекращение загрузки. + */ + public function close():void { + if (loaderState != STATE_IDLE) { + loaderState = STATE_IDLE; + bitmapLoader.close(); + } + } + + /** + * Очищает внутренние ссылки на загруженные объекты, чтобы сборщик мусора смог их удалить. + */ + public function unload():void { + if (bitmapLoader != null && loaderState == STATE_IDLE) { + bitmapLoader.unload(); + loaderContext = null; + _bitmapData = null; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as new file mode 100644 index 0000000..2d44686 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as @@ -0,0 +1,420 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.ColorUtils; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, раскрашивающий грани оттенками заданного цвета в зависимости от значения свойства parameterType. + */ + public class DevMaterial extends SurfaceMaterial { + /** + * Значение свойства parameterType для отображения глубины полигона в BSP-дереве. Глубина кодируется оттенком базового цвета материала. Оттенок + * получается покомпонентным умножением базового цвета на отношение значения уровня полигона в дереве к максимальному значению, задаваемому + * свойством maxParameterValue. Уровни нумеруются начиная от корня дерева, таким образом, чем глубже в дереве расположен + * полигон, тем он будет светлее. + */ + public static const BSP_DEPTH:int = 0; + /** + * Значение свойства parameterType для отображения мобильности полигона. Мобильность кодируется оттенком базового цвета материала. Оттенок + * получается покомпонентным умножением базового цвета на коэффициент, характеризующий положение мобильности полигона на отрезке, + * задаваемом свойствами minMobility и maxMobility. Более мобильные полигоны имеют более светлый оттенок. + */ + public static const MOBILITY:int = 1; + /** + * Значение свойства parameterType для отображения количества фрагментов грани, которой принадлежит отрисовываемый полигон. Количество фрагментов кодируется + * оттенком базового цвета материала. Оттенок получается покомпонентным умножением базового цвета на отношние количества фрагментов текущей грани + * к максимальному значению, задаваемому свойством maxParameterValue. Чем больше грань фрагментирована, тем она светлее. + */ + public static const FRAGMENTATION:int = 2; + /** + * Значение свойства parameterType для отображения граней с отсутствующими UV-координатами. Такие грани отображаются красной заливкой. + */ + public static const NO_UV_MAPPING:int = 3; + /** + * Значение свойства parameterType для отображения вырожденных полигонов. Вырожденные полигоны отображаются красной заливкой с красной обводкой толщиной пять + * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным. + */ + public static const DEGENERATE_POLY:int = 4; + + private static const point1:Point3D = new Point3D(); + private static const point2:Point3D = new Point3D(); + + private var _parameterType:int = BSP_DEPTH; + + private var _showNormals:Boolean; + private var _normalsColor:uint = 0x00FFFF; + private var _minMobility:int = 0; + private var _maxMobility:int = 255; + private var _maxParameterValue:Number = 20; + private var currentColor:int; + private var currentWireThickness:Number; + private var currentWireColor:uint; + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param parameterType тип отображаемого параметра + * @param color цвет заливки + * @param maxParameterValue максимальное значение отображаемого параметра + * @param showNormals включение режима отображения нормалей + * @param normalsColor цвет нормалей + * @param minMobility начало интервала мобильности + * @param maxMobility окончание интервала мобильности + * @param alpha прозрачность + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function DevMaterial(parameterType:uint = 0, color:uint = 0xFFFFFF, maxParameterValue:Number = 20, showNormals:Boolean = false, normalsColor:uint = 0x00FFFF, minMobility:int = 0, maxMobility:int = 255, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _parameterType = parameterType; + _color = color; + _maxParameterValue = maxParameterValue; + _showNormals = showNormals; + _normalsColor = normalsColor; + _minMobility = minMobility; + _maxMobility = maxMobility; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + var perspective:Number; + + setDrawingParameters(skin); + + if (currentColor > -1) { + gfx.beginFill(currentColor); + } + if (currentWireThickness >= 0) { + gfx.lineStyle(currentWireThickness, currentWireColor); + } + point = points[0]; + + if (camera._orthographic) { + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (currentWireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + perspective = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (currentWireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + + // Отрисовка нормали + if (_showNormals) { + point1.reset(); + for (i = 0; i < length; i++) { + point = points[i]; + point1.x += point.x; + point1.y += point.y; + point1.z += point.z; + } + point1.multiply(1 / length); + + var multiplyer:Number = 10; + var normal:Point3D = skin.primitive.face.globalNormal; + var m:Matrix3D = camera.cameraMatrix; + point2.x = (normal.x * m.a + normal.y * m.b + normal.z * m.c) * multiplyer + point1.x; + point2.y = (normal.x * m.e + normal.y * m.f + normal.z * m.g) * multiplyer + point1.y; + point2.z = (normal.x * m.i + normal.y * m.j + normal.z * m.k) * multiplyer + point1.z; + + if (camera._orthographic) { + gfx.moveTo(point1.x, point1.y); + gfx.lineStyle(0, _normalsColor); + gfx.lineTo(point2.x, point2.y); + } else { + perspective = camera.focalLength / point1.z; + gfx.moveTo(point1.x * perspective, point1.y * perspective); + gfx.lineStyle(0, _normalsColor); + perspective = camera.focalLength / point2.z; + gfx.lineTo(point2.x * perspective, point2.y * perspective); + } + gfx.lineStyle(); + } + } + + /** + * Установка параметров отрисовки. + */ + private function setDrawingParameters(skin:Skin):void { + currentColor = _color; + currentWireColor = _wireColor; + currentWireThickness = _wireThickness; + + var param:int = 0; + switch (_parameterType) { + case BSP_DEPTH: + // Глубина вложенности в BSP-tree + var node:BSPNode = skin.primitive.node; + while (node != null) { + node = node.parent; + param++; + } + currentColor = ColorUtils.multiply(_color, param / _maxParameterValue); + break; + case MOBILITY: + // Мобильность + var value:Number = (skin.primitive.mobility - _minMobility) / (_maxMobility - _minMobility); + if (value < 0) { + value = 0; + } + currentColor = ColorUtils.multiply(_color, value); + break; + case FRAGMENTATION: + // Степень фрагментирования + currentColor = ColorUtils.multiply(_color, calculateFragments(skin.primitive.face.primitive) / _maxParameterValue); + break; + case NO_UV_MAPPING: + // Отсутствие UV + if (skin.primitive.face.uvMatrix == null) { + currentColor = 0xFF0000; + } + break; + case DEGENERATE_POLY: + // Вырожденные полигоны + var face:Face = skin.primitive.face; + point1.copy(face._vertices[1]._coords); + point2.copy(face._vertices[2]._coords); + point2.subtract(point1); + point1.subtract(face._vertices[0]._coords); + var crossX:Number = point1.y * point2.z - point1.z * point2.y; + var crossY:Number = point1.z * point2.x - point1.x * point2.z; + var crossZ:Number = point1.x * point2.y - point1.y * point2.x; + if (crossX * crossX + crossY * crossY + crossZ * crossZ < 0.001) { + currentColor = 0xFF0000; + currentWireColor = 0xFF0000; + currentWireThickness = 5; + } + break; + } + } + + /** + * Расчёт количества фрагментов грани примитива. + */ + private function calculateFragments(primitive:PolyPrimitive):int { + if (primitive.frontFragment == null) { + return 1; + } + return calculateFragments(primitive.frontFragment) + calculateFragments(primitive.backFragment); + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:DevMaterial = new DevMaterial(_parameterType, _color, _maxParameterValue, _showNormals, _normalsColor, _minMobility, _maxMobility, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + + /** + * Тип отображаемого параметра. + */ + public function set parameterType(value:int):void { + if (_parameterType != value) { + _parameterType = value; + markToChange(); + } + } + + /** + * @private + */ + public function get parameterType():int { + return _parameterType; + } + + /** + * Включение режима отображения нормалей. + */ + public function set showNormals(value:Boolean):void { + if (_showNormals != value) { + _showNormals = value; + markToChange(); + } + } + + /** + * @private + */ + public function get showNormals():Boolean { + return _showNormals; + } + + /** + * Цвет нормалей. + * + * @default 0x00FFFF + */ + public function set normalsColor(value:uint):void { + if (_normalsColor != value) { + _normalsColor = value; + markToChange(); + } + } + + /** + * @private + */ + public function get normalsColor():uint { + return _normalsColor; + } + + /** + * Начало интервала мобильности. + */ + public function set minMobility(value:int):void { + if (_minMobility != value) { + _minMobility = value; + markToChange(); + } + } + + /** + * @private + */ + public function get minMobility():int { + return _minMobility; + } + + /** + * Окончание интервала мобильности. + */ + public function set maxMobility(value:int):void { + if (_maxMobility != value) { + _maxMobility = value; + markToChange(); + } + } + + /** + * @private + */ + public function get maxMobility():int { + return _maxMobility; + } + + /** + * Максимальное значение отображаемого параметра. + */ + public function set maxParameterValue(value:Number):void { + if (_maxParameterValue != value) { + _maxParameterValue = value; + markToChange(); + } + } + + /** + * @private + */ + public function get maxParameterValue():Number { + return _maxParameterValue; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as new file mode 100644 index 0000000..2447283 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as @@ -0,0 +1,47 @@ +package alternativa.engine3d.materials { + /** + * @private + * Точка, подготовленная к отрисовке. + */ + public final class DrawPoint { + + /** + * Координата X в системе координат камеры. + */ + public var x:Number; + /** + * Координата Y в системе координат камеры. + */ + public var y:Number; + /** + * Координата Z в системе координат камеры. + */ + public var z:Number; + /** + * Координата U в текстурном пространстве. + */ + public var u:Number; + /** + * Координата V в текстурном пространстве. + */ + public var v:Number; + + /** + * Создаёт новый экземпляр класса. + * + * @param x координата X в системе координат камеры + * @param y координата Y в системе координат камеры + * @param z координата Z в системе координат камеры + * @param u координата U в текстурном пространстве + * @param v координата V в текстурном пространстве + */ + public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) { + this.x = x; + this.y = y; + this.z = z; + this.u = u; + this.v = v; + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..9eb21c1 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,160 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, заполняющий полигон сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу + * полигона линией заданной толщины и цвета. + */ + public class FillMaterial extends SurfaceMaterial { + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..e7dcedc --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as @@ -0,0 +1,94 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов. + */ + public class Material { + + /** + * @private + * Альфа. + */ + alternativa3d var _alpha:Number; + /** + * @private + * Режим наложения цвета. + */ + alternativa3d var _blendMode:String; + + /** + * Создание экземпляра класса. + * + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function Material(alpha:Number, blendMode:String) { + _alpha = alpha; + _blendMode = blendMode; + } + + /** + * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + */ + public function get alpha():Number { + return _alpha; + } + + /** + * @private + */ + public function set alpha(value:Number):void { + if (_alpha != value) { + _alpha = value; + markToChange(); + } + } + + /** + * Режим наложения цвета. + */ + public function get blendMode():String { + return _blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + if (_blendMode != value) { + _blendMode = value; + markToChange(); + } + } + + /** + * Отметить материал на перерисовку. + */ + protected function markToChange():void {} + + /** + * @private + * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.). + * + * @param skin скин для очистки + */ + alternativa3d function clear(skin:Skin):void { + skin.gfx.clear(); + } + + /** + * Создание клона материала. + * + * @return клон материала + */ + public function clone():Material { + return new Material(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as new file mode 100644 index 0000000..387a0d8 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as @@ -0,0 +1,119 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Sprite3D; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Базовый класс для материалов спрайтов. + */ + public class SpriteMaterial extends Material { + + /** + * @private + * Спрайт. + */ + alternativa3d var _sprite:Sprite3D; + + /** + * @inheritDoc + */ + public function SpriteMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + } + + /** + * Спрайт, которому назначен материал. + */ + public function get sprite():Sprite3D { + return _sprite; + } + + /** + * @private + * Добавление на сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Назначение спрайту. + * + * @param sprite спрайт + */ + alternativa3d function addToSprite(sprite:Sprite3D):void { + _sprite = sprite; + } + + /** + * @private + * Удаление из спрайта. + * + * @param sprite спрайт + */ + alternativa3d function removeFromSprite(sprite:Sprite3D):void { + _sprite = null; + } + + /** + * @private + * Метод определяет, может ли материал нарисовать спрайт. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной со спрайтом. Реализация по умолчанию возвращает + * true. + * + * @param camera камера через которую происходит отрисовка. + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + alternativa3d function canDraw(camera:Camera3D):Boolean { + return true; + } + + /** + * @private + * Метод выполняет отрисовку в заданный скин. + * + * @param camera камера, вызвавшая метод + * @param skin скин, в котором нужно отрисовать + */ + alternativa3d function draw(camera:Camera3D, skin:Skin):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override protected function markToChange():void { + if (_sprite != null) { + _sprite.addMaterialChangedOperationToScene(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SpriteMaterial(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as new file mode 100644 index 0000000..abb711d --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as @@ -0,0 +1,243 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, отображающий заданную текстуру в точке нахождения спрайта. Текстура отображается так, как если бы она находилась в плоскости, + * параллельной плоскости области вывода камеры, а верхний край текстуры был параллелен верхнему краю области вывода. При отрисовке изображения + * начало координат текстуры в области вывода совпадает с проекцией точки спрайта. По умолчанию начало координат текстуры перенесено в её центр. + */ + public class SpriteTextureMaterial extends SpriteMaterial { + + /** + * @private + * Вспомогательный прямоугольник, используется при отрисовке для хранения параметров отрисовки. + */ + private static var drawRect:Rectangle = new Rectangle(); + + /** + * @private + * Матрица, используемая для отрисовки текстуры спрайта + */ + private static var textureMatrix:Matrix = new Matrix(); + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + + /** + * @private + * Смещение начала координат по оси X + */ + alternativa3d var _originX:Number; + + /** + * @private + * Смещение начала координат по оси Y + */ + alternativa3d var _originY:Number; + + /** + * Создание экземпляра класса. + * + * @param texture текстура для отображения + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param smooth сглаживание текстуры + * @param blendMode режим наложения цвета + * @param originX относительное смещение начала координат в текстуре по оси X + * @param originY относительное смещение начала координат в текстуре по оси Y + */ + public function SpriteTextureMaterial(texture:Texture, alpha:Number = 1, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, originX:Number = 0.5, originY:Number = 0.5) { + super(alpha, blendMode); + _texture = texture; + _smooth = smooth; + _originX = originX; + _originY = originY; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(camera:Camera3D):Boolean { + if (_texture == null) { + return false; + } + + // Переводим координаты в систему камеры + var cameraMatrix:Matrix3D = camera.cameraMatrix; + + var x:Number = _sprite.globalCoords.x; + var y:Number = _sprite.globalCoords.y; + var z:Number = _sprite.globalCoords.z; + var pointX:Number = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + var pointY:Number = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + var pointZ:Number = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + + var w:Number; + var h:Number; + + if (camera._orthographic) { + if ((camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) { + return false; + } + w = _texture._width*camera._zoom*_sprite._materialScale; + h = _texture._height*camera._zoom*_sprite._materialScale; + x = pointX - w*_originX; + y = pointY - h*_originY; + } else { + if ((pointZ <= 0) || (camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) { + return false; + } + var perspective:Number = camera.focalLength/pointZ; + w = _texture._width*perspective*_sprite._materialScale; + h = _texture._height*perspective*_sprite._materialScale; + x = pointX*perspective - w*_originX; + y = pointY*perspective - h*_originY; + } + var halfW:Number = camera._view._width*0.5; + var halfH:Number = camera._view._height*0.5; + + if (camera._viewClipping && (x >= halfW || y >= halfH || x + w <= -halfW || y + h <= -halfH)) { + return false; + } + + textureMatrix.a = w/_texture._width; + textureMatrix.d = h/_texture._height; + textureMatrix.tx = x; + textureMatrix.ty = y; + + if (camera._viewClipping) { + if (x < -halfW) { + w -= -halfW - x; + x = -halfW; + } + if (x + w > halfW) { + w = halfW - x; + } + if (y < -halfH) { + h -= -halfH - y; + y = -halfH; + } + if (y + h > halfH) { + h = halfH - y; + } + } + drawRect.x = x; + drawRect.y = y; + drawRect.width = w; + drawRect.height = h; + + return true; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + skin.gfx.beginBitmapFill(_texture._bitmapData, textureMatrix, false, _smooth); + skin.gfx.drawRect(drawRect.x, drawRect.y, drawRect.width, drawRect.height); + } + + /** + * Текстура. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + markToChange(); + } + } + + /** + * Сглаживание текстуры. + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + markToChange(); + } + } + + /** + * Относительное смещение начала координат в текстуре по оси X. + * + * @default 0.5 + */ + public function get originX():Number { + return _originX; + } + + /** + * @private + */ + public function set originX(value:Number):void { + if (_originX != value) { + _originX = value; + markToChange(); + } + } + + /** + * Относительное смещение начала координат в текстуре по оси Y. + * + * @default 0.5 + */ + public function get originY():Number { + return _originY; + } + + /** + * @private + */ + public function set originY(value:Number):void { + if (_originY != value) { + _originY = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SpriteTextureMaterial(_texture, _alpha, _smooth, _blendMode, _originX, _originY); + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as new file mode 100644 index 0000000..51fca54 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as @@ -0,0 +1,147 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов полигональных поверхностей. + */ + public class SurfaceMaterial extends Material { + + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Флаг, определяет использует ли материал UV-координаты в грани + */ + alternativa3d var useUV:Boolean = false; + + /** + * @inheritDoc + */ + public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + } + + /** + * Поверхность, которой назначен материал. + */ + public function get surface():Surface { + return _surface; + } + + /** + * @private + * Добавление на сцену + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Добавление к мешу + * + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void {} + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void {} + + /** + * @private + * Добавление на поверхность + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Сохраняем поверхность + _surface = surface; + } + + /** + * @private + * Удаление с поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Удаляем ссылку на поверхность + _surface = null; + } + + /** + * @inheritDoc + */ + override protected function markToChange():void { + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + + /** + * @private + * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает + * true. + * + * @param primitive примитив для проверки + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return true; + } + + /** + * @private + * Метод выполняет отрисовку в заданный скин. + * + * @param camera камера, вызвавшая метод + * @param skin скин, в котором нужно рисовать + * @param length длина массива points + * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса + * alternativa.engine3d.materials.DrawPoint + * + * @see DrawPoint + */ + alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SurfaceMaterial(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..85d8357 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,361 @@ +package alternativa.engine3d.materials { + import __AS3__.vec.Vector; + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.display.BitmapData; + import flash.display.Graphics; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, заполняющий полигон текстурой. Помимо наложения текстуры, материал может рисовать границу полигона линией + * заданной толщины и цвета. + */ + public class TextureMaterial extends SurfaceMaterial { + + private static var stubBitmapData:BitmapData; + private static var stubMatrix:Matrix; + + private var gfx:Graphics; + private var textureMatrix:Matrix = new Matrix(); + private var focalLength:Number; + private var distortion:Number; + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + /** + * @private + * Повтор текстуры + */ + alternativa3d var _repeat:Boolean; + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + /** + * @private + * Точность перспективной коррекции + */ + alternativa3d var _precision:Number; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра текстурного материала. + * + * @param texture текстура материала + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param repeat повтор текстуры при заполнении + * @param smooth сглаживание текстуры при увеличении масштаба + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + * @param precision точность перспективной коррекции. Может быть задана одной из констант класса + * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более + * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра. + * + * @see TextureMaterialPrecision + */ + public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) { + super(alpha, blendMode); + _texture = texture; + _repeat = repeat; + _smooth = smooth; + _wireThickness = wireThickness; + _wireColor = wireColor; + _precision = precision; + useUV = true; + } + + /** + * @private + * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. + * + * @param primitive примитив для проверки + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _texture != null; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + gfx = skin.gfx; + + // Проверка на нулевую UV-матрицу + if (skin.primitive.face.uvMatrixBase == null) { + if (stubBitmapData == null) { + // Создание текстуры-заглушки + stubBitmapData = new BitmapData(2, 2, false, 0); + stubBitmapData.setPixel(0, 0, 0xFF00FF); + stubBitmapData.setPixel(1, 1, 0xFF00FF); + stubMatrix = new Matrix(10, 0, 0, 10, 0, 0); + } + gfx.beginBitmapFill(stubBitmapData, stubMatrix); + if (camera._orthographic) { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + return; + } + + if (camera._orthographic) { + // Расчитываем матрицу наложения текстуры + var face:Face = skin.primitive.face; + // Если матрица не расчитана, считаем + if (!camera.uvMatricesCalculated[face]) { + camera.calculateUVMatrix(face, _texture._width, _texture._height); + } + gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + focalLength = camera.focalLength; + //distortion = camera.focalDistortion*_precision; + + var front:int = 0; + var back:int = length - 1; + + var newFront:int = 1; + var newBack:int = (back > 0) ? (back - 1) : (length - 1); + var direction:Boolean = true; + + var a:DrawPoint = points[back]; + var b:DrawPoint; + var c:DrawPoint = points[front]; + + var drawVertices:Vector. = new Vector.(); + var drawUVTs:Vector. = new Vector.(); + + for (i = 0; i < length; i++) { + var p:DrawPoint = points[i]; + var t:Number = focalLength/p.z; + drawVertices[i << 1] = p.x*t; + drawVertices[(i << 1) + 1] = p.y*t; + drawUVTs.push(p.u, 1 - p.v, t); + } + + var drawIndices:Vector. = new Vector.(); + + while (front != newBack) { + if (direction) { +/* a = points[front]; + b = points[newFront]; + c = points[back]; + */ + drawIndices.push(front, newFront, back); + + front = newFront; + newFront = (front < length - 1) ? (front + 1) : 0; + } else { +/* a = points[newBack]; + b = points[back]; + c = points[front]; + */ + drawIndices.push(newBack, back, front); + + back = newBack; + newBack = (back > 0) ? (back - 1) : (length - 1); + } + + direction = !direction; + } + gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + gfx.drawTriangles(drawVertices, drawIndices, drawUVTs); + + } + } + + /** + * Текстура материала. Материал не выполняет никаких действий по отрисовке, если не задана текстура. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + markToChange(); + } + } + + /** + * Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get repeat():Boolean { + return _repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + if (_repeat != value) { + _repeat = value; + markToChange(); + } + } + + /** + * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки полигона. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * Точность перспективной коррекции. + */ + public function get precision():Number { + return _precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + if (_precision != value) { + _precision = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision); + return res; + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as new file mode 100644 index 0000000..ca955e5 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as @@ -0,0 +1,39 @@ +package alternativa.engine3d.materials { + + /** + * Класс содержит константы точности перспективной коррекции текстурного материала. + * + * @see TextureMaterial + */ + public class TextureMaterialPrecision { + + /** + * Адаптивная триангуляция не будет выполняться, только простая триангуляция. + */ + public static const NONE:Number = -1; + /** + * Очень низкое качество адаптивной триангуляции. + */ + public static const VERY_LOW:Number = 50; + /** + * Низкое качество адаптивной триангуляции. + */ + public static const LOW:Number = 25; + /** + * Среднее качество адаптивной триангуляции. + */ + public static const MEDIUM:Number = 10; + /** + * Высокое качество адаптивной триангуляции. + */ + public static const HIGH:Number = 6; + /** + * Очень высокое качество адаптивной триангуляции. + */ + public static const VERY_HIGH:Number = 3; + /** + * Максимальное качество адаптивной триангуляции. + */ + public static const BEST:Number = 1; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as new file mode 100644 index 0000000..6b112b1 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as @@ -0,0 +1,127 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал для рисования рёбер полигонов. + */ + public class WireMaterial extends SurfaceMaterial { + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + /** + * @private + * Толщина линий + */ + alternativa3d var _thickness:Number; + + /** + * Создание экземпляра класса. + * + * @param thickness толщина линий + * @param color цвет линий + * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + _color = color; + _thickness = thickness; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _thickness >= 0; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + gfx.moveTo(point.x, point.y); + for (i = 0; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + var perspective:Number = camera.focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 0; i < length; i++) { + point = points[i]; + perspective = camera.focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет линий. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется. + */ + public function get thickness():Number { + return _thickness; + } + + /** + * @private + */ + public function set thickness(value:Number):void { + if (_thickness != value) { + _thickness = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new WireMaterial(_thickness, _color, _alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as new file mode 100644 index 0000000..5e0e560 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as @@ -0,0 +1,30 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к + * эллипсоиду плоскость, проходящая через точку столкновения с гранью. + */ + public class Collision { + /** + * Грань, с которой произошло столкновение. + */ + public var face:Face; + /** + * Нормаль плоскости столкновения. + */ + public var normal:Point3D; + /** + * Смещение плоскости столкновения. + */ + public var offset:Number; + /** + * Координаты точки столкновения. + */ + public var point:Point3D; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as new file mode 100644 index 0000000..65c3634 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as @@ -0,0 +1,60 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + */ + public class CollisionPlane { + // Узел BSP дерева, который содержит плоскость + public var node:BSPNode; + // Индикатор положения объекта относительно плоскости (спереди или сзади) + public var infront:Boolean; + // Расстояние до плоскости в начальной точке (всегда положительное) + public var sourceOffset:Number; + // Расстояние до плоскости в конечной точке + public var destinationOffset:Number; + + // Хранилище неиспользуемых плоскостей + static private var collector:Array = new Array(); + + + /** + * Создание плоскости + * + * @param node + * @param infront + * @param sourceOffset + * @param destinationOffset + * @return + */ + static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane { + + // Достаём плоскость из коллектора + var plane:CollisionPlane = collector.pop(); + // Если коллектор пуст, создаём новую плоскость + if (plane == null) { + plane = new CollisionPlane(); + } + + plane.node = node; + plane.infront = infront; + plane.sourceOffset = sourceOffset; + plane.destinationOffset = destinationOffset; + + return plane; + } + + /** + * Удаление плоскости, все ссылки должны быть почищены + * + * @param plane + */ + static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void { + plane.node = null; + collector.push(plane); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as new file mode 100644 index 0000000..7dd776e --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.physics { + /** + * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet + * при определении столкновений. + * + * @see EllipsoidCollider#collisionSet + */ + public class CollisionSetMode { + /** + * Грани объектов игнорируются при определении столкновений. + */ + static public const EXCLUDE:int = 1; + /** + * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам. + */ + static public const INCLUDE:int = 2; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as new file mode 100644 index 0000000..bcf933c --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as @@ -0,0 +1,994 @@ +package alternativa.engine3d.physics { + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + use namespace alternativa3d; + + /** + * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками. + */ + public class EllipsoidCollider { + // Максимальное количество попыток найти свободное от столкновения со сценой направление + private static const MAX_COLLISIONS:uint = 50; + // Радиус наибольшей сферы + private var _radius:Number = 100; + private var _radius2:Number = _radius * _radius; + private var _radiusX:Number = _radius; + private var _radiusY:Number = _radius; + private var _radiusZ:Number = _radius; + private var _radiusX2:Number = _radiusX * _radiusX; + private var _radiusY2:Number = _radiusY * _radiusY; + private var _radiusZ2:Number = _radiusZ * _radiusZ; + // Коэффициенты масштабирования осей + private var _scaleX:Number = 1; + private var _scaleY:Number = 1; + private var _scaleZ:Number = 1; + // Квадраты коэффициентов масштабирования осей + private var _scaleX2:Number = 1; + private var _scaleY2:Number = 1; + private var _scaleZ2:Number = 1; + + private var collisionSource:Point3D; + private var currentDisplacement:Point3D = new Point3D(); + private var collisionDestination:Point3D = new Point3D(); + + private var collisionPlanes:Array = new Array(); + private var collisionPrimitive:PolyPrimitive; + private var collisionPrimitiveNearest:PolyPrimitive; + private var collisionPlanePoint:Point3D = new Point3D(); + private var collisionPrimitiveNearestLengthSqr:Number; + private var collisionPrimitivePoint:Point3D = new Point3D(); + + private var collisionNormal:Point3D = new Point3D(); + private var collisionPoint:Point3D = new Point3D(); + private var collisionOffset:Number; + + private var currentCoords:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + private var collisionRadius:Number; + private var radiusVector:Point3D = new Point3D(); + private var p1:Point3D = new Point3D(); + private var p2:Point3D = new Point3D();; + private var localCollisionPlanePoint:Point3D = new Point3D(); + + // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу. + private var useSimpleAlgorithm:Boolean = true; + + /** + * Сцена, в которой определяются столкновения. + */ + public var scene:Scene3D; + /** + * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих + * координат меньше указанной погрешности. + */ + public var offsetThreshold:Number = 0.0001; + /** + * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры + * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит + * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества. + * + * @see #collisionSetMode + * @see alternativa.engine3d.core.Mesh + * @see alternativa.engine3d.core.Surface + */ + public var collisionSet:Set; + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если + * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений. + * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным + * в множестве объектам. + * + * @default true + * @see #collisionSet + */ + private var _collisionSetMode:int = CollisionSetMode.EXCLUDE; + + /** + * Создаёт новый экземпляр класса. + * + * @param scene сцена, в которой определяются столкновения + * @param scaleX радиус эллипсоида по оси X + * @param scaleY радиус эллипсоида по оси Y + * @param scaleZ радиус эллипсоида по оси Z + */ + public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) { + this.scene = scene; + this.radiusX = radiusX; + this.radiusY = radiusY; + this.radiusZ = radiusZ; + } + + /** + * @private + */ + public function get collisionSetMode():int { + return _collisionSetMode; + } + + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. + * + * @default CollisionSetMode.EXCLUDE + * @see #collisionSet + * @see CollisionSetMode + */ + public function set collisionSetMode(value:int):void { + if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) { + throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value"); + } + _collisionSetMode = value; + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusX():Number { + return _radiusX; + } + + /** + * @private + */ + public function set radiusX(value:Number):void { + _radiusX = value >= 0 ? value : -value; + _radiusX2 = _radiusX * _radiusX; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusY():Number { + return _radiusY; + } + + /** + * @private + */ + public function set radiusY(value:Number):void { + _radiusY = value >= 0 ? value : -value; + _radiusY2 = _radiusY * _radiusY; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusZ():Number { + return _radiusZ; + } + + /** + * @private + */ + public function set radiusZ(value:Number):void { + _radiusZ = value >= 0 ? value : -value; + _radiusZ2 = _radiusZ * _radiusZ; + calculateScales(); + } + + /** + * Расчёт коэффициентов масштабирования осей. + */ + private function calculateScales():void { + _radius = _radiusX; + if (_radiusY > _radius) { + _radius = _radiusY; + } + if (_radiusZ > _radius) { + _radius = _radiusZ; + } + _radius2 = _radius * _radius; + _scaleX = _radiusX / _radius; + _scaleY = _radiusY / _radius; + _scaleZ = _radiusZ / _radius; + _scaleX2 = _scaleX * _scaleX; + _scaleY2 = _scaleY * _scaleY; + _scaleZ2 = _scaleZ * _scaleZ; + + useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ); + } + + /** + * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение + * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены, + * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если + * значение поля scene равно null, то результат работы метода будет простой суммой двух + * входных векторов. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль + * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке. + * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат + * корневого объекта сцены + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + * @see #offsetThreshold + */ + public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void { + // Расчеты не производятся, если перемещение мало + if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold && + displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold && + displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + return; + } + + // Начальные координаты + currentCoords.x = sourcePoint.x; + currentCoords.y = sourcePoint.y; + currentCoords.z = sourcePoint.z; + // Начальный вектор перемещения + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + // Начальная точка назначения + destinationPoint.x = sourcePoint.x + currentDisplacement.x; + destinationPoint.y = sourcePoint.y + currentDisplacement.y; + destinationPoint.z = sourcePoint.z + currentDisplacement.z; + + if (useSimpleAlgorithm) { + calculateDestinationS(sourcePoint, destinationPoint); + } else { + calculateDestinationE(sourcePoint, destinationPoint); + } + } + + /** + * Вычисление точки назначения для сферы. + * @param sourcePoint + * @param destinationPoint + */ + private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision ) { + // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали + var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра сферы для следующей итерации + currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold); + currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold); + currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold); + // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Вычисление точки назначения для эллипсоида. + * @param destinationPoint + * @return + */ + private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не + // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и + // необходимость принудительного выхода. + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision) { + // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали + var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра эллипсоида для следующей итерации + collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius; + currentCoords.x = collision.point.x - collisionRadius * radiusVector.x; + currentCoords.y = collision.point.y - collisionRadius * radiusVector.y; + currentCoords.z = collision.point.z - collisionRadius * radiusVector.z; + // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + destinationPoint.x = currentCoords.x; + destinationPoint.y = currentCoords.y; + destinationPoint.z = currentCoords.z; + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора + * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены + * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат + * корневого объекта сцены + * + * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены, + * false если столкновений нет или не задано значение поля scene. + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + */ + public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean { + if (scene == null) { + return false; + } + + collisionSource = sourcePoint; + + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + + collisionDestination.x = collisionSource.x + currentDisplacement.x; + collisionDestination.y = collisionSource.y + currentDisplacement.y; + collisionDestination.z = collisionSource.z + currentDisplacement.z; + + collectPotentialCollisionPlanes(scene.bsp); + collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING); + + var plane:CollisionPlane; + // Пока не найдём столкновение с примитивом или плоскости не кончатся + if (useSimpleAlgorithm) { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneS(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } else { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneE(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } + + var collisionFound:Boolean = collisionPrimitive != null; + if (collisionFound) { + collision.face = collisionPrimitive.face; + collision.normal = collisionNormal; + collision.offset = collisionOffset; + collision.point = collisionPoint; + } + + collisionPrimitive = null; + collisionSource = null; + + return collisionFound; + } + + /** + * Сбор потенциальных плоскостей столкновения. + * + * @param node текущий узел BSP-дерева + */ + private function collectPotentialCollisionPlanes(node:BSPNode):void { + if (node == null) { + return; + } + + var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset; + var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset; + var plane:CollisionPlane; + + if (sourceOffset >= 0) { + // Исходное положение центра перед плоскостью ноды + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + // Или мы наткнулись на спрайтовую ноду + if (destinationOffset < _radius && !node.sprited) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset); + collisionPlanes.push(plane); + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + } + } else { + // Исходное положение центра за плоскостью ноды + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + if (destinationOffset > -_radius) { + // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад + if (node.backPrimitives != null) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset); + collisionPlanes.push(plane); + } + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + } + } + } + + /** + * @private + * Определение пересечения сферы с примитивами, лежащими в заданной плоскости. + * + * @param plane плоскость, содержащая примитивы для проверки + */ + private function calculateCollisionWithPlaneS(plane:CollisionPlane):void { + collisionPlanePoint.copy(collisionSource); + + var normal:Point3D = plane.node.normal; + // Если сфера врезана в плоскость + if (plane.sourceOffset <= _radius) { + if (plane.infront) { + collisionPlanePoint.x -= normal.x * plane.sourceOffset; + collisionPlanePoint.y -= normal.y * plane.sourceOffset; + collisionPlanePoint.z -= normal.z * plane.sourceOffset; + } else { + collisionPlanePoint.x += normal.x * plane.sourceOffset; + collisionPlanePoint.y += normal.y * plane.sourceOffset; + collisionPlanePoint.z += normal.z * plane.sourceOffset; + } + } else { + // Находим центр сферы во время столкновения с плоскостью + var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset); + collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time; + collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time; + collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time; + + // Устанавливаем точку пересечения cферы с плоскостью + if (plane.infront) { + collisionPlanePoint.x -= normal.x * _radius; + collisionPlanePoint.y -= normal.y * _radius; + collisionPlanePoint.z -= normal.z * _radius; + } else { + collisionPlanePoint.x += normal.x * _radius; + collisionPlanePoint.y += normal.y * _radius; + collisionPlanePoint.z += normal.z * _radius; + } + } + + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(plane.node.primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + + // Нормаль плоскости при столкновении - нормаль плоскости + if (plane.infront) { + collisionNormal.x = normal.x; + collisionNormal.y = normal.y; + collisionNormal.z = normal.z; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normal.x; + collisionNormal.y = -normal.y; + collisionNormal.z = -normal.z; + collisionOffset = -plane.node.offset; + } + + // Точка столкновения в точке столкновения с плоскостью + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + + } else { + // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей + + // Вектор из ближайшей точки в центр сферы + var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x; + var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y; + var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z; + + // Если движение в сторону точки + if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) { + + // Ищем нормализованный вектор обратного направления + var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z); + var vectorX:Number = -currentDisplacement.x / vectorLength; + var vectorY:Number = -currentDisplacement.y / vectorLength; + var vectorZ:Number = -currentDisplacement.z / vectorLength; + + // Длина вектора из ближайшей точки в центр сферы + var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ; + + // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления + var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ; + + var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength; + + if (projectionInsideSphereLengthSqr > 0) { + // Находим расстояние из ближайшей точки до сферы + var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr); + + if (distance < vectorLength) { + // Столкновение сферы с ближайшей точкой произошло + + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x; + collisionPoint.y = collisionPrimitivePoint.y; + collisionPoint.z = collisionPrimitivePoint.z; + + // Находим нормаль плоскости столкновения + var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr); + collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength; + collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength; + collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength; + + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + } + } + + /** + * @private + * Определение столкновения сферы с примитивом. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void { + + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + for (var i:uint = 0; i < length; i++) { + + var p1:Point3D = points[i]; + var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0]; + + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = collisionPlanePoint.x - p1.x; + var vectorY:Number = collisionPlanePoint.y - p1.y; + var vectorZ:Number = collisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = collisionPlanePoint.x - p2.x; + vectorY = collisionPlanePoint.y - p2.y; + vectorZ = collisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + + /** + * Проверка на действительное столкновение эллипсоида с плоскостью. + */ + private function calculateCollisionWithPlaneE(plane:CollisionPlane):void { + var normalX:Number = plane.node.normal.x; + var normalY:Number = plane.node.normal.y; + var normalZ:Number = plane.node.normal.z; + // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление + // от плоскости, в этом случае столкновения не происходит. + var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ; + if (plane.infront) { + displacementAlongNormal = -displacementAlongNormal; + } + // Выходим из функции в случае удаления от плоскости + if (displacementAlongNormal < 0) { + return; + } + // Определение ближайшей к плоскости точки эллипсоида + var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2); + // Положение точки в локальной системе координат эллипсоида + var localClosestX:Number = k * normalX * _scaleX2; + var localClosestY:Number = k * normalY * _scaleY2; + var localClosestZ:Number = k * normalZ * _scaleZ2; + // Глобальные координаты точки + var px:Number = collisionSource.x + localClosestX; + var py:Number = collisionSource.y + localClosestY; + var pz:Number = collisionSource.z + localClosestZ; + // Растояние от найденной точки эллипсоида до плоскости + var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + if (closestPointDistance > plane.sourceOffset) { + // Найдена наиболее удалённая точка, расчитываем вторую + px = collisionSource.x - localClosestX; + py = collisionSource.y - localClosestY; + pz = collisionSource.z - localClosestZ; + closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + } + // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости, + // то столкновения не произошло и нужно завершить выполнение функции + if (closestPointDistance > displacementAlongNormal) { + return; + } + // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения + // с ближайшим полигоном, лежащим в этой плоскости + if (closestPointDistance <= 0 ) { + // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость + if (plane.infront) { + collisionPlanePoint.x = px - normalX * closestPointDistance; + collisionPlanePoint.y = py - normalY * closestPointDistance; + collisionPlanePoint.z = pz - normalZ * closestPointDistance; + } else { + collisionPlanePoint.x = px + normalX * closestPointDistance; + collisionPlanePoint.y = py + normalY * closestPointDistance; + collisionPlanePoint.z = pz + normalZ * closestPointDistance; + } + } else { + // Эллипсоид не пересекается с плоскостью, ищем точку контакта + var t:Number = closestPointDistance / displacementAlongNormal; + collisionPlanePoint.x = px + currentDisplacement.x * t; + collisionPlanePoint.y = py + currentDisplacement.y * t; + collisionPlanePoint.z = pz + currentDisplacement.z * t; + } + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + // Нормаль плоскости при столкновении - нормаль плоскости примитива + if (plane.infront) { + collisionNormal.x = normalX; + collisionNormal.y = normalY; + collisionNormal.z = normalZ; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normalX; + collisionNormal.y = -normalY; + collisionNormal.z = -normalZ; + collisionOffset = -plane.node.offset; + } + // Радиус эллипсоида в точке столкновения + collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px - collisionSource.x; + radiusVector.y = py - collisionSource.y; + radiusVector.z = pz - collisionSource.z; + // Точка столкновения совпадает с точкой столкновения с плоскостью примитива + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + } else { + // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива + // Трансформированная в пространство эллипсоида ближайшая точка на примитиве + px = collisionPrimitivePoint.x; + py = collisionPrimitivePoint.y; + pz = collisionPrimitivePoint.z; + + var collisionExists:Boolean; + // Квадрат расстояния из центра эллипсоида до точки примитива + var r2:Number = px*px + py*py + pz*pz; + if (r2 < _radius2) { + // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе + k = _radius / Math.sqrt(r2); + px *= k * _scaleX; + py *= k * _scaleY; + pz *= k * _scaleZ; + + collisionExists = true; + } else { + // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки + // примитива, с поверхностью эллипсоида + // Трансформированный в пространство эллипсоида противоположный вектор скорости + var vx:Number = - currentDisplacement.x / _scaleX; + var vy:Number = - currentDisplacement.y / _scaleY; + var vz:Number = - currentDisplacement.z / _scaleZ; + // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости + var a:Number = vx*vx + vy*vy + vz*vz; + var b:Number = 2 * (px*vx + py*vy + pz*vz); + var c:Number = r2 - _radius2; + var d:Number = b*b - 4*a*c; + // Решение есть только при действительном дискриминанте квадратного уравнения + if (d >=0) { + // Выбирается минимальное время, т.к. нужна первая точка пересечения + t = -0.5 * (b + Math.sqrt(d)) / a; + // Точка лежит на луче только если время положительное + if (t >= 0 && t <= 1) { + // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство + px = (px + t * vx) * _scaleX; + py = (py + t * vy) * _scaleY; + pz = (pz + t * vz) * _scaleZ; + + collisionExists = true; + } + } + } + if (collisionExists) { + // Противоположная нормаль к эллипсоиду в точке пересечения + collisionNormal.x = - px / _scaleX2; + collisionNormal.y = - py / _scaleY2; + collisionNormal.z = - pz / _scaleZ2; + collisionNormal.normalize(); + // Радиус эллипсоида в точке столкновения + collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px; + radiusVector.y = py; + radiusVector.z = pz; + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x; + collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y; + collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z; + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + + /** + * @private + * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит + * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки + * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через + * минимальное время. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void { + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + var point1:Point3D; + var point2:Point3D = points[length - 1]; + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX; + localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY; + localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ; + // Обход всех рёбер примитива + for (var i:uint = 0; i < length; i++) { + point1 = point2; + point2 = points[i]; + + p1.x = p2.x; + p1.y = p2.y; + p1.z = p2.z; + + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра + // с целью определения положения точки столкновения относительно полигона + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = localCollisionPlanePoint.x - p1.x; + var vectorY:Number = localCollisionPlanePoint.y - p1.y; + var vectorZ:Number = localCollisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = localCollisionPlanePoint.x - p2.x; + vectorY = localCollisionPlanePoint.y - p2.y; + vectorZ = localCollisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..ec4459b --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,316 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Прямоугольный параллелепипед. + *

Параллелепипед содержит шесть поверхностей с идентификаторами "front", "back", "left", + * "right", "top", "bottom", на каждую из которых может быть установлен свой материал. + *

+ */ + public class Box extends Mesh { + + /** + * Создание нового параллелепипеда. + * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param height высота. Размерность по оси Z. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param heightSegments количество сегментов по по высоте + * @param reverse задает направление нормалей граней. Если указано значение true, то нормали будут направлены внутрь фигуры. + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы. + */ + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + height = (height < 0)? 0 : height; + + var wh:Number = width/2; + var lh:Number = length/2; + var hh:Number = height/2; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + // Создание точек + for (x = 0; x <= widthSegments; x++) { + for (y = 0; y <= lengthSegments; y++) { + for (z = 0; z <= heightSegments; z++) { + if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) { + createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z); + } + } + } + } + + // Создание поверхностей + var front:Surface = createSurface(null, "front"); + var back:Surface = createSurface(null, "back"); + var left:Surface = createSurface(null, "left"); + var right:Surface = createSurface(null, "right"); + var top:Surface = createSurface(null, "top"); + var bottom:Surface = createSurface(null, "bottom"); + + // Создание граней + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var faceId:String; + + // Для оптимизаций UV при триангуляции + var aUV:Point; + var cUV:Point; + + // Построение верхней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "top_"+x+"_"+y; + if (reverse) { + if (triangulate) { + aUV = new Point(x*wd, (lengthSegments - y)*ld); + cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1"); + setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0"); + setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, y*ld); + cUV = new Point((x + 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + top.addFace(faceId + ":0"); + top.addFace(faceId + ":1"); + } else { + top.addFace(faceId); + } + } + } + + // Построение нижней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "bottom_" + x + "_" + y; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld); + cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1"); + setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, y*ld); + cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + bottom.addFace(faceId + ":0"); + bottom.addFace(faceId + ":1"); + } else { + bottom.addFace(faceId); + } + } + } + + // Построение фронтальной грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "front_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, z*hd); + cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0"); + } else { + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, z*hd); + cUV = new Point((x + 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + front.addFace(faceId + ":0"); + front.addFace(faceId + ":1"); + } else { + front.addFace(faceId); + } + } + } + + // Построение задней грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "back_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point(x * wd, (z + 1) * hd); + cUV = new Point((x + 1) * wd, z * hd); + createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (z + 1)*hd); + cUV = new Point((widthSegments - x - 1)*wd, z*hd); + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0"); + createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + back.addFace(faceId + ":0"); + back.addFace(faceId + ":1"); + } else { + back.addFace(faceId); + } + } + } + + // Построение левой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "left_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point(y*ld, (z + 1)*hd); + cUV = new Point((y + 1)*ld, z*hd); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((lengthSegments - y - 1)*ld, z*hd); + cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + left.addFace(faceId + ":0"); + left.addFace(faceId + ":1"); + } else { + left.addFace(faceId); + } + } + } + + // Построение правой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "right_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point((lengthSegments - y)*ld, z*hd); + cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(y*ld, z*hd); + cUV = new Point((y + 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + right.addFace(faceId + ":0"); + right.addFace(faceId + ":1"); + } else { + right.addFace(faceId); + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Box(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as new file mode 100644 index 0000000..44fe0c2 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as @@ -0,0 +1,264 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Усеченный конус или цилиндр. + */ + public class Cone extends Mesh { + + /** + * Создает усеченный конус или цилиндр. + *

Различные значения параметров позволяют создавать различные примитивы. + * При установленном параметре topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном bottomRadius = topRadius будет построен цилиндр.

+ *

По умолчанию параметр triangulate установлен в false и на примитив не может быть наложена текстура. + * Только при установленном параметре triangulate в true это возможно.

+ *

После создания примитив всегда содержит в себе поверхность "side". + * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom", + * при установленном параметре topRadius в примитиве создается поверхность "top". + * На каждую из поверхностей может быть наложен свой материал

+ * + * @param height высота примтива. Размерность по оси Z. Не может быть меньше нуля. + * @param bottomRadius нижний радиус примитива + * @param topRadius верхний радиус примтива + * @param heightSegments количество сегментов по высоте примитива + * @param radialSegments количество сегментов по радиусу примтива + * @param reverse задает направление нормалей. При значении true нормли будут направлены внутрь примитива. + * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы + * и появится возможность наложить на примитив текстуру. + */ + public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) { + + if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) { + return; + } + height = (height < 0)? 0 : height; + bottomRadius = (bottomRadius < 0)? 0 : bottomRadius; + topRadius = (topRadius < 0)? 0 : topRadius; + + const radialSegment:Number = MathUtils.DEG360/radialSegments; + const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments; + const heightSegment:Number = height/heightSegments; + const halfHeight:Number = height*0.5 + const uSegment:Number = 1/radialSegments; + const vSegment:Number = 1/heightSegments; + + // Создание вершин + if (topRadius == 0 || triangulate) { + var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp"); + } + if (bottomRadius == 0 || triangulate) { + var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown"); + } + + var radial:uint; + var segment:uint; + + var topSegment:uint = heightSegments - int(topRadius == 0); + var bottomSegment:uint = int(bottomRadius == 0) ; + for (segment = bottomSegment; segment <= topSegment; segment++) { + for (radial = 0; radial < radialSegments; radial++) { + var currentAngle:Number = radialSegment*radial; + var currentRadius:Number = bottomRadius - (radiusSegment*segment); + createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var face:Face; + + var points:Array; + + var side:Surface = createSurface(null, "side"); + + if (topRadius == 0) { + // Создание граней у верхнего полюса + var prevRadial:uint = radialSegments - 1; + var centerUV:Point = new Point(0.5, 1); + var v:Number = topSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней верхней крышки + var top:Surface = createSurface(null, "top"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + var UV:Point; + var prevUV:Point; + if (reverse) { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + topSegment); + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + topSegment); + } + } + top.addFace(createFace(points, "top")); + } + } + // Создание боковых граней + var face2:Face; + var aUV:Point; + var cUV:Point; + for (segment = bottomSegment; segment < topSegment; segment++) { + prevRadial = radialSegments - 1; + v = segment * vSegment; + for (radial = 0; radial < radialSegments; radial++) { + if (triangulate) { + if (reverse) { + face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"); + face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"); + aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment) + cUV = new Point(1 - prevRadial*uSegment, v); + setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2); + } else { + face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"); + face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"); + aUV = new Point(prevRadial*uSegment, v) + cUV = new Point((prevRadial + 1)*uSegment, v + vSegment); + setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2); + } + side.addFace(face); + side.addFace(face2); + } else { + if (reverse) { + side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } else { + side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment)); + } + } + prevRadial = radial; + } + } + + if (bottomRadius == 0) { + // Создание граней у нижнего полюса + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0); + v = bottomSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней нижней крышки + var bottom:Surface = createSurface(null, "bottom"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + if (reverse) { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + bottomSegment); + } + } else { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + bottomSegment); + } + } + bottom.addFace(createFace(points, "bottom")); + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Cone(0, 0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as new file mode 100644 index 0000000..ea681b4 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as @@ -0,0 +1,197 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Геоплоскость. + */ + public class GeoPlane extends Mesh { + + /** + * Создает геоплоскость. + *

Геоплоскость это плоскость с сетчатой структурой граней.

+ *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значение параметра равно true, то создаётся двусторонняя поверхность + * @param reverse флаг инвертирования нормалей + */ + public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var hh:Number = length/2; + // Размеры сегмента + var ws:Number = width/widthSegments; + var hs:Number = length/lengthSegments; + + // Размеры UV-сегмента + var us:Number = 1/widthSegments; + var vs:Number = 1/lengthSegments; + + // Создание точек + var x:uint; + var y:uint; + var frontUV:Array = new Array(); + var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array(); + for (y = 0; y <= lengthSegments; y++) { + frontUV[y] = new Array(); + if (backUV != null) { + backUV[y] = new Array(); + } + for (x = 0; x <= widthSegments; x++) { + if ((y & 1) == 0) { + // Если чётный ряд + createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(x*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(x*us, 1 - y*vs); + } + } else { + // Если нечётный ряд + if (x == 0) { + // Первая точка + createVertex(-wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(0, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(0, 1 - y*vs); + } + } else { + createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point((x - 0.5)*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs); + } + if (x == widthSegments) { + // Последняя точка + createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1)); + frontUV[y][x + 1] = new Point(1, y*vs); + if (backUV != null) { + backUV[y][x + 1] = new Point(1, 1 - y*vs); + } + } + } + } + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + var face:Face; + for (y = 0; y < lengthSegments; y++) { + for (var n:uint = 0; n <= (widthSegments << 1); n++) { + x = n >> 1; + if ((y & 1) == 0) { + // Если чётный ряд + if ((n & 1) == 0) { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } + } else { + // Если нечётный ряд + if ((n & 1) == 0) { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } + } + } + } + + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoPlane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..7ba3bf2 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,321 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Геосфера. + */ + public class GeoSphere extends Mesh { + + /** + * Создает геосферу. + *

Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

Текстурные координаты у геосферы не находятся в промежутке [0, 1], + * поэтому для материала с текстурой необходимо устанавливать флаг repeat. + * + * @param radius радиус геосферы. Не может быть меньше нуля. + * @param segments количество сегментов геосферы + * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы. + */ + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + if (segments == 0) { + return; + } + radius = (radius < 0)? 0 : radius; + + const sections:uint = 20; + + //var nfaces:uint = sections*segments*segments; + //var nverts:Number = nfaces/2 + 2; + var points:Array = new Array(); + + var i:uint; + var f:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + points.push(createVertex(0, 0, radius, "poleUp")); + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG360*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, subz)); + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG180*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, -subz)); + } + points.push(createVertex(0, 0, -radius, "poleDown")); + + for (i = 1; i < 6; i++) { + interpolate(0, i, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i % 5 + 1, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i + 5, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, (i + 3) % 5 + 6, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i + 5, i % 5 + 6, segments, points); + } + for (i = 6; i < 11; i++) { + interpolate(11, i, segments, points); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points); + } + } + // Создание граней + var face:Face; + var surface:Surface = createSurface(); + for (f = 0; f < sections; f++) { + for (var row:uint = 0; row < segments; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + var va:Vertex = points[a]; + var vb:Vertex = points[b]; + var vc:Vertex = points[c]; + var aUV:Point; + var bUV:Point; + var cUV:Point; + var coordA:Point3D = va._coords; + var coordB:Point3D = vb._coords; + var coordC:Point3D = vc._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + // полюс + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, cUV, bUV, face); + } else { + face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f); + setUVsToFace(aUV, bUV, cUV, face); + } + surface.addFace(face); + //trace(a + "_" + b + "_" + c); + if (column < row) { + b = findVertices(segments, f, row, column + 1); + var vd:Vertex = points[b]; + coordB = vd._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, bUV, cUV, face); + } else { + face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f); + setUVsToFace(aUV, cUV, bUV, face); + } + surface.addFace(face); + } + } + } + } + } + +/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point { + if (radius == 0) { + radius = point.length; + } + if (reverse) { + var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360; + } else { + u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5; + } + return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5); + } + */ + private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void { + if (num < 2) { + return; + } + var a:Vertex = Vertex(points[v1]); + var b:Vertex = Vertex(points[v2]); + var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin)); + } + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoSphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..d346048 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,117 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Плоскость. + */ + public class Plane extends Mesh { + + /** + * Создает плоскость. + *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси Х. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значении параметра равно true, то формируется двусторонняя плоскость + * @param reverse инвертирование нормалей + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы. + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var lh:Number = length/2; + + // Размеры сегмента + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + + // Размеры UV-сегмента + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + + // Создание точек и UV + var x:int; + var y:int; + var uv:Array = new Array(); + for (y = 0; y <= lengthSegments; y++) { + uv[y] = new Array(); + for (x = 0; x <= widthSegments; x++) { + uv[y][x] = new Point(x*wd, y*ld); + createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y); + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + if (twoSided || !reverse) { + if (triangulate) { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0"); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1"); + setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1"); + front.addFace("front" + x + "_" + y + ":0"); + front.addFace("front" + x + "_" + y + ":1"); + } else { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y); + front.addFace("front" + x + "_" + y); + } + } + if (twoSided || reverse) { + if (triangulate) { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0"); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1"); + setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1"); + back.addFace("back" + x + "_" + y + ":0"); + back.addFace("back"+x+"_"+y + ":1"); + } else { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y); + back.addFace("back" + x + "_" + y); + } + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Plane(0, 0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as new file mode 100644 index 0000000..994d7f7 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as @@ -0,0 +1,144 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + import alternativa.engine3d.core.Face; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Сфера. + */ + public class Sphere extends Mesh { + + /** + * Создает сферу. + *

После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

По умолчанию параметр triangulate установлен в false и на сферу нельзя наложить текстуру. + * Только при установленном triangulate в true это возможно.

+ * + * @param radius Радиус сферы. Не может быть меньше нуля. + * @param radialSegments количество сегментов по экватору сферы + * @param heightSegments количество сегментов по высоте + * @param reverse флаг инвертирования нормалей. При параметре установленном в true нормали направлены внутрь сферы. + * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы, + * и будет возможно наложить на примитив текстуру. + */ + public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) { + if ((radialSegments < 3) || (heightSegments < 2)) { + return; + } + radius = (radius < 0)? 0 : radius; + + var poleUp:Vertex = createVertex(0, 0, radius, "poleUp"); + var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown"); + + const radialAngle:Number = MathUtils.DEG360/radialSegments; + const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1); + + var radial:uint; + var segment:uint; + + // Создание вершин + for (segment = 1; segment < heightSegments; segment++) { + var currentHeightAngle:Number = heightAngle*segment; + var segmentRadius:Number = Math.sin(currentHeightAngle)*radius; + var segmentZ:Number = Math.cos(currentHeightAngle)*radius; + for (radial = 0; radial < radialSegments; radial++) { + var currentRadialAngle:Number = radialAngle*radial; + createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var surface:Surface = createSurface(); + + var prevRadial:uint = radialSegments - 1; + var lastSegmentString:String = "_" + (heightSegments - 1); + + var uStep:Number = 1/radialSegments; + var vStep:Number = 1/heightSegments; + + var face:Face; + + // Для триангуляции + var aUV:Point; + var cUV:Point; + + var u:Number; + + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString)); + + // Если включена триангуляция + if (triangulate) { + // Триангулируем середину и просчитываем маппинг + u = uStep*prevRadial; + setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1))); + cUV = new Point(1 - u, 1 - vStep*segment); + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString); + + } else { + // Просто создаем середину + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString)); + + if (triangulate) { + u = uStep*prevRadial; + setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(u, 1 - (vStep*segment)); + cUV = new Point(u + uStep, 1 - vStep * (segment + 1)); + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString); + } else { + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sphere(0, 0); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as new file mode 100644 index 0000000..5def643 --- /dev/null +++ b/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as @@ -0,0 +1,834 @@ +package alternativa.utils { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Утилиты для работы с Mesh-объектами. + */ + public class MeshUtils { + + static private var verticesSort:Array = ["x", "y", "z"]; + static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC]; + + /** + * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются. + * + * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh + * + * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов + */ + static public function uniteMeshes(... meshes):Mesh { + var res:Mesh = new Mesh(); + + var length:uint = meshes.length; + var key:*; + var vertex:Vertex; + var face:Face; + var j:uint; + for (var i:uint = 0; i < length; i++) { + var mesh:Mesh = meshes[i]; + var vertices:Map = mesh._vertices.clone(); + for (key in vertices) { + vertex = vertices[key]; + vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z); + } + var faces:Map = mesh._faces.clone(); + for (key in faces) { + face = faces[key]; + var faceVertices:Array = new Array().concat(face._vertices); + for (j = 0; j < face._verticesCount; j++) { + vertex = faceVertices[j]; + faceVertices[j] = vertices[vertex.id]; + } + faces[key] = res.createFace(faceVertices); + res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]); + } + for (key in mesh._surfaces) { + var surface:Surface = mesh._surfaces[key]; + var surfaceFaces:Array = surface._faces.toArray(); + var numFaces:uint = surfaceFaces.length; + for (j = 0; j < numFaces; j++) { + face = surfaceFaces[j]; + surfaceFaces[j] = faces[face.id]; + } + var newSurface:Surface = res.createSurface(surfaceFaces); + newSurface.material = SurfaceMaterial(surface.material.clone()); + } + } + return res; + } + + /** + * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности. + * + * @param mesh объект, вершины которого объединяются + * @param threshold погрешность измерения расстояний + */ + static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void { + // Получаем список вершин меша и сортируем по координатам + var vertices:Array = mesh._vertices.toArray(true); + vertices.sortOn(verticesSort, verticesSortOptions); + + // Поиск вершин с одинаковыми координатами + var weld:Map = new Map(true); + var vertex:Vertex; + var currentVertex:Vertex = vertices[0]; + var length:uint = vertices.length; + var i:uint; + for (i = 1; i < length; i++) { + vertex = vertices[i]; + if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) { + weld[vertex] = currentVertex; + } else { + currentVertex = vertex; + } + } + + // Собираем грани объединяемых вершин + var faces:Set = new Set(true); + var keyVertex:*; + var keyFace:*; + for (keyVertex in weld) { + vertex = keyVertex; + for (keyFace in vertex._faces) { + faces[keyFace] = true; + } + } + + // Заменяем грани + for (keyFace in faces) { + var face:Face = keyFace; + var id:Object = mesh.getFaceId(face); + var surface:Surface = face._surface; + var aUV:Point = face._aUV; + var bUV:Point = face._bUV; + var cUV:Point = face._cUV; + vertices = new Array().concat(face._vertices); + length = vertices.length; + for (i = 0; i < length; i++) { + vertex = weld[vertices[i]]; + if (vertex != null) { + vertices[i] = vertex; + } + } + mesh.removeFace(face); + face = mesh.createFace(vertices, id); + if (surface != null) { + surface.addFace(face); + } + face.aUV = aUV; + face.bUV = bUV; + face.cUV = cUV; + } + + // Удаляем вершины + for (keyVertex in weld) { + mesh.removeVertex(keyVertex); + } + } + + /** + * Объединение соседних граней, образующих плоский выпуклый многоугольник. + * + * @param mesh объект, грани которого объединяются + * @param angleThreshold погрешность измерения углов + * @param uvThreshold погрешность измерения UV-координат + */ + static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void { + angleThreshold = Math.cos(angleThreshold); + + var face:Face; + var sibling:Face; + var key:*; + var i:uint; + + // Формируем списки граней + var faces1:Set = new Set(true); + var faces2:Set = new Set(true); + + // Формируем список нормалей + var normals:Map = new Map(true); + for each (face in mesh._faces.clone()) { + var faceNormal:Point3D = face.normal; + if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) { + faces1[face] = true; + normals[face] = faceNormal; + } else { + mesh.removeFace(face); + } + } + + // Объединение + do { + // Флаг объединения + var weld:Boolean = false; + // Объединяем грани + while ((face = faces1.take()) != null) { + //var num:uint = face.num; + //var vertices:Array = face.vertices; + var currentWeld:Boolean = false; + + // Проверка общих граней по точкам + + + // Проверка общих граней по рёбрам + + // Перебираем точки грани + for (i = 0; (i < face._verticesCount) && !currentWeld; i++) { + var faceIndex1:uint = i; + var faceIndex2:uint; + var siblingIndex1:int; + var siblingIndex2:uint; + + // Перебираем грани текущей точки + var vertex:Vertex = face._vertices[faceIndex1]; + var vertexFaces:Set = vertex.faces; + for (key in vertexFaces) { + sibling = key; + // Если грань в списке на объединение и в одной поверхности + if (faces1[sibling] && face._surface == sibling._surface) { + faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0; + siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]); + // Если общее ребро + if (siblingIndex1 >= 0) { + // Если грани сонаправлены + var normal:Point3D = normals[face]; + if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) { + // Если в точках объединения нет перегибов + siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0; + + // Расширяем грани объединения + var i1:uint; + var i2:uint; + while (true) { + i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1); + i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0; + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex1 = i1; + siblingIndex2 = i2; + } else { + break; + } + } + + while (true) { + i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0; + i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1); + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex2 = i1; + siblingIndex1 = i2; + } else { + break; + } + } + + vertex = face._vertices[faceIndex1]; + var a:Point3D = vertex.coords; + vertex = face._vertices[faceIndex2]; + var b:Point3D = vertex.coords; + + // Считаем первый перегиб + vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0]; + var c:Point3D = vertex.coords; + vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)]; + var d:Point3D = vertex.coords; + + var cx:Number = c.x - a.x; + var cy:Number = c.y - a.y; + var cz:Number = c.z - a.z; + var dx:Number = d.x - a.x; + var dy:Number = d.y - a.y; + var dz:Number = d.z - a.z; + + var crossX:Number = cy*dz - cz*dy; + var crossY:Number = cz*dx - cx*dz; + var crossZ:Number = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если в первой точке перегиба нет + if (dot >= 0) { + + // Считаем второй перегиб + vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0]; + c = vertex.coords; + vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)]; + d = vertex.coords; + + cx = c.x - b.x; + cy = c.y - b.y; + cz = c.z - b.z; + dx = d.x - b.x; + dy = d.y - b.y; + dz = d.z - b.z; + + crossX = cy*dz - cz*dy; + crossY = cz*dx - cx*dz; + crossZ = cx*dy - cy*dx; + + if (crossX == 0 && crossY == 0 && crossZ == 0) { + if (cx*dx + cy*dy + cz*dz > 0) { + break; + } + } + + dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z; + + // Если во второй точке перегиба нет + if (dot >= 0) { + + // Флаг наличия UV у обеих граней + var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null); + + if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) { + + // Если грани имеют UV, проверяем совместимость + if (hasUV) { + vertex = sibling._vertices[0]; + var uv:Point = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[1]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[2]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) { + break; + } + } + + // Формируем новую грань + var newVertices:Array = new Array(); + var n:uint = faceIndex2; + do { + newVertices.push(face._vertices[n]); + n = (n < face._verticesCount - 1) ? (n + 1) : 0; + } while (n != faceIndex1); + n = siblingIndex2; + do { + newVertices.push(sibling._vertices[n]); + n = (n < sibling._verticesCount - 1) ? (n + 1) : 0; + } while (n != siblingIndex1); + + // Выбираем начальную точку + n = getBestBeginVertexIndex(newVertices); + for (var m:uint = 0; m < n; m++) { + newVertices.push(newVertices.shift()); + } + + // Заменяем грани новой + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (hasUV) { + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + mesh.removeFace(sibling); + + // Обновляем список нормалей + delete normals[sibling]; + delete normals[face]; + normals[newFace] = newFace.normal; + + // Обновляем списки расчётов + delete faces1[sibling]; + faces2[newFace] = true; + + // Помечаем объединение + weld = true; + currentWeld = true; + break; + } + } + } + } + } + } + } + } + + + + // Если не удалось объединить, переносим грань + faces2[face] = true; + } + + // Меняем списки + var fs:Set = faces1; + faces1 = faces2; + faces2 = fs; + } while (weld); + + removeIsolatedVertices(mesh); + removeUselessVertices(mesh); + } + + /** + * Удаление вершин объекта, не принадлежащим ни одной грани. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeIsolatedVertices(mesh:Mesh):void { + for each (var vertex:Vertex in mesh._vertices.clone()) { + if (vertex._faces.isEmpty()) { + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeUselessVertices(mesh:Mesh):void { + var v:Vertex; + var key:*; + var face:Face; + var index:uint; + var length:uint; + for each (var vertex:Vertex in mesh._vertices.clone()) { + var unless:Boolean = true; + var indexes:Map = new Map(true); + for (key in vertex._faces) { + face = key; + length = face._vertices.length; + index = face._vertices.indexOf(vertex); + v = face._vertices[index]; + var a:Point3D = v.coords; + v = face._vertices[(index < length - 1) ? (index + 1) : 0]; + var b:Point3D = v.coords; + v = face._vertices[(index > 0) ? (index - 1) : (length - 1)]; + var c:Point3D = v.coords; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) { + indexes[face] = index; + } else { + unless = false; + break; + } + } + if (unless && !indexes.isEmpty()) { + // Удаляем + for (key in indexes) { + var i:uint; + face = key; + index = indexes[face]; + length = face._vertices.length; + var newVertices:Array = new Array(); + for (i = 0; i < length; i++) { + if (i != index) { + newVertices.push(face._vertices[i]); + } + } + var n:uint = getBestBeginVertexIndex(newVertices); + for (i = 0; i < n; i++) { + newVertices.push(newVertices.shift()); + } + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var normal:Point3D = face.normal; + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + } + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вырожденных граней. + * + * @param mesh объект, грани которого удаляются + */ + static public function removeSingularFaces(mesh:Mesh):void { + for each (var face:Face in mesh._faces.clone()) { + var normal:Point3D = face.normal; + if (normal.x == 0 && normal.y == 0 && normal.z == 0) { + mesh.removeFace(face); + } + } + } + + /** + * @private + * Находит наиболее подходящую первую точку. + * @param vertices + * @return + */ + static public function getBestBeginVertexIndex(vertices:Array):uint { + var bestIndex:uint = 0; + var num:uint = vertices.length; + if (num > 3) { + var maxCrossLength:Number = 0; + var v:Vertex = vertices[num - 1]; + var c1:Point3D = v.coords; + v = vertices[0]; + var c2:Point3D = v.coords; + + var prevX:Number = c2.x - c1.x; + var prevY:Number = c2.y - c1.y; + var prevZ:Number = c2.z - c1.z; + + for (var i:uint = 0; i < num; i++) { + c1 = c2; + v = vertices[(i < num - 1) ? (i + 1) : 0]; + c2 = v.coords; + + var nextX:Number = c2.x - c1.x; + var nextY:Number = c2.y - c1.y; + var nextZ:Number = c2.z - c1.z; + + var crossX:Number = prevY*nextZ - prevZ*nextY; + var crossY:Number = prevZ*nextX - prevX*nextZ; + var crossZ:Number = prevX*nextY - prevY*nextX; + + + var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ; + if (crossLength > maxCrossLength) { + maxCrossLength = crossLength; + bestIndex = i; + } + + prevX = nextX; + prevY = nextY; + prevZ = nextZ; + } + // Берём предыдущий + bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1); + } + + return bestIndex; + } + + /** + * Генерация AS-класса. + * + * @param mesh объект, на базе которого генерируется класс + * @param packageName имя пакета для генерируемого класса + * @return AS-класс в текстовом виде + */ + static public function generateClass(mesh:Mesh, packageName:String = ""):String { + + var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1); + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["alternativa.engine3d.core.Mesh"] = true; + + var materialSet:Map = new Map(true); + var materialName:String; + var materialNum:uint = 1; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r"; + + + var newLine:Boolean = false; + if (mesh.mobility != 0) { + constructor += "\t\t\tmobility = " + mesh.mobility +";\r"; + newLine = true; + } + + if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) { + importSet["alternativa.types.Point3D"] = true; + constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r"; + newLine = true; + } else { + if (mesh.x != 0) { + constructor += "\t\t\tx = " + mesh.x + ";\r"; + newLine = true; + } + if (mesh.y != 0) { + constructor += "\t\t\ty = " + mesh.y + ";\r"; + newLine = true; + } + if (mesh.z != 0) { + constructor += "\t\t\tz = " + mesh.z + ";\r"; + newLine = true; + } + } + if (mesh.rotationX != 0) { + constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r"; + newLine = true; + } + if (mesh.rotationY != 0) { + constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r"; + newLine = true; + } + if (mesh.rotationZ != 0) { + constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r"; + newLine = true; + } + if (mesh.scaleX != 1) { + constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r"; + newLine = true; + } + if (mesh.scaleY != 1) { + constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r"; + newLine = true; + } + if (mesh.scaleZ != 1) { + constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + function idToString(value:*):String { + return isNaN(value) ? ("\"" + value + "\"") : value; + } + + function blendModeToString(value:String):String { + switch (value) { + case BlendMode.ADD: return "BlendMode.ADD"; + case BlendMode.ALPHA: return "BlendMode.ALPHA"; + case BlendMode.DARKEN: return "BlendMode.DARKEN"; + case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE"; + case BlendMode.ERASE: return "BlendMode.ERASE"; + case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT"; + case BlendMode.INVERT: return "BlendMode.INVERT"; + case BlendMode.LAYER: return "BlendMode.LAYER"; + case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN"; + case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY"; + case BlendMode.NORMAL: return "BlendMode.NORMAL"; + case BlendMode.OVERLAY: return "BlendMode.OVERLAY"; + case BlendMode.SCREEN: return "BlendMode.SCREEN"; + case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT"; + default: return "BlendMode.NORMAL"; + } + } + + function colorToString(value:uint):String { + var hex:String = value.toString(16).toUpperCase(); + var res:String = "0x"; + var len:uint = 6 - hex.length; + for (var j:uint = 0; j < len; j++) { + res += "0"; + } + res += hex; + return res; + } + + var i:uint; + var length:uint; + var key:*; + var id:String; + var face:Face; + var surface:Surface; + + newLine = false; + for (id in mesh._vertices) { + var vertex:Vertex = mesh._vertices[id]; + var coords:Point3D = vertex.coords; + constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + newLine = false; + for (id in mesh._faces) { + face = mesh._faces[id]; + length = face._verticesCount; + constructor += "\t\t\tcreateFace([" + for (i = 0; i < length - 1; i++) { + constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", "; + } + constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r"; + + if (face._aUV != null || face._bUV != null || face._cUV != null) { + importSet["flash.geom.Point"] = true; + constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r"; + } + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + for (id in mesh._surfaces) { + surface = mesh._surfaces[id]; + var facesStr:String = ""; + for (key in surface._faces) { + facesStr += idToString(mesh.getFaceId(key)) + ", "; + } + constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r"; + + if (surface.material != null) { + var material:String; + var defaultAlpha:Boolean = surface.material.alpha == 1; + var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL; + if (surface.material is WireMaterial) { + importSet["alternativa.engine3d.materials.WireMaterial"] = true; + var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0; + var defaultColor:Boolean = WireMaterial(surface.material).color == 0; + material = "new WireMaterial("; + if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) { + material += WireMaterial(surface.material).thickness; + if (!defaultColor || !defaultAlpha || !defaultBlendMode) { + material += ", " + colorToString(WireMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode) { + material += ", " + surface.material.alpha ; + if (!defaultBlendMode) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + } + } + } + } + } + var defaultWireThickness:Boolean; + var defaultWireColor:Boolean; + if (surface.material is FillMaterial) { + importSet["alternativa.engine3d.materials.FillMaterial"] = true; + defaultWireThickness = FillMaterial(surface.material).wireThickness < 0; + defaultWireColor = FillMaterial(surface.material).wireColor == 0; + material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + material += ", " + surface.material.alpha; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor) { + material += ", " + FillMaterial(surface.material).wireThickness; + if (!defaultWireColor) { + material += ", " + colorToString(FillMaterial(surface.material).wireColor); + } + } + } + } + } + if (surface.material is TextureMaterial) { + importSet["alternativa.engine3d.materials.TextureMaterial"] = true; + var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat; + var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth; + defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0; + defaultWireColor = TextureMaterial(surface.material).wireColor == 0; + var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM; + + if (TextureMaterial(surface.material).texture == null) { + materialName = "null"; + } else { + importSet["alternativa.types.Texture"] = true; + if (materialSet[TextureMaterial(surface.material).texture] == undefined) { + materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++; + materialSet[TextureMaterial(surface.material).texture] = materialName; + } else { + materialName = materialSet[TextureMaterial(surface.material).texture]; + } + materialName = materialName.split(".")[0]; + } + material = "new TextureMaterial(" + materialName; + if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).alpha; + if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).repeat; + if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).smooth; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).wireThickness; + if (!defaultWireColor || !defaultPrecision) { + material += ", " + colorToString(TextureMaterial(surface.material).wireColor); + if (!defaultPrecision) { + material += ", " + TextureMaterial(surface.material).precision; + } + } + } + } + } + } + } + } + + constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r"; + } + } + + var imports:String = ""; + newLine = false; + + var importArray:Array = new Array(); + for (key in importSet) { + importArray.push(key); + } + importArray.sort(); + + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + var embeds:String = ""; + newLine = false; + for each (materialName in materialSet) { + var materialClassName:String = materialName.split(".")[0]; + var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1); + embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r"; + newLine = true; + } + embeds += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/Alternativa3D.as b/Alternativa3D5/5.6/alternativa/Alternativa3D.as new file mode 100644 index 0000000..398627f --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "5.6.0"; + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as new file mode 100644 index 0000000..3d7aceb --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as @@ -0,0 +1,894 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием + * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены. + */ + public class CameraController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; +// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; +// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения угла тангажа. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш уменьшения угла тангажа. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево). + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо). + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + + // флаги действий + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _up:Boolean; + private var _down:Boolean; + private var _pitchUp:Boolean; + private var _pitchDown:Boolean; + private var _yawLeft:Boolean; + private var _yawRight:Boolean; + private var _accelerate:Boolean; + + private var _moveLocal:Boolean = true; + + // Флаг включения управления камерой + private var _controlsEnabled:Boolean = false; + + // Значение таймера в начале прошлого кадра + private var lastFrameTime:uint; + + // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов. + private var _mouseSensitivity:Number = 1; + // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + private var _mousePitch:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по тангажу + private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch; + // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + private var _mouseYaw:Number = Math.PI / 360; + // Результирующий коэффициент поворота камеры мышью по расканью + private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw; + + // Вспомогательные переменные для обзора мышью + private var mouseLookActive:Boolean; + private var startDragCoords:Point = new Point(); + private var currentDragCoords:Point = new Point(); + private var prevDragCoords:Point = new Point(); + private var startRotX:Number; + private var startRotZ:Number; + + // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры + private var _pitchSpeed:Number = 1; + // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры + private var _yawSpeed:Number = 1; + // Скорость изменения крена в радианах за секунду при управлении с клавиатуры +// public var bankSpeed:Number = 2; +// private var bankMatrix:Matrix3D = new Matrix3D(); + + // Скорость поступательного движения в единицах за секунду + private var _speed:Number = 100; + // Коэффициент увеличения скорости при соответствующей нажатой клавише + private var _speedMultiplier:Number = 2; + + private var velocity:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + + private var _fovStep:Number = Math.PI / 180; + private var _zoomMultiplier:Number = 0.1; + + // Привязка клавиш к действиям + private var keyBindings:Map = new Map(); + // Привязка действий к обработчикам + private var actionBindings:Map = new Map(); + + // Источник событий клавиатуры и мыши + private var _eventsSource:DisplayObject; + // Управляемая камера + private var _camera:Camera3D; + + // Класс реализации определния столкновений + private var _collider:EllipsoidCollider; + // Флаг необходимости проверки столкновений + private var _checkCollisions:Boolean; + // Радиус сферы для определения столкновений + private var _collisionRadius:Number = 0; + // Набор исключаемых из проверки столкновений объектов + private var _collisionIgnoreSet:Set = new Set(true); + // Флаг движения + private var _isMoving:Boolean; + + private var _onStartMoving:Function; + private var _onStopMoving:Function; + + /** + * Создание экземпляра контроллера. + * + * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры + * + * @throws ArgumentError в качестве eventsSourceObject не может быть указан null + */ + public function CameraController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = forward; + actionBindings[ACTION_BACK] = back; + actionBindings[ACTION_LEFT] = left; + actionBindings[ACTION_RIGHT] = right; + actionBindings[ACTION_UP] = up; + actionBindings[ACTION_DOWN] = down; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
+ */ + public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + } + + /** + * Направление камеры на точку. + * + * @param point координаты точки направления камеры + */ + public function lookAt(point:Point3D):void { + if (_camera == null) { + return; + } + var dx:Number = point.x - _camera.x; + var dy:Number = point.y - _camera.y; + var dz:Number = point.z - _camera.z; + _camera.rotationZ = -Math.atan2(dx, dy); + _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90; + } + + /** + * Callback-функция, вызываемая при начале движения камеры. + */ + public function get onStartMoving():Function { + return _onStartMoving; + } + + /** + * @private + */ + public function set onStartMoving(value:Function):void { + _onStartMoving = value; + } + + /** + * Callback-функция, вызываемая при прекращении движения камеры. + */ + public function get onStopMoving():Function { + return _onStopMoving; + } + + /** + * @private + */ + public function set onStopMoving(value:Function):void { + _onStopMoving = value; + } + + /** + * Набор объектов, исключаемых из проверки столкновений. + */ + public function get collisionIgnoreSet():Set { + return _collisionIgnoreSet; + } + + /** + * Источник событий клавиатуры и мыши. + * + * @throws ArgumentError в качестве eventsSource не может быть указан null + */ + public function get eventsSource():DisplayObject { + return _eventsSource; + } + + /** + * @private + */ + public function set eventsSource(value:DisplayObject):void { + if (_eventsSource != value) { + if (value == null) { + throw new ArgumentError("CameraController: eventsSource is null"); + } + if (_controlsEnabled) { + unregisterEventsListeners(); + } + _eventsSource = value; + if (_controlsEnabled) { + registerEventListeners(); + } + } + } + + /** + * Ассоциированная камера. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + _camera = value; + if (value == null) { + controlsEnabled = false; + } else { + createCollider(); + } + } + } + + /** + * Режим движения камеры. Если значение равно true, то перемещения камеры происходят относительно + * локальной системы координат, иначе относительно глобальной. + * + * @default true + */ + public function get moveLocal():Boolean { + return _moveLocal; + } + + /** + * @private + */ + public function set moveLocal(value:Boolean):void { + _moveLocal = value; + } + + /** + * Включение режима проверки столкновений. + */ + public function get checkCollisions():Boolean { + return _checkCollisions; + } + + /** + * @private + */ + public function set checkCollisions(value:Boolean):void { + _checkCollisions = value; + } + + /** + * Радиус сферы для определения столкновений. + * + * @default 0 + */ + public function get collisionRadius():Number { + return _collisionRadius; + } + + /** + * @private + */ + public function set collisionRadius(value:Number):void { + _collisionRadius = value; + if (_collider != null) { + _collider.radiusX = _collisionRadius; + _collider.radiusY = _collisionRadius; + _collider.radiusZ = _collisionRadius; + } + } + + /** + * Привязка клавиши к действию. + * + * @param keyCode код клавиши + * @param action наименование действия + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Активация движения камеры вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function forward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения камеры назад. + * + * @param value true для начала движения, false для окончания + */ + public function back(value:Boolean):void { + _back = value; + } + + /** + * Активация движения камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function left(value:Boolean):void { + _left = value; + } + + /** + * Активация движения камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function right(value:Boolean):void { + _right = value; + } + + /** + * Активация движения камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function up(value:Boolean):void { + _up = value; + } + + /** + * Активация движения камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function down(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота камеры вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота камеры вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота камеры влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота камеры вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * @private + */ + private function createCollider():void { + _collider = new EllipsoidCollider(_camera.scene, _collisionRadius); + _collider.offsetThreshold = 0.01; + _collider.collisionSet = _collisionIgnoreSet; + } + + /** + * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw. + * + * @default 1 + * + * @see #mousePitch() + * @see #mouseYaw() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mousePitch():Number { + return _mousePitch; + } + + /** + * @private + */ + public function set mousePitch(pitch:Number):void { + _mousePitch = pitch; + _mousePitchCoeff = _mouseSensitivity * _mousePitch; + } + + /** + * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель). + * + * @default Math.PI / 360 + */ + public function get mouseYaw():Number { + return _mouseYaw; + } + + /** + * @private + */ + public function set mouseYaw(yaw:Number):void { + _mouseYaw = yaw; + _mouseYawCoeff = _mouseSensitivity * _mouseYaw; + } + + /** + * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость поступательного движения (единицы в секунду). + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(spd:Number):void { + _speed = spd; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Активность управления камеры. + * + * @default false + */ + public function get controlsEnabled():Boolean { + return _controlsEnabled; + } + + /** + * @private + */ + public function set controlsEnabled(value:Boolean):void { + if (_camera == null || _controlsEnabled == value) return; + if (value) { + lastFrameTime = getTimer(); + registerEventListeners(); + } + else { + unregisterEventsListeners(); + } + _controlsEnabled = value; + } + + /** + * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину + * MouseEvent.delta. + * + * @default Math.PI / 180 + */ + public function get fovStep():Number { + return _fovStep; + } + + /** + * @private + */ + public function set fovStep(value:Number):void { + _fovStep = value; + } + + /** + * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier). + * + * @default 0.1 + */ + public function get zoomMultiplier():Number { + return _zoomMultiplier; + } + + /** + * @private + */ + public function set zoomMultiplier(value:Number):void { + _zoomMultiplier = value; + } + + /** + * @private + */ + private function registerEventListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + */ + private function unregisterEventsListeners():void { + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + /** + * @private + * @param e + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * @private + * @param e + */ + private function onMouseDown(e:MouseEvent):void { + mouseLookActive = true; + currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY; + startRotX = _camera.rotationX; + startRotZ = _camera.rotationZ; + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseUp(e:MouseEvent):void { + mouseLookActive = false; + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * @private + * @param e + */ + private function onMouseWheel(e:MouseEvent):void { + if (_camera.orthographic) { + _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier); + } else { + _camera.fov -= _fovStep * e.delta; + } + } + + /** + * Обработка управляющих воздействий. + * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate(). + * + * @see alternativa.engine3d.core.Scene3D#calculate() + */ + public function processInput(): void { + if (!_controlsEnabled || _camera == null) return; + + // Время в секундах от начала предыдущего кадра + var frameTime:Number = getTimer() - lastFrameTime; + lastFrameTime += frameTime; + frameTime /= 1000; + + // Обработка mouselook + if (mouseLookActive) { + prevDragCoords.x = currentDragCoords.x; + prevDragCoords.y = currentDragCoords.y; + currentDragCoords.x = _eventsSource.stage.mouseX; + currentDragCoords.y = _eventsSource.stage.mouseY; + if (!prevDragCoords.equals(currentDragCoords)) { + _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff; + var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawLeft) { + _camera.rotationZ += _yawSpeed * frameTime; + } else if (_yawRight) { + _camera.rotationZ -= _yawSpeed * frameTime; + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + rotX = _camera.rotationX + _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } else if (_pitchDown) { + rotX = _camera.rotationX - _pitchSpeed * frameTime; + _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX; + } + + // TODO: Поворот относительно продольной оси (крен, roll) + + var frameDistance:Number = _speed * frameTime; + if (_accelerate) { + frameDistance *= _speedMultiplier; + } + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + var transformation:Matrix3D = _camera._transformation; + + if (_moveLocal) { + // Режим относительных пермещений + // Движение вперед-назад + if (_forward) { + velocity.x += frameDistance * transformation.c; + velocity.y += frameDistance * transformation.g; + velocity.z += frameDistance * transformation.k; + } else if (_back) { + velocity.x -= frameDistance * transformation.c; + velocity.y -= frameDistance * transformation.g; + velocity.z -= frameDistance * transformation.k; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * transformation.a; + velocity.y -= frameDistance * transformation.e; + velocity.z -= frameDistance * transformation.i; + } else if (_right) { + velocity.x += frameDistance * transformation.a; + velocity.y += frameDistance * transformation.e; + velocity.z += frameDistance * transformation.i; + } + // Движение вверх-вниз + if (_up) { + velocity.x -= frameDistance * transformation.b; + velocity.y -= frameDistance * transformation.f; + velocity.z -= frameDistance * transformation.j; + } else if (_down) { + velocity.x += frameDistance * transformation.b; + velocity.y += frameDistance * transformation.f; + velocity.z += frameDistance * transformation.j; + } + } + else { + // Режим глобальных перемещений + var cosZ:Number = Math.cos(_camera.rotationZ); + var sinZ:Number = Math.sin(_camera.rotationZ); + // Движение вперед-назад + if (_forward) { + velocity.x -= frameDistance * sinZ; + velocity.y += frameDistance * cosZ; + } else if (_back) { + velocity.x += frameDistance * sinZ; + velocity.y -= frameDistance * cosZ; + } + // Движение влево-вправо + if (_left) { + velocity.x -= frameDistance * cosZ; + velocity.y -= frameDistance * sinZ; + } else if (_right) { + velocity.x += frameDistance * cosZ; + velocity.y += frameDistance * sinZ; + } + // Движение вверх-вниз + if (_up) { + velocity.z += frameDistance; + } else if (_down) { + velocity.z -= frameDistance; + } + } + + // Коррекция модуля вектора скорости + if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) { + velocity.length = frameDistance; + } + + // Проверка столкновений + if (_checkCollisions) { + _collider.calculateDestination(_camera.coords, velocity, destination); + _camera.x = destination.x; + _camera.y = destination.y; + _camera.z = destination.z; + } else { + _camera.x += velocity.x; + _camera.y += velocity.y; + _camera.z += velocity.z; + } + + // Обработка начала/окончания движения + if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (_onStartMoving != null) { + _onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (_onStopMoving != null) { + _onStopMoving.call(this); + } + } + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as new file mode 100644 index 0000000..6199401 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as @@ -0,0 +1,475 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление объектом, находящимся в корневом объекте сцены, подобно управлению летательным аппаратом. + * Повороты выполняются вокруг локальных осей объекта, собственные ускорения действуют вдоль локальных осей. + * + *

Соответствия локальных осей для объектов, не являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВперёдКрен
ZВверхРысканье

+ * + *

Соответствия локальных осей для объектов, являющихся камерой: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ОсьНаправлениеПоворот
XВправоТангаж
YВнизРысканье
ZВперёдКрен

+ *

+ * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или + * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются + * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши + * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент + * чувствительности мыши по горизонтали даёт угловую скорость по крену. + *

+ */ + public class FlyController extends ObjectController { + /** + * Имя действия для привязки клавиш поворота по крену влево. + */ + public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; + /** + * Имя действия для привязки клавиш поворота по крену вправо. + */ + public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + + private var _rollLeft:Boolean; + private var _rollRight:Boolean; + + private var _rollSpeed:Number = 1; + + private var rotations:Point3D; + private var rollMatrix:Matrix3D = new Matrix3D(); + private var transformation:Matrix3D = new Matrix3D(); + private var axis:Point3D = new Point3D(); + + private var velocity:Point3D = new Point3D(); + private var displacement:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + private var deltaVelocity:Point3D = new Point3D(); + private var accelerationVector:Point3D = new Point3D(); + private var currentTransform:Matrix3D = new Matrix3D(); + + private var _currentSpeed:Number = 1; + /** + * Текущие координаты мышиного курсора в режиме mouse look. + */ + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * Модуль вектора ускорния, получаемого от команд движения. + */ + public var acceleration:Number = 1000; + /** + * Модуль вектора замедляющего ускорения. + */ + public var deceleration:Number = 50; + /** + * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + /** + * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор + * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту + * прикладывается замедляющее ускорение. + */ + public var inertialMode:Boolean; + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + * + * @throws ArgumentError в качестве eventsSourceObject не может быть указан null + */ + public function FlyController(eventsSourceObject:DisplayObject) { + super(eventsSourceObject); + + actionBindings[ACTION_ROLL_LEFT] = rollLeft; + actionBindings[ACTION_ROLL_RIGHT] = rollRight; + } + + /** + * Текущая скорость движения. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Активация вращения по крену влево. + */ + public function rollLeft(value:Boolean):void { + _rollLeft = value; + } + + /** + * Активация вращения по крену вправо. + */ + public function rollRight(value:Boolean):void { + _rollRight = value; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_ROLL_LEFT
RIGHTACTION_ROLL_RIGHT
QACTION_YAW_LEFT
EACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT); + bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями. + * + * @param frameTime длительность текущего кадра в секундах + */ + override protected function rotateObject(frameTime:Number):void { + var transformation:Matrix3D = _object._transformation; + if (_mouseLookActive) { + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!currentMouseCoords.equals(startMouseCoords)) { + var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX; + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + + rotateObjectAroundAxis(axis, deltaYaw * frameTime); + + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY; + axis.x = currentTransform.a; + axis.y = currentTransform.e; + axis.z = currentTransform.i; + + rotateObjectAroundAxis(axis, deltaPitch * frameTime); + } + } + + // Поворот относительно продольной оси (крен, roll) + if (_rollLeft) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, -_rollSpeed * frameTime); + } else if (_rollRight) { + if (_object is Camera3D) { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + } else { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + } + rotateObjectAroundAxis(axis, _rollSpeed * frameTime); + } + + // Поворот относительно поперечной оси (тангаж, pitch) + if (_pitchUp) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, _pitchSpeed * frameTime); + } else if (_pitchDown) { + axis.x = transformation.a; + axis.y = transformation.e; + axis.z = transformation.i; + rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime); + } + + // Поворот относительно вертикальной оси (рысканье, yaw) + if (_yawRight) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } + } else if (_yawLeft) { + if (_object is Camera3D) { + axis.x = transformation.b; + axis.y = transformation.f; + axis.z = transformation.j; + rotateObjectAroundAxis(axis, -_yawSpeed * frameTime); + } else { + axis.x = transformation.c; + axis.y = transformation.g; + axis.z = transformation.k; + rotateObjectAroundAxis(axis, _yawSpeed * frameTime); + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + // Движение вперед-назад + accelerationVector.x = 0; + accelerationVector.y = 0; + accelerationVector.z = 0; + if (_forward) { + accelerationVector.y = 1; + } else if (_back) { + accelerationVector.y = -1; + } + // Движение влево-вправо + if (_right) { + accelerationVector.x = 1; + } else if (_left) { + accelerationVector.x = -1; + } + // Движение ввверх-вниз + if (_up) { + accelerationVector.z = 1; + } else if (_down) { + accelerationVector.z = -1; + } + + var speedLoss:Number; + var len:Number; + + if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) { + // Управление активно + if (_object is Camera3D) { + var tmp:Number = accelerationVector.z; + accelerationVector.z = accelerationVector.y; + accelerationVector.y = -tmp; + } + accelerationVector.normalize(); + accelerationVector.x *= acceleration; + accelerationVector.y *= acceleration; + accelerationVector.z *= acceleration; + currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + accelerationVector.transform(currentTransform); + deltaVelocity.x = accelerationVector.x; + deltaVelocity.y = accelerationVector.y; + deltaVelocity.z = accelerationVector.z; + deltaVelocity.x *= frameTime; + deltaVelocity.y *= frameTime; + deltaVelocity.z *= frameTime; + + if (!inertialMode) { + speedLoss = deceleration * frameTime; + var dot:Number = Point3D.dot(velocity, accelerationVector); + if (dot > 0) { + len = accelerationVector.length; + var x:Number = accelerationVector.x / len; + var y:Number = accelerationVector.y / len; + var z:Number = accelerationVector.z / len; + len = dot / len; + x = velocity.x - len * x; + y = velocity.y - len * y; + z = velocity.z - len * z; + len = Math.sqrt(x*x + y*y + z*z); + if (len > speedLoss) { + x *= speedLoss / len; + y *= speedLoss / len; + z *= speedLoss / len; + } + velocity.x -= x; + velocity.y -= y; + velocity.z -= z; + } else { + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + velocity.x += deltaVelocity.x; + velocity.y += deltaVelocity.y; + velocity.z += deltaVelocity.z; + + if (velocity.length > _speed) { + velocity.length = _speed; + } + } else { + // Управление неактивно + if (!inertialMode) { + speedLoss = deceleration * frameTime; + len = velocity.length; + velocity.length = (len > speedLoss) ? (len - speedLoss) : 0; + } + } + + displacement.x = velocity.x * frameTime; + displacement.y = velocity.y * frameTime; + displacement.z = velocity.z * frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x / frameTime; + velocity.y = displacement.y / frameTime; + velocity.z = displacement.z / frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Поворот объекта вокруг заданной оси. + * + * @param axis + * @param angle + */ + private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void { + transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1); + rollMatrix.fromAxisAngle(axis, angle); + rollMatrix.inverseCombine(transformation); + rotations = rollMatrix.getRotations(rotations); + _object.rotationX = rotations.x; + _object.rotationY = rotations.y; + _object.rotationZ = rotations.z; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + + /** + * @inheritDoc + */ + override public function set enabled(value:Boolean):void { + super.enabled = value; + if (!value) { + velocity.reset(); + _currentSpeed = 0; + } + } + + /** + * @inheritDoc + */ + override protected function clearCommandFlags():void { + super.clearCommandFlags(); + _rollLeft = false; + _rollRight = false; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/ObjectController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/ObjectController.as new file mode 100644 index 0000000..1a69ece --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/ObjectController.as @@ -0,0 +1,888 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.physics.EllipsoidCollider; + import alternativa.types.Map; + import alternativa.types.Point3D; + import alternativa.utils.ObjectUtils; + + import flash.display.DisplayObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе + * реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд + * перемещения. + */ + public class ObjectController { + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + /** + * Флаг движения вперёд. + */ + protected var _forward:Boolean; + /** + * Флаг движения назад. + */ + protected var _back:Boolean; + /** + * Флаг движения влево. + */ + protected var _left:Boolean; + /** + * Флаг движения вправо. + */ + protected var _right:Boolean; + /** + * Флаг движения вверх. + */ + protected var _up:Boolean; + /** + * Флаг движения вниз. + */ + protected var _down:Boolean; + /** + * Флаг поворота относительно оси X в положительном направлении (взгляд вверх). + */ + protected var _pitchUp:Boolean; + /** + * Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз). + */ + protected var _pitchDown:Boolean; + /** + * Флаг поворота относительно оси Z в положительном направлении (взгляд налево). + */ + protected var _yawLeft:Boolean; + /** + * Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо). + */ + protected var _yawRight:Boolean; + /** + * Флаг активности режима ускорения. + */ + protected var _accelerate:Boolean; + /** + * Флаг активности режима поворотов мышью. + */ + protected var _mouseLookActive:Boolean; + /** + * Начальные координаты мышиного курсора в режиме mouse look. + */ + protected var startMouseCoords:Point3D = new Point3D(); + /** + * Флаг активности контроллера. + */ + protected var _enabled:Boolean = true; + /** + * Источник событий клавиатуры и мыши + */ + protected var _eventsSource:DisplayObject; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Map = new Map(); + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + protected var actionBindings:Map = new Map(); + /** + * Флаг активности клавиатуры. + */ + protected var _keyboardEnabled:Boolean; + /** + * Флаг активности мыши. + */ + protected var _mouseEnabled:Boolean; + /** + * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали. + */ + protected var _mouseSensitivity:Number = 1; + /** + * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseSensitivityY:Number = Math.PI / 360; + /** + * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseSensitivityX:Number = Math.PI / 360; + /** + * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали. + */ + protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY; + /** + * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали. + */ + protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX; + /** + * Угловая скорость поворота вокруг поперечной оси в радианах за секунду. + */ + protected var _pitchSpeed:Number = 1; + /** + * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду. + */ + protected var _yawSpeed:Number = 1; + /** + * Скорость поступательного движения в единицах за секунду. + */ + protected var _speed:Number = 100; + /** + * Коэффициент увеличения скорости при соответствующей активной команде. + */ + protected var _speedMultiplier:Number = 2; + /** + * Управляемый объект. + */ + protected var _object:Object3D; + /** + * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра). + */ + protected var lastFrameTime:uint; + /** + * Текущие координаты контроллера. + */ + protected var _coords:Point3D = new Point3D(); + /** + * Индикатор движения объекта (перемещения или поворота) в текущем кадре. + */ + protected var _isMoving:Boolean; + /** + * Объект для определения столкновений. + */ + protected var _collider:EllipsoidCollider = new EllipsoidCollider(); + + /** + * Включение и выключение режима проверки столкновений. + */ + public var checkCollisions:Boolean; + /** + * Функция вида function():void, вызываемая при начале движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStartMoving:Function; + /** + * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением + * понимается изменение координат или ориентации. + */ + public var onStopMoving:Function; + + // Вектор смещения + private var _displacement:Point3D = new Point3D(); + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + * + * @throws ArgumentError в качестве eventsSourceObject не может быть указан null + */ + public function ObjectController(eventsSourceObject:DisplayObject) { + if (eventsSourceObject == null) { + throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null"); + } + _eventsSource = eventsSourceObject; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_PITCH_UP] = pitchUp; + actionBindings[ACTION_PITCH_DOWN] = pitchDown; + actionBindings[ACTION_YAW_LEFT] = yawLeft; + actionBindings[ACTION_YAW_RIGHT] = yawRight; + actionBindings[ACTION_ACCELERATE] = accelerate; + actionBindings[ACTION_MOUSE_LOOK] = setMouseLook; + + keyboardEnabled = true; + mouseEnabled = true; + } + + /** + * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput(). + * + * @default true + * + * @see #processInput() + */ + public function get enabled():Boolean { + return _enabled; + } + + /** + * @private + */ + public function set enabled(value:Boolean):void { + _enabled = value; + if (_enabled) { + if (_mouseEnabled) { + registerMouseListeners(); + } + if (_keyboardEnabled) { + registerKeyboardListeners(); + } + } else { + if (_mouseEnabled) { + unregisterMouseListeners(); + setMouseLook(false); + } + if (_keyboardEnabled) { + unregisterKeyboardListeners(); + } + } + } + + /** + * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения + * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера. + * + * @see #setObjectCoords() + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + _coords.copy(value); + setObjectCoords(); + } + + /** + * Чтение координат контроллера в заданную переменную. + * + * @param point переменная, в которую записываются координаты контроллера + */ + public function readCoords(point:Point3D):void { + point.copy(_coords); + } + + /** + * Управляемый объект. + */ + public function get object():Object3D { + return _object; + } + + /** + * @private + * При установке объекта устанавливается сцена для коллайдера. + */ + public function set object(value:Object3D):void { + _object = value; + _collider.scene = _object == null ? null : _object.scene; + setObjectCoords(); + } + + /** + * Объект, реализующий проверку столкновений для эллипсоида. + */ + public function get collider():EllipsoidCollider { + return _collider; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация поворота вверх. + * + * @param value true для начала движения, false для окончания + */ + public function pitchUp(value:Boolean):void { + _pitchUp = value; + } + + /** + * Активация поворота вниз. + * + * @param value true для начала движения, false для окончания + */ + public function pitchDown(value:Boolean):void { + _pitchDown = value; + } + + /** + * Активация поворота влево. + * + * @param value true для начала движения, false для окончания + */ + public function yawLeft(value:Boolean):void { + _yawLeft = value; + } + + /** + * Активация поворота вправо. + * + * @param value true для начала движения, false для окончания + */ + public function yawRight(value:Boolean):void { + _yawRight = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Угловая скорость поворота вокруг поперечной оси (радианы в секунду). + * + * @default 1 + */ + public function get pitchSpeed():Number { + return _pitchSpeed; + } + + /** + * @private + */ + public function set pitchSpeed(spd:Number):void { + _pitchSpeed = spd; + } + + /** + * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду). + * + * @default 1 + */ + public function get yawSpeed():Number { + return _yawSpeed; + } + + /** + * @private + */ + public function set yawSpeed(spd:Number):void { + _yawSpeed = spd; + } + + /** + * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get speed():Number { + return _speed; + } + + /** + * @private + */ + public function set speed(value:Number):void { + _speed = value < 0 ? -value : value; + } + + /** + * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE. + * + * @default 2 + */ + public function get speedMultiplier():Number { + return _speedMultiplier; + } + + /** + * @private + */ + public function set speedMultiplier(value:Number):void { + _speedMultiplier = value; + } + + /** + * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY. + * + * @default 1 + * + * @see #mouseSensitivityY() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivity():Number { + return _mouseSensitivity; + } + + /** + * @private + */ + public function set mouseSensitivity(sensitivity:Number):void { + _mouseSensitivity = sensitivity; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Чувствительность мыши по вертикали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityX() + */ + public function get mouseSensitivityY():Number { + return _mouseSensitivityY; + } + + /** + * @private + */ + public function set mouseSensitivityY(value:Number):void { + _mouseSensitivityY = value; + _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY; + } + + /** + * Чувствительность мыши по горизонтали. + * + * @default Math.PI / 360 + * + * @see #mouseSensitivity() + * @see #mouseSensitivityY() + */ + public function get mouseSensitivityX():Number { + return _mouseSensitivityX; + } + + /** + * @private + */ + public function set mouseSensitivityX(value:Number):void { + _mouseSensitivityX = value; + _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX; + } + + /** + * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(), + * при выключении — stoptMouseLook(). + * + * @see #startMouseLook() + * @see #stopMouseLook() + */ + public function setMouseLook(value:Boolean):void { + if (_mouseLookActive != value) { + _mouseLookActive = value; + if (_mouseLookActive) { + startMouseLook(); + } else { + stopMouseLook(); + } + } + } + + /** + * Метод выполняет необходимые действия при включении режима вращения объекта мышью. + * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords. + * + * @see #startMouseCoords + * @see #setMouseLook() + * @see #stopMouseLook() + */ + protected function startMouseLook():void { + startMouseCoords.x = _eventsSource.stage.mouseX; + startMouseCoords.y = _eventsSource.stage.mouseY; + } + + /** + * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает + * ничего. + * + * @see #setMouseLook() + * @see #startMouseLook() + */ + protected function stopMouseLook():void { + } + + /** + * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled + * равно false, метод не выполняется. + *

+ * Алгоритм работы метода следующий: + *

+ */ + public function processInput():void { + if (!_enabled || _object == null) { + return; + } + var frameTime:Number = getTimer() - lastFrameTime; + // Проверка в связи с возможным багом десятого плеера, ну и вообще на всякий случай + if (frameTime == 0) { + return; + } + + lastFrameTime += frameTime; + if (frameTime > 100) { + frameTime = 100; + } + frameTime /= 1000; + + rotateObject(frameTime); + getDisplacement(frameTime, _displacement); + applyDisplacement(frameTime, _displacement); + + // Обработка начала/окончания движения + if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) { + if (!_isMoving) { + _isMoving = true; + if (onStartMoving != null) { + onStartMoving.call(this); + } + } + } else { + if (_isMoving) { + _isMoving = false; + if (onStopMoving != null) { + onStopMoving.call(this); + } + } + } + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + */ + protected function rotateObject(frameTime:Number):void { + } + + /** + * Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement смещение объекта, которое нужно обработать + */ + protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) { + keyBindings[keyCode] = method; + } + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + keyBindings.remove(keyCode); + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + keyBindings.clear(); + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + } + + /** + * Включение и выключение обработки клавиатурных событий. При включении выполняется метод registerKeyboardListeners, + * при выключении — unregisterKeyboardListeners. + * + * @see #registerKeyboardListeners() + * @see #unregisterKeyboardListeners() + */ + public function get keyboardEnabled():Boolean { + return _keyboardEnabled; + } + + /** + * @private + */ + public function set keyboardEnabled(value:Boolean):void { + if (_keyboardEnabled != value) { + _keyboardEnabled = value; + if (_keyboardEnabled) { + if (_enabled) { + registerKeyboardListeners(); + } + } else { + unregisterKeyboardListeners(); + } + } + } + + /** + * @private + * Запуск обработчиков клавиатурных команд. + */ + private function onKeyboardEvent(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) { + method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки клавиатурных событий. + * + * @see #unregisterKeyboardListeners() + */ + protected function registerKeyboardListeners():void { + _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Удаление обработчиков при выключении обработки клавиатурных событий. + * + * @see #registerKeyboardListeners() + */ + protected function unregisterKeyboardListeners():void { + clearCommandFlags(); + _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); + _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); + } + + /** + * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners, + * при выключении — unregisterMouseListeners. + * + * @see #registerMouseListeners() + * @see #unregisterMouseListeners() + */ + public function get mouseEnabled():Boolean { + return _mouseEnabled; + } + + /** + * @private + */ + public function set mouseEnabled(value:Boolean):void { + if (_mouseEnabled != value) { + _mouseEnabled = value; + if (_mouseEnabled) { + if (_enabled) { + registerMouseListeners(); + } + } else { + unregisterMouseListeners(); + } + } + } + + /** + * Регистрация необходимых обработчиков при включении обработки мышиных событий. + * + * @see #unregisterMouseListeners() + */ + protected function registerMouseListeners():void { + _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + } + + /** + * Удаление используемых обработчиков при выключении обработки мышиных событий. + * + * @see #registerMouseListeners() + */ + protected function unregisterMouseListeners():void { + _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Активация mouselook + */ + private function onMouseDown(e:MouseEvent):void { + setMouseLook(true); + _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Отключение mouselook + */ + private function onMouseUp(e:MouseEvent):void { + setMouseLook(false); + _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Установка координат управляемого объекта в зависимости от текущих координат контроллера. + */ + protected function setObjectCoords():void { + if (_object != null) { + _object.coords = _coords; + } + } + + /** + * Индикатор режима увеличенной скорости. + */ + public function get accelerated():Boolean { + return _accelerate; + } + + /** + * Метод сбрасывает флаги активных команд. + */ + protected function clearCommandFlags():void { + _forward = false; + _back = false; + _left = false; + _right = false; + _up = false; + _down = false; + _pitchUp = false; + _pitchDown = false; + _yawLeft = false; + _yawRight = false; + _accelerate = false; + _mouseLookActive = false; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as new file mode 100644 index 0000000..8b8d46a --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as @@ -0,0 +1,570 @@ +package alternativa.engine3d.controllers { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.physics.Collision; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.KeyboardUtils; + import alternativa.utils.MathUtils; + + import flash.display.DisplayObject; + + use namespace alternativa3d; + + /** + * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены. + * + *

Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила + * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка + * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность + * движения по вертикали.

+ * + *

Для всех объектов, за исключением Camera3D, направлением "вперёд" считается направление его оси + * Y, направлением "вверх" — направление оси Z. Для объектов класса + * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление + * "вверх" противоположно направлению локальной оси Y.

+ * + *

Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для + * эллипсоида, параметры которого устанавливаются через свойство collider. Координаты управляемого + * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида, + * задаваемого параметром objectZPosition.

+ * + *

Команда ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях + * происходит движение вверх.

+ */ + public class WalkController extends ObjectController { + /** + * Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z, + * при отрицательном — по оси Z. + */ + public var gravity:Number = 0; + /** + * Вертикальная скорость прыжка. + */ + public var jumpSpeed:Number = 0; + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + private var _groundMesh:Mesh; + /** + * Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности + * скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + + /** + * Ограничение поворота объекта по поперечной оси + * + * @default Math.PI / 2 + * + * @see minPitch + */ + public var maxPitch:Number = 0.5*Math.PI; + + /** + * Ограничение поворота объекта по поперечной оси + * + * @default -Math.PI / 2 + * + * @see maxPitch + */ + public var minPitch:Number = -0.5*Math.PI; + + // Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации. + private var _airControlCoefficient:Number = 1; + + private var _currentSpeed:Number = 0; + + private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70)); + + private var destination:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + + private var _objectZPosition:Number = 0.5; + private var _flyMode:Boolean; + private var _onGround:Boolean; + private var _jumpLocked:Boolean; + + private var velocity:Point3D = new Point3D(); + private var tmpVelocity:Point3D = new Point3D(); + + private var controlsActive:Boolean; + + private var inJump:Boolean; + private var startRotX:Number; + private var startRotZ:Number; + + // Координаты мышиного курсора в режиме mouse look в предыдущем кадре. + private var prevMouseCoords:Point3D = new Point3D(); + // Текущие координаты мышиного курсора в режиме mouse look. + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + * @param object управляемый объект + * + * @throws ArgumentError в качестве eventsSourceObject не может быть указан null + */ + public function WalkController(eventSourceObject:DisplayObject, object:Object3D = null) { + super(eventSourceObject); + this.object = object; + } + + /** + * Объект, на котором стоит эллипсоид при ненулевой гравитации. + */ + public function get groundMesh():Mesh { + return _groundMesh; + } + + /** + * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд" + * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю. + * + * @param point координаты точки, на которую должен быть направлен объект + */ + public function lookAt(point:Point3D):void { + if (_object == null) { + return; + } + var dx:Number = point.x - _object.x; + var dy:Number = point.y - _object.y; + var dz:Number = point.z - _object.z; + _object.rotationX = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0); + _object.rotationY = 0; + _object.rotationZ = -Math.atan2(dx, dy); + } + + /** + * Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации. + * Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при + * нахождении на поверхности. + * + * @default 1 + */ + public function get airControlCoefficient():Number { + return _airControlCoefficient; + } + + /** + *@ private + */ + public function set airControlCoefficient(value:Number):void { + _airControlCoefficient = value > 0 ? value : -value; + } + + /** + * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте + * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство + * onGround будет иметь значение false. + * + * @see #onGround + */ + public function get maxGroundAngle():Number { + return Math.acos(minGroundCos); + } + + /** + * @private + */ + public function set maxGroundAngle(value:Number):void { + minGroundCos = Math.cos(value); + } + + /** + * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида, + * значение 1 — положение в верхней точке эллипсоида. + */ + public function get objectZPosition():Number { + return _objectZPosition; + } + + /** + * @private + */ + public function set objectZPosition(value:Number):void { + _objectZPosition = value; + setObjectCoords(); + } + + /** + * Включение и выключение режима полёта. + * + * @default false + */ + public function get flyMode():Boolean { + return _flyMode; + } + + /** + *@private + */ + public function set flyMode(value:Boolean):void { + _flyMode = value; + } + + /** + * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности, + * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения. + * + * @see #maxGroundAngle + */ + public function get onGround():Boolean { + return _onGround; + } + + /** + * Модуль текущей скорости. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
ZACTION_DOWN
SHIFTACTION_ACCELERATE
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_YAW_LEFT
RIGHTACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
+ */ + override public function setDefaultBindings():void { + unbindAll(); + bindKey(KeyboardUtils.W, ACTION_FORWARD); + bindKey(KeyboardUtils.S, ACTION_BACK); + bindKey(KeyboardUtils.A, ACTION_LEFT); + bindKey(KeyboardUtils.D, ACTION_RIGHT); + bindKey(KeyboardUtils.SPACE, ACTION_UP); + bindKey(KeyboardUtils.Z, ACTION_DOWN); + bindKey(KeyboardUtils.UP, ACTION_PITCH_UP); + bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN); + bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT); + bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT); + bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE); + bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK); + } + + /** + * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен + * промежутком [minPitch, maxPitch] от горизонтали. + * + * @param frameTime длительность текущего кадра в секундах + * + * @see minPitch + * @see maxPitch + */ + override protected function rotateObject(frameTime:Number):void { + // Mouse look + var rotX:Number; + if (_mouseLookActive) { + prevMouseCoords.copy(currentMouseCoords); + currentMouseCoords.x = _eventsSource.stage.mouseX; + currentMouseCoords.y = _eventsSource.stage.mouseY; + if (!prevMouseCoords.equals(currentMouseCoords)) { + _object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x)*_mouseCoefficientX; + rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y)*_mouseCoefficientY; + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = ((rotX > maxPitch) ? maxPitch : (rotX < minPitch) ? minPitch : rotX) - MathUtils.DEG90; + } else { + _object.rotationX = (rotX > maxPitch) ? maxPitch : (rotX < minPitch) ? minPitch : rotX; + } + } + } + + // Повороты влево-вправо + if (_yawLeft) { + _object.rotationZ += _yawSpeed*frameTime; + } else if (_yawRight) { + _object.rotationZ -= _yawSpeed*frameTime; + } + // Взгляд вверх-вниз + rotX = NaN; + if (_pitchUp) { + rotX = _object.rotationX + _pitchSpeed*frameTime; + } else if (_pitchDown) { + rotX = _object.rotationX - _pitchSpeed*frameTime; + } + if (!isNaN(rotX)) { + if (_object is Camera3D) { + // Коррекция поворота для камеры + _object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX; + } else { + _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX; + } + } + } + + /** + * Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести. + * + * @param frameTime длительность текущего кадра в секундах + * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта + */ + override protected function getDisplacement(frameTime:Number, displacement:Point3D):void { + var cos:Number = 0; + if (checkCollisions && !_flyMode) { + // Проверка наличия под ногами поверхности + displacement.x = 0; + displacement.y = 0; + displacement.z = - 0.5*gravity*frameTime*frameTime; + if (_collider.getCollision(_coords, displacement, collision)) { + cos = collision.normal.z; + _groundMesh = collision.face._mesh; + } else { + _groundMesh = null; + } + } + _onGround = cos > minGroundCos; + + if (_onGround && inJump) { + inJump = false; + if (!_up) { + _jumpLocked = false; + } + } + + var len:Number; + var x:Number; + var y:Number; + var z:Number; + + // При наличии управляющих воздействий расчитывается приращение скорости + controlsActive = _forward || _back || _right || _left; + if (flyMode || gravity == 0) { + controlsActive ||= _up || _down; + } else { + + } + if (controlsActive) { + if (_flyMode) { + tmpVelocity.x = 0; + tmpVelocity.y = 0; + tmpVelocity.z = 0; + // Режим полёта, ускорения направлены вдоль локальных осей + // Ускорение вперёд-назад + if (_forward) { + tmpVelocity.y = 1; + } else if (_back) { + tmpVelocity.y = -1; + } + // Ускорение влево-вправо + if (_right) { + tmpVelocity.x = 1; + } else if (_left) { + tmpVelocity.x = -1; + } + // Ускорение вверх-вниз + if (_up) { + tmpVelocity.z = 1; + } else if (_down) { + tmpVelocity.z = -1; + } + var matrix:Matrix3D = _object._transformation; + x = tmpVelocity.x; + if (_object is Camera3D) { + y = -tmpVelocity.z; + z = tmpVelocity.y; + } else { + y = tmpVelocity.y; + z = tmpVelocity.z; + } + // Поворот вектора из локальной системы координат объекта в глобальную + velocity.x += (x*matrix.a + y*matrix.b + z*matrix.c)*_speed; + velocity.y += (x*matrix.e + y*matrix.f + z*matrix.g)*_speed; + velocity.z += (x*matrix.i + y*matrix.j + z*matrix.k)*_speed; + } else { + + // Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z + var heading:Number = _object.rotationZ; + var headingCos:Number = Math.cos(heading); + var headingSin:Number = Math.sin(heading); + + var spd:Number = _speed; + if (gravity != 0 && !_onGround) { + spd *= _airControlCoefficient; + } + + // Вперёд-назад + if (_forward) { + velocity.x -= spd*headingSin; + velocity.y += spd*headingCos; + } else if (_back) { + velocity.x += spd*headingSin; + velocity.y -= spd*headingCos; + } + // Влево-вправо + if (_right) { + velocity.x += spd*headingCos; + velocity.y += spd*headingSin; + } else if (_left) { + velocity.x -= spd*headingCos; + velocity.y -= spd*headingSin; + } + if (gravity == 0) { + // Ускорение вверх-вниз + if (_up) { + velocity.z += _speed; + } else if (_down) { + velocity.z -= _speed; + } + } + } + } else { + // Управление неактивно, замедление движения + len = 1/Math.pow(3, frameTime*10); + if (_flyMode || gravity == 0) { + velocity.x *= len; + velocity.y *= len; + velocity.z *= len; + } else { + if (_onGround) { + velocity.x *= len; + velocity.y *= len; + if (velocity.z < 0) { + velocity.z *= len; + } + } else { + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + } + } + // Прыжок + if (_onGround && _up && !inJump && !_jumpLocked) { + velocity.z = jumpSpeed; + inJump = true; + _onGround = false; + _jumpLocked = true; + cos = 0; + } + // В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности + if (!(_flyMode || _onGround)) { + velocity.z -= gravity*frameTime; + } + + // Ограничение скорости + var max:Number = _accelerate ? _speed*_speedMultiplier : _speed; + if (_flyMode || gravity == 0) { + len = velocity.length; + if (len > max) { + velocity.length = max; + } + } else { + len = Math.sqrt(velocity.x*velocity.x + velocity.y*velocity.y); + if (len > max) { + velocity.x *= max/len; + velocity.y *= max/len; + } + if (cos > 0 && velocity.z > 0) { + velocity.z = 0; + } + } + + // Cмещение за кадр + displacement.x = velocity.x*frameTime; + displacement.y = velocity.y*frameTime; + displacement.z = velocity.z*frameTime; + } + + /** + * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён + * соотвествующий режим. + * + * @param frameTime время кадра в секундах + * @param displacement векотр потенциального смещения эллипсоида + */ + override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void { + if (checkCollisions) { + _collider.calculateDestination(_coords, displacement, destination); + + displacement.x = destination.x - _coords.x; + displacement.y = destination.y - _coords.y; + displacement.z = destination.z - _coords.z; + } else { + destination.x = _coords.x + displacement.x; + destination.y = _coords.y + displacement.y; + destination.z = _coords.z + displacement.z; + } + + velocity.x = displacement.x/frameTime; + velocity.y = displacement.y/frameTime; + velocity.z = displacement.z/frameTime; + + _coords.x = destination.x; + _coords.y = destination.y; + _coords.z = destination.z; + setObjectCoords(); + + var len:Number = Math.sqrt(velocity.x*velocity.x + velocity.y*velocity.y + velocity.z*velocity.z); + if (len < speedThreshold) { + velocity.x = 0; + velocity.y = 0; + velocity.z = 0; + _currentSpeed = 0; + } else { + _currentSpeed = len; + } + } + + /** + * Метод устанавливает координаты управляемого объекта в зависимости от параметра objectZPosition. + * + * @see #objectZPosition + */ + override protected function setObjectCoords():void { + if (_object != null) { + _object.x = _coords.x; + _object.y = _coords.y; + _object.z = _coords.z + (2*_objectZPosition - 1)*_collider.radiusZ; + } + } + + /** + * Метод выполняет необходимые действия при включении вращения объекта мышью. + */ + override protected function startMouseLook():void { + super.startMouseLook(); + startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX; + startRotZ = _object.rotationZ; + } + + /** + * @inheritDoc + */ + override public function set enabled(value:Boolean):void { + super.enabled = value; + if (!value) { + velocity.reset(); + _currentSpeed = 0; + } + } + + /** + * @inheritDoc + */ + override public function moveUp(value:Boolean):void { + super.moveUp(value); + if (!inJump && !value) { + _jumpLocked = false; + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as new file mode 100644 index 0000000..c0ec092 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as @@ -0,0 +1,93 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public final class BSPNode { + + // Сплиттер ноды (если есть) + alternativa3d var splitter:Splitter; + + // Передний и задний сектора (если есть сплиттер) + alternativa3d var frontSector:Sector; + alternativa3d var backSector:Sector; + + // Тип примитива + alternativa3d var isSprite:Boolean; + + // Родительская нода + alternativa3d var parent:BSPNode; + + // Дочерние ветки + alternativa3d var front:BSPNode; + alternativa3d var back:BSPNode; + + // Нормаль плоскости ноды + alternativa3d var normal:Point3D = new Point3D(); + + // Смещение плоскости примитива + alternativa3d var offset:Number; + + // Минимальная мобильность ноды + alternativa3d var mobility:int = int.MAX_VALUE; + + // Набор примитивов в ноде + alternativa3d var primitive:PolyPrimitive; + alternativa3d var backPrimitives:Set; + alternativa3d var frontPrimitives:Set; + + // Хранилище неиспользуемых нод + static private var collector:Array = new Array(); + + // Создать ноду на основе примитива + static alternativa3d function create(primitive:PolyPrimitive):BSPNode { + var node:BSPNode; + if ((node = collector.pop()) == null) { + node = new BSPNode(); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + // Сохраняем ноду + primitive.node = node; + // Если это спрайтовый примитив или сплиттеровый примитив + if (primitive.face == null) { + var sprimitive:SplitterPrimitive = primitive as SplitterPrimitive; + if (sprimitive == null) { + // SpritePrimitive + node.normal.x = 0; + node.normal.y = 0; + node.normal.z = 0; + node.offset = 0; + node.isSprite = true; + } else { + node.splitter = sprimitive.splitter; + node.normal.copy(sprimitive.splitter.normal); + node.offset = sprimitive.splitter.offset; + node.isSprite = false; + } + } else { + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + node.isSprite = false; + } + // Сохраняем мобильность + node.mobility = primitive.mobility; + return node; + } + + // Удалить ноду, все ссылки должны быть почищены + static alternativa3d function destroy(node:BSPNode):void { + //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives); + collector.push(node); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..e8cf69d --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,1311 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + import alternativa.engine3d.display.View; + import alternativa.engine3d.materials.DrawPoint; + import alternativa.engine3d.materials.SpriteMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.geom.Matrix; + + use namespace alternativa3d; + + /** + * Камера для отображения 3D-сцены на экране. + * + *

Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе + * координат родителя.

+ * + *

Для отображения видимой через камеру части сцены на экран, к камере должен быть подключён вьюпорт — + * экземпляр класса alternativa.engine3d.display.View.

+ * + * @see alternativa.engine3d.display.View + */ + public class Camera3D extends Object3D { + + /** + * @private + * Расчёт матрицы пространства камеры + */ + alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX); + /** + * @private + * Расчёт плоскостей отсечения + */ + alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES); + /** + * @private + * Отрисовка + */ + alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Полупространство камеры при отрисовке + */ + alternativa3d var sector:Sector; + + /** + * @private + * Поле зрения + */ + alternativa3d var _fov:Number = Math.PI/2; + /** + * @private + * Фокусное расстояние + */ + alternativa3d var _focalLength:Number; + /** + * @private + * Перспективное искажение + */ + alternativa3d var focalDistortion:Number; + + /** + * @private + * Флаги рассчитанности UV-матриц + */ + alternativa3d var uvMatricesCalculated:Set = new Set(true); + + // Всмомогательные точки для расчёта UV-матриц + private var textureA:Point3D = new Point3D(); + private var textureB:Point3D = new Point3D(); + private var textureC:Point3D = new Point3D(); + + /** + * @private + * Вид из камеры + */ + alternativa3d var _view:View; + + /** + * @private + * Режим отрисовки + */ + alternativa3d var _orthographic:Boolean = false; + private var fullDraw:Boolean; + + /** + * @private + * Масштаб + */ + alternativa3d var _zoom:Number = 1; + + // Синус половинчатого угла обзора камеры + private var viewAngle:Number; + + /** + * @private + * Направление камеры + */ + private var direction:Point3D = new Point3D(0, 0, 1); + + /** + * @private + * Обратная трансформация камеры + */ + alternativa3d var cameraMatrix:Matrix3D = new Matrix3D(); + + // Скины + private var firstSkin:Skin; + private var prevSkin:Skin; + private var currentSkin:Skin; + + // Плоскости отсечения + private var leftPlane:Point3D = new Point3D(); + private var rightPlane:Point3D = new Point3D(); + private var topPlane:Point3D = new Point3D(); + private var bottomPlane:Point3D = new Point3D(); + private var farPlane:Point3D = new Point3D(); + private var leftOffset:Number; + private var rightOffset:Number; + private var topOffset:Number; + private var bottomOffset:Number; + private var nearOffset:Number; + private var farOffset:Number; + + // Вспомогательные массивы точек для отрисовки + private var points1:Array = new Array(); + private var points2:Array = new Array(); + private var drawPoints:Array = new Array(); + private var spritePoint:Point3D = new Point3D(); + // Массив для сортировки спрайтов + private var spritePrimitives:Array = new Array(); + + /** + * @private + */ + alternativa3d var _nearClippingDistance:Number = 1; + /** + * @private + */ + alternativa3d var _farClippingDistance:Number = 1; + /** + * @private + */ + alternativa3d var _nearClipping:Boolean = false; + /** + * @private + */ + alternativa3d var _farClipping:Boolean = false; + /** + * @private + */ + alternativa3d var _viewClipping:Boolean = true; + + /** + * Создаёт новый экземпляр камеры. + * + * @param name имя камеры + */ + public function Camera3D(name:String = null) { + super(name); + } + + /** + * @private + */ + private function calculateMatrix():void { + // Расчёт матрицы пространства камеры + cameraMatrix.copy(_transformation); + cameraMatrix.invert(); + if (_orthographic) { + cameraMatrix.scale(_zoom, _zoom, _zoom); + } + // Направление камеры + direction.x = _transformation.c; + direction.y = _transformation.g; + direction.z = _transformation.k; + direction.normalize(); + } + + /** + * @private + * Расчёт плоскостей отсечения + */ + private function calculatePlanes():void { + var halfWidth:Number = _view._width*0.5; + var halfHeight:Number = _view._height*0.5; + + var aw:Number = _transformation.a*halfWidth; + var ew:Number = _transformation.e*halfWidth; + var iw:Number = _transformation.i*halfWidth; + var bh:Number = _transformation.b*halfHeight; + var fh:Number = _transformation.f*halfHeight; + var jh:Number = _transformation.j*halfHeight; + if (_orthographic) { + if (_viewClipping) { + // Расчёт плоскостей отсечения в изометрии + aw /= _zoom; + ew /= _zoom; + iw /= _zoom; + bh /= _zoom; + fh /= _zoom; + jh /= _zoom; + + // Левая плоскость + leftPlane.x = _transformation.f*_transformation.k - _transformation.j*_transformation.g; + leftPlane.y = _transformation.j*_transformation.c - _transformation.b*_transformation.k; + leftPlane.z = _transformation.b*_transformation.g - _transformation.f*_transformation.c; + leftOffset = (_transformation.d - aw)*leftPlane.x + (_transformation.h - ew)*leftPlane.y + (_transformation.l - iw)*leftPlane.z; + + // Правая плоскость + rightPlane.x = -leftPlane.x; + rightPlane.y = -leftPlane.y; + rightPlane.z = -leftPlane.z; + rightOffset = (_transformation.d + aw)*rightPlane.x + (_transformation.h + ew)*rightPlane.y + (_transformation.l + iw)*rightPlane.z; + + // Верхняя плоскость + topPlane.x = _transformation.g*_transformation.i - _transformation.k*_transformation.e; + topPlane.y = _transformation.k*_transformation.a - _transformation.c*_transformation.i; + topPlane.z = _transformation.c*_transformation.e - _transformation.g*_transformation.a; + topOffset = (_transformation.d - bh)*topPlane.x + (_transformation.h - fh)*topPlane.y + (_transformation.l - jh)*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = -topPlane.x; + bottomPlane.y = -topPlane.y; + bottomPlane.z = -topPlane.z; + bottomOffset = (_transformation.d + bh)*bottomPlane.x + (_transformation.h + fh)*bottomPlane.y + (_transformation.l + jh)*bottomPlane.z; + } + } else { + // Вычисляем расстояние фокуса + _focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov); + // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции + focalDistortion = 1/(_focalLength*_focalLength); + + if (_viewClipping) { + // Расчёт плоскостей отсечения в перспективе + var cl:Number = _transformation.c*_focalLength; + var gl:Number = _transformation.g*_focalLength; + var kl:Number = _transformation.k*_focalLength; + + // Угловые вектора пирамиды видимости + var leftTopX:Number = -aw - bh + cl; + var leftTopY:Number = -ew - fh + gl; + var leftTopZ:Number = -iw - jh + kl; + var rightTopX:Number = aw - bh + cl; + var rightTopY:Number = ew - fh + gl; + var rightTopZ:Number = iw - jh + kl; + var leftBottomX:Number = -aw + bh + cl; + var leftBottomY:Number = -ew + fh + gl; + var leftBottomZ:Number = -iw + jh + kl; + var rightBottomX:Number = aw + bh + cl; + var rightBottomY:Number = ew + fh + gl; + var rightBottomZ:Number = iw + jh + kl; + + // Левая плоскость + leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY; + leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ; + leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX; + leftOffset = _transformation.d*leftPlane.x + _transformation.h*leftPlane.y + _transformation.l*leftPlane.z; + + // Правая плоскость + rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY; + rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ; + rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX; + rightOffset = _transformation.d*rightPlane.x + _transformation.h*rightPlane.y + _transformation.l*rightPlane.z; + + // Верхняя плоскость + topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY; + topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ; + topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX; + topOffset = _transformation.d*topPlane.x + _transformation.h*topPlane.y + _transformation.l*topPlane.z; + + // Нижняя плоскость + bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY; + bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ; + bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX; + bottomOffset = _transformation.d*bottomPlane.x + _transformation.h*bottomPlane.y + _transformation.l*bottomPlane.z; + + // Расчёт угла конуса + var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ); + leftTopX /= length; + leftTopY /= length; + leftTopZ /= length; + length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ); + rightTopX /= length; + rightTopY /= length; + rightTopZ /= length; + length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ); + leftBottomX /= length; + leftBottomY /= length; + leftBottomZ /= length; + length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ); + rightBottomX /= length; + rightBottomY /= length; + rightBottomZ /= length; + + viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z; + var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z; + viewAngle = (dot < viewAngle) ? dot : viewAngle; + + viewAngle = Math.sin(Math.acos(viewAngle)); + } else { + viewAngle = 1; + } + } + + var x:Number; + var y:Number; + var z:Number; + var k:Number; + + if (_nearClipping) { + if (_orthographic) { + k = _nearClippingDistance/_zoom; + x = _transformation.c*k + _transformation.d; + y = _transformation.g*k + _transformation.h; + z = _transformation.k*k + _transformation.l; + } else { + x = _transformation.c*_nearClippingDistance + _transformation.d; + y = _transformation.g*_nearClippingDistance + _transformation.h; + z = _transformation.k*_nearClippingDistance + _transformation.l; + } + nearOffset = direction.x*x + direction.y*y + direction.z*z; + } + + if (_farClipping) { + if (_orthographic) { + k = _farClippingDistance/_zoom; + x = _transformation.c*k + _transformation.d; + y = _transformation.g*k + _transformation.h; + z = _transformation.k*k + _transformation.l; + } else { + x = _transformation.c*_farClippingDistance + _transformation.d; + y = _transformation.g*_farClippingDistance + _transformation.h; + z = _transformation.k*_farClippingDistance + _transformation.l; + } + + farPlane.x = -direction.x; + farPlane.y = -direction.y; + farPlane.z = -direction.z; + farOffset = farPlane.x*x + farPlane.y*y + farPlane.z*z; + } + } + + /** + * @private + */ + private function render():void { + // Режим отрисовки + fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued); + + // Отрисовка + prevSkin = null; + currentSkin = firstSkin; + + sector = null; + // Определяем текущее полупространство камеры + findSector(_scene.bsp); + // Отрисовка БСП дерева + renderSplitterNode(_scene.bsp); + + // Очистка рассчитанных текстурных матриц + uvMatricesCalculated.clear(); + + // Удаление ненужных скинов + while (currentSkin != null) { + removeCurrentSkin(); + } + + if (_view._interactive) { + _view.checkMouseOverOut(true); + } + } + + /** + * Поиск сектора камеры. + */ + private function findSector(node:BSPNode):void { + if (node != null && node.splitter != null) { + var normal:Point3D = node.normal; + if (globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset >= 0) { + if (node.frontSector != null) { + sector = node.frontSector; + } else { + findSector(node.front); + } + } else { + if (node.backSector != null) { + sector = node.backSector; + } else { + findSector(node.back); + } + } + } + } + + /** + * @private + */ + private function renderSplitterNode(node:BSPNode):void { + if (node != null) { + if (node.splitter != null) { + var primitive:*; + var normal:Point3D = node.normal; + var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z; + var cameraOffset:Number; + if (!_orthographic) { + cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset; + } + // В ноде только базовый примитив + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if ((_orthographic || cameraAngle < viewAngle) && (node.splitter._open && (node.backSector == null || sector == null || sector._visible[node.backSector]))) { + // Полупространство видно в камере + renderSplitterNode(node.back); + } + if (node.frontSector == null || sector == null || sector._visible[node.frontSector]) { + renderSplitterNode(node.front); + } + } else { + // Камера сзади ноды + if ((_orthographic || cameraAngle > -viewAngle) && (node.splitter._open && (node.frontSector == null || sector == null || sector._visible[node.frontSector]))) { + renderSplitterNode(node.front); + } + if (node.backSector == null || sector == null || sector._visible[node.backSector]) { + renderSplitterNode(node.back); + } + } + } else { + // Обычная отрисовка + renderBSPNode(node); + } + } + } + + /** + * @private + */ + private function renderBSPNode(node:BSPNode):void { + if (node != null) { + if (node.isSprite) { + // Спрайтовая нода + if (node.primitive != null) { + drawSpriteSkin(node.primitive as SpritePrimitive); + } else { + drawSpritePrimitives(node.frontPrimitives); + } + } else { + var primitive:*; + var normal:Point3D = node.normal; + var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z; + var cameraOffset:Number; + if (!_orthographic) { + cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset; + } + // В ноде только базовый примитив + if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) { + // Камера спереди ноды + if (_orthographic || cameraAngle < viewAngle) { + renderBSPNode(node.back); + if (node.primitive != null) { + drawSkin(node.primitive); + } else { + for (primitive in node.frontPrimitives) { + drawSkin(primitive); + } + } + } + renderBSPNode(node.front); + } else { + // Камера сзади ноды + if (_orthographic || cameraAngle > -viewAngle) { + renderBSPNode(node.front); + if (node.primitive == null) { + for (primitive in node.backPrimitives) { + drawSkin(primitive); + } + } + } + renderBSPNode(node.back); + } + } + } + } + + /** + * @private + * Функция сортирует список спрайтовых примитивов по удаленности и отправляет на отрисовку. + * + * @param primitives список примитивов для отрисовки + */ + private function drawSpritePrimitives(primitives:Set):void { + var primitive:SpritePrimitive; + var counter:int = -1; + for (var p:* in primitives) { + primitive = p; + var point:Point3D = primitive.sprite.globalCoords; + var z:Number = cameraMatrix.i*point.x + cameraMatrix.j*point.y + cameraMatrix.k*point.z + cameraMatrix.l; + primitive.screenDepth = z; + spritePrimitives[++counter] = primitive; + } + if (counter > 0) { + sortSpritePrimitives(0, counter); + } + for (var i:int = counter; i >= 0; i--) { + drawSpriteSkin(spritePrimitives[i]); + } + } + + /** + * @private + * Сортировка примитивов по удаленности. + * + * @param l начальный индекс + * @param r конечный индекс + */ + private function sortSpritePrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:SpritePrimitive; + var mid:Number = spritePrimitives[(r + l) >> 1].screenDepth; + var right:SpritePrimitive; + do { + while ((left = spritePrimitives[i]).screenDepth < mid) {i++}; + while (mid < (right = spritePrimitives[j]).screenDepth) {j--}; + if (i <= j) { + spritePrimitives[i++] = right; + spritePrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortSpritePrimitives(l, j); + } + if (i < r) { + sortSpritePrimitives(i, r); + } + } + + /** + * @private + * Отрисовка скина спрайтового примитива. + * + * @param primitive спрайтовый примитив + */ + private function drawSpriteSkin(primitive:SpritePrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + var sprite:Sprite3D = primitive.sprite; + + var material:SpriteMaterial = sprite._material; + if (material == null) { + return; + } + + if (!material.canDraw(this)) { + return; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin); + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Скин текущего примитива дальше по списку скинов + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + + } + } + } + + /** + * @private + * Отрисовка скина примитива + */ + private function drawSkin(primitive:PolyPrimitive):void { + if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) { + // Пропуск скина + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Проверка поверхности + var surface:Surface = primitive.face._surface; + if (surface == null) { + return; + } + // Проверка материала + var material:SurfaceMaterial = surface._material; + if (material == null || !material.canDraw(primitive)) { + return; + } + // Отсечение выходящих за окно просмотра частей + var length:uint = primitive.num; + var tmp:Array; + var points:Array = primitive.points; + if (_farClipping && _nearClipping) { + // Отсечение по ближней плоскости + if ((length = clip(length, points, points1, direction, nearOffset)) < 3) { + return; + } + // Отсечение по дальней плоскости + if ((length = clip(length, points1, points2, farPlane, farOffset)) < 3) { + return; + } + points = points2; + } else if (_nearClipping) { + // Отсечение по ближней плоскости + if ((length = clip(length, points, points2, direction, nearOffset)) < 3) { + return; + } + points = points2; + } else if (_farClipping) { + // Отсечение по дальней плоскости + if ((length = clip(length, points, points2, farPlane, farOffset)) < 3) { + return; + } + points = points2; + } + + if (_viewClipping) { + // Отсечение по левой стороне + if ((length = clip(length, points, points1, leftPlane, leftOffset)) < 3) { + return; + } + // Отсечение по правой стороне + if ((length = clip(length, points1, points2, rightPlane, rightOffset)) < 3) { + return; + } + // Отсечение по верхней стороне + if ((length = clip(length, points2, points1, topPlane, topOffset)) < 3) { + return; + } + // Отсечение по нижней стороне + if ((length = clip(length, points1, points2, bottomPlane, bottomOffset)) < 3) { + return; + } + points = points2; + } + + if (fullDraw || _scene.changedPrimitives[primitive]) { + var i:uint; + var point:Point3D; + var drawPoint:DrawPoint; + var x:Number; + var y:Number; + var z:Number; + var cz:Number; + var uvMatrix:Matrix3D = primitive.face.uvMatrix; + // Переводим координаты в систему камеры + if (!_orthographic && material.useUV && uvMatrix) { + // Формируем список точек и UV-координат полигона + for (i = 0; i < length; i++) { + point = points[i]; + x = point.x; + y = point.y; + z = point.z; + cz = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + if (cz < 0) { + // Пропускаем полигон + return; + } + // Расчет UV + var u:Number = uvMatrix.a*x + uvMatrix.b*y + uvMatrix.c*z + uvMatrix.d; + var v:Number = uvMatrix.e*x + uvMatrix.f*y + uvMatrix.g*z + uvMatrix.h; + + drawPoint = drawPoints[i]; + if (drawPoint == null) { + drawPoints[i] = new DrawPoint(cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d, cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h, cz, u, v); + } else { + drawPoint.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + drawPoint.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + drawPoint.z = cz; + drawPoint.u = u; + drawPoint.v = v; + } + } + } else { + // Формируем список точек полигона + for (i = 0; i < length; i++) { + point = points[i]; + x = point.x; + y = point.y; + z = point.z; + cz = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + if (cz < 0 && !_orthographic) { + // Пропускаем полигон + return; + } + drawPoint = drawPoints[i]; + if (drawPoint == null) { + drawPoints[i] = new DrawPoint(cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d, cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h, cz); + } else { + drawPoint.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + drawPoint.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + drawPoint.z = cz; + } + } + } + + // Если конец списка скинов + if (currentSkin == null) { + // Добавляем скин в конец + addCurrentSkin(); + } else { + if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) { + // Очистка скина + currentSkin.material.clear(currentSkin); + } else { + // Вставка скина перед текущим + insertCurrentSkin(); + } + } + + // Назначаем скину примитив и материал + currentSkin.primitive = primitive; + currentSkin.material = material; + material.draw(this, currentSkin, length, drawPoints); + + // Переключаемся на следующий скин + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } else { + // Скин текущего примитива дальше по списку скинов + + // Удаление ненужных скинов + while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) { + removeCurrentSkin(); + } + + // Переключение на следующий скин + if (currentSkin != null) { + prevSkin = currentSkin; + currentSkin = currentSkin.nextSkin; + } + } + } + } + + /** + * @private + * Отсечение полигона плоскостью. + * + * @param length кол-во точек в полигоне + * @param points1 исходный полигон + * @param points2 отсеченный полигон + * @param plane нормаль плоскости отсечения + * @param offset оффсет плоскости отсечения + * + * @return кол-во точек в отсеченном полигоне + */ + private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number):uint { + var i:uint; + var k:Number; + var index:uint = 0; + var point:Point3D; + var point1:Point3D; + var point2:Point3D; + var offset1:Number; + var offset2:Number; + + point1 = points1[length - 1]; + offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset; + + for (i = 0; i < length; i++) { + point2 = points1[i]; + offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset; + + if (offset2 > 0) { + if (offset1 <= 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new Point3D(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + point = points2[index]; + if (point == null) { + point = new Point3D(point2.x, point2.y, point2.z); + points2[index] = point; + } else { + point.x = point2.x; + point.y = point2.y; + point.z = point2.z; + } + index++; + } else { + if (offset1 > 0) { + k = offset2/(offset2 - offset1); + point = points2[index]; + if (point == null) { + point = new Point3D(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k); + points2[index] = point; + } else { + point.x = point2.x - (point2.x - point1.x)*k; + point.y = point2.y - (point2.y - point1.y)*k; + point.z = point2.z - (point2.z - point1.z)*k; + } + index++; + } + } + offset1 = offset2; + point1 = point2; + } + return index; + } + + /** + * @private + * Добавление текущего скина. + */ + private function addCurrentSkin():void { + currentSkin = Skin.createSkin(); + _view.canvas.addChild(currentSkin); + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + * Вставляем под текущий скин. + */ + private function insertCurrentSkin():void { + var skin:Skin = Skin.createSkin(); + _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin)); + skin.nextSkin = currentSkin; + if (prevSkin == null) { + firstSkin = skin; + } else { + prevSkin.nextSkin = skin; + } + currentSkin = skin; + } + + /** + * @private + * Удаляет текущий скин. + */ + private function removeCurrentSkin():void { + // Сохраняем следующий + var next:Skin = currentSkin.nextSkin; + // Удаляем из канваса + _view.canvas.removeChild(currentSkin); + // Очистка скина + if (currentSkin.material != null) { + currentSkin.material.clear(currentSkin); + } + // Зачищаем ссылки + currentSkin.nextSkin = null; + currentSkin.primitive = null; + currentSkin.material = null; + // Удаляем + Skin.destroySkin(currentSkin); + // Следующий устанавливаем текущим + currentSkin = next; + // Устанавливаем связь от предыдущего скина + if (prevSkin == null) { + firstSkin = currentSkin; + } else { + prevSkin.nextSkin = currentSkin; + } + } + + /** + * @private + */ + alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void { + // Расчёт точек базового примитива в координатах камеры + var point:Point3D = face.primitive.points[0]; + textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[1]; + textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + point = face.primitive.points[2]; + textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z; + textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z; + + // Находим AB и AC + var abx:Number = textureB.x - textureA.x; + var aby:Number = textureB.y - textureA.y; + var acx:Number = textureC.x - textureA.x; + var acy:Number = textureC.y - textureA.y; + + // Расчёт текстурной матрицы + var uvMatrixBase:Matrix = face.uvMatrixBase; + var uvMatrix:Matrix = face.orthoTextureMatrix; + uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width; + uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width; + uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height; + uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height; + uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d; + uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h; + + // Помечаем, как рассчитанную + uvMatricesCalculated[face] = true; + } + + /** + * Вьюпорт, в который выводится изображение с камеры. + */ + public function get view():View { + return _view; + } + + /** + * @private + */ + public function set view(value:View):void { + if (value != _view) { + if (_view != null) { + _view.camera = null; + } + if (value != null) { + value.camera = this; + } + } + } + + /** + * Флаг режима аксонометрической проекции. + * + * @default false + */ + public function get orthographic():Boolean { + return _orthographic; + } + + /** + * @private + */ + public function set orthographic(value:Boolean):void { + if (_orthographic != value) { + // Отправляем сигнал об изменении типа камеры + addOperationToScene(calculateMatrixOperation); + // Сохраняем новое значение + _orthographic = value; + } + } + + /** + * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние + * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали вьюпорта. + * Угол зрения ограничен диапазоном 0-180 градусов. + * + * @see focalLength + */ + public function get fov():Number { + return _fov; + } + + /** + * @private + */ + public function set fov(value:Number):void { + value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value); + if (_fov != value) { + // Если перспектива + if (!_orthographic) { + // Отправляем сигнал об изменении плоскостей отсечения + addOperationToScene(calculatePlanesOperation); + } + // Сохраняем новое значение + _fov = value; + } + } + + /** + * Фокусное расстояние камеры. + * Вычисляется по формуле f = d/tan(fov/2), где d является половиной диагонали вьюпорта. + * Если с камерой не связан вьюпорт, метод вернет NaN. + * + * @see fov + */ + public function get focalLength():Number { + if (_view == null) { + return NaN; + } + if (orthographic || calculatePlanesOperation.queued || scene == null) { + // Требуется пересчет + return 0.5*Math.sqrt(_view._width*_view._width + _view._height*_view._height)/Math.tan(0.5*_fov); + } else { + return _focalLength; + } + } + + /** + * Коэффициент увеличения изображения в режиме аксонометрической проекции. + */ + public function get zoom():Number { + return _zoom; + } + + /** + * @private + */ + public function set zoom(value:Number):void { + value = (value < 0) ? 0 : value; + if (_zoom != value) { + // Если изометрия + if (_orthographic) { + // Отправляем сигнал об изменении zoom + addOperationToScene(calculateMatrixOperation); + } + // Сохраняем новое значение + _zoom = value; + } + } + + /** + * Сектор в котором находится камера. + * + *

Для правильной работы метода, сцена должна быть построена вызовом метода + * calculate после изменения следующих свойств сцены: splitters, sectors, planeOffsetThreshold.

+ * + * @see Scene3D#calculate() + */ + public function get currentSector():Sector { + if (_scene == null) { + return null; + } + sector = null; + findSector(_scene.bsp); + return sector; + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + if (_view != null) { + // Отправляем операцию расчёта плоскостей отсечения + scene.addOperation(calculatePlanesOperation); + // Подписываемся на сигналы сцены + scene.changePrimitivesOperation.addSequel(renderOperation); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + super.removeFromScene(scene); + + // Удаляем все операции из очереди + scene.removeOperation(calculateMatrixOperation); + scene.removeOperation(calculatePlanesOperation); + scene.removeOperation(renderOperation); + + if (_view != null) { + // Отписываемся от сигналов сцены + scene.changePrimitivesOperation.removeSequel(renderOperation); + } + } + + /** + * @private + */ + alternativa3d function addToView(view:View):void { + // Сохраняем первый скин + firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null; + + // Подписка на свои операции + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.addSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.addSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.addSequel(renderOperation); + + if (_scene != null) { + // Отправляем сигнал перерисовки + _scene.addOperation(calculateMatrixOperation); + // Подписываемся на сигналы сцены + _scene.changePrimitivesOperation.addSequel(renderOperation); + } + + // Сохраняем вид + _view = view; + } + + /** + * @private + */ + alternativa3d function removeFromView(view:View):void { + // Сброс ссылки на первый скин + firstSkin = null; + + // Отписка от своих операций + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.removeSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.removeSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.removeSequel(renderOperation); + + if (_scene != null) { + // Удаляем все операции из очереди + _scene.removeOperation(calculateMatrixOperation); + _scene.removeOperation(calculatePlanesOperation); + _scene.removeOperation(renderOperation); + // Отписываемся от сигналов сцены + _scene.changePrimitivesOperation.removeSequel(renderOperation); + } + // Удаляем ссылку на вид + _view = null; + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "camera" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Camera3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Camera3D = Camera3D(source); + orthographic = src._orthographic; + zoom = src._zoom; + fov = src._fov; + } + + /** + * Включает отсечение по ближней плоскости. Если задано значение true, объекты или их части, имеющие в камере + * координату Z меньше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется. + * + * @see #farClipping + * @see #viewClipping + * @see #nearClippingDistance + * + * @default false + */ + public function get nearClipping():Boolean { + return _nearClipping; + } + + /** + * @private + */ + public function set nearClipping(value:Boolean):void { + if (_nearClipping != value) { + _nearClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Расстояние до ближней плоскости отсечения в системе координат камеры. Значение ограничено снизу нулём. + * + * @see #nearClipping + * + * @default 1 + */ + public function get nearClippingDistance():Number { + return _nearClippingDistance; + } + + /** + * @private + */ + public function set nearClippingDistance(value:Number):void { + if (value < 0) { + value = 0; + } + if (_nearClippingDistance != value) { + _nearClippingDistance = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Включает отсечение по дальней плоскости. Если задано значение true, объекты или их части, имеющие в камере + * координату Z больше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется. + * + * @see #nearClipping + * @see #viewClipping + * @see #farClippingDistance + * + * @default false + */ + public function get farClipping():Boolean { + return _farClipping; + } + + /** + * @private + */ + public function set farClipping(value:Boolean):void { + if (_farClipping != value) { + _farClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Расстояние до дальней плоскости отсечения в системе координат камеры. Значение ограничено снизу нулём. + * + * @see #farClipping + * + * @default 1 + */ + public function get farClippingDistance():Number { + return _farClippingDistance; + } + + /** + * @private + */ + public function set farClippingDistance(value:Number):void { + if (value < 0) { + value = 0; + } + if (_farClippingDistance != value) { + _farClippingDistance = value; + addOperationToScene(calculatePlanesOperation); + } + } + + /** + * Включает отсечение по пирамиде видимости. Если задано значение true, выполняется отсечение объектов сцены по + * пирамиде видимости. Если задано значение false, отсечение не выполняется. Такой режим полезен, когда + * заранее известно, что вся сцена находится в пирамиде видимости. В этом случае отключение отсечения позволит ускорить отрисовку. Не следует отключать + * отсечение по пирамиде видимости одновременно с отсечением по ближней плоскости, если позади камеры находятся объекты или части объектов, т.к. это приведёт к + * артефактам отрисовки, а при использовании текстурных материалов и к зацикливанию адаптивной триангуляции. + * + * @see #nearClipping + * @see #farClipping + * + * @default true + */ + public function get viewClipping():Boolean { + return _viewClipping; + } + + /** + * @private + */ + public function set viewClipping(value:Boolean):void { + if (_viewClipping != value) { + _viewClipping = value; + addOperationToScene(calculatePlanesOperation); + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..9e81042 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as @@ -0,0 +1,904 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же гранью. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event(name="click", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event(name="mouseDown", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event(name="mouseUp", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на грань. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event(name="mouseOver", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с грани. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event(name="mouseOut", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event(name="mouseMove", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над гранью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event(name="mouseWheel", type = "alternativa.engine3d.events.MouseEvent3D")] + /** + * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань + * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения + * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается + * матрица трансформации текстуры. + * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие.

+ */ + final public class Face implements IEventDispatcher { + // Погрешность определения вырожденной UV матрицы + private static const uvThreshold:Number = 1.0 / 2880; + // Операции + /** + * @private + * Расчёт глобальной нормали плоскости грани. + */ + alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL); + /** + * @private + * Расчёт базовой UV матрицы, используется для расчета UV матрицы. + */ + alternativa3d var calculateBaseUVOperation:Operation = new Operation("calculateBaseUV", this, calculateBaseUV, Operation.FACE_CALCULATE_BASE_UV); + /** + * @private + * Расчёт UV матрицы. + */ + alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV); + /** + * @private + * Обновление примитива в сцене. + */ + alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE); + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Вершины грани + */ + alternativa3d var _vertices:Array; + /** + * @private + * Количество вершин + */ + alternativa3d var _verticesCount:uint; + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + // UV-координаты + /** + * @private + */ + alternativa3d var _aUV:Point; + /** + * @private + */ + alternativa3d var _bUV:Point; + /** + * @private + */ + alternativa3d var _cUV:Point; + + /** + * @private + * базовая UV матрица + */ + alternativa3d var uvMatrixBase:Matrix; + /** + * @private + * UV матрица + */ + alternativa3d var uvMatrix:Matrix3D; + /** + * @private + * UV Матрица перевода текстурных координат в изометрическую камеру. + */ + alternativa3d var orthoTextureMatrix:Matrix; + + /** + * @private + * Нормаль плоскости + */ + alternativa3d var globalNormal:Point3D = new Point3D(); + /** + * @private + * Смещение плоскости + */ + alternativa3d var globalOffset:Number; + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра грани. + * + * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в + * порядке обхода лицевой стороны грани против часовой стрелки. + * + * @see Vertex + */ + public function Face(vertices:Array) { + // Сохраняем вершины + _vertices = vertices; + _verticesCount = vertices.length; + + // Создаём оригинальный примитив + primitive = PolyPrimitive.create(); + primitive.face = this; + primitive.num = _verticesCount; + + // Обрабатываем вершины + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = vertices[i]; + // Добавляем координаты вершины в примитив + primitive.points.push(vertex.globalCoords); + // Добавляем вершину в грань + vertex.addToFace(this); + } + + // Расчёт нормали + calculateNormalOperation.addSequel(updatePrimitiveOperation); + + // Расчет нормали заставляет пересчитаться UV матрицу + calculateNormalOperation.addSequel(calculateUVOperation); + // Расчет базововй UV матрицы инициирует расчет UV матрицы грани + calculateBaseUVOperation.addSequel(calculateUVOperation); + // Расчёт UV матрицы грани инициирует перерисовку + calculateUVOperation.addSequel(updateMaterialOperation); + } + + /** + * @private + * Расчёт нормали в глобальных координатах + */ + private function calculateNormal():void { + // Вектор AB + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.globalCoords; + vertex = _vertices[1]; + var bv:Point3D = vertex.globalCoords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + // Вектор AC + vertex = _vertices[2]; + var cv:Point3D = vertex.globalCoords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + // Перпендикуляр к плоскости + globalNormal.x = acz*aby - acy*abz; + globalNormal.y = acx*abz - acz*abx; + globalNormal.z = acy*abx - acx*aby; + // Нормализация перпендикуляра + globalNormal.normalize(); + } + + /** + * @private + * Расчитывает глобальное смещение плоскости грани. + * Помечает конечные примитивы на удаление, а базовый на добавление в сцене. + */ + private function updatePrimitive():void { + // Расчёт смещения + var vertex:Vertex = _vertices[0]; + globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z; + + removePrimitive(primitive); + primitive.mobility = _mesh.inheritedMobility; + _mesh._scene.addPrimitives.push(primitive); + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены + */ + private function removePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + removePrimitive(primitive.backFragment); + removePrimitive(primitive.frontFragment); + primitive.backFragment = null; + primitive.frontFragment = null; + if (primitive != this.primitive) { + primitive.parent = null; + primitive.sibling = null; + PolyPrimitive.destroy(primitive); + } + } else { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _mesh._scene.removeBSPPrimitive(primitive); + } + } + } + + /** + * @private + * Пометка на перерисовку фрагментов грани. + */ + private function updateMaterial():void { + if (!updatePrimitiveOperation.queued) { + changePrimitive(primitive); + } + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку + */ + private function changePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + changePrimitive(primitive.backFragment); + changePrimitive(primitive.frontFragment); + } else { + _mesh._scene.changedPrimitives[primitive] = true; + } + } + + /** + * @private + * Расчёт UV-матрицы на основании первых трёх UV-координат. + */ + private function calculateBaseUV():void { + // Расчёт UV-матрицы + if (_aUV != null && _bUV != null && _cUV != null) { + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + var det:Number = abu*acv - abv*acu; + + // Проверка на нулевой определитель + if (det < uvThreshold && det > -uvThreshold) { + var len:Number; + if (abu < uvThreshold && abu > -uvThreshold && abv < uvThreshold && abv > -uvThreshold) { + if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) { + // Оба вырождены + abu = uvThreshold; + acv = uvThreshold; + } else { + // Вырожден AB + len = Math.sqrt(acu*acu + acv*acv); + abu = uvThreshold*acv/len; + abv = -uvThreshold*acu/len; + } + } else { + if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) { + //Вырожден AC + len = Math.sqrt(abu*abu + abv*abv); + acu = -uvThreshold*abv/len; + acv = uvThreshold*abu/len; + } else { + // Сонаправлены + len = Math.sqrt(abu*abu + abv*abv); + acu += uvThreshold*abv/len; + acv -= uvThreshold*abu/len; + } + } + // Пересчитываем определитель + det = abu*acv - abv*acu; + } + // Создаём матрицу + if (uvMatrixBase == null) { + uvMatrixBase = new Matrix(); + orthoTextureMatrix = new Matrix(); + } + uvMatrixBase.a = acv / det; + uvMatrixBase.b = -abv / det; + uvMatrixBase.c = -acu / det; + uvMatrixBase.d = abu / det; + uvMatrixBase.tx = -(uvMatrixBase.a * _aUV.x + uvMatrixBase.c * _aUV.y); + uvMatrixBase.ty = -(uvMatrixBase.b * _aUV.x + uvMatrixBase.d * _aUV.y); + } else { + // Удаляем UV-матрицу + uvMatrixBase = null; + orthoTextureMatrix = null; + } + } + + /** + * @private + * Расчет UV матрицы грани. + */ + private function calculateUV():void { + if (uvMatrixBase != null) { + if (uvMatrix == null) { + uvMatrix = new Matrix3D(); + } + var a:Point3D = _vertices[0].globalCoords; + var b:Point3D = _vertices[1].globalCoords; + var c:Point3D = _vertices[2].globalCoords; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + + uvMatrix.a = abx*uvMatrixBase.a + acx*uvMatrixBase.b; + uvMatrix.b = abx*uvMatrixBase.c + acx*uvMatrixBase.d; + uvMatrix.c = globalNormal.x; + uvMatrix.d = abx*uvMatrixBase.tx + acx*uvMatrixBase.ty + a.x; + uvMatrix.e = aby*uvMatrixBase.a + acy*uvMatrixBase.b; + uvMatrix.f = aby*uvMatrixBase.c + acy*uvMatrixBase.d; + uvMatrix.g = globalNormal.y; + uvMatrix.h = aby*uvMatrixBase.tx + acy*uvMatrixBase.ty + a.y; + uvMatrix.i = abz*uvMatrixBase.a + acz*uvMatrixBase.b; + uvMatrix.j = abz*uvMatrixBase.c + acz*uvMatrixBase.d; + uvMatrix.k = globalNormal.z; + uvMatrix.l = abz*uvMatrixBase.tx + acz*uvMatrixBase.ty + a.z; + + // Считаем invert + var _a:Number = uvMatrix.a; + var _b:Number = uvMatrix.b; + var _c:Number = uvMatrix.c; + var _d:Number = uvMatrix.d; + var _e:Number = uvMatrix.e; + var _f:Number = uvMatrix.f; + var _g:Number = uvMatrix.g; + var _h:Number = uvMatrix.h; + var _i:Number = uvMatrix.i; + var _j:Number = uvMatrix.j; + var _k:Number = uvMatrix.k; + var _l:Number = uvMatrix.l; + + var det:Number = -_c*_f*_i + _b*_g*_i + _c*_e*_j - _a*_g*_j - _b*_e*_k + _a*_f*_k; + if (det != 0) { + uvMatrix.a = (-_g*_j + _f*_k)/det; + uvMatrix.b = (_c*_j - _b*_k)/det; + uvMatrix.c = (-_c*_f + _b*_g)/det; + uvMatrix.d = (_d*_g*_j - _c*_h*_j - _d*_f*_k + _b*_h*_k + _c*_f*_l - _b*_g*_l)/det; + uvMatrix.e = (_g*_i - _e*_k)/det; + uvMatrix.f = (-_c*_i + _a*_k)/det; + uvMatrix.g = (_c*_e - _a*_g)/det; + uvMatrix.h = (_c*_h*_i - _d*_g*_i + _d*_e*_k - _a*_h*_k - _c*_e*_l + _a*_g*_l)/det; + uvMatrix.i = (-_f*_i + _e*_j)/det; + uvMatrix.j = (_b*_i - _a*_j)/det; + uvMatrix.k = (-_b*_e + _a*_f)/det; + uvMatrix.l = (_d*_f*_i - _b*_h*_i - _d*_e*_j + _a*_h*_j + _b*_e*_l - _a*_f*_l)/det; + } else { + uvMatrix = null; + } + } else { + uvMatrix = null; + } + } + + /** + * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex. + * + * @see Vertex + */ + public function get vertices():Array { + return new Array().concat(_vertices); + } + + /** + * Количество вершин грани. + */ + public function get verticesCount():uint { + return _verticesCount; + } + + /** + * Полигональный объект, которому принадлежит грань. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Поверхность, которой принадлежит грань. + */ + public function get surface():Surface { + return _surface; + } + + /** + * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор + * имеет значение null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getFaceId(this) : null; + } + + /** + * UV-координаты, соответствующие первой вершине грани. + */ + public function get aUV():Point { + return (_aUV != null) ? _aUV.clone() : null; + } + + /** + * UV-координаты, соответствующие второй вершине грани. + */ + public function get bUV():Point { + return (_bUV != null) ? _bUV.clone() : null; + } + + /** + * UV-координаты, соответствующие третьей вершине грани. + */ + public function get cUV():Point { + return (_cUV != null) ? _cUV.clone() : null; + } + + /** + * @private + */ + public function set aUV(value:Point):void { + if (_aUV != null) { + if (value != null) { + if (!_aUV.equals(value)) { + _aUV.x = value.x; + _aUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + _aUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + if (value != null) { + _aUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } + } + + /** + * @private + */ + public function set bUV(value:Point):void { + if (_bUV != null) { + if (value != null) { + if (!_bUV.equals(value)) { + _bUV.x = value.x; + _bUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + _bUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + if (value != null) { + _bUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } + } + + /** + * @private + */ + public function set cUV(value:Point):void { + if (_cUV != null) { + if (value != null) { + if (!_cUV.equals(value)) { + _cUV.x = value.x; + _cUV.y = value.y; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + _cUV = null; + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } else { + if (value != null) { + _cUV = value.clone(); + if (_mesh != null) { + _mesh.addOperationToScene(calculateBaseUVOperation); + } + } + } + } + + /** + * Нормаль в локальной системе координат. + */ + public function get normal():Point3D { + var res:Point3D = new Point3D(); + var vertex:Vertex = _vertices[0]; + var av:Point3D = vertex.coords; + vertex = _vertices[1]; + var bv:Point3D = vertex.coords; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + vertex = _vertices[2]; + var cv:Point3D = vertex.coords; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + res.x = acz*aby - acy*abz; + res.y = acx*abz - acz*abx; + res.z = acy*abx - acx*aby; + if (res.x != 0 || res.y != 0 || res.z != 0) { + var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z); + res.x /= k; + res.y /= k; + res.z /= k; + } + return res; + } + + /** + * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @return UV-координаты заданной точки + */ + public function getUV(point:Point3D):Point { + return getUVFast(point, normal); + } + + /** + * @private + * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта + * локальной нормали грани. Используется для оптимизации. + * + * @param point точка в плоскости грани, для которой производится расчёт UV-координат + * @param normal нормаль плоскости грани в локальной системе координат + * @return UV-координаты заданной точки + */ + alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point { + if (_aUV == null || _bUV == null || _cUV == null) { + return null; + } + + // Выбор наиболее длинной оси нормали + var dir:uint; + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) { + if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 0; + } else { + dir = 2; + } + } else { + if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) { + dir = 1; + } else { + dir = 2; + } + } + + // Расчёт соотношения по векторам AB и AC + var v:Vertex = _vertices[0]; + var a:Point3D = v._coords; + v = _vertices[1]; + var b:Point3D = v._coords; + v = _vertices[2]; + var c:Point3D = v._coords; + + var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x); + var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z); + var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x); + var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z); + var det:Number = ab1*ac2 - ac1*ab2; + + var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x); + var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z); + var abk:Number = (ad1*ac2 - ac1*ad2)/det; + var ack:Number = (ab1*ad2 - ad1*ab2)/det; + + // Интерполяция по UV первых точек + var abu:Number = _bUV.x - _aUV.x; + var abv:Number = _bUV.y - _aUV.y; + var acu:Number = _cUV.x - _aUV.x; + var acv:Number = _cUV.y - _aUV.y; + + return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack); + } + + /** + * Множество граней, имеющих общие рёбра с текущей гранью. + */ + public function get edgeJoinedFaces():Set { + var res:Set = new Set(true); + // Перебираем точки грани + for (var i:uint = 0; i < _verticesCount; i++) { + var a:Vertex = _vertices[i]; + var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0]; + + // Перебираем грани текущей точки + for (var key:* in a._faces) { + var face:Face = key; + // Если это другая грань и у неё также есть следующая точка + if (face != this && face._vertices.indexOf(b) >= 0) { + // Значит у граней общее ребро + res[face] = true; + } + } + } + return res; + } + + /** + * @private + * Удаление всех вершин из грани. + * Очистка базового примитива. + */ + alternativa3d function removeVertices():void { + // Удалить вершины + for (var i:uint = 0; i < _verticesCount; i++) { + // Удаляем из списка + var vertex:Vertex = _vertices.pop(); + // Удаляем вершину из грани + vertex.removeFromFace(this); + } + // Очищаем вершины в примитиве + primitive.points.length = 0; + // Обнуляем количество вершин + _verticesCount = 0; + } + + /** + * @private + * Добавление грани на сцену + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену рассчитываем плоскость и UV + scene.addOperation(calculateNormalOperation); + scene.addOperation(calculateBaseUVOperation); + + // Подписываем сцену на операции + updatePrimitiveOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Удаление грани из сцены + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateBaseUVOperation); + scene.removeOperation(calculateNormalOperation); + scene.removeOperation(updatePrimitiveOperation); + scene.removeOperation(updateMaterialOperation); + + // Удаляем примитивы из сцены + removePrimitive(primitive); + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + } + + /** + * @private + * Добавление грани в меш + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation); + // При перемещении меша, пересчитать UV. При вращении вызовется calculateNormal и UV пересчитаются. + mesh.changeCoordsOperation.addSequel(calculateUVOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление грани из меша + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation); + mesh.changeCoordsOperation.removeSequel(calculateUVOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation); + mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation); + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Добавление к поверхности + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Подписка поверхности на операции + surface.changeMaterialOperation.addSequel(updateMaterialOperation); + // Если при смене поверхности изменился материал + if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Сохранить поверхность + _surface = surface; + } + + /** + * @private + * Удаление из поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Отписка поверхности от операций + surface.changeMaterialOperation.removeSequel(updateMaterialOperation); + // Если был материал + if (surface._material != null) { + // Отправляем сигнал смены материала + _mesh.addOperationToScene(updateMaterialOperation); + } + // Удалить ссылку на поверхность + _surface = null; + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : ""); + for (var i:uint = 0; i < _verticesCount; i++) { + var vertex:Vertex = _vertices[i]; + res += vertex.id + ((i < _verticesCount - 1) ? ", " : ""); + } + res += "]"; + return res; + } + + /** + * Добавление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылка события. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверка наличия зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as new file mode 100644 index 0000000..e6b6d93 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as @@ -0,0 +1,986 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNeedMoreVerticesError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.errors.SurfaceExistsError; + import alternativa.engine3d.errors.SurfaceNotFoundError; + import alternativa.engine3d.errors.VertexExistsError; + import alternativa.engine3d.errors.VertexNotFoundError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Map; + import alternativa.utils.ObjectUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект + * содержит в себе наборы вершин, граней и поверхностей. + */ + public class Mesh extends Object3D { + + // Инкремент количества объектов + private static var counter:uint = 0; + + // Инкременты для идентификаторов вершин, граней и поверхностей + private var vertexIDCounter:uint = 0; + private var faceIDCounter:uint = 0; + private var surfaceIDCounter:uint = 0; + + /** + * @private + * Список вершин + */ + alternativa3d var _vertices:Map = new Map(); + /** + * @private + * Список граней + */ + alternativa3d var _faces:Map = new Map(); + /** + * @private + * Список поверхностей + */ + alternativa3d var _surfaces:Map = new Map(); + + /** + * Создание экземпляра полигонального объекта. + * + * @param name имя экземпляра + */ + public function Mesh(name:String = null) { + super(name); + } + + /** + * Добавление новой вершины к объекту. + * + * @param x координата X в локальной системе координат объекта + * @param y координата Y в локальной системе координат объекта + * @param z координата Z в локальной системе координат объекта + * @param id идентификатор вершины. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной вершины + * + * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex { + // Проверяем ID + if (id != null) { + // Если уже есть вершина с таким ID + if (_vertices[id] != undefined) { + if (_vertices[id] is Vertex) { + throw new VertexExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный + while (_vertices[vertexIDCounter] != undefined) { + vertexIDCounter++; + } + id = vertexIDCounter; + } + + // Создаём вершину + var v:Vertex = new Vertex(x, y, z); + + // Добавляем вершину на сцену + if (_scene != null) { + v.addToScene(_scene); + } + + // Добавляем вершину в меш + v.addToMesh(this); + _vertices[id] = v; + + return v; + } + + /** + * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины + * + * @return экземпляр удалённой вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeVertex(vertex:Object):Vertex { + var byLink:Boolean = vertex is Vertex; + + // Проверяем на null + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + + // Проверяем наличие вершины в меше + if (byLink) { + // Если удаляем по ссылке + if (Vertex(vertex)._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + } else { + // Если удаляем по ID + if (_vertices[vertex] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertex, this); + } else if (!(_vertices[vertex] is Vertex)) { + // По этому id не вершина + throw new InvalidIDError(vertex, this); + } + } + + // Находим вершину и её ID + var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex]; + var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex; + + // Удаляем вершину из сцены + if (_scene != null) { + v.removeFromScene(_scene); + } + + // Удаляем вершину из меша + v.removeFromMesh(this); + delete _vertices[id]; + + return v; + } + + /** + * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная + * ни к одной поверхности. + * + * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой + * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex, + * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину. + * @param id идентификатор грани. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной грани + * + * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан + * null, либо количество вершин в массиве меньше трёх + * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива + * + * @see Vertex + */ + public function createFace(vertices:Array, id:Object = null):Face { + // Проверяем на null + if (vertices == null) { + throw new FaceNeedMoreVerticesError(this); + } + + // Проверяем ID + if (id != null) { + // Если уже есть грань с таким ID + if (_faces[id] != undefined) { + if (_faces[id] is Face) { + throw new FaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_faces[faceIDCounter] != undefined) { + faceIDCounter++; + } + id = faceIDCounter; + } + + // Проверяем количество точек + var length:uint = vertices.length; + if (length < 3) { + throw new FaceNeedMoreVerticesError(this, length); + } + + // Проверяем и формируем список вершин + var v:Array = new Array(); + var vertex:Vertex; + for (var i:uint = 0; i < length; i++) { + if (vertices[i] is Vertex) { + // Если работаем со ссылками + vertex = vertices[i]; + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertices[i], this); + } + } else { + // Если работаем с ID + if (_vertices[vertices[i]] == null) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(vertices[i], this); + } else if (!(_vertices[vertices[i]] is Vertex)) { + // Если id зарезервировано + throw new InvalidIDError(vertices[i],this); + } + vertex = _vertices[vertices[i]]; + } + v.push(vertex); + } + + // Создаём грань + var f:Face = new Face(v); + + // Добавляем грань на сцену + if (_scene != null) { + f.addToScene(_scene); + } + + // Добавляем грань в меш + f.addToMesh(this); + _faces[id] = f; + + return f; + } + + /** + * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит. + * + * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани + * + * @return экземпляр удалённой грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeFace(face:Object):Face { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань и её ID + var f:Face = byLink ? Face(face) : _faces[face] ; + var id:Object = byLink ? getFaceId(Face(face)) : face; + + // Удаляем вершины из грани + f.removeVertices(); + + // Удаляем грань из поверхности + if (f._surface != null) { + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Удаляем грань из сцены + if (_scene != null) { + f.removeFromScene(_scene); + } + + // Удаляем грань из меша + f.removeFromMesh(this); + + delete _faces[id]; + + return f; + } + + /** + * Добавление новой поверхности к объекту. + * + * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса + * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать + * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если + * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность. + * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет + * сформирован автоматически. + * + * @return экземпляр добавленной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function createSurface(faces:Array = null, id:Object = null):Surface { + // Проверяем ID + if (id != null) { + // Если уже есть поверхность с таким ID + if (_surfaces[id] != undefined) { + if (_surfaces[id] is Surface) { + throw new SurfaceExistsError(id, this); + } else { + throw new InvalidIDError(id, this); + } + } + } else { + // Ищем первый свободный ID + while (_surfaces[surfaceIDCounter] != undefined) { + surfaceIDCounter++; + } + id = surfaceIDCounter; + } + + // Создаём поверхность + var s:Surface = new Surface(); + + // Добавляем поверхность на сцену + if (_scene != null) { + s.addToScene(_scene); + } + + // Добавляем поверхность в меш + s.addToMesh(this); + _surfaces[id] = s; + + // Добавляем грани, если есть + if (faces != null) { + var length:uint = faces.length; + for (var i:uint = 0; i < length; i++) { + s.addFace(faces[i]); + } + } + return s; + } + + /** + * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани. + * + * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности + * + * @return экземпляр удалённой поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function removeSurface(surface:Object):Surface { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если удаляем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если удаляем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность и её ID + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface; + + // Удаляем поверхность из сцены + if (_scene != null) { + s.removeFromScene(_scene); + } + + // Удаляем грани из поверхности + s.removeFaces(); + + // Удаляем поверхность из меша + s.removeFromMesh(this); + delete _surfaces[id]; + + return s; + } + + /** + * Добавление всех граней объекта в указанную поверхность. + * + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в + * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором, + * будет создана новая поверхность. + * + * @param removeSurfaces удалять или нет пустые поверхности после переноса граней + * + * @return экземпляр поверхности, в которую перенесены грани + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface { + var returnSurface:Surface; + var returnSurfaceId:Object; + if (surface is Surface) { + // Работаем с экземпляром Surface + if (surface._mesh == this) { + returnSurface = Surface(surface); + } else { + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Работаем с идентификатором + if (_surfaces[surface] == undefined) { + // Поверхности еще нет + returnSurface = createSurface(null, surface); + returnSurfaceId = surface; + } else { + if (_surfaces[surface] is Surface) { + returnSurface = _surfaces[surface]; + } else { + // _surfaces[surface] по идентификатору возвращает не Surface + throw new InvalidIDError(surface, this); + } + } + } + // Перемещаем все грани + for each (var face:Face in _faces) { + if (face._surface != returnSurface) { + returnSurface.addFace(face); + } + } + if (removeSurfaces) { + // Удаляем старые, теперь вручную - меньше проверок, но рискованно + if (returnSurfaceId == null) { + returnSurfaceId = getSurfaceId(returnSurface); + } + var newSurfaces:Map = new Map(); + newSurfaces[returnSurfaceId] = returnSurface; + delete _surfaces[returnSurfaceId]; + // Удаляем оставшиеся + for (var currentSurfaceId:* in _surfaces) { + // Удаляем поверхность из сцены + var currentSurface:Surface = _surfaces[currentSurfaceId]; + if (_scene != null) { + currentSurface.removeFromScene(_scene); + } + // Удаляем поверхность из меша + currentSurface.removeFromMesh(this); + delete _surfaces[currentSurfaceId]; + } + // Новый список граней + _surfaces = newSurfaces; + } + return returnSurface; + } + + /** + * Установка материала для указанной поверхности. + * + * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности. + * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Surface + */ + public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void { + var byLink:Boolean = surface is Surface; + + // Проверяем на null + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + + // Проверяем наличие поверхности в меше + if (byLink) { + // Если назначаем по ссылке + if (Surface(surface)._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + } else { + // Если назначаем по ID + if (_surfaces[surface] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(surface, this); + } else if (!(_surfaces[surface] is Surface)) { + throw new InvalidIDError(surface, this); + } + } + + // Находим поверхность + var s:Surface = byLink ? Surface(surface) : _surfaces[surface]; + + // Назначаем материал + s.material = material; + } + + /** + * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала. + * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей. + * + * @param material устанавливаемый материал + */ + public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void { + for each (var surface:Surface in _surfaces) { + surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null; + } + } + + /** + * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по + * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать + * невырожденный треугольник в UV-пространстве. + * + * @param aUV UV-координаты, соответствующие первой вершине грани + * @param bUV UV-координаты, соответствующие второй вершине грани + * @param cUV UV-координаты, соответствующие третьей вершине грани + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если назначаем по ссылке + if (Face(face)._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если назначаем по ID + if (_faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else if (!(_faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _faces[face]; + + // Назначаем UV-координаты + f.aUV = aUV; + f.bUV = bUV; + f.cUV = cUV; + } + + /** + * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин. + */ + public function get vertices():Map { + return _vertices.clone(); + } + + /** + * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней. + */ + public function get faces():Map { + return _faces.clone(); + } + + /** + * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей. + */ + public function get surfaces():Map { + return _surfaces.clone(); + } + + /** + * Получение вершины объекта по её идентификатору. + * + * @param id идентификатор вершины + * + * @return экземпляр вершины с указанным идентификатором + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getVertexById(id:Object):Vertex { + if (id == null) { + throw new VertexNotFoundError(null, this); + } + if (_vertices[id] == undefined) { + // Если нет вершины с таким ID + throw new VertexNotFoundError(id, this); + } else { + if (_vertices[id] is Vertex) { + return _vertices[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора вершины объекта. + * + * @param экземпляр вершины + * + * @return идентификатор указанной вершины + * + * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину + */ + public function getVertexId(vertex:Vertex):Object { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex._mesh != this) { + // Если вершина не в меше + throw new VertexNotFoundError(vertex, this); + } + for (var i:Object in _vertices) { + if (_vertices[i] == vertex) { + return i; + } + } + throw new VertexNotFoundError(vertex, this); + } + + /** + * Проверка наличия вершины в объекте. + * + * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины + * + * @return true, если объект содержит указанную вершину, иначе false + * + * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Vertex + */ + public function hasVertex(vertex:Object):Boolean { + if (vertex == null) { + throw new VertexNotFoundError(null, this); + } + if (vertex is Vertex) { + // Проверка вершины + return vertex._mesh == this; + } else { + // Проверка ID вершины + if (_vertices[vertex] != undefined) { + // По этому ID есть объект + if (_vertices[vertex] is Vertex) { + // Объект является вершиной + return true; + } else { + // ID некорректный + throw new InvalidIDError(vertex, this); + } + } else { + return false; + } + } + } + + /** + * Получение грани объекта по ее идентификатору. + * + * @param id идентификатор грани + * + * @return экземпляр грани с указанным идентификатором + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getFaceById(id:Object):Face { + if (id == null) { + throw new FaceNotFoundError(null, this); + } + if (_faces[id] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(id, this); + } else { + if (_faces[id] is Face) { + return _faces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора грани объекта. + * + * @param face экземпляр грани + * + * @return идентификатор указанной грани + * + * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань + */ + public function getFaceId(face:Face):Object { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face._mesh != this) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + for (var i:Object in _faces) { + if (_faces[i] == face) { + return i; + } + } + throw new FaceNotFoundError(face, this); + } + + /** + * Проверка наличия грани в объекте. + * + * @param face экземпляр класса Face или идентификатор грани + * + * @return true, если объект содержит указанную грань, иначе false + * + * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasFace(face:Object):Boolean { + if (face == null) { + throw new FaceNotFoundError(null, this); + } + if (face is Face) { + // Проверка грани + return face._mesh == this; + } else { + // Проверка ID грани + if (_faces[face] != undefined) { + // По этому ID есть объект + if (_faces[face] is Face) { + // Объект является гранью + return true; + } else { + // ID некорректный + throw new InvalidIDError(face, this); + } + } else { + return false; + } + } + } + + /** + * Получение поверхности объекта по ее идентификатору + * + * @param id идентификатор поверхности + * + * @return экземпляр поверхности с указанным идентификатором + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function getSurfaceById(id:Object):Surface { + if (id == null) { + throw new SurfaceNotFoundError(null, this); + } + if (_surfaces[id] == undefined) { + // Если нет поверхности с таким ID + throw new SurfaceNotFoundError(id, this); + } else { + if (_surfaces[id] is Surface) { + return _surfaces[id]; + } else { + throw new InvalidIDError(id, this); + } + } + } + + /** + * Получение идентификатора поверхности объекта. + * + * @param surface экземпляр поверхности + * + * @return идентификатор указанной поверхности + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность + */ + public function getSurfaceId(surface:Surface):Object { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface._mesh != this) { + // Если поверхность не в меше + throw new SurfaceNotFoundError(surface, this); + } + for (var i:Object in _surfaces) { + if (_surfaces[i] == surface) { + return i; + } + } + return null; + } + + /** + * Проверка наличия поверхности в объекте. + * + * @param surface экземпляр класса Surface или идентификатор поверхности + * + * @return true, если объект содержит указанную поверхность, иначе false + * + * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + */ + public function hasSurface(surface:Object):Boolean { + if (surface == null) { + throw new SurfaceNotFoundError(null, this); + } + if (surface is Surface) { + // Проверка поверхности + return surface._mesh == this; + } else { + // Проверка ID поверхности + if (_surfaces[surface] != undefined) { + // По этому ID есть объект + if (_surfaces[surface] is Surface) { + // Объект является поверхностью + return true; + } else { + // ID некорректный + throw new InvalidIDError(surface, this); + } + } else { + return false; + } + } + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + var vertex:Vertex; + var face:Face; + var surface:Surface; + if (value != null) { + // Добавить вершины на сцену + for each (vertex in _vertices) { + vertex.addToScene(value); + } + // Добавить грани на сцену + for each (face in _faces) { + face.addToScene(value); + } + // Добавить поверхности на сцену + for each (surface in _surfaces) { + surface.addToScene(value); + } + } else { + // Удалить вершины из сцены + for each (vertex in _vertices) { + vertex.removeFromScene(_scene); + } + // Удалить грани из сцены + for each (face in _faces) { + face.removeFromScene(_scene); + } + // Удалить поверхности из сцены + for each (surface in _surfaces) { + surface.removeFromScene(_scene); + } + } + } + super.setScene(value); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "mesh" + ++counter; + } + + /** + * @inheritDoc + */ + override public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]"; + } + + /** + * @inheritDoc + */ + override protected function createEmptyObject():Object3D { + return new Mesh(); + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Mesh = Mesh(source); + + var id:*; + var len:int; + var i:int; + // Копирование вершин + var vertexMap:Map = new Map(true); + for (id in src._vertices) { + var sourceVertex:Vertex = src._vertices[id]; + vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id); + } + + // Копирование граней + var faceMap:Map = new Map(true); + for (id in src._faces) { + var sourceFace:Face = src._faces[id]; + len = sourceFace._vertices.length; + var faceVertices:Array = new Array(len); + for (i = 0; i < len; i++) { + faceVertices[i] = vertexMap[sourceFace._vertices[i]]; + } + var newFace:Face = createFace(faceVertices, id); + newFace.aUV = sourceFace._aUV; + newFace.bUV = sourceFace._bUV; + newFace.cUV = sourceFace._cUV; + faceMap[sourceFace] = newFace; + } + + // Копирование поверхностей + for (id in src._surfaces) { + var sourceSurface:Surface = src._surfaces[id]; + var surfaceFaces:Array = sourceSurface._faces.toArray(); + len = surfaceFaces.length; + for (i = 0; i < len; i++) { + surfaceFaces[i] = faceMap[surfaceFaces[i]]; + } + var surface:Surface = createSurface(surfaceFaces, id); + var sourceMaterial:SurfaceMaterial = sourceSurface.material; + if (sourceMaterial != null) { + surface.material = SurfaceMaterial(sourceMaterial.clone()); + } + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..be74ffd --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,991 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.errors.Object3DHierarchyError; + import alternativa.engine3d.errors.Object3DNotFoundError; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над объектом. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения + * о трансформации объекта как единого целого. + * + *

Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая + * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей + * X, Y, Z и параллельного переноса центра объекта из начала координат. + * Операции применяются в порядке их перечисления.

+ * + *

Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций + * самого объекта и всех его предков по иерархии объектов сцены.

+ * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие. На данный момент не реализованы capture и bubbling фазы рассылки + * событий.

+ */ + public class Object3D implements IEventDispatcher { + /** + * @private + * Вспомогательная матрица + */ + alternativa3d static var matrix1:Matrix3D = new Matrix3D(); + /** + * @private + * Вспомогательная матрица + */ + alternativa3d static var matrix2:Matrix3D = new Matrix3D(); + + /** + * @private + * Поворот или масштабирование + */ + alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this); + /** + * @private + * Перемещение + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт матрицы трансформации + */ + alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION); + /** + * @private + * Изменение уровеня мобильности + */ + alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY); + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * @private + * Наименование + */ + alternativa3d var _name:String; + /** + * @private + * Сцена + */ + alternativa3d var _scene:Scene3D; + /** + * @private + * Родительский объект + */ + alternativa3d var _parent:Object3D; + /** + * @private + * Дочерние объекты + */ + alternativa3d var _children:Set = new Set(); + /** + * @private + * Уровень мобильности + */ + alternativa3d var _mobility:int = 0; + /** + * @private + */ + alternativa3d var inheritedMobility:int; + /** + * @private + * Координаты объекта относительно родителя + */ + alternativa3d var _coords:Point3D = new Point3D(); + /** + * @private + * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationX:Number = 0; + /** + * @private + * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationY:Number = 0; + /** + * @private + * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах. + */ + alternativa3d var _rotationZ:Number = 0; + /** + * @private + * Мастшаб объекта по оси X относительно родителя + */ + alternativa3d var _scaleX:Number = 1; + /** + * @private + * Мастшаб объекта по оси Y относительно родителя + */ + alternativa3d var _scaleY:Number = 1; + /** + * @private + * Мастшаб объекта по оси Z относительно родителя + */ + alternativa3d var _scaleZ:Number = 1; + /** + * @private + * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены + */ + alternativa3d var _transformation:Matrix3D = new Matrix3D(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра класса. + * + * @param name имя экземпляра + */ + public function Object3D(name:String = null) { + // Имя по-умолчанию + _name = (name != null) ? name : defaultName(); + + // Последствия операций + changeRotationOrScaleOperation.addSequel(calculateTransformationOperation); + changeCoordsOperation.addSequel(calculateTransformationOperation); + } + + /** + * @private + * Расчёт трансформации + */ + alternativa3d function calculateTransformation():void { + if (changeRotationOrScaleOperation.queued) { + // Если полная трансформация + _transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + if (_parent != null) { + _transformation.combine(_parent._transformation); + } + // Сохраняем глобальные координаты объекта + globalCoords.x = _transformation.d; + globalCoords.y = _transformation.h; + globalCoords.z = _transformation.l; + } else { + // Если только перемещение + globalCoords.copy(_coords); + if (_parent != null) { + globalCoords.transform(_parent._transformation); + } + _transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z); + } + } + + /** + * @private + * Расчёт общей мобильности + */ + private function calculateMobility():void { + inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility; + } + + /** + * Добавляет дочерний объект. Добавляемый объект удаляется из списка детей предыдущего родителя. + * Новой сценой дочернего объекта становится сцена родителя. + * + * @param child добавляемый объект + * + * @return добавленный объект + * + * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены + */ + public function addChild(child:Object3D):Object3D { + + // Проверка на null + if (child == null) { + throw new Object3DHierarchyError(null, this); + } + + // Проверка на наличие + if (child._parent == this) { + return child; + } + + // Проверка на добавление к самому себе + if (child == this) { + throw new Object3DHierarchyError(this, this); + } + + // Проверка на добавление родительского объекта + if (child._scene == _scene) { + // Если объект был в той же сцене, либо оба не были в сцене + var parentObject:Object3D = _parent; + while (parentObject != null) { + if (child == parentObject) { + throw new Object3DHierarchyError(child, this); + return; + } + parentObject = parentObject._parent; + } + } + + // Если объект был в другом объекте + if (child._parent != null) { + // Удалить его оттуда + child._parent._children.remove(child); + } else { + // Если объект был корневым в сцене + if (child._scene != null && child._scene._root == child) { + child._scene.root = null; + } + } + + // Добавляем в список + _children.add(child); + // Указываем себя как родителя + child.setParent(this); + // Устанавливаем уровни + child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1); + // Указываем сцену + child.setScene(_scene); + + return child; + } + + /** + * Удаляет указанный объект из списка детей. + * + * @param child удаляемый дочерний объект + * + * @return удаленный объект + * + * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта + */ + public function removeChild(child:Object3D):Object3D { + // Проверка на null + if (child == null) { + throw new Object3DNotFoundError(null, this); + } + // Проверка на наличие + if (child._parent != this) { + throw new Object3DNotFoundError(child, this); + } + // Убираем из списка + _children.remove(child); + // Удаляем ссылку на родителя + child.setParent(null); + // Удаляем ссылку на сцену + child.setScene(null); + + return child; + } + + /** + * @private + * Установка родительского объекта. + * + * @param value родительский объект + */ + alternativa3d function setParent(value:Object3D):void { + // Отписываемся от сигналов старого родителя + if (_parent != null) { + removeParentSequels(); + } + // Сохранить родителя + _parent = value; + // Если устанавливаем родителя + if (value != null) { + // Подписка на сигналы родителя + addParentSequels(); + } + } + + /** + * @private + * Установка новой сцены для объекта. + * + * @param value сцена + */ + alternativa3d function setScene(value:Scene3D):void { + if (_scene != value) { + // Если была сцена + if (_scene != null) { + // Удалиться из сцены + removeFromScene(_scene); + } + // Если новая сцена + if (value != null) { + // Добавиться на сцену + addToScene(value); + } + // Сохранить сцену + _scene = value; + } else { + // Посылаем операцию трансформации + addOperationToScene(changeRotationOrScaleOperation); + // Посылаем операцию пересчёта мобильности + addOperationToScene(calculateMobilityOperation); + } + // Установить эту сцену у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setScene(_scene); + } + } + + /** + * @private + * Установка уровня операции трансформации. + * + * @param value уровень операции трансформации + */ + alternativa3d function setLevel(value:uint):void { + // Установить уровень операции трансформации и расчёта мобильности + calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value; + calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value; + // Установить уровни у дочерних объектов + for (var key:* in _children) { + var object:Object3D = key; + object.setLevel(value + 1); + } + } + + /** + * @private + * Подписка на сигналы родителя. + */ + private function addParentSequels():void { + _parent.changeCoordsOperation.addSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation); + } + + /** + * @private + * Удаление подписки на сигналы родителя. + */ + private function removeParentSequels():void { + _parent.changeCoordsOperation.removeSequel(changeCoordsOperation); + _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation); + _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation); + } + + /** + * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, в которую добавляется объект + */ + protected function addToScene(scene:Scene3D):void { + // При добавлении на сцену полная трансформация и расчёт мобильности + scene.addOperation(changeRotationOrScaleOperation); + scene.addOperation(calculateMobilityOperation); + } + + /** + * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения + * специфических действий. + * + * @param scene сцена, из которой удаляется объект + */ + protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeRotationOrScaleOperation); + scene.removeOperation(changeCoordsOperation); + scene.removeOperation(calculateMobilityOperation); + } + + /** + * Имя объекта. + */ + public function get name():String { + return _name; + } + + /** + * @private + */ + public function set name(value:String):void { + _name = value; + } + + /** + * Сцена, которой принадлежит объект. + */ + public function get scene():Scene3D { + return _scene; + } + + /** + * Родительский объект. + */ + public function get parent():Object3D { + return _parent; + } + + /** + * Набор дочерних объектов. + */ + public function get children():Set { + return _children.clone(); + } + + /** + * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков + * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные + * объекты находятся ближе к корню дерева, чем более мобильные. + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + if (_mobility != value) { + _mobility = value; + addOperationToScene(calculateMobilityOperation); + } + } + + /** + * Координата X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * Координата Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * Координата Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Координаты объекта. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + addOperationToScene(changeCoordsOperation); + } + } + + /** + * Угол поворота вокруг оси X, заданный в радианах. + */ + public function get rotationX():Number { + return _rotationX; + } + + /** + * Угол поворота вокруг оси Y, заданный в радианах. + */ + public function get rotationY():Number { + return _rotationY; + } + + /** + * Угол поворота вокруг оси Z, заданный в радианах. + */ + public function get rotationZ():Number { + return _rotationZ; + } + + /** + * @private + */ + public function set rotationX(value:Number):void { + if (_rotationX != value) { + _rotationX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationY(value:Number):void { + if (_rotationY != value) { + _rotationY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set rotationZ(value:Number):void { + if (_rotationZ != value) { + _rotationZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Коэффициент масштабирования вдоль оси X. + */ + public function get scaleX():Number { + return _scaleX; + } + + /** + * Коэффициент масштабирования вдоль оси Y. + */ + public function get scaleY():Number { + return _scaleY; + } + + /** + * Коэффициент масштабирования вдоль оси Z. + */ + public function get scaleZ():Number { + return _scaleZ; + } + + /** + * @private + */ + public function set scaleX(value:Number):void { + if (_scaleX != value) { + _scaleX = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleY(value:Number):void { + if (_scaleY != value) { + _scaleY = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * @private + */ + public function set scaleZ(value:Number):void { + if (_scaleZ != value) { + _scaleZ = value; + addOperationToScene(changeRotationOrScaleOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[" + ObjectUtils.getClassName(this) + " " + _name + "]"; + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + protected function defaultName():String { + return "object" + ++counter; + } + + /** + * @private + * Добавление операции в очередь. + * + * @param operation добавляемая операция + */ + alternativa3d function addOperationToScene(operation:Operation):void { + if (_scene != null) { + _scene.addOperation(operation); + } + } + + /** + * @private + * Удаление операции из очереди. + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperationFromScene(operation:Operation):void { + if (_scene != null) { + _scene.removeOperation(operation); + } + } + + /** + * Создаёт пустой объект без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при + * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и + * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения + * правильного объекта. + * + * @return новый пустой объект + */ + protected function createEmptyObject():Object3D { + return new Object3D(); + } + + /** + * Копирует свойства объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в + * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те + * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется + * вызовом super.clonePropertiesFrom(source). + * + * @param source объект, свойства которого копируются + */ + protected function clonePropertiesFrom(source:Object3D):void { + _name = source._name; + _mobility = source._mobility; + _coords.x = source._coords.x; + _coords.y = source._coords.y; + _coords.z = source._coords.z; + _rotationX = source._rotationX; + _rotationY = source._rotationY; + _rotationZ = source._rotationZ; + _scaleX = source._scaleX; + _scaleY = source._scaleY; + _scaleZ = source._scaleZ; + } + + /** + * Клонирует объект. Для реализации собственного клонирования наследники должны переопределять методы + * createEmptyObject() и clonePropertiesFrom(). + * + * @return клонированный экземпляр объекта + * + * @see #createEmptyObject() + * @see #clonePropertiesFrom() + */ + public function clone():Object3D { + var copy:Object3D = createEmptyObject(); + copy.clonePropertiesFrom(this); + + // Клонирование детей + for (var key:* in _children) { + var child:Object3D = key; + copy.addChild(child.clone()); + } + + return copy; + } + + /** + * Получает дочерний объект с заданным именем. + * + * @param name имя дочернего объекта + * @param deep флаг углублённого поиска. Если задано значение false, поиск будет осуществляться только среди непосредственных + * дочерних объектов, иначе поиск будет выполняться по всему дереву дочерних объектов. + * + * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов + */ + public function getChildByName(name:String, deep:Boolean = false):Object3D { + var key:*; + var child:Object3D; + for (key in _children) { + child = key; + if (child._name == name) { + return child; + } + } + if (deep) { + for (key in _children) { + child = key; + child = child.getChildByName(name, true); + if (child != null) { + return child; + } + } + } + return null; + } + + /** + * Трансформирует точку из локальной системы координат объекта в систему координат сцены. Матрица трансформации корневого объекта сцены + * не учитывается, т.к. его система координат является системой координат сцены. + * + * @param point локальные координаты точки + * @param result в этот параметр будут записаны трансформированные координаты. Если передано значение null, то будет создан + * новый экземпляр класса Point3D. + * + * @return координаты точки в сцене или null в случае, если объект не находится в сцене + */ + public function localToGlobal(point:Point3D, result:Point3D = null):Point3D { + if (_scene == null) { + return null; + } + if (result == null) { + result = point.clone(); + } + + if (_parent == null) { + // Для корневого объекта трансформация единичная вне зависимости от матрицы + return result; + } + + getTransformation(matrix2); + result.transform(matrix2); + + return result; + } + + /** + * Трансформирует точку из глобальной системы координат в локальную. + * + * @param point точка, заданная в глобальной системе координат. + * @param result в этот параметр будут записаны трансформированные координаты. Если передано значение null, то будет создан + * новый экземпляр класса Point3D. + * + * @return точка, трансформированная в локальную систему координат объекта или null в случае, если объект не находится в сцене. + */ + public function globalToLocal(point:Point3D, result:Point3D = null):Point3D { + if (_scene == null) { + return null; + } + if (result == null) { + result = point.clone(); + } + + if (_parent == null) { + // Для корневого объекта трансформация единичная вне зависимости от матрицы + return result; + } + + getTransformation(matrix2); + matrix2.invert(); + result.transform(matrix2); + return result; + } + + /** + * Получает матрицу полной трансформации объекта. + * + * @return матрица полной трансформации, переводящая координаты из локальной системы объекта в систему координат сцены или null в случае, + * если объект не находится в сцене + */ + public function get transformation():Matrix3D { + if (_scene == null) { + return null; + } + var result:Matrix3D = new Matrix3D(); + getTransformation(result); + return result; + } + + /** + * Добавляет обработчик события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылает событие. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверяет наличие зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаляет обработчик события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + + /** + * @private + * Получение матрицы трансформации объекта. Перед расчётом выполняется проверка необходимости пересчёта матрицы. + * + * @param matrix матрица, в которую записывается результат + * + * @return true, если был произведён пересчёт матрицы трансформации, false, если пересчёт не понадобился + */ + alternativa3d function getTransformation(matrix:Matrix3D):Boolean { + var rootObject:Object3D = _scene._root; + var topNonTransformedObject:Object3D; + var currentObject:Object3D = this; + + // Поиск первого нетрансформированного объекта в дереве объектов + do { + if (currentObject.changeCoordsOperation.queued || currentObject.changeRotationOrScaleOperation.queued) { + topNonTransformedObject = currentObject._parent; + } + } while ((currentObject = currentObject._parent) != rootObject); + + if (topNonTransformedObject != null) { + // Расчёт матрицы трансформации + matrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); + currentObject = this; + while ((currentObject = currentObject._parent) != topNonTransformedObject) { + matrix1.toTransform(currentObject._coords.x, currentObject._coords.y, currentObject._coords.z, currentObject._rotationX, currentObject._rotationY, currentObject._rotationZ, currentObject._scaleX, currentObject._scaleY, currentObject._scaleZ); + matrix.combine(matrix1); + } + if (topNonTransformedObject != rootObject) { + matrix.combine(topNonTransformedObject._transformation); + } + return true; + } + matrix.copy(_transformation); + return false; + } + + /** + * Вызывает заданную функцию для объекта и всех его детей, передавая текущий объект в качестве параметра. + * + * @param func функция вида Function(object:Object3D):void + */ + public function forEach(func:Function):void { + func.call(this, this); + for (var child:* in _children) { + Object3D(child).forEach(func); + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as new file mode 100644 index 0000000..5d1e8e2 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as @@ -0,0 +1,131 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.*; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * @private + */ + public class Operation { + + alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000; + alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000; + alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000; + alternativa3d static const FACE_CALCULATE_BASE_UV:uint = 0x04000000; + alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x05000000; + alternativa3d static const FACE_CALCULATE_UV:uint = 0x06000000; + alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x07000000; + alternativa3d static const SECTOR_UPDATE:uint = 0x08000000; + alternativa3d static const SPLITTER_UPDATE:uint = 0x09000000; + alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x0A000000; + alternativa3d static const SECTOR_FIND_NODE:uint = 0x0B000000; + alternativa3d static const SPLITTER_CHANGE_STATE:uint = 0x0C000000; + alternativa3d static const SECTOR_CHANGE_VISIBLE:uint = 0x0D000000; + alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x0E000000; + alternativa3d static const SPRITE_UPDATE_MATERIAL:uint = 0x0F000000; + alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x10000000; + alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x11000000; + alternativa3d static const CAMERA_RENDER:uint = 0x12000000; + alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x13000000; + + // Объект + alternativa3d var object:Object; + + // Метод + alternativa3d var method:Function; + + // Название метода + alternativa3d var name:String; + + // Последствия + private var sequel:Operation; + private var sequels:Set; + + // Приоритет операции + alternativa3d var priority:uint; + + // Находится ли операция в очереди + alternativa3d var queued:Boolean = false; + + public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) { + this.object = object; + this.method = method; + this.name = name; + this.priority = priority; + } + + // Добавить последствие + alternativa3d function addSequel(operation:Operation):void { + if (sequel == null) { + if (sequels == null) { + sequel = operation; + } else { + sequels[operation] = true; + } + } else { + if (sequel != operation) { + sequels = new Set(true); + sequels[sequel] = true; + sequels[operation] = true; + sequel = null; + } + } + } + + // Удалить последствие + alternativa3d function removeSequel(operation:Operation):void { + if (sequel == null) { + if (sequels != null) { + delete sequels[operation]; + var key:*; + var single:Boolean = false; + for (key in sequels) { + if (single) { + single = false; + break; + } + single = true; + } + if (single) { + sequel = key; + sequels = null; + } + } + } else { + if (sequel == operation) { + sequel = null; + } + } + } + + alternativa3d function collectSequels(collector:Array):void { + if (sequel == null) { + // Проверяем последствия + for (var key:* in sequels) { + var operation:Operation = key; + // Если операция ещё не в очереди + if (!operation.queued) { + // Добавляем её в очередь + collector.push(operation); + // Устанавливаем флаг очереди + operation.queued = true; + // Вызываем добавление в очередь её последствий + operation.collectSequels(collector); + } + } + } else { + if (!sequel.queued) { + collector.push(sequel); + sequel.queued = true; + sequel.collectSequels(collector); + } + } + } + + public function toString():String { + return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as new file mode 100644 index 0000000..87dbc74 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as @@ -0,0 +1,123 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + * Примитивный полигон (примитив), хранящийся в узле BSP-дерева. + */ + public class PolyPrimitive { + + /** + * @private + * Количество точек + */ + alternativa3d var num:uint; + /** + * @private + * Точки + */ + alternativa3d var points:Array = new Array(); + /** + * @private + * Грань + */ + alternativa3d var face:Face; + /** + * @private + * Родительский примитив + */ + alternativa3d var parent:PolyPrimitive; + /** + * @private + * Соседний примитив (при наличии родительского) + */ + alternativa3d var sibling:PolyPrimitive; + /** + * @private + * Фрагменты + */ + alternativa3d var backFragment:PolyPrimitive; + /** + * @private + */ + alternativa3d var frontFragment:PolyPrimitive; + /** + * @private + * BSP-нода, в которой находится примитив + */ + alternativa3d var node:BSPNode; + /** + * @private + * Значения для расчёта качества сплиттера + */ + alternativa3d var splits:uint; + /** + * @private + */ + alternativa3d var disbalance:int; + /** + * @private + * Качество примитива как сплиттера (меньше - лучше) + */ + public var splitQuality:Number; + /** + * @private + * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве. + */ + public var mobility:int; + + /** + * @private + * Метод создает новый фрагмент этого примитива. + */ + alternativa3d function createFragment():PolyPrimitive { + var primitive:PolyPrimitive = create(); + primitive.face = face; + primitive.mobility = mobility; + return primitive; + } + + // Хранилище неиспользуемых примитивов + static private var collector:Array = new Array(); + + /** + * @private + * Создать примитив + */ + static alternativa3d function create():PolyPrimitive { + var primitive:PolyPrimitive; + if ((primitive = collector.pop()) != null) { + return primitive; + } + return new PolyPrimitive(); + } + + /** + * @private + * Кладёт примитив в коллектор для последующего реиспользования. + * Ссылка на грань и массивы точек зачищаются в этом методе. + * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода. + * + * Исключение: + * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода. + * + * @param primitive примитив на реиспользование + */ + static alternativa3d function destroy(primitive:PolyPrimitive):void { + primitive.face = null; + primitive.points.length = 0; + collector.push(primitive); + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[Primitive " + face._mesh._name + "]"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as new file mode 100644 index 0000000..1f26485 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as @@ -0,0 +1,1425 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.errors.SectorInOtherSceneError; + import alternativa.engine3d.errors.SplitterInOtherSceneError; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SpriteTextureMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.Shape; + import flash.display.Sprite; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов + * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения + * BSP-дерева для содержимого сцены. + * + *

Можно управлять видимостью между частями сцены. Для этого сцена разделяется на части при помощи + * сплиттеров. Затем частям сцены назначаются сектора которые будут определять отношения видимости между ними.

+ * + * @see #splitters + * @see #sectors + */ + public class Scene3D { + + /** + * @private + * Полное обновление BSP-дерева + */ + alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this); + /** + * @private + * Обновление сплиттеров + */ + alternativa3d var updateSplittersOperation:Operation = new Operation("updateSplitters", this); + /** + * @private + * Изменение примитивов + */ + alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this); + /** + * @private + * Расчёт BSP-дерева + */ + alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP); + /** + * @private + * Очистка списков изменений + */ + alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES); + + /** + * @private + * Корневой объект + */ + alternativa3d var _root:Object3D; + + /** + * @private + * Список операций на выполнение + */ + alternativa3d var operations:Array = new Array(); + /** + * @private + * Вспомогательная пустая операция, используется при удалении операций из списка + */ + alternativa3d var dummyOperation:Operation = new Operation("removed", this); + + /** + * @private + * Флаг анализа сплиттеров + */ + alternativa3d var _splitAnalysis:Boolean = true; + /** + * @private + * Cбалансированность дерева + */ + alternativa3d var _splitBalance:Number = 0; + /** + * @private + * Список изменённых примитивов + */ + alternativa3d var changedPrimitives:Set = new Set(); + + // Вспомогательный список для сборки дочерних примитивов + private var childPrimitives:Set = new Set(); + /** + * @private + * Список примитивов на добавление/удаление + */ + alternativa3d var addPrimitives:Array = new Array(); + + /** + * @private + * Погрешность при определении точек на плоскости + */ + private var _planeOffsetThreshold:Number = 0.01; + /** + * @private + * BSP-дерево + */ + alternativa3d var bsp:BSPNode; + + /** + * @private + * Список нод на удаление + */ + alternativa3d var removeNodes:Set = new Set(); + /** + * @private + * Вспомогательная пустая нода, используется при удалении нод из дерева + */ + alternativa3d var dummyNode:BSPNode = new BSPNode(); + + /** + * @private + * Сплиттеры + */ + private var _splitters:Array = new Array(); + + /** + * @private + * Сектора + */ + private var _sectors:Array = new Array(); + + /** + * Создание экземпляра сцены. + */ + public function Scene3D() { + // Обновление BSP-дерева требует его пересчёта + updateBSPOperation.addSequel(calculateBSPOperation); + // Обновление BSP-дерева требует пересчёта сплиттеров + updateBSPOperation.addSequel(updateSplittersOperation); + // Обновление сплиттеров требует пересчёта БСП дерева + updateSplittersOperation.addSequel(calculateBSPOperation); + // Изменение примитивов в случае пересчёта дерева + calculateBSPOperation.addSequel(changePrimitivesOperation); + // При изменении примитивов необходимо очистить списки изменений + changePrimitivesOperation.addSequel(clearPrimitivesOperation); + } + + /** + * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список + * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех + * областях вывода, к которым подключены находящиеся в сцене камеры. + */ + public function calculate():void { + if (operations[0] != undefined) { + // Формируем последствия + var operation:Operation; + var length:uint = operations.length; + var i:uint; + for (i = 0; i < length; i++) { + operation = operations[i]; + operation.collectSequels(operations); + } + // Сортируем операции + length = operations.length; + sortOperations(0, length - 1); + // Запускаем операции + //trace("----------------------------------------"); + for (i = 0; i < length; i++) { + operation = operations[i]; + if (operation.method != null) { + //trace("EXECUTE:", operation); + operation.method(); + } else { + /*if (operation == dummyOperation) { + trace("REMOVED"); + } else { + trace(operation); + }*/ + } + } + // Очищаем список операций + for (i = 0; i < length; i++) { + operation = operations.pop(); + operation.queued = false; + } + } + } + + /** + * @private + * Сортировка операций, если массив operations пуст, будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortOperations(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:Operation; + var mid:uint = operations[(r + l) >> 1].priority; + var right:Operation; + do { + while ((left = operations[i]).priority < mid) {i++}; + while (mid < (right = operations[j]).priority) {j--}; + if (i <= j) { + operations[i++] = right; + operations[j--] = left; + } + } while (i <= j) + if (l < j) { + sortOperations(l, j); + } + if (i < r) { + sortOperations(i, r); + } + } + + /** + * @private + * Добавление операции в список + * + * @param operation добавляемая операция + */ + alternativa3d function addOperation(operation:Operation):void { + if (!operation.queued) { + operations.push(operation); + operation.queued = true; + } + } + + /** + * @private + * удаление операции из списка + * + * @param operation удаляемая операция + */ + alternativa3d function removeOperation(operation:Operation):void { + if (operation.queued) { + operations[operations.indexOf(operation)] = dummyOperation; + operation.queued = false; + } + } + + /** + * @private + * Расчёт изменений в BSP-дереве. + * Обработка удалённых и добавленных примитивов. + */ + protected function calculateBSP():void { + if (updateSplittersOperation.queued || updateBSPOperation.queued) { + // Удаление списка нод, помеченных на удаление + removeNodes.clear(); + + // Удаление BSP-дерева, перенос примитивов в список дочерних + childBSP(bsp); + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + // Встраиваем сплиттеры в БСП дерево + var count:int = _splitters.length; + if (count > 0) { + var splitter:Splitter; + // Если корневого нода ещё нет, создаём + splitter = _splitters[0]; + bsp = BSPNode.create(splitter.primitive); + for (var i:int = 1; i < count; i++) { + splitter = splitters[i]; + addBSP(bsp, splitter.primitive); + } + } else { + // Нет сплиттеров + bsp = null; + } + } else { + var key:*; + var primitive:PolyPrimitive; + + // Удаляем ноды из дерева + if (!removeNodes.isEmpty()) { + var node:BSPNode; + while ((node = removeNodes.peek()) != null) { + // Ищем верхнюю удаляемую ноду + var removeNode:BSPNode = node; + while ((node = node.parent) != null) { + if (removeNodes[node]) { + removeNode = node; + } + } + + // Удаляем ветку + var parent:BSPNode = removeNode.parent; + var replace:BSPNode = removeBSPNode(removeNode); + + // Если вернулась вспомогательная нода, игнорируем её + if (replace == dummyNode) { + replace = null; + } + + // Если есть родительская нода + if (parent != null) { + // Заменяем себя на указанную ноду + if (parent.front == removeNode) { + parent.front = replace; + } else { + parent.back = replace; + } + } else { + // Если нет родительской ноды, значит заменяем корень на указанную ноду + bsp = replace; + } + + // Устанавливаем связь с родителем для заменённой ноды + if (replace != null) { + replace.parent = parent; + } + } + + // Собираем дочерние примитивы в список на добавление + assembleChildPrimitives(); + } + } + + // Если есть примитивы на добавление + if (addPrimitives[0] != undefined) { + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Если корневого нода ещё нет, создаём + if (bsp == null) { + primitive = addPrimitives.pop(); + bsp = BSPNode.create(primitive); + changedPrimitives[primitive] = true; + } + + // Встраиваем примитивы в дерево + while ((primitive = addPrimitives.pop()) != null) { + addBSP(bsp, primitive); + } + } + } + + /** + * @private + * Сортировка граней, если массив addPrimitives пуст будет ошибка + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitives(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:PolyPrimitive = addPrimitives[(r + l) >> 1]; + var midMobility:int = mid.mobility; + var midSplitQuality:Number = mid.splitQuality; + var right:PolyPrimitive; + do { + while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++}; + while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitives(l, j); + } + if (i < r) { + sortPrimitives(i, r); + } + } + + /** + * @private + * Сортировка только по мобильности + * + * @param l начальный элемент + * @param r конечный элемент + */ + alternativa3d function sortPrimitivesByMobility(l:int, r:int):void { + var i:int = l; + var j:int = r; + var left:PolyPrimitive; + var mid:int = addPrimitives[(r + l) >> 1].mobility; + var right:PolyPrimitive; + do { + while ((left = addPrimitives[i]).mobility > mid) {i++}; + while (mid > (right = addPrimitives[j]).mobility) {j--}; + if (i <= j) { + addPrimitives[i++] = right; + addPrimitives[j--] = left; + } + } while (i <= j) + if (l < j) { + sortPrimitivesByMobility(l, j); + } + if (i < r) { + sortPrimitivesByMobility(i, r); + } + } + + /** + * @private + * Анализ качества сплиттеров + */ + private function analyseSplitQuality():void { + // Перебираем примитивы на добавление + var i:uint; + var length:uint = addPrimitives.length; + var maxSplits:uint = 0; + var maxDisbalance:uint = 0; + var splitter:PolyPrimitive; + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + if (splitter.face == null) { + // Пропускаем спрайтовые примитивы + continue; + } + splitter.splits = 0; + splitter.disbalance = 0; + var normal:Point3D = splitter.face.globalNormal; + var offset:Number = splitter.face.globalOffset; + // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление + for (var j:uint = 0; j < length; j++) { + if (i != j) { + var primitive:PolyPrimitive = addPrimitives[j]; + if (primitive.face == null) { + // Пропускаем спрайтовые примитивы + continue; + } + if (splitter.mobility <= primitive.mobility) { + // Проверяем наличие точек спереди и сзади сплиттера + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + for (var k:uint = 0; k < primitive.num; k++) { + var point:Point3D = primitive.points[k]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset; + if (pointOffset > _planeOffsetThreshold) { + if (!pointsFront) { + splitter.disbalance++; + pointsFront = true; + } + if (pointsBack) { + splitter.splits++; + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + if (!pointsBack) { + splitter.disbalance--; + pointsBack = true; + } + if (pointsFront) { + splitter.splits++; + break; + } + } + } + } + } + } + } + // Абсолютное значение дисбаланса + splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance; + // Ищем максимальное количество рассечений и значение дисбаланса + maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits; + maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance; + } + // Расчитываем качество сплиттеров + for (i = 0; i < length; i++) { + splitter = addPrimitives[i]; + splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance; + } + } + + /** + * @private + * Добавление примитива в BSP-дерево + * + * @param node текущий узел дерева, в который добавляется примитив + * @param primitive добавляемый примитив + */ + protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void { + var point:Point3D; + var normal:Point3D; + var key:*; + + // Сравниваем мобильности ноды и примитива + if (primitive.mobility < node.mobility || (node.isSprite && primitive.face != null)) { + // Мобильность примитива ниже мобильности ноды + // Или мобильность примитива равна мобильности спрайтовой ноды + + // Формируем список содержимого ноды и всех примитивов ниже + if (node.primitive != null) { + childPrimitives[node.primitive] = true; + changedPrimitives[node.primitive] = true; + node.primitive.node = null; + } else { + var p:PolyPrimitive; + for (key in node.backPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + for (key in node.frontPrimitives) { + p = key; + childPrimitives[p] = true; + changedPrimitives[p] = true; + p.node = null; + } + } + childBSP(node.back); + childBSP(node.front); + + // Собираем дочерние примитивы в список нижних + assembleChildPrimitives(); + + // Если включен анализ сплиттеров + if (_splitAnalysis) { + // Рассчитываем качество рассечения примитивов + analyseSplitQuality(); + // Сортируем массив примитивов c учётом качества + sortPrimitives(0, addPrimitives.length - 1); + } else { + // Сортируем массив по мобильности + sortPrimitivesByMobility(0, addPrimitives.length - 1); + } + + // Добавляем примитив в ноду + node.primitive = primitive; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Сохраняем ноду + primitive.node = node; + + // Сохраняем плоскость + node.normal.copy(primitive.face.globalNormal); + node.offset = primitive.face.globalOffset; + node.isSprite = false; + + // Сохраняем мобильность + node.mobility = primitive.mobility; + + // Чистим списки примитивов + node.backPrimitives = null; + node.frontPrimitives = null; + + // Удаляем дочерние ноды + node.back = null; + node.front = null; + } else { + // Получаем нормаль из ноды + normal = node.normal; + + var points:Array = primitive.points; + + // Собирательные флаги + var pointsFront:Boolean = false; + var pointsBack:Boolean = false; + + // Собираем расстояния точек до плоскости + for (var i:uint = 0; i < primitive.num; i++) { + point = points[i]; + var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + if (pointOffset > _planeOffsetThreshold) { + pointsFront = true; + if (pointsBack) { + break; + } + } else { + if (pointOffset < -_planeOffsetThreshold) { + pointsBack = true; + if (pointsFront) { + break; + } + } + } + } + + if (node.splitter != null && !pointsFront && !pointsBack) { + // Примитив в сплиттеровой ноде + if (primitive.face == null) { + // Сплиттер или спрайт + var sprimitive:SplitterPrimitive = primitive as SplitterPrimitive; + if (sprimitive != null) { + // Сплиттер + if (Point3D.dot(normal, sprimitive.splitter.normal) > 0) { + pointsFront = true; + } else { + pointsBack = true; + } + } else { + // Спрайт + pointsFront = true; + } + } else { + // Обычный примитив + if (Point3D.dot(normal, primitive.face.globalNormal) > 0) { + pointsFront = true; + } else { + pointsBack = true; + } + } + } + + // Если все точки в плоскости или это добавление спрайтовой точки в спрайтовую ноду + if (!pointsFront && !pointsBack && (primitive.face != null || node.isSprite)) { + // Сохраняем ноду + primitive.node = node; + + // Если был только базовый примитив, переносим его в список + if (node.primitive != null) { + node.frontPrimitives = new Set(true); + node.frontPrimitives[node.primitive] = true; + node.primitive = null; + } + + // Если примитив спрайтовый или нормаль полигона сонаправлена с нормалью ноды + if (primitive.face == null || Point3D.dot(primitive.face.globalNormal, normal) > 0) { + node.frontPrimitives[primitive] = true; + } else { + if (node.backPrimitives == null) { + node.backPrimitives = new Set(true); + } + node.backPrimitives[primitive] = true; + } + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + } else { + if (!pointsBack) { + // Примитив спереди плоскости ноды + if (node.front == null) { + // Создаём переднюю ноду + node.front = BSPNode.create(primitive); + node.front.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в переднюю ноду + addBSP(node.front, primitive); + } + } else { + if (!pointsFront) { + // Примитив сзади плоскости ноды + if (node.back == null) { + // Создаём заднюю ноду + node.back = BSPNode.create(primitive); + node.back.parent = node; + changedPrimitives[primitive] = true; + } else { + // Добавляем примитив в заднюю ноду + addBSP(node.back, primitive); + } + } else { + // Рассечение + var backFragment:PolyPrimitive = primitive.createFragment(); + var frontFragment:PolyPrimitive = primitive.createFragment(); + + point = points[0]; + var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + var offset1:Number = offset0; + var offset2:Number; + for (i = 0; i < primitive.num; i++) { + var j:uint; + if (i < primitive.num - 1) { + j = i + 1; + point = points[j]; + offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset; + } else { + j = 0; + offset2 = offset0; + } + + if (offset1 > _planeOffsetThreshold) { + // Точка спереди плоскости ноды + frontFragment.points.push(points[i]); + } else { + if (offset1 < -_planeOffsetThreshold) { + // Точка сзади плоскости ноды + backFragment.points.push(points[i]); + } else { + // Рассечение по точке примитива + backFragment.points.push(points[i]); + frontFragment.points.push(points[i]); + } + } + + // Рассечение ребра + if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) { + // Находим точку рассечения + var t:Number = offset1/(offset1 - offset2); + point = Point3D.interpolate(points[i], points[j], t); + backFragment.points.push(point); + frontFragment.points.push(point); + } + + offset1 = offset2; + } + backFragment.num = backFragment.points.length; + frontFragment.num = frontFragment.points.length; + + // Устанавливаем связи рассечённых примитивов + backFragment.parent = primitive; + frontFragment.parent = primitive; + backFragment.sibling = frontFragment; + frontFragment.sibling = backFragment; + primitive.backFragment = backFragment; + primitive.frontFragment = frontFragment; + + // Добавляем фрагменты в дочерние ноды + if (node.back == null) { + node.back = BSPNode.create(backFragment); + node.back.parent = node; + changedPrimitives[backFragment] = true; + } else { + addBSP(node.back, backFragment); + } + if (node.front == null) { + node.front = BSPNode.create(frontFragment); + node.front.parent = node; + changedPrimitives[frontFragment] = true; + } else { + addBSP(node.front, frontFragment); + } + } + } + } + } + } + + /** + * @private + * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления. + * + * @param node удаляемый узел + * @return корневой узел поддерева, оставшегося после операции удаления + */ + protected function removeBSPNode(node:BSPNode):BSPNode { + var replaceNode:BSPNode; + if (node != null) { + // Удаляем дочерние + node.back = removeBSPNode(node.back); + node.front = removeBSPNode(node.front); + + if (!removeNodes[node]) { + // Если нода не удаляется, возвращает себя + replaceNode = node; + + // Проверяем дочерние ноды + if (node.back != null) { + if (node.back != dummyNode) { + node.back.parent = node; + } else { + node.back = null; + } + } + if (node.front != null) { + if (node.front != dummyNode) { + node.front.parent = node; + } else { + node.front = null; + } + } + } else { + // Проверяем дочерние ветки + if (node.back == null) { + if (node.front != null) { + // Есть только передняя ветка + replaceNode = node.front; + node.front = null; + } + } else { + if (node.front == null) { + // Есть только задняя ветка + replaceNode = node.back; + node.back = null; + } else { + // Есть обе ветки - собираем дочерние примитивы + childBSP(node.back); + childBSP(node.front); + // Используем вспомогательную ноду + replaceNode = dummyNode; + // Удаляем связи с дочерними нодами + node.back = null; + node.front = null; + } + } + + // Удаляем ноду из списка на удаление + delete removeNodes[node]; + // Удаляем ноду + node.parent = null; + BSPNode.destroy(node); + } + } + return replaceNode; + } + + /** + * @private + * Удаление примитива из узла дерева + * + * @param primitive удаляемый примитив + */ + alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void { + var node:BSPNode = primitive.node; + primitive.node = null; + + var single:Boolean = false; + var key:*; + + // Пометка об изменении примитива + changedPrimitives[primitive] = true; + + // Если нода единичная + if (node.primitive == primitive) { + removeNodes[node] = true; + node.primitive = null; + } else { + // Есть передние примитивы + if (node.frontPrimitives[primitive]) { + // Удаляем примитив спереди + delete node.frontPrimitives[primitive]; + + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + if (key == null) { + // Передняя пуста или не спрайтовая нода, значит сзади кто-то есть + + // Переворачиваем дочерние ноды + var t:BSPNode = node.back; + node.back = node.front; + node.front = t; + + // Переворачиваем плоскость ноды + node.normal.invert(); + node.offset = -node.offset; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если сзади один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Если сзади несколько примитивов, переносим их в передние + node.frontPrimitives = node.backPrimitives; + // Пересчитываем мобильность ноды по передним примитивам + // Присваивается наименьшая мобильность из всех примитивов + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + + } else { + // Если остался один примитив и сзади примитивов нет + if (single && node.backPrimitives == null) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } else { + // Удаляем примитив сзади + delete node.backPrimitives[primitive]; + + // Проверяем количество примитивов сзади + for (key in node.backPrimitives) { + break; + } + + // Если сзади примитивов больше нет + if (key == null) { + // Проверяем количество примитивов спереди + for (key in node.frontPrimitives) { + if (single) { + single = false; + break; + } + single = true; + } + + // Если спереди один примитив + if (single) { + // Устанавливаем базовый примитив ноды + node.primitive = key; + // Устанавливаем мобильность + node.mobility = node.primitive.mobility; + // Стираем список передних примитивов + node.frontPrimitives = null; + } else { + // Пересчитываем мобильность ноды по передним примитивам + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + + // Стираем список задних примитивов + node.backPrimitives = null; + } else { + // Пересчитываем мобильность ноды + if (primitive.mobility == node.mobility) { + node.mobility = int.MAX_VALUE; + for (key in node.backPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + for (key in node.frontPrimitives) { + primitive = key; + node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility; + } + } + } + } + } + } + + /** + * @private + * Удаление и перевставка ветки + * + * @param node + */ + protected function childBSP(node:BSPNode):void { + if (node != null && node != dummyNode) { + var primitive:PolyPrimitive = node.primitive; + if (primitive != null) { + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + node.primitive = null; + primitive.node = null; + } else { + for (var key:* in node.backPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + for (key in node.frontPrimitives) { + primitive = key; + childPrimitives[primitive] = true; + changedPrimitives[primitive] = true; + primitive.node = null; + } + node.backPrimitives = null; + node.frontPrimitives = null; + } + childBSP(node.back); + childBSP(node.front); + // Удаляем ноду + node.parent = null; + node.back = null; + node.front = null; + BSPNode.destroy(node); + } + } + + /** + * @private + * Сборка списка дочерних примитивов в коллектор + */ + protected function assembleChildPrimitives():void { + var primitive:PolyPrimitive; + while ((primitive = childPrimitives.take()) != null) { + assemblePrimitive(primitive); + } + } + + /** + * @private + * Сборка примитивов и разделение на добавленные и удалённые + * + * @param primitive + */ + private function assemblePrimitive(primitive:PolyPrimitive):void { + // Если есть соседний примитив и он может быть собран + if (primitive.sibling != null && canAssemble(primitive.sibling)) { + // Собираем их в родительский + assemblePrimitive(primitive.parent); + // Зачищаем связи между примитивами + primitive.sibling.sibling = null; + primitive.sibling.parent = null; + PolyPrimitive.destroy(primitive.sibling); + primitive.sibling = null; + primitive.parent.backFragment = null; + primitive.parent.frontFragment = null; + primitive.parent = null; + PolyPrimitive.destroy(primitive); + } else { + // Если собраться не получилось или родительский + addPrimitives.push(primitive); + } + } + + /** + * @private + * Проверка, может ли примитив в списке дочерних быть собран + * + * @param primitive + * @return + */ + private function canAssemble(primitive:PolyPrimitive):Boolean { + if (childPrimitives[primitive]) { + delete childPrimitives[primitive]; + return true; + } else { + var backFragment:PolyPrimitive = primitive.backFragment; + var frontFragment:PolyPrimitive = primitive.frontFragment; + if (backFragment != null) { + var assembleBack:Boolean = canAssemble(backFragment); + var assembleFront:Boolean = canAssemble(frontFragment); + if (assembleBack && assembleFront) { + backFragment.parent = null; + frontFragment.parent = null; + backFragment.sibling = null; + frontFragment.sibling = null; + primitive.backFragment = null; + primitive.frontFragment = null; + PolyPrimitive.destroy(backFragment); + PolyPrimitive.destroy(frontFragment); + return true; + } else { + if (assembleBack) { + addPrimitives.push(backFragment); + } + if (assembleFront) { + addPrimitives.push(frontFragment); + } + } + } + } + return false; + } + + /** + * @private + * Очистка списка измененных примитивов + */ + private function clearPrimitives():void { + changedPrimitives.clear(); + } + + /** + * Проверка наличия изменений в сцене. + * + * @return true, если в сцене были изменения с предыдущего вызова метода calculate + * + * @see #calculate() + */ + public function hasChanges():Boolean { + var len:int = operations.length; + for (var i:int = 0; i < len; i++) { + if (operations[i] != dummyOperation) { + return true; + } + } + return false; + } + + /** + * Корневой объект сцены. + */ + public function get root():Object3D { + return _root; + } + + /** + * @private + */ + public function set root(value:Object3D):void { + // Если ещё не является корневым объектом + if (_root != value) { + // Если устанавливаем не пустой объект + if (value != null) { + // Если объект был в другом объекте + if (value._parent != null) { + // Удалить его оттуда + value._parent._children.remove(value); + } else { + // Если объект был корневым в сцене + if (value._scene != null && value._scene._root == value) { + value._scene.root = null; + } + } + // Удаляем ссылку на родителя + value.setParent(null); + // Указываем сцену + value.setScene(this); + // Устанавливаем уровни + value.setLevel(0); + } + + // Если был корневой объект + if (_root != null) { + // Удаляем ссылку на родителя + _root.setParent(null); + // Удаляем ссылку на камеру + _root.setScene(null); + } + + // Сохраняем корневой объект + _root = value; + } + } + + /** + * Список сплиттеров сцены. Сплиттеры предназначены для разделения пространства сцены + * на части. Порядок разделения сцены сплиттерами определяется порядком расположения + * объекта сплиттера в массиве. + * + * @see Splitter + * + * @throws alternativa.engine3d.errors.SplitterInOtherSceneError сплиттер уже расположен в другой сцене. + */ + public function get splitters():Array { + return new Array().concat(_splitters); + } + + /** + * @private + */ + public function set splitters(value:Array):void { + // Убираем сплиттеры из сцены + var splitter:Splitter; + for each (splitter in _splitters) { + splitter.removeFromScene(this); + } + if (value != null) { + var count:int = value.length; + for (var i:int = 0; i < count; i++) { + splitter = value[i]; + if (splitter._scene == null) { + splitter.addToScene(this); + _splitters[i] = splitter; + } else { + _splitters.length = i; + addOperation(updateSplittersOperation); + throw new SplitterInOtherSceneError(splitter, this); + } + } + _splitters.length = count; + } else { + _splitters.length = 0; + } + addOperation(updateSplittersOperation); + } + + /** + * Сектора сцены. Сектора задают видимость между образованными сплиттерами частями сцены. + * + * @see Sector + * + * @throws alternativa.engine3d.errors.SectorInOtherSceneError сектор уже расположен в другой сцене. + */ + public function get sectors():Array { + return new Array().concat(_sectors); + } + + /** + * @private + */ + public function set sectors(value:Array):void { + // Убираем полупространства из сцены + var sector:Sector; + for each (sector in _sectors) { + sector.removeFromScene(this); + } + if (value != null) { + var count:int = value.length; + for (var i:int = 0; i < count; i++) { + sector = value[i]; + if (sector._scene == null) { + sector.addToScene(this); + sector.setLevel(i); + _sectors[i] = sector; + } else { + _sectors.length = i; + throw new SectorInOtherSceneError(sector, this); + } + } + _sectors.length = count; + } else { + _sectors.length = 0; + } + } + + /** + * Флаг активности анализа сплиттеров. + * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей + * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми. + * + *

Изменением свойства splitBalance можно влиять на конечный вид BSP-дерева.

+ * + * @see #splitBalance + * @default true + */ + public function get splitAnalysis():Boolean { + return _splitAnalysis; + } + + /** + * @private + */ + public function set splitAnalysis(value:Boolean):void { + if (_splitAnalysis != value) { + _splitAnalysis = value; + addOperation(updateBSPOperation); + } + } + + /** + * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров. + * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева). + * + * @see #splitAnalysis + * @default 0 + */ + public function get splitBalance():Number { + return _splitBalance; + } + + /** + * @private + */ + public function set splitBalance(value:Number):void { + value = (value < 0) ? 0 : ((value > 1) ? 1 : value); + if (_splitBalance != value) { + _splitBalance = value; + if (_splitAnalysis) { + addOperation(updateBSPOperation); + } + } + } + + /** + * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold. + * + * @default 0.01 + */ + public function get planeOffsetThreshold():Number { + return _planeOffsetThreshold; + } + + /** + * @private + */ + public function set planeOffsetThreshold(value:Number):void { + value = (value < 0) ? 0 : value; + if (_planeOffsetThreshold != value) { + _planeOffsetThreshold = value; + addOperation(updateBSPOperation); + } + } + + /** + * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей + * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого + * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу. + * + * @param container контейнер для отрисовки дерева + */ + public function drawBSP(container:Sprite):void { + + container.graphics.clear(); + while (container.numChildren > 0) { + container.removeChildAt(0); + } + if (bsp != null) { + drawBSPNode(bsp, container, 0, 0, 1); + } + } + + /** + * @private + * Отрисовка узла BSP-дерева при визуализации + * + * @param node + * @param container + * @param x + * @param y + * @param size + */ + private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void { + var s:Shape = new Shape(); + container.addChild(s); + s.x = x; + s.y = y; + var color:uint = 0xFF0000; + if (node.splitter != null) { + // Сплиттеровая нода + if (node.splitter._open) { + color = 0xFF00; + } else { + color = 0xFFF000; + } + s.graphics.beginFill(color); + s.graphics.moveTo(-4, 0); + s.graphics.lineTo(0, -4); + s.graphics.lineTo(4, 0); + s.graphics.lineTo(0, 4); + s.graphics.endFill(); + } else { + var sprimitive:SpritePrimitive; + if (node.isSprite) { + if (node.primitive != null) { + sprimitive = node.primitive as SpritePrimitive; + } else { + if (node.frontPrimitives != null) { + sprimitive = node.frontPrimitives.peek(); + } + } + if (sprimitive != null) { + var material:SpriteTextureMaterial = sprimitive.sprite._material as SpriteTextureMaterial; + if (material != null && material._texture != null) { + color = material._texture._bitmapData.getPixel(material._texture._bitmapData.width >> 1, material._texture._bitmapData.height >> 1); + } + } + s.graphics.beginFill(color); + s.graphics.drawRect(0, 0, 5, 5); + s.graphics.endFill(); + } else { + var primitive:PolyPrimitive; + if (node.primitive != null) { + primitive = node.primitive; + } else { + if (node.frontPrimitives != null) { + primitive = node.frontPrimitives.peek(); + } + } + if (primitive != null) { + if (primitive.face._surface != null && primitive.face._surface._material != null) { + if (primitive.face._surface._material is FillMaterial) { + color = FillMaterial(primitive.face._surface._material)._color; + } + if (primitive.face._surface._material is WireMaterial) { + color = WireMaterial(primitive.face._surface._material)._color; + } + if ((primitive.face._surface._material is TextureMaterial) && TextureMaterial(primitive.face._surface._material)._texture != null) { + color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0); + } + } + } + if (node == dummyNode) { + color = 0xFF00FF; + } + s.graphics.beginFill(color); + s.graphics.drawCircle(0, 0, 3); + s.graphics.endFill(); + } + } + + var xOffset:Number = 100; + var yOffset:Number = 20; + if (node.back != null) { + container.graphics.lineStyle(0, 0x660000); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x - xOffset*size, y + yOffset); + drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8); + } + if (node.front != null) { + container.graphics.lineStyle(0, 0x006600); + container.graphics.moveTo(x, y); + container.graphics.lineTo(x + xOffset*size, y + yOffset); + drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8); + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as new file mode 100644 index 0000000..2a47f23 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as @@ -0,0 +1,321 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Cектор предоставляет механизм управления видимостью частей сцены, который дополняет систему сплиттеров. Каждый сектор находится в какой-то + * из ветвей BSP-дерева, образованных сплиттерами. Также каждый сектор хранит множество других видимых из него секторов. + * + *

Если камера находится в дочерней ветке узла, образованного сплиттером, то видимость соседней относительно сплиттера ветки определяется + * следующим образом: + *

+ *

+ * + *

Сектора задаются в свойстве Scene.sectors.

+ * + * @see Scene3D#sectors + * @see Splitter + */ + public class Sector { + + // Счетчик имен объекта + private static var counter:uint = 0; + + /** + * @private + * Убрать из бсп дерева перед перестроением сплиттеров. + */ + alternativa3d var updateOperation:Operation = new Operation("removeSector", this, removeFromBSP, Operation.SECTOR_UPDATE); + /** + * @private + * Поиск сплиттеровой ноды в БСП дереве. + */ + alternativa3d var findNodeOperation:Operation = new Operation("addSector", this, addToBSP, Operation.SECTOR_FIND_NODE); + /** + * @private + * Изименение видимости. + */ + alternativa3d var changeVisibleOperation:Operation = new Operation("changeSectorVisibility", this, changeVisible, Operation.SECTOR_CHANGE_VISIBLE); + + /** + * @private + * Список видимых секторов. + */ + alternativa3d var _visible:Set = new Set(); + + /** + * @private + * Координата x сектора. + */ + private var x:Number; + /** + * @private + * Координата y сектора. + */ + private var y:Number; + /** + * @private + * Координата z сектора. + */ + private var z:Number; + + /** + * @private + */ + alternativa3d var _scene:Scene3D; + + // Сплиттеровая нода + private var _node:BSPNode; + + /** + * Имя сектора. + */ + public var name:String; + + /** + * Создаёт новый экземпляр сектора. По координатам сектора определяется та дочерняя ветка сплиттера, для которой сектор задает видимость. + * + * @param x координата по оси X + * @param y координата по оси Y + * @param z координата по оси Z + * @param name имя сектора. При указании null используется автоматически сгенерированное имя. + */ + public function Sector(x:Number = 0, y:Number = 0, z:Number = 0, name:String = null) { + this.name = (name != null) ? name : "sector" + ++counter; + this.x = x; + this.y = y; + this.z = z; + _visible[this] = true; + // Обновление в дереве требует перевставки + updateOperation.addSequel(findNodeOperation); + // Обновление в дереве требует перерисовки + findNodeOperation.addSequel(changeVisibleOperation); + } + + /** + * Добавляет сектора в список видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически добавляется + * в список видимых для каждого из указанных секторов. + * + * @param sector сектор, добавляемый в список видимых + * @param sectors дополнительные сектора, добавляемые в список видимых + * + * @see #removeVisible() + */ + public function addVisible(sector:Sector, ...sectors):void { + sector._visible[this] = true; + _visible[sector] = true; + sector.markToChange(); + var count:int = sectors.length; + for (var i:int = 0; i < count; i++) { + var sc:Sector = sectors[i]; + sc._visible[this] = true; + _visible[sc] = true; + sc.markToChange(); + } + markToChange(); + } + + /** + * Удаляет сектора из списка видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически удаляется из списка видимости указанных секторов. + * + * @param sector сектор, удаляемый из списка видимости + * @param sectors дополнительные сектора, удаляемые из списка видимых + * + * @see #addVisible() + */ + public function removeVisible(sector:Sector, ...sectors):void { + if (_visible[sector] && sector != this) { + delete sector._visible[this]; + sector.markToChange(); + delete _visible[sector]; + markToChange(); + } + var count:int = sectors.length; + for (var i:int = 0; i < count; i++) { + var sc:Sector = sectors[i]; + if (_visible[sc] && sc != this) { + delete sc._visible[this]; + sc.markToChange(); + delete _visible[sc]; + markToChange(); + } + } + } + + /** + * Определяет потенциальную видимость заданного сектора. + * + * @param sector сектор, видимость которого проверяется + * @return true, если указанный сектор находится в списке видимости текущего, иначе false + */ + public function isVisible(sector:Sector):Boolean { + return _visible[sector]; + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var s:String = "[Sector " + name + " X:" + x.toFixed(3) + " Y:" + y.toFixed(3) + " Z:" + z.toFixed(3); + var sector:Sector; + var v:String = ""; + for (var sc:* in _visible) { + if (sc != this) { + if (sector == null) { + sector = sc; + v = sector.name; + } else { + sector = sc; + v += " " + sector.name; + } + } + } + return (sector == null) ? s + "]" : s + " visible:[" + v + "]]"; + } + + /** + * @private + */ + alternativa3d function addToScene(scene:Scene3D):void { + _scene = scene; + + // Перестройка сплиттеров перевставляет сектор + scene.updateSplittersOperation.addSequel(updateOperation); + // Изменение видимости вызывает перерисовку + changeVisibleOperation.addSequel(scene.changePrimitivesOperation); + // Поиск ноды + scene.addOperation(findNodeOperation); + } + + /** + * @private + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + scene.updateSplittersOperation.removeSequel(updateOperation); + changeVisibleOperation.removeSequel(scene.changePrimitivesOperation); + + scene.removeOperation(findNodeOperation); + scene.removeOperation(changeVisibleOperation); + + // Убираем сектор из бсп дерева + removeFromBSP(); + + _scene = null; + } + + /** + * @private + * Установка приоритета сектора. Используется для сохранения последовательности добавления в дерево. + */ + alternativa3d function setLevel(level:int):void { + findNodeOperation.priority = (findNodeOperation.priority & 0xFF000000) | level; + } + + /** + * @private + * Послать операцию обновления видимости на сцену + */ + alternativa3d function markToChange():void { + if (_scene != null) { + _scene.addOperation(changeVisibleOperation); + } + } + + /** + * Убирает сектор из БСП дерева. + */ + private function removeFromBSP():void { + if (_node != null) { + if (_node.frontSector == this) { + _node.frontSector = null; + } else { + _node.backSector = null; + } + _node = null; + } + } + + /** + * Поиск сплиттеровой ноды для этого сектора. + */ + private function addToBSP():void { + findSectorNode(_scene.bsp); + } + + /** + * Рекурсивный поиск ноды сектора. + */ + private function findSectorNode(node:BSPNode):void { + if (node != null && node.splitter != null) { + var normal:Point3D = node.normal; + if (x*normal.x + y*normal.y + z*normal.z - node.offset >= 0) { + if (node.front == null || node.front.splitter == null) { + if (node.frontSector == null) { + node.frontSector = this; + _node = node; + } + } else { + findSectorNode(node.front); + } + } else { + if (node.back == null || node.back.splitter == null) { + if (node.backSector == null) { + node.backSector = this; + _node = node; + } + } else { + findSectorNode(node.back); + } + } + } + } + + /** + * Отправляет примитивы текущего сектора на перерисовку. + */ + private function changeVisible():void { + if (_node != null) { + var primitive:*; + if (_node.frontSector == this) { + changeNode(_node.front); + } else { + changeNode(_node.back); + } + } + } + + /** + * Отправляет на перерисовку ветку бсп дерева. + */ + private function changeNode(node:BSPNode):void { + if (node != null) { + if (node.primitive != null) { + _scene.changedPrimitives[node.primitive] = true; + } else { + var primitive:*; + for (primitive in node.frontPrimitives) { + _scene.changedPrimitives[primitive] = true; + } + for (primitive in node.backPrimitives) { + _scene.changedPrimitives[primitive] = true; + } + } + changeNode(node.back); + changeNode(node.front); + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as new file mode 100644 index 0000000..175d39e --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as @@ -0,0 +1,307 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.errors.SplitterNeedMoreVerticesError; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Сплиттеры применяются для управления видимостью отдельных частей сцены. Сплиттеры находятся ближе к корню BSP-дерева, чем обычные полигоны. + * Таким образом, они образуют систему ветвей, каждая из которых может быть скрыта от своего соседа. + * + *

Например, пусть в сцене задан единственный сплиттер, а камера находится в левой ветке. При открытом сплиттере камера будет + * иметь возможность видеть содержимое правой ветки. Если же сплиттер закрыт, то содержимое правой ветки дерева не будет отображаться совсем.

+ * + *

Глубина сплиттеров в сцене определяется порядком их расположения в массиве при задании свойства Scene3D.splitters. Каждый + * последующий сплиттер разделяет на части пространства, образованные предыдущими сплиттерами. При этом он, как и обычный полигон, может быть + * поделён на несколько частей в процессе встраивания в BSP-дерево.

+ * + *

Для управления видимостью ветвей дерева, образованных системой сплиттеров, дополнительно могут быть использованы объекты класса + * Sector.

+ * + * @see Scene3D#splitters + * @see Sector + */ + public class Splitter { + + // Счетчик имен объекта + private static var counter:uint = 0; + + /** + * Создает сплиттер из грани. Если объект, которому принадлежит грань, находится на сцене, + * используются глобальные координаты вершин в сцене, иначе используются локальные координаты вершин грани. + * + * @param face грань, которая будет использована для создания сплиттера + * @param name имя нового экземпляра сплиттера. Если указано значение null, имя будет выбрано автоматически. + */ + public static function createFromFace(face:Face, name:String = null):Splitter { + var src:Array = face._vertices; + var dest:Array = new Array(); + var i:int; + if (face._mesh != null && face._mesh._scene != null) { + var m:Matrix3D = Object3D.matrix2; + face._mesh.getTransformation(m); + for (i = 0; i < face._verticesCount; i++) { + var p:Point3D = Vertex(src[i])._coords.clone(); + p.transform(m); + dest[i] = p; + } + } else { + for (i = 0; i < face._verticesCount; i++) { + dest[i] = Vertex(src[i])._coords; + } + } + return new Splitter(dest, name); + } + + /** + * @private + * Изменение состояния сплиттера в БСП дереве. + */ + alternativa3d var changeStateOperation:Operation = new Operation("changeSplitterState", this, changeState, Operation.SPLITTER_CHANGE_STATE); + /** + * @private + * Обновление примитива в сцене. + */ + alternativa3d var updatePrimitiveOperation:Operation = new Operation("updateSplitter", this, updatePrimitive, Operation.SPLITTER_UPDATE); + + /** + * @private + * Состояние + */ + alternativa3d var _open:Boolean = true; + + /** + * @private + * Примитив + */ + alternativa3d var primitive:SplitterPrimitive; + + /** + * @private + * Нормаль + */ + alternativa3d var normal:Point3D = new Point3D(); + + /** + * @private + * Оффсет + */ + alternativa3d var offset:Number; + + /** + * @private + * Сцена + */ + alternativa3d var _scene:Scene3D; + + /** + * Имя объекта. + */ + public var name:String; + + /** + * Создает экземпляр сплиттера. + * + * @param vertices массив координат вершин сплиттера. Плоскость, в которой расположены вершины, + * будет использована в качестве плоскости сплиттера. + * + * @param name имя объекта. Если указано значение null, имя будет выбрано автоматически. + * + * @throws alternativa.engine3d.errors.SplitterNeedMoreVerticesError для создания сплиттера было передано менее трех точек. + */ + public function Splitter(vertices:Array, name:String = null) { + var count:int = vertices.length; + if (count < 3) { + throw new SplitterNeedMoreVerticesError(count); + } + primitive = SplitterPrimitive.create(); + primitive.mobility = int.MIN_VALUE; + primitive.splitter = this; + for (var i:int = 0; i < count; i++) { + primitive.points[i] = Point3D(vertices[i]).clone(); + } + primitive.num = count; + calculatePlane(); + + this.name = (name != null) ? name : "splitter" + ++counter; + } + + /** + * Список вершин сплиттера. Элементами являются объекты класса Point3D. + */ + public function get vertices():Array { + var res:Array = new Array().concat(primitive.points); + for (var i:int = 0; i < primitive.num; i++) { + res[i] = Point3D(res[i]).clone(); + } + return res; + } + + /** + * Состояние сплиттера. При закрытом состоянии сплиттера в камере не рисуются части сцены, + * расположенные в соседней ветке относительно данного сплиттера. + */ + public function get open():Boolean { + return _open; + } + + /** + * @private + */ + public function set open(value:Boolean):void { + if (_open != value) { + _open = value; + if (_scene != null) { + _scene.addOperation(changeStateOperation); + } + } + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[Splitter " + name + ((_open) ? " open]" : " closed]"); + } + + /** + * @private + * Добавление на сцену + */ + alternativa3d function addToScene(scene:Scene3D):void { + _scene = scene + + // Изменение состояния вызывает перерисовку + changeStateOperation.addSequel(_scene.changePrimitivesOperation); + // Обновление сплиттеров в сцене вызывает перевставку + _scene.updateBSPOperation.addSequel(updatePrimitiveOperation); + } + + /** + * @private + * Удаление из сцены + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + changeStateOperation.removeSequel(scene.changePrimitivesOperation); + scene.updateBSPOperation.removeSequel(updatePrimitiveOperation); + + scene.removeOperation(changeStateOperation); + + // Удаляем примитив сплиттера из сцены + removePrimitive(primitive); + + _scene = null + } + + /** + * @private + * Расчет нормали и оффсета плоскости. + */ + private function calculatePlane():void { + // Вектор AB + var av:Point3D = primitive.points[0]; + var bv:Point3D = primitive.points[1]; + var abx:Number = bv.x - av.x; + var aby:Number = bv.y - av.y; + var abz:Number = bv.z - av.z; + // Вектор AC + var cv:Point3D = primitive.points[2]; + var acx:Number = cv.x - av.x; + var acy:Number = cv.y - av.y; + var acz:Number = cv.z - av.z; + // Перпендикуляр к плоскости + normal.x = acz*aby - acy*abz; + normal.y = acx*abz - acz*abx; + normal.z = acy*abx - acx*aby; + // Нормализация перпендикуляра + normal.normalize(); + offset = av.x*normal.x + av.y*normal.y + av.z*normal.z; + } + + /** + * @private + * Помечает конечные примитивы на удаление. + */ + private function updatePrimitive():void { + removePrimitive(primitive); + } + + /** + * @private + * Отправляет на перерисовку примитивы сплиттеровой ноды. + */ + private function changeState():void { + changePrimitiveNode(primitive); + } + + /** + * @private + * Отправляет на перерисовку ноды сплиттера. + */ + private function changePrimitiveNode(primitive:PolyPrimitive):void { + if (primitive.backFragment == null) { + // Базовый примитив + changePrimitivesInNode(primitive.node.back); + changePrimitivesInNode(primitive.node.front); + } else { + // Примитив попилился на куски + changePrimitiveNode(primitive.backFragment); + changePrimitiveNode(primitive.frontFragment); + } + } + + /** + * @private + * Отправляет на перерисовку ветку бсп дерева. + */ + private function changePrimitivesInNode(node:BSPNode):void { + if (node != null) { + if (node.primitive != null) { + _scene.changedPrimitives[node.primitive] = true; + } else { + var primitive:*; + for (primitive in node.frontPrimitives) { + _scene.changedPrimitives[primitive] = true; + } + for (primitive in node.backPrimitives) { + _scene.changedPrimitives[primitive] = true; + } + } + changePrimitivesInNode(node.back); + changePrimitivesInNode(node.front); + } + } + + /** + * @private + * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены + */ + private function removePrimitive(primitive:PolyPrimitive):void { + if (primitive.backFragment != null) { + // Удаляем куски примитива + removePrimitive(primitive.backFragment); + removePrimitive(primitive.frontFragment); + primitive.backFragment = null; + primitive.frontFragment = null; + } else { + // Если примитив в BSP-дереве + if (primitive.node != null) { + primitive.node.splitter = null; + // Удаление примитива + _scene.removeBSPPrimitive(primitive); + } + } + if (primitive != this.primitive) { + primitive.parent = null; + primitive.sibling = null; + SplitterPrimitive.destroy(primitive as SplitterPrimitive); + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as new file mode 100644 index 0000000..431d749 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as @@ -0,0 +1,61 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * @private + * Сплиттеровый примитив. + */ + public class SplitterPrimitive extends PolyPrimitive { + + /** + * Сплиттер + */ + alternativa3d var splitter:Splitter; + + /** + * @inheritDoc + */ + override alternativa3d function createFragment():PolyPrimitive { + var primitive:SplitterPrimitive = create(); + primitive.splitter = splitter; + primitive.mobility = mobility; + return primitive; + } + + // Хранилище неиспользуемых примитивов + static private var collector:Array = new Array(); + + /** + * @private + * Создать примитив + */ + static alternativa3d function create():SplitterPrimitive { + var primitive:SplitterPrimitive; + if ((primitive = collector.pop()) != null) { + return primitive; + } + return new SplitterPrimitive(); + } + + /** + * @private + * Кладёт примитив в коллектор для последующего реиспользования. + * Ссылка на грань и массивы точек зачищаются в этом методе. + * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода. + * + * Исключение: + * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода. + * + * @param primitive примитив на реиспользование + */ + static alternativa3d function destroy(primitive:SplitterPrimitive):void { + primitive.splitter = null; + primitive.points.length = 0; + collector.push(primitive); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as new file mode 100644 index 0000000..af54c44 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as @@ -0,0 +1,208 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.materials.SpriteMaterial; + + use namespace alternativa3d; + + /** + * Объект представляет собой точку в трёхмерном пространстве. Объекту может быть назначен материал для вывода различных изображений в + * месте его нахождения. + */ + public class Sprite3D extends Object3D { + /** + * Счетчик имен объектов + */ + private static var counter:uint = 0; + + /** + * @private + * Обновление материала. + */ + alternativa3d var updateMaterialOperation:Operation = new Operation("updateSpriteMaterial", this, updateMaterial, Operation.SPRITE_UPDATE_MATERIAL); + /** + * @private + * Примитив. + */ + alternativa3d var primitive:SpritePrimitive; + /** + * @private + * Материал. + */ + alternativa3d var _material:SpriteMaterial; + /** + * @private + * Размер материала. + */ + alternativa3d var _materialScale:Number; + + /** + * Создание экземпляра спрайта. + * + * @param name имя спрайта + */ + public function Sprite3D(name:String = null) { + super(name); + // Создаем примитив спрайта + primitive = new SpritePrimitive(); + primitive.sprite = this; + // В примитиве одна точка - координата спрайта + primitive.points = [this.globalCoords]; + primitive.num = 1; + primitive.mobility = int.MAX_VALUE; + } + + /** + * @private + * Расчет перемещения точки спрайта. + */ + override alternativa3d function calculateTransformation():void { + super.calculateTransformation(); + // Произошло перемещение спрайта, необходимо перевставить точку в БСП + updatePrimitive(); + if (changeRotationOrScaleOperation.queued) { + // Считаем размер материала + // Считается для любого материала, без отдельных операций + var a:Number = _transformation.a; + var b:Number = _transformation.b; + var c:Number = _transformation.c; + var e:Number = _transformation.e; + var f:Number = _transformation.f; + var g:Number = _transformation.g; + var i:Number = _transformation.i; + var j:Number = _transformation.j; + var k:Number = _transformation.k; + _materialScale = (Math.sqrt(a*a + e*e + i*i) + Math.sqrt(b*b + f*f + j*j) + Math.sqrt(c*c + g*g + k*k))/3; + } + } + + /** + * Перевставка точки спрайта в БСП дереве. + */ + private function updatePrimitive():void { + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаление примитива + _scene.removeBSPPrimitive(primitive); + } + _scene.addPrimitives.push(primitive); + } + + /** + * Перерисовка скинов спрайта. + */ + private function updateMaterial():void { + if (!calculateTransformationOperation.queued) { + _scene.changedPrimitives[primitive] = true; + } + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + // Подписываем сцену на операции + calculateTransformationOperation.addSequel(scene.calculateBSPOperation); + updateMaterialOperation.addSequel(scene.changePrimitivesOperation); + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(updateMaterialOperation); + + // Если примитив в BSP-дереве + if (primitive.node != null) { + // Удаляем примитив из сцены + scene.removeBSPPrimitive(primitive); + } + + // Посылаем операцию сцены на расчёт BSP + scene.addOperation(scene.calculateBSPOperation); + + // Отписываем сцену от операций + calculateTransformationOperation.removeSequel(scene.calculateBSPOperation); + updateMaterialOperation.removeSequel(scene.changePrimitivesOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + super.removeFromScene(scene); + } + + /** + * @private + * Изменение материала. + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_scene != null) { + _scene.addOperation(updateMaterialOperation); + } + } + + /** + * Материал для отображения спрайта. + */ + public function get material():SpriteMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SpriteMaterial):void { + if (_material != value) { + if (_material != null) { + _material.removeFromSprite(this); + if (_scene != null) { + _material.removeFromScene(_scene); + } + } + if (value != null) { + if (value._sprite != null) { + value._sprite.material = null; + } + value.addToSprite(this); + if (_scene != null) { + value.addToScene(_scene); + } + } + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Имя объекта по умолчанию. + * + * @return имя объекта по умолчанию + */ + override protected function defaultName():String { + return "sprite" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sprite3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + material = (source as Sprite3D).material.clone() as SpriteMaterial; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as new file mode 100644 index 0000000..6d143c1 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as @@ -0,0 +1,33 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + + use namespace alternativa3d; + + /** + * @private + * Спрайтовый примитив. + */ + public class SpritePrimitive extends PolyPrimitive { + + /** + * @private + * Спрайт, которому принадлежит примитив. + */ + alternativa3d var sprite:Sprite3D; + /** + * @private + * Параметр используется для сортировки примитивов в камере. + */ + alternativa3d var screenDepth:Number; + + /** + * @private + * Строковое представление объекта. + */ + override public function toString():String { + return "[SpritePrimitive " + sprite.toString() + "]"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as new file mode 100644 index 0000000..6147199 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as @@ -0,0 +1,479 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.engine3d.errors.FaceExistsError; + import alternativa.engine3d.errors.FaceNotFoundError; + import alternativa.engine3d.errors.InvalidIDError; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.types.Set; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же поверхностью. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на поверхность. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с поверхности. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над поверхностью. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")] + /** + * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов, + * визуализирующих грани объекта. + * + *

Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию + * о точке в трёхмерном пространстве, в которой произошло событие.

+ */ + public class Surface implements IEventDispatcher { + // Операции + /** + * @private + * Изменение набора граней + */ + alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this); + /** + * @private + * Изменение материала + */ + alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Материал + */ + alternativa3d var _material:SurfaceMaterial; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + + /** + * Флаг указывает, будет ли объект принимать мышиные события. + */ + public var mouseEnabled:Boolean = true; + /** + * Диспетчер событий. + */ + private var dispatcher:EventDispatcher; + + /** + * Создание экземпляра поверхности. + */ + public function Surface() {} + + /** + * Добавление грани в поверхность. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань + * + * @see Face + */ + public function addFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (_faces.has(f)) { + // Если грань уже в поверхности + throw new FaceExistsError(f, this); + } + + // Проверяем грань на нахождение в другой поверхности + if (f._surface != null) { + // Удаляем её из той поверхности + f._surface._faces.remove(f); + f.removeFromSurface(f._surface); + } + + // Добавляем грань в поверхность + _faces.add(f); + f.addToSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Удаление грани из поверхности. + * + * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта + * + * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань + * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора + * + * @see Face + */ + public function removeFace(face:Object):void { + var byLink:Boolean = face is Face; + + // Проверяем на нахождение поверхности в меше + if (_mesh == null) { + throw new FaceNotFoundError(face, this); + } + + // Проверяем на null + if (face == null) { + throw new FaceNotFoundError(null, this); + } + + // Проверяем наличие грани в меше + if (byLink) { + // Если удаляем по ссылке + if (Face(face)._mesh != _mesh) { + // Если грань не в меше + throw new FaceNotFoundError(face, this); + } + } else { + // Если удаляем по ID + if (_mesh._faces[face] == undefined) { + // Если нет грани с таким ID + throw new FaceNotFoundError(face, this); + } else { + if (!(_mesh._faces[face] is Face)) { + throw new InvalidIDError(face, this); + } + } + + } + + // Находим грань + var f:Face = byLink ? Face(face) : _mesh._faces[face]; + + // Проверяем наличие грани в поверхности + if (!_faces.has(f)) { + // Если грань не в поверхности + throw new FaceNotFoundError(f, this); + } + + // Удаляем грань из поверхности + _faces.remove(f); + f.removeFromSurface(this); + + // Отправляем операцию изменения набора граней + _mesh.addOperationToScene(changeFacesOperation); + } + + /** + * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности. + */ + public function get material():SurfaceMaterial { + return _material; + } + + /** + * @private + */ + public function set material(value:SurfaceMaterial):void { + if (_material != value) { + // Если был материал + if (_material != null) { + // Удалить материал из поверхности + _material.removeFromSurface(this); + // Удалить материал из меша + if (_mesh != null) { + _material.removeFromMesh(_mesh); + // Удалить материал из сцены + if (_mesh._scene != null) { + _material.removeFromScene(_mesh._scene); + } + } + } + // Если новый материал + if (value != null) { + // Если материал был в другой поверхности + if (value._surface != null) { + // Удалить его оттуда + value._surface.material = null; + } + // Добавить материал в поверхность + value.addToSurface(this); + // Добавить материал в меш + if (_mesh != null) { + value.addToMesh(_mesh); + // Добавить материал в сцену + if (_mesh._scene != null) { + value.addToScene(_mesh._scene); + } + } + } + // Сохраняем материал + _material = value; + // Отправляем операцию изменения материала + addMaterialChangedOperationToScene(); + } + } + + /** + * Набор граней поверхности. + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Полигональный объект, которому принадлежит поверхность. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту, + * значение идентификатора равно null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getSurfaceId(this) : null; + } + + /** + * @private + * Добавление в сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // Добавляем на сцену материал + if (_material != null) { + _material.addToScene(scene); + } + } + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(changeFacesOperation); + scene.removeOperation(changeMaterialOperation); + // Удаляем из сцены материал + if (_material != null) { + _material.removeFromScene(scene); + } + } + + /** + * @private + * Добавление к мешу + * + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + + // Добавляем в меш материал + if (_material != null) { + _material.addToMesh(mesh); + } + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + + // Удаляем из меша материал + if (_material != null) { + _material.removeFromMesh(mesh); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * Удаление граней + */ + alternativa3d function removeFaces():void { + for (var key:* in _faces) { + var face:Face = key; + _faces.remove(face); + face.removeFromSurface(this); + } + } + + /** + * @private + * Изменение материала + */ + alternativa3d function addMaterialChangedOperationToScene():void { + if (_mesh != null) { + _mesh.addOperationToScene(changeMaterialOperation); + } + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + var length:uint = _faces.length; + var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : ""); + var i:uint = 0; + for (var key:* in _faces) { + var face:Face = key; + res += face.id + ((i < length - 1) ? ", " : ""); + i++; + } + res += "]"; + return res; + } + + /** + * Добавление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется, + * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом + * выполняются в порядке их добавления. + * @param useWeakReference флаг использования слабой ссылки для обработчика + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (dispatcher == null) { + dispatcher = new EventDispatcher(this); + } + useCapture = false; + dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + /** + * Рассылка события. + * + * @param event посылаемое событие + * @return false + */ + public function dispatchEvent(event:Event):Boolean { + if (dispatcher != null) { + dispatcher.dispatchEvent(event); + } + return false; + } + + /** + * Проверка наличия зарегистрированных обработчиков события указанного типа. + * + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.hasEventListener(type); + } + return false; + } + + /** + * Удаление обработчика события. + * + * @param type тип события + * @param listener обработчик события + * @param useCapture не используется + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (dispatcher != null) { + useCapture = false; + dispatcher.removeEventListener(type, listener, useCapture); + } + } + + /** + * + */ + public function willTrigger(type:String):Boolean { + if (dispatcher != null) { + return dispatcher.willTrigger(type); + } + return false; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..bbfbf7c --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,251 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.*; + import alternativa.types.Point3D; + import alternativa.types.Set; + + use namespace alternativa3d; + + /** + * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на + * полигональный объект и грани этого объекта, которым она принадлежит. + */ + final public class Vertex { + // Операции + /** + * @private + * Изменение локальных координат + */ + alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); + /** + * @private + * Расчёт глобальных координат + */ + alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS); + + /** + * @private + * Меш + */ + alternativa3d var _mesh:Mesh; + /** + * @private + * Координаты точки + */ + alternativa3d var _coords:Point3D; + /** + * @private + * Грани + */ + alternativa3d var _faces:Set = new Set(); + /** + * @private + * Координаты в сцене + */ + alternativa3d var globalCoords:Point3D = new Point3D(); + + /** + * Создание экземпляра вершины. + * + * @param x координата вершины по оси X + * @param y координата вершины по оси Y + * @param z координата вершины по оси Z + */ + public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) { + _coords = new Point3D(x, y, z); + + // Изменение координат инициирует пересчёт глобальных координат + changeCoordsOperation.addSequel(calculateCoordsOperation); + } + + /** + * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины + */ + private function calculateCoords():void { + globalCoords.copy(_coords); + globalCoords.transform(_mesh._transformation); + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_coords.x != value) { + _coords.x = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_coords.y != value) { + _coords.y = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_coords.z != value) { + _coords.z = value; + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * @private + */ + public function set coords(value:Point3D):void { + if (!_coords.equals(value)) { + _coords.copy(value); + if (_mesh != null) { + _mesh.addOperationToScene(changeCoordsOperation); + } + } + } + + /** + * координата вершины по оси X. + */ + public function get x():Number { + return _coords.x; + } + + /** + * координата вершины по оси Y. + */ + public function get y():Number { + return _coords.y; + } + + /** + * координата вершины по оси Z. + */ + public function get z():Number { + return _coords.z; + } + + /** + * Координаты вершины. + */ + public function get coords():Point3D { + return _coords.clone(); + } + + /** + * Полигональный объект, которому принадлежит вершина. + */ + public function get mesh():Mesh { + return _mesh; + } + + /** + * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса + * altertnativa.engine3d.core.Face. + * + * @see Face + */ + public function get faces():Set { + return _faces.clone(); + } + + /** + * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null. + */ + public function get id():Object { + return (_mesh != null) ? _mesh.getVertexId(this) : null; + } + + /** + * @private + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void { + // При добавлении на сцену расчитать глобальные координаты + scene.addOperation(calculateCoordsOperation); + } + + /** + * @private + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void { + // Удаляем все операции из очереди + scene.removeOperation(calculateCoordsOperation); + scene.removeOperation(changeCoordsOperation); + } + + /** + * @private + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void { + // Подписка на операции меша + mesh.changeCoordsOperation.addSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation); + // Сохранить меш + _mesh = mesh; + } + + /** + * @private + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void { + // Отписка от операций меша + mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation); + mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation); + // Удалить зависимые грани + for (var key:* in _faces) { + var face:Face = key; + mesh.removeFace(face); + } + // Удалить ссылку на меш + _mesh = null; + } + + /** + * @private + * @param face + */ + alternativa3d function addToFace(face:Face):void { + // Подписка грани на операции + changeCoordsOperation.addSequel(face.calculateUVOperation); + changeCoordsOperation.addSequel(face.calculateNormalOperation); + // Добавить грань в список + _faces.add(face); + } + + /** + * @private + * @param face + */ + alternativa3d function removeFromFace(face:Face):void { + // Отписка грани от операций + changeCoordsOperation.removeSequel(face.calculateUVOperation); + changeCoordsOperation.removeSequel(face.calculateNormalOperation); + // Удалить грань из списка + _faces.remove(face); + } + + /** + * Строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as new file mode 100644 index 0000000..2aaae45 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as @@ -0,0 +1,65 @@ +package alternativa.engine3d.display { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.materials.Material; + + import flash.display.Graphics; + import flash.display.Sprite; + + use namespace alternativa3d; + + /** + * @private + * Контейнер, используемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере. + */ + public class Skin extends Sprite { + + /** + * @private + * Графика скина (для быстрого доступа) + */ + alternativa3d var gfx:Graphics = graphics; + + /** + * @private + * Ссылка на следующий скин + */ + alternativa3d var nextSkin:Skin; + + /** + * @private + * Примитив + */ + alternativa3d var primitive:PolyPrimitive; + + /** + * @private + * Материал, связанный со скином. + */ + alternativa3d var material:Material; + + // Хранилище неиспользуемых скинов + static private var collector:Array = new Array(); + + /** + * @private + * Создание скина. + */ + static alternativa3d function createSkin():Skin { + var skin:Skin; + if ((skin = collector.pop()) != null) { + return skin; + } + return new Skin(); + } + + /** + * @private + * Удаление скина, все ссылки должны быть почищены. + */ + static alternativa3d function destroySkin(skin:Skin):void { + collector.push(skin); + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/display/View.as b/Alternativa3D5/5.6/alternativa/engine3d/display/View.as new file mode 100644 index 0000000..0d39fbd --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/display/View.as @@ -0,0 +1,892 @@ +package alternativa.engine3d.display { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Sprite3D; + import alternativa.engine3d.core.SpritePrimitive; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.events.MouseEvent3D; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + + import flash.display.Sprite; + import flash.events.Event; + import flash.events.MouseEvent; + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Вьюпорт для вывода изображения с камеры. + */ + public class View extends Sprite { + + /** + * @private + * Область отрисовки спрайтов + */ + alternativa3d var canvas:Sprite; + + private var _camera:Camera3D; + + /** + * @private + * Ширина области вывода + */ + alternativa3d var _width:Number; + /** + * @private + * Высота области вывода + */ + alternativa3d var _height:Number; + + /** + * @private + * Флаг интерактивности вьюпорта. В интерактивном режиме вьюпорт принимает события мыши и транслирует их + * в подсистему трёхмерных событий, которая, в свою очередь, преобразует двумерный клик в трёхмерный и рассылает + * события всем подписчикам. + */ + alternativa3d var _interactive:Boolean; + + // Грань под курсором + private var faceUnderPoint:Face; + private var objectUnderPoint:Object3D; + + private var lastMouseEvent:MouseEvent; + private var stagePoint:Point = new Point(); + + // Текущая грань + private var currentFace:Face; + // Текущая поверхность + private var currentSurface:Surface; + // Текущий объект + private var currentObject:Object3D; + // Грань, на которой было событие MOUSE_DOWN + private var pressedFace:Face; + // Поверхность, на которой было событие MOUSE_DOWN + private var pressedSurface:Surface; + // Объект, на котором было событие MOUSE_DOWN + private var pressedObject:Object3D; + + // Направляющий вектор проецирующей прямой в камере + private var lineVector:Point3D = new Point3D(); + // Вспомогательная переменная для хранения точки проецирующей прямой в ортографической камере + private var linePoint:Point3D = new Point3D(); + // Точка на объекте под курсором в глобальной системе координат. + private var globalCursor3DCoords:Point3D = new Point3D(); + // Координаты курсора в системе координат объекта + private var localCursor3DCoords:Point3D = new Point3D(); + // UV-координаты в грани под курсором + private var uvPoint:Point = new Point(); + // Вспомогательная матрица + private var inverseMatrix:Matrix3D = new Matrix3D(); + + /** + * Создаёт новый экземпляр вьюпорта. + * + * @param camera камера, связанная с вьюпортом + * @param width ширина вьюпорта + * @param height высота вьюпорта + */ + public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) { + canvas = new Sprite(); + canvas.mouseEnabled = false; + canvas.mouseChildren = false; + canvas.tabEnabled = false; + canvas.tabChildren = false; + addChild(canvas); + + this.camera = camera; + this.width = width; + this.height = height; + + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + } + + /** + * Камера, с которой выводится изображение. + */ + public function get camera():Camera3D { + return _camera; + } + + /** + * @private + */ + public function set camera(value:Camera3D):void { + if (_camera != value) { + // Если была камера + if (_camera != null) { + // Удалить камеру + _camera.removeFromView(this); + } + // Если новая камера + if (value != null) { + // Если камера была в другом вьюпорте + if (value._view != null) { + // Удалить её оттуда + value._view.camera = null; + } + // Добавить камеру + value.addToView(this); + } else { + // Зачистка скинов + if (canvas.numChildren > 0) { + var skin:Skin = Skin(canvas.getChildAt(0)); + while (skin != null) { + // Сохраняем следующий + var next:Skin = skin.nextSkin; + // Удаляем из канваса + canvas.removeChild(skin); + // Очистка скина + if (skin.material != null) { + skin.material.clear(skin); + } + // Зачищаем ссылки + skin.nextSkin = null; + skin.primitive = null; + skin.material = null; + // Удаляем + Skin.destroySkin(skin); + // Следующий устанавливаем текущим + skin = next; + } + } + } + // Сохраняем камеру + _camera = value; + } + } + + /** + * Ширина вьюпорта в пикселях. + */ + override public function get width():Number { + return _width; + } + + /** + * @private + */ + override public function set width(value:Number):void { + if (_width != value) { + _width = value; + canvas.x = _width*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Высота вьюпорта в пикселях. + */ + override public function get height():Number { + return _height; + } + + /** + * @private + */ + override public function set height(value:Number):void { + if (_height != value) { + _height = value; + canvas.y = _height*0.5; + if (_camera != null) { + camera.addOperationToScene(camera.calculatePlanesOperation); + } + } + } + + /** + * Возвращает объект, находящийся под указанной точкой вьюпорта. + * + * @param viewPoint координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0). + * + * @return ближайший к камере объект под заданной точкой вьюпорта, либо null, + * если под указанной точкой нет объектов или вьюпорт не помещён на Stage. + * Объект может быть гранью (Face) или спрайтом (Sprite3D). + */ + public function getObjectUnderPoint(viewPoint:Point):Object { + if (stage == null) { + return null; + } + var stagePoint:Point = localToGlobal(viewPoint); + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var skin:Skin; + for (var i:int = objects.length - 1; i >= 0; i--) { + skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + return skin.primitive.face != null ? skin.primitive.face : (skin.primitive as SpritePrimitive).sprite; + } + } + return null; + } + + /** + * Возвращает объекты, находящиеся под указанной точкой вьюпорта. + * + * @param viewPoint координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0). + * + * @return массив объектов, расположенных под заданной точкой вьюпорта. Первым элементом массива является самый дальний объект. + * Объектами могут быть грани (Face) или спрайты (Sprite3D). Если под указанной точкой нет ни одного объекта, массив будет пустым. + * Если вьюпорт не помещен на Stage, возвращается null. + */ + override public function getObjectsUnderPoint(viewPoint:Point):Array { + if (stage == null) { + return null; + } + var stagePoint:Point = localToGlobal(viewPoint); + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var res:Array = new Array(); + var length:uint = objects.length; + for (var i:uint = 0; i < length; i++) { + var skin:Skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + if (skin.primitive.face != null) { + res.push(skin.primitive.face); + } else { + res.push((skin.primitive as SpritePrimitive).sprite); + } + } + } + return res; + } + + /** + * Проецирует заданную глобальными координатами точку на плоскость вьюпорта. + * + * @param point глобальные координаты проецируемой точки + * + * @return объект Point3D, содержащий координаты проекции точки относительно левого верхнего угла вьюпорта и z-координату + * точки в системе координат камеры. Если вьюпорту не назначена камера или камера не находится в сцене, возвращается null. + */ + public function projectPoint(point:Point3D):Point3D { + if (_camera == null || _camera._scene == null) { + return null; + } + + var cameraMatrix:Matrix3D = Object3D.matrix2; + var focalLength:Number = _camera._focalLength;; + var zoom:Number; + + // Вычисление матрицы трансформации камеры + if (_camera.getTransformation(cameraMatrix)) { + // Матрица была пересчитана заново + cameraMatrix.invert(); + if (_camera._orthographic) { + // Учёт масштабирования в ортографической камере + zoom = _camera.zoom; + cameraMatrix.scale(zoom, zoom, zoom); + } + } else { + // Пересчёта не потребовалось, проверяем изменение зума + if (_camera._orthographic && _camera.calculateMatrixOperation.queued) { + cameraMatrix.invert(); + zoom = _camera.zoom; + cameraMatrix.scale(zoom, zoom, zoom); + } else { + // Зум не менялся или перспективный режим, просто копируем обратную матрицу + cameraMatrix = _camera.cameraMatrix; + } + } + // Расчёт фокусного расстояния + if (!_camera._orthographic && _camera.calculatePlanesOperation.queued) { + focalLength = 0.5 * Math.sqrt(_height * _height + _width * _width) / Math.tan(0.5 * _camera._fov); + } + // Координаты точки в системе координат камеры + var x:Number = cameraMatrix.a * point.x + cameraMatrix.b * point.y + cameraMatrix.c * point.z + cameraMatrix.d; + var y:Number = cameraMatrix.e * point.x + cameraMatrix.f * point.y + cameraMatrix.g * point.z + cameraMatrix.h; + var z:Number = cameraMatrix.i * point.x + cameraMatrix.j * point.y + cameraMatrix.k * point.z + cameraMatrix.l; + // Проекция точки на вьюпорт + if (_camera._orthographic) { + return new Point3D(x + (_width >> 1), y + (_height >> 1), z); + } else { + return new Point3D(x * focalLength / z + (_width >> 1), y * focalLength / z + (_height >> 1), z); + } + } + + /** + * Интерактивность области вьюпорта. При включённой интерактивности возможно использование системы мышиных событий. + * + * @default false + */ + public function get interactive():Boolean { + return _interactive; + } + + /** + * @private + */ + public function set interactive(value:Boolean):void { + if (_interactive == value) { + return; + } + _interactive = value; + + if (_interactive) { + addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + addEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent); + addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent); + addEventListener(MouseEvent.MOUSE_OUT, onMouseEvent); + } else { + removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent); + removeEventListener(MouseEvent.MOUSE_OUT, onMouseEvent); + if (stage != null) { + stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp); + } + pressedFace = currentFace = null; + pressedSurface = currentSurface = null; + pressedObject = currentObject = null; + } + } + + /** + * Проецирует двумерную точку во вьюпорте на заданную плоскость в трёхмерном пространстве. + * + * @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0). + * @param planeNormal нормаль плоскости в глобальной системе координат, на которую проецируется точка + * @param planeOffset смещение плоскости в глобальной системе координат, на которую проецируется точка + * @param result результат проецирования будет записан в этот параметр. Если в качестве значения будет указано null, метод + * создаст новый экземпляр Point3D и вернёт результат в нём. + * @return переданный в параметре result экземпляр Point3D или новый экземпляр, если значение result равно null. Если возможно бесконечное + * количество решений (линия зрения параллельна заданной плоскости), то результат содержит значения NaN. + * Если вьюпорту не назначена камера или камера не находится в сцене, возвращается null. + */ + public function projectViewPointToPlane(viewPoint:Point, planeNormal:Point3D, planeOffset:Number, result:Point3D = null):Point3D { + if (_camera == null || _camera._scene == null) { + return null; + } + if (result == null) { + result = new Point3D(); + } + calculateRayOriginAndVector(viewPoint.x - (_width >> 1), viewPoint.y - (_height >> 1), linePoint, lineVector, true); + if (!calculateLineAndPlaneIntersection(linePoint, lineVector, planeNormal, planeOffset, result)) { + result.reset(NaN, NaN, NaN); + } + return result; + } + + /** + * Вычисляет координаты точки в системе координат камеры, связанной с вьюпортом. Если камера в режиме перспективной + * проекции, то метод вычислит координаты точки, лежащей на прямой, проходящей через начало координат камеры и указанную точку + * вьюпорта. Если камера в режиме ортографической проекции, то метод вычислит координаты точки, лежащей на прямой, + * перпендикулярной фокальной плоскости камеры и проходящей через указанную точку вьюпорта. + * + * @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0). + * @param depth глубина точки в камере — координата Z в системе координат камеры + * @param result результат будет записан в этот параметр. Если в качестве значения будет указано null, метод + * создаст новый экземпляр Point3D и вернёт результат в нём. + * @return координаты точки в системе координат камеры или null, если с вьюпортом не связана камера + */ + public function get3DCoords(viewPoint:Point, depth:Number, result:Point3D = null):Point3D { + if (_camera == null) { + return null; + } + if (result == null) { + result = new Point3D(); + } + if (_camera._orthographic) { + result.x = (viewPoint.x - (_width >> 1))/camera._zoom; + result.y = (viewPoint.y - (_height >> 1))/camera._zoom; + } else { + var k:Number = depth/_camera.focalLength; + result.x = (viewPoint.x - (_width >> 1))*k; + result.y = (viewPoint.y - (_height >> 1))*k; + } + result.z = depth; + return result; + } + + /** + * + */ + private function onRemovedFromStage(e:Event):void { + interactive = false; + } + + /** + * Сброс нажатых объектов при отпускании кнопки мыши вне вьюпорта. + */ + private function stageMouseUp(e:MouseEvent):void { + if (stage == null) { + return; + } + pressedFace = null; + pressedSurface = null; + pressedObject = null; + stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp); + } + + /** + * Метод находит интерактивный объект (Object3D) и интерактивную грань, если возможно, находящиеся под указанной точкой в области вывода. + * + * @param pointX X-координата точки относительно области вывода + * @param pointY Y-координата точки относительно области вывода + */ + private function getInteractiveObjectUnderPoint(pointX:Number, pointY:Number):void { + if (stage == null) { + return; + } + faceUnderPoint = null; + objectUnderPoint = null; + stagePoint.x = pointX; + stagePoint.y = pointY; + var objects:Array = stage.getObjectsUnderPoint(stagePoint); + var skin:Skin; + for (var i:int = objects.length - 1; i >= 0; i--) { + skin = objects[i] as Skin; + if (skin != null && skin.parent.parent == this) { + if (skin.primitive.face != null) { + // Скин, содержащий PolyPrimitive + if (skin.primitive.face._mesh.mouseEnabled) { + faceUnderPoint = skin.primitive.face; + objectUnderPoint = faceUnderPoint._mesh; + return; + } + } else { + // Скин, содержащий SpritePrimitive + var sprite:Sprite3D = (skin.primitive as SpritePrimitive).sprite; + if (sprite.mouseEnabled) { + objectUnderPoint = sprite; + return; + } + } + } + } + } + + /** + * Вычисление свойств точки объекта, находящегося под указанной точкой фокусной плоскости камеры. Метод расчитывает глобальные и локальные + * 3D-координаты точки, а также её UV-координаты. + * + * @param canvasX + * @param canvasY + */ + private function getInteractiveObjectPointProperties(canvasX:Number, canvasY:Number):void { + if (objectUnderPoint == null) { + return; + } + calculateRayOriginAndVector(canvasX, canvasY, linePoint, lineVector); + // Вычисление глобальных координат точки пересечения проецирующей прямой и плоскости объекта + var normal:Point3D; + var offset:Number; + if (faceUnderPoint != null) { + // Работаем с гранью + normal = faceUnderPoint.globalNormal; + offset = faceUnderPoint.globalOffset; + } else { + // Работаем со спрайтом + normal = lineVector.clone(); + normal.invert(); + globalCursor3DCoords.copy(objectUnderPoint._coords); + globalCursor3DCoords.transform(objectUnderPoint._transformation); + offset = globalCursor3DCoords.dot(normal); + } + calculateLineAndPlaneIntersection(linePoint, lineVector, normal, offset, globalCursor3DCoords); + // Вычисление локальных координат точки пересечения + inverseMatrix.copy((faceUnderPoint != null ? faceUnderPoint._mesh : objectUnderPoint)._transformation); + inverseMatrix.invert(); + localCursor3DCoords.copy(globalCursor3DCoords); + localCursor3DCoords.transform(inverseMatrix); + // Вычисление UV-координат + if (faceUnderPoint != null) { + var uv:Point = faceUnderPoint.getUV(localCursor3DCoords); + if (uv != null) { + uvPoint.x = uv.x; + uvPoint.y = uv.y; + } else { + uvPoint.x = NaN; + uvPoint.y = NaN; + } + } + } + + /** + * + */ + private function createSimpleMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D { + var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey; + var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey; + var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey; + var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta; + return new MouseEvent3D(type, this, object, surface, face, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + altKey, ctrlKey, shiftKey, delta); + } + + /** + * + */ + private function createFullMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D { + var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey; + var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey; + var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey; + var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta; + return new MouseEvent3D(type, this, object, surface, face, + globalCursor3DCoords.x, globalCursor3DCoords.y, globalCursor3DCoords.z, localCursor3DCoords.x, localCursor3DCoords.y, localCursor3DCoords.z, uvPoint.x, uvPoint.y, + altKey, ctrlKey, shiftKey, delta); + } + + /** + * Обработка мышиного события на вьюпорте и передача его в систему трёхмерных событий. + */ + private function onMouseEvent(e:MouseEvent):void { + if (stage == null) { + return; + } + // Сохранение события для использования в функциях создания MouseEvent3D + lastMouseEvent = e; + // Получение объекта под курсором и свойств точки на этом объекте + getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY); + getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1)); + // Обработка события + switch (e.type) { + case MouseEvent.MOUSE_MOVE: + processMouseMove(); + break; + case MouseEvent.MOUSE_OUT: + stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUp); + checkMouseOverOut(); + break; + case MouseEvent.MOUSE_DOWN: + processMouseDown(); + break; + case MouseEvent.MOUSE_UP: + processMouseUp(); + break; + case MouseEvent.MOUSE_WHEEL: + processMouseWheel(); + break; + } + lastMouseEvent = null; + } + + /** + * Обработка нажатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_DOWN. + */ + private function processMouseDown():void { + if (objectUnderPoint == null) { + return; + } + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + } else { + currentFace = null; + currentSurface = null; + } + currentObject = pressedObject = objectUnderPoint; + + var evt:MouseEvent3D; + if (currentFace != null && currentFace.mouseEnabled) { + pressedFace = currentFace; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface.mouseEnabled) { + pressedSurface = currentSurface; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + /** + * Обработка отжатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_UP, MouseEvent3D.CLICK. + */ + private function processMouseUp():void { + if (objectUnderPoint == null) { + pressedFace = null; + pressedSurface = null; + pressedObject = null; + return; + } + + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + } else { + currentFace = null; + currentSurface = null; + } + currentObject = objectUnderPoint; + + var evt:MouseEvent3D; + // MouseEvent3D.MOUSE_UP + if (currentFace != null && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + + // MouseEvent3D.CLICK + if (currentFace != null && currentFace == pressedFace && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface != null && currentSurface == pressedSurface && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + + if (currentObject == pressedObject) { + evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + pressedFace = null; + pressedSurface = null; + pressedObject = null; + } + + /** + * Обработка вращения колеса мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_WHEEL. + */ + private function processMouseWheel():void { + if (objectUnderPoint == null) { + return; + } + + var evt:MouseEvent3D; + if (faceUnderPoint != null) { + currentFace = faceUnderPoint; + currentSurface = faceUnderPoint._surface; + + if (currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } else { + currentFace = null; + currentSurface = null; + } + currentObject = objectUnderPoint; + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + /** + * @private + * Метод проверяет наличиче событий MOUSE_OVER, MOUSE_OUT для объектов сцены, их поверхностей и граней. + * + * @param checkObject флаг необходимости предварительно получить объект под курсором. Используется при вызове метода из функции отрисовки камеры. + */ + alternativa3d function checkMouseOverOut(checkObject:Boolean = false):void { + if (stage == null) { + return; + } + if (checkObject) { + getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY); + getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1)); + } + var evt:MouseEvent3D; + if (objectUnderPoint == null) { + // Мышь ушла с объекта, генерируются события MOUSE_OUT + if (currentFace != null) { + // MOUSE_OUT для грани + if (currentFace.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + // MOUSE_OUT для поверхности + if (currentSurface.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + + if (currentObject != null) { + // MOUSE_OUT для объекта + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + + currentFace = null; + currentSurface = null; + currentObject = null; + } else { + // Мышь на каком-то объекте + var surface:Surface; + var faceChanged:Boolean; + var surfaceChanged:Boolean; + var objectChanged:Boolean; + + if (faceUnderPoint != null) { + surface = faceUnderPoint._surface; + } + // + if (faceUnderPoint != currentFace) { + // MOUSE_OUT для грани + if (currentFace != null && currentFace.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + faceChanged = true; + // MOUSE_OUT для поверхности + if (surface != currentSurface) { + if (currentSurface != null && currentSurface.mouseEnabled) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + surfaceChanged = true; + } + } + // MOUSE_OUT для объекта + if (objectUnderPoint != currentObject) { + if (currentObject != null) { + evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + objectChanged = true; + } + + currentFace = faceUnderPoint; + currentSurface = surface; + currentObject = objectUnderPoint; + if (currentFace != null) { + // MOUSE_OVER для грани + if (faceChanged && currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + // MOUSE_OVER для поверхности + if (surfaceChanged && currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + // MOUSE_OVER для объекта + if (objectChanged) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + } + } + + /** + * Обработчик движения мыши. + */ + private function processMouseMove():void { + // Запуск проверки на наличие событий MOUSE_OVER и MOUSE_OUT + checkMouseOverOut(); + // Генерация событий MOUSE_MOVE + var evt:MouseEvent3D; + if (currentFace != null) { + // Мышь на каком-то объекте + if (currentFace.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentFace.dispatchEvent(evt); + } + + if (currentSurface.mouseEnabled) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentSurface.dispatchEvent(evt); + } + } + + if (currentObject != null) { + evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace); + currentObject.dispatchEvent(evt); + } + } + + /** + * @private + * Вычисляет точку пересечения прямой и плоскости. + * + * @param linePoint точка, лежащая на прямой + * @param lineVector направляющий вектор прямой + * @param planeNormal нормаль плоскости + * @param planeOffset смещение плоскости + * @param result переменная для сохранения координат точки пересечения + * + * @return true если прямая и плоскость пересекаются, иначе false + */ + private function calculateLineAndPlaneIntersection(linePoint:Point3D, lineVector:Point3D, planeNormal:Point3D, planeOffset:Number, result:Point3D):Boolean { + var dot:Number = planeNormal.x*lineVector.x + planeNormal.y*lineVector.y + planeNormal.z*lineVector.z; + if (dot < 1E-8 && dot > -1E-8) { + // Прямая и плосоксть параллельны + return false; + } + var k:Number = (planeOffset - linePoint.x*planeNormal.x - linePoint.y*planeNormal.y - linePoint.z*planeNormal.z)/dot; + result.x = linePoint.x + k*lineVector.x; + result.y = linePoint.y + k*lineVector.y; + result.z = linePoint.z + k*lineVector.z; + return true; + } + + /** + * Вычисляет точку и направляющий вектор для луча зрения, проходящего через заданную точку вьюпорта. + * + * @param rayOrigin сюда будут записаны глобальные координаты начальной точки луча + * @param rayVector сюда будут записаны глобальные координаты направляющего вектора + * @param calculate определяет необходимо ли вычислять актуальные значения объекта при вызове метода + */ + private function calculateRayOriginAndVector(canvasX:Number, canvasY:Number, rayOrigin:Point3D, rayVector:Point3D, calculate:Boolean = false):void { + var x:Number; + var y:Number; + var z:Number; + // Вычисление направляющего вектора и точки проецирующей прямой в глобальном пространстве + // Вычисление матрицы трансформации камеры + var m:Matrix3D; + if (calculate) { + m = Object3D.matrix2; + _camera.getTransformation(m); + } else { + m = _camera._transformation; + } + if (_camera._orthographic) { + // Координаты точки на луче + x = canvasX/_camera.zoom; + y = canvasY/_camera.zoom; + rayOrigin.x = m.a*x + m.b*y + m.d; + rayOrigin.y = m.e*x + m.f*y + m.h; + rayOrigin.z = m.i*x + m.j*y + m.l; + // Координаты локального направляющего вектора + x = y = 0; + z = 1; + } else { + // Координаты точки на луче + rayOrigin.x = m.d; + rayOrigin.y = m.h; + rayOrigin.z = m.l; + // Координаты локального направляющего вектора + x = canvasX; + y = canvasY; + if (calculate) { + z = _camera.focalLength; + } else { + z = _camera._focalLength; + } + } + // Направляющий вектор в глобальном пространстве + lineVector.x = x*m.a + y*m.b + z*m.c; + lineVector.y = x*m.e + y*m.f + z*m.g; + lineVector.z = x*m.i + y*m.j + z*m.k; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as new file mode 100644 index 0000000..7c3edd0 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as @@ -0,0 +1,25 @@ +package alternativa.engine3d.errors { + + /** + * Базовый класс для ошибок 3d-engine. + */ + public class Engine3DError extends Error { + + /** + * Источник ошибки - объект в котором произошла ошибка. + */ + public var source:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param source источник ошибки + */ + public function Engine3DError(message:String = "", source:Object = null) { + super(message); + this.source = source; + this.name = "Engine3DError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as new file mode 100644 index 0000000..ecc7f59 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as @@ -0,0 +1,35 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Surface; + + /** + * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте. + */ + public class FaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани, которая уже содержится в объекте + * @param source источник ошибки + */ + public function FaceExistsError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else if (source is Surface) { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 already exists."; + } else { + message += "%1. Face with ID '%2' already exists."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as new file mode 100644 index 0000000..3341f63 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая недостаточное количество вершин для создания грани. + * Для создания грани должно быть указано не менее трех вершин. + */ + public class FaceNeedMoreVerticesError extends Engine3DError { + + /** + * Количество переданных для создания грани вершин + */ + public var count:uint; + + /** + * Создание экземпляра класса. + * + * @param mesh объект, в котором произошла ошибка + * @param count количество вершин, переданное для создания грани + */ + public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) { + super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh); + this.count = count; + this.name = "FaceNeedMoreVerticesError"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as new file mode 100644 index 0000000..912d6a4 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, если грань не найдена в объекте. + */ + public class FaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param face экземпляр или идентификатор грани + * @param source объект, в котором произошла ошибка + */ + public function FaceNotFoundError(face:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh "; + } else { + message = "Surface "; + } + if (face is Face) { + message += "%1. Face %2 not found."; + } else { + message += "%1. Face with ID '%2' not found."; + } + super(TextUtils.insertVars(message, source, face), face, source); + this.name = "FaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as new file mode 100644 index 0000000..fbf4666 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.errors { + import alternativa.utils.TextUtils; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + + + /** + * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован. + */ + public class InvalidIDError extends Engine3DError { + /** + * Зарезервированный идентификатор + */ + public var id:Object; + + /** + * Создание экземпляра класса. + * + * @param id идентификатор + * @param source объект, в котором произошла ошибка + */ + public function InvalidIDError(id:Object = null, source:Object = null) { + var message:String; + if (source is Mesh) { + message = "Mesh %2. "; + } else if (source is Surface) { + message = "Surface %2. "; + } + super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source); + this.id = id; + this.name = "InvalidIDError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as new file mode 100644 index 0000000..8d61bd0 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, связанная с нарушением иерархии объектов сцены. + */ + public class Object3DHierarchyError extends Engine3DError + { + + /** + * Объект сцены, нарушающий иерархию + */ + public var object:Object3D; + + /** + * Создание экземпляра класса. + * + * @param object объект, нарушающий иерархию + * @param source источник ошибки + */ + public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source); + this.object = object; + this.name = "Object3DHierarchyError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as new file mode 100644 index 0000000..d1ddd1e --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Object3D; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены. + */ + public class Object3DNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param object ненайденный объект сцены + * @param source объект сцены, в котором произошла ошибка + */ + public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) { + super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source); + this.name = "Object3DNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as new file mode 100644 index 0000000..7743a53 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Ошибка, обозначающая, что объект уже присутствует в контейнере. + */ + public class ObjectExistsError extends Engine3DError { + + /** + * Экземпляр или идентификатор объекта, который уже присутствует в контейнере + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object объект, который уже присутствует в контейнере + * @param source объект, вызвавший ошибку + */ + public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectExistsError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as new file mode 100644 index 0000000..87b3d14 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as @@ -0,0 +1,26 @@ +package alternativa.engine3d.errors { + + /** + * Необходимый объект не был найден в контейнере. + */ + public class ObjectNotFoundError extends Engine3DError { + + /** + * Объект, который отсутствует в контейнере. + */ + public var object:Object; + + /** + * Создание экземпляра класса. + * + * @param message описание ошибки + * @param object отсутствующий объект + * @param source объект, вызвавший ошибку + */ + public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) { + super(message, source); + this.object = object; + this.name = "ObjectNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as new file mode 100644 index 0000000..4d3dced --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as @@ -0,0 +1,24 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Sector; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая при попытке добавить на сцену сектор, расположенный в другой сцене. + */ + public class SectorInOtherSceneError extends Engine3DError { + + /** + * Создание экземпляра класса. + * + * @param sector экземпляр сектора, расположенный в другой сцене + * @param source сцена, из которой было вызвано исключение. + */ + public function SectorInOtherSceneError(sector:Sector = null, source:Scene3D = null) { + super(TextUtils.insertVars("%1. Sector %2 is aready situated in the other scene", source, sector), source); + this.name = "SectorInOtherSceneError"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as new file mode 100644 index 0000000..a0f3789 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as @@ -0,0 +1,24 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Splitter; + import alternativa.utils.TextUtils; + + /** + * Ошибка, возникающая при попытке добавить на сцену сплиттер, расположенный в другой сцене. + */ + public class SplitterInOtherSceneError extends Engine3DError { + + /** + * Создание экземпляра класса. + * + * @param splitter экземпляр сплиттера, расположенный в другой сцене + * @param source сцена, из которой было вызвано исключение. + */ + public function SplitterInOtherSceneError(splitter:Splitter = null, source:Scene3D = null) { + super(TextUtils.insertVars("%1. Splitter %2 is aready situated in the other scene", source, splitter), source); + this.name = "SplitterInOtherSceneError"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as new file mode 100644 index 0000000..f262da9 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as @@ -0,0 +1,29 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая недостаточное количество точек для создания сплиттера. + * Для создания сплиттера должно быть указано не менее трех точек. + */ + public class SplitterNeedMoreVerticesError extends Engine3DError { + + /** + * Количество переданных для создания сплиттера точек + */ + public var count:uint; + + /** + * Создание экземпляра класса. + * + * @param count количество точек, переданное для создания сплиттера + */ + public function SplitterNeedMoreVerticesError(count:uint = 0) { + super(TextUtils.insertVars("%1 points not enough for splitter creation.", count), null); + this.count = count; + this.name = "SplitterNeedMoreVerticesError"; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as new file mode 100644 index 0000000..e13bee7 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность уже присутствует в контейнере. + */ + public class SurfaceExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая уже присутствует в контейнере + * @param mesh объект, вызвавший ошибку + */ + public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh); + this.name = "SurfaceExistsError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as new file mode 100644 index 0000000..05457bf --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as @@ -0,0 +1,31 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что поверхность не найдена в контейнере. + */ + public class SurfaceNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param surface поверхность, которая отсутствует в объекте + * @param mesh объект, который вызвал ошибку + */ + public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) { + if (mesh == null) { + + } + if (surface is Surface) { + message = "Mesh %1. Surface %2 not found."; + } else { + message = "Mesh %1. Surface with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, surface), surface, mesh); + this.name = "SurfaceNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as new file mode 100644 index 0000000..9f0fff5 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина уже содержится в объекте. + */ + public class VertexExistsError extends ObjectExistsError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая уже есть в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) { + super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh); + this.name = "VertexExistsError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as new file mode 100644 index 0000000..edf80aa --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.errors { + + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.TextUtils; + + /** + * Ошибка, обозначающая, что вершина не найдена в объекте. + */ + public class VertexNotFoundError extends ObjectNotFoundError { + + /** + * Создание экземпляра класса. + * + * @param vertex вершина, которая не найдена в объекте + * @param mesh объект, вызвавший ошибку + */ + public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) { + if (vertex is Vertex) { + message = "Mesh %1. Vertex %2 not found."; + } else { + message = "Mesh %1. Vertex with ID '%2' not found."; + } + super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh); + this.name = "VertexNotFoundError"; + } + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as b/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as new file mode 100644 index 0000000..24c6b84 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as @@ -0,0 +1,174 @@ +package alternativa.engine3d.events { + + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.View; + + import flash.events.Event; + + /** + * Событие, возникающее при взаимодействии мыши с объектами сцены. + */ + public class MouseEvent3D extends Event { + /** + * Значение свойства type для объекта события click. + * @eventType click + */ + public static const CLICK:String = "click"; + /** + * Значение свойства type для объекта события mouseDown. + * @eventType mouseDown + */ + public static const MOUSE_DOWN:String = "mouseDown"; + /** + * Значение свойства type для объекта события mouseUp. + * @eventType mouseUp + */ + public static const MOUSE_UP:String = "mouseUp"; + /** + * Значение свойства type для объекта события mouseOver. + * @eventType mouseOver + */ + public static const MOUSE_OVER:String = "mouseOver"; + /** + * Значение свойства type для объекта события mouseOut. + * @eventType mouseOut + */ + public static const MOUSE_OUT:String = "mouseOut"; + /** + * Значение свойства type для объекта события mouseMove. + * @eventType mouseMove + */ + public static const MOUSE_MOVE:String = "mouseMove"; + /** + * Значение свойства type для объекта события mouseWheel. + * @eventType mouseWheel + */ + public static const MOUSE_WHEEL:String = "mouseWheel"; + + /** + * Объект сцены, с которым связано событие. + */ + public var object:Object3D; + /** + * Поверхность объекта сцены, с которой связано событие. + */ + public var surface:Surface; + /** + * Грань объекта сцены, с которой связано событие. + */ + public var face:Face; + /** + * Область вывода, в которой произошло событие. + */ + public var view:View; + + /** + * X-координата мышиного курсора в сцене. + */ + public var globalX:Number; + /** + * Y-координата мышиного курсора в сцене. + */ + public var globalY:Number; + /** + * Z-координата мышиного курсора в сцене. + */ + public var globalZ:Number; + + /** + * X-координата мышиного курсора в системе координат объекта. + */ + public var localX:Number; + /** + * Y-координата мышиного курсора в системе координат объекта. + */ + public var localY:Number; + /** + * Z-координата мышиного курсора в системе координат объекта. + */ + public var localZ:Number; + + /** + * Текстурная координата U в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN. + */ + public var u:Number; + /** + * Текстурная координата V в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN. + */ + public var v:Number; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Alt. + */ + public var altKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Control. + */ + public var ctrlKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Shift. + */ + public var shiftKey:Boolean; + /** + * Количество линий прокрутки при вращении колеса мыши. + */ + public var delta:int; + + /** + * Создаёт новый экземпляр события. + * + * @param type тип события + * @param view область вывода, в которой произошло событие + * @param object объект сцены, с которым связано событие + * @param surface поверхность, с которой связано событие + * @param face грань, с которой связано событие + * @param globalX X-координата мышиного курсора в сцене + * @param globalY Y-координата мышиного курсора в сцене + * @param globalZ Z-координата мышиного курсора в сцене + * @param localX X-координата мышиного курсора в системе координат объекта + * @param localY Y-координата мышиного курсора в системе координат объекта + * @param localZ Z-координата мышиного курсора в системе координат объекта + * @param u текстурная координата U в точке нахождения мышиного курсора + * @param v текстурная координата V в точке нахождения мышиного курсора + */ + public function MouseEvent3D(type:String, view:View, object:Object3D, surface:Surface, face:Face, globalX:Number = NaN, globalY:Number = NaN, globalZ:Number = NaN, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, u:Number = NaN, v:Number = NaN, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) { + super(type); + this.view = view; + this.object = object; + this.surface = surface; + this.face = face; + this.globalX = globalX; + this.globalY = globalY; + this.globalZ = globalZ; + this.localX = localX; + this.localY = localY; + this.localZ = localZ; + this.u = u; + this.v = v; + this.altKey = altKey; + this.ctrlKey = ctrlKey; + this.shiftKey = shiftKey; + this.delta = delta; + } + + /** + * Получение строкового представления объекта. + * + * @return строковое представление объекта + */ + override public function toString():String { + return formatToString("MouseEvent3D", "object", "surface", "face", "globalX", "globalY", "globalZ", "localX", "localY", "localZ", "u", "v", "delta", "altKey", "ctrlKey", "shiftKey"); + } + + /** + * Возвращает клон объекта. + * + * @return клон события + */ + override public function clone():Event { + return new MouseEvent3D(type, view, object, surface, face, globalX, globalY, globalZ, localX, localY, localZ, u, v, altKey, ctrlKey, shiftKey, delta); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as new file mode 100644 index 0000000..ced1012 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as @@ -0,0 +1,269 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + + /** + * Рассылается в начале очередного этапа загрузки сцены. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START + */ + [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается в процессе получения данных во время загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS + */ + [Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + /** + * Рассылается после окончания очередного этапа загрузки сцены. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE + */ + [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается после окончания загрузки сцены. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Рассылается, если в процессе загрузки возникает ошибка, которая прерывает загрузку. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Рассылается, если вызов Loader3D.load() нарушает текущие правила безопасности. + * + * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR + */ + [Event (name="securityError", type="flash.events.SecurityErrorEvent")] + + /** + * Базовый класс загрузчиков. Реализует загрузку основного файла сцены и включает ряд методов, расширяя которые, + * наследники могут реализовывать специфическую функциональность для разбора сцены и загрузки дополнительных данных. + */ + public class Loader3D extends EventDispatcher { + /** + * Текущее состояние загрузчика. Показывает наличие активных процессов загрузки. В качестве значений используются объявленные в классе константы. + */ + protected var loaderState:int = Loader3DState.IDLE; + /** + * Базовый URL основного файла сцены, не включающий имя файла. Если путь не пустой, то он заканчивается символом "/". + */ + protected var baseURL:String; + /** + * LoaderContext для загрузки файлов текстур. + */ + protected var loaderContext:LoaderContext; + /** + * Контейнер, который должен содержать объекты, загруженные из файла сцены. + */ + protected var _content:Object3D; + /** + * Загрузчик основного файла сцены. + */ + private var mainLoader:URLLoader; + + /** + * Создаёт новый экземпляр класса. + */ + public function Loader3D() { + super(this); + } + + /** + * Контейнер, содержащий все загруженные из сцены объекты. + */ + public final function get content():Object3D { + return _content; + } + + /** + * Загружает сцену из файла с заданным URL. Метод отменяет текущую загрузку. + * Если файл успешно загружен, вызывается метод parse(), который выполняет разбор полученных данных. + * + * @param url URL файла со сценой + * @param context LoaderContext для загрузки файлов текстур + * + * @see #parse() + */ + public final function load(url:String, loaderContext:LoaderContext = null):void { + this.baseURL = url.substring(0, url.lastIndexOf("/") + 1); + this.loaderContext = loaderContext; + + if (mainLoader == null) { + // Первоначальное создание загрузчика файла сцены + mainLoader = new URLLoader(); + mainLoader.dataFormat = URLLoaderDataFormat.BINARY; + mainLoader.addEventListener(Event.COMPLETE, onMainLoadingComplete); + mainLoader.addEventListener(ProgressEvent.PROGRESS, onMainLoadingProgress); + mainLoader.addEventListener(IOErrorEvent.IO_ERROR, onMainLoadingError); + mainLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMainLoadingError); + } else { + // Прекращение активной загрузки + close(); + } + _content = null; + // Загрузка основного файла + setState(Loader3DState.LOADING_MAIN); + mainLoader.load(new URLRequest(url)); + if (hasEventListener(LoaderEvent.LOADING_START)) { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MAIN_FILE)); + } + } + + /** + * Загружает сцену из массива бинарных данных. + * Для чтения и разбора данных из массива вызывается метод parse(). + * + * @param data данные сцены + * @param baseUrl базовый URL для файлов текстур + * @param loaderContext LoaderContext для загрузки файлов текстур + * + * @see #parse() + */ + public final function loadBytes(data:ByteArray, baseUrl:String = null, loaderContext:LoaderContext = null):void { + if (baseUrl == null) { + baseUrl = ""; + } else if (baseUrl.length > 0 && baseUrl.charAt(baseUrl.length - 1) != "/") { + baseUrl += "/"; + } + this.baseURL = baseUrl; + this.loaderContext = loaderContext; + close(); + _content = null; + parse(data); + } + + /** + * Прекращает текущую загрузку. Базовая реализация останавливает процесс загрузки основного файла сцены. Наследники должны расширять метод closeInternal для + * прекращения специфических процессов загрузки. + * + * @see #closeInternal() + */ + public final function close():void { + if (loaderState == Loader3DState.LOADING_MAIN) { + mainLoader.close(); + } + closeInternal(); + clean(); + setState(Loader3DState.IDLE); + } + + /** + * Очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время процесса загрузки данных. Реализация метода удаляет сылку на контейнер с загруженными объектами и вызывает метод unloadInternal(). + * + * @see #unloadInternal() + */ + public final function unload():void { + if (loaderState != Loader3DState.IDLE) { + return; + } + _content = null; + unloadInternal(); + } + + /** + * Метод вызывается из close() и должен прекращать процессы загрузки, инициированные наследниками. Базовая реализация не делает ничего. + * + * @see #close() + */ + protected function closeInternal():void { + } + + /** + * Вызывается из метода unload(). Наследники должны расширять метод для удаления внутренних ссылок на объекты. Базовая реализация не делает ничего. + * + * @see #unload() + */ + protected function unloadInternal():void { + } + + /** + * Устанавливает внутреннее состояние загрузчика. + * + * @param state новое состояние + */ + protected function setState(state:int):void { + loaderState = state; + } + + /** + * Наследники должны расширять метод, реализуя функционал по разбору данных. Базовая реализация не делает ничего. + * + * @param data данные трёхмерной сцены + */ + protected function parse(data:ByteArray):void { + } + + /** + * Обрабатывает ошибку, возникшую при загрузке основного файла. Базовая реализация устанавливает состояние загрузчика в STATE_IDLE и рассылает + * полученное событие. + * + * @param e событие, описывающего ошибку + */ + protected function onMainLoadingError(e:ErrorEvent):void { + setState(Loader3DState.IDLE); + dispatchEvent(e); + } + + /** + * Метод должен вызываться после того как все данные сцены успешно загружены. Метод переводит загрузчик в состояние ожидания, вызывает метод очистки + * clean() и рассылает событие Event.COMPLETE. + * + * @see #clean() + */ + protected final function complete():void { + setState(Loader3DState.IDLE); + clean(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * Метод должен очищать внутренние вспомогательные переменные. Вызывается из метода complete. Базовая реализация не делает ничего, наследники должны расширять + * метод при необходимости. + * + * @see #complete() + */ + protected function clean():void { + } + + /** + * Обработчик успешной загрузки основного файла. Запускает функцию parse() для разбора данных. + */ + private function onMainLoadingComplete(e:Event):void { + setState(Loader3DState.IDLE); + if (hasEventListener(LoaderEvent.LOADING_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MAIN_FILE)); + } + parse(mainLoader.data); + } + + /** + * Рассылает событие прогресса загрузки основного файла. + */ + private function onMainLoadingProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADING_PROGRESS)) { + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MAIN_FILE, 1, 0, e.bytesLoaded, e.bytesTotal)); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..71eb0e1 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,262 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.types.Texture; + + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.utils.ByteArray; + + use namespace alternativa3d; + + /** + * Загрузчик сцен в формате 3DS. + *

+ * Класс предоставляет возможность загрузить 3DS-данные из сети или из бинарного массива. Полученные данные разбираются с помощью класса + * Parser3DS, после чего автоматически загружаются текстуры, используемые в сцене, которые затем назначаются текстурным материалам. + * Если при загрузке текстуры происходит ошибка (например, файл отсутствует), то текстура заменяется текстурой-заглушкой и рассылается + * сообщение IOErrorEvent. + *

+ *

+ * Перед загрузкой данных можно установить ряд свойств, влияющих на создаваемые текстурные материалы. + *

+ * + * @see alternativa.engine3d.loaders.Parser3DS + */ + public class Loader3DS extends Loader3D { + + /** + * Если указано значение false, то материалы загружаться не будут. + * + * @default true + */ + public var loadMaterials:Boolean = true; + + private var bitmapLoader:TextureMapsBatchLoader; + private var parser:Parser3DS; + + /** + * Создаёт новый экземпляр класса. + */ + public function Loader3DS() { + super(); + parser = new Parser3DS(); + } + + /** + * Значение свойства repeat для текстурных материалов. + * + * @default true + * @see alternativa.engine3d.materials.TextureMaterial + */ + public function get repeat():Boolean { + return parser.repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + parser.repeat = value; + } + + /** + * Значение свойства smooth для текстурных материалов. + * + * @default false + * @see alternativa.engine3d.materials.TextureMaterial#smooth + */ + public function get smooth():Boolean { + return parser.smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + parser.smooth = value; + } + + /** + * Значение свойства blendMode для текстурных материалов. + * + * @default BlendMode.NORMAL + * @see alternativa.engine3d.materials.Material#blendMode + */ + public function get blendMode():String { + return parser.blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + parser.blendMode = value; + } + + /** + * Значение свойства precision для текстурных материалов. + * + * @default TextureMaterialPrecision.MEDIUM + * @see alternativa.engine3d.materials.TextureMaterial#precision + * @see alternativa.engine3d.materials.TextureMaterialPrecision + */ + public function get precision():Number { + return parser.precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + parser.precision = value; + } + + /** + * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + * @default 1 + */ + public function get scale():Number { + return parser.scale; + } + + /** + * @private + */ + public function set scale(value:Number):void { + parser.scale = value; + } + + /** + * Уровень мобильности для загруженных объектов. + * @default 0 + * @see alternativa.engine3d.core.Object3D#mobility + */ + public function get mobility():int { + return parser.mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + parser.mobility = value; + } + + /** + * Выполняет действия для прекращения текущей загрузки. + */ + override protected function closeInternal():void { + super.closeInternal(); + if (loaderState == Loader3DState.LOADING_TEXTURE) { + bitmapLoader.close(); + } + } + + /** + * @inheritDoc + */ + override protected function unloadInternal():void { + if (bitmapLoader != null) { + bitmapLoader.unload(); + } + } + + /** + * Разбирает загруженные данные. + * + * @param data данные в формате 3DS + */ + override protected function parse(data:ByteArray):void { + parser.parse(data); + _content = parser.content; + + if (loadMaterials && parser.textureMaterials != null) { + loadTextures(); + } else { + complete(); + } + } + + /** + * Запускает процесс загрузки текстур. + */ + private function loadTextures():void { + if (bitmapLoader == null) { + bitmapLoader = new TextureMapsBatchLoader(); + bitmapLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart); + bitmapLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete); + bitmapLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress); + bitmapLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete); + bitmapLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError); + } + setState(Loader3DState.LOADING_TEXTURE); + bitmapLoader.load(baseURL, parser.textureMaterials, loaderContext); + } + + /** + * Обрабатывает сообщение об ошибке загрузки текстуры. + */ + private function onTextureLoadingError(e:IOErrorEvent):void { + dispatchEvent(e); + } + + /** + * Обрабатывает сообщение о начале загрузки очередной текстуры. + */ + private function onTextureLoadingStart(e:LoaderEvent):void { + if (hasEventListener(e.type)) { + dispatchEvent(e); + } + } + + /** + * Обрабатывает сообщение об окончании загрузки очередного файла текстуры. + */ + private function onTextureLoadingComplete(e:LoaderEvent):void { + if (hasEventListener(e.type)) { + dispatchEvent(e); + } + } + + /** + * Рассылает событие прогресса загрузки очередного файла текстуры. + */ + private function onTextureLoadingProgress(e:LoaderProgressEvent):void { + if (hasEventListener(e.type)) { + dispatchEvent(e); + } + } + + /** + * Устанавливает текстуры для материалов после завершения загрузки всех текстур. + */ + private function onTextureMaterialsLoadingComplete(e:Event):void { + parser.content.forEach(setTextures); + complete(); + } + + /** + * Устанавливает текстуры для текстурных материалов объекта. + */ + private function setTextures(object:Object3D):void { + var mesh:Mesh = object as Mesh; + if (mesh != null) { + for (var key:String in mesh._surfaces) { + var textureMapsInfo:TextureMapsInfo = parser.textureMaterials[key]; + if (textureMapsInfo != null) { + var texture:Texture = new Texture(bitmapLoader.textures[key], textureMapsInfo.diffuseMapFileName); + var s:Surface = mesh._surfaces[key]; + TextureMaterial(s.material).texture = texture; + } + } + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as new file mode 100644 index 0000000..4650bd6 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.loaders { + + /** + * @private + * Возможные состояния загрузчиков. + */ + public final class Loader3DState { + + public static const IDLE:int = 0; + public static const LOADING_MAIN:int = 1; + public static const LOADING_TEXTURE:int = 2; + public static const LOADING_LIBRARY:int = 3; + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as new file mode 100644 index 0000000..2958619 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as @@ -0,0 +1,257 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.types.Map; + import alternativa.utils.ColorUtils; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + use namespace alternativa3d; + + /** + * Событие рассылается после начала загрузки. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие рассылается в процессе получения данных во время загрузки. + * + * @eventType flash.events.ProgressEvent.PROGRESS + */ + [Event (name="progress", type="flash.events.ProgressEvent")] + /** + * Событие рассылается после завершении загрузки. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие рассылается при ошибке загрузки. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие рассылается при нарушении безопасности. + * + * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR + */ + [Event (name="securityError", type="flash.events.SecurityErrorEvent")] + /** + * @private + * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ). + *

+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала. + *

+ */ + public class LoaderMTL extends EventDispatcher { + + private static const COMMENT_CHAR:String = "#"; + private static const CMD_NEW_MATERIAL:String = "newmtl"; + private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd"; + private static const CMD_DISSOLVE:String = "d"; + private static const CMD_MAP_DIFFUSE:String = "map_Kd"; + private static const CMD_MAP_DISSOLVE:String = "map_d"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private static const STATE_IDLE:int = 0; + private static const STATE_LOADING:int = 1; + + // Загрузчик файла MTL + private var mtlFileLoader:URLLoader; + + // Библиотека загруженных материалов + private var _library:Map; + // Имя текущего материала + private var materialName:String; + // параметры текущего материала + private var currentMaterialInfo:MTLMaterialInfo = new MTLMaterialInfo(); + + private var loaderState:int = STATE_IDLE; + + /** + * Создаёт новый экземпляр класса. + */ + public function LoaderMTL() { + } + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING) { + mtlFileLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + _library = null; + } + } + + /** + * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса + * alternativa.engine3d.loaders.MaterialInfo. + * @see alternativa.engine3d.loaders.MaterialInfo + */ + public function get library():Map { + return _library; + } + + /** + * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и + * формирование библиотеки материалов. После окончания работы метода посылается сообщение + * Event.COMPLETE и становится доступна библиотека материалов через свойство library. + *

+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и + *

+ * SecurityErrorEvent.SECURITY_ERROR соответственно. + *

+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку. + *

+ *

+ * @param url URL MTL-файла + * @param loaderContext LoaderContext для загрузки файлов текстур + * + * @see #library + */ + public function load(url:String):void { + if (mtlFileLoader == null) { + mtlFileLoader = new URLLoader(); + mtlFileLoader.addEventListener(Event.OPEN, retranslateEvent); + mtlFileLoader.addEventListener(ProgressEvent.PROGRESS, retranslateEvent); + mtlFileLoader.addEventListener(Event.COMPLETE, parseMTLFile); + mtlFileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError); + mtlFileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); + } else { + close(); + } + loaderState = STATE_LOADING; + mtlFileLoader.load(new URLRequest(url)); + } + + /** + * + */ + private function retranslateEvent(e:Event):void { + dispatchEvent(e); + } + + /** + * Обработка ошибки загрузки MTL-файла. + */ + private function onError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + /** + * Разбор содержимого загруженного файла материалов. + */ + private function parseMTLFile(e:Event = null):void { + loaderState = STATE_IDLE; + parse(mtlFileLoader.data); + complete(); + } + + /** + * + */ + public function parse(data:String):void { + _library = new Map(); + data.split(REGEXP_SPLIT_FILE).forEach(parseLine); + } + + /** + * Разбор строки файла. + * + * @param line строка файла + */ + private function parseLine(line:String, index:int, lines:Array):void { + line = line.replace(REGEXP_TRIM,"$1") + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + case CMD_NEW_MATERIAL: + defineMaterial(parts); + break; + case CMD_DIFFUSE_REFLECTIVITY: + readDiffuseReflectivity(parts); + break; + case CMD_DISSOLVE: + readAlpha(parts); + break; + case CMD_MAP_DIFFUSE: + currentMaterialInfo.diffuseMapInfo = MTLTextureMapInfo.parse(parts); + break; + case CMD_MAP_DISSOLVE: + currentMaterialInfo.dissolveMapInfo = MTLTextureMapInfo.parse(parts); + break; + } + } + + /** + * Определение нового материала. + */ + private function defineMaterial(parts:Array = null):void { + materialName = parts[1]; + currentMaterialInfo = new MTLMaterialInfo(); + _library[materialName] = currentMaterialInfo; + } + + /** + * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей + * версии движка данные коэффициенты преобразуются в цвет материала. + */ + private function readDiffuseReflectivity(parts:Array):void { + var r:Number = Number(parts[1]); + // Проверка, заданы ли коэффициенты в виде r g b + if (!isNaN(r)) { + var g:Number = Number(parts[2]); + var b:Number = Number(parts[3]); + currentMaterialInfo.color = ColorUtils.rgb(255*r, 255*g, 255*b); + } + } + + /** + * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом + * (не поддерживается параметр -halo). + */ + private function readAlpha(parts:Array):void { + var alpha:Number = Number(parts[1]); + if (!isNaN(alpha)) { + currentMaterialInfo.alpha = alpha; + } + } + + /** + * Обработка успешного завершения загрузки. + */ + private function complete():void { + loaderState = STATE_IDLE; + dispatchEvent(new Event(Event.COMPLETE)); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as new file mode 100644 index 0000000..5b31862 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as @@ -0,0 +1,423 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.types.Map; + import alternativa.types.Texture; + + import flash.display.BlendMode; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Point; + import flash.utils.ByteArray; + + use namespace alternativa3d; + + /** + * Загрузчик моделей из файла в формате OBJ. Загрузчик использует класс ParserOBJ для создания объектов из OBJ-файла, после чего выполняет загрузку + * библиотек материалов, создаёт материалы и назначает их объектам. + *

+ * При загрузке текстурных материалов учитывается наличие карт прозрачности. + *

+ * + * @see alternativa.engine3d.loaders.ParserOBJ + */ + public class LoaderOBJ extends Loader3D { + + /** + * Если указано значение false, то материалы загружаться не будут. + * + * @default true + */ + public var loadMaterials:Boolean = true; + + /** + * Значение свойства smooth для текстурных материалов. + * + * @default false + * @see alternativa.engine3d.materials.TextureMaterial#smooth + */ + public var smooth:Boolean = false; + + /** + * Значение свойства blendMode для текстурных материалов. + * + * @default BlendMode.NORMAL + * @see alternativa.engine3d.materials.Material#blendMode + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Значение свойства precision для текстурных материалов. + * + * @default TextureMaterialPrecision.MEDIUM + * @see alternativa.engine3d.materials.TextureMaterial#precision + * @see alternativa.engine3d.materials.TextureMaterialPrecision + */ + public var precision:Number = TextureMaterialPrecision.MEDIUM; + + private static const STATE_LOADING_LIBRARY:int = 3; + + private var mtlLoader:LoaderMTL; + private var materialsLibrary:Map; + private var uv:Point = new Point(); + private var parser:ParserOBJ; + + private var currentMaterialFileIndex:int; + private var numMaterialFiles:int; + private var textureMaterials:Map; + + private var bitmapsLoader:TextureMapsBatchLoader; + + /** + * Создаёт новый экземпляр класса. + */ + public function LoaderOBJ() { + super(); + parser = new ParserOBJ(); + } + + /** + * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + * @default 1 + */ + public function get scale():Number { + return parser.scale; + } + + /** + * @private + */ + public function set scale(value:Number):void { + parser.scale = value; + } + + /** + * При установленном значении true объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут + * использовать слово "g" для определения объектов в OBJ-файле. + * + * @default false + */ + public function get objectsAsGroups():Boolean { + return parser.objectsAsGroups; + } + + /** + * @private + */ + public function set objectsAsGroups(value:Boolean):void { + parser.objectsAsGroups = value; + } + + /** + * При установленном значении true выполняется преобразование координат геометрических вершин посредством + * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью Y, + * в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z). + * + * @default false + */ + public function get rotateModel():Boolean { + return parser.rotateModel; + } + + /** + * @private + */ + public function set rotateModel(value:Boolean):void { + parser.rotateModel = value; + } + + /** + * Уровень мобильности для загруженных объектов. + * @default 0 + * @see alternativa.engine3d.core.Object3D#mobility + */ + public function get mobility():int { + return parser.mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + parser.mobility = value; + } + + /** + * @inheritDoc + */ + override protected function closeInternal():void { + super.closeInternal(); + if (loaderState == Loader3DState.LOADING_LIBRARY) { + mtlLoader.close(); + } + } + + /** + * Метод очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + override protected function unloadInternal():void { + if (mtlLoader != null) { + mtlLoader.unload(); + } + if (bitmapsLoader != null) { + bitmapsLoader.unload(); + } + } + + /** + * @inheritDoc + */ + override protected function clean():void { + super.clean(); + loaderContext = null; + textureMaterials = null; + } + + /** + * Метод выполняет разбор данных, полученных из OBJ-файла. + * + * @param s содержимое OBJ-файла + * @param materialLibrary библиотека материалов + * @return объект, содержащий все трёхмерные объекты, определённые в OBJ-файле + */ + override protected function parse(data:ByteArray):void { + parser.parse(data.toString()); + // После разбора файла имеем дерево 3д-объектов и массив с именами файлов библиотек материалов + _content = parser.content; + // Загружаем библиотеки материалов или заканчиваем работу, если материалов нет + numMaterialFiles = parser.materialFileNames.length; + if (loadMaterials && numMaterialFiles > 0) { + loadMaterialsLibrary(); + } else { + complete(); + } + } + + /** + * Загружает библиотеки материалов. + * + * @param materialFileNames массив с именами файлов библиотек материалов + */ + private function loadMaterialsLibrary():void { + loaderState = Loader3DState.LOADING_LIBRARY; + if (mtlLoader == null) { + mtlLoader = new LoaderMTL(); + mtlLoader.addEventListener(Event.OPEN, onMaterialLibLoadingStart); + mtlLoader.addEventListener(ProgressEvent.PROGRESS, onMaterialLibLoadingProgress); + mtlLoader.addEventListener(Event.COMPLETE, onMaterialLibLoadingComplete); + mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialLibLoadingError); + mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMaterialLibLoadingError); + } + materialsLibrary = new Map(); + + currentMaterialFileIndex = -1; + loadNextMaterialFile(); + } + + /** + * Обрабатывает ошибки при загрузке библиотеки материалов. + */ + private function onMaterialLibLoadingError(e:ErrorEvent):void { + close(); + dispatchEvent(e); + } + + /** + * + */ + private function onMaterialLibLoadingStart(e:Event):void { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MATERIAL_LIBRARY)); + } + + /** + * + */ + private function onMaterialLibLoadingProgress(e:ProgressEvent):void { + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MATERIAL_LIBRARY, numMaterialFiles, currentMaterialFileIndex, e.bytesLoaded, e.bytesTotal)); + } + + /** + * Обработка успешной загрузки библиотеки материалов. + */ + private function onMaterialLibLoadingComplete(e:Event):void { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MATERIAL_LIBRARY)); + // Слияние загруженной библиотеки с уже имеющейся + materialsLibrary.concat(mtlLoader.library); + // Загрузка следующего файла материалов + loadNextMaterialFile(); + } + + /** + * Загрузка и разбор очередного файла материалов. + */ + private function loadNextMaterialFile():void { + currentMaterialFileIndex++; + if (currentMaterialFileIndex == numMaterialFiles) { + // Все материалы загружены, проверяется наличие текстурных материалов и выполняется их загрузка + checkMaterials(); + } else { + mtlLoader.load(baseURL + parser.materialFileNames[currentMaterialFileIndex]); + } + } + + /** + * + */ + private function checkMaterials():void { + collectTextureMaterialNames(); + if (textureMaterials != null) { + loadTextures(); + } else { + setMaterials(); + complete(); + } + } + + /** + * Запускает процесс загрузки текстур. + */ + private function loadTextures():void { + if (bitmapsLoader == null) { + bitmapsLoader = new TextureMapsBatchLoader(); + bitmapsLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart); + bitmapsLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete); + bitmapsLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress); + bitmapsLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete); + bitmapsLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError); + } + setState(Loader3DState.LOADING_TEXTURE); + bitmapsLoader.load(baseURL, textureMaterials, loaderContext); + } + + /** + * Обрабатывает неудачную загрузку текстуры. + */ + private function onTextureLoadingError(e:IOErrorEvent):void { + dispatchEvent(e); + } + + /** + * Обрабатывает начало загрузки очередного файла текстуры. + */ + private function onTextureLoadingStart(e:Event):void { + dispatchEvent(e); + } + + /** + * Обрабатывает окончание загрузки очередного файла текстуры. + */ + private function onTextureLoadingComplete(e:Event):void { + dispatchEvent(e); + } + + /** + * Рассылает событие прогресса загрузки файла текстуры. + */ + private function onTextureLoadingProgress(e:LoaderProgressEvent):void { + dispatchEvent(e); + } + + /** + * Обрабатывает завершение загрузки текстур материала. + */ + private function onTextureMaterialsLoadingComplete(e:Event):void { + setMaterials(); + complete(); + } + + /** + * + */ + private function collectTextureMaterialNames():void { + for (var materialName:String in materialsLibrary) { + var info:MTLMaterialInfo = materialsLibrary[materialName]; + if (info.diffuseMapInfo != null) { + if (textureMaterials == null) { + textureMaterials = new Map(); + } + var textureMapsInfo:TextureMapsInfo = new TextureMapsInfo(); + textureMapsInfo.diffuseMapFileName = info.diffuseMapInfo.fileName; + if (info.dissolveMapInfo != null) { + textureMapsInfo.opacityMapFileName = info.dissolveMapInfo.fileName; + } + textureMaterials.add(materialName, textureMapsInfo); + } + } + } + + /** + * Устанавливает материалы. + */ + private function setMaterials():void { + if (materialsLibrary != null) { + for (var objectKey:* in _content.children) { + var object:Mesh = objectKey; + for (var surfaceKey:* in object._surfaces) { + var surface:Surface = object._surfaces[surfaceKey]; + // Поверхности имеют идентификаторы, соответствующие именам материалов + var materialInfo:MTLMaterialInfo = materialsLibrary[surfaceKey]; + if (materialInfo != null) { + if (materialInfo.diffuseMapInfo == null) { + surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode); + } else { + var texture:Texture = new Texture(bitmapsLoader.textures[surfaceKey], materialInfo.diffuseMapInfo.fileName); + surface.material = new TextureMaterial(texture, materialInfo.alpha, materialInfo.diffuseMapInfo.repeat, smooth, blendMode, -1, 0, precision); + transformUVs(surface, new Point(materialInfo.diffuseMapInfo.offsetU, materialInfo.diffuseMapInfo.offsetV), new Point(materialInfo.diffuseMapInfo.sizeU, materialInfo.diffuseMapInfo.sizeV)); + } + } + } + } + } + } + + /** + * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MTL предусмотрено + * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо + * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения, + * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину + * смещения. + * + * @param surface поверхность, грани которой обрабатываюся + * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y + * указывает смещение по V. + * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования + * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V. + */ + private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void { + for (var key:* in surface._faces) { + var face:Face = key; + if (face._aUV) { + uv.x = face._aUV.x; + uv.y = face._aUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y; + face.aUV = uv; + + uv.x = face._bUV.x; + uv.y = face._bUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y; + face.bUV = uv; + + uv.x = face._cUV.x; + uv.y = face._cUV.y; + uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x; + uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y; + face.cUV = uv; + } + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as new file mode 100644 index 0000000..0e4aad2 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders { + + /** + * Набор констант, которые описывают этапы загрузки. + */ + public final class LoadingStage { + /** + * Обозначает загрузку основного файла. + */ + public static const MAIN_FILE:int = 0; + /** + * Обозначает загрузку текстур. + */ + public static const TEXTURE:int = 1; + /** + * Обозначает загрузку библиотек материалов. + */ + public static const MATERIAL_LIBRARY:int = 2; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as new file mode 100644 index 0000000..4149a8b --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as @@ -0,0 +1,16 @@ +package alternativa.engine3d.loaders { + import flash.display.BitmapData; + import flash.geom.Point; + + /** + * @private + * Класс содержит обобщённую информацию о материале. + */ + public class MTLMaterialInfo { + public var color:uint; + public var alpha:Number; + + public var diffuseMapInfo:MTLTextureMapInfo; + public var dissolveMapInfo:MTLTextureMapInfo; + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as new file mode 100644 index 0000000..421b3cb --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.loaders { + /** + * @private + * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора + * описания текстуры. + * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/ + */ + public class MTLTextureMapInfo { + + // Ассоциация параметров команды объявления текстуры и методов для их чтения + private static const optionReaders:Object = { + "-clamp": clampReader, + "-o": offsetReader, + "-s": sizeReader, + + "-blendu": stubReader, + "-blendv": stubReader, + "-bm": stubReader, + "-boost": stubReader, + "-cc": stubReader, + "-imfchan": stubReader, + "-mm": stubReader, + "-t": stubReader, + "-texres": stubReader + }; + + // Смещение в текстурном пространстве + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var offsetW:Number = 0; + + // Масштабирование текстурного пространства + public var sizeU:Number = 1; + public var sizeV:Number = 1; + public var sizeW:Number = 1; + + // Флаг повторения текстуры + public var repeat:Boolean = true; + // Имя файла текстуры + public var fileName:String; + + /** + * Метод выполняет разбор данных о текстуре. + * + * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла. + * @return объект, содержащий данные о текстуре + */ + public static function parse(parts:Array):MTLTextureMapInfo { + var info:MTLTextureMapInfo = new MTLTextureMapInfo(); + // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры + var index:int = 1; + var reader:Function; + // Чтение параметров текстуры + while ((reader = optionReaders[parts[index]]) != null) { + index = reader(index, parts, info); + } + // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры + info.fileName = parts[index]; + return info; + } + + /** + * Читатель-заглушка. Пропускает все неподдерживаемые параметры. + */ + private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + index++; + var maxIndex:int = parts.length - 1; + while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) { + index++; + } + return index; + } + + /** + * Метод чтения параметров масштабирования текстурного пространства. + */ + private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.sizeU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.sizeV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.sizeW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметров смещения текстуры. + */ + private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.offsetU = Number(parts[index + 1]); + index += 2; + var value:Number = Number(parts[index]); + if (!isNaN(value)) { + info.offsetV = value; + index++; + value = Number(parts[index]); + if (!isNaN(value)) { + info.offsetW = value; + index++; + } + } + return index; + } + + /** + * Метод чтения параметра повторения текстуры. + */ + private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int { + info.repeat = parts[index + 1] == "off"; + return index + 2; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..f2d9932 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,1121 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.Map; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.ColorUtils; + import alternativa.utils.MathUtils; + + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * Класс позволяет разобрать бинарные данные в формате 3DS. После разбора данных получается дерево трёхмерных объектов и список используемых текстур. + *

+ * В процессе построения дерева объектов неподдерживаемые объекты заменяются экземплярами Object3D. + *

+ *

+ * Материалы, для которых указан файл диффузной текстуры, представляются в виде TextureMaterial с пустой текстурой, которая должна быть установлена позднее. + * Информация о файлах диффузной карты и карты прозрачности добавляется в список textureMaterials. При этом имена файлов переводятся в нижний регистр. + *

+ *

+ * Материалы, для которых файл диффузной текстуры не указан, представляются экземпляром FillMaterial с указанным цветом. + *

+ *

+ * При формировании каждого объекта, имеющие одинаковый материал грани объединяются в поверхности. Идентификаторы поверхностей совпадают + * с названием соответствующего материала. Если для объекта не задан ни один материал, то все грани помещаются в одну поверхность, для которой + * устанавливается материал WireMaterial с линиями серого цвета. + *

+ *

+ * Класс имеет ряд свойств, влияющих на создаваемые текстурные материалы. + *

+ */ + public class Parser3DS { + private static const CHUNK_MAIN:uint = 0x4D4D; + private static const CHUNK_VERSION:uint = 0x0002; + private static const CHUNK_SCENE:uint = 0x3D3D; + private static const CHUNK_ANIMATION:uint = 0xB000; + private static const CHUNK_OBJECT:uint = 0x4000; + private static const CHUNK_TRIMESH:uint = 0x4100; + private static const CHUNK_VERTICES:uint = 0x4110; + private static const CHUNK_FACES:uint = 0x4120; + private static const CHUNK_FACESMATERIAL:uint = 0x4130; + private static const CHUNK_MAPPINGCOORDS:uint = 0x4140; + private static const CHUNK_OBJECTCOLOR:uint = 0x4165; + private static const CHUNK_TRANSFORMATION:uint = 0x4160; + private static const CHUNK_MESHANIMATION:uint = 0xB002; + private static const CHUNK_MATERIAL:uint = 0xAFFF; + + private var data:ByteArray; + // Контейнер с объектами сцены + private var _content:Object3D; + // Мапа, хранящая имена файлов текстур. Ключи -- имена материалов, значения -- объекты типа TextureMapsInfo. + private var _textureMaterials:Map; + // Флаг повтора текстуры для создаваемых текстурных материалов. + private var _repeat:Boolean = true; + // Флаг сглаживания текстур для создаваемых текстурных материалов. + private var _smooth:Boolean = false; + // Режим наложения цвета для создаваемых текстурных материалов. + private var _blendMode:String = BlendMode.NORMAL; + // Точность перспективной коррекции для создаваемых текстурных материалов. + private var _precision:Number = TextureMaterialPrecision.MEDIUM; + // Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + private var _scale:Number = 1; + // Уровень мобильности для загруженных объектов. + private var _mobility:int = 0; + + // Внутренние переменные для хранения промежуточных данных при разборе + private var version:uint; + private var objectDatas:Map; + private var animationDatas:Array; + private var materialDatas:Map; + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Object3D { + return _content; + } + + /** + * Флаг повтора текстуры для создаваемых текстурных материалов. + * + * @default true + * @see alternativa.engine3d.materials.TextureMaterial#repeat + */ + public function get repeat():Boolean { + return _repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + _repeat = value; + } + + /** + * Флаг сглаживания текстур для создаваемых текстурных материалов. + * + * @default false + * @see alternativa.engine3d.materials.TextureMaterial#smooth + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + _smooth = value; + } + + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @default BlendMode.NORMAL + * @see alternativa.engine3d.materials.Material#blendMode + */ + public function get blendMode():String { + return _blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + _blendMode = value; + } + + /** + * Точность перспективной коррекции для создаваемых текстурных материалов. + * + * @default TextureMaterialPrecision.MEDIUM + * @see alternativa.engine3d.materials.TextureMaterial#precision + * @see alternativa.engine3d.materials.TextureMaterialPrecision + */ + public function get precision():Number { + return _precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + _precision = value; + } + + /** + * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + * @default 1 + */ + public function get scale():Number { + return _scale; + } + + /** + * @private + */ + public function set scale(value:Number):void { + _scale = value; + } + + /** + * Уровень мобильности для загруженных объектов. + * @default 0 + * @see alternativa.engine3d.core.Object3D#mobility + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + _mobility = value; + } + + /** + * Список файлов текстур. Ключами являются имена материалов, значениями — объекты класса TextureMapsInfo, содержащие + * имена файлов диффузной текстуры и карты прозрачности. + * + * @see alternativa.engine3d.loaders.TextureMapsInfo + */ + public function get textureMaterials():Map { + return _textureMaterials; + } + + /** + * Разбирает 3DS-данные и формирует дерево трёхмерных объектов. Чтение данных начинается с текущей позиции в массиве. После успешного окончания работы метода + * контейнер с объектами становится доступным через свойство content. Свойство textureMaterials будет содержать имена файлов текстур для каждого + * текстурного материала, либо будет равно null, если в сцене нет текстурных материалов. + * + * @param data массив с данными в формате 3DS + * @return true в случае успешного разбора данных, иначе false + * + * @see #content + * @see #textureMaterials + */ + public function parse(data:ByteArray):Boolean { + unload(); + + if (data.bytesAvailable < 6) { + return false; + } + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + + try { + parse3DSChunk(data.position, data.bytesAvailable); + buildContent(); + } catch (e:Error) { + unload(); + throw e; + } finally { + clean(); + } + + return true; + } + + /** + * Удаляет внутренние ссылки на сформированные данные. + */ + public function unload():void { + _content = null; + _textureMaterials = null; + } + + /** + * Удаляет ссылки на временные данные. + */ + private function clean():void { + version = 0; + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + /** + * Читает заголовок блока и возвращает его описание. + * + * @param dataPosition + * @param bytesAvailable + * @return + */ + private function readChunkInfo(dataPosition:uint, bytesAvailable:uint):ChunkInfo { + if (bytesAvailable < 6) { + return null; + } + data.position = dataPosition; + var chunkInfo:ChunkInfo = new ChunkInfo(); + chunkInfo.id = data.readUnsignedShort(); + chunkInfo.size = data.readUnsignedInt(); + chunkInfo.dataSize = chunkInfo.size - 6; + chunkInfo.dataPosition = data.position; + chunkInfo.nextChunkPosition = dataPosition + chunkInfo.size; + return chunkInfo; + } + + /** + * + */ + private function parse3DSChunk(dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + data.position = dataPosition; + + switch (chunkInfo.id) { + // Главный + case CHUNK_MAIN: + parseMainChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + + parse3DSChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseMainChunk(dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Версия + case CHUNK_VERSION: + version = data.readUnsignedInt(); + break; + // 3D-сцена + case CHUNK_SCENE: + parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Анимация + case CHUNK_ANIMATION: + parseAnimationChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + + parseMainChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parse3DChunk(dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Материал + case CHUNK_MATERIAL: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Объект + case CHUNK_OBJECT: + parseObject(chunkInfo); + break; + } + + parse3DChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseObject(chunkInfo:ChunkInfo):void { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Map(); + } + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + // Получаем название объекта + object.name = getString(chunkInfo.dataPosition); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + // Парсим объект + var offset:int = object.name.length + 1; + parseObjectChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + /** + * + */ + private function parseObjectChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Меш + case CHUNK_TRIMESH: + parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseMeshChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Вершины + case CHUNK_VERTICES: + parseVertices(object); + break; + // UV + case CHUNK_MAPPINGCOORDS: + parseUVs(object); + break; + // Трансформация + case CHUNK_TRANSFORMATION: + parseMatrix(object); + break; + // Грани + case CHUNK_FACES: + parseFaces(object, chunkInfo); + break; + } + + parseMeshChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseVertices(object:ObjectData):void { + var num:uint = data.readUnsignedShort(); + object.vertices = new Array(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + /** + * + */ + private function parseUVs(object:ObjectData):void { + var num:uint = data.readUnsignedShort(); + object.uvs = new Array(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + /** + * + */ + private function parseMatrix(object:ObjectData):void { + object.matrix = new Matrix3D(); + object.matrix.a = data.readFloat(); + object.matrix.e = data.readFloat(); + object.matrix.i = data.readFloat(); + object.matrix.b = data.readFloat(); + object.matrix.f = data.readFloat(); + object.matrix.j = data.readFloat(); + object.matrix.c = data.readFloat(); + object.matrix.g = data.readFloat(); + object.matrix.k = data.readFloat(); + object.matrix.d = data.readFloat(); + object.matrix.h = data.readFloat(); + object.matrix.l = data.readFloat(); + } + + /** + * + */ + private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void { + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг отрисовки рёбер + } + var offset:uint = 2 + 8*num; + parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + /** + * + */ + private function parseFacesChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Поверхности + case CHUNK_FACESMATERIAL: + parseSurface(object); + break; + } + + parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseSurface(object:ObjectData):void { + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Map(); + } + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Получаем название материала + surface.materialName = getString(data.position); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + // Получаем грани поверхности + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(num); + for (var i:uint = 0; i < num; i++) { + surface.faces[i] = data.readUnsignedShort(); + } + } + + /** + * + */ + private function parseAnimationChunk(dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + if (animationDatas == null) { + animationDatas = new Array(); + } + var animation:AnimationData = new AnimationData(); + animationDatas.push(animation); + parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Идентификация объекта и его связь + case 0xB010: + // Имя объекта + animation.objectName = getString(data.position); + data.position += 4; + // Индекс родительского объекта в линейном списке объектов сцены + animation.parentIndex = data.readUnsignedShort(); + break; + // Имя dummy объекта + case 0xB011: + animation.objectName = getString(data.position); + break; + // Точка привязки объекта (pivot) + case 0xB013: + animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Смещение объекта относительно родителя + case 0xB020: + data.position += 20; + animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + data.position += 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + data.position += 20; + animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + } + + parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function parseMaterialChunk(material:MaterialData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + var textureMapsInfo:TextureMapsInfo; + switch (chunkInfo.id) { + // Имя материала + case 0xA000: + parseMaterialName(material); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = chunkInfo.dataPosition + 6; + material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte()); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = chunkInfo.dataPosition + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = chunkInfo.dataPosition + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = chunkInfo.dataPosition + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, chunkInfo.dataPosition, chunkInfo.dataSize); + textureMapsInfo = getTextureMapsInfo(material.name); + textureMapsInfo.diffuseMapFileName = material.diffuseMap.filename; + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, chunkInfo.dataPosition, chunkInfo.dataSize); + textureMapsInfo = getTextureMapsInfo(material.name); + textureMapsInfo.opacityMapFileName = material.opacityMap.filename; + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function getTextureMapsInfo(materialName:String):TextureMapsInfo { + if (_textureMaterials == null) { + _textureMaterials = new Map(); + } + var info:TextureMapsInfo = _textureMaterials[materialName]; + if (info == null) { + info = new TextureMapsInfo(); + _textureMaterials[materialName] = info; + } + return info; + } + + /** + * + */ + private function parseMaterialName(material:MaterialData):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Map(); + } + // Получаем название материала + material.name = getString(data.position); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + /** + * + */ + private function parseMapChunk(materialName:String, map:MapData, dataPosition:uint, bytesAvailable:uint):void { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable); + if (chunkInfo == null) { + return; + } + + switch (chunkInfo.id) { + // Имя файла + case 0xA300: + map.filename = getString(chunkInfo.dataPosition).toLowerCase(); + break; + case 0xA351: + // Параметры текстурирования +// trace("MAP OPTIONS", data.readShort().toString(2)); + break; + // Масштаб по U + case 0xA354: + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + /** + * + */ + private function buildContent():void { + // Формируем связи объектов + _content = new Object3D("container_3ds"); + + // Расчёт матриц текстурных материалов + buildMaterialMatrices(); + + var i:uint; + var length:uint; + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + // В сцене есть иерархически связанные оьъекты и (или) указаны данные о трансформации объектов. + if (animationDatas != null) { + if (objectDatas != null) { + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + // Для проверки, не представлен ли один объект в нескольких экземплярах, каждый раз проходим до конца списка секций анимации, + // проверяя совпадение очередного имени объекта с текущим именем. Если обнаруживается совпадение, в список объектных данных + // добавляется новая запись. Имя нового объекта формируется из имени оригинального объекта с добавлением значения счётчика. + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + // Найдено совпадение имени объекта в проверяемой секции анимации. Создаём описание нового экземпляра объекта. + var newName:String = objectName + nameCounter; + animationData2.objectName = newName; + var newObjectData:ObjectData = new ObjectData(); + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Array().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Array().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Создание полигонального объекта + mesh = new Mesh(objectName); + animationData.object = mesh; + setBasicObjectProperties(animationData); + buildMesh(mesh, objectData, animationData); + } else { + // Создание пустого 3д-объекта + var object:Object3D = new Object3D(objectName); + animationData.object = object; + setBasicObjectProperties(animationData); + } + } + // Создание дерева объектов + buildHierarchy(); + } + } else { + // В сцене нет иерархически связанных объектов и не заданы трансформации для объектов. В контейнер добавляются только полигональные объекты. + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(objectName); + buildMesh(mesh, objectData, null); + _content.addChild(mesh); + } + } + } + } + + /** + * Расчитывает матрицы преобразования UV-координат для всех текстурных материалов. + */ + private function buildMaterialMatrices():void { + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var materialMatrix:Matrix = new Matrix(); + var mapData:MapData = materialData.diffuseMap; + if (mapData != null) { + var rot:Number = MathUtils.toRadian(mapData.rotation); + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + } + + /** + * Устанавливает базовые свойства трёхмерных объектов. + * + * @param animationData + */ + private function setBasicObjectProperties(animationData:AnimationData):void { + var object:Object3D = animationData.object; + if (animationData.position != null) { + object.x = animationData.position.x*_scale; + object.y = animationData.position.y*_scale; + object.z = animationData.position.z*_scale; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + object.mobility = _mobility; + } + + /** + * Создаёт геометрию, поверхности и материалы объекта. + * + * @param mesh + * @param objectData + * @param animationData + */ + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:int; + var key:*; + var vertex:Vertex; + var face:Face; + var length:int = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Point3D = objectData.vertices[i]; + objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i); + } + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.d -= animationData.pivot.x; + objectData.matrix.h -= animationData.pivot.y; + objectData.matrix.l -= animationData.pivot.z; + } + // Трансформируем вершины + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.transform(objectData.matrix); + } + } + // Преобразование единиц измерения + for (key in mesh._vertices) { + vertex = mesh._vertices[key]; + vertex._coords.multiply(_scale); + } + // Добавляем грани + length = (objectData.faces == null) ? 0 : objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i); + if (objectData.uvs != null) { + face.aUV = objectData.uvs[faceData.a]; + face.bUV = objectData.uvs[faceData.b]; + face.cUV = objectData.uvs[faceData.c]; + } + } + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + var surfaceData:SurfaceData = objectData.surfaces[surfaceId]; + var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId); + if (materialData.diffuseMap != null) { + // Текстурный материал + length = surfaceData.faces.length; + for (i = 0; i < length; i++) { + face = mesh.getFaceById(surfaceData.faces[i]); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var m:Matrix = materialData.matrix; + var x:Number = face.aUV.x; + var y:Number = face.aUV.y; + face._aUV.x = m.a*x + m.b*y + m.tx; + face._aUV.y = m.c*x + m.d*y + m.ty; + x = face._bUV.x; + y = face._bUV.y; + face._bUV.x = m.a*x + m.b*y + m.tx; + face._bUV.y = m.c*x + m.d*y + m.ty; + x = face._cUV.x; + y = face._cUV.y; + face._cUV.x = m.a*x + m.b*y + m.tx; + face._cUV.y = m.c*x + m.d*y + m.ty; + } + } + // Материал создаётся с нулевой текстурой, которая в дальнейшем должна быть установлена внешними средствами + surface.material = new TextureMaterial(null, 1 - materialData.transparency/100, _repeat, _smooth, _blendMode, -1, 0, _precision); + } else { + surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100); + } + } + } else { + // Поверхность по умолчанию + var defaultSurface:Surface = mesh.createSurface(); + // Добавляем грани + for (var faceId:String in mesh._faces) { + defaultSurface.addFace(mesh._faces[faceId]); + } + defaultSurface.material = new WireMaterial(0, 0x7F7F7F); + } + } + + /** + * Создаёт дерево объектов сцены. + */ + private function buildHierarchy():void { + var len:Number = animationDatas.length; + for (var i:int = 0; i < len; i++) { + var animData:AnimationData = animationDatas[i]; + if (animData.parentIndex == 0xFFFF) { + _content.addChild(animData.object); + } else { + AnimationData(animationDatas[animData.parentIndex]).object.addChild(animData.object); + } + } + } + + /** + * Считывает строку, заканчивающуюся на нулевой байт. + * + * @param index + * @return + */ + private function getString(index:uint):String { + data.position = index; + var charCode:uint; + var res:String = ""; + while ((charCode = data.readByte()) != 0) { + res += String.fromCharCode(charCode); + } + return res; + } + + /** + * + * @param angle + * @param x + * @param z + * @param y + * @return + */ + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D { + var res:Point3D = new Point3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k <= -1) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.types.Matrix3D; +import alternativa.types.Point3D; + +import flash.geom.Matrix; +import alternativa.types.Map; +import flash.utils.ByteArray; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Array; + public var uvs:Array; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Map; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:uint; + public var pivot:Point3D; + public var position:Point3D; + public var rotation:Point3D; + public var scale:Point3D; +} + +/** + * + */ +class ChunkInfo { + public var id:uint; + public var size:uint; + public var dataSize:uint; + public var dataPosition:uint; + public var nextChunkPosition:uint; +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as new file mode 100644 index 0000000..b6d1d86 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as @@ -0,0 +1,374 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.types.Point3D; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Класс позволяет разобрать данные в формате OBJ. После обработки данных контейнер с трёхмерными объектами становится доступным через свойство content, а свойство + * materialFileNames содержит список имён файлов библиотек материалов. Каждый созданный объект содержит поверхности, идентификаторы которых совпадают с именами + * назначенных материалов, но сами материалы поверхностей не создаются, т.к. они будут известны только после загрузки библиотек материалов. + *

+ * При разборе данных распознаются следующие ключевые слова формата OBJ: + *

+ *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Ключевое словоОписаниеДействие парсера
o object_nameОбъявление нового объекта с именем object_nameЕсли для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем, + * иначе у текущего объекта просто меняется имя на указанное.
v x y zОбъявление вершины с координатами x y zВершина помещается в общий список вершин сцены для дальнейшего использования
vt u [v]Объявление текстурной вершины с координатами u vВершина помещается в общий список текстурных вершин сцены для дальнейшего использования
f v0[/vt0] v1[/vt1] ... vN[/vtN]Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность + * текущего объекта, соответствующую текущему материалу.
usemtl material_nameУстановка текущего материала с именем material_nameС момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность, + * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
mtllib file1 file2 ...Объявление файлов, содержащих определения материаловИмена файлов добавляются в список.

+ */ + public class ParserOBJ { + + private static const COMMENT_CHAR:String = "#"; + + private static const CMD_OBJECT_NAME:String = "o"; + private static const CMD_GROUP_NAME:String = "g"; + private static const CMD_VERTEX:String = "v"; + private static const CMD_TEXTURE_VERTEX:String = "vt"; + private static const CMD_FACE:String = "f"; + private static const CMD_MATERIAL_LIB:String = "mtllib"; + private static const CMD_USE_MATERIAL:String = "usemtl"; + + private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; + private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; + private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; + + private var objectKey:String = CMD_OBJECT_NAME; + + // Контейнер, содержащий все определённые в OBJ-файле объекты + private var _content:Object3D; + // Текущий конструируемый объект + private var currentObject:Mesh; + // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта + private var vIndexStart:int = 0; + // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта + private var vtIndexStart:int = 0; + // Глобальный массив вершин, определённых во входном файле + private var globalVertices:Array; + // Глобальный массив текстурных вершин, определённых во входном файле + private var globalTextureVertices:Array; + // Имя текущего активного материала. Если значение равно null, то активного материала нет. + private var currentMaterialName:String; + // Массив граней текущего объекта, которым назначен текущий материал + private var materialFaces:Array; + // Массив имён файлов, содержащих определения материалов + private var _materialFileNames:Array; + // Мобильность объектов + private var _mobility:int; + // Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + private var _scale:Number = 1; + // Флаг поворота объектов на 90 градусов по оси X + private var _rotateModel:Boolean; + + /** + * Создаёт новый экземпляр класса. + */ + public function ParserOBJ() { + } + + /** + * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение. + * @default 1 + */ + public function get scale():Number { + return _scale; + } + + /** + * @private + */ + public function set scale(value:Number):void { + _scale = value; + } + + /** + * При установленном значении true объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут + * использовать слово "g" для определения объектов в OBJ-файле. + * + * @default false + */ + public function get objectsAsGroups():Boolean { + return objectKey == CMD_GROUP_NAME; + } + + /** + * @private + */ + public function set objectsAsGroups(value:Boolean):void { + objectKey = value ? CMD_GROUP_NAME : CMD_OBJECT_NAME; + } + + /** + * Уровень мобильности для загруженных объектов. + * @default 0 + * @see alternativa.engine3d.core.Object3D#mobility + */ + public function get mobility():int { + return _mobility; + } + + /** + * @private + */ + public function set mobility(value:int):void { + _mobility = value; + } + + /** + * При установленном значении true выполняется преобразование координат геометрических вершин посредством + * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью Y, + * в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z). + * + * @default false + */ + public function get rotateModel():Boolean { + return _rotateModel; + } + + /** + * @private + */ + public function set rotateModel(value:Boolean):void { + _rotateModel = value; + } + + /** + * Контейнер, содержащий все определённые в OBJ-файле объекты. + */ + public function get content():Object3D { + return _content; + } + + /** + * Массив имён файлов, содержащих библиотеки материалов. + */ + public function get materialFileNames():Array { + return _materialFileNames; + } + + /** + * Разбирает содержимое OBJ-файла и формирует объекты. + * + * @param data данные OBJ-файла + */ + public function parse(data:String):void { + globalVertices = new Array(); + globalTextureVertices = new Array(); + _materialFileNames = new Array(); + + _content = new Object3D(); + createNewObject(""); + + var lines:Array = data.split(REGEXP_SPLIT_FILE); + lines.forEach(parseLine); + moveFacesToSurface(); + } + + /** + * + */ + private function createNewObject(objectName:String):void { + currentObject = new Mesh(objectName); + currentObject.mobility = _mobility; + _content.addChild(currentObject); + } + + /** + * Разбирает строку входного файла. + */ + private function parseLine(line:String, index:int, lines:Array):void { + line = line.replace(REGEXP_TRIM,"$1"); + if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { + return; + } + var parts:Array = line.split(REGEXP_SPLIT_LINE); + switch (parts[0]) { + // Объявление нового объекта + case objectKey: + defineObject(parts[1]); + break; + // Объявление вершины + case CMD_VERTEX: + globalVertices.push(new Point3D(Number(parts[1])*_scale, Number(parts[2])*_scale, Number(parts[3])*_scale)); + break; + // Объявление текстурной вершины + case CMD_TEXTURE_VERTEX: + globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3]))); + break; + // Объявление грани + case CMD_FACE: + createFace(parts); + break; + case CMD_MATERIAL_LIB: + storeMaterialFileNames(parts); + break; + case CMD_USE_MATERIAL: + setNewMaterial(parts); + break; + } + } + + /** + * Определяет новый объект. + * + * @param objectName имя объекта + */ + private function defineObject(objectName:String):void { + if (currentObject._faces.length == 0) { + // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя + currentObject.name = objectName; + } else { + // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект + moveFacesToSurface(); + createNewObject(objectName); + } + vIndexStart = globalVertices.length; + vtIndexStart = globalTextureVertices.length; + } + + /** + * Создаёт грани в текущем объекте. + * + * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1 + */ + private function createFace(parts:Array):void { + // Стартовый индекс вершины в объекте для добавляемой грани + var startVertexIndex:int = currentObject._vertices.length; + // Создание вершин в объекте + var faceVertexCount:int = parts.length - 1; + var vtIndices:Array = new Array(3); + // Массив идентификаторов вершин грани + var faceVertices:Array = new Array(faceVertexCount); + for (var i:int = 0; i < faceVertexCount; i++) { + var indices:Array = parts[i + 1].split("/"); + // Создание вершины + var vIdx:int = int(indices[0]); + // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1. + // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта. + var actualIndex:int = vIdx > 0 ? vIdx - 1 : (vIndexStart + vIdx); + + var vertex:Vertex = currentObject._vertices[actualIndex]; + // Если вершины нет в объекте, она добавляется + if (vertex == null) { + var p:Point3D = globalVertices[actualIndex]; + if (_rotateModel) { + // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот объекта на 90 градусов по оси X + vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex); + } else { + vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex); + } + } + faceVertices[i] = vertex; + + // Запись индекса текстурной вершины + if (i < 3) { + vtIndices[i] = int(indices[1]); + } + } + // Создание грани + var face:Face = currentObject.createFace(faceVertices, currentObject._faces.length); + // Установка uv координат + if (vtIndices[0] != 0) { + p = globalTextureVertices[vtIndices[0] - 1]; + face.aUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[1] - 1]; + face.bUV = new Point(p.x, p.y); + p = globalTextureVertices[vtIndices[2] - 1]; + face.cUV = new Point(p.x, p.y); + } + // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте + if (currentMaterialName != null) { + materialFaces.push(face); + } + } + + /** + * Сохраняет имена библиотек материалов. + * + * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1 + */ + private function storeMaterialFileNames(parts:Array):void { + for (var i:int = 1; i < parts.length; i++) { + _materialFileNames.push(parts[i]); + } + } + + /** + * Устанавливает новый текущий материал. + * + * @param parts массив, во втором элементе которого содержится имя материала + */ + private function setNewMaterial(parts:Array):void { + // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта + moveFacesToSurface(); + // Установка нового текущего материала + currentMaterialName = parts[1]; + } + + /** + * Добавляет все грани с текущим материалом в поверхность с идентификатором, совпадающим с именем материала. + */ + private function moveFacesToSurface():void { + if (currentMaterialName != null && materialFaces.length > 0) { + if (currentObject.hasSurface(currentMaterialName)) { + // При наличии поверхности с таким идентификатором, грани добавляются в неё + var surface:Surface = currentObject.getSurfaceById(currentMaterialName); + for each (var face:* in materialFaces) { + surface.addFace(face); + } + } else { + // При отсутствии поверхности с таким идентификатором, создатся новая поверхность + currentObject.createSurface(materialFaces, currentMaterialName); + } + } + if (materialFaces == null) { + materialFaces = new Array(); + } else { + materialFaces.length = 0; + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as new file mode 100644 index 0000000..c21dae9 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as @@ -0,0 +1,218 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + import alternativa.types.Map; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.system.LoaderContext; + + /** + * Событие рассылается в начале очередного этапа загрузки сцены. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START + */ + [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие рассылается в процессе получения данных во время загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS + */ + [Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + /** + * Событие рассылается после окончания очередного этапа загрузки сцены. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE + */ + [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие рассылается после окончания загрузки сцены. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Тип события, рассылаемого при возникновении ошибки загрузки текстуры. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + + /** + * @private + * Пакетный загрузчик текстур. Используется загрузчиками внешних сцен для получения битмапов текстур материалов. + */ + public class TextureMapsBatchLoader extends EventDispatcher { + /** + * Текстура-заглушка для замены незагруженных текстур. + */ + public static var stubBitmapData:BitmapData; + + // Загрузчик файлов текстур. + private var loader:TextureMapsLoader; + // Контекст безопасности загрузчика. + private var loaderContext:LoaderContext; + // Базовый URL файлов текстур. + private var baseUrl:String; + // Пакет с описанием текстур материалов. + private var batch:Map; + // Список имён материалов. + private var materialNames:Array; + // Общее количество файлов текстур. + private var totalFiles:int; + // Номер текущего + private var currFileIndex:int; + // Индекс текущего материала. + private var materialIndex:int; + // Результирующий список битмапов для каждого материала. + private var _textures:Map; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function TextureMapsBatchLoader() { + } + + /** + * Результирующий список битмапов для каждого материала. Ключами являются имена материалов, значениями -- объекты класса BitmapData. + */ + public function get textures():Map { + return _textures; + } + + /** + * Метод для получения текстуры-заглушки. + * + * @return текстура-заглушка для замещения незагруженных текстур + */ + private function getStubBitmapData():BitmapData { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stubBitmapData.setPixel((i%2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stubBitmapData; + } + + /** + * Прекращает текущую загрузку. + */ + public function close():void { + if (loader != null) { + loader.close(); + } + } + + /** + * Очищает внутренние ссылки на объекты. + */ + private function clean():void { + loaderContext = null; + batch = null; + materialNames = null; + } + + /** + * Очищает ссылку на загруженный список текстур материалов. + */ + public function unload():void { + _textures = null; + } + + /** + * Загружает текстуры для материалов. + * + * @param baseURL базовый URL файлов текстур + * @param batch массив соответствий имён текстурных материалов и их текстур, описываемых объектами класса TextureMapsInfo + * @param loaderContext LoaderContext для загрузки файлов текстур + */ + public function load(baseURL:String, batch:Map, loaderContext:LoaderContext):void { + this.baseUrl = baseURL; + this.batch = batch; + this.loaderContext = loaderContext; + + if (loader == null) { + loader = new TextureMapsLoader(); + loader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart); + loader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete); + loader.addEventListener(ProgressEvent.PROGRESS, onProgress); + loader.addEventListener(Event.COMPLETE, onMaterialTexturesLoadingComplete); + loader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialTexturesLoadingComplete); + } else { + close(); + } + // Получение массива имён материалов и подсчёт количества файлов текстур + totalFiles = 0; + materialNames = new Array(); + for (var materialName:String in batch) { + materialNames.push(materialName); + var info:TextureMapsInfo = batch[materialName]; + totalFiles += info.opacityMapFileName == null ? 1 : 2; + } + // Старт загрузки + currFileIndex = 0; + materialIndex = 0; + _textures = new Map(); + loadNextTextureFile(); + } + + /** + * Загружает очередной файл с текстурой. + */ + private function loadNextTextureFile():void { + var info:TextureMapsInfo = batch[materialNames[materialIndex]]; + loader.load(baseUrl + info.diffuseMapFileName, info.opacityMapFileName == null ? null : baseUrl + info.opacityMapFileName, loaderContext); + } + + /** + * Ретранслирует событие начала загрузки текстуры. + */ + private function onTextureLoadingStart(e:Event):void { + dispatchEvent(e); + } + + /** + * Ретранслирует событие окончания загрузки текстуры. + */ + private function onTextureLoadingComplete(e:Event):void { + dispatchEvent(e); + currFileIndex++; + } + + /** + * Рассылает событие прогресса загрузки файлов. + */ + private function onProgress(e:ProgressEvent):void { + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.TEXTURE, totalFiles, currFileIndex, e.bytesLoaded, e.bytesTotal)); + } + + /** + * Обрабатывает завершение загрузки текстуры материала. + */ + private function onMaterialTexturesLoadingComplete(e:Event):void { + // В зависимости от полученного события устанавливается загруженное изображение или битмап-заглушка + if (e is IOErrorEvent) { + _textures.add(materialNames[materialIndex], getStubBitmapData()); + dispatchEvent(e); + } else { + _textures.add(materialNames[materialIndex], loader.bitmapData); + } + if ((++materialIndex) == materialNames.length) { + // Загружены текстуры для всех материалов, отправляется сообщение о завершении + clean(); + dispatchEvent(new Event(Event.COMPLETE)); + } else { + loadNextTextureFile(); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as new file mode 100644 index 0000000..d2bc121 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as @@ -0,0 +1,21 @@ +package alternativa.engine3d.loaders { + + /** + * Структура для хранения имён файла диффузной текстуры и файла карты прозрачности. + */ + public class TextureMapsInfo { + /** + * Имя файла диффузной текстуры. + */ + public var diffuseMapFileName:String; + /** + * Имя файла карты прозрачности. + */ + public var opacityMapFileName:String; + + public function TextureMapsInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) { + this.diffuseMapFileName = diffuseMapFileName; + this.opacityMapFileName = opacityMapFileName; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as new file mode 100644 index 0000000..a71107b --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as @@ -0,0 +1,215 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderEvent; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.errors.IOError; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Событие рассылается в начале загрузки каждой текстуры. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START + */ + [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие рассылается после окончания загрузки каждой текстуры. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE + */ + [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие рассылается после завершения загрузки. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие рассылается при возникновении ошибки, приводящей к прерыванию загрузки. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие рассылается в процессе получения данных во время загрузки. + * + * @eventType flash.events.ProgressEvent.PROGRESS + */ + [Event (name="progress", type="flash.events.ProgressEvent")] + /** + * Загрузчик битмапов диффузной текстуры и карты прозрачности. + * @private + */ + public class TextureMapsLoader extends EventDispatcher { + + private static const STATE_IDLE:int = 0; + private static const STATE_LOADING_DIFFUSE_MAP:int = 1; + private static const STATE_LOADING_ALPHA_MAP:int = 2; + + private var _bitmapData:BitmapData; + private var bitmapLoader:Loader; + private var alphaTextureUrl:String; + private var loaderContext:LoaderContext; + + private var loaderState:int = STATE_IDLE; + + /** + * Создаёт новый экземпляр класса. Если параметр diffuseTextureUrl не равен null, конструктор запускает процесс + * загрузки. + * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext LoaderContext, используемый для загрузки файлов + */ + public function TextureMapsLoader(diffuseTextureUrl:String = null, alphaTextureUrl:String = null, loaderContext:LoaderContext = null) { + if (diffuseTextureUrl != null) { + load(diffuseTextureUrl, alphaTextureUrl, loaderContext); + } + } + + /** + * Загружает текстурные карты. Если помимо файла диффузной текстуры указан файл карты прозрачности, результирующая текстура будет получена из диффузной карты путём заполнения + * её альфа-канала на основе карты прозрачности. Карта прозрачности должна быть задана оттенками серого, при этом белый цвет должен задавать полностью непрозрачную область. + *

+ * После завершения загрузки текстура становится доступной через свойство bitmapData. + *

+ * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext LoaderContext, используемый для загрузки файлов + * + * @see #bitmapData + */ + public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void { + this.alphaTextureUrl = alphaTextureUrl; + this.loaderContext = loaderContext; + if (bitmapLoader == null) { + bitmapLoader = new Loader(); + bitmapLoader.contentLoaderInfo.addEventListener(Event.OPEN, onOpen); + bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); + bitmapLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } else { + close(); + } + startLoading(STATE_LOADING_DIFFUSE_MAP, diffuseTextureUrl); + } + + /** + * + */ + private function onOpen(e:Event):void { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.TEXTURE)); + } + + /** + * + */ + private function onProgress(e:Event):void { + dispatchEvent(e); + } + + /** + * Запускает загрузку файла текстуры. + * + * @param state фаза загрузки + * @param url URL загружаемого файла + */ + private function startLoading(state:int, url:String):void { + loaderState = state; + bitmapLoader.load(new URLRequest(url), loaderContext); + } + + /** + * + */ + private function onComplete(e:Event):void { + dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.TEXTURE)); + switch (loaderState) { + case STATE_LOADING_DIFFUSE_MAP: + // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности. + _bitmapData = Bitmap(bitmapLoader.content).bitmapData; + if (alphaTextureUrl != null) { + startLoading(STATE_LOADING_ALPHA_MAP, alphaTextureUrl); + } else { + complete(); + } + break; + case STATE_LOADING_ALPHA_MAP: + // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры. + var tmpBmp:BitmapData = _bitmapData; + _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height, true, 0); + _bitmapData.copyPixels(tmpBmp, tmpBmp.rect, new Point()); + + var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData; + if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) { + tmpBmp.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = tmpBmp; + } + _bitmapData.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + complete(); + break; + } + } + + /** + * + */ + private function onLoadError(e:IOErrorEvent):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + loaderState = STATE_IDLE; + bitmapLoader.unload(); + dispatchEvent(new Event(Event.COMPLETE)); + } + + /** + * Загруженная текстура. + */ + public function get bitmapData():BitmapData { + return _bitmapData; + } + + /** + * Прекращает текущую загрузку. + */ + public function close():void { + if (loaderState != STATE_IDLE) { + loaderState = STATE_IDLE; + bitmapLoader.close(); + } + unload(); + } + + /** + * Очищает внутренние ссылки на загруженные объекты. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + if (bitmapLoader != null) { + bitmapLoader.unload(); + } + loaderContext = null; + _bitmapData = null; + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..b2fdc3c --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,60 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Рассылается загрузчиками на различных этапах загрузки. + */ + public class LoaderEvent extends Event { + /** + * Значение свойства type для события loadingStart. + * @eventType loadingStart + */ + public static const LOADING_START:String = "loadingStart"; + /** + * Значение свойства type для события loadingComplete. + * @eventType loadingComplete + */ + public static const LOADING_COMPLETE:String = "loadingComplete"; + + // Этап загрузки + private var _loadingStage:int; + + /** + * Создаёт новый экземпляр объекта. + * + * @param type тип события + * @param loadingStage этап загрузки + */ + public function LoaderEvent(type:String, loadingStage:int) { + super(type); + _loadingStage = loadingStage; + } + + /** + * Этап загрузки. Может принимать значения констант, описанных в классе LoadingStage. + * + * @see alternativa.engine3d.loaders.LoadingStage + */ + public function get loadingStage():int { + return _loadingStage; + } + + /** + * Создаёт клон объекта. + * + * @return клонированный объект + */ + override public function clone():Event { + return new LoaderEvent(type, _loadingStage); + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=\"" + type + "\", loadingStage=" + _loadingStage + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..44eafd5 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,84 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Рассылается загрузчиками для отображения прогресса загрузки. + * Свойства bytesLoaded и bytesTotal показывают значения для текущего загружаемого элемента. + */ + public class LoaderProgressEvent extends ProgressEvent { + /** + * Значение свойства type для события loadingProgress. + * @eventType loadingProgress + */ + public static const LOADING_PROGRESS:String = "loadingProgress"; + + // Этап загрузки сцены + private var _loadingStage:int; + // Общее количество загружаемых элементов на текущем этапе загрузки + private var _totalItems:int; + // Номер элемента на текущем этапе загрузки сцены, с которым связано событие. Нумерация начинается с нуля. + private var _currentItem:int; + + /** + * Создаёт новый экземпляр события. + * + * @param type тип события + * @param loadingStage этап загрузки сцены, в качестве значения параметра могут быть использованы константы класса LoadingStage + * @param totalItems общее количество загружаемых элементов на текущем этапе загрузки + * @param currentItem номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля. + * @param bytesLoaded количество загруженных байтов текущего элемента + * @param bytesTotal общее количество байтов текущего элемента + * + * @see alternativa.engine3d.loaders.LoadingStage + */ + public function LoaderProgressEvent(type:String, loadingStage:int, totalItems:int, currentItem:int, bytesLoaded:uint = 0, bytesTotal:uint = 0) { + super(type, false, false, bytesLoaded, bytesTotal); + _loadingStage = loadingStage; + _totalItems = totalItems; + _currentItem = currentItem; + } + + /** + * Этап загрузки сцены. В качестве значения параметра могут быть использованы константы класса LoadingStage. + * + * @see alternativa.engine3d.loaders.LoadingStage + */ + public function get loadingStage():int { + return _loadingStage; + } + + /** + * Общее количество загружаемых элементов на текущем этапе загрузки. + */ + public function get totalItems():int { + return _totalItems; + } + + /** + * Номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля. + */ + public function get currentItem():int { + return _currentItem; + } + + /** + * Создаёт клон объекта. + * + * @return клонированный объект + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _loadingStage, _totalItems, _currentItem, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent type=\"" + type + "\", loadingStage=" + _loadingStage + ", totalItems=" + _totalItems + ", currentItem=" + _currentItem + ", bytesTotal=" + bytesTotal + ", bytesLoaded=" + bytesLoaded + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as new file mode 100644 index 0000000..e87b3d1 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as @@ -0,0 +1,504 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.display.Skin; + import alternativa.types.Matrix3D; + import alternativa.types.Point3D; + import alternativa.utils.ColorUtils; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, раскрашивающий грани оттенками заданного цвета в зависимости от значения свойства parameterType. + */ + public class DevMaterial extends SurfaceMaterial { + /** + * Значение свойства parameterType для отображения глубины полигона в BSP-дереве. Глубина кодируется оттенком базового цвета материала. Оттенок + * получается покомпонентным умножением базового цвета на отношение значения уровня полигона в дереве к максимальному значению, задаваемому + * свойством maxParameterValue. Уровни нумеруются начиная от корня дерева, таким образом, чем глубже в дереве расположен + * полигон, тем он будет светлее. + */ + public static const BSP_DEPTH:int = 0; + /** + * Значение свойства parameterType для отображения мобильности полигона. Мобильность кодируется оттенком базового цвета материала. Оттенок + * получается покомпонентным умножением базового цвета на коэффициент, характеризующий положение мобильности полигона на отрезке, + * задаваемом свойствами minMobility и maxMobility. Более мобильные полигоны имеют более светлый оттенок. + */ + public static const MOBILITY:int = 1; + /** + * Значение свойства parameterType для отображения количества фрагментов грани, которой принадлежит отрисовываемый полигон. Количество фрагментов кодируется + * оттенком базового цвета материала. Оттенок получается покомпонентным умножением базового цвета на отношние количества фрагментов текущей грани + * к максимальному значению, задаваемому свойством maxParameterValue. Чем больше грань фрагментирована, тем она светлее. + */ + public static const FRAGMENTATION:int = 2; + /** + * Значение свойства parameterType для отображения граней с отсутствующими UV-координатами. Такие грани отображаются красной заливкой. + */ + public static const NO_UV_MAPPING:int = 3; + /** + * Значение свойства parameterType для отображения вырожденных полигонов. Вырожденные полигоны отображаются красной заливкой с красной обводкой толщиной пять + * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным. + */ + public static const DEGENERATE_POLY:int = 4; + /** + * Значение свойства parameterType для отображения неплоских полигонов. + * Значение maxParameterValue в этом режиме определяет максимальное отклонение в геометрии полигона от абсолютно плоского для признания его неплоским. + * Неплоские полигоны отображаются красной заливкой с красной обводкой толщиной пять + * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным. + */ + public static const NON_PLANAR_POLY:int = 5; + + private static const point1:Point3D = new Point3D(); + private static const point2:Point3D = new Point3D(); + + private var _parameterType:int = BSP_DEPTH; + + private var _showNormals:Boolean; + private var _normalsColor:uint = 0x00FFFF; + private var _minMobility:int = 0; + private var _maxMobility:int = 255; + private var _maxParameterValue:Number = 20; + private var currentColor:int; + private var currentWireThickness:Number; + private var currentWireColor:uint; + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param parameterType тип отображаемого параметра + * @param color цвет заливки + * @param maxParameterValue максимальное значение отображаемого параметра + * @param showNormals включение режима отображения нормалей + * @param normalsColor цвет нормалей + * @param minMobility начало интервала мобильности + * @param maxMobility окончание интервала мобильности + * @param alpha прозрачность + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function DevMaterial(parameterType:uint = 0, color:uint = 0xFFFFFF, maxParameterValue:Number = 20, showNormals:Boolean = false, normalsColor:uint = 0x00FFFF, minMobility:int = 0, maxMobility:int = 255, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _parameterType = parameterType; + _color = color; + _maxParameterValue = maxParameterValue; + _showNormals = showNormals; + _normalsColor = normalsColor; + _minMobility = minMobility; + _maxMobility = maxMobility; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + var perspective:Number; + + setDrawingParameters(skin); + + if (currentColor > -1) { + gfx.beginFill(currentColor); + } + if (currentWireThickness >= 0) { + gfx.lineStyle(currentWireThickness, currentWireColor); + } + point = points[0]; + + var minX:Number; + var maxX:Number; + var minY:Number; + var maxY:Number; + + if (camera._orthographic) { + minX = point.x; + maxX = minX; + minY = point.y; + maxY = minY; + gfx.moveTo(minX, minY); + for (i = 1; i < length; i++) { + point = points[i]; + if (point.x > maxX) { + maxX = point.x; + } else if (point.x < minX) { + minX = point.x; + } + if (point.y > maxY) { + maxY = point.y; + } else if (point.y < minY) { + minY = point.y; + } + gfx.lineTo(point.x, point.y); + } + if (currentWireThickness >= 0) { + point = points[0]; + if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) { + gfx.lineTo(point.x, point.y); + } else { + // Очень маленький полигон + gfx.drawRect(point.x - 0.5, point.y - 0.5, 1, 1); + } + } + } else { + perspective = camera._focalLength/point.z; + minX = point.x*perspective; + maxX = minX; + minY = point.y*perspective; + maxY = minY; + gfx.moveTo(minX, minY); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera._focalLength/point.z; + var x:Number = point.x*perspective; + var y:Number = point.y*perspective; + if (x > maxX) { + maxX = x; + } else if (x < minX) { + minX = x; + } + if (y > maxY) { + maxY = y; + } else if (y < minY) { + minY = y; + } + gfx.lineTo(x, y); + } + if (currentWireThickness >= 0) { + point = points[0]; + perspective = camera._focalLength/point.z; + if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) { + gfx.lineTo(point.x*perspective, point.y*perspective); + } else { + // Очень маленький полигон + gfx.drawRect(point.x*perspective - 0.5, point.y*perspective - 0.5, 1, 1); + } + } + } + + // Отрисовка нормали + if (_showNormals) { + point1.reset(); + for (i = 0; i < length; i++) { + point = points[i]; + point1.x += point.x; + point1.y += point.y; + point1.z += point.z; + } + point1.multiply(1/length); + + var multiplyer:Number = 10; + var normal:Point3D = skin.primitive.face.globalNormal; + var m:Matrix3D = camera.cameraMatrix; + point2.x = (normal.x*m.a + normal.y*m.b + normal.z*m.c)*multiplyer + point1.x; + point2.y = (normal.x*m.e + normal.y*m.f + normal.z*m.g)*multiplyer + point1.y; + point2.z = (normal.x*m.i + normal.y*m.j + normal.z*m.k)*multiplyer + point1.z; + + if (camera._orthographic) { + gfx.moveTo(point1.x, point1.y); + gfx.lineStyle(0, _normalsColor); + gfx.lineTo(point2.x, point2.y); + } else { + perspective = camera._focalLength/point1.z; + gfx.moveTo(point1.x*perspective, point1.y*perspective); + gfx.lineStyle(0, _normalsColor); + perspective = camera._focalLength/point2.z; + gfx.lineTo(point2.x*perspective, point2.y*perspective); + } + gfx.lineStyle(); + } + } + + /** + * Установка параметров отрисовки. + */ + private function setDrawingParameters(skin:Skin):void { + currentColor = _color; + currentWireColor = _wireColor; + currentWireThickness = _wireThickness; + + var param:int = 0; + var i:int; + switch (_parameterType) { + case BSP_DEPTH: + // Глубина вложенности в BSP-tree + var node:BSPNode = skin.primitive.node; + while (node != null) { + node = node.parent; + param++; + } + currentColor = ColorUtils.multiply(_color, param/_maxParameterValue); + break; + case MOBILITY: + // Мобильность + var value:Number = (skin.primitive.mobility - _minMobility)/(_maxMobility - _minMobility); + if (value < 0) { + value = 0; + } + currentColor = ColorUtils.multiply(_color, value); + break; + case FRAGMENTATION: + // Степень фрагментирования + currentColor = ColorUtils.multiply(_color, calculateFragments(skin.primitive.face.primitive)/_maxParameterValue); + break; + case NO_UV_MAPPING: + // Отсутствие UV + if (skin.primitive.face.uvMatrix == null) { + currentColor = 0xFF0000; + } + break; + case DEGENERATE_POLY: + // Вырожденные полигоны + var face:Face = skin.primitive.face; + var point0:Point3D = face._vertices[0]._coords; + point1.copy(face._vertices[1]._coords); + point2.copy(face._vertices[2]._coords); + point2.subtract(point1); + point1.subtract(point0); + var crossX:Number = point1.y*point2.z - point1.z*point2.y; + var crossY:Number = point1.z*point2.x - point1.x*point2.z; + var crossZ:Number = point1.x*point2.y - point1.y*point2.x; + var sum:Number = crossX*crossX + crossY*crossY + crossZ*crossZ; + for (i = 3; i < face._verticesCount; i++) { + point1.copy(face._vertices[i - 1]._coords); + point2.copy(face._vertices[i]._coords); + point2.subtract(point1); + point1.subtract(point0); + crossX = point1.y*point2.z - point1.z*point2.y; + crossY = point1.z*point2.x - point1.x*point2.z; + crossZ = point1.x*point2.y - point1.y*point2.x; + sum += crossX*crossX + crossY*crossY + crossZ*crossZ; + } + if (sum < 0.001) { + currentColor = 0xFF0000; + currentWireColor = 0xFF0000; + currentWireThickness = 5; + } + break; + case NON_PLANAR_POLY: + // Неплоские полигоны + face = skin.primitive.face; + var normal:Point3D = face.globalNormal; + var offset:Number = face.globalOffset; + for (i = 3; i < face._verticesCount; i++) { + var vertex:Point3D = (face._vertices[i] as Vertex).globalCoords; + var distance:Number = vertex.x*normal.x + vertex.y*normal.y + vertex.z*normal.z - offset; + if (distance > _maxParameterValue || distance < -_maxParameterValue) { + currentColor = 0xFF0000; + currentWireColor = 0xFF0000; + currentWireThickness = -1; + return; + } + } + break; + } + } + + /** + * Расчёт количества фрагментов грани примитива. + */ + private function calculateFragments(primitive:PolyPrimitive):int { + if (primitive.frontFragment == null) { + return 1; + } + return calculateFragments(primitive.frontFragment) + calculateFragments(primitive.backFragment); + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:DevMaterial = new DevMaterial(_parameterType, _color, _maxParameterValue, _showNormals, _normalsColor, _minMobility, _maxMobility, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + + /** + * Тип отображаемого параметра. + */ + public function set parameterType(value:int):void { + if (_parameterType != value) { + _parameterType = value; + markToChange(); + } + } + + /** + * @private + */ + public function get parameterType():int { + return _parameterType; + } + + /** + * Включение режима отображения нормалей. + */ + public function set showNormals(value:Boolean):void { + if (_showNormals != value) { + _showNormals = value; + markToChange(); + } + } + + /** + * @private + */ + public function get showNormals():Boolean { + return _showNormals; + } + + /** + * Цвет нормалей. + * + * @default 0x00FFFF + */ + public function set normalsColor(value:uint):void { + if (_normalsColor != value) { + _normalsColor = value; + markToChange(); + } + } + + /** + * @private + */ + public function get normalsColor():uint { + return _normalsColor; + } + + /** + * Начало интервала мобильности. + */ + public function set minMobility(value:int):void { + if (_minMobility != value) { + _minMobility = value; + markToChange(); + } + } + + /** + * @private + */ + public function get minMobility():int { + return _minMobility; + } + + /** + * Окончание интервала мобильности. + */ + public function set maxMobility(value:int):void { + if (_maxMobility != value) { + _maxMobility = value; + markToChange(); + } + } + + /** + * @private + */ + public function get maxMobility():int { + return _maxMobility; + } + + /** + * Максимальное значение отображаемого параметра. + */ + public function set maxParameterValue(value:Number):void { + if (_maxParameterValue != value) { + _maxParameterValue = value; + markToChange(); + } + } + + /** + * @private + */ + public function get maxParameterValue():Number { + return _maxParameterValue; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as new file mode 100644 index 0000000..2447283 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as @@ -0,0 +1,47 @@ +package alternativa.engine3d.materials { + /** + * @private + * Точка, подготовленная к отрисовке. + */ + public final class DrawPoint { + + /** + * Координата X в системе координат камеры. + */ + public var x:Number; + /** + * Координата Y в системе координат камеры. + */ + public var y:Number; + /** + * Координата Z в системе координат камеры. + */ + public var z:Number; + /** + * Координата U в текстурном пространстве. + */ + public var u:Number; + /** + * Координата V в текстурном пространстве. + */ + public var v:Number; + + /** + * Создаёт новый экземпляр класса. + * + * @param x координата X в системе координат камеры + * @param y координата Y в системе координат камеры + * @param z координата Z в системе координат камеры + * @param u координата U в текстурном пространстве + * @param v координата V в текстурном пространстве + */ + public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) { + this.x = x; + this.y = y; + this.z = z; + this.u = u; + this.v = v; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..c2e88dc --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,160 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал, заполняющий полигон сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу + * полигона линией заданной толщины и цвета. + */ + public class FillMaterial extends SurfaceMaterial { + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра класса. + * + * @param color цвет заливки + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + */ + public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) { + super(alpha, blendMode); + _color = color; + _wireThickness = wireThickness; + _wireColor = wireColor; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + gfx.beginFill(_color); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera._focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera._focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera._focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет заливки. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor); + return res; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..e7dcedc --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as @@ -0,0 +1,94 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.display.Skin; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов. + */ + public class Material { + + /** + * @private + * Альфа. + */ + alternativa3d var _alpha:Number; + /** + * @private + * Режим наложения цвета. + */ + alternativa3d var _blendMode:String; + + /** + * Создание экземпляра класса. + * + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function Material(alpha:Number, blendMode:String) { + _alpha = alpha; + _blendMode = blendMode; + } + + /** + * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + */ + public function get alpha():Number { + return _alpha; + } + + /** + * @private + */ + public function set alpha(value:Number):void { + if (_alpha != value) { + _alpha = value; + markToChange(); + } + } + + /** + * Режим наложения цвета. + */ + public function get blendMode():String { + return _blendMode; + } + + /** + * @private + */ + public function set blendMode(value:String):void { + if (_blendMode != value) { + _blendMode = value; + markToChange(); + } + } + + /** + * Отметить материал на перерисовку. + */ + protected function markToChange():void {} + + /** + * @private + * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.). + * + * @param skin скин для очистки + */ + alternativa3d function clear(skin:Skin):void { + skin.gfx.clear(); + } + + /** + * Создание клона материала. + * + * @return клон материала + */ + public function clone():Material { + return new Material(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as new file mode 100644 index 0000000..f55cdce --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as @@ -0,0 +1,226 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.events.MouseEvent3D; + import alternativa.types.Texture; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.MovieClip; + import flash.events.Event; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Материал, позволяющий использовать мувиклип в качестве интерактивной текстуры. + *

+ * При использовании материала следует учитывать тот факт, что для перерисовки текстуры мувиклипу назначается обработчик события + * ENTER_FRAME. Если ссылка на материал будет потеряна, обработчик всё равно будет выполняться. Поэтому при окончании работы с + * материалом следует установить свойству movieClip значение null для отключения обработчика. + *

+ */ + public class MovieClipMaterial extends TextureMaterial { + + private var _movieClip:MovieClip; + private var texWidth:uint; + private var texHeight:uint; + private var _clearRect:Rectangle; + private var _clipRect:Rectangle; + private var _matrix:Matrix; + private var _refreshRate:int; + private var _refreshCounter:int; + /** + * Цвет, которым заливается текстура материала перед отрисовкой мувиклипа. + */ + public var fillColor:uint; + + /** + * Создаёт новый экземпляр материала. + * + * @param movieClip мувиклип, используемый в качестве текстуры + * @param textureWidth ширина текстуры материала + * @param textureHeight высота текстуры материала + * @param clipRect область мувиклипа, отрисовываемая в текстуру материала. Если передано значение null, будет отрисовываться вся область мувиклипа. + * @param matrix матрица трансформации мувиклипа при отрисовке. Если передано значение null, трансформация выполняться не будет. + * @param smooth сглаживание текстуры при увеличении масштаба + * @param precision точность перспективной коррекции. Может быть задана одной из констант класса + * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более + * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра. + * @param fillColor цвет, которым заливается текстура материала перед отрисовкой мувиклипа + * @param refreshRate частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка — каждые два кадра и так далее + * + * @see TextureMaterialPrecision + */ + public function MovieClipMaterial(movieClip:MovieClip, textureWidth:uint, textureHeight:uint, clipRect:Rectangle = null, matrix:Matrix = null, smooth:Boolean = false, precision:Number = 10, fillColor:uint = 0, refreshRate:int = 1) { + texWidth = textureWidth; + texHeight = textureHeight; + _clearRect = new Rectangle(0, 0, texWidth, texHeight); + if (clipRect != null) { + _clipRect = clipRect.clone(); + } + if (matrix != null) { + _matrix = matrix.clone(); + } + _texture = new Texture(new BitmapData(texWidth, texWidth)); + this.refreshRate = refreshRate; + this.movieClip = movieClip; + this.fillColor = fillColor; + + super(_texture, 1, false, smooth, BlendMode.NORMAL, -1, 0, precision); + } + + /** + * Мувиклип, используемый в качестве текстуры. + *

+ * При установке свойства указанному мувиклипу назначается обработчик события ENTER_FRAME. Во избежании утечки памяти, при прекращении работы с материалом + * следует устанавливать данному свойству значение null для отключения обработчика. + *

+ */ + public function get movieClip():MovieClip { + return _movieClip; + } + + /** + * @private + */ + public function set movieClip(value:MovieClip):void { + if (value != _movieClip) { + if (_movieClip != null) { + _movieClip.removeEventListener(Event.ENTER_FRAME, redraw); + } + _movieClip = value; + if (_movieClip != null) { + _movieClip.addEventListener(Event.ENTER_FRAME, redraw); + } else { + _texture.bitmapData.fillRect(_clearRect, fillColor); + } + } + } + + /** + * @private + */ + private function redraw(e:Event):void { + if (_movieClip != null) { + _refreshCounter++; + if (_refreshCounter == _refreshRate) { + _refreshCounter = 0; + _texture.bitmapData.fillRect(_clearRect, fillColor); + _texture.bitmapData.draw(_movieClip, _matrix, null, BlendMode.NORMAL, _clearRect, _smooth); + } + } + } + + /** + * @private + */ + override alternativa3d function addToSurface(surface:Surface):void { + super.addToSurface(surface); + // Устанавливаем обработчики мышиных событий, которые будут обеспечивать корректное позиционирование мувиклипа + surface.addEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove); + surface.addEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut); + surface.addEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut); + } + + /** + * @private + */ + override alternativa3d function removeFromSurface(surface:Surface):void { + super.removeFromSurface(surface); + // Удаляем обработчики мышиных событий + surface.removeEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove); + surface.removeEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut); + surface.removeEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut); + } + + /** + * @private + */ + private function onSurfaceMouseMove(e:MouseEvent3D):void { + if (_movieClip != null) { + // При перемещении мыши над гранью позиционируем мувиклип так, чтобы совместить точку грани под курсором мыши с соотвествующей точкой на мувиклипе + _movieClip.x = e.view.mouseX - texWidth*e.u; + _movieClip.y = e.view.mouseY - texHeight*(1 - e.v); + } + } + + /** + * @private + */ + private function onSurfaceMouseOverOut(e:MouseEvent3D):void { + if (_movieClip != null) { + // Удаляем мувиклип с вьюпорта при уходе мыши с грани и добавляем при наведении мыши на грань. + // Это нужно, чтобы вьюпорт получал событие MOUSE_MOVE при перемещении мыши над мувиклипом. + if (e.type == MouseEvent3D.MOUSE_OVER) { + e.view.addChild(_movieClip); + _movieClip.alpha = 0; + } else { + if (_movieClip.parent == e.view) { + e.view.removeChild(_movieClip); + } + } + } + } + + /** + * @private + */ + override public function set texture(value:Texture):void { + } + + /** + * Частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка — каждые два кадра и так далее. + */ + public function get refreshRate():int { + return _refreshRate; + } + + /** + * @private + */ + public function set refreshRate(value:int):void { + _refreshRate = value > 0 ? value : 1; + } + + /** + * Область мувиклипа, отрисовываемая в текстуру материала. При установленном значении null будет отрисовываться вся область мувиклипа. + */ + public function get clipRect():Rectangle { + return _clipRect == null ? null : _clipRect.clone(); + } + + /** + * @private + */ + public function set clipRect(value:Rectangle):void { + _clipRect = value; + } + + /** + * Матрица трансформации мувиклипа при отрисовке. При установленном значении null трансформация выполняться не будет. + */ + public function get matrix():Matrix { + return _matrix == null ? null : _matrix.clone(); + } + + /** + * @private + */ + public function set matrix(value:Matrix):void { + _matrix = value; + } + + /** + * Выполняет клонирование материала. + * + * @return копия материала + */ + override public function clone():Material { + return new MovieClipMaterial(_movieClip, texWidth, texHeight, _clipRect, _matrix, _smooth, _precision, fillColor, _refreshRate); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as new file mode 100644 index 0000000..94bd4c1 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as @@ -0,0 +1,122 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Sprite3D; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Базовый класс для материалов спрайтов. + */ + public class SpriteMaterial extends Material { + + /** + * @private + * Спрайт. + */ + alternativa3d var _sprite:Sprite3D; + + /** + * Создание экземпляра материала. + * + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function SpriteMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + } + + /** + * Спрайт, которому назначен материал. + */ + public function get sprite():Sprite3D { + return _sprite; + } + + /** + * @private + * Добавление на сцену. + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены. + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Назначение спрайту. + * + * @param sprite спрайт + */ + alternativa3d function addToSprite(sprite:Sprite3D):void { + _sprite = sprite; + } + + /** + * @private + * Удаление из спрайта. + * + * @param sprite спрайт + */ + alternativa3d function removeFromSprite(sprite:Sprite3D):void { + _sprite = null; + } + + /** + * @private + * Метод определяет, может ли материал нарисовать спрайт. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной со спрайтом. Реализация по умолчанию возвращает + * true. + * + * @param camera камера через которую происходит отрисовка. + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + alternativa3d function canDraw(camera:Camera3D):Boolean { + return true; + } + + /** + * @private + * Метод выполняет отрисовку в заданный скин. + * + * @param camera камера, вызвавшая метод + * @param skin скин, в котором нужно отрисовать + */ + alternativa3d function draw(camera:Camera3D, skin:Skin):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override protected function markToChange():void { + if (_sprite != null) { + _sprite.addMaterialChangedOperationToScene(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SpriteMaterial(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as new file mode 100644 index 0000000..00fad62 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as @@ -0,0 +1,243 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, отображающий заданную текстуру в точке нахождения спрайта. Текстура отображается так, как если бы она находилась в плоскости, + * параллельной плоскости области вывода камеры, а верхний край текстуры был параллелен верхнему краю области вывода. При отрисовке изображения + * начало координат текстуры в области вывода совпадает с проекцией точки спрайта. По умолчанию начало координат текстуры перенесено в её центр. + */ + public class SpriteTextureMaterial extends SpriteMaterial { + + /** + * @private + * Вспомогательный прямоугольник, используется при отрисовке для хранения параметров отрисовки. + */ + private static var drawRect:Rectangle = new Rectangle(); + + /** + * @private + * Матрица, используемая для отрисовки текстуры спрайта + */ + private static var textureMatrix:Matrix = new Matrix(); + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + + /** + * @private + * Смещение начала координат по оси X + */ + alternativa3d var _originX:Number; + + /** + * @private + * Смещение начала координат по оси Y + */ + alternativa3d var _originY:Number; + + /** + * Создание экземпляра класса. + * + * @param texture текстура для отображения + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param smooth сглаживание текстуры + * @param blendMode режим наложения цвета + * @param originX относительное смещение начала координат в текстуре по оси X + * @param originY относительное смещение начала координат в текстуре по оси Y + */ + public function SpriteTextureMaterial(texture:Texture, alpha:Number = 1, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, originX:Number = 0.5, originY:Number = 0.5) { + super(alpha, blendMode); + _texture = texture; + _smooth = smooth; + _originX = originX; + _originY = originY; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(camera:Camera3D):Boolean { + if (_texture == null) { + return false; + } + + // Переводим координаты в систему камеры + var cameraMatrix:Matrix3D = camera.cameraMatrix; + + var x:Number = _sprite.globalCoords.x; + var y:Number = _sprite.globalCoords.y; + var z:Number = _sprite.globalCoords.z; + var pointX:Number = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d; + var pointY:Number = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h; + var pointZ:Number = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l; + + var w:Number; + var h:Number; + + if (camera._orthographic) { + if ((camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) { + return false; + } + w = _texture._width*camera._zoom*_sprite._materialScale; + h = _texture._height*camera._zoom*_sprite._materialScale; + x = pointX - w*_originX; + y = pointY - h*_originY; + } else { + if ((pointZ <= 0) || (camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) { + return false; + } + var perspective:Number = camera._focalLength/pointZ; + w = _texture._width*perspective*_sprite._materialScale; + h = _texture._height*perspective*_sprite._materialScale; + x = pointX*perspective - w*_originX; + y = pointY*perspective - h*_originY; + } + var halfW:Number = camera._view._width*0.5; + var halfH:Number = camera._view._height*0.5; + + if (camera._viewClipping && (x >= halfW || y >= halfH || x + w <= -halfW || y + h <= -halfH)) { + return false; + } + + textureMatrix.a = w/_texture._width; + textureMatrix.d = h/_texture._height; + textureMatrix.tx = x; + textureMatrix.ty = y; + + if (camera._viewClipping) { + if (x < -halfW) { + w -= -halfW - x; + x = -halfW; + } + if (x + w > halfW) { + w = halfW - x; + } + if (y < -halfH) { + h -= -halfH - y; + y = -halfH; + } + if (y + h > halfH) { + h = halfH - y; + } + } + drawRect.x = x; + drawRect.y = y; + drawRect.width = w; + drawRect.height = h; + + return true; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + skin.gfx.beginBitmapFill(_texture._bitmapData, textureMatrix, false, _smooth); + skin.gfx.drawRect(drawRect.x, drawRect.y, drawRect.width, drawRect.height); + } + + /** + * Текстура. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + markToChange(); + } + } + + /** + * Сглаживание текстуры. + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + markToChange(); + } + } + + /** + * Относительное смещение начала координат в текстуре по оси X. + * + * @default 0.5 + */ + public function get originX():Number { + return _originX; + } + + /** + * @private + */ + public function set originX(value:Number):void { + if (_originX != value) { + _originX = value; + markToChange(); + } + } + + /** + * Относительное смещение начала координат в текстуре по оси Y. + * + * @default 0.5 + */ + public function get originY():Number { + return _originY; + } + + /** + * @private + */ + public function set originY(value:Number):void { + if (_originY != value) { + _originY = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SpriteTextureMaterial(_texture, _alpha, _smooth, _blendMode, _originX, _originY); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as new file mode 100644 index 0000000..c429d8b --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as @@ -0,0 +1,150 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + + use namespace alternativa3d; + + /** + * Базовый класс для материалов полигональных поверхностей. + */ + public class SurfaceMaterial extends Material { + + /** + * @private + * Поверхность + */ + alternativa3d var _surface:Surface; + /** + * @private + * Флаг, определяет использует ли материал UV-координаты в грани + */ + alternativa3d var useUV:Boolean = false; + + /** + * Создание экземпляра класса. + * + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + } + + /** + * Поверхность, которой назначен материал. + */ + public function get surface():Surface { + return _surface; + } + + /** + * @private + * Добавление на сцену + * + * @param scene + */ + alternativa3d function addToScene(scene:Scene3D):void {} + + /** + * @private + * Удаление из сцены + * + * @param scene + */ + alternativa3d function removeFromScene(scene:Scene3D):void {} + + /** + * @private + * Добавление к мешу + * + * @param mesh + */ + alternativa3d function addToMesh(mesh:Mesh):void {} + + /** + * @private + * Удаление из меша + * + * @param mesh + */ + alternativa3d function removeFromMesh(mesh:Mesh):void {} + + /** + * @private + * Добавление на поверхность + * + * @param surface + */ + alternativa3d function addToSurface(surface:Surface):void { + // Сохраняем поверхность + _surface = surface; + } + + /** + * @private + * Удаление с поверхности + * + * @param surface + */ + alternativa3d function removeFromSurface(surface:Surface):void { + // Удаляем ссылку на поверхность + _surface = null; + } + + /** + * @inheritDoc + */ + override protected function markToChange():void { + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + + /** + * @private + * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться + * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает + * true. + * + * @param primitive примитив для проверки + * + * @return true, если материал может отрисовать указанный примитив, иначе false + */ + alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return true; + } + + /** + * @private + * Метод выполняет отрисовку в заданный скин. + * + * @param camera камера, вызвавшая метод + * @param skin скин, в котором нужно рисовать + * @param length длина массива points + * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса + * alternativa.engine3d.materials.DrawPoint + * + * @see DrawPoint + */ + alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new SurfaceMaterial(_alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..6e717b0 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,356 @@ +package alternativa.engine3d.materials { + import __AS3__.vec.Vector; + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + import alternativa.types.*; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.geom.Matrix; + import flash.display.BitmapData; + import flash.display.Graphics; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Материал, заполняющий полигон текстурой. Помимо наложения текстуры, материал может рисовать границу полигона линией + * заданной толщины и цвета. + */ + public class TextureMaterial extends SurfaceMaterial { + + private static var stubBitmapData:BitmapData; + private static var stubMatrix:Matrix; + + private var gfx:Graphics; + private var textureMatrix:Matrix = new Matrix(); + private var focalLength:Number; + private var distortion:Number; + + /** + * @private + * Текстура + */ + alternativa3d var _texture:Texture; + /** + * @private + * Повтор текстуры + */ + alternativa3d var _repeat:Boolean; + /** + * @private + * Сглаженность текстуры + */ + alternativa3d var _smooth:Boolean; + /** + * @private + * Точность перспективной коррекции + */ + alternativa3d var _precision:Number; + + /** + * @private + * Толщина линий обводки + */ + alternativa3d var _wireThickness:Number; + + /** + * @private + * Цвет линий обводки + */ + alternativa3d var _wireColor:uint; + + /** + * Создание экземпляра текстурного материала. + * + * @param texture текстура материала + * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param repeat повтор текстуры при заполнении + * @param smooth сглаживание текстуры при увеличении масштаба + * @param blendMode режим наложения цвета + * @param wireThickness толщина линии обводки + * @param wireColor цвет линии обводки + * @param precision точность перспективной коррекции. Может быть задана одной из констант класса + * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более + * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра. + * + * @see TextureMaterialPrecision + */ + public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) { + super(alpha, blendMode); + _texture = texture; + _repeat = repeat; + _smooth = smooth; + _wireThickness = wireThickness; + _wireColor = wireColor; + _precision = precision; + useUV = true; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _texture != null; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + gfx = skin.gfx; + + // Проверка на нулевую UV-матрицу + if (skin.primitive.face.uvMatrixBase == null) { + if (stubBitmapData == null) { + // Создание текстуры-заглушки + stubBitmapData = new BitmapData(2, 2, false, 0); + stubBitmapData.setPixel(0, 0, 0xFF00FF); + stubBitmapData.setPixel(1, 1, 0xFF00FF); + stubMatrix = new Matrix(10, 0, 0, 10, 0, 0); + } + gfx.beginBitmapFill(stubBitmapData, stubMatrix); + if (camera._orthographic) { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + var perspective:Number = camera._focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 1; i < length; i++) { + point = points[i]; + perspective = camera._focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + if (_wireThickness >= 0) { + point = points[0]; + perspective = camera._focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + return; + } + + if (camera._orthographic) { + // Расчитываем матрицу наложения текстуры + var face:Face = skin.primitive.face; + // Если матрица не расчитана, считаем + if (!camera.uvMatricesCalculated[face]) { + camera.calculateUVMatrix(face, _texture._width, _texture._height); + } + gfx.beginBitmapFill(_texture._bitmapData, face.orthoTextureMatrix, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + point = points[0]; + gfx.moveTo(point.x, point.y); + for (i = 1; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + if (_wireThickness >= 0) { + point = points[0]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + focalLength = camera._focalLength; + //distortion = camera.focalDistortion*_precision; + + var front:int = 0; + var back:int = length - 1; + + var newFront:int = 1; + var newBack:int = (back > 0) ? (back - 1) : (length - 1); + var direction:Boolean = true; + + var a:DrawPoint = points[back]; + var b:DrawPoint; + var c:DrawPoint = points[front]; + + var drawVertices:Vector. = new Vector.(); + var drawUVTs:Vector. = new Vector.(); + + for (i = 0; i < length; i++) { + var p:DrawPoint = points[i]; + var t:Number = focalLength/p.z; + drawVertices[i << 1] = p.x*t; + drawVertices[(i << 1) + 1] = p.y*t; + drawUVTs.push(p.u, 1 - p.v, t); + } + + var drawIndices:Vector. = new Vector.(); + + while (front != newBack) { + if (direction) { +/* a = points[front]; + b = points[newFront]; + c = points[back]; + */ + drawIndices.push(front, newFront, back); + + front = newFront; + newFront = (front < length - 1) ? (front + 1) : 0; + } else { +/* a = points[newBack]; + b = points[back]; + c = points[front]; + */ + drawIndices.push(newBack, back, front); + + back = newBack; + newBack = (back > 0) ? (back - 1) : (length - 1); + } + + direction = !direction; + } + gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth); + if (_wireThickness >= 0) { + gfx.lineStyle(_wireThickness, _wireColor); + } + gfx.drawTriangles(drawVertices, drawIndices, drawUVTs); + + } + } + + /** + * Текстура материала. Материал не выполняет никаких действий по отрисовке, если не задана текстура. + */ + public function get texture():Texture { + return _texture; + } + + /** + * @private + */ + public function set texture(value:Texture):void { + if (_texture != value) { + _texture = value; + markToChange(); + } + } + + /** + * Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get repeat():Boolean { + return _repeat; + } + + /** + * @private + */ + public function set repeat(value:Boolean):void { + if (_repeat != value) { + _repeat = value; + markToChange(); + } + } + + /** + * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода + * flash.display.Graphics#beginBitmapFill(). + */ + public function get smooth():Boolean { + return _smooth; + } + + /** + * @private + */ + public function set smooth(value:Boolean):void { + if (_smooth != value) { + _smooth = value; + if (_surface != null) { + _surface.addMaterialChangedOperationToScene(); + } + } + } + + /** + * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется. + */ + public function get wireThickness():Number { + return _wireThickness; + } + + /** + * @private + */ + public function set wireThickness(value:Number):void { + if (_wireThickness != value) { + _wireThickness = value; + markToChange(); + } + } + + /** + * Цвет линии обводки полигона. + */ + public function get wireColor():uint { + return _wireColor; + } + + /** + * @private + */ + public function set wireColor(value:uint):void { + if (_wireColor != value) { + _wireColor = value; + markToChange(); + } + } + + /** + * Точность перспективной коррекции. + */ + public function get precision():Number { + return _precision; + } + + /** + * @private + */ + public function set precision(value:Number):void { + if (_precision != value) { + _precision = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision); + return res; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as new file mode 100644 index 0000000..22be349 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as @@ -0,0 +1,40 @@ +package alternativa.engine3d.materials { + + /** + * Класс содержит константы точности перспективной коррекции текстурного материала. + * + * @see TextureMaterial + */ + public class TextureMaterialPrecision { + + /** + * Адаптивная триангуляция не будет выполняться, только простая триангуляция. + */ + public static const NONE:Number = -1; + /** + * Очень низкое качество адаптивной триангуляции. + */ + public static const VERY_LOW:Number = 50; + /** + * Низкое качество адаптивной триангуляции. + */ + public static const LOW:Number = 25; + /** + * Среднее качество адаптивной триангуляции. + */ + public static const MEDIUM:Number = 10; + /** + * Высокое качество адаптивной триангуляции. + */ + public static const HIGH:Number = 6; + /** + * Очень высокое качество адаптивной триангуляции. + */ + public static const VERY_HIGH:Number = 3; + /** + * Максимальное качество адаптивной триангуляции. + */ + public static const BEST:Number = 1; + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as new file mode 100644 index 0000000..2407d74 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as @@ -0,0 +1,127 @@ +package alternativa.engine3d.materials { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.display.Skin; + + import flash.display.BlendMode; + import flash.display.Graphics; + + use namespace alternativa3d; + + /** + * Материал для рисования рёбер полигонов. + */ + public class WireMaterial extends SurfaceMaterial { + + /** + * @private + * Цвет + */ + alternativa3d var _color:uint; + /** + * @private + * Толщина линий + */ + alternativa3d var _thickness:Number; + + /** + * Создание экземпляра класса. + * + * @param thickness толщина линий + * @param color цвет линий + * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности. + * @param blendMode режим наложения цвета + */ + public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) { + super(alpha, blendMode); + _color = color; + _thickness = thickness; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean { + return _thickness >= 0; + } + + /** + * @private + * @inheritDoc + */ + override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void { + skin.alpha = _alpha; + skin.blendMode = _blendMode; + + var i:uint; + var point:DrawPoint; + var gfx:Graphics = skin.gfx; + + if (camera._orthographic) { + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + gfx.moveTo(point.x, point.y); + for (i = 0; i < length; i++) { + point = points[i]; + gfx.lineTo(point.x, point.y); + } + } else { + // Отрисовка + gfx.lineStyle(_thickness, _color); + point = points[length - 1]; + var perspective:Number = camera._focalLength/point.z; + gfx.moveTo(point.x*perspective, point.y*perspective); + for (i = 0; i < length; i++) { + point = points[i]; + perspective = camera._focalLength/point.z; + gfx.lineTo(point.x*perspective, point.y*perspective); + } + } + } + + /** + * Цвет линий. + */ + public function get color():uint { + return _color; + } + + /** + * @private + */ + public function set color(value:uint):void { + if (_color != value) { + _color = value; + markToChange(); + } + } + + /** + * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется. + */ + public function get thickness():Number { + return _thickness; + } + + /** + * @private + */ + public function set thickness(value:Number):void { + if (_thickness != value) { + _thickness = value; + markToChange(); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + return new WireMaterial(_thickness, _color, _alpha, _blendMode); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as new file mode 100644 index 0000000..3442e66 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as @@ -0,0 +1,32 @@ +package alternativa.engine3d.physics { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.types.Point3D; + + use namespace alternativa3d; + + /** + * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к + * эллипсоиду плоскость, проходящая через точку столкновения с гранью. + */ + public class Collision { + /** + * Грань, с которой произошло столкновение. + */ + public var face:Face; + /** + * Нормаль плоскости столкновения. + */ + public var normal:Point3D; + /** + * Смещение плоскости столкновения. + */ + public var offset:Number; + /** + * Координаты точки столкновения. + */ + public var point:Point3D; + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as new file mode 100644 index 0000000..200b5a2 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as @@ -0,0 +1,62 @@ +package alternativa.engine3d.physics { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + + use namespace alternativa3d; + + /** + * @private + */ + public class CollisionPlane { + + // Узел BSP дерева, который содержит плоскость + public var node:BSPNode; + // Индикатор положения объекта относительно плоскости (спереди или сзади) + public var infront:Boolean; + // Расстояние до плоскости в начальной точке (всегда положительное) + public var sourceOffset:Number; + // Расстояние до плоскости в конечной точке + public var destinationOffset:Number; + + // Хранилище неиспользуемых плоскостей + static private var collector:Array = new Array(); + + /** + * Создание плоскости + * + * @param node + * @param infront + * @param sourceOffset + * @param destinationOffset + * @return + */ + static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane { + + // Достаём плоскость из коллектора + var plane:CollisionPlane = collector.pop(); + // Если коллектор пуст, создаём новую плоскость + if (plane == null) { + plane = new CollisionPlane(); + } + + plane.node = node; + plane.infront = infront; + plane.sourceOffset = sourceOffset; + plane.destinationOffset = destinationOffset; + + return plane; + } + + /** + * Удаление плоскости, все ссылки должны быть почищены + * + * @param plane + */ + static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void { + plane.node = null; + collector.push(plane); + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as new file mode 100644 index 0000000..0eba376 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.physics { + + /** + * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet + * при определении столкновений. + * + * @see EllipsoidCollider#collisionSet + */ + public class CollisionSetMode { + /** + * Грани объектов игнорируются при определении столкновений. + */ + static public const EXCLUDE:int = 1; + /** + * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам. + */ + static public const INCLUDE:int = 2; + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as new file mode 100644 index 0000000..e2ef88a --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as @@ -0,0 +1,1003 @@ +package alternativa.engine3d.physics { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.PolyPrimitive; + import alternativa.engine3d.core.Scene3D; + import alternativa.engine3d.core.SplitterPrimitive; + import alternativa.types.Point3D; + import alternativa.types.Set; + import alternativa.utils.ObjectUtils; + + use namespace alternativa3d; + + /** + * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками. + */ + public class EllipsoidCollider { + + // Максимальное количество попыток найти свободное от столкновения со сценой направление + private static const MAX_COLLISIONS:uint = 50; + // Радиус наибольшей сферы + private var _radius:Number = 100; + private var _radius2:Number = _radius * _radius; + private var _radiusX:Number = _radius; + private var _radiusY:Number = _radius; + private var _radiusZ:Number = _radius; + private var _radiusX2:Number = _radiusX * _radiusX; + private var _radiusY2:Number = _radiusY * _radiusY; + private var _radiusZ2:Number = _radiusZ * _radiusZ; + // Коэффициенты масштабирования осей + private var _scaleX:Number = 1; + private var _scaleY:Number = 1; + private var _scaleZ:Number = 1; + // Квадраты коэффициентов масштабирования осей + private var _scaleX2:Number = 1; + private var _scaleY2:Number = 1; + private var _scaleZ2:Number = 1; + + private var collisionSource:Point3D; + private var currentDisplacement:Point3D = new Point3D(); + private var collisionDestination:Point3D = new Point3D(); + + private var collisionPlanes:Array = new Array(); + private var collisionPrimitive:PolyPrimitive; + private var collisionPrimitiveNearest:PolyPrimitive; + private var collisionPlanePoint:Point3D = new Point3D(); + private var collisionPrimitiveNearestLengthSqr:Number; + private var collisionPrimitivePoint:Point3D = new Point3D(); + + private var collisionNormal:Point3D = new Point3D(); + private var collisionPoint:Point3D = new Point3D(); + private var collisionOffset:Number; + + private var currentCoords:Point3D = new Point3D(); + private var collision:Collision = new Collision(); + private var collisionRadius:Number; + private var radiusVector:Point3D = new Point3D(); + private var p1:Point3D = new Point3D(); + private var p2:Point3D = new Point3D();; + private var localCollisionPlanePoint:Point3D = new Point3D(); + + // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу. + private var useSimpleAlgorithm:Boolean = true; + + /** + * Сцена, в которой определяются столкновения. + */ + public var scene:Scene3D; + /** + * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих + * координат меньше указанной погрешности. + */ + public var offsetThreshold:Number = 0.0001; + /** + * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры + * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит + * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества. + * + * @see #collisionSetMode + * @see alternativa.engine3d.core.Mesh + * @see alternativa.engine3d.core.Surface + */ + public var collisionSet:Set; + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если + * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений. + * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным + * в множестве объектам. + * + * @default true + * @see #collisionSet + */ + private var _collisionSetMode:int = CollisionSetMode.EXCLUDE; + + /** + * Создаёт новый экземпляр класса. + * + * @param scene сцена, в которой определяются столкновения + * @param scaleX радиус эллипсоида по оси X + * @param scaleY радиус эллипсоида по оси Y + * @param scaleZ радиус эллипсоида по оси Z + */ + public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) { + this.scene = scene; + this.radiusX = radiusX; + this.radiusY = radiusY; + this.radiusZ = radiusZ; + } + + /** + * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. + * + * @default CollisionSetMode.EXCLUDE + * @see #collisionSet + * @see CollisionSetMode + * + * @throws ArgumentError было указано значение не являющееся константой CollisionSetMode + */ + public function get collisionSetMode():int { + return _collisionSetMode; + } + + /** + * @private + */ + public function set collisionSetMode(value:int):void { + if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) { + throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value"); + } + _collisionSetMode = value; + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusX():Number { + return _radiusX; + } + + /** + * @private + */ + public function set radiusX(value:Number):void { + _radiusX = value >= 0 ? value : -value; + _radiusX2 = _radiusX * _radiusX; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusY():Number { + return _radiusY; + } + + /** + * @private + */ + public function set radiusY(value:Number):void { + _radiusY = value >= 0 ? value : -value; + _radiusY2 = _radiusY * _radiusY; + calculateScales(); + } + + /** + * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль. + * + * @default 100 + */ + public function get radiusZ():Number { + return _radiusZ; + } + + /** + * @private + */ + public function set radiusZ(value:Number):void { + _radiusZ = value >= 0 ? value : -value; + _radiusZ2 = _radiusZ * _radiusZ; + calculateScales(); + } + + /** + * Расчёт коэффициентов масштабирования осей. + */ + private function calculateScales():void { + _radius = _radiusX; + if (_radiusY > _radius) { + _radius = _radiusY; + } + if (_radiusZ > _radius) { + _radius = _radiusZ; + } + _radius2 = _radius * _radius; + _scaleX = _radiusX / _radius; + _scaleY = _radiusY / _radius; + _scaleZ = _radiusZ / _radius; + _scaleX2 = _scaleX * _scaleX; + _scaleY2 = _scaleY * _scaleY; + _scaleZ2 = _scaleZ * _scaleZ; + + useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ); + } + + /** + * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение + * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены, + * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если + * значение поля scene равно null, то результат работы метода будет простой суммой двух + * входных векторов. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль + * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке. + * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат + * корневого объекта сцены + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + * @see #offsetThreshold + */ + public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void { + // Расчеты не производятся, если перемещение мало + if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold && + displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold && + displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + return; + } + + // Начальные координаты + currentCoords.x = sourcePoint.x; + currentCoords.y = sourcePoint.y; + currentCoords.z = sourcePoint.z; + // Начальный вектор перемещения + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + // Начальная точка назначения + destinationPoint.x = sourcePoint.x + currentDisplacement.x; + destinationPoint.y = sourcePoint.y + currentDisplacement.y; + destinationPoint.z = sourcePoint.z + currentDisplacement.z; + + if (useSimpleAlgorithm) { + calculateDestinationS(sourcePoint, destinationPoint); + } else { + calculateDestinationE(sourcePoint, destinationPoint); + } + } + + /** + * Вычисление точки назначения для сферы. + * @param sourcePoint + * @param destinationPoint + */ + private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision ) { + // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали + var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра сферы для следующей итерации + currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold); + currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold); + currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold); + // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Вычисление точки назначения для эллипсоида. + * @param destinationPoint + * @return + */ + private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void { + var collisionCount:uint = 0; + var hasCollision:Boolean; + // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не + // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и + // необходимость принудительного выхода. + do { + hasCollision = getCollision(currentCoords, currentDisplacement, collision); + if (hasCollision) { + // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали + var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z; + destinationPoint.x += collision.normal.x * offset; + destinationPoint.y += collision.normal.y * offset; + destinationPoint.z += collision.normal.z * offset; + // Коррекция текущих кординат центра эллипсоида для следующей итерации + collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius; + currentCoords.x = collision.point.x - collisionRadius * radiusVector.x; + currentCoords.y = collision.point.y - collisionRadius * radiusVector.y; + currentCoords.z = collision.point.z - collisionRadius * radiusVector.z; + // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения. + currentDisplacement.x = destinationPoint.x - currentCoords.x; + currentDisplacement.y = destinationPoint.y - currentCoords.y; + currentDisplacement.z = destinationPoint.z - currentCoords.z; + // Если смещение слишком мало, останавливаемся + if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold && + currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold && + currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) { + destinationPoint.x = currentCoords.x; + destinationPoint.y = currentCoords.y; + destinationPoint.z = currentCoords.z; + break; + } + } + } while (hasCollision && (++collisionCount < MAX_COLLISIONS)); + // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте + if (collisionCount == MAX_COLLISIONS) { + destinationPoint.x = sourcePoint.x; + destinationPoint.y = sourcePoint.y; + destinationPoint.z = sourcePoint.z; + } + } + + /** + * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора + * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode. + * + * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены + * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены + * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат + * корневого объекта сцены + * + * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены, + * false если столкновений нет или не задано значение поля scene. + * + * @see #scene + * @see #collisionSet + * @see #collisionSetMode + */ + public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean { + if (scene == null) { + return false; + } + + collisionSource = sourcePoint; + + currentDisplacement.x = displacementVector.x; + currentDisplacement.y = displacementVector.y; + currentDisplacement.z = displacementVector.z; + + collisionDestination.x = collisionSource.x + currentDisplacement.x; + collisionDestination.y = collisionSource.y + currentDisplacement.y; + collisionDestination.z = collisionSource.z + currentDisplacement.z; + + collectPotentialCollisionPlanes(scene.bsp); + collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING); + + var plane:CollisionPlane; + // Пока не найдём столкновение с примитивом или плоскости не кончатся + if (useSimpleAlgorithm) { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneS(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } else { + while ((plane = collisionPlanes.pop()) != null) { + if (collisionPrimitive == null) { + calculateCollisionWithPlaneE(plane); + } + CollisionPlane.destroyCollisionPlane(plane); + } + } + + var collisionFound:Boolean = collisionPrimitive != null; + if (collisionFound) { + collision.face = collisionPrimitive.face; + collision.normal = collisionNormal; + collision.offset = collisionOffset; + collision.point = collisionPoint; + } + + collisionPrimitive = null; + collisionSource = null; + + return collisionFound; + } + + /** + * Сбор потенциальных плоскостей столкновения. + * + * @param node текущий узел BSP-дерева + */ + private function collectPotentialCollisionPlanes(node:BSPNode):void { + if (node == null) { + return; + } + + var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset; + var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset; + var plane:CollisionPlane; + + if (sourceOffset >= 0) { + // Исходное положение центра перед плоскостью ноды + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + // Или мы наткнулись на спрайтовую ноду + if (destinationOffset < _radius && !node.isSprite) { + // Добавляем плоскость если это не сплитеровая нода + if (node.splitter == null) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset); + collisionPlanes.push(plane); + } + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + } + } else { + // Исходное положение центра за плоскостью ноды + // Проверяем задние ноды + collectPotentialCollisionPlanes(node.back); + // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида + if (destinationOffset > -_radius) { + // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад + if (node.backPrimitives != null) { + // Нашли потенциальное пересечение с плоскостью + plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset); + collisionPlanes.push(plane); + } + // Проверяем передние ноды + collectPotentialCollisionPlanes(node.front); + } + } + } + + /** + * @private + * Определение пересечения сферы с примитивами, лежащими в заданной плоскости. + * + * @param plane плоскость, содержащая примитивы для проверки + */ + private function calculateCollisionWithPlaneS(plane:CollisionPlane):void { + collisionPlanePoint.copy(collisionSource); + + var normal:Point3D = plane.node.normal; + // Если сфера врезана в плоскость + if (plane.sourceOffset <= _radius) { + if (plane.infront) { + collisionPlanePoint.x -= normal.x * plane.sourceOffset; + collisionPlanePoint.y -= normal.y * plane.sourceOffset; + collisionPlanePoint.z -= normal.z * plane.sourceOffset; + } else { + collisionPlanePoint.x += normal.x * plane.sourceOffset; + collisionPlanePoint.y += normal.y * plane.sourceOffset; + collisionPlanePoint.z += normal.z * plane.sourceOffset; + } + } else { + // Находим центр сферы во время столкновения с плоскостью + var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset); + collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time; + collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time; + collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time; + + // Устанавливаем точку пересечения cферы с плоскостью + if (plane.infront) { + collisionPlanePoint.x -= normal.x * _radius; + collisionPlanePoint.y -= normal.y * _radius; + collisionPlanePoint.z -= normal.z * _radius; + } else { + collisionPlanePoint.x += normal.x * _radius; + collisionPlanePoint.y += normal.y * _radius; + collisionPlanePoint.z += normal.z * _radius; + } + } + + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(plane.node.primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveS(primitive); + if (collisionPrimitive != null) break; + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + + // Нормаль плоскости при столкновении - нормаль плоскости + if (plane.infront) { + collisionNormal.x = normal.x; + collisionNormal.y = normal.y; + collisionNormal.z = normal.z; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normal.x; + collisionNormal.y = -normal.y; + collisionNormal.z = -normal.z; + collisionOffset = -plane.node.offset; + } + + // Точка столкновения в точке столкновения с плоскостью + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + + } else { + // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей + + // Вектор из ближайшей точки в центр сферы + var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x; + var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y; + var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z; + + // Если движение в сторону точки + if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) { + + // Ищем нормализованный вектор обратного направления + var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z); + var vectorX:Number = -currentDisplacement.x / vectorLength; + var vectorY:Number = -currentDisplacement.y / vectorLength; + var vectorZ:Number = -currentDisplacement.z / vectorLength; + + // Длина вектора из ближайшей точки в центр сферы + var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ; + + // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления + var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ; + + var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength; + + if (projectionInsideSphereLengthSqr > 0) { + // Находим расстояние из ближайшей точки до сферы + var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr); + + if (distance < vectorLength) { + // Столкновение сферы с ближайшей точкой произошло + + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x; + collisionPoint.y = collisionPrimitivePoint.y; + collisionPoint.z = collisionPrimitivePoint.z; + + // Находим нормаль плоскости столкновения + var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr); + collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength; + collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength; + collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength; + + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + } + } + + /** + * @private + * Определение столкновения сферы с примитивом. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void { + + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + for (var i:uint = 0; i < length; i++) { + + var p1:Point3D = points[i]; + var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0]; + + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = collisionPlanePoint.x - p1.x; + var vectorY:Number = collisionPlanePoint.y - p1.y; + var vectorZ:Number = collisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = collisionPlanePoint.x - p2.x; + vectorY = collisionPlanePoint.y - p2.y; + vectorZ = collisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + + /** + * Проверка на действительное столкновение эллипсоида с плоскостью. + */ + private function calculateCollisionWithPlaneE(plane:CollisionPlane):void { + var normalX:Number = plane.node.normal.x; + var normalY:Number = plane.node.normal.y; + var normalZ:Number = plane.node.normal.z; + // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление + // от плоскости, в этом случае столкновения не происходит. + var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ; + if (plane.infront) { + displacementAlongNormal = -displacementAlongNormal; + } + // Выходим из функции в случае удаления от плоскости + if (displacementAlongNormal < 0) { + return; + } + // Определение ближайшей к плоскости точки эллипсоида + var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2); + // Положение точки в локальной системе координат эллипсоида + var localClosestX:Number = k * normalX * _scaleX2; + var localClosestY:Number = k * normalY * _scaleY2; + var localClosestZ:Number = k * normalZ * _scaleZ2; + // Глобальные координаты точки + var px:Number = collisionSource.x + localClosestX; + var py:Number = collisionSource.y + localClosestY; + var pz:Number = collisionSource.z + localClosestZ; + // Растояние от найденной точки эллипсоида до плоскости + var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + if (closestPointDistance > plane.sourceOffset) { + // Найдена наиболее удалённая точка, расчитываем вторую + px = collisionSource.x - localClosestX; + py = collisionSource.y - localClosestY; + pz = collisionSource.z - localClosestZ; + closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset; + if (!plane.infront) { + closestPointDistance = -closestPointDistance; + } + } + // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости, + // то столкновения не произошло и нужно завершить выполнение функции + if (closestPointDistance > displacementAlongNormal) { + return; + } + // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения + // с ближайшим полигоном, лежащим в этой плоскости + if (closestPointDistance <= 0 ) { + // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость + if (plane.infront) { + collisionPlanePoint.x = px - normalX * closestPointDistance; + collisionPlanePoint.y = py - normalY * closestPointDistance; + collisionPlanePoint.z = pz - normalZ * closestPointDistance; + } else { + collisionPlanePoint.x = px + normalX * closestPointDistance; + collisionPlanePoint.y = py + normalY * closestPointDistance; + collisionPlanePoint.z = pz + normalZ * closestPointDistance; + } + } else { + // Эллипсоид не пересекается с плоскостью, ищем точку контакта + var t:Number = closestPointDistance / displacementAlongNormal; + collisionPlanePoint.x = px + currentDisplacement.x * t; + collisionPlanePoint.y = py + currentDisplacement.y * t; + collisionPlanePoint.z = pz + currentDisplacement.z * t; + } + // Проверяем примитивы плоскости + var primitive:*; + collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE; + collisionPrimitiveNearest = null; + if (plane.infront) { + if ((primitive = plane.node.primitive) != null) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + } + } else { + for (primitive in plane.node.frontPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + } else { + for (primitive in plane.node.backPrimitives) { + if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) || + ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) { + calculateCollisionWithPrimitiveE(primitive); + if (collisionPrimitive != null) { + break; + } + } + } + } + + if (collisionPrimitive != null) { + // Если точка пересечения попала в примитив + // Нормаль плоскости при столкновении - нормаль плоскости примитива + if (plane.infront) { + collisionNormal.x = normalX; + collisionNormal.y = normalY; + collisionNormal.z = normalZ; + collisionOffset = plane.node.offset; + } else { + collisionNormal.x = -normalX; + collisionNormal.y = -normalY; + collisionNormal.z = -normalZ; + collisionOffset = -plane.node.offset; + } + // Радиус эллипсоида в точке столкновения + collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px - collisionSource.x; + radiusVector.y = py - collisionSource.y; + radiusVector.z = pz - collisionSource.z; + // Точка столкновения совпадает с точкой столкновения с плоскостью примитива + collisionPoint.x = collisionPlanePoint.x; + collisionPoint.y = collisionPlanePoint.y; + collisionPoint.z = collisionPlanePoint.z; + } else { + // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива + // Трансформированная в пространство эллипсоида ближайшая точка на примитиве + px = collisionPrimitivePoint.x; + py = collisionPrimitivePoint.y; + pz = collisionPrimitivePoint.z; + + var collisionExists:Boolean; + // Квадрат расстояния из центра эллипсоида до точки примитива + var r2:Number = px*px + py*py + pz*pz; + if (r2 < _radius2) { + // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе + k = _radius / Math.sqrt(r2); + px *= k * _scaleX; + py *= k * _scaleY; + pz *= k * _scaleZ; + + collisionExists = true; + } else { + // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки + // примитива, с поверхностью эллипсоида + // Трансформированный в пространство эллипсоида противоположный вектор скорости + var vx:Number = - currentDisplacement.x / _scaleX; + var vy:Number = - currentDisplacement.y / _scaleY; + var vz:Number = - currentDisplacement.z / _scaleZ; + // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости + var a:Number = vx*vx + vy*vy + vz*vz; + var b:Number = 2 * (px*vx + py*vy + pz*vz); + var c:Number = r2 - _radius2; + var d:Number = b*b - 4*a*c; + // Решение есть только при действительном дискриминанте квадратного уравнения + if (d >=0) { + // Выбирается минимальное время, т.к. нужна первая точка пересечения + t = -0.5 * (b + Math.sqrt(d)) / a; + // Точка лежит на луче только если время положительное + if (t >= 0 && t <= 1) { + // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство + px = (px + t * vx) * _scaleX; + py = (py + t * vy) * _scaleY; + pz = (pz + t * vz) * _scaleZ; + + collisionExists = true; + } + } + } + if (collisionExists) { + // Противоположная нормаль к эллипсоиду в точке пересечения + collisionNormal.x = - px / _scaleX2; + collisionNormal.y = - py / _scaleY2; + collisionNormal.z = - pz / _scaleZ2; + collisionNormal.normalize(); + // Радиус эллипсоида в точке столкновения + collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z; + if (collisionRadius < 0) { + collisionRadius = -collisionRadius; + } + radiusVector.x = px; + radiusVector.y = py; + radiusVector.z = pz; + // Точка столкновения в ближайшей точке + collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x; + collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y; + collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z; + // Смещение плоскости столкновения + collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z; + collisionPrimitive = collisionPrimitiveNearest; + } + } + } + + /** + * @private + * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит + * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки + * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через + * минимальное время. + * + * @param primitive примитив, столкновение с которым проверяется + */ + private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void { + var length:uint = primitive.num; + var points:Array = primitive.points; + var normal:Point3D = primitive.face.globalNormal; + var inside:Boolean = true; + + var point1:Point3D; + var point2:Point3D = points[length - 1]; + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX; + localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY; + localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ; + // Обход всех рёбер примитива + for (var i:uint = 0; i < length; i++) { + point1 = point2; + point2 = points[i]; + + p1.x = p2.x; + p1.y = p2.y; + p1.z = p2.z; + + p2.x = (point2.x - currentCoords.x) / _scaleX; + p2.y = (point2.y - currentCoords.y) / _scaleY; + p2.z = (point2.z - currentCoords.z) / _scaleZ; + + // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра + // с целью определения положения точки столкновения относительно полигона + var edgeX:Number = p2.x - p1.x; + var edgeY:Number = p2.y - p1.y; + var edgeZ:Number = p2.z - p1.z; + + var vectorX:Number = localCollisionPlanePoint.x - p1.x; + var vectorY:Number = localCollisionPlanePoint.y - p1.y; + var vectorZ:Number = localCollisionPlanePoint.z - p1.z; + + var crossX:Number = vectorY * edgeZ - vectorZ * edgeY; + var crossY:Number = vectorZ * edgeX - vectorX * edgeZ; + var crossZ:Number = vectorX * edgeY - vectorY * edgeX; + + if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) { + // Точка за пределами полигона + inside = false; + + var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ; + var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr; + + // Если расстояние до прямой меньше текущего ближайшего + if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) { + // Ищем нормализованный вектор ребра + var edgeLength:Number = Math.sqrt(edgeLengthSqr); + var edgeNormX:Number = edgeX / edgeLength; + var edgeNormY:Number = edgeY / edgeLength; + var edgeNormZ:Number = edgeZ / edgeLength; + // Находим расстояние до точки перпендикуляра вдоль ребра + var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ; + var vectorLengthSqr:Number; + if (t < 0) { + // Ближайшая точка - первая + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p1.x; + collisionPrimitivePoint.y = p1.y; + collisionPrimitivePoint.z = p1.z; + collisionPrimitiveNearest = primitive; + } + } else { + if (t > edgeLength) { + // Ближайшая точка - вторая + vectorX = localCollisionPlanePoint.x - p2.x; + vectorY = localCollisionPlanePoint.y - p2.y; + vectorZ = localCollisionPlanePoint.z - p2.z; + vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ; + if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) { + collisionPrimitiveNearestLengthSqr = vectorLengthSqr; + collisionPrimitivePoint.x = p2.x; + collisionPrimitivePoint.y = p2.y; + collisionPrimitivePoint.z = p2.z; + collisionPrimitiveNearest = primitive; + } + } else { + // Ближайшая точка на ребре + collisionPrimitiveNearestLengthSqr = edgeDistanceSqr; + collisionPrimitivePoint.x = p1.x + edgeNormX * t; + collisionPrimitivePoint.y = p1.y + edgeNormY * t; + collisionPrimitivePoint.z = p1.z + edgeNormZ * t; + collisionPrimitiveNearest = primitive; + } + } + } + } + } + + // Если попали в примитив + if (inside) { + collisionPrimitive = primitive; + } + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..2a34fed --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,328 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Прямоугольный параллелепипед. + *

Параллелепипед содержит шесть поверхностей с идентификаторами "front", "back", "left", + * "right", "top", "bottom", на каждую из которых может быть установлен свой материал. + *

+ */ + public class Box extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создание нового параллелепипеда. + * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param height высота. Размерность по оси Z. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param heightSegments количество сегментов по по высоте + * @param reverse задает направление нормалей граней. Если указано значение true, то нормали будут направлены внутрь фигуры. + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы. + */ + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + height = (height < 0)? 0 : height; + + var wh:Number = width/2; + var lh:Number = length/2; + var hh:Number = height/2; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + // Создание точек + for (x = 0; x <= widthSegments; x++) { + for (y = 0; y <= lengthSegments; y++) { + for (z = 0; z <= heightSegments; z++) { + if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) { + createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z); + } + } + } + } + + // Создание поверхностей + var front:Surface = createSurface(null, "front"); + var back:Surface = createSurface(null, "back"); + var left:Surface = createSurface(null, "left"); + var right:Surface = createSurface(null, "right"); + var top:Surface = createSurface(null, "top"); + var bottom:Surface = createSurface(null, "bottom"); + + // Создание граней + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var faceId:String; + + // Для оптимизаций UV при триангуляции + var aUV:Point; + var cUV:Point; + + // Построение верхней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "top_"+x+"_"+y; + if (reverse) { + if (triangulate) { + aUV = new Point(x*wd, (lengthSegments - y)*ld); + cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1"); + setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0"); + setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, y*ld); + cUV = new Point((x + 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId); + setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + top.addFace(faceId + ":0"); + top.addFace(faceId + ":1"); + } else { + top.addFace(faceId); + } + } + } + + // Построение нижней грани + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + faceId = "bottom_" + x + "_" + y; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld); + cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld); + createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1"); + setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1"); + } else { + createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, y*ld); + cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld); + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0"); + } else { + createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId); + } + } + if (triangulate) { + bottom.addFace(faceId + ":0"); + bottom.addFace(faceId + ":1"); + } else { + bottom.addFace(faceId); + } + } + } + + // Построение фронтальной грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "front_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, z*hd); + cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0"); + } else { + createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(x*wd, z*hd); + cUV = new Point((x + 1)*wd, (z + 1)*hd); + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + front.addFace(faceId + ":0"); + front.addFace(faceId + ":1"); + } else { + front.addFace(faceId); + } + } + } + + // Построение задней грани + for (z = 0; z < heightSegments; z++) { + for (x = 0; x < widthSegments; x++) { + faceId = "back_"+x+"_"+z; + if (reverse) { + if (triangulate) { + aUV = new Point(x * wd, (z + 1) * hd); + cUV = new Point((x + 1) * wd, z * hd); + createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0"); + createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId); + setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((widthSegments - x)*wd, (z + 1)*hd); + cUV = new Point((widthSegments - x - 1)*wd, z*hd); + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0"); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0"); + createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1"); + setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1"); + } else { + createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId); + setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId); + } + } + if (triangulate) { + back.addFace(faceId + ":0"); + back.addFace(faceId + ":1"); + } else { + back.addFace(faceId); + } + } + } + + // Построение левой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "left_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point(y*ld, (z + 1)*hd); + cUV = new Point((y + 1)*ld, z*hd); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0"); + setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point((lengthSegments - y - 1)*ld, z*hd); + cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd); + createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0"); + createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + left.addFace(faceId + ":0"); + left.addFace(faceId + ":1"); + } else { + left.addFace(faceId); + } + } + } + + // Построение правой грани + for (y = 0; y < lengthSegments; y++) { + for (z = 0; z < heightSegments; z++) { + faceId = "right_" + y + "_" + z; + if (reverse) { + if (triangulate) { + aUV = new Point((lengthSegments - y)*ld, z*hd); + cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1"); + setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0"); + setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId); + setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId); + } + } else { + if (triangulate) { + aUV = new Point(y*ld, z*hd); + cUV = new Point((y + 1)*ld, (z + 1)*hd); + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0"); + setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0"); + createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1"); + setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1"); + } else { + createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId); + setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId); + } + } + if (triangulate) { + right.addFace(faceId + ":0"); + right.addFace(faceId + ":1"); + } else { + right.addFace(faceId); + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Box(0, 0, 0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "box" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as new file mode 100644 index 0000000..b410241 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as @@ -0,0 +1,277 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Усеченный конус или цилиндр. + */ + public class Cone extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создает усеченный конус или цилиндр. + *

Различные значения параметров позволяют создавать различные примитивы. + * При установленном параметре topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном bottomRadius = topRadius будет построен цилиндр.

+ *

По умолчанию параметр triangulate установлен в false и на примитив не может быть наложена текстура. + * Только при установленном параметре triangulate в true это возможно.

+ *

После создания примитив всегда содержит в себе поверхность "side". + * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom", + * при установленном параметре topRadius в примитиве создается поверхность "top". + * На каждую из поверхностей может быть наложен свой материал

+ * + * @param height высота примтива. Размерность по оси Z. Не может быть меньше нуля. + * @param bottomRadius нижний радиус примитива + * @param topRadius верхний радиус примтива + * @param heightSegments количество сегментов по высоте примитива + * @param radialSegments количество сегментов по радиусу примтива + * @param reverse задает направление нормалей. При значении true нормли будут направлены внутрь примитива. + * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы + * и появится возможность наложить на примитив текстуру. + */ + public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) { + + if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) { + return; + } + height = (height < 0)? 0 : height; + bottomRadius = (bottomRadius < 0)? 0 : bottomRadius; + topRadius = (topRadius < 0)? 0 : topRadius; + + const radialSegment:Number = MathUtils.DEG360/radialSegments; + const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments; + const heightSegment:Number = height/heightSegments; + const halfHeight:Number = height*0.5 + const uSegment:Number = 1/radialSegments; + const vSegment:Number = 1/heightSegments; + + // Создание вершин + if (topRadius == 0 || triangulate) { + var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp"); + } + if (bottomRadius == 0 || triangulate) { + var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown"); + } + + var radial:uint; + var segment:uint; + + var topSegment:uint = heightSegments - int(topRadius == 0); + var bottomSegment:uint = int(bottomRadius == 0) ; + for (segment = bottomSegment; segment <= topSegment; segment++) { + for (radial = 0; radial < radialSegments; radial++) { + var currentAngle:Number = radialSegment*radial; + var currentRadius:Number = bottomRadius - (radiusSegment*segment); + createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var face:Face; + + var points:Array; + + var side:Surface = createSurface(null, "side"); + + if (topRadius == 0) { + // Создание граней у верхнего полюса + var prevRadial:uint = radialSegments - 1; + var centerUV:Point = new Point(0.5, 1); + var v:Number = topSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment); + if (triangulate) { + setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней верхней крышки + var top:Surface = createSurface(null, "top"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + var UV:Point; + var prevUV:Point; + if (reverse) { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + top.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + topSegment); + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + topSegment); + } + } + top.addFace(createFace(points, "top")); + } + } + // Создание боковых граней + var face2:Face; + var aUV:Point; + var cUV:Point; + for (segment = bottomSegment; segment < topSegment; segment++) { + prevRadial = radialSegments - 1; + v = segment * vSegment; + for (radial = 0; radial < radialSegments; radial++) { + if (triangulate) { + if (reverse) { + face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"); + face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"); + aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment) + cUV = new Point(1 - prevRadial*uSegment, v); + setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2); + } else { + face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"); + face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"); + aUV = new Point(prevRadial*uSegment, v) + cUV = new Point((prevRadial + 1)*uSegment, v + vSegment); + setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face); + setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2); + } + side.addFace(face); + side.addFace(face2); + } else { + if (reverse) { + side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } else { + side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment)); + } + } + prevRadial = radial; + } + } + + if (bottomRadius == 0) { + // Создание граней у нижнего полюса + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0); + v = bottomSegment*vSegment; + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0"); + if (triangulate) { + setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face); + } + side.addFace(face); + prevRadial = radial; + } + } + } else { + // Создание граней нижней крышки + var bottom:Surface = createSurface(null, "bottom"); + if (triangulate) { + prevRadial = radialSegments - 1; + centerUV = new Point(0.5, 0.5); + if (reverse) { + prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial*radialSegment; + UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, prevUV, UV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } else { + prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5); + for (radial = 0; radial < radialSegments; radial++) { + face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial); + currentAngle = radial * radialSegment; + UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5); + setUVsToFace(centerUV, UV, prevUV, face); + bottom.addFace(face); + prevUV = UV; + prevRadial = radial; + } + } + } else { + points = new Array(); + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + points.push(radial + "_" + bottomSegment); + } + } else { + for (radial = (radialSegments - 1); radial < uint(-1); radial--) { + points.push(radial + "_" + bottomSegment); + } + } + bottom.addFace(createFace(points, "bottom")); + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Cone(0, 0, 0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "cone" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as new file mode 100644 index 0000000..dddcf48 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as @@ -0,0 +1,209 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Геоплоскость. + */ + public class GeoPlane extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создает геоплоскость. + *

Геоплоскость это плоскость с сетчатой структурой граней.

+ *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси X. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значение параметра равно true, то создаётся двусторонняя поверхность + * @param reverse флаг инвертирования нормалей + */ + public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var hh:Number = length/2; + // Размеры сегмента + var ws:Number = width/widthSegments; + var hs:Number = length/lengthSegments; + + // Размеры UV-сегмента + var us:Number = 1/widthSegments; + var vs:Number = 1/lengthSegments; + + // Создание точек + var x:uint; + var y:uint; + var frontUV:Array = new Array(); + var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array(); + for (y = 0; y <= lengthSegments; y++) { + frontUV[y] = new Array(); + if (backUV != null) { + backUV[y] = new Array(); + } + for (x = 0; x <= widthSegments; x++) { + if ((y & 1) == 0) { + // Если чётный ряд + createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(x*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(x*us, 1 - y*vs); + } + } else { + // Если нечётный ряд + if (x == 0) { + // Первая точка + createVertex(-wh, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point(0, y*vs); + if (backUV != null) { + backUV[y][x] = new Point(0, 1 - y*vs); + } + } else { + createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x); + frontUV[y][x] = new Point((x - 0.5)*us, y*vs); + if (backUV != null) { + backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs); + } + if (x == widthSegments) { + // Последняя точка + createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1)); + frontUV[y][x + 1] = new Point(1, y*vs); + if (backUV != null) { + backUV[y][x + 1] = new Point(1, 1 - y*vs); + } + } + } + } + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + var face:Face; + for (y = 0; y < lengthSegments; y++) { + for (var n:uint = 0; n <= (widthSegments << 1); n++) { + x = n >> 1; + if ((y & 1) == 0) { + // Если чётный ряд + if ((n & 1) == 0) { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } + } else { + // Если нечётный ряд + if ((n & 1) == 0) { + // Если остриём вниз + if (twoSided || !reverse) { + face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face); + } + back.addFace(face); + } + } else { + // Если остриём вверх + if (twoSided || !reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]); + setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face); + front.addFace(face); + } + if (twoSided || reverse) { + face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]); + if (backUV != null) { + setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face); + } else { + setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face); + } + back.addFace(face); + } + } + } + } + } + + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoPlane(0, 0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "geoPlane" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..fd11ecd --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,334 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.types.Point3D; + import alternativa.utils.MathUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Геосфера. + */ + public class GeoSphere extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создает геосферу. + *

Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

Текстурные координаты у геосферы не находятся в промежутке [0, 1], + * поэтому для материала с текстурой необходимо устанавливать флаг repeat.

+ * + * @param radius радиус геосферы. Не может быть меньше нуля. + * @param segments количество сегментов геосферы + * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы. + */ + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + if (segments == 0) { + return; + } + radius = (radius < 0)? 0 : radius; + + const sections:uint = 20; + + //var nfaces:uint = sections*segments*segments; + //var nverts:Number = nfaces/2 + 2; + var points:Array = new Array(); + + var i:uint; + var f:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + points.push(createVertex(0, 0, radius, "poleUp")); + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG360*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, subz)); + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = MathUtils.DEG180*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + points.push(createVertex(subrad*cos, subrad*sin, -subz)); + } + points.push(createVertex(0, 0, -radius, "poleDown")); + + for (i = 1; i < 6; i++) { + interpolate(0, i, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i % 5 + 1, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, i + 5, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i, (i + 3) % 5 + 6, segments, points); + } + for (i = 1; i < 6; i++) { + interpolate(i + 5, i % 5 + 6, segments, points); + } + for (i = 6; i < 11; i++) { + interpolate(11, i, segments, points); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points); + } + } + // Создание граней + var face:Face; + var surface:Surface = createSurface(); + for (f = 0; f < sections; f++) { + for (var row:uint = 0; row < segments; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + var va:Vertex = points[a]; + var vb:Vertex = points[b]; + var vc:Vertex = points[c]; + var aUV:Point; + var bUV:Point; + var cUV:Point; + var coordA:Point3D = va._coords; + var coordB:Point3D = vb._coords; + var coordC:Point3D = vc._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + // полюс + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, cUV, bUV, face); + } else { + face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f); + setUVsToFace(aUV, bUV, cUV, face); + } + surface.addFace(face); + //trace(a + "_" + b + "_" + c); + if (column < row) { + b = findVertices(segments, f, row, column + 1); + var vd:Vertex = points[b]; + coordB = vd._coords; + + if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } else { + aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } else { + bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5); + } + if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } else { + cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5); + } + if (a == 0 || a == 11) { + aUV.x = bUV.x + (cUV.x - bUV.x)*0.5; + } + if (b == 0 || b == 11) { + bUV.x = aUV.x + (cUV.x - aUV.x)*0.5; + } + if (c == 0 || c == 11) { + cUV.x = aUV.x + (bUV.x - aUV.x)*0.5; + } + + if (reverse) { + face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f); + aUV.x = 1 - aUV.x; + bUV.x = 1 - bUV.x; + cUV.x = 1 - cUV.x; + setUVsToFace(aUV, bUV, cUV, face); + } else { + face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f); + setUVsToFace(aUV, cUV, bUV, face); + } + surface.addFace(face); + } + } + } + } + } + +/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point { + if (radius == 0) { + radius = point.length; + } + if (reverse) { + var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360; + } else { + u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5; + } + return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5); + } + */ + private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void { + if (num < 2) { + return; + } + var a:Vertex = Vertex(points[v1]); + var b:Vertex = Vertex(points[v2]); + var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin)); + } + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new GeoSphere(0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "geoSphere" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..167a323 --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,129 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Плоскость. + */ + public class Plane extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создает плоскость. + *

Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров. + * При значении reverse установленном в true примитив будет содержать грань - "back". + * При значении reverse установленном в false примитив будет содержать грань - "front". + * Параметр twoSided указывает методу создать обе поверхности.

+ * + * @param width ширина. Размерность по оси Х. Не может быть меньше нуля. + * @param length длина. Размерность по оси Y. Не может быть меньше нуля. + * @param widthSegments количество сегментов по ширине + * @param lengthSegments количество сегментов по длине + * @param twoSided если значении параметра равно true, то формируется двусторонняя плоскость + * @param reverse инвертирование нормалей + * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы. + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) { + super(); + + if ((widthSegments == 0) || (lengthSegments == 0)) { + return; + } + width = (width < 0)? 0 : width; + length = (length < 0)? 0 : length; + + // Середина + var wh:Number = width/2; + var lh:Number = length/2; + + // Размеры сегмента + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + + // Размеры UV-сегмента + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + + // Создание точек и UV + var x:int; + var y:int; + var uv:Array = new Array(); + for (y = 0; y <= lengthSegments; y++) { + uv[y] = new Array(); + for (x = 0; x <= widthSegments; x++) { + uv[y][x] = new Point(x*wd, y*ld); + createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y); + } + } + + // Создание поверхностей + var front:Surface; + var back:Surface; + + if (twoSided || !reverse) { + front = createSurface(null, "front"); + } + if (twoSided || reverse) { + back = createSurface(null, "back"); + } + + // Создание полигонов + for (y = 0; y < lengthSegments; y++) { + for (x = 0; x < widthSegments; x++) { + if (twoSided || !reverse) { + if (triangulate) { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0"); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1"); + setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1"); + front.addFace("front" + x + "_" + y + ":0"); + front.addFace("front" + x + "_" + y + ":1"); + } else { + createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y); + setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y); + front.addFace("front" + x + "_" + y); + } + } + if (twoSided || reverse) { + if (triangulate) { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0"); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0"); + createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1"); + setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1"); + back.addFace("back" + x + "_" + y + ":0"); + back.addFace("back"+x+"_"+y + ":1"); + } else { + createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y); + setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y); + back.addFace("back" + x + "_" + y); + } + } + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Plane(0, 0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "plane" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as new file mode 100644 index 0000000..cb2424f --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as @@ -0,0 +1,157 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.utils.MathUtils; + + import flash.geom.Point; + + use namespace alternativa3d; + + /** + * Сфера. + */ + public class Sphere extends Mesh { + + // Инкремент количества объектов + private static var counter:uint = 0; + + /** + * Создает сферу. + *

После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.

+ *

По умолчанию параметр triangulate установлен в false и на сферу нельзя наложить текстуру. + * Только при установленном triangulate в true это возможно.

+ * + * @param radius Радиус сферы. Не может быть меньше нуля. + * @param radialSegments количество сегментов по экватору сферы + * @param heightSegments количество сегментов по высоте + * @param reverse флаг инвертирования нормалей. При параметре установленном в true нормали направлены внутрь сферы. + * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы, + * и будет возможно наложить на примитив текстуру. + */ + public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) { + if ((radialSegments < 3) || (heightSegments < 2)) { + return; + } + radius = (radius < 0)? 0 : radius; + + var poleUp:Vertex = createVertex(0, 0, radius, "poleUp"); + var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown"); + + const radialAngle:Number = MathUtils.DEG360/radialSegments; + const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1); + + var radial:uint; + var segment:uint; + + // Создание вершин + for (segment = 1; segment < heightSegments; segment++) { + var currentHeightAngle:Number = heightAngle*segment; + var segmentRadius:Number = Math.sin(currentHeightAngle)*radius; + var segmentZ:Number = Math.cos(currentHeightAngle)*radius; + for (radial = 0; radial < radialSegments; radial++) { + var currentRadialAngle:Number = radialAngle*radial; + createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment); + } + } + + // Создание граней и поверхности + var surface:Surface = createSurface(); + + var prevRadial:uint = radialSegments - 1; + var lastSegmentString:String = "_" + (heightSegments - 1); + + var uStep:Number = 1/radialSegments; + var vStep:Number = 1/heightSegments; + + var face:Face; + + // Для триангуляции + var aUV:Point; + var cUV:Point; + + var u:Number; + + if (reverse) { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString)); + + // Если включена триангуляция + if (triangulate) { + // Триангулируем середину и просчитываем маппинг + u = uStep*prevRadial; + setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1))); + cUV = new Point(1 - u, 1 - vStep*segment); + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString); + + } else { + // Просто создаем середину + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } else { + for (radial = 0; radial < radialSegments; radial++) { + // Грани верхнего полюса + surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0")); + // Грани нижнего полюса + surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString)); + + if (triangulate) { + u = uStep*prevRadial; + setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0"); + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + aUV = new Point(u, 1 - (vStep*segment)); + cUV = new Point(u + uStep, 1 - vStep * (segment + 1)); + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0")); + surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1")); + setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0"); + setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1"); + } + setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString); + } else { + // Грани середки + for (segment = 1; segment < (heightSegments - 1); segment++) { + surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment)); + } + } + prevRadial = (radial == 0) ? 0 : prevRadial + 1; + } + } + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Sphere(0, 0); + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "sphere" + ++counter; + } + + } +} diff --git a/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as new file mode 100644 index 0000000..5d25b2a --- /dev/null +++ b/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as @@ -0,0 +1,843 @@ +package alternativa.utils { + + import alternativa.engine3d.*; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Mesh; + import alternativa.engine3d.core.Surface; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.SurfaceMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.TextureMaterialPrecision; + import alternativa.engine3d.materials.WireMaterial; + import alternativa.types.*; + + import flash.display.BlendMode; + import flash.geom.Point; + + use namespace alternativa3d; + use namespace alternativatypes; + + /** + * Утилиты для работы с Mesh-объектами. + */ + public class MeshUtils { + + static private var verticesSort:Array = ["x", "y", "z"]; + static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC]; + + /** + * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются. + * + * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh + * + * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов + */ + static public function uniteMeshes(... meshes):Mesh { + var res:Mesh = new Mesh(); + + var length:uint = meshes.length; + var key:*; + var vertex:Vertex; + var face:Face; + var j:uint; + for (var i:uint = 0; i < length; i++) { + var mesh:Mesh = meshes[i]; + var vertices:Map = mesh._vertices.clone(); + for (key in vertices) { + vertex = vertices[key]; + vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z); + } + var faces:Map = mesh._faces.clone(); + for (key in faces) { + face = faces[key]; + var faceVertices:Array = new Array().concat(face._vertices); + for (j = 0; j < face._verticesCount; j++) { + vertex = faceVertices[j]; + faceVertices[j] = vertices[vertex.id]; + } + faces[key] = res.createFace(faceVertices); + res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]); + } + for (key in mesh._surfaces) { + var surface:Surface = mesh._surfaces[key]; + var surfaceFaces:Array = surface._faces.toArray(); + var numFaces:uint = surfaceFaces.length; + for (j = 0; j < numFaces; j++) { + face = surfaceFaces[j]; + surfaceFaces[j] = faces[face.id]; + } + var newSurface:Surface = res.createSurface(surfaceFaces); + newSurface.material = SurfaceMaterial(surface.material.clone()); + } + } + return res; + } + + /** + * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности. + * + * @param mesh объект, вершины которого объединяются + * @param threshold погрешность измерения расстояний + */ + static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void { + // Получаем список вершин меша и сортируем по координатам + var vertices:Array = mesh._vertices.toArray(true); + vertices.sortOn(verticesSort, verticesSortOptions); + + // Поиск вершин с одинаковыми координатами + var weld:Map = new Map(true); + var vertex:Vertex; + var currentVertex:Vertex = vertices[0]; + var length:uint = vertices.length; + var i:uint; + for (i = 1; i < length; i++) { + vertex = vertices[i]; + if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) { + weld[vertex] = currentVertex; + } else { + currentVertex = vertex; + } + } + + // Собираем грани объединяемых вершин + var faces:Set = new Set(true); + var keyVertex:*; + var keyFace:*; + for (keyVertex in weld) { + vertex = keyVertex; + for (keyFace in vertex._faces) { + faces[keyFace] = true; + } + } + + // Заменяем грани + for (keyFace in faces) { + var face:Face = keyFace; + var id:Object = mesh.getFaceId(face); + var surface:Surface = face._surface; + var aUV:Point = face._aUV; + var bUV:Point = face._bUV; + var cUV:Point = face._cUV; + vertices = new Array().concat(face._vertices); + length = vertices.length; + for (i = 0; i < length; i++) { + vertex = weld[vertices[i]]; + if (vertex != null) { + vertices[i] = vertex; + } + } + mesh.removeFace(face); + face = mesh.createFace(vertices, id); + if (surface != null) { + surface.addFace(face); + } + face.aUV = aUV; + face.bUV = bUV; + face.cUV = cUV; + } + + // Удаляем вершины + for (keyVertex in weld) { + mesh.removeVertex(keyVertex); + } + } + + /** + * Объединение соседних граней, образующих плоский выпуклый многоугольник. + * + * @param mesh объект, грани которого объединяются + * @param angleThreshold погрешность измерения углов + * @param uvThreshold погрешность измерения UV-координат + * @param ignoreLineJoints значение true запрещает объединения, в результате которых два соседних ребра оказываются на одной линии. + * Например, если флаг включен, два прямоугольника с общим ребром не объединятся. + */ + static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0, ignoreLineJoints:Boolean = false):void { + angleThreshold = Math.cos(angleThreshold); + var digitThreshold:Number = 0.001; + + var vertex:Vertex; + var face:Face; + var sibling:Face; + var key:*; + var i:uint; + var normal:Point3D; + + // Формируем списки граней + var faces1:Set = new Set(true); + var faces2:Set = new Set(true); + + // Формируем список нормалей + var normals:Map = new Map(true); + for each (face in mesh._faces.clone()) { + normal = new Point3D(); + vertex = face._vertices[0]; + var av:Point3D = vertex._coords; + vertex = face._vertices[1]; + var abx:Number = vertex._coords.x - av.x; + var aby:Number = vertex._coords.y - av.y; + var abz:Number = vertex._coords.z - av.z; + vertex = face._vertices[2]; + var acx:Number = vertex._coords.x - av.x; + var acy:Number = vertex._coords.y - av.y; + var acz:Number = vertex._coords.z - av.z; + normal.x = acz*aby - acy*abz; + normal.y = acx*abz - acz*abx; + normal.z = acy*abx - acx*aby; + var normalLength:Number = Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z); + if (normalLength > digitThreshold) { + normal.x /= normalLength; + normal.y /= normalLength; + normal.z /= normalLength; + faces1[face] = true; + normals[face] = normal; + } else { + mesh.removeFace(face); + } + } + + // Объединение + do { + // Флаг объединения + var weld:Boolean = false; + // Объединяем грани + while ((face = faces1.take()) != null) { + //var num:uint = face.num; + //var vertices:Array = face.vertices; + var currentWeld:Boolean = false; + + // Проверка общих граней по точкам + + + // Проверка общих граней по рёбрам + + // Перебираем точки грани + for (i = 0; (i < face._verticesCount) && !currentWeld; i++) { + var faceIndex1:uint = i; + var faceIndex2:uint; + var siblingIndex1:int; + var siblingIndex2:uint; + + // Перебираем грани текущей точки + vertex = face._vertices[faceIndex1]; + var vertexFaces:Set = vertex.faces; + for (key in vertexFaces) { + sibling = key; + // Если грань в списке на объединение и в одной поверхности + if (faces1[sibling] && face._surface == sibling._surface) { + faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0; + siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]); + // Если общее ребро + if (siblingIndex1 >= 0) { + // Если грани сонаправлены + normal = normals[face]; + if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) { + // Если в точках объединения нет перегибов + siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0; + + // Расширяем грани объединения + var i1:uint; + var i2:uint; + while (true) { + i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1); + i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0; + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex1 = i1; + siblingIndex2 = i2; + } else { + break; + } + } + + while (true) { + i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0; + i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1); + if (face._vertices[i1] == sibling._vertices[i2]) { + faceIndex2 = i1; + siblingIndex1 = i2; + } else { + break; + } + } + + vertex = face._vertices[faceIndex1]; + var a:Point3D = vertex.coords; + vertex = face._vertices[faceIndex2]; + var b:Point3D = vertex.coords; + + // Считаем первый перегиб + vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0]; + var c:Point3D = vertex.coords; + vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)]; + var d:Point3D = vertex.coords; + + var cx:Number = c.x - a.x; + var cy:Number = c.y - a.y; + var cz:Number = c.z - a.z; + var dx:Number = d.x - a.x; + var dy:Number = d.y - a.y; + var dz:Number = d.z - a.z; + + var crossX:Number = cy*dz - cz*dy; + var crossY:Number = cz*dx - cx*dz; + var crossZ:Number = cx*dy - cy*dx; + var zeroCross:Boolean = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold; + if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) { + break; + } + + // Считаем второй перегиб + vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0]; + c = vertex.coords; + vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)]; + d = vertex.coords; + + cx = c.x - b.x; + cy = c.y - b.y; + cz = c.z - b.z; + dx = d.x - b.x; + dy = d.y - b.y; + dz = d.z - b.z; + + crossX = cy*dz - cz*dy; + crossY = cz*dx - cx*dz; + crossZ = cx*dy - cy*dx; + zeroCross = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold; + if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) { + break; + } + + // Флаг наличия UV у обеих граней + var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null); + + if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) { + + // Если грани имеют UV, проверяем совместимость + if (hasUV) { + vertex = sibling._vertices[0]; + var uv:Point = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[1]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) { + break; + } + + vertex = sibling._vertices[2]; + uv = face.getUVFast(vertex.coords, normal); + if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) { + break; + } + } + + // Формируем новую грань + var newVertices:Array = new Array(); + var n:uint = faceIndex2; + do { + newVertices.push(face._vertices[n]); + n = (n < face._verticesCount - 1) ? (n + 1) : 0; + } while (n != faceIndex1); + n = siblingIndex2; + do { + newVertices.push(sibling._vertices[n]); + n = (n < sibling._verticesCount - 1) ? (n + 1) : 0; + } while (n != siblingIndex1); + + // Выбираем начальную точку + n = getBestBeginVertexIndex(newVertices); + for (var m:uint = 0; m < n; m++) { + newVertices.push(newVertices.shift()); + } + + // Заменяем грани новой + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (hasUV) { + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + mesh.removeFace(sibling); + + // Обновляем список нормалей + delete normals[sibling]; + delete normals[face]; + normals[newFace] = newFace.normal; + + // Обновляем списки расчётов + delete faces1[sibling]; + faces2[newFace] = true; + + // Помечаем объединение + weld = true; + currentWeld = true; + break; + } + } + } + } + } + } + + // Если не удалось объединить, переносим грань + faces2[face] = true; + } + + // Меняем списки + var fs:Set = faces1; + faces1 = faces2; + faces2 = fs; + } while (weld); + + removeIsolatedVertices(mesh); + removeUselessVertices(mesh); + } + + /** + * Удаление вершин объекта, не принадлежащим ни одной грани. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeIsolatedVertices(mesh:Mesh):void { + for each (var vertex:Vertex in mesh._vertices.clone()) { + if (vertex._faces.isEmpty()) { + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной. + * + * @param mesh объект, вершины которого удаляются + */ + static public function removeUselessVertices(mesh:Mesh):void { + var digitThreshold:Number = 0.001; + var v:Vertex; + var key:*; + var face:Face; + var index:uint; + var length:uint; + for each (var vertex:Vertex in mesh._vertices.clone()) { + var useless:Boolean = true; + var indexes:Map = new Map(true); + for (key in vertex._faces) { + face = key; + length = face._vertices.length; + index = face._vertices.indexOf(vertex); + v = face._vertices[index]; + var a:Point3D = v.coords; + v = face._vertices[(index < length - 1) ? (index + 1) : 0]; + var b:Point3D = v.coords; + v = face._vertices[(index > 0) ? (index - 1) : (length - 1)]; + var c:Point3D = v.coords; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + var crossX:Number = aby*acz - abz*acy; + var crossY:Number = abz*acx - abx*acz; + var crossZ:Number = abx*acy - aby*acx; + if (crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold) { + indexes[face] = index; + } else { + useless = false; + break; + } + } + if (useless && !indexes.isEmpty()) { + // Удаляем + for (key in indexes) { + var i:uint; + face = key; + index = indexes[face]; + length = face._vertices.length; + var newVertices:Array = new Array(); + for (i = 0; i < length; i++) { + if (i != index) { + newVertices.push(face._vertices[i]); + } + } + var n:uint = getBestBeginVertexIndex(newVertices); + for (i = 0; i < n; i++) { + newVertices.push(newVertices.shift()); + } + var surface:Surface = face._surface; + var newFace:Face = mesh.createFace(newVertices); + if (face._aUV != null && face._bUV != null && face._cUV != null) { + var normal:Point3D = face.normal; + newFace.aUV = face.getUVFast(newVertices[0].coords, normal); + newFace.bUV = face.getUVFast(newVertices[1].coords, normal); + newFace.cUV = face.getUVFast(newVertices[2].coords, normal); + } + if (surface != null) { + surface.addFace(newFace); + } + mesh.removeFace(face); + } + mesh.removeVertex(vertex); + } + } + } + + /** + * Удаление вырожденных граней. + * + * @param mesh объект, грани которого удаляются + */ + static public function removeSingularFaces(mesh:Mesh):void { + for each (var face:Face in mesh._faces.clone()) { + var normal:Point3D = face.normal; + if (normal.x == 0 && normal.y == 0 && normal.z == 0) { + mesh.removeFace(face); + } + } + } + + /** + * @private + * Находит наиболее подходящую первую точку. + * @param vertices + * @return + */ + static public function getBestBeginVertexIndex(vertices:Array):uint { + var bestIndex:uint = 0; + var num:uint = vertices.length; + if (num > 3) { + var maxCrossLength:Number = 0; + var v:Vertex = vertices[num - 1]; + var c1:Point3D = v.coords; + v = vertices[0]; + var c2:Point3D = v.coords; + + var prevX:Number = c2.x - c1.x; + var prevY:Number = c2.y - c1.y; + var prevZ:Number = c2.z - c1.z; + + for (var i:uint = 0; i < num; i++) { + c1 = c2; + v = vertices[(i < num - 1) ? (i + 1) : 0]; + c2 = v.coords; + + var nextX:Number = c2.x - c1.x; + var nextY:Number = c2.y - c1.y; + var nextZ:Number = c2.z - c1.z; + + var crossX:Number = prevY*nextZ - prevZ*nextY; + var crossY:Number = prevZ*nextX - prevX*nextZ; + var crossZ:Number = prevX*nextY - prevY*nextX; + + + var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ; + if (crossLength > maxCrossLength) { + maxCrossLength = crossLength; + bestIndex = i; + } + + prevX = nextX; + prevY = nextY; + prevZ = nextZ; + } + // Берём предыдущий + bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1); + } + + return bestIndex; + } + + /** + * Генерация AS-класса. + * + * @param mesh объект, на базе которого генерируется класс + * @param packageName имя пакета для генерируемого класса + * @return AS-класс в текстовом виде + */ + static public function generateClass(mesh:Mesh, packageName:String = ""):String { + + var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1); + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["alternativa.engine3d.core.Mesh"] = true; + + var materialSet:Map = new Map(true); + var materialName:String; + var materialNum:uint = 1; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r"; + + + var newLine:Boolean = false; + if (mesh.mobility != 0) { + constructor += "\t\t\tmobility = " + mesh.mobility +";\r"; + newLine = true; + } + + if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) { + importSet["alternativa.types.Point3D"] = true; + constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r"; + newLine = true; + } else { + if (mesh.x != 0) { + constructor += "\t\t\tx = " + mesh.x + ";\r"; + newLine = true; + } + if (mesh.y != 0) { + constructor += "\t\t\ty = " + mesh.y + ";\r"; + newLine = true; + } + if (mesh.z != 0) { + constructor += "\t\t\tz = " + mesh.z + ";\r"; + newLine = true; + } + } + if (mesh.rotationX != 0) { + constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r"; + newLine = true; + } + if (mesh.rotationY != 0) { + constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r"; + newLine = true; + } + if (mesh.rotationZ != 0) { + constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r"; + newLine = true; + } + if (mesh.scaleX != 1) { + constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r"; + newLine = true; + } + if (mesh.scaleY != 1) { + constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r"; + newLine = true; + } + if (mesh.scaleZ != 1) { + constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + function idToString(value:*):String { + return isNaN(value) ? ("\"" + value + "\"") : value; + } + + function blendModeToString(value:String):String { + switch (value) { + case BlendMode.ADD: return "BlendMode.ADD"; + case BlendMode.ALPHA: return "BlendMode.ALPHA"; + case BlendMode.DARKEN: return "BlendMode.DARKEN"; + case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE"; + case BlendMode.ERASE: return "BlendMode.ERASE"; + case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT"; + case BlendMode.INVERT: return "BlendMode.INVERT"; + case BlendMode.LAYER: return "BlendMode.LAYER"; + case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN"; + case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY"; + case BlendMode.NORMAL: return "BlendMode.NORMAL"; + case BlendMode.OVERLAY: return "BlendMode.OVERLAY"; + case BlendMode.SCREEN: return "BlendMode.SCREEN"; + case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT"; + default: return "BlendMode.NORMAL"; + } + } + + function colorToString(value:uint):String { + var hex:String = value.toString(16).toUpperCase(); + var res:String = "0x"; + var len:uint = 6 - hex.length; + for (var j:uint = 0; j < len; j++) { + res += "0"; + } + res += hex; + return res; + } + + var i:uint; + var length:uint; + var key:*; + var id:String; + var face:Face; + var surface:Surface; + + newLine = false; + for (id in mesh._vertices) { + var vertex:Vertex = mesh._vertices[id]; + var coords:Point3D = vertex.coords; + constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r"; + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + newLine = false; + for (id in mesh._faces) { + face = mesh._faces[id]; + length = face._verticesCount; + constructor += "\t\t\tcreateFace([" + for (i = 0; i < length - 1; i++) { + constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", "; + } + constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r"; + + if (face._aUV != null || face._bUV != null || face._cUV != null) { + importSet["flash.geom.Point"] = true; + constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r"; + } + newLine = true; + } + + constructor += newLine ? "\r" : ""; + + for (id in mesh._surfaces) { + surface = mesh._surfaces[id]; + var facesStr:String = ""; + for (key in surface._faces) { + facesStr += idToString(mesh.getFaceId(key)) + ", "; + } + constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r"; + + if (surface.material != null) { + var material:String; + var defaultAlpha:Boolean = surface.material.alpha == 1; + var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL; + if (surface.material is WireMaterial) { + importSet["alternativa.engine3d.materials.WireMaterial"] = true; + var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0; + var defaultColor:Boolean = WireMaterial(surface.material).color == 0; + material = "new WireMaterial("; + if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) { + material += WireMaterial(surface.material).thickness; + if (!defaultColor || !defaultAlpha || !defaultBlendMode) { + material += ", " + colorToString(WireMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode) { + material += ", " + surface.material.alpha ; + if (!defaultBlendMode) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + } + } + } + } + } + var defaultWireThickness:Boolean; + var defaultWireColor:Boolean; + if (surface.material is FillMaterial) { + importSet["alternativa.engine3d.materials.FillMaterial"] = true; + defaultWireThickness = FillMaterial(surface.material).wireThickness < 0; + defaultWireColor = FillMaterial(surface.material).wireColor == 0; + material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color); + if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + material += ", " + surface.material.alpha; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor) { + material += ", " + FillMaterial(surface.material).wireThickness; + if (!defaultWireColor) { + material += ", " + colorToString(FillMaterial(surface.material).wireColor); + } + } + } + } + } + if (surface.material is TextureMaterial) { + importSet["alternativa.engine3d.materials.TextureMaterial"] = true; + var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat; + var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth; + defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0; + defaultWireColor = TextureMaterial(surface.material).wireColor == 0; + var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM; + + if (TextureMaterial(surface.material).texture == null) { + materialName = "null"; + } else { + importSet["alternativa.types.Texture"] = true; + if (materialSet[TextureMaterial(surface.material).texture] == undefined) { + materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++; + materialSet[TextureMaterial(surface.material).texture] = materialName; + } else { + materialName = materialSet[TextureMaterial(surface.material).texture]; + } + materialName = materialName.split(".")[0]; + } + material = "new TextureMaterial(" + materialName; + if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).alpha; + if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).repeat; + if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).smooth; + if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) { + importSet["flash.display.BlendMode"] = true; + material += ", " + blendModeToString(surface.material.blendMode); + if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) { + material += ", " + TextureMaterial(surface.material).wireThickness; + if (!defaultWireColor || !defaultPrecision) { + material += ", " + colorToString(TextureMaterial(surface.material).wireColor); + if (!defaultPrecision) { + material += ", " + TextureMaterial(surface.material).precision; + } + } + } + } + } + } + } + } + + constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r"; + } + } + + var imports:String = ""; + newLine = false; + + var importArray:Array = new Array(); + for (key in importSet) { + importArray.push(key); + } + importArray.sort(); + + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + var embeds:String = ""; + newLine = false; + for each (materialName in materialSet) { + var materialClassName:String = materialName.split(".")[0]; + var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1); + embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r"; + newLine = true; + } + embeds += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + } +}