This commit is contained in:
Pyogenics
2024-09-28 17:41:47 +01:00
parent ecf60d961b
commit 413f563f33
76 changed files with 16648 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
package alternativa {
/**
* Класс содержит информацию о версии библиотеки.
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
*/
public class Alternativa3D {
/**
* Версия библиотеки в формате: поколение.feature-версия.fix-версия
*/
public static const version:String = "8.0.0";
}
}

View File

@@ -0,0 +1,3 @@
package alternativa.engine3d {
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
}

View File

@@ -0,0 +1,180 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Object3D;
/**
* Анимация объекта.
*/
public class Animation {
use namespace alternativa3d;
protected static const WEIGHTS_X:uint = 0;
protected static const WEIGHTS_Y:uint = 1;
protected static const WEIGHTS_Z:uint = 2;
protected static const WEIGHTS_ROT_X:uint = 3;
protected static const WEIGHTS_ROT_Y:uint = 4;
protected static const WEIGHTS_ROT_Z:uint = 5;
protected static const WEIGHTS_SCALE_X:uint = 6;
protected static const WEIGHTS_SCALE_Y:uint = 7;
protected static const WEIGHTS_SCALE_Z:uint = 8;
protected static const WEIGHTS_BOUND_BOX:uint = 9;
/**
* Анимируемый объект.
*/
public var object:Object3D = null;
/**
* Вес анимации по отношению к другим анимациям этого параметра.
* Анимация с более высоким весом оказывает большее влияние на конечное значение параметра.
* Вес наследуется на дочерние анимации.
*/
public var weight:Number = 1.0;
/**
* Скорость проигрывания анимации. Скорость наследуется на дочерние анимации.
*/
public var speed:Number = 1.0;
/**
* Длина анимации, включая дочерние анимации.
* После изменения длины треков и длины дочерних анимаций необходимо вызвать updateLength().
* @see #updateLength()
*/
public var length:Number = 0.0;
/**
* Создает анимацию
*
* @param object анимируемый объект
* @param weight вес анимации
* @param speed скорость проигрывания анимации
*/
public function Animation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) {
this.object = object;
this.weight = weight;
this.speed = speed;
}
/**
* Пересчитывает длину анимации.
*/
public function updateLength():void {
length = 0;
}
/**
* Расчет кадра анимации.
*
* @param position время кадра
*/
public function sample(position:Number):void {
prepareBlending();
blend(position, 1.0);
}
/**
* Подготавливает объект к выполнению смешения анимаций.
* Для смешения нескольких анимаций, необходимо на каждой из них вызвать prepareBlending() и затем blend().
* Для смешения и проигрывания нескольких анимаций может быть удобнее использовать класс AnimationController.
*
* @see #blend()
* @see AnimationController
*/
public function prepareBlending():void {
if (object != null) {
if (object.weightsSum != null) {
object.weightsSum[0] = 0; object.weightsSum[1] = 0; object.weightsSum[2] = 0;
object.weightsSum[3] = 0; object.weightsSum[4] = 0; object.weightsSum[5] = 0;
object.weightsSum[6] = 0; object.weightsSum[7] = 0; object.weightsSum[8] = 0;
object.weightsSum[9] = 0;
} else {
object.weightsSum = new Vector.<Number>(10);
}
}
}
/**
* Выполняет смешение анимации с другими анимациями этого параметра.
* Перед вызовом этого метода, на всех анимациях которые требуется смешать, нужно вызвать метод prepareBlending().
* Не вызывать метод, если вес меньше нуля.
* Для смешения и проигрывания нескольких анимаций может быть удобнее использовать класс AnimationController.
*
* @param position время анимации
* @param weight вес анимации
*
* @see #prepareBlending()
* @see AnimationController
*/
public function blend(position:Number, weight:Number):void {
position = (position < 0) ? 0 : (position > length) ? length : position;
control(position, weight);
}
/**
* Расчитывает значение интерполяции параметра для текущей анимации и заданного веса.
* @param param индекс параметра
* @param weight вес анимации
*/
protected function calculateBlendInterpolation(param:int, weight:Number):Number {
var sum:Number = object.weightsSum[param];
sum += weight;
object.weightsSum[param] = sum;
return weight/sum;
}
/**
* Реализация расчета кадра анимации.
* @param position время кадра анимации
* @param weight вес анимации
*/
protected function control(position:Number, weight:Number):void {
}
/**
* Возвращает копию анимации. Копия анимации использует общие ключевые кадры с исходной анимацией.
*/
public function clone():Animation {
var cloned:Animation = new Animation(object, weight, speed);
cloned.length = length;
return cloned;
}
/**
* Создает копию анимации, которая будет анимировать указанный объект.
*
* @param object объект, который должен анимироваться копией текущей анимации
* @return копия анимации
*/
public function copyTo(object:Object3D):Animation {
var result:Animation = clone();
result.object = object;
return result;
}
/**
* Возвращает часть анимации в промежутке времени между start и end.
* @param start начало части анимации
* @param end конец части анимации
* @return часть анимации
*/
public function slice(start:Number, end:Number = Number.MAX_VALUE):Animation {
return new Animation(object, weight, speed);
}
/**
* Интерполяция между двумя ненормализованными углами.
*/
protected function interpolateAngle(angle1:Number, angle2:Number, weight1:Number):Number {
const PI2:Number = 2*Math.PI;
angle1 = (angle1 > Math.PI) ? angle1%PI2 - PI2 : (angle1 <= -Math.PI) ? (angle1%PI2) + PI2 : angle1;
angle2 = (angle2 > Math.PI) ? angle2%PI2 - PI2 : (angle2 <= -Math.PI) ? (angle2%PI2) + PI2 : angle2;
var delta:Number = angle2 - angle1;
delta = (delta > Math.PI) ? delta - PI2 : (delta < -Math.PI) ? delta + PI2 : delta;
return angle1 + weight1 * delta;
}
}
}

View File

@@ -0,0 +1,179 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.alternativa3d;
/**
* Управляет проигрыванием и смешением анимаций.
*/
public class AnimationController {
use namespace alternativa3d;
/**
* Включение/выключение контроллера.
*/
public var enabled:Boolean = true;
private var _animations:Object = new Object();
/**
* Создает экземпляр контроллера.
*/
public function AnimationController() {
}
/**
* Проиграть анимацию сначала.
*
* @param name имя анимации для проигрывания
* @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения.
*/
public function replay(name:String, fade:Number = 0):void {
var state:AnimationState = _animations[name];
if (state == null) {
throw new ArgumentError('Animation with name "' + name + '" not found');
}
state.replay(fade);
}
/**
* Продолжить проигрывание анимации с текущей позиции.
*
* @param name имя анимации для проигрывания
* @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения.
*/
public function play(name:String, fade:Number = 0):void {
var state:AnimationState = _animations[name];
if (state == null) {
throw new ArgumentError('Animation with name "' + name + '" not found');
}
state.play(fade);
}
/**
* Остановить анимацию.
*
* @param name имя анимации для останова
* @param fade время в течение которого вес анимации должен уменьшиться с максимального значения до 0.
*/
public function stop(name:String, fade:Number = 0):void {
var state:AnimationState = _animations[name];
if (state == null) {
throw new ArgumentError('Animation with name "' + name + '" not found');
}
state.stop(fade);
}
/**
* Проиграть все анимации сначала.
*
* @param fade время в течение которого вес каждой анимации должен увеличиться с нуля до максимального значения.
*/
public function replayAll(fade:Number = 0):void {
for each (var state:AnimationState in _animations) {
state.replay(fade);
}
}
/**
* Продолжить проигрывание всех анимаций с текущей позиции.
*
* @param fade время в течение которого вес каждой анимации должен увеличиться с нуля до максимального значения.
*/
public function playAll(fade:Number = 0):void {
for each (var state:AnimationState in _animations) {
state.play(fade);
}
}
/**
* Остановить все анимации.
*
* @param fade время в течение которого вес каждой анимации должен уменьшиться с максимального значения до 0.
*/
public function stopAll(fade:Number = 0):void {
for each (var state:AnimationState in _animations) {
state.stop(fade);
}
}
/**
* Проиграть анимации за прошедшее время и выполнить их смешение.
* Для автоматического ежекадрового обновления можно использовать класс AnimationTimer.
*
* @param interval прошедшее время.
*
* @see AnimationTimer
*/
public function update(interval:Number):void {
if (!enabled) {
return;
}
var state:AnimationState;
for each (state in _animations) {
state.prepareBlending();
}
for each (state in _animations) {
state.update(interval);
}
}
/**
* Добавляет анимацию в контроллер и возвращает объект состояния проигрывания анимации.
*
* @param name имя анимации
* @param animation добавляемая анимация
* @param loop проиграть анимацию сначала после достижения конца
* @return экземляр класса AnimationState через который выполняется управление проигрыванием анимации.
*
* @see AnimationState
*/
public function addAnimation(name:String, animation:Animation, loop:Boolean = true):AnimationState {
var state:AnimationState = _animations[name];
if (state != null) {
throw new ArgumentError('Animation with this name "' + name + '" already exist');
}
state = new AnimationState(this, animation, name, loop);
_animations[name] = state;
return state;
}
/**
* Убирает анимацию из контроллера.
*
* @param name имя анимации для удаления.
*/
public function removeAnimation(name:String):void {
var state:AnimationState = _animations[name];
if (state == null) {
throw new ArgumentError('Animation with name"' + name + '" not exists');
}
delete _animations[name];
}
/**
* Возвращает объект состояния проигрывания анимации по имени.
*
* @param name имя анимации.
*
* @see AnimationState
*/
public function getAnimation(name:String):AnimationState {
return _animations[name];
}
/**
* Возвращает словарь со всеми анимациями. Свойство - имя анимации, значение - экземпляр класса AnimationState.
*
* @see AnimationState
*/
public function get animations():Object {
var result:Object = new Object();
for (var name:String in _animations) {
result[name] = _animations[name];
}
return result;
}
}
}

View File

@@ -0,0 +1,221 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Object3DContainer;
import alternativa.engine3d.objects.Joint;
import alternativa.engine3d.objects.Skin;
/**
* Группа анимаций. Предназначена для объединения и синхронизации анимаций.
*/
public class AnimationGroup extends Animation {
private var _numAnimations:int = 0;
private var _animations:Vector.<Animation> = new Vector.<Animation>();
/**
* Создает группу анимаций.
*
* @param object анимируемый объект
* @param weight вес анимации
* @param speed скорость проигрывания анимации
*/
public function AnimationGroup(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) {
super(object, weight, speed);
}
/**
* @inheritDoc
*/
override public function updateLength():void {
super.updateLength();
for (var i:int = 0; i < _numAnimations; i++) {
var animation:Animation = _animations[i];
animation.updateLength();
var len:Number = animation.length;
if (len > length) {
length = len;
}
}
}
/**
* @inheritDoc
*/
override public function prepareBlending():void {
super.prepareBlending();
for (var i:int = 0; i < _numAnimations; i++) {
_animations[i].prepareBlending();
}
}
/**
* @inheritDoc
*/
override public function blend(position:Number, weight:Number):void {
super.blend(position, weight);
for (var i:int = 0; i < _numAnimations; i++) {
var animation:Animation = _animations[i];
if (animation.weight != 0) {
animation.blend(position*animation.speed, weight*animation.weight);
}
}
}
private function getObjectNumChildren(object:Object3D):int {
if (object is Skin) {
return Skin(object).numJoints;
} else if (object is Joint) {
return Joint(object).numJoints;
} else if (object is Object3DContainer) {
return Object3DContainer(object).numChildren;
}
return 0;
}
private function getObjectChildAt(object:Object3D, index:int):Object3D {
if (object is Skin) {
return Skin(object).getJointAt(index);
} else if (object is Joint) {
return Joint(object).getJointAt(index);
} else if (object is Object3DContainer) {
return Object3DContainer(object).getChildAt(index);
}
return null;
}
/**
* @inheritDoc
*/
override public function clone():Animation {
var cloned:AnimationGroup = new AnimationGroup(object, weight, speed);
for (var i:int = 0; i < _numAnimations; i++) {
cloned.addAnimation(_animations[i].clone());
}
cloned.length = length;
return cloned;
}
/**
* Создает копию анимации, которая будет анимировать заданную иерархию.
* Переносятся и копируются только те анимации, которые анимирует объекты с аналогичными именами
* из заданной иерархии.
*
* @param object объект, который должен анимироваться копией текущей анимации
* @return копия анимации
*/
override public function copyTo(object:Object3D):Animation {
if (object == null) {
throw new ArgumentError("Object must be not null");
}
var base:Object3D = this.object;
if (base == null) {
throw new ArgumentError("Base animation object must be not null");
}
var group:AnimationGroup = new AnimationGroup(object, weight, speed);
copyAnimationForEachObject(object, group);
if (group._numAnimations == 1 && group._animations[0].object == object) {
// Если только одна анимация и та принадлежит объекту, то вернуть эту анимацию
return group._animations[0];
}
return group;
}
private function copyAnimationForEachObject(object:Object3D, group:AnimationGroup):void {
var i:int;
var name:String = object.name;
var from:Object3D;
if (name != null && name.length > 0) {
collectAnimations(name, this, group, object);
}
var count:int = getObjectNumChildren(object);
for (i = 0; i < count; i++) {
var child:Object3D = getObjectChildAt(object, i);
copyAnimationForEachObject(child, group);
}
}
private function collectAnimations(name:String, animations:AnimationGroup, collector:AnimationGroup, object:Object3D, from:Object3D = null):Object3D {
for (var i:int = 0; i < animations._numAnimations; i++) {
var animation:Animation = animations._animations[i];
var group:AnimationGroup = animation as AnimationGroup;
if (group != null) {
from = collectAnimations(name, group, collector, object, from);
} else {
if (animation.object != null && animation.object.name == name && (from == null || animation.object == from)) {
from = animation.object;
collector.addAnimation(animation.copyTo(object));
}
}
}
return from;
}
/**
* @inheritDoc
*/
override public function slice(start:Number, end:Number = Number.MAX_VALUE):Animation {
var group:AnimationGroup = new AnimationGroup(object, weight, speed);
for (var i:int = 0; i < _numAnimations; i++) {
var animation:Animation = _animations[i];
group.addAnimation(animation.slice(start * animation.speed, end * animation.speed));
}
group.updateLength();
return group;
}
/**
* Добавляет дочернюю анимацию и обновляет длину анимации после этого.
*/
public function addAnimation(animation:Animation):Animation {
if (animation == null) {
throw new Error("Animation cannot be null");
}
_animations[_numAnimations++] = animation;
if (animation.length > length) {
length = animation.length;
}
return animation;
}
/**
* Убирает дочернюю анимацию и обновляет длину анимации.
*/
public function removeAnimation(animation:Animation):Animation {
var index:int = _animations.indexOf(animation);
if (index < 0) throw new ArgumentError("Animation not found");
_numAnimations--;
var j:int = index + 1;
while (index < _numAnimations) {
_animations[index] = _animations[j];
index++;
j++;
}
_animations.length = _numAnimations;
// Пересчитываем длину
length = 0;
for (var i:int = 0; i < _numAnimations; i++) {
var anim:Animation = _animations[i];
if (anim.length > length) {
length = anim.length;
}
}
return animation;
}
/**
* Количество дочерних анимаций.
*/
public function get numAnimations():int {
return _numAnimations;
}
/**
* Возвращает анимацию по индексу.
*/
public function getAnimationAt(index:int):Animation {
return _animations[index];
}
}
}

View File

@@ -0,0 +1,282 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.alternativa3d;
/**
* Cостояние проигрывания анимации в контроллере.
*/
public final class AnimationState {
use namespace alternativa3d;
/**
* Проигрываемая анимация.
*/
public var animation:Animation;
/**
* Зацикленность анимации. Зацикленная анимация будет проигрываться сначала после достижения конца.
*/
public var loop:Boolean;
/**
* Для незацикленной анимации задает время с отсчетом от конца анимации после которого начнется затухание анимации.
* 0 - без затухания, 1 - затухание с начала анимации.
*/
public var endingFadeOut:Number = 0;
private var fadeInTime:Number;
private var fadedIn:Boolean;
private var fadeInPosition:Number;
private var fadeOutTime:Number;
private var fadedOut:Boolean;
private var fadeOutPosition:Number;
private var manualControl:Boolean = false;
private var _controller:AnimationController;
private var _name:String;
private var _played:Boolean = false;
private var _position:Number = 0;
/**
* Конструктор состояния анимации, вызывается в AnimationController.
*
* @see AnimationController
*/
public function AnimationState(controller:AnimationController, animation:Animation, name:String, loop:Boolean) {
this.animation = animation;
this._controller = controller;
this._name = name;
this.loop = loop;
}
/**
* Проиграть анимацию сначала.
*
* @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения.
*/
public function replay(fade:Number = 0):void {
if (!_played) {
play(fade);
_position = 0;
}
}
/**
* Продолжить проигрывание анимации с текущей позиции.
*
* @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения.
*/
public function play(fade:Number = 0):void {
if (!_played) {
_played = true;
fadeInTime = fade;
fadedIn = true;
if (fadedOut) {
fadeInPosition = fadeInTime*(1 - fadeOutPosition/fadeOutTime);
fadedOut = false;
} else {
fadeInPosition = 0;
}
manualControl = false;
}
}
/**
* Остановить проигрывание анимации.
*
* @param fade время в течение которого вес анимации должен уменьшиться с максимального значения до 0.
*/
public function stop(fade:Number = 0):void {
if (_played) {
_played = false;
fadeOutTime = fade;
if (fadedIn) {
fadeOutPosition = fadeOutTime*(1 - fadeInPosition/fadeInTime);
fadedIn = false;
fadedOut = true;
} else {
if (!fadedOut) {
fadeOutPosition = 0;
fadedOut = true;
}
}
manualControl = false;
}
}
/**
* @private
*/
alternativa3d function prepareBlending():void {
if (animation != null) {
animation.prepareBlending();
}
}
private function loopPosition():void {
if (_position < 0) {
// _position = (length <= 0) ? 0 : _position % length;
_position = 0;
} else {
if (_position >= animation.length) {
_position = (animation.length <= 0) ? 0 : _position % animation.length;
}
}
}
private function fading(position:Number):Number {
if (position > 1) {
return 1;
}
if (position < 0) {
return 0;
}
return position;
}
/**
* @private
*/
alternativa3d function update(interval:Number):void {
if (animation == null) {
return;
}
var weight:Number = animation.weight;
if (_played) {
_position += interval*animation.speed;
if (loop) {
loopPosition();
if (fadedIn) {
fadeInPosition += interval;
if (fadeInPosition < fadeInTime) {
weight *= fading(fadeInPosition/fadeInTime);
} else {
fadedIn = false;
}
}
} else {
if (_position < 0) {
_position = 0;
if (interval < 0) {
_played = false;
}
weight = 0;
} else {
if (_position > animation.length) {
if (interval > 0) {
_position = 0;
_played = false;
} else {
_position = animation.length;
}
weight = 0;
} else {
if ((_position/animation.length + endingFadeOut) > 1) {
fadedOut = true;
fadeOutTime = endingFadeOut;
fadeOutPosition = _position/animation.length + endingFadeOut - 1;
} else {
fadedOut = false;
}
if (fadedIn) {
fadeInPosition += interval;
}
if ((fadedIn && (fadeInPosition < fadeInTime)) && fadedOut) {
var w1:Number = fading(fadeInPosition/fadeInTime);
var w2:Number = fading(1 - fadeOutPosition/fadeOutTime);
if (w1 < w2) {
weight *= w1;
} else {
weight *= w2;
fadedIn = false;
}
} else {
if (fadedIn) {
if (fadeInPosition < fadeInTime) {
weight *= fading(fadeInPosition/fadeInTime);
} else {
fadedIn = false;
}
} else if (fadedOut) {
weight *= fading(1 - fadeOutPosition/fadeOutTime);
}
}
}
}
}
} else {
if (!manualControl) {
if (fadedOut) {
_position += interval*animation.speed;
if (loop) {
loopPosition();
} else {
if (_position < 0) {
_position = 0;
} else {
if (_position >= animation.length) {
_position = animation.length;
}
}
}
fadeOutPosition += interval;
if (fadeOutPosition < fadeOutTime) {
weight *= fading(1 - fadeOutPosition/fadeOutTime);
} else {
fadedOut = false;
weight = 0;
}
} else {
weight = 0;
}
}
}
if (weight != 0) {
animation.blend(_position, weight);
}
}
/**
* Контроллер, управляющий воспроизведением анимации.
*/
public function get controller():AnimationController {
return _controller;
}
/**
* Имя анимации в контроллере.
*/
public function get name():String {
return _name;
}
/**
* Проигрывается анимация в данный момент или нет.
*/
public function get played():Boolean {
return _played;
}
/**
* Позиция проигрывания анимации.
*/
public function get position():Number {
return _position;
}
/**
* @private
*/
public function set position(value:Number):void {
_position = value;
manualControl = true;
_played = false;
fadedIn = false;
fadedOut = false;
}
}
}

View File

@@ -0,0 +1,109 @@
package alternativa.engine3d.animation {
import flash.utils.getTimer;
/**
* Выполняет ежекадровое обновление контроллеров.
*/
public class AnimationTimer {
/**
* Соотношение виртуального времени реальному
*/
public var timeScale:Number = 1.0;
private var _numControllers:int;
private var _controllers:Vector.<AnimationController> = new Vector.<AnimationController>();
private var lastTime:int = -1;
/**
* Создает экземпляр контроллера.
*/
public function AnimationTimer() {
}
/**
* Начинает отсчет времени.
*/
public function start():void {
lastTime = getTimer();
}
/**
* Обновляет контроллеры с времени последнего вызова start() или update().
*
* @see #start()
*/
public function update():void {
if (lastTime >= 0) {
var time:int = getTimer();
var interval:Number = 0.001*timeScale*(time - lastTime);
for (var i:int = 0; i < _numControllers; i++) {
var controller:AnimationController = _controllers[i];
controller.update(interval);
}
lastTime = time;
}
}
/**
* Приостанавливает отсчет времени.
*/
public function stop():void {
lastTime = -1;
}
/**
* Возвращает <code>true</code> если таймер в данный момент остановлен.
*/
public function get stoped():Boolean {
return lastTime == -1;
}
/**
* Добавляет контроллер.
*/
public function addController(controller:AnimationController):AnimationController {
if (controller == null) {
throw new Error("Controller cannot be null");
}
_controllers[_numControllers++] = controller;
return controller;
}
/**
* Убирает контроллер.
*/
public function removeController(controller:AnimationController):AnimationController {
var index:int = _controllers.indexOf(controller);
if (index < 0) throw new ArgumentError("Controller not found");
_numControllers--;
var j:int = index + 1;
while (index < _numControllers) {
_controllers[index] = _controllers[j];
index++;
j++;
}
_controllers.length = _numControllers;
return controller;
}
/**
* Возвращает количество контроллеров.
*/
public function get numControllers():int {
return _numControllers;
}
/**
* Возвращает контроллер по индексу.
*
* @param index индекс контроллера.
*/
public function getControllerAt(index:int):AnimationController {
return _controllers[index];
}
}
}

View File

@@ -0,0 +1,74 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.animation.keys.BoundBoxKey;
import alternativa.engine3d.core.Object3D;
public class BoundBoxAnimation extends Animation {
/**
* Временная шкала с ключевыми кадрами анимации баунд-бокса.
*/
public var boundBox:Track;
private var boundBoxKey:BoundBoxKey = new BoundBoxKey(0);
/**
* Конструктор анимации.
*
* @param object анимируемый объект.
* @param weight вес анимации.
* @param speed скорость проигрывания анимации.
*/
public function BoundBoxAnimation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) {
super(object, weight, speed);
}
override public function updateLength():void {
super.updateLength();
if (boundBox != null) {
var len:Number = boundBox.length;
length = (len > length) ? len : length;
}
}
override protected function control(position:Number, weight:Number):void {
if (boundBox != null && object != null) {
var object3D:Object3D = Object3D(object);
boundBox.getKey(position, boundBoxKey);
var c:Number = calculateBlendInterpolation(WEIGHTS_BOUND_BOX, weight);
if (c >= 1) {
// Анимация влияет полностью
object3D.boundMinX = boundBoxKey.boundMinX;
object3D.boundMinY = boundBoxKey.boundMinY;
object3D.boundMinZ = boundBoxKey.boundMinZ;
object3D.boundMaxX = boundBoxKey.boundMaxX;
object3D.boundMaxY = boundBoxKey.boundMaxY;
object3D.boundMaxZ = boundBoxKey.boundMaxZ;
} else {
// Иначе добавляем анимацию к объекту
object3D.boundMinX = (object3D.boundMinX < boundBoxKey.boundMinX) ? object3D.boundMinX : boundBoxKey.boundMinX;
object3D.boundMinY = (object3D.boundMinY < boundBoxKey.boundMinY) ? object3D.boundMinY : boundBoxKey.boundMinY;
object3D.boundMinZ = (object3D.boundMinZ < boundBoxKey.boundMinZ) ? object3D.boundMinZ : boundBoxKey.boundMinZ;
object3D.boundMaxX = (object3D.boundMaxX > boundBoxKey.boundMaxX) ? object3D.boundMaxX : boundBoxKey.boundMaxX;
object3D.boundMaxY = (object3D.boundMaxY > boundBoxKey.boundMaxY) ? object3D.boundMaxY : boundBoxKey.boundMaxY;
object3D.boundMaxZ = (object3D.boundMaxZ > boundBoxKey.boundMaxZ) ? object3D.boundMaxZ : boundBoxKey.boundMaxZ;
}
}
}
override public function clone():Animation {
var cloned:BoundBoxAnimation = new BoundBoxAnimation(object, weight, speed);
cloned.boundBox = boundBox;
cloned.length = length;
return cloned;
}
override public function slice(start:Number, end:Number = Number.MAX_VALUE):Animation {
var animation:BoundBoxAnimation = new BoundBoxAnimation(object, weight, speed);
animation.boundBox = (boundBox != null) ? boundBox.slice(start, end) : null;
animation.updateLength();
return animation;
}
}
}

View File

@@ -0,0 +1,126 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.animation.keys.MatrixKey;
import alternativa.engine3d.core.Object3D;
import flash.geom.Vector3D;
/**
* Анимация матрицы объекта.
*/
public class MatrixAnimation extends Animation {
/**
* Временная шкала с ключевыми кадрами анимации матрицы.
*/
public var matrix:Track;
private var matrixKey:MatrixKey = new MatrixKey(0, null);
/**
* Создает новый экземпляр объекта.
*
* @param object объект, матрица которого анимируется.
* @param weight вес анимации.
* @param speed скорость проигрывания анимации.
*/
public function MatrixAnimation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) {
super(object, weight, speed);
}
/**
* @inheritDoc
*/
override protected function control(position:Number, weight:Number):void {
if (matrix != null && object != null) {
matrix.getKey(position, matrixKey);
var t:Vector3D = matrixKey.translation;
var r:Vector3D = matrixKey.rotation;
setEulerAngles(r);
var s:Vector3D = matrixKey.scale;
var c:Number;
c = calculateBlendInterpolation(WEIGHTS_X, weight);
object.x = (1 - c)*object.x + c*t.x;
c = calculateBlendInterpolation(WEIGHTS_Y, weight);
object.y = (1 - c)*object.y + c*t.y;
c = calculateBlendInterpolation(WEIGHTS_Z, weight);
object.z = (1 - c)*object.z + c*t.z;
c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight);
object.rotationX = interpolateAngle(object.rotationX, r.x, c);
c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight);
object.rotationY = interpolateAngle(object.rotationY, r.y, c);
c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight);
object.rotationZ = interpolateAngle(object.rotationZ, r.z, c);
c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight);
object.scaleX = (1 - c)*object.scaleX + c*s.x;
c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight);
object.scaleY = (1 - c)*object.scaleY + c*s.y;
c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight);
object.scaleZ = (1 - c)*object.scaleZ + c*s.z;
}
}
private function setEulerAngles(quat:Vector3D):void {
var qi2:Number = 2*quat.x*quat.x;
var qj2:Number = 2*quat.y*quat.y;
var qk2:Number = 2*quat.z*quat.z;
var qij:Number = 2*quat.x*quat.y;
var qjk:Number = 2*quat.y*quat.z;
var qki:Number = 2*quat.z*quat.x;
var qri:Number = 2*quat.w*quat.x;
var qrj:Number = 2*quat.w*quat.y;
var qrk:Number = 2*quat.w*quat.z;
var aa:Number = 1 - qj2 - qk2;
var bb:Number = qij - qrk;
var ee:Number = qij + qrk;
var ff:Number = 1 - qi2 - qk2;
var ii:Number = qki - qrj;
var jj:Number = qjk + qri;
var kk:Number = 1 - qi2 - qj2;
if (-1 < ii && ii < 1) {
quat.x = Math.atan2(jj, kk);
quat.y = -Math.asin(ii);
quat.z = Math.atan2(ee, aa);
} else {
quat.x = 0;
quat.y = (ii <= -1) ? Math.PI : -Math.PI;
quat.y *= 0.5;
quat.z = Math.atan2(-bb, ff);
}
}
/**
* @inheritDoc
*/
override public function clone():Animation {
var cloned:MatrixAnimation = new MatrixAnimation(object, weight, speed);
cloned.matrix = matrix;
cloned.length = length;
return cloned;
}
/**
* @inheritDoc
*/
override public function slice(start:Number, end:Number = Number.MAX_VALUE):Animation {
var animation:MatrixAnimation = new MatrixAnimation(object, weight, speed);
animation.matrix = (matrix != null) ? matrix.slice(start, end) : null;
animation.updateLength();
return animation;
}
/**
* @inheritDoc
*/
override public function updateLength():void {
super.updateLength();
if (matrix != null) {
var len:Number = matrix.length;
length = (len > length) ? len : length;
}
}
}
}

View File

@@ -0,0 +1,176 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.animation.keys.Key;
use namespace alternativa3d;
/**
* Временная шкала с ключевыми кадрами.
*/
public class Track {
/**
* Ключевые кадры.
*/
public var keyList:Key;
/**
* Добавляет ключевой кадр.
*/
public function addKey(key:Key):void {
key.next = keyList;
keyList = key;
}
/**
* Сортирует ключевые кадры по времени.
*/
public function sortKeys():void {
keyList = sortKeysByTime(keyList);
}
/**
* Возвращает кадр соответствующий заданному времени.
*
* @param time время кадра.
* @param key если не <code>null</code>, результат будет записан в этот объект.
*/
public function getKey(time:Number, key:Key = null):Key {
var prev:Key;
var next:Key = keyList;
while (next != null && next.time < time) {
prev = next;
next = next.next;
}
if (prev != null) return prev.interpolate(time, next, key);
if (next != null) return next.interpolate(time, null, key);
return null;
}
private function sortKeysByTime(list:Key):Key {
var left:Key = list;
var right:Key = list.next;
while (right != null && right.next != null) {
list = list.next;
right = right.next.next;
}
right = list.next;
list.next = null;
if (left.next != null) {
left = sortKeysByTime(left);
}
if (right.next != null) {
right = sortKeysByTime(right);
}
var flag:Boolean = left.time < right.time;
if (flag) {
list = left;
left = left.next;
} else {
list = right;
right = right.next;
}
var last:Key = list;
while (true) {
if (left == null) {
last.next = right;
return list;
} else if (right == null) {
last.next = left;
return list;
}
if (flag) {
if (left.time < right.time) {
last = left;
left = left.next;
} else {
last.next = right;
last = right;
right = right.next;
flag = false;
}
} else {
if (right.time < left.time) {
last = right;
right = right.next;
} else {
last.next = left;
last = left;
left = left.next;
flag = true;
}
}
}
return null;
}
/**
* Возвращает время последнего ключевого кадра.
*/
public function get length():Number {
if (keyList != null) {
var key:Key = keyList;
while (key.next != null) {
key = key.next;
}
return key.time;
} else {
return 0;
}
}
/**
* Возвращает часть трека в промежутке между start и end.
* Ключи в треке должны быть отсортированы.
*
* @param start начало времени промежутка трека
* @param end конец времени промежутка трека
* @return часть трека
*/
public function slice(start:Number, end:Number = Number.MAX_VALUE):Track {
var track:Track = new Track();
var prev:Key;
var next:Key = keyList;
while (next != null && next.time <= start) {
prev = next;
next = next.next;
}
var key:Key;
if (prev != null) {
key = prev.interpolate(start, next);
} else {
if (next != null) {
// Время до начала анимации
key = next.interpolate(next.time, next.next);
start = next.time;
} else {
// Пустой трек
return track;
}
}
key.time -= start;
track.keyList = key;
prev = next;
next = next.next;
while (next != null && next.time < end) {
key.next = next.interpolate(next.time, next.next);
key = key.next;
key.time -= start;
prev = next;
next = next.next;
}
if (next != null) {
// Время между prev.time и next.time
key.next = prev.interpolate(end, next);
key.next.time -= start;
} else {
// Последний ключ
key.next = prev.interpolate(prev.time, null);
key.next.time -= start;
}
return track;
}
}
}

View File

@@ -0,0 +1,267 @@
package alternativa.engine3d.animation {
import alternativa.engine3d.animation.keys.PointKey;
import alternativa.engine3d.animation.keys.ValueKey;
import alternativa.engine3d.core.Object3D;
import flash.geom.Vector3D;
/**
* Анимация компонентов положения и ориентации объекта.
*/
public class TransformAnimation extends Animation {
/**
* Временная шкала с ключевыми кадрами положения объекта.
*/
public var translation:Track;
/**
* Временная шкала с ключевыми кадрами вращения объекта.
*/
public var rotation:Track;
/**
* Временная шкала с ключевыми кадрами масштаба объекта.
*/
public var scale:Track;
/**
* Временная шкала с ключевыми кадрами перемещения объекта по оси X.
*/
public var x:Track;
/**
* Временная шкала с ключевыми кадрами перемещения объекта по оси Y.
*/
public var y:Track;
/**
* Временная шкала с ключевыми кадрами перемещения объекта по оси Z.
*/
public var z:Track;
/**
* Временная шкала с ключевыми кадрами вращения объекта по оси X.
*/
public var rotationX:Track;
/**
* Временная шкала с ключевыми кадрами вращения объекта по оси Y.
*/
public var rotationY:Track;
/**
* Временная шкала с ключевыми кадрами вращения объекта по оси Z.
*/
public var rotationZ:Track;
/**
* Временная шкала с ключевыми кадрами масштаба объекта по оси X.
*/
public var scaleX:Track;
/**
* Временная шкала с ключевыми кадрами масштаба объекта по оси Y.
*/
public var scaleY:Track;
/**
* Временная шкала с ключевыми кадрами масштаба объекта по оси Z.
*/
public var scaleZ:Track;
private var valueKey:ValueKey = new ValueKey(0, 0);
private var pointKey:PointKey = new PointKey(0, 0, 0, 0);
/**
* Конструктор анимации.
*
* @param object анимируемый объект.
* @param weight вес анимации.
* @param speed скорость проигрывания анимации.
*/
public function TransformAnimation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) {
super(object, weight, speed);
}
/**
* @inheritDoc
*/
override protected function control(position:Number, weight:Number):void {
if (object == null) {
return;
}
var c:Number;
//var t:Vector3D = object.translation;
//var r:Vector3D = object.rotation;
//var s:Vector3D = object.scale;
if (translation != null) {
translation.getKey(position, pointKey);
c = calculateBlendInterpolation(WEIGHTS_X, weight);
object.x = (1 - c)*object.x + c*pointKey.x;
c = calculateBlendInterpolation(WEIGHTS_Y, weight);
object.y = (1 - c)*object.y + c*pointKey.y;
c = calculateBlendInterpolation(WEIGHTS_Z, weight);
object.z = (1 - c)*object.z + c*pointKey.z;
} else {
if (x != null) {
x.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_X, weight);
object.x = (1 - c)*object.x + c*valueKey.value;
}
if (y != null) {
y.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_Y, weight);
object.y = (1 - c)*object.y + c*valueKey.value;
}
if (z != null) {
z.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_Z, weight);
object.z = (1 - c)*object.z + c*valueKey.value;
}
}
if (rotation != null) {
rotation.getKey(position, pointKey);
c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight);
object.rotationX = interpolateAngle(object.rotationX, pointKey.x, c);
c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight);
object.rotationY = interpolateAngle(object.rotationY, pointKey.y, c);
c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight);
object.rotationZ = interpolateAngle(object.rotationZ, pointKey.z, c);
} else {
if (rotationX != null) {
rotationX.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight);
object.rotationX = interpolateAngle(object.rotationX, valueKey.value, c);
}
if (rotationY != null) {
rotationY.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight);
object.rotationY = interpolateAngle(object.rotationY, valueKey.value, c);
}
if (rotationZ != null) {
rotationZ.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight);
object.rotationZ = interpolateAngle(object.rotationZ, valueKey.value, c);
}
}
if (scale != null) {
scale.getKey(position, pointKey);
c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight);
object.scaleX = (1 - c)*object.scaleX + c*pointKey.x;
c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight);
object.scaleY = (1 - c)*object.scaleY + c*pointKey.y;
c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight);
object.scaleZ = (1 - c)*object.scaleZ + c*pointKey.z;
} else {
if (scaleX != null) {
scaleX.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight);
object.scaleX = (1 - c)*object.scaleX + c*valueKey.value;
}
if (scaleY != null) {
scaleY.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight);
object.scaleY = (1 - c)*object.scaleY + c*valueKey.value;
}
if (scaleZ != null) {
scaleZ.getKey(position, valueKey);
c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight);
object.scaleZ = (1 - c)*object.scaleZ + c*valueKey.value;
}
}
}
/**
* @inheritDoc
*/
override public function slice(start:Number, end:Number = Number.MAX_VALUE):Animation {
var animation:TransformAnimation = new TransformAnimation(object, weight, speed);
animation.translation = (translation != null) ? translation.slice(start, end) : null;
animation.rotation = (rotation != null) ? rotation.slice(start, end) : null;
animation.scale = (scale != null) ? scale.slice(start, end) : null;
animation.x = (x != null) ? x.slice(start, end) : null;
animation.y = (y != null) ? y.slice(start, end) : null;
animation.z = (z != null) ? z.slice(start, end) : null;
animation.rotationX = (rotationX != null) ? rotationX.slice(start, end) : null;
animation.rotationY = (rotationY != null) ? rotationY.slice(start, end) : null;
animation.rotationZ = (rotationZ != null) ? rotationZ.slice(start, end) : null;
animation.scaleX = (scaleX != null) ? scaleX.slice(start, end) : null;
animation.scaleY = (scaleY != null) ? scaleY.slice(start, end) : null;
animation.scaleZ = (scaleZ != null) ? scaleZ.slice(start, end) : null;
animation.updateLength();
return animation;
}
/**
* @inheritDoc
*/
override public function updateLength():void {
super.updateLength();
var len:Number;
if (translation != null) {
len = translation.length;
length = (len > length) ? len : length;
}
if (rotation != null) {
len = rotation.length;
length = (len > length) ? len : length;
}
if (scale != null) {
len = scale.length;
length = (len > length) ? len : length;
}
if (x != null) {
len = x.length;
length = (len > length) ? len : length;
}
if (y != null) {
len = y.length;
length = (len > length) ? len : length;
}
if (z != null) {
len = z.length;
length = (len > length) ? len : length;
}
if (rotationX != null) {
len = rotationX.length;
length = (len > length) ? len : length;
}
if (rotationY != null) {
len = rotationY.length;
length = (len > length) ? len : length;
}
if (rotationZ != null) {
len = rotationZ.length;
length = (len > length) ? len : length;
}
if (scaleX != null) {
len = scaleX.length;
length = (len > length) ? len : length;
}
if (scaleY != null) {
len = scaleY.length;
length = (len > length) ? len : length;
}
if (scaleZ != null) {
len = scaleZ.length;
length = (len > length) ? len : length;
}
}
/**
* @inheritDoc
*/
override public function clone():Animation {
var cloned:TransformAnimation = new TransformAnimation(object, weight, speed);
cloned.translation = translation;
cloned.rotation = rotation;
cloned.scale = scale;
cloned.x = x;
cloned.y = y;
cloned.z = z;
cloned.rotationX = rotationX;
cloned.rotationY = rotationY;
cloned.rotationZ = rotationZ;
cloned.scaleX = scaleX;
cloned.scaleY = scaleY;
cloned.scaleZ = scaleZ;
cloned.length = length;
return cloned;
}
}
}

View File

@@ -0,0 +1,95 @@
package alternativa.engine3d.animation.keys {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Object3D;
use namespace alternativa3d;
public class BoundBoxKey extends Key {
public var boundMinX:Number = -1e+22;
public var boundMinY:Number = -1e+22;
public var boundMinZ:Number = -1e+22;
public var boundMaxX:Number = 1e+22;
public var boundMaxY:Number = 1e+22;
public var boundMaxZ:Number = 1e+22;
public function BoundBoxKey(time:Number, object:Object3D = null) {
super(time);
if (object != null) {
this.boundMinX = object.boundMinX;
this.boundMinY = object.boundMinY;
this.boundMinZ = object.boundMinZ;
this.boundMaxX = object.boundMaxX;
this.boundMaxY = object.boundMaxY;
this.boundMaxZ = object.boundMaxZ;
}
}
/**
* @private
*/
alternativa3d override function interpolate(time:Number, next:Key, key:Key = null):Key {
var boundBoxKey:BoundBoxKey;
if (key == null) {
boundBoxKey = new BoundBoxKey(time);
key = boundBoxKey;
} else {
key.time = time;
boundBoxKey = BoundBoxKey(key);
}
if (next != null) {
var boundBoxNextKey:BoundBoxKey = BoundBoxKey(next);
if (time == this.time) {
boundBoxKey.boundMinX = this.boundMinX;
boundBoxKey.boundMinY = this.boundMinY;
boundBoxKey.boundMinZ = this.boundMinZ;
boundBoxKey.boundMaxX = this.boundMaxX;
boundBoxKey.boundMaxY = this.boundMaxY;
boundBoxKey.boundMaxZ = this.boundMaxZ;
return key;
} else if (time == next.time) {
boundBoxKey.boundMinX = boundBoxNextKey.boundMinX;
boundBoxKey.boundMinY = boundBoxNextKey.boundMinY;
boundBoxKey.boundMinZ = boundBoxNextKey.boundMinZ;
boundBoxKey.boundMaxX = boundBoxNextKey.boundMaxX;
boundBoxKey.boundMaxY = boundBoxNextKey.boundMaxY;
boundBoxKey.boundMaxZ = boundBoxNextKey.boundMaxZ;
return key;
}
boundBoxKey.boundMinX = (this.boundMinX < boundBoxNextKey.boundMinX) ? this.boundMinX : boundBoxNextKey.boundMinX;
boundBoxKey.boundMinY = (this.boundMinY < boundBoxNextKey.boundMinY) ? this.boundMinY : boundBoxNextKey.boundMinY;
boundBoxKey.boundMinZ = (this.boundMinZ < boundBoxNextKey.boundMinZ) ? this.boundMinZ : boundBoxNextKey.boundMinZ;
boundBoxKey.boundMaxX = (this.boundMaxX > boundBoxNextKey.boundMaxX) ? this.boundMaxX : boundBoxNextKey.boundMaxX;
boundBoxKey.boundMaxY = (this.boundMaxY > boundBoxNextKey.boundMaxY) ? this.boundMaxY : boundBoxNextKey.boundMaxY;
boundBoxKey.boundMaxZ = (this.boundMaxZ > boundBoxNextKey.boundMaxZ) ? this.boundMaxZ : boundBoxNextKey.boundMaxZ;
} else {
boundBoxKey.boundMinX = this.boundMinX;
boundBoxKey.boundMinY = this.boundMinY;
boundBoxKey.boundMinZ = this.boundMinZ;
boundBoxKey.boundMaxX = this.boundMaxX;
boundBoxKey.boundMaxY = this.boundMaxY;
boundBoxKey.boundMaxZ = this.boundMaxZ;
}
return key;
}
public function setBoundBox(object:Object3D):void {
this.boundMinX = object.boundMinX;
this.boundMinY = object.boundMinY;
this.boundMinZ = object.boundMinZ;
this.boundMaxX = object.boundMaxX;
this.boundMaxY = object.boundMaxY;
this.boundMaxZ = object.boundMaxZ;
}
// /**
// * Строковое представление объекта.
// */
// public function toString():String {
// return "[BoundBoxKey " + time + ":" + + "]";
// }
}
}

View File

@@ -0,0 +1,40 @@
package alternativa.engine3d.animation.keys {
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* Ключевой кадр.
*/
public class Key {
/**
* Время кадра.
*/
public var time:Number;
/**
* Ссылка на следующий ключевой кадр на верменной шкале.
*
* @see alternativa.engine3d.animation.Track
*/
public var next:Key;
/**
* Создает экземпляр ключевого кадра.
*
* @param time время кадра.
*/
public function Key(time:Number) {
this.time = time;
}
/**
* @private
*/
alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key {
return key;
}
}
}

View File

@@ -0,0 +1,223 @@
package alternativa.engine3d.animation.keys {
import alternativa.engine3d.alternativa3d;
import flash.geom.Matrix3D;
import flash.geom.Orientation3D;
import flash.geom.Vector3D;
use namespace alternativa3d;
/**
* Ключевой кадр матричного типа.
*/
public class MatrixKey extends Key {
// public static function test():void {
// const min:Number = Math.PI / 3;
// var key:MatrixKey = new MatrixKey(0);
// trace("[1]");
// testAll(0);
// trace("[2]");
// testAll(min);
// trace("[3]");
// testAll(2*min);
// trace("[4]");
// testAll(3*min);
// trace("[5]");
// testAll(-min);
// trace("[6]");
// testAll(-2*min);
// trace("[7]");
// testAll(-3*min);
// }
//
// private static function testAll(angle:Number):void {
// const min:Number = Math.PI / 3;
// const toDegree:Number = 180/Math.PI;
// var key:MatrixKey = new MatrixKey(0);
// trace("0", key.interpolateAngle(angle, 0, 0.1)*toDegree);
// trace("60", key.interpolateAngle(angle, min, 0.1)*toDegree);
// trace("120", key.interpolateAngle(angle, 2*min, 0.1)*toDegree);
// trace("180", key.interpolateAngle(angle, 3*min, 0.1)*toDegree);
// trace("-60", key.interpolateAngle(angle, -min, 0.1)*toDegree);
// trace("-120", key.interpolateAngle(angle, -2*min, 0.1)*toDegree);
// trace("-180", key.interpolateAngle(angle, -3*min, 0.1)*toDegree);
// }
/**
* Компоненты перемещения по осям X, Y, Z
*/
public var translation:Vector3D;
/**
* Кватернион вращения
*/
public var rotation:Vector3D;
/**
* Компоненты масштаба по осям X, Y, Z
*/
public var scale:Vector3D;
/**
* Создает ключевой кадр матричного типа.
*
* @param time время кадра.
* @param matrix значение кадра.
*/
public function MatrixKey(time:Number, matrix:Matrix3D = null) {
super(time);
if (matrix != null) {
var v:Vector.<Vector3D> = matrix.decompose(Orientation3D.QUATERNION);
translation = v[0];
rotation = v[1];
scale = v[2];
} else {
translation = new Vector3D();
rotation = new Vector3D();
scale = new Vector3D();
}
}
/**
* @private
*/
override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key {
if (key != null) {
key.time = time;
} else {
key = new MatrixKey(time);
}
var t:Vector3D = MatrixKey(key).translation;
var r:Vector3D = MatrixKey(key).rotation;
var s:Vector3D = MatrixKey(key).scale;
if (next != null) {
var nt:Vector3D = MatrixKey(next).translation;
var nr:Vector3D = MatrixKey(next).rotation;
var ns:Vector3D = MatrixKey(next).scale;
var c2:Number = (time - this.time)/(next.time - this.time);
var c1:Number = 1 - c2;
t.x = c1 * translation.x + c2 * nt.x;
t.y = c1 * translation.y + c2 * nt.y;
t.z = c1 * translation.z + c2 * nt.z;
slerp(rotation, nr, c2, r);
s.x = c1 * scale.x + c2 * ns.x;
s.y = c1 * scale.y + c2 * ns.y;
s.z = c1 * scale.z + c2 * ns.z;
} else {
t.x = translation.x;
t.y = translation.y;
t.z = translation.z;
r.x = rotation.x;
r.y = rotation.y;
r.z = rotation.z;
s.x = scale.x;
s.y = scale.y;
s.z = scale.z;
}
return key;
}
/**
* Выполняет сферическую интерполяцию между двумя заданными кватернионами по наименьшему расстоянию.
*
* @param a первый кватерион
* @param b второй кватернион
* @param t параметр интерполяции, обычно принадлежит отрезку [0, 1]
* @return this
*/
private function slerp(a:Vector3D, b:Vector3D, t:Number, result:Vector3D):void {
var flip:Number = 1;
// Так как одна и та же ориентация представляется двумя значениями q и -q, нужно сменить знак одного из кватернионов
// если скалярное произведение отрицательно. Иначе будет получено интерполированное значение по наибольшему расстоянию.
var cosine:Number = a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z;
if (cosine < 0) {
cosine = -cosine;
flip = -1;
}
if ((1 - cosine) < 0.001) {
// Вблизи нуля используется линейная интерполяция
var k1:Number = 1 - t;
var k2:Number = t*flip;
result.w = a.w*k1 + b.w*k2;
result.x = a.x*k1 + b.x*k2;
result.y = a.y*k1 + b.y*k2;
result.z = a.z*k1 + b.z*k2;
var d:Number = result.w*result.w + result.x*result.x + result.y*result.y + result.z*result.z;
if (d == 0) {
result.w = 1;
} else {
result.scaleBy(1/Math.sqrt(d));
}
} else {
var theta:Number = Math.acos(cosine);
var sine:Number = Math.sin(theta);
var beta:Number = Math.sin((1 - t)*theta)/sine;
var alpha:Number = Math.sin(t*theta)/sine*flip;
result.w = a.w*beta + b.w*alpha;
result.x = a.x*beta + b.x*alpha;
result.y = a.y*beta + b.y*alpha;
result.z = a.z*beta + b.z*alpha;
}
}
/**
* Интерполяция между двумя ненормализованными углами.
*/
private function interpolateAngle(angle1:Number, angle2:Number, c:Number):Number {
const PI:Number = Math.PI;
const PITwice:Number = 2*PI;
var delta:Number = angle2 - angle1;
if (delta > PI) {
delta -= PITwice;
} else if (delta < -PI) {
delta += PITwice;
}
// if (delta < (PI + 0.01) && delta > (PI - 0.01)) {
// trace("[BDif+]", angle1, angle2);
// }
// if (delta < (-PI + 0.01) && delta > (-PI - 0.01)) {
// trace("[BDif-]", angle1, angle2);
// }
return angle1 + c * delta;
}
// private function interpolateAngle(angle1:Number, angle2:Number, c:Number):Number {
// const PI2:Number = 2*Math.PI;
// angle1 = (angle1 > Math.PI) ? angle1%PI2 - PI2 : (angle1 <= -Math.PI) ? (angle1%PI2) + PI2 : angle1;
// angle2 = (angle2 > Math.PI) ? angle2%PI2 - PI2 : (angle2 <= -Math.PI) ? (angle2%PI2) + PI2 : angle2;
// var delta:Number = angle2 - angle1;
// delta = (delta > Math.PI) ? delta - PI2 : (delta < -Math.PI) ? delta + PI2 : delta;
// return angle1 + c * delta;
// }
/**
* Создает и возвращает матрицу на основе компонентов ключа
*/
public function getMatrix():Matrix3D {
var m:Matrix3D = new Matrix3D();
var v:Vector.<Vector3D> = new Vector.<Vector3D>(3);
v[0] = translation;
v[1] = rotation;
v[2] = scale;
m.recompose(v, Orientation3D.QUATERNION);
return m;
}
/**
* Устанавливает значения компонентов ключа из матрицы
*/
public function setMatrix(value:Matrix3D):void {
var v:Vector.<Vector3D> = value.decompose(Orientation3D.QUATERNION);
translation = v[0];
rotation = v[1];
scale = v[2];
}
/**
* Строковое представление объекта.
*/
public function toString():String {
return "[MatrixKey " + time + " translation:" + translation.toString() + " rotation:" + rotation + " scale:" + scale + "]]";
}
}
}

View File

@@ -0,0 +1,76 @@
package alternativa.engine3d.animation.keys {
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* Ключевой кадр точечного типа.
*/
public class PointKey extends Key {
/**
* Координата по оси X.
*/
public var x:Number;
/**
* Координата по оси Y.
*/
public var y:Number;
/**
* Координата по оси Z.
*/
public var z:Number;
/**
* Создает экземпляр ключевого кадра.
*
* @param time время кадра.
* @param x координата по оси X
* @param y координата по оси Y
* @param z координата по оси Z
*/
public function PointKey(time:Number, x:Number, y:Number, z:Number) {
super(time);
this.x = x;
this.y = y;
this.z = z;
}
/**
* @private
*/
override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key {
var x:Number;
var y:Number;
var z:Number;
if (next != null) {
var t:Number = (time - this.time)/(next.time - this.time);
x = this.x + (PointKey(next).x - this.x)*t;
y = this.y + (PointKey(next).y - this.y)*t;
z = this.z + (PointKey(next).z - this.z)*t;
} else {
x = this.x;
y = this.y;
z = this.z;
}
if (key != null) {
key.time = time;
PointKey(key).x = x;
PointKey(key).y = y;
PointKey(key).z = z;
return key;
} else {
return new PointKey(time, x, y, z);
}
}
/**
* Строковое представление объекта.
*/
public function toString():String {
return "[PointKey " + time.toFixed(3) + ":" + x + "," + y + "," + z + "]";
}
}
}

View File

@@ -0,0 +1,55 @@
package alternativa.engine3d.animation.keys {
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* Ключевой кадр вещественного типа.
*/
public class ValueKey extends Key {
/**
* Значение ключевого кадра.
*/
public var value:Number;
/**
* Создает ключевой кадр.
*
* @param time время кадра.
* @param value значение кадра.
*/
public function ValueKey(time:Number, value:Number) {
super(time);
this.value = value;
}
/**
* @private
*/
override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key {
var value:Number;
if (next != null) {
value = this.value + (ValueKey(next).value - this.value)*(time - this.time)/(next.time - this.time);
} else {
value = this.value;
}
if (key != null) {
key.time = time;
ValueKey(key).value = value;
return key;
} else {
return new ValueKey(time, value);
}
}
/**
* Строковое представление объекта.
*/
public function toString():String {
return "[ValueKey " + time + ":" + value + "]";
}
}
}

View File

@@ -0,0 +1,471 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import flash.display.InteractiveObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.ui.Keyboard;
import flash.utils.getTimer;
/**
* Базовый контроллер для <code>Object3D</code>.
* @see Object3D
*/
public class SimpleObjectController {
/**
* Имя действия для привязки клавиш движения вперёд.
*/
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";
/**
* Скорость в метрах в секунду.
*/
public var speed:Number;
/**
* Величина, на которую домножается скорость в режиме ускорения.
*/
public var speedMultiplier:Number;
/**
* Чувствительность мыши.
*/
public var mouseSensitivity:Number;
/**
* Максимальный наклон.
*/
public var maxPitch:Number = 1e+22;
/**
* Минимальный наклон.
*/
public var minPitch:Number = -1e+22;
private var eventSource:InteractiveObject;
private var _object:Object3D;
private var _up:Boolean;
private var _down:Boolean;
private var _forward:Boolean;
private var _back:Boolean;
private var _left:Boolean;
private var _right:Boolean;
private var _accelerate:Boolean;
private var displacement:Vector3D = new Vector3D();
private var mousePoint:Point = new Point();
private var mouseLook:Boolean;
private var objectTransform:Vector.<Vector3D>;
private var time:int;
/**
* Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
* function(value:Boolean):void. Значение параметра <code>value</code> указывает, нажата или отпущена соответствующая команде.
* клавиша.
*/
private var actionBindings:Object = {};
/**
* Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
*/
protected var keyBindings:Object = {};
/**
* Создаёт новый экземпляр.
* @param eventSource Источник событий для контроллера.
* @param speed Скорость поступательного перемещения объекта.
* @param mouseSensitivity Чувствительность мыши — количество градусов поворота на один пиксель перемещения мыши.
*/
public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) {
this.eventSource = eventSource;
this.object = object;
this.speed = speed;
this.speedMultiplier = speedMultiplier;
this.mouseSensitivity = mouseSensitivity;
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_ACCELERATE] = accelerate;
setDefaultBindings();
enable();
}
/**
* Активирует контроллер.
*/
public function enable():void {
eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Деактивирует контроллер.
*/
public function disable():void {
eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stopMouseLook();
}
private function onMouseDown(e:MouseEvent):void {
startMouseLook();
}
private function onMouseUp(e:MouseEvent):void {
stopMouseLook();
}
/**
* Включает режим взгляда мышью.
*/
public function startMouseLook():void {
mousePoint.x = eventSource.mouseX;
mousePoint.y = eventSource.mouseY;
mouseLook = true;
}
/**
* Отключает режим взгляда мышью.
*/
public function stopMouseLook():void {
mouseLook = false;
}
private function onKey(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
/**
* Управляемый объект.
*/
public function get object():Object3D {
return _object;
}
/**
* @private
*/
public function set object(value:Object3D):void {
_object = value;
updateObjectTransform();
}
/**
* Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера.
*/
public function updateObjectTransform():void {
if (_object != null) objectTransform = _object.matrix.decompose();
}
/**
* Вычисляет новое положение объекта, используя внутренний счётчик времени.
*/
public function update():void {
if (_object == null) return;
var frameTime:Number = time;
time = getTimer();
frameTime = 0.001*(time - frameTime);
if (frameTime > 0.1) frameTime = 0.1;
var moved:Boolean = false;
if (mouseLook) {
var dx:Number = eventSource.mouseX - mousePoint.x;
var dy:Number = eventSource.mouseY - mousePoint.y;
mousePoint.x = eventSource.mouseX;
mousePoint.y = eventSource.mouseY;
var v:Vector3D = objectTransform[1];
v.x -= dy*Math.PI/180*mouseSensitivity;
if (v.x > maxPitch) v.x = maxPitch;
if (v.x < minPitch) v.x = minPitch;
v.z -= dx*Math.PI/180*mouseSensitivity;
moved = true;
}
displacement.x = _right ? 1 : (_left ? -1 : 0);
displacement.y = _forward ? 1 : (_back ? -1 : 0);
displacement.z = _up ? 1 : (_down ? -1 : 0);
if (displacement.lengthSquared > 0) {
if (_object is Camera3D) {
var tmp:Number = displacement.z;
displacement.z = displacement.y;
displacement.y = -tmp;
}
deltaTransformVector(displacement);
if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length);
else displacement.scaleBy(speed*frameTime/displacement.length);
(objectTransform[0] as Vector3D).incrementBy(displacement);
moved = true;
}
if (moved) {
var m:Matrix3D = new Matrix3D();
m.recompose(objectTransform);
_object.matrix = m;
}
}
/**
* Устанавливает позицию объекта.
* @param pos Объект <code>Vector3D</code>.
*/
public function setObjectPos(pos:Vector3D):void {
if (_object != null) {
var v:Vector3D = objectTransform[0];
v.x = pos.x;
v.y = pos.y;
v.z = pos.z;
}
}
/**
* Устанавливает позицию объекта.
* @param x Координата X.
* @param y Координата Y.
* @param z Координата Z.
*/
public function setObjectPosXYZ(x:Number, y:Number, z:Number):void {
if (_object != null) {
var v:Vector3D = objectTransform[0];
v.x = x;
v.y = y;
v.z = z;
}
}
/**
* Устанавливает направление камеры в заданную точку.
* @param point Объект <code>Vector3D</code>.
*/
public function lookAt(point:Vector3D):void {
lookAtXYZ(point.x, point.y, point.z);
}
/**
* Устанавливает направление камеры в заданную точку.
* @param x Координата X.
* @param y Координата Y.
* @param z Координата Z.
*/
public function lookAtXYZ(x:Number, y:Number, z:Number):void {
if (_object == null) return;
var v:Vector3D = objectTransform[0];
var dx:Number = x - v.x;
var dy:Number = y - v.y;
var dz:Number = z - v.z;
v = objectTransform[1];
v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy));
if (_object is Camera3D) v.x -= 0.5*Math.PI;
v.y = 0;
v.z = -Math.atan2(dx, dy);
var m:Matrix3D = _object.matrix;
m.recompose(objectTransform);
_object.matrix = m;
}
private var _vin:Vector.<Number> = new Vector.<Number>(3);
private var _vout:Vector.<Number> = new Vector.<Number>(3);
private function deltaTransformVector(v:Vector3D):void {
_vin[0] = v.x;
_vin[1] = v.y;
_vin[2] = v.z;
_object.matrix.transformVectors(_vin, _vout);
var c:Vector3D = objectTransform[0];
v.x = _vout[0] - c.x;
v.y = _vout[1] - c.y;
v.z = _vout[2] - c.z;
}
/**
* Активация движения вперёд.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveForward(value:Boolean):void {
_forward = value;
}
/**
* Активация движения назад.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveBack(value:Boolean):void {
_back = value;
}
/**
* Активация движения влево.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveLeft(value:Boolean):void {
_left = value;
}
/**
* Активация движения вправо.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveRight(value:Boolean):void {
_right = value;
}
/**
* Активация движения вверх.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveUp(value:Boolean):void {
_up = value;
}
/**
* Активация движения вниз.
* @param value <code>true</code> для начала движения, <code>false</code> для окончания.
*/
public function moveDown(value:Boolean):void {
_down = value;
}
/**
* Активация режима увеличенной скорости.
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения.
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
* @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 bindings Массив, в котором поочерёдно содержатся коды клавиш и действия.
*/
public function bindKeys(bindings:Array):void {
for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]);
}
/**
* Очистка привязки клавиши.
* @param keyCode Код клавиши.
* @see #bindKey()
* @see #unbindAll()
*/
public function unbindKey(keyCode:uint):void {
delete keyBindings[keyCode];
}
/**
* Очистка привязки всех клавиш.
* @see #bindKey()
* @see #unbindKey()
*/
public function unbindAll():void {
for (var key:String in keyBindings) delete keyBindings[key];
}
/**
* Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
* @see #bindKey()
* @see #unbindKey()
* @see #unbindAll()
*/
public function setDefaultBindings():void {
bindKey(87, ACTION_FORWARD);
bindKey(83, ACTION_BACK);
bindKey(65, ACTION_LEFT);
bindKey(68, ACTION_RIGHT);
bindKey(69, ACTION_UP);
bindKey(67, ACTION_DOWN);
bindKey(Keyboard.SHIFT, ACTION_ACCELERATE);
bindKey(Keyboard.UP, ACTION_FORWARD);
bindKey(Keyboard.DOWN, ACTION_BACK);
bindKey(Keyboard.LEFT, ACTION_LEFT);
bindKey(Keyboard.RIGHT, ACTION_RIGHT);
}
}
}

View File

@@ -0,0 +1,788 @@
package alternativa.engine3d.core {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.lights.OmniLight;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Mesh;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display3D.Context3D;
import flash.display3D.Context3DClearMask;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.system.System;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.Dictionary;
import flash.utils.getTimer;
use namespace alternativa3d;
public class Camera3D extends Object3D {
public var debug:Boolean = true;
public var window:Rectangle;
/**
* Вьюпорт камеры.
* Если вьюпорт не указан, отрисовка осуществляться не будет.
*/
public var view:View;
/**
* Поле зрения (field of view).
* Указывается в радианах.
* Значение по умолчанию <code>Math.PI/2</code> — это 90 градусов.
*/
public var fov:Number = Math.PI/2;
/**
* Ближнее расстояние отсечения.
* Значение по умолчанию <code>0</code>.
*/
public var nearClipping:Number;
/**
* Дальнее расстояние отсечения.
* Значение по умолчанию <code>Number.MAX_VALUE</code>.
*/
public var farClipping:Number;
/**
* Настройки клиппинга для текущего сплита
*/
alternativa3d var currentNearClipping:Number;
alternativa3d var currentFarClipping:Number;
public var shadowCaster:DirectionalLight;
public var directionalLights:Vector.<DirectionalLight>;
alternativa3d var numDirectionalLights:int;
public var omniLights:Vector.<OmniLight>;
public var sortTransparentObjects:Boolean = false;
/**
* @private
*/
alternativa3d var focalLength:Number;
/**
* @private
*/
alternativa3d var projectionMatrixData:Vector.<Number> = new Vector.<Number>(16);
alternativa3d var globalMatrix:Matrix3D = new Matrix3D();
/**
* @private
*/
alternativa3d var numDraws:int;
/**
* @private
*/
alternativa3d var numTriangles:int;
alternativa3d var transparentObjects:Vector.<Object3D> = new Vector.<Object3D>();
alternativa3d var numTransparent:int = 0;
private static const cachedContext3dCachedPrograms:Dictionary = new Dictionary(true);
alternativa3d var context3dCachedPrograms:Object;
public function Camera3D(nearClipping:Number, farClipping:Number) {
this.nearClipping = nearClipping;
this.farClipping = farClipping;
}
/**
* Отрисовывает иерархию объектов, в которой находится камера.
* Чтобы отрисовка произошла, камере должен быть назначен <code>context3d</code>.
*/
public function render():void {
// Сброс счётчиков
numDraws = 0;
numTriangles = 0;
if (view != null) {
context3dCachedPrograms = cachedContext3dCachedPrograms[view._context3d];
if (context3dCachedPrograms == null) {
context3dCachedPrograms = new Object();
cachedContext3dCachedPrograms[view._context3d] = context3dCachedPrograms;
}
// Расчёт параметров проецирования
var viewSizeX:Number = view._width*0.5;
var viewSizeY:Number = view._height*0.5;
if (window != null) {
// projectionMatrixData[12] = -viewSizeX;
// projectionMatrixData[13] = viewSizeY;
// projectionMatrixData[12] = (window.x + window.width/2 - viewSizeX);
// projectionMatrixData[13] = (-window.y - window.height/2 + viewSizeY);
} else {
// projectionMatrixData[12] = 0;
// projectionMatrixData[13] = 0;
}
focalLength = Math.sqrt(viewSizeX*viewSizeX + viewSizeY*viewSizeY)/Math.tan(fov*0.5);
if (window) {
projectionMatrixData[0] = focalLength/viewSizeX*window.width/view._width;
projectionMatrixData[5] = -focalLength/viewSizeY*window.height/view._height;
} else {
projectionMatrixData[0] = focalLength/viewSizeX;
projectionMatrixData[5] = -focalLength/viewSizeY;
}
projectionMatrixData[10]= farClipping/(farClipping - nearClipping);
projectionMatrixData[11]= 1;
projectionMatrixData[14]= -nearClipping*farClipping/(farClipping - nearClipping);
projectionMatrix.rawData = projectionMatrixData;
composeMatrix();
globalMatrix.identity();
globalMatrix.append(cameraMatrix);
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
globalMatrix.append(root.cameraMatrix);
}
// Расчёт матрицы перевода из глобального пространства в камеру
var i:int;
// Отрисовка
var r:Number = (view.backgroundColor >> 16)/255;
var g:Number = ((view.backgroundColor >> 8) & 0xFF)/255;
var b:Number = (view.backgroundColor & 0xFF)/255;
view._context3d.clear(r, g, b, view.backgroundAlpha, 1, 0, view.clearMask);
if (root != this && root.visible) {
numDirectionalLights = (directionalLights == null) ? 0 : directionalLights.length;
if (numDirectionalLights > 0) {
for each (var directional:DirectionalLight in directionalLights) {
if (directional != shadowCaster) {
directional.calcLightMatrix(this);
}
}
}
if (omniLights != null) {
cameraMatrix.identity();
cameraMatrix.append(globalMatrix);
cameraMatrix.invert();
for each (var omni:OmniLight in omniLights) {
omni.composeMatrix();
root = omni;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
omni.cameraMatrix.append(root.cameraMatrix);
}
omni.cameraMatrix.append(cameraMatrix);
omni.cameraCoords[0] = 0;
omni.cameraCoords[1] = 0;
omni.cameraCoords[2] = 0;
omni.cameraMatrix.transformVectors(omni.cameraCoords, omni.cameraCoords);
}
}
if (shadowCaster != null) {
for (var j:int = shadowCaster.numSplits - 1; j >= 0; j--) {
// В методе update происходит отрисовка сцены в шедоумапу
// При этом затирается матрица cameraMatrix всех объектов и камеры в том числе
shadowCaster.update(this, view._context3d, j);
if (j < (shadowCaster.numSplits - 1)) {
view._context3d.clear(0, 0, 0, 1, 1, 0, Context3DClearMask.DEPTH);
}
currentNearClipping = shadowCaster.currentSplitNear;
currentFarClipping = shadowCaster.currentSplitFar;
projectionMatrixData[10]= currentFarClipping/(currentFarClipping - currentNearClipping);
projectionMatrixData[14]= -currentNearClipping*currentFarClipping/(currentFarClipping - currentNearClipping);
projectionMatrix.rawData = projectionMatrixData;
// Считаем матрицу перевода в камеру в цикле, поскольку она перетирается в методе DirectionalLight.update выше
cameraMatrix.identity();
cameraMatrix.append(globalMatrix);
cameraMatrix.invert();
root.composeMatrix();
root.cameraMatrix.append(cameraMatrix);
if (root.cullingInCamera(this, 63) >= 0) {
root.draw(this);
if (sortTransparentObjects && numTransparent > 1) sortByDistance();
for (i = 0; i < numTransparent; i++) {
transparentObjects[i].draw(this);
}
numTransparent = 0;
transparentObjects.length = 0;
}
}
} else {
currentNearClipping = nearClipping;
currentFarClipping = farClipping;
cameraMatrix.identity();
cameraMatrix.append(globalMatrix);
cameraMatrix.invert();
//cameraMatrix.append(projectionMatrix);
root.composeMatrix();
root.cameraMatrix.append(cameraMatrix);
if (root.cullingInCamera(this, 63) >= 0) {
root.draw(this);
if (sortTransparentObjects && numTransparent > 1) sortByDistance();
for (i = 0; i < numTransparent; i++) {
transparentObjects[i].draw(this);
}
numTransparent = 0;
transparentObjects.length = 0;
}
}
}
view._context3d.swap();
}
}
private function sortByDistance():void {
var sortingStack:Vector.<int> = new Vector.<int>();
var i:int;
var j:int;
var child:Object3D;
var l:int = 0;
var r:int = numTransparent - 1;
var stackIndex:int;
var left:Number;
var median:Number;
var right:Number;
sortingStack[0] = l;
sortingStack[1] = r;
stackIndex = 2;
for (i = 0; i < numTransparent; i++) {
child = transparentObjects[i];
child.distance = child.cameraMatrix.position.z;
}
while (stackIndex > 0) {
r = sortingStack[--stackIndex];
l = sortingStack[--stackIndex];
j = r;
i = l;
child = transparentObjects[(r + l) >> 1];
median = child.distance;
do {
while ((left = (transparentObjects[i] as Object3D).distance) > median) i++;
while ((right = (transparentObjects[j] as Object3D).distance) < median) j--;
if (i <= j) {
child = transparentObjects[i];
transparentObjects[i++] = transparentObjects[j];
transparentObjects[j--] = child;
}
} while (i <= j);
if (l < j) {
sortingStack[stackIndex++] = l;
sortingStack[stackIndex++] = j;
}
if (i < r) {
sortingStack[stackIndex++] = i;
sortingStack[stackIndex++] = r;
}
}
}
/**
* Переводит точку из глобального пространства в экранные координаты.
* Для расчёта необходимо, чтобы у камеры был установлен вьюпорт.
* @param point Точка в глобальном пространстве.
* @return Объект <code>Vector3D</code>, в котором содержатся экранные координаты.
*/
public function projectGlobal(point:Vector3D):Vector3D {
if (view == null) throw new Error("It is necessary to have view set.");
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
cameraMatrix.invert();
var res:Vector3D = cameraMatrix.transformVector(point);
var viewSizeX:Number = view._width*0.5;
var viewSizeY:Number = view._height*0.5;
focalLength = Math.sqrt(viewSizeX*viewSizeX + viewSizeY*viewSizeY)/Math.tan(fov*0.5);
res.x = res.x*focalLength/res.z + viewSizeX;
res.y = res.y*focalLength/res.z + viewSizeY;
return res;
}
/**
* Расчитывает луч в глобальном пространстве.
* Прямая луча проходит через начало координат камеры и точку на плоскости вьюпорта, заданную <code>deltaX</code> и <code>deltaY</code>.
* Луч может быть использован в методе <code>intersectRay()</code> трёхмерных объектов.
* @param origin Начало луча. Луч начинается от <code>nearClipping</code> камеры.
* @param direction Направление луча.
* @param viewX Горизонтальная координата в плоскости вьюпорта.
* @param viewY Вертикальная координата в плоскости вьюпорта.
*/
public function calculateRay(origin:Vector3D, direction:Vector3D, viewX:Number, viewY:Number):void {
if (view == null) throw new Error("It is necessary to have view set.");
var viewSizeX:Number = view._width*0.5;
var viewSizeY:Number = view._height*0.5;
focalLength = Math.sqrt(viewSizeX*viewSizeX + viewSizeY*viewSizeY)/Math.tan(fov*0.5);
// Создание луча в камере
direction.x = viewX - viewSizeX;
direction.y = viewY - viewSizeY;
direction.z = focalLength;
origin.x = direction.x*nearClipping/focalLength;
origin.y = direction.y*nearClipping/focalLength;
origin.z = nearClipping;
// Перевод луча в глобальное пространство
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
var org:Vector3D = cameraMatrix.transformVector(origin);
var dir:Vector3D = cameraMatrix.deltaTransformVector(direction);
dir.normalize();
origin.x = org.x;
origin.y = org.y;
origin.z = org.z;
direction.x = dir.x;
direction.y = dir.y;
direction.z = dir.z;
}
/**
* @inheritDoc
*/
override public function clone():Object3D {
var camera:Camera3D = new Camera3D(nearClipping, farClipping);
camera.cloneBaseProperties(this);
camera.fov = fov;
camera.nearClipping = nearClipping;
camera.farClipping = farClipping;
return camera;
}
// Диаграмма
private var _diagram:Sprite = createDiagram();
/**
* Количество кадров, через которые происходит обновление значения FPS в диаграмме.
* @see #diagram
*/
public var fpsUpdatePeriod:int = 10;
/**
* Количество кадров, через которые происходит обновление значения MS в диаграмме.
* @see #diagram
*/
public var timerUpdatePeriod:int = 10;
private var fpsTextField:TextField;
private var memoryTextField:TextField;
private var drawsTextField:TextField;
private var trianglesTextField:TextField;
private var timerTextField:TextField;
private var graph:Bitmap;
private var rect:Rectangle;
private var _diagramAlign:String = "TR";
private var _diagramHorizontalMargin:Number = 2;
private var _diagramVerticalMargin:Number = 2;
private var fpsUpdateCounter:int;
private var previousFrameTime:int;
private var previousPeriodTime:int;
private var maxMemory:int;
private var timerUpdateCounter:int;
private var timeSum:int;
private var timeCount:int;
private var timer:int;
/**
* Начинает отсчёт времени.
* Методы <code>startTimer()</code> и <code>stopTimer()</code> нужны для того, чтобы замерять время выполнения ежекадрово вызывающегося куска кода.
* Результат замера показывается в диаграмме в поле MS.
* @see #diagram
* @see #stopTimer()
*/
public function startTimer():void {
timer = getTimer();
}
/**
* Заканчивает отсчёт времени.
* Методы <code>startTimer()</code> и <code>stopTimer()</code> нужны для того, чтобы замерять время выполнения ежекадрово вызывающегося куска кода.
* Результат замера показывается в диаграмме в поле MS.
* @see #diagram
* @see #startTimer()
*/
public function stopTimer():void {
timeSum += getTimer() - timer;
timeCount++;
}
/**
* Диаграмма, на которой отображается отладочная информация.
* Чтобы отобразить диаграмму, её нужно добавить на экран.
* FPS — Среднее количество кадров в секунду за промежуток в <code>fpsUpdatePeriod</code> кадров.<br>
* MS — Среднее время выполнения замеряемого с помощью <code>startTimer</code> - <code>stopTimer</code> участка кода в миллисекундах за промежуток в <code>timerUpdatePeriod</code> кадров.<br>
* MEM — Количество занимаемой плеером памяти в мегабайтах.<br>
* DRW — Количество отрисовочных вызовов в текущем кадре.<br>
* PLG — Количество видимых полигонов в текущем кадре.<br>
* TRI — Количество отрисованных треугольников в текущем кадре.
* @see #fpsUpdatePeriod
* @see #timerUpdatePeriod
* @see #startTimer()
* @see #stopTimer()
*/
public function get diagram():DisplayObject {
return _diagram;
}
/**
* Выравнивание диаграммы относительно рабочей области.
* Можно использовать константы класса <code>StageAlign</code>.
*/
public function get diagramAlign():String {
return _diagramAlign;
}
/**
* @private
*/
public function set diagramAlign(value:String):void {
_diagramAlign = value;
resizeDiagram();
}
/**
* Отступ диаграммы от края рабочей области по горизонтали.
*/
public function get diagramHorizontalMargin():Number {
return _diagramHorizontalMargin;
}
/**
* @private
*/
public function set diagramHorizontalMargin(value:Number):void {
_diagramHorizontalMargin = value;
resizeDiagram();
}
/**
* Отступ диаграммы от края рабочей области по вертикали.
*/
public function get diagramVerticalMargin():Number {
return _diagramVerticalMargin;
}
/**
* @private
*/
public function set diagramVerticalMargin(value:Number):void {
_diagramVerticalMargin = value;
resizeDiagram();
}
private function createDiagram():Sprite {
var diagram:Sprite = new Sprite();
diagram.mouseEnabled = false;
diagram.mouseChildren = false;
// Инициализация диаграммы
diagram.addEventListener(Event.ADDED_TO_STAGE, function():void {
// FPS
fpsTextField = new TextField();
fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC);
fpsTextField.autoSize = TextFieldAutoSize.LEFT;
fpsTextField.text = "FPS:";
fpsTextField.selectable = false;
fpsTextField.x = -3;
fpsTextField.y = -5;
diagram.addChild(fpsTextField);
fpsTextField = new TextField();
fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC);
fpsTextField.autoSize = TextFieldAutoSize.RIGHT;
fpsTextField.text = Number(diagram.stage.frameRate).toFixed(2);
fpsTextField.selectable = false;
fpsTextField.x = -3;
fpsTextField.y = -5;
fpsTextField.width = 85;
diagram.addChild(fpsTextField);
// Время выполнения метода
timerTextField = new TextField();
timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF);
timerTextField.autoSize = TextFieldAutoSize.LEFT;
timerTextField.text = "MS:";
timerTextField.selectable = false;
timerTextField.x = -3;
timerTextField.y = 4;
diagram.addChild(timerTextField);
timerTextField = new TextField();
timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF);
timerTextField.autoSize = TextFieldAutoSize.RIGHT;
timerTextField.text = "";
timerTextField.selectable = false;
timerTextField.x = -3;
timerTextField.y = 4;
timerTextField.width = 85;
diagram.addChild(timerTextField);
// Память
memoryTextField = new TextField();
memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00);
memoryTextField.autoSize = TextFieldAutoSize.LEFT;
memoryTextField.text = "MEM:";
memoryTextField.selectable = false;
memoryTextField.x = -3;
memoryTextField.y = 13;
diagram.addChild(memoryTextField);
memoryTextField = new TextField();
memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00);
memoryTextField.autoSize = TextFieldAutoSize.RIGHT;
memoryTextField.text = bytesToString(System.totalMemory);
memoryTextField.selectable = false;
memoryTextField.x = -3;
memoryTextField.y = 13;
memoryTextField.width = 85;
diagram.addChild(memoryTextField);
// Отрисовочные вызовы
drawsTextField = new TextField();
drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00);
drawsTextField.autoSize = TextFieldAutoSize.LEFT;
drawsTextField.text = "DRW:";
drawsTextField.selectable = false;
drawsTextField.x = -3;
drawsTextField.y = 22;
diagram.addChild(drawsTextField);
drawsTextField = new TextField();
drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00);
drawsTextField.autoSize = TextFieldAutoSize.RIGHT;
drawsTextField.text = "0";
drawsTextField.selectable = false;
drawsTextField.x = -3;
drawsTextField.y = 22;
drawsTextField.width = 72;
diagram.addChild(drawsTextField);
// Треугольники
trianglesTextField = new TextField();
trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF3300); // 0xFF6600, 0xFF0033
trianglesTextField.autoSize = TextFieldAutoSize.LEFT;
trianglesTextField.text = "TRI:";
trianglesTextField.selectable = false;
trianglesTextField.x = -3;
trianglesTextField.y = 31;
diagram.addChild(trianglesTextField);
trianglesTextField = new TextField();
trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF3300);
trianglesTextField.autoSize = TextFieldAutoSize.RIGHT;
trianglesTextField.text = "0";
trianglesTextField.selectable = false;
trianglesTextField.x = -3;
trianglesTextField.y = 31;
trianglesTextField.width = 72;
diagram.addChild(trianglesTextField);
// График
graph = new Bitmap(new BitmapData(80, 40, true, 0x20FFFFFF));
rect = new Rectangle(0, 0, 1, 40);
graph.x = 0;
graph.y = 45;
diagram.addChild(graph);
// Сброс параметров
previousPeriodTime = getTimer();
previousFrameTime = previousPeriodTime;
fpsUpdateCounter = 0;
maxMemory = 0;
timerUpdateCounter = 0;
timeSum = 0;
timeCount = 0;
// Подписка
diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000);
diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000);
resizeDiagram();
});
// Деинициализация диаграммы
diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void {
// Обнуление
diagram.removeChild(fpsTextField);
diagram.removeChild(memoryTextField);
diagram.removeChild(drawsTextField);
diagram.removeChild(trianglesTextField);
diagram.removeChild(timerTextField);
diagram.removeChild(graph);
fpsTextField = null;
memoryTextField = null;
drawsTextField = null;
trianglesTextField = null;
timerTextField = null;
graph.bitmapData.dispose();
graph = null;
rect = null;
// Отписка
diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram);
diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram);
});
return diagram;
}
private function resizeDiagram(e:Event = null):void {
if (_diagram.stage != null) {
var coord:Point = _diagram.parent.globalToLocal(new Point());
if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) {
_diagram.x = Math.round(coord.x + _diagramHorizontalMargin);
}
if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) {
_diagram.x = Math.round(coord.x + _diagram.stage.stageWidth/2 - graph.width/2);
}
if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) {
_diagram.x = Math.round(coord.x + _diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width);
}
if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) {
_diagram.y = Math.round(coord.y + _diagramVerticalMargin);
}
if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) {
_diagram.y = Math.round(coord.y + _diagram.stage.stageHeight/2 - (graph.y + graph.height)/2);
}
if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) {
_diagram.y = Math.round(coord.y + _diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height);
}
}
}
private function updateDiagram(e:Event):void {
var value:Number;
var mod:int;
var time:int = getTimer();
var stageFrameRate:int = _diagram.stage.frameRate;
// FPS текст
if (++fpsUpdateCounter == fpsUpdatePeriod) {
value = 1000*fpsUpdatePeriod/(time - previousPeriodTime);
if (value > stageFrameRate) value = stageFrameRate;
mod = value*100 % 100;
fpsTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00"));
previousPeriodTime = time;
fpsUpdateCounter = 0;
}
// FPS график
value = 1000/(time - previousFrameTime);
if (value > stageFrameRate) value = stageFrameRate;
graph.bitmapData.scroll(1, 0);
graph.bitmapData.fillRect(rect, 0x20FFFFFF);
graph.bitmapData.setPixel32(0, 40*(1 - value/stageFrameRate), 0xFFCCCCCC);
previousFrameTime = time;
// Время текст
if (++timerUpdateCounter == timerUpdatePeriod) {
if (timeCount > 0) {
value = timeSum/timeCount;
mod = value*100 % 100;
timerTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00"));
} else {
timerTextField.text = "";
}
timerUpdateCounter = 0;
timeSum = 0;
timeCount = 0;
}
// Память текст
var memory:int = System.totalMemory;
value = memory/1048576;
mod = value*100 % 100;
memoryTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00"));
// Память график
if (memory > maxMemory) maxMemory = memory;
graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00);
// Отрисовочные вызовы текст
drawsTextField.text = formatInt(numDraws);
// Треугольники текст
trianglesTextField.text = formatInt(numTriangles);
}
private function formatInt(num:int):String {
var n:int;
var s:String;
if (num < 1000) {
return "" + num;
} else if (num < 1000000) {
n = num % 1000;
if (n < 10) {
s = "00" + n;
} else if (n < 100) {
s = "0" + n;
} else {
s = "" + n;
}
return int(num/1000) + " " + s;
} else {
n = (num % 1000000)/1000;
if (n < 10) {
s = "00" + n;
} else if (n < 100) {
s = "0" + n;
} else {
s = "" + n;
}
n = num % 1000;
if (n < 10) {
s += " 00" + n;
} else if (n < 100) {
s += " 0" + n;
} else {
s += " " + n;
}
return int(num/1000000) + " " + s;
}
}
private function bytesToString(bytes:int):String {
if (bytes < 1024) return bytes + "b";
else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb";
else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb";
else if (bytes < 1048576) return (bytes >> 10) + "kb";
else if (bytes < 10485760) return (bytes/1048576).toFixed(2);// + "mb";
else if (bytes < 104857600) return (bytes/1048576).toFixed(1);// + "mb";
else return String(bytes >> 20);// + "mb";
}
private static var planeMesh:Mesh = createPlaneMesh();
private static function createPlaneMesh():Mesh {
var mesh:Mesh = new Mesh();
var g:Geometry = new Geometry();
g.addQuadFace(g.addVertex(-1, 1, 0, 0, 1), g.addVertex(1, 1, 0, 1, 1), g.addVertex(1, -1, 0, 1, 0), g.addVertex(-1, -1, 0, 0, 0));
mesh.geometry = g;
return mesh;
}
/**
* @private
* Отрисовывает материал на весь экран
*/
alternativa3d function drawMaterial(material:Material):void {
planeMesh.geometry.update(view._context3d);
material.update(view._context3d);
material.drawMesh(planeMesh, this);
}
}
}

View File

@@ -0,0 +1,261 @@
package alternativa.engine3d.core {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import flash.geom.Point;
import flash.geom.Vector3D;
use namespace alternativa3d;
/**
* Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов.
* @see alternativa.engine3d.core.Geometry
* @see alternativa.engine3d.core.Vertex
*/
public class Face {
// /**
// * Материал грани.
// * @see alternativa.engine3d.materials.Material
// */
// public var material:Material;
alternativa3d var geometry:Geometry;
/**
* @private
*/
alternativa3d var next:Face;
/**
* @private
*/
alternativa3d var wrapper:Wrapper;
/**
* @private
*/
alternativa3d var normalX:Number;
/**
* @private
*/
alternativa3d var normalY:Number;
/**
* @private
*/
alternativa3d var normalZ:Number;
/**
* @private
*/
alternativa3d var offset:Number;
/**
* @private
*/
static alternativa3d var collector:Face;
/**
* @private
*/
static alternativa3d function create():Face {
if (collector != null) {
var res:Face = collector;
collector = res.next;
res.next = null;
/*if (res.processNext != null) trace("!!!processNext!!!");
if (res.geometry != null) trace("!!!geometry!!!");
if (res.negative != null) trace("!!!negative!!!");
if (res.positive != null) trace("!!!positive!!!");*/
return res;
} else {
//trace("new Face");
return new Face();
}
}
/**
* @private
*/
alternativa3d function create():Face {
if (collector != null) {
var res:Face = collector;
collector = res.next;
res.next = null;
/*if (res.processNext != null) trace("!!!processNext!!!");
if (res.geometry != null) trace("!!!geometry!!!");
if (res.negative != null) trace("!!!negative!!!");
if (res.positive != null) trace("!!!positive!!!");*/
return res;
} else {
//trace("new Face");
return new Face();
}
}
/**
* Нормаль грани.
*/
public function get normal():Vector3D {
var w:Wrapper = wrapper;
var a:Vertex = w.vertex; w = w.next;
var b:Vertex = w.vertex; w = w.next;
var c:Vertex = w.vertex;
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 nx:Number = acz*aby - acy*abz;
var ny:Number = acx*abz - acz*abx;
var nz:Number = acy*abx - acx*aby;
var len:Number = nx*nx + ny*ny + nz*nz;
if (len > 0.001) {
len = 1/Math.sqrt(len);
nx *= len;
ny *= len;
nz *= len;
}
normalX = nx;
normalY = ny;
normalZ = nz;
return new Vector3D(nx, ny, nz, a._x*nx + a._y*ny + a._z*nz);
}
public function getUV(point:Vector3D):Point {
if (normalX != normalX) {
var normal:Vector3D = normal;
normalX = normal.x;
normalY = normal.y;
normalZ = normal.z;
}
var a:Vertex = wrapper.vertex;
var b:Vertex = wrapper.next.vertex;
var c:Vertex = wrapper.next.next.vertex;
var abx:Number = b.x - a.x;
var aby:Number = b.y - a.y;
var abz:Number = b.z - a.z;
var abu:Number = b.u - a.u;
var abv:Number = b.v - a.v;
var acx:Number = c.x - a.x;
var acy:Number = c.y - a.y;
var acz:Number = c.z - a.z;
var acu:Number = c.u - a.u;
var acv:Number = c.v - a.v;
// Нахождение матрицы uv-трансформации
var det:Number = -normalX*acy*abz + acx*normalY*abz + normalX*aby*acz - abx*normalY*acz - acx*aby*normalZ + abx*acy*normalZ;
var ima:Number = (-normalY*acz + acy*normalZ)/det;
var imb:Number = (normalX*acz - acx*normalZ)/det;
var imc:Number = (-normalX*acy + acx*normalY)/det;
var imd:Number = (a.x*normalY*acz - normalX*a.y*acz - a.x*acy*normalZ + acx*a.y*normalZ + normalX*acy*a.z - acx*normalY*a.z)/det;
var ime:Number = (normalY*abz - aby*normalZ)/det;
var imf:Number = (-normalX*abz + abx*normalZ)/det;
var img:Number = (normalX*aby - abx*normalY)/det;
var imh:Number = (normalX*a.y*abz - a.x*normalY*abz + a.x*aby*normalZ - abx*a.y*normalZ - normalX*aby*a.z + abx*normalY*a.z)/det;
var ma:Number = abu*ima + acu*ime;
var mb:Number = abu*imb + acu*imf;
var mc:Number = abu*imc + acu*img;
var md:Number = abu*imd + acu*imh + a.u;
var me:Number = abv*ima + acv*ime;
var mf:Number = abv*imb + acv*imf;
var mg:Number = abv*imc + acv*img;
var mh:Number = abv*imd + acv*imh + a.v;
// UV
return new Point(ma*point.x + mb*point.y + mc*point.z + md, me*point.x + mf*point.y + mg*point.z + mh);
}
/**
* Вершины, на базе которых построена грань.
* @see alternativa.engine3d.core.Vertex
*/
public function get vertices():Vector.<Vertex> {
var res:Vector.<Vertex> = new Vector.<Vertex>();
var len:int = 0;
for (var w:Wrapper = wrapper; w != null; w = w.next) {
res[len] = w.vertex;
len++;
}
return res;
}
/**
* @private
*/
alternativa3d function calculateBestSequenceAndNormal():void {
if (wrapper.next.next.next != null) {
var max:Number = -1e+22;
var s:Wrapper;
var sm:Wrapper;
var sp:Wrapper;
for (w = wrapper; w != null; w = w.next) {
var wn:Wrapper = (w.next != null) ? w.next : wrapper;
var wm:Wrapper = (wn.next != null) ? wn.next : wrapper;
a = w.vertex;
b = wn.vertex;
c = wm.vertex;
abx = b._x - a._x;
aby = b._y - a._y;
abz = b._z - a._z;
acx = c._x - a._x;
acy = c._y - a._y;
acz = c._z - a._z;
nx = acz*aby - acy*abz;
ny = acx*abz - acz*abx;
nz = acy*abx - acx*aby;
nl = nx*nx + ny*ny + nz*nz;
if (nl > max) {
max = nl;
s = w;
}
}
if (s != wrapper) {
//for (sm = wrapper.next.next.next; sm.next != null; sm = sm.next);
sm = wrapper.next.next.next;
while (sm.next != null) sm = sm.next;
//for (sp = wrapper; sp.next != s && sp.next != null; sp = sp.next);
sp = wrapper;
while (sp.next != s && sp.next != null) sp = sp.next;
sm.next = wrapper;
sp.next = null;
wrapper = s;
}
}
var w:Wrapper = wrapper;
var a:Vertex = w.vertex;
w = w.next;
var b:Vertex = w.vertex;
w = w.next;
var c:Vertex = w.vertex;
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 nx:Number = acz*aby - acy*abz;
var ny:Number = acx*abz - acz*abx;
var nz:Number = acy*abx - acx*aby;
var nl:Number = nx*nx + ny*ny + nz*nz;
if (nl > 0) {
nl = 1/Math.sqrt(nl);
nx *= nl;
ny *= nl;
nz *= nl;
normalX = nx;
normalY = ny;
normalZ = nz;
}
offset = a._x*nx + a._y*ny + a._z*nz;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,720 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.lights.DirectionalLight;
import flash.events.EventDispatcher;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
use namespace alternativa3d;
public class Object3D extends EventDispatcher {
public var useShadows:Boolean = true;
/**
* Имя объекта.
*/
public var name:String;
/**
* Флаг видимости объекта.
*/
public var visible:Boolean = true;
/**
* Координата X.
*/
public var x:Number = 0;
/**
* Координата Y.
*/
public var y:Number = 0;
/**
* Координата Z.
*/
public var z:Number = 0;
/**
* Угол поворота вокруг оси X.
* Указывается в радианах.
*/
public var rotationX:Number = 0;
/**
* Угол поворота вокруг оси Y.
* Указывается в радианах.
*/
public var rotationY:Number = 0;
/**
* Угол поворота вокруг оси Z.
* Указывается в радианах.
*/
public var rotationZ:Number = 0;
/**
* Коэффициент масштабирования по оси X.
*/
public var scaleX:Number = 1;
/**
* Коэффициент масштабирования по оси Y.
*/
public var scaleY:Number = 1;
/**
* Коэффициент масштабирования по оси Z.
*/
public var scaleZ:Number = 1;
/**
* Левая граница объекта в его системе координат.
*/
public var boundMinX:Number = -1e+22;
/**
* Задняя граница объекта в его системе координат.
*/
public var boundMinY:Number = -1e+22;
/**
* Нижняя граница объекта в его системе координат.
*/
public var boundMinZ:Number = -1e+22;
/**
* Правая граница объекта в его системе координат.
*/
public var boundMaxX:Number = 1e+22;
/**
* Передняя граница объекта в его системе координат.
*/
public var boundMaxY:Number = 1e+22;
/**
* Верхняя граница объекта в его системе координат.
*/
public var boundMaxZ:Number = 1e+22;
alternativa3d var cameraMatrix:Matrix3D = new Matrix3D();
alternativa3d var cameraMatrixData:Vector.<Number> = new Vector.<Number>(16);
alternativa3d var inverseCameraMatrix:Matrix3D = new Matrix3D();
alternativa3d var projectionMatrix:Matrix3D = new Matrix3D();
alternativa3d var _parent:Object3DContainer;
alternativa3d var next:Object3D;
alternativa3d var culling:int = 0;
alternativa3d var distance:Number;
alternativa3d var weightsSum:Vector.<Number>;
alternativa3d function get isTransparent():Boolean {
return false;
}
/**
* Возвращает родительский объект <code>Object3DContainer</code>.
*/
public function get parent():Object3DContainer {
return _parent;
}
/**
* Объект <code>Matrix3D</code>, содержащий значения, влияющие на масштабирование, поворот и перемещение объекта.
*/
public function get matrix():Matrix3D {
var m:Matrix3D = new Matrix3D();
var t:Vector3D = new Vector3D(x, y, z);
var r:Vector3D = new Vector3D(rotationX, rotationY, rotationZ);
var s:Vector3D = new Vector3D(scaleX, scaleY, scaleZ);
var v:Vector.<Vector3D> = new Vector.<Vector3D>();
v[0] = t;
v[1] = r;
v[2] = s;
m.recompose(v);
return m;
}
/**
* @private
*/
public function set matrix(value:Matrix3D):void {
var v:Vector.<Vector3D> = value.decompose();
var t:Vector3D = v[0];
var r:Vector3D = v[1];
var s:Vector3D = v[2];
x = t.x;
y = t.y;
z = t.z;
rotationX = r.x;
rotationY = r.y;
rotationZ = r.z;
scaleX = s.x;
scaleY = s.y;
scaleZ = s.z;
}
/**
* Расчитывает границы объекта в его системе координат.
*/
public function calculateBounds():void {
// Выворачивание баунда
boundMinX = 1e+22;
boundMinY = 1e+22;
boundMinZ = 1e+22;
boundMaxX = -1e+22;
boundMaxY = -1e+22;
boundMaxZ = -1e+22;
// Заполнение баунда
updateBounds(this, null);
// Если баунд вывернут
if (boundMinX > boundMaxX) {
boundMinX = -1e+22;
boundMinY = -1e+22;
boundMinZ = -1e+22;
boundMaxX = 1e+22;
boundMaxY = 1e+22;
boundMaxZ = 1e+22;
}
}
/**
* Преобразует точку из локальных координат в глобальные.
* @param point Точка в локальных координатах объекта.
* @return Точка в глобальном пространстве.
*/
public function localToGlobal(point:Vector3D):Vector3D {
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
return cameraMatrix.transformVector(point);
}
/**
* Преобразует точку из глобальной системы координат в локальные координаты объекта.
* @param point Точка в глобальном пространстве.
* @return Точка в локальных координатах объекта.
*/
public function globalToLocal(point:Vector3D):Vector3D {
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
cameraMatrix.invert();
return cameraMatrix.transformVector(point);
}
/**
* Осуществляет поиск пересечение луча с объектом.
* @param origin Начало луча.
* @param direction Направление луча.
* @param exludedObjects Ассоциативный массив, ключами которого являются экземпляры <code>Object3D</code> и его наследников. Объекты, содержащиеся в этом массиве будут исключены из проверки.
* @param camera Камера для правильного поиска пересечения луча с объектами <code>Sprite3D</code>. Эти объекты всегда повёрнуты к камере. Если камера не указана, результат пересечения луча со спрайтом будет <code>null</code>.
* @return Результат поиска пересечения — объект <code>RayIntersectionData</code>. Если пересечения нет, будет возвращён <code>null</code>.
* @see RayIntersectionData
* @see alternativa.engine3d.objects.Sprite3D
*/
public function intersectRay(origin:Vector3D, direction:Vector3D, exludedObjects:Dictionary = null, camera:Camera3D = null):RayIntersectionData {
return null;
}
/**
* Возвращает объект, являющийся точной копией исходного объекта.
* @return Клон исходного объекта.
*/
public function clone():Object3D {
var res:Object3D = new Object3D();
res.cloneBaseProperties(this);
return res;
}
/**
* Копирует базовые свойства. Метод вызывается внутри <code>clone()</code>.
* @param source Объект, с которого копируются базовые свойства.
*/
protected function cloneBaseProperties(source:Object3D):void {
name = source.name;
visible = source.visible;
distance = source.distance;
x = source.x;
y = source.y;
z = source.z;
rotationX = source.rotationX;
rotationY = source.rotationY;
rotationZ = source.rotationZ;
scaleX = source.scaleX;
scaleY = source.scaleY;
scaleZ = source.scaleZ;
boundMinX = source.boundMinX;
boundMinY = source.boundMinY;
boundMinZ = source.boundMinZ;
boundMaxX = source.boundMaxX;
boundMaxY = source.boundMaxY;
boundMaxZ = source.boundMaxZ;
}
/**
* Возвращает строковое представление заданного объекта.
* @return Строковое представление объекта.
*/
override public function toString():String {
var className:String = getQualifiedClassName(this);
return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]";
}
// Переопределяемые закрытые методы
/**
* @private
*/
alternativa3d function draw(camera:Camera3D):void {
}
/**
* @private
*/
alternativa3d function drawInShadowMap(camera:Camera3D, light:DirectionalLight):void {
}
/**
* @private
*/
alternativa3d function updateBounds(bounds:Object3D, matrix:Matrix3D = null):void {
}
/**
* @private
*/
alternativa3d function boundIntersectRay(origin:Vector3D, direction:Vector3D, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):Boolean {
if (origin.x >= boundMinX && origin.x <= boundMaxX && origin.y >= boundMinY && origin.y <= boundMaxY && origin.z >= boundMinZ && origin.z <= boundMaxZ) return true;
if (origin.x < boundMinX && direction.x <= 0) return false;
if (origin.x > boundMaxX && direction.x >= 0) return false;
if (origin.y < boundMinY && direction.y <= 0) return false;
if (origin.y > boundMaxY && direction.y >= 0) return false;
if (origin.z < boundMinZ && direction.z <= 0) return false;
if (origin.z > boundMaxZ && direction.z >= 0) return false;
var a:Number;
var b:Number;
var c:Number;
var d:Number;
var threshold:Number = 0.000001;
// Пересечение проекций X и Y
if (direction.x > threshold) {
a = (boundMinX - origin.x)/direction.x;
b = (boundMaxX - origin.x)/direction.x;
} else if (direction.x < -threshold) {
a = (boundMaxX - origin.x)/direction.x;
b = (boundMinX - origin.x)/direction.x;
} else {
a = -1e+22;
b = 1e+22;
}
if (direction.y > threshold) {
c = (boundMinY - origin.y)/direction.y;
d = (boundMaxY - origin.y)/direction.y;
} else if (direction.y < -threshold) {
c = (boundMaxY - origin.y)/direction.y;
d = (boundMinY - origin.y)/direction.y;
} else {
c = -1e+22;
d = 1e+22;
}
if (c >= b || d <= a) return false;
if (c < a) {
if (d < b) b = d;
} else {
a = c;
if (d < b) b = d;
}
// Пересечение проекций XY и Z
if (direction.z > threshold) {
c = (boundMinZ - origin.z)/direction.z;
d = (boundMaxZ - origin.z)/direction.z;
} else if (direction.z < -threshold) {
c = (boundMaxZ - origin.z)/direction.z;
d = (boundMinZ - origin.z)/direction.z;
} else {
c = -1e+22;
d = 1e+22;
}
if (c >= b || d <= a) return false;
return true;
}
/**
* @private
*/
alternativa3d function composeMatrix():void {
var cosX:Number = Math.cos(rotationX);
var sinX:Number = Math.sin(rotationX);
var cosY:Number = Math.cos(rotationY);
var sinY:Number = Math.sin(rotationY);
var cosZ:Number = Math.cos(rotationZ);
var sinZ:Number = Math.sin(rotationZ);
var cosZsinY:Number = cosZ*sinY;
var sinZsinY:Number = sinZ*sinY;
var cosYscaleX:Number = cosY*scaleX;
var sinXscaleY:Number = sinX*scaleY;
var cosXscaleY:Number = cosX*scaleY;
var cosXscaleZ:Number = cosX*scaleZ;
var sinXscaleZ:Number = sinX*scaleZ;
cameraMatrixData[0] = cosZ*cosYscaleX;
cameraMatrixData[4] = cosZsinY*sinXscaleY - sinZ*cosXscaleY;
cameraMatrixData[8] = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ;
cameraMatrixData[12] = x;
cameraMatrixData[1] = sinZ*cosYscaleX;
cameraMatrixData[5] = sinZsinY*sinXscaleY + cosZ*cosXscaleY;
cameraMatrixData[9] = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ;
cameraMatrixData[13] = y;
cameraMatrixData[2] = -sinY*scaleX;
cameraMatrixData[6] = cosY*sinXscaleY;
cameraMatrixData[10] = cosY*cosXscaleZ;
cameraMatrixData[14] = z;
cameraMatrixData[3] = 0;
cameraMatrixData[7] = 0;
cameraMatrixData[11] = 0;
cameraMatrixData[15] = 1;
cameraMatrix.rawData = cameraMatrixData;
}
static private const boundVertices:Vector.<Number> = new Vector.<Number>(24);
alternativa3d function cullingInCamera(camera:Camera3D, culling:int):int {
if (culling > 0) {
var i:int;
var infront:Boolean;
var behind:Boolean;
// Заполнение
boundVertices[0] = boundMinX;
boundVertices[1] = boundMinY;
boundVertices[2] = boundMinZ;
boundVertices[3] = boundMaxX;
boundVertices[4] = boundMinY;
boundVertices[5] = boundMinZ;
boundVertices[6] = boundMinX;
boundVertices[7] = boundMaxY;
boundVertices[8] = boundMinZ;
boundVertices[9] = boundMaxX;
boundVertices[10] = boundMaxY;
boundVertices[11] = boundMinZ;
boundVertices[12] = boundMinX;
boundVertices[13] = boundMinY;
boundVertices[14] = boundMaxZ;
boundVertices[15] = boundMaxX;
boundVertices[16] = boundMinY;
boundVertices[17] = boundMaxZ;
boundVertices[18] = boundMinX;
boundVertices[19] = boundMaxY;
boundVertices[20] = boundMaxZ;
boundVertices[21] = boundMaxX;
boundVertices[22] = boundMaxY;
boundVertices[23] = boundMaxZ;
// Трансформация в камеру
cameraMatrix.transformVectors(boundVertices, boundVertices);
// Коррекция под 90 градусов
var sx:Number = camera.focalLength*2/camera.view._width;
var sy:Number = camera.focalLength*2/camera.view._height;
for (i = 0; i < 24; i += 2) {
boundVertices[i] *= sx; i++;
boundVertices[i] *= sy;
}
// Куллинг
if (culling & 1) {
for (i = 2, infront = false, behind = false; i <= 23; i += 3) {
if (boundVertices[i] > camera.currentNearClipping) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 62;
}
}
if (culling & 2) {
for (i = 2, infront = false, behind = false; i <= 23; i += 3) {
if (boundVertices[i] < camera.currentFarClipping) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 61;
}
}
if (culling & 4) {
for (i = 0, infront = false, behind = false; i <= 21; i += 3) {
if (-boundVertices[i] < boundVertices[int(i + 2)]) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 59;
}
}
if (culling & 8) {
for (i = 0, infront = false, behind = false; i <= 21; i += 3) {
if (boundVertices[i] < boundVertices[int(i + 2)]) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 55;
}
}
if (culling & 16) {
for (i = 1, infront = false, behind = false; i <= 22; i += 3) {
if (-boundVertices[i] < boundVertices[int(i + 1)]) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 47;
}
}
if (culling & 32) {
for (i = 1, infront = false, behind = false; i <= 22; i += 3) {
if (boundVertices[i] < boundVertices[int(i + 1)]) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 31;
}
}
}
this.culling = culling;
return culling;
}
alternativa3d function cullingInLight(light:DirectionalLight, culling:int):int {
if (culling > 0) {
var i:int;
var infront:Boolean;
var behind:Boolean;
// Заполнение
boundVertices[0] = boundMinX;
boundVertices[1] = boundMinY;
boundVertices[2] = boundMinZ;
boundVertices[3] = boundMaxX;
boundVertices[4] = boundMinY;
boundVertices[5] = boundMinZ;
boundVertices[6] = boundMinX;
boundVertices[7] = boundMaxY;
boundVertices[8] = boundMinZ;
boundVertices[9] = boundMaxX;
boundVertices[10] = boundMaxY;
boundVertices[11] = boundMinZ;
boundVertices[12] = boundMinX;
boundVertices[13] = boundMinY;
boundVertices[14] = boundMaxZ;
boundVertices[15] = boundMaxX;
boundVertices[16] = boundMinY;
boundVertices[17] = boundMaxZ;
boundVertices[18] = boundMinX;
boundVertices[19] = boundMaxY;
boundVertices[20] = boundMaxZ;
boundVertices[21] = boundMaxX;
boundVertices[22] = boundMaxY;
boundVertices[23] = boundMaxZ;
// Трансформация в камеру
cameraMatrix.transformVectors(boundVertices, boundVertices);
// Куллинг
if (culling & 1) {
for (i = 2, infront = false, behind = false; i <= 23; i += 3) {
if (boundVertices[i] > light.frustumMinZ) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 62;
}
}
if (culling & 2) {
for (i = 2, infront = false, behind = false; i <= 23; i += 3) {
if (boundVertices[i] < light.frustumMaxZ) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 61;
}
}
// left
if (culling & 4) {
for (i = 0, infront = false, behind = false; i <= 21; i += 3) {
if (boundVertices[i] > light.frustumMinX) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 59;
}
}
// right
if (culling & 8) {
for (i = 0, infront = false, behind = false; i <= 21; i += 3) {
if (boundVertices[i] < light.frustumMaxX) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 55;
}
}
// up
if (culling & 16) {
for (i = 1, infront = false, behind = false; i <= 22; i += 3) {
if (boundVertices[i] > light.frustumMinY) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 47;
}
}
// down
if (culling & 32) {
for (i = 1, infront = false, behind = false; i <= 22; i += 3) {
if (boundVertices[i] < light.frustumMaxY) {
infront = true;
if (behind) break;
} else {
behind = true;
if (infront) break;
}
}
if (behind) {
if (!infront) return -1;
} else {
culling &= 31;
}
}
}
this.culling = culling;
return culling;
}
/*protected function composeRenderMatrix():void {
if (matrix != null) {
cameraMatrix.identity();
cameraMatrix.append(matrix);
} else {
composeVectors[0] = translation;
composeVectors[1] = rotation;
composeVectors[2] = scale;
cameraMatrix.recompose(composeVectors);
}
}*/
/*public function draw(camera:Camera3D, parent:Object3DContainer = null):void {
}*/
/**
* Возвращает объект, являющийся точной копией исходного объекта.
* @return Клон исходного объекта.
*/
/*public function clone():Object3D {
var res:Object3D = new Object3D();
res.cloneBaseProperties(this);
return res;
}*/
/**
* Копирует базовые свойства. Метод вызывается внутри <code>clone()</code>.
* @param source Объект, с которого копируются базовые свойства.
*/
/*protected function cloneBaseProperties(source:Object3D):void {
name = source.name;
visible = source.visible;
if (source.matrix != null) {
matrix = source.matrix.clone();
} else {
translation.x = source.translation.x;
translation.y = source.translation.y;
translation.z = source.translation.z;
rotation.x = source.rotation.x;
rotation.y = source.rotation.y;
rotation.z = source.rotation.z;
scale.x = source.scale.x;
scale.y = source.scale.y;
scale.z = source.scale.z;
}
}*/
}
}

View File

@@ -0,0 +1,400 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.lights.DirectionalLight;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
/**
* Базовый контейнер трёхмерных объектов.
* Логика контейнеров и child-parent-отношений идентична логике displayObject'ов во Flash.
* Дочерние объекты отрисовываются в том порядке, в котором находятся в списке.
*/
public class Object3DContainer extends Object3D {
/**
* @private
*/
alternativa3d var childrenList:Object3D;
/**
* Добавляет дочерний объект. Объект добавляется в конец списка.
* Если добавляется объект, предком которого уже является другой контейнер, то объект удаляется из списка потомков старого контейнера.
* @param child Добавляемый дочерний объект.
* @return Экземпляр Object3D, передаваемый в параметре <code>child</code>.
*/
public function addChild(child:Object3D):Object3D {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
if (child == this) throw new ArgumentError("An object cannot be added as a child of itself.");
for (var container:Object3DContainer = _parent; container != null; container = container._parent) {
if (container == child) throw new ArgumentError("An object cannot be added as a child to one of it's children (or children's children, etc.).");
}
// Удаление из старого родителя
if (child._parent != null) child._parent.removeChild(child);
// Добавление
addToList(child);
return child;
}
/**
* Удаляет дочерний объект. Свойство <code>parent</code> удаленного объекта получает значение <code>null</code>.
* @param child Удаляемый дочерний объект.
* @return Экземпляр Object3D, передаваемый в параметре <code>child</code>.
*/
public function removeChild(child:Object3D):Object3D {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller.");
// Удаление
var prev:Object3D;
var current:Object3D;
for (current = childrenList; current != null; current = current.next) {
if (current == child) {
if (prev != null) {
prev.next = current.next;
} else {
childrenList = current.next;
}
current.next = null;
current._parent = null;
return child;
}
prev = current;
}
throw new ArgumentError("Cannot remove child.");
}
/**
* Добавляет дочерний объект. Объект добавляется в указанную позицию в списке.
* @param child Добавляемый дочерний объект.
* @param index Позиция, в которую добавляется дочерний объект.
* @return Экземпляр Object3D, передаваемый в параметре <code>child</code>.
*/
public function addChildAt(child:Object3D, index:int):Object3D {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
if (child == this) throw new ArgumentError("An object cannot be added as a child of itself.");
if (index < 0) throw new RangeError("The supplied index is out of bounds.");
for (var container:Object3DContainer = _parent; container != null; container = container._parent) {
if (container == child) throw new ArgumentError("An object cannot be added as a child to one of it's children (or children's children, etc.).");
}
// Поиск элемента по индексу
var current:Object3D = childrenList;
for (var i:int = 0; i < index; i++) {
if (current == null) throw new RangeError("The supplied index is out of bounds.");
current = current.next;
}
// Удаление из старого родителя
if (child._parent != null) child._parent.removeChild(child);
// Добавление
addToList(child, current);
return child;
}
/**
* Удаляет дочерний объект из заданной позиции. Свойство <code>parent</code> удаленного объекта получает значение <code>null</code>.
* @param index Позиция, из которой удаляется дочерний объект.
* @return Удаленный экземпляр Object3D.
*/
public function removeChildAt(index:int):Object3D {
// Проверка на ошибки
if (index < 0) throw new RangeError("The supplied index is out of bounds.");
// Поиск элемента по индексу
var current:Object3D = childrenList;
for (var i:int = 0; i < index; i++) {
if (current == null) throw new RangeError("The supplied index is out of bounds.");
current = current.next;
}
if (current == null) throw new RangeError("The supplied index is out of bounds.");
// Удаление
removeChild(current);
return current;
}
/**
* Возвращает экземпляр дочернего объекта, существующий в заданной позиции.
* @param index Позиция дочернего объекта.
* @return Дочерний объект с заданной позицией.
*/
public function getChildAt(index:int):Object3D {
// Проверка на ошибки
if (index < 0) throw new RangeError("The supplied index is out of bounds.");
// Поиск элемента по индексу
var current:Object3D = childrenList;
for (var i:int = 0; i < index; i++) {
if (current == null) throw new RangeError("The supplied index is out of bounds.");
current = current.next;
}
if (current == null) throw new RangeError("The supplied index is out of bounds.");
return current;
}
/**
* Возвращает позицию дочернего объекта.
* @param child Дочерний объект.
* @return Позиция заданного дочернего объекта.
*/
public function getChildIndex(child:Object3D):int {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller.");
// Поиск индекса элемента
var index:int = 0;
for (var current:Object3D = childrenList; current != null; current = current.next) {
if (current == child) return index;
index++;
}
throw new ArgumentError("Cannot get child index.");
}
/**
* Устанавливает позицию дочернего объекта.
* @param child Дочерний объект.
* @param index Устанавливаемая позиция объекта.
*/
public function setChildIndex(child:Object3D, index:int):void {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller.");
if (index < 0) throw new RangeError("The supplied index is out of bounds.");
// Поиск элемента по индексу
var current:Object3D = childrenList;
for (var i:int = 0; i < index; i++) {
if (current == null) throw new RangeError("The supplied index is out of bounds.");
current = current.next;
}
// Удаление
removeChild(child);
// Добавление
addToList(child, current);
}
/**
* Меняет местами два дочерних объекта в списке.
* @param child1 Первый дочерний объект.
* @param child2 Второй дочерний объект.
*/
public function swapChildren(child1:Object3D, child2:Object3D):void {
// Проверка на ошибки
if (child1 == null || child2 == null) throw new TypeError("Parameter child must be non-null.");
if (child1._parent != this || child2._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller.");
// Перестановка
if (child1 != child2) {
if (child1.next == child2) {
removeChild(child2);
addToList(child2, child1);
} else if (child2.next == child1) {
removeChild(child1);
addToList(child1, child2);
} else {
var nxt:Object3D = child1.next;
removeChild(child1);
addToList(child1, child2);
removeChild(child2);
addToList(child2, nxt);
}
}
}
/**
* Меняет местами два дочерних объекта в списке по указанным позициям.
* @param index1 Позиция первого дочернего объекта.
* @param index2 Позиция второго дочернего объекта.
*/
public function swapChildrenAt(index1:int, index2:int):void {
// Проверка на ошибки
if (index1 < 0 || index2 < 0) throw new RangeError("The supplied index is out of bounds.");
// Перестановка
if (index1 != index2) {
// Поиск элементов по индексам
var i:int;
var child1:Object3D = childrenList;
for (i = 0; i < index1; i++) {
if (child1 == null) throw new RangeError("The supplied index is out of bounds.");
child1 = child1.next;
}
if (child1 == null) throw new RangeError("The supplied index is out of bounds.");
var child2:Object3D = childrenList;
for (i = 0; i < index2; i++) {
if (child2 == null) throw new RangeError("The supplied index is out of bounds.");
child2 = child2.next;
}
if (child2 == null) throw new RangeError("The supplied index is out of bounds.");
if (child1 != child2) {
if (child1.next == child2) {
removeChild(child2);
addToList(child2, child1);
} else if (child2.next == child1) {
removeChild(child1);
addToList(child1, child2);
} else {
var nxt:Object3D = child1.next;
removeChild(child1);
addToList(child1, child2);
removeChild(child2);
addToList(child2, nxt);
}
}
}
}
/**
* Возвращает дочерний объект с заданным именем.
* Если объектов с заданным именем несколько, возвратится первый попавшийся.
* Если объект с заданным именем не содержится в контейнере, возвратится <code>null</code>.
* @param name Имя дочернего объекта.
* @return Дочерний объект с заданным именем.
*/
public function getChildByName(name:String):Object3D {
// Проверка на ошибки
if (name == null) throw new TypeError("Parameter name must be non-null.");
// Поиск объекта
for (var child:Object3D = childrenList; child != null; child = child.next) {
if (child.name == name) return child;
}
return null;
}
/**
* Определяет, содержится ли заданный объект среди дочерних объектов.
* Область поиска охватывает часть иерархии, начиная с данного Object3DContainer.
* @param child Дочерний объект.
* @return Значение <code>true</code>, если заданный объект является самим контейнером или одним из его потомков, в противном случае значение <code>false</code>.
*/
public function contains(child:Object3D):Boolean {
// Проверка на ошибки
if (child == null) throw new TypeError("Parameter child must be non-null.");
// Поиск объекта
if (child == this) return true;
for (var object:Object3D = childrenList; object != null; object = object.next) {
if (object is Object3DContainer) {
if ((object as Object3DContainer).contains(child)) {
return true;
}
} else if (object == child) {
return true;
}
}
return false;
}
/**
* Возвращает количество дочерних объектов.
*/
public function get numChildren():int {
var num:int = 0;
for (var current:Object3D = childrenList; current != null; current = current.next) num++;
return num;
}
/**
* @inheritDoc
*/
override public function intersectRay(origin:Vector3D, direction:Vector3D, exludedObjects:Dictionary = null, camera:Camera3D = null):RayIntersectionData {
if (exludedObjects != null && exludedObjects[this]) return null;
if (!boundIntersectRay(origin, direction, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ)) return null;
var res:RayIntersectionData;
var minTime:Number = 1e+22;
for (var child:Object3D = childrenList; child != null; child = child.next) {
child.composeMatrix();
child.cameraMatrix.invert();
var childOrigin:Vector3D = child.cameraMatrix.transformVector(origin);
var childDirection:Vector3D = child.cameraMatrix.deltaTransformVector(direction);
var data:RayIntersectionData = child.intersectRay(childOrigin, childDirection, exludedObjects, camera);
if (data != null && data.time < minTime) {
minTime = data.time;
res = data;
}
}
return res;
}
/**
* @inheritDoc
*/
override public function clone():Object3D {
var container:Object3DContainer = new Object3DContainer();
container.cloneBaseProperties(this);
for (var child:Object3D = childrenList, lastChild:Object3D; child != null; child = child.next) {
var newChild:Object3D = child.clone();
if (container.childrenList != null) {
lastChild.next = newChild;
} else {
container.childrenList = newChild;
}
lastChild = newChild;
newChild._parent = container;
}
return container;
}
/**
* @private
*/
override alternativa3d function draw(camera:Camera3D):void {
for (var child:Object3D = childrenList; child != null; child = child.next) {
if (child.visible) {
child.composeMatrix();
child.cameraMatrix.append(cameraMatrix);
if (child.cullingInCamera(camera, culling) >= 0) {
if (child.isTransparent) {
camera.transparentObjects[int(camera.numTransparent++)] = child;
} else {
child.draw(camera);
}
}
}
}
}
/**
* @private
*/
override alternativa3d function drawInShadowMap(camera:Camera3D, light:DirectionalLight):void {
for (var child:Object3D = childrenList; child != null; child = child.next) {
if (child.visible && !child.isTransparent && child.useShadows) {
child.composeMatrix();
child.cameraMatrix.append(cameraMatrix);
if (child.cullingInLight(light, culling) >= 0) {
child.drawInShadowMap(camera, light);
}
}
}
}
/**
* @private
*/
override alternativa3d function updateBounds(bounds:Object3D, matrix:Matrix3D = null):void {
for (var child:Object3D = childrenList; child != null; child = child.next) {
child.composeMatrix();
if (matrix != null) child.cameraMatrix.append(matrix);
child.updateBounds(bounds, child.cameraMatrix);
}
}
/**
* @private
*/
alternativa3d function addToList(child:Object3D, item:Object3D = null):void {
child.next = item;
child._parent = this;
if (item == childrenList) {
childrenList = child;
} else {
for (var current:Object3D = childrenList; current != null; current = current.next) {
if (current.next == item) {
current.next = child;
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
package alternativa.engine3d.core {
import flash.geom.Vector3D;
/**
* Результат проверки пересечения луча с объектом.
*/
public class RayIntersectionData {
/**
* Объект, с которым найдено пересечение луча.
*/
public var object:Object3D;
/**
* Ближайшая к началу луча грань объекта, с которым найдено пересечение.
*/
public var face:Face;
/**
* Точка пересечения луча с объектом.
*/
public var point:Vector3D;
/**
* Положение точки пересечения с объектом на луче.
*/
public var time:Number;
}
}

View File

@@ -0,0 +1,267 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.core.View;
import flash.display.BitmapData;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Texture3D;
import flash.display3D.TextureCube3D;
import flash.filters.ConvolutionFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
use namespace alternativa3d;
public class Utils {
static public var temporaryBitmapData:BitmapData;
static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true);
static private const matrix:Matrix = new Matrix(0.5, 0, 0, 0.5);
static private const rect:Rectangle = new Rectangle();
static private const point:Point = new Point();
static public function uploadMap(view:View, map:BitmapData, dispose:Boolean = false):Texture3D {
var texture3d:Texture3D = view.context3d.createTexture(map.width, map.height, Context3DTextureFormat.BGRA, false);
filter.preserveAlpha = !map.transparent;
var bmp:BitmapData = (temporaryBitmapData != null) ? temporaryBitmapData : new BitmapData(map.width, map.height, map.transparent);
var level:int = 0;
texture3d.upload(map, level++);
var current:BitmapData = map;
rect.width = map.width;
rect.height = map.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != map) current.dispose();
current = new BitmapData(rect.width, rect.height, map.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
texture3d.upload(current, level++);
}
if (temporaryBitmapData == null) bmp.dispose();
if (dispose) map.dispose();
return texture3d;
}
static public function uploadCubeMap(view:View, left:BitmapData, right:BitmapData, back:BitmapData, front:BitmapData, bottom:BitmapData, top:BitmapData, dispose:Boolean = false):TextureCube3D {
var textureCube3D:TextureCube3D = view.context3d.createTextureCube(left.width, Context3DTextureFormat.BGRA, false);
filter.preserveAlpha = !left.transparent;
var bmp:BitmapData = (temporaryBitmapData != null) ? temporaryBitmapData : new BitmapData(left.width, left.height, left.transparent);
var level:int = 0;
textureCube3D.upload(left, 1, level++);
var current:BitmapData = left;
rect.width = left.width;
rect.height = left.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != left) current.dispose();
current = new BitmapData(rect.width, rect.height, left.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 1, level++);
}
level = 0;
textureCube3D.upload(right, 0, level++);
current = right;
rect.width = right.width;
rect.height = right.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != right) current.dispose();
current = new BitmapData(rect.width, rect.height, right.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 0, level++);
}
level = 0;
textureCube3D.upload(back, 3, level++);
current = back;
rect.width = back.width;
rect.height = back.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != back) current.dispose();
current = new BitmapData(rect.width, rect.height, back.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 3, level++);
}
level = 0;
textureCube3D.upload(front, 2, level++);
current = front;
rect.width = front.width;
rect.height = front.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != front) current.dispose();
current = new BitmapData(rect.width, rect.height, front.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 2, level++);
}
level = 0;
textureCube3D.upload(bottom, 5, level++);
current = bottom;
rect.width = bottom.width;
rect.height = bottom.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != bottom) current.dispose();
current = new BitmapData(rect.width, rect.height, bottom.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 5, level++);
}
level = 0;
textureCube3D.upload(top, 4, level++);
current = top;
rect.width = top.width;
rect.height = top.height;
while (rect.width % 2 == 0 || rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
if (rect.width == 0) rect.width = 1;
if (rect.height == 0) rect.height = 1;
if (current != top) current.dispose();
current = new BitmapData(rect.width, rect.height, top.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
textureCube3D.upload(current, 4, level++);
}
if (temporaryBitmapData == null) bmp.dispose();
if (dispose) {
left.dispose();
right.dispose();
back.dispose();
front.dispose();
bottom.dispose();
top.dispose();
}
return textureCube3D;
}
static public function uploadGeometry(view:View, geometry:Geometry, dispose:Boolean = false):void {
geometry.update(view.context3d);
if (dispose) {
geometry._vertices = null;
geometry._faces = null;
geometry.uvChannels = null;
}
}
static public function calculateVertexNormals(geometry:Geometry, weld:Boolean = false):void {
var vertex:Vertex;
var face:Face;
var normal:Vector3D;
var nx:Number;
var ny:Number;
var nz:Number;
var len:Number;
if (weld) {
// Заполнение массива вершин
var verts:Vector.<Vertex> = new Vector.<Vertex>();
var vertsLength:int = 0;
for each (vertex in geometry._vertices) {
verts[vertsLength] = vertex;
vertsLength++;
}
// Группировка
geometry.group(verts, 0, vertsLength, 0, 0.001, 100, new Vector.<int>());
// Расчёт нормалей объединённых вершин
for each (face in geometry.faces) {
normal = face.normal;
for each (vertex in face.vertices) {
if (vertex.value != null) vertex = vertex.value;
if (vertex.attributes == null) {
vertex.attributes = Vector.<Number>([normal.x, normal.y, normal.z]);
} else {
vertex.attributes[0] += normal.x;
vertex.attributes[1] += normal.y;
vertex.attributes[2] += normal.z;
}
}
}
// Нормализация
for each (vertex in geometry.vertices) {
if (vertex.value == null && vertex.attributes != null) {
nx = vertex.attributes[0];
ny = vertex.attributes[1];
nz = vertex.attributes[2];
len = 1/Math.sqrt(nx*nx + ny*ny + nz*nz);
vertex.attributes[0] = nx*len;
vertex.attributes[1] = ny*len;
vertex.attributes[2] = nz*len;
vertex._jointsWeights = null;
vertex._jointsIndices = null;
}
}
// Установка нормалей и сброс ремапа
for each (vertex in geometry.vertices) {
if (vertex.value != null) {
vertex.attributes = vertex.value.attributes;
vertex.value = null;
}
}
} else {
for each (face in geometry.faces) {
normal = face.normal;
for each (vertex in face.vertices) {
if (vertex.attributes == null) {
vertex.attributes = Vector.<Number>([normal.x, normal.y, normal.z]);
} else {
vertex.attributes[0] += normal.x;
vertex.attributes[1] += normal.y;
vertex.attributes[2] += normal.z;
}
}
}
for each (vertex in geometry.vertices) {
if (vertex.attributes != null) {
nx = vertex.attributes[0];
ny = vertex.attributes[1];
nz = vertex.attributes[2];
len = 1/Math.sqrt(nx*nx + ny*ny + nz*nz);
vertex.attributes[0] = nx*len;
vertex.attributes[1] = ny*len;
vertex.attributes[2] = nz*len;
vertex._jointsWeights = null;
vertex._jointsIndices = null;
}
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
package alternativa.engine3d.core {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import flash.geom.Vector3D;
use namespace alternativa3d;
/**
* Вершина в трёхмерном пространстве. Вершины являются составными частями полигональных объектов. На базе вершин строятся грани.
* @see alternativa.engine3d.core.Geometry
* @see alternativa.engine3d.core.Face
*/
public class Vertex {
alternativa3d var geometry:Geometry;
/**
* Координата X.
*/
public var _x:Number = 0;
/**
* Координата Y.
*/
public var _y:Number = 0;
/**
* Координата Z.
*/
public var _z:Number = 0;
/**
* Текстурная координата по горизонтали.
*/
public var _u:Number = 0;
/**
* Текстурная координата по вертикали.
*/
public var _v:Number = 0;
public var normal:Vector3D;
public var tangent:Vector3D;
/**
* Индексы костей, влияющих на эту вершину
*/
public var _jointsIndices:Vector.<uint>;
/**
* Веса костей, влияющих на эту вершину.
*/
public var _jointsWeights:Vector.<Number>;
/**
* Дополнительные аттрибуты в вершине для шейдера
*/
public var _attributes:Vector.<Number>;
/**
* @private
*/
alternativa3d var next:Vertex;
/**
* @private
*/
alternativa3d var value:Vertex;
/**
* @private
*/
alternativa3d var offset:Number;
/**
* @private
*/
alternativa3d var index:int;
/**
* @private
*/
static alternativa3d var collector:Vertex;
public function get x():Number {
return _x;
}
public function set x(value:Number):void {
_x = value;
geometry.reset();
}
public function get y():Number {
return _y;
}
public function set y(value:Number):void {
_y = value;
geometry.reset();
}
public function get z():Number {
return _z;
}
public function set z(value:Number):void {
_z = value;
geometry.reset();
}
public function get u():Number {
return _u;
}
public function set u(value:Number):void {
_u = value;
geometry.reset();
}
public function get v():Number {
return _v;
}
public function set v(value:Number):void {
_v = value;
geometry.reset();
}
public function get jointsIndices():Vector.<uint> {
return _jointsIndices;
}
public function set jointsIndices(value:Vector.<uint>):void {
_jointsIndices = value;
geometry.reset();
}
public function get jointsWeights():Vector.<Number> {
return _jointsWeights;
}
public function set jointsWeights(value:Vector.<Number>):void {
_jointsWeights = value;
geometry.reset();
}
public function get attributes():Vector.<Number> {
return _attributes;
}
public function set attributes(value:Vector.<Number>):void {
_attributes = value;
geometry.reset();
}
/**
* @private
*/
static alternativa3d function createList(num:int):Vertex {
var res:Vertex = collector;
var last:Vertex;
if (res != null) {
for (last = res; num > 1; last = last.next,num--) {
if (last.next == null) {
//for (; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--);
while (num > 1) {
//trace("new Vertex");
last.next = new Vertex();
last = last.next;
num--;
}
break;
}
}
collector = last.next;
last.next = null;
} else {
//for (res = new Vertex(), /*trace("new Vertex"), */last = res; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--);
//trace("new Vertex");
res = new Vertex();
last = res;
while (num > 1) {
//trace("new Vertex");
last.next = new Vertex();
last = last.next;
num--;
}
}
return res;
}
/**
* @private
*/
alternativa3d function create():Vertex {
if (collector != null) {
var res:Vertex = collector;
collector = res.next;
res.next = null;
return res;
} else {
//trace("new Vertex");
return new Vertex();
}
}
alternativa3d function clone():Vertex {
var res:Vertex = new Vertex();
res._x = _x;
res._y = _y;
res._z = _z;
res._u = _u;
res._v = _v;
var i:int, count:int;
if (_attributes != null) {
count = _attributes.length;
res._attributes = new Vector.<Number>(count);
for (i = 0; i < count; i++) {
res._attributes[i] = _attributes[i];
}
}
if (_jointsIndices != null) {
count = _jointsIndices.length;
res._jointsIndices = new Vector.<uint>(count);
for (i = 0; i < count; i++) {
res._jointsIndices[i] = _jointsIndices[i];
}
}
if (_jointsWeights != null) {
count = _jointsWeights.length;
res._jointsWeights = new Vector.<Number>(count);
for (i = 0; i < count; i++) {
res._jointsWeights[i] = _jointsWeights[i];
}
}
return res;
}
}
}

View File

@@ -0,0 +1,94 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import flash.display.BitmapData;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.geom.Rectangle;
import flash.display3D.Context3DClearMask;
use namespace alternativa3d;
public class View {
public var backgroundColor:int = 0;
public var backgroundAlpha:Number = 1;
public var clearMask:uint = Context3DClearMask.ALL;
alternativa3d var _width:Number = 0;
alternativa3d var _height:Number = 0;
/**
* @private
*/
alternativa3d var _context3d:Context3D;
/**
* @private
*/
alternativa3d var _stage3d:Stage3D;
/**
* @private
*/
alternativa3d var cachedPrograms:Object = new Object();
public function View(width:Number, height:Number, renderMode:String = "auto", stage3d:Stage3D = null, antialias:uint = 0, depthstencil:Boolean = true) {
_context3d = new Context3D(renderMode);
this.stage3d = stage3d;
setupBackBuffer(width, height, antialias, depthstencil);
}
public function get stage3d():Stage3D {
return stage3d;
}
public function set stage3d(value:Stage3D):void {
_stage3d = value;
if (_stage3d != null) {
_stage3d.attachContext3D(_context3d);
}
}
public function get renderMode():String {
return _context3d.renderMode;
}
public function clear(red:Number, green:Number, blue:Number, alpha:Number = 1.0, depth:Number = 1.0, stencil:int = 0, clearMask:uint = 7):void {
_context3d.clear(red, green, blue, alpha, depth, stencil, clearMask);
}
public function drawToBitmapData(destination:BitmapData):void {
_context3d.drawToBitmapData(destination);
}
public function setColorWriteMask(writeRed:Boolean, writeGreen:Boolean, writeBlue:Boolean, writeAlpha:Boolean):void {
_context3d.setColorWriteMask(writeRed, writeGreen, writeBlue, writeAlpha);
}
public function setScissor(rectangle:Rectangle):void {
_context3d.setScissor(rectangle);
}
public function get width():Number {
return _width;
}
public function get height():Number {
return _height;
}
public function get context3d():Context3D {
return _context3d;
}
public function setupBackBuffer(width:uint, height:uint, antialias:uint = 0, depthStencil:Boolean = true):void {
if (_stage3d != null) _stage3d.viewPort = new Rectangle(0, 0, width, height);
_context3d.setupBackBuffer(width, height, antialias, depthStencil);
_width = width;
_height = height;
}
}
}

View File

@@ -0,0 +1,43 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* @private
*/
public class Wrapper {
alternativa3d var next:Wrapper;
alternativa3d var vertex:Vertex;
static alternativa3d var collector:Wrapper;
static alternativa3d function create():Wrapper {
if (collector != null) {
var res:Wrapper = collector;
collector = collector.next;
res.next = null;
return res;
} else {
//trace("new Wrapper");
return new Wrapper();
}
}
alternativa3d function create():Wrapper {
if (collector != null) {
var res:Wrapper = collector;
collector = collector.next;
res.next = null;
return res;
} else {
//trace("new Wrapper");
return new Wrapper();
}
}
}
}

View File

@@ -0,0 +1,295 @@
package alternativa.engine3d.lights {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.materials.GridMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Texture3D;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
use namespace alternativa3d;
public class DirectionalLight extends Object3D {
public var useSingle:Boolean = true;
// lambda scales between logarithmic and uniform
public var shadowLambda:Number = 1.0;
public var debugShadowMaterial:GridMaterial;
public var createTextures:Boolean = false;
private static var dummy:Texture3D;
public var color:Vector.<Number> = Vector.<Number>([1, 1, 1, 1]);
public var debugMaterial:TextureMaterial;
public var options:Vector.<Number> = Vector.<Number>([0.007, 10000, 1.0, 0]); // Смещение, множитель
// Матрица перевода в источник света
alternativa3d var lightMatrix:Matrix3D = new Matrix3D();
alternativa3d var uvMatrix:Matrix3D = new Matrix3D();
private var near:Number;
private var far:Number;
private var _width:uint;
private var _height:uint;
public var numSplits:uint;
alternativa3d var currentSplitNear:Number;
alternativa3d var currentSplitFar:Number;
alternativa3d var currentSplitHaveShadow:Boolean = false;
alternativa3d var globalVector:Vector3D;
// Баунды сплита
alternativa3d var frustumMinX:Number;
alternativa3d var frustumMaxX:Number;
alternativa3d var frustumMinY:Number;
alternativa3d var frustumMaxY:Number;
alternativa3d var frustumMinZ:Number;
alternativa3d var frustumMaxZ:Number;
private var shadowMap:Texture3D;
public function DirectionalLight(width:uint, height:uint, near:Number, far:Number, numSplits:uint = 4) {
this._width = width;
this._height = height;
this.near = near;
this.far = far;
this.numSplits = numSplits;
}
alternativa3d function calcLightMatrix(camera:Camera3D):void {
// Расчёт матрицы перевода из глобального пространства в камеру
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
globalVector = cameraMatrix.deltaTransformVector(Vector3D.Z_AXIS);
cameraMatrix.invert();
lightMatrix.identity();
lightMatrix.append(camera.globalMatrix);
lightMatrix.append(cameraMatrix);
// lightMatrix.append(uvMatrix);
}
alternativa3d function update(camera:Camera3D, context3d:Context3D, splitIndex:int):void {
// Расчёт матрицы перевода из глобального пространства в камеру
composeMatrix();
var root:Object3D = this;
while (root._parent != null) {
root = root._parent;
root.composeMatrix();
cameraMatrix.append(root.cameraMatrix);
}
globalVector = cameraMatrix.deltaTransformVector(Vector3D.Z_AXIS);
cameraMatrix.invert();
var cameraWCoeff:Number = camera.view._width/2/camera.focalLength;
var cameraHCoeff:Number = camera.view._height/2/camera.focalLength;
var points:Vector.<Vector3D> = new Vector.<Vector3D>();
points[0] = new Vector3D(-cameraWCoeff, -cameraHCoeff, 0);
points[1] = new Vector3D(cameraWCoeff, -cameraHCoeff, 0);
points[2] = new Vector3D(cameraWCoeff, cameraHCoeff, 0);
points[3] = new Vector3D(-cameraWCoeff, cameraHCoeff, 0);
var p:Vector3D;
var transformed:Vector3D = new Vector3D();
var currentNear:Number = (camera.nearClipping < near) ? near : camera.nearClipping;
var currentFar:Number = (camera.farClipping < far) ? camera.farClipping : far;
frustumMinX = Number.MAX_VALUE;
frustumMinY = Number.MAX_VALUE;
frustumMinZ = Number.MAX_VALUE;
frustumMaxX = -Number.MAX_VALUE;
frustumMaxY = -Number.MAX_VALUE;
frustumMaxZ = -Number.MAX_VALUE;
var fNear:Number = splitIndex/numSplits;
var splitNear:Number = shadowLambda*currentNear*Math.pow(currentFar/currentNear, fNear) + (1 - shadowLambda)*(currentNear + fNear*(currentFar - currentNear));
var fFar:Number = (splitIndex + 1)/numSplits;
var splitFar:Number = shadowLambda*currentNear*Math.pow(currentFar/currentNear, fFar) + (1 - shadowLambda)*(currentNear + fFar*(currentFar - currentNear));
currentSplitNear = splitNear;
currentSplitFar = splitFar;
projectionMatrix.identity();
projectionMatrix.append(camera.globalMatrix);
projectionMatrix.append(cameraMatrix);
// Считаем баунд куска в источнике света
for (var j:int = 0; j < 8; j++) {
if (j < 4) {
p = points[j];
transformed.x = p.x * splitNear;
transformed.y = p.y * splitNear;
transformed.z = splitNear;
} else {
p = points[j - 4];
transformed.x = p.x * splitFar;
transformed.y = p.y * splitFar;
transformed.z = splitFar;
}
p = projectionMatrix.transformVector(transformed);
if (p.x < frustumMinX) {
frustumMinX = p.x;
} else if (p.x > frustumMaxX) {
frustumMaxX = p.x;
}
if (p.y < frustumMinY) {
frustumMinY = p.y;
} else if (p.y > frustumMaxY) {
frustumMaxY = p.y;
}
if (p.z > frustumMaxZ) {
frustumMaxZ = p.z;
}
}
frustumMinZ = 0;
var pixelW:Number = 1/_width;
// Считаем матрицу проецирования
var rawData:Vector.<Number> = new Vector.<Number>(16);
// cameraMatrixData
rawData[0] = 2/(frustumMaxX - frustumMinX);
rawData[5] = 2/(frustumMaxY - frustumMinY);
rawData[10]= 1/(frustumMaxZ - frustumMinZ);
rawData[12] = (-0.5 * (frustumMaxX + frustumMinX) * rawData[0]);
rawData[13] = (-0.5 * (frustumMaxY + frustumMinY) * rawData[5]);
rawData[14]= -frustumMinZ/(frustumMaxZ - frustumMinZ);
rawData[15]= 1;
projectionMatrix.rawData = rawData;
rawData[0] = 1/((frustumMaxX - frustumMinX));
if (useSingle) {
rawData[5] = 1/((frustumMaxY - frustumMinY));
} else {
rawData[5] = -1/((frustumMaxY - frustumMinY));
}
rawData[12] = 0.5 - (0.5 * (frustumMaxX + frustumMinX) * rawData[0]);
rawData[13] = 0.5 - (0.5 * (frustumMaxY + frustumMinY) * rawData[5]);
uvMatrix = new Matrix3D(rawData);
lightMatrix.identity();
lightMatrix.append(camera.globalMatrix);
lightMatrix.append(cameraMatrix);
lightMatrix.append(uvMatrix);
if (debugShadowMaterial != null) {
debugShadowMaterial.update(context3d);
}
currentSplitHaveShadow = false;
if (root != this && root.visible && !root.isTransparent && root.useShadows) {
root.composeMatrix();
root.cameraMatrix.append(cameraMatrix);
if (root.cullingInLight(this, 63) >= 0) {
root.drawInShadowMap(camera, this);
}
}
if (currentSplitHaveShadow) {
context3d.setRenderToBackbuffer();
}
}
alternativa3d function predraw(context3d:Context3D):void {
if (!currentSplitHaveShadow) {
// Создаем или очищаем шедоумапу
if (createTextures || shadowMap == null) {
if (useSingle) {
// shadowMap = context3d.createTexture(_width, _height, Context3DTextureFormat.SINGLE, true);
shadowMap = context3d.createTexture(_width, _height, Context3DTextureFormat.SINGLE_FLOAT, true);
} else {
// shadowMap = context3d.createTexture(_width, _height, Context3DTextureFormat.BGRA, true);
// shadowMap = context3d.createTexture(_width, _height, Context3DTextureFormat.RGBA_FLOAT, true);
}
}
// if (debugMaterial != null && splitIndex == 0) {
// debugMaterial.texture3d = shadowMap;
// }
context3d.setRenderToTexture(shadowMap, true);
context3d.clear(1, 1, 1, 1, 1);
currentSplitHaveShadow = true;
}
}
alternativa3d function getKey():String {
return (debugShadowMaterial != null) ? "D" : "d";
}
alternativa3d function attenuateVertexShader(v4:String, const4x4:uint):String {
// if (!shadow) {
// return "";
// }
var shader:String = "dp4 " + v4 + ".x, va0, vc" + const4x4+ " \n" +
"dp4 " + v4 + ".y, va0, vc" + (const4x4 + 1) + " \n" +
"dp4 " + v4 + ".z, va0, vc" + (const4x4 + 2) + " \n" +
"dp4 " + v4 + ".w, va0, vc" + (const4x4 + 3) + " \n";
return shader;
}
alternativa3d function attenuateFragmentShader(result4:String, v4:String, texture:uint, const0:uint):String {
// if (!shadow) {
// return "mov " + result4 + ", fc" + const0 + ".zzzz \n";
// }
if (debugShadowMaterial != null) {
return debugShadowMaterial.evaluateFragmentShaderSimple(result4, v4, const0, texture);
}
// Отображение шедоу мапы
// return "tex " + result4 + ", " + v4 + ", fs" + texture.toString() + " <2d,clamp,nearest> \n";
var shader:String =
"tex " + result4 + ", " + v4 + ", fs" + texture.toString() + " <2d," + ((useSingle) ? "single," : "") + "clamp,nearest> \n" +
"add " + result4 + ".z, " + result4 + ".z, fc" + const0.toString() + ".x \n" + // Смещение
"sub " + result4 + ".z, " + result4 + ".z, " + v4 + ".z \n" +
"mul " + result4 + ".z, " + result4 + ".z, fc" + const0.toString() + ".y \n" +
"sat " + result4 + ".z, " + result4 + ".z \n" +
"mov " + result4 + ", " + result4 + ".zzzz \n";
return shader;
}
alternativa3d function attenuateProgramSetup(context3d:Context3D, obj:Object3D, const4x4:uint, texture:uint, constF2:uint):void {
// if (!shadow) {
// context3d.setProgramConstants("FRAGMENT", constF2, 1, options);
// }
// trace("SET:", texture);
var lightMatrix:Matrix3D = obj.cameraMatrix.clone();
lightMatrix.append(this.lightMatrix);
context3d.setTexture(texture, shadowMap);
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, const4x4, lightMatrix, true);
if (debugShadowMaterial != null) {
debugShadowMaterial.evaluateProgramSetupSimple(context3d, constF2, texture);
} else {
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, constF2, 1, options);
}
}
alternativa3d function attenuateProgramClean(context3d:Context3D, texture:uint):void {
// trace("CLN:", texture);
// if (shadow && !createTextures) {
if (!createTextures) {
if (dummy == null) {
dummy = context3d.createTexture(1, 1, Context3DTextureFormat.BGRA, false);
}
context3d.setTexture(texture, dummy);
}
}
}
}

View File

@@ -0,0 +1,26 @@
package alternativa.engine3d.lights {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Object3D;
import __AS3__.vec.Vector;
use namespace alternativa3d;
public class OmniLight extends Object3D {
public var color:int;
public var radius:Number;
public var strength:Number;
alternativa3d var cameraCoords:Vector.<Number> = new Vector.<Number>(3);
public function OmniLight(color:int, radius:Number, strength:Number = 1) {
this.color = color;
this.radius = radius;
this.strength = strength;
}
}
}

View File

@@ -0,0 +1,121 @@
package alternativa.engine3d.loaders {
import __AS3__.vec.Vector;
import alternativa.engine3d.materials.CommonMaterial;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.materials.TextureMaterial;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.net.URLRequest;
import flash.system.LoaderContext;
public class CommonMaterialsLoader extends EventDispatcher {
private var originals:Vector.<Material>;
private var currentMaterialIndex:int;
private var currentComponentIndex:int;
private var loader:Loader;
private var context:LoaderContext;
public function CommonMaterialsLoader() {
}
public function load(materials:Vector.<Material>, context:LoaderContext = null):void {
this.originals = materials;
this.context = context;
currentMaterialIndex = 0;
currentComponentIndex = 0;
loadTexture();
}
private function loadTexture():void {
var material:Material = originals[currentMaterialIndex];
while (material != null) {
var url:String = null;
if (currentComponentIndex == 0) {
if (material is TextureMaterial) url = TextureMaterial(material).diffuseMapURL;
if (material is CommonMaterial) url = CommonMaterial(material).diffuseMapURL;
}
if (currentComponentIndex <= 1 && url == null) {
if (material is TextureMaterial) url = TextureMaterial(material).opacityMapURL;
if (material is CommonMaterial) url = CommonMaterial(material).opacityMapURL;
currentComponentIndex = 1;
}
if (currentComponentIndex <= 2 && url == null) {
if (material is CommonMaterial) url = CommonMaterial(material).normalMapURL;
currentComponentIndex = 2;
}
if (currentComponentIndex <= 3 && url == null) {
if (material is CommonMaterial) url = CommonMaterial(material).specularMapURL;
currentComponentIndex = 3;
}
if (currentComponentIndex <= 4 && url == null) {
if (material is CommonMaterial) url = CommonMaterial(material).emissionMapURL;
currentComponentIndex = 4;
}
if (url != null) {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTextureLoad);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
loader.load(new URLRequest(url));
return;
}
currentComponentIndex = 0;
currentMaterialIndex++;
material = (currentMaterialIndex < originals.length) ? originals[currentMaterialIndex] : null;
}
originals = null;
loader = null;
context = null;
dispatchEvent(new Event(Event.COMPLETE));
}
private function onTextureLoad(e:Event):void {
var original:Material = originals[currentMaterialIndex];
var tMaterial:TextureMaterial = original as TextureMaterial;
var cMaterial:CommonMaterial = original as CommonMaterial;
var bmd:BitmapData = Bitmap(loader.content).bitmapData;
switch (currentComponentIndex) {
case 0: // diffuse
if (tMaterial) {
tMaterial.texture = bmd;
}
if (cMaterial) {
cMaterial.diffuse = bmd;
}
break;
case 1: // opacity
if (cMaterial) {
cMaterial.opacity = bmd;
}
break;
case 2: // normals
cMaterial.normals = bmd;
break;
case 3: // specular
cMaterial.specular = bmd;
break;
case 4: // emission
cMaterial.emission = bmd;
break;
}
currentComponentIndex++;
loadTexture();
}
private function onError(e:Event):void {
currentComponentIndex++;
loadTexture();
dispatchEvent(e);
}
}
}

View File

@@ -0,0 +1,230 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.loaders.events.LoaderErrorEvent;
import alternativa.engine3d.loaders.events.LoaderEvent;
import alternativa.engine3d.loaders.events.LoaderProgressEvent;
import alternativa.engine3d.materials.TextureMaterial;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.Loader;
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.Matrix;
import flash.geom.Point;
import flash.net.URLRequest;
import flash.system.LoaderContext;
/**
* Рассылается вначале загрузки очередного материала.
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN
*/
[Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Рассылается после окончания этапа очередного материала.
* В событии в свойстве target содержится загруженный материал.
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE
*/
[Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Рассылается после окончания загрузки всех материалов.
* @eventType flash.events.Event.COMPLETE
*/
[Event (name="complete", type="flash.events.Event")]
/**
* Рассылается, если в процессе загрузки возникает ошибка.
* @eventType alternativa.engine3d.loaders.events.LoaderErrorEvent.LOADER_ERROR
*/
[Event (name="loaderError", type="alternativa.engine3d.loaders.events.LoaderErrorEvent")]
/**
* Загрузчик текстур материалов.
* @see alternativa.engine3d.materials.TextureMaterial
*/
public class MaterialLoader extends EventDispatcher {
static private var stub:BitmapData;
private var loader:Loader;
private var context:LoaderContext;
private var materials:Vector.<TextureMaterial>;
private var urls:Vector.<String>;
private var filesTotal:int;
private var filesLoaded:int;
private var diffuse:BitmapData;
private var currentURL:String;
private var index:int;
/**
* Начинает загрузку текстур материалов.
* @param materials Список объектов <code>TextureMaterial</code> для загрузки их текстур.
* @param context Объект <code>LoaderContext</code>.
* @see alternativa.engine3d.materials.TextureMaterial
*/
public function load(materials:Vector.<TextureMaterial>, context:LoaderContext = null):void {
this.context = context;
this.materials = materials;
urls = new Vector.<String>();
for (var i:int = 0, j:int = 0; i < materials.length; i++) {
var material:TextureMaterial = materials[i];
urls[j++] = material.diffuseMapURL;
filesTotal++;
if (material.opacityMapURL != null) {
urls[j++] = material.opacityMapURL;
filesTotal++;
} else {
urls[j++] = null;
}
}
filesLoaded = 0;
index = -1;
loadNext(null);
}
/**
* Останавливает загрузку и выполняет зачистку загрузчика.
*/
public function close():void {
destroyLoader();
materials = null;
urls = null;
diffuse = null;
currentURL = null;
context = null;
}
private function destroyLoader():void {
if (loader != null) {
loader.unload();
loader.contentLoaderInfo.removeEventListener(Event.OPEN, onPartOpen);
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadNext);
loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, onFileProgress);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.DISK_ERROR, loadNext);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, loadNext);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.NETWORK_ERROR, loadNext);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.VERIFY_ERROR, loadNext);
loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext);
loader = null;
}
}
private function loadNext(e:Event):void {
if (index >= 0) {
if (index % 2 == 0) {
// Завершение загрузки диффузии
if (e is ErrorEvent) {
diffuse = getStub();
onFileError((e as ErrorEvent).text);
} else {
diffuse = (loader.content as Bitmap).bitmapData;
}
filesLoaded++;
} else {
// Завершение загрузки альфы
var material:TextureMaterial = materials[(index - 1) >> 1];
if (e == null) {
material.texture = diffuse;
} else {
if (e is ErrorEvent) {
material.texture = diffuse;
onFileError((e as ErrorEvent).text);
} else {
material.texture = merge(diffuse, (loader.content as Bitmap).bitmapData);
}
filesLoaded++;
}
onPartComplete((index - 1) >> 1, material);
diffuse = null;
}
destroyLoader();
}
if (++index >= urls.length) {
// Завершение всей загрузки
close();
if (hasEventListener(Event.COMPLETE)) {
dispatchEvent(new Event(Event.COMPLETE));
}
} else {
// Загрузка следующего файла
currentURL = urls[index];
if (currentURL != null && (diffuse == null || diffuse != stub)) {
loader = new Loader();
if (index % 2 == 0) {
loader.contentLoaderInfo.addEventListener(Event.OPEN, onPartOpen);
}
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNext);
loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onFileProgress);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.DISK_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext);
loader.load(new URLRequest(currentURL), context);
} else {
loadNext(null);
}
}
}
private function onPartOpen(e:Event):void {
if (hasEventListener(LoaderEvent.PART_OPEN)) {
dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, urls.length >> 1, index >> 1, materials[index >> 1]));
}
}
private function onPartComplete(partsLoaded:int, material:TextureMaterial):void {
if (hasEventListener(LoaderEvent.PART_COMPLETE)) {
dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, urls.length >> 1, partsLoaded, material));
}
}
private function onFileProgress(e:ProgressEvent):void {
if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) {
dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, filesTotal, filesLoaded, (filesLoaded + e.bytesLoaded/e.bytesTotal)/filesTotal, e.bytesLoaded, e.bytesTotal));
}
}
private function onFileError(text:String):void {
dispatchEvent(new LoaderErrorEvent(LoaderErrorEvent.LOADER_ERROR, currentURL, text));
}
private function merge(diffuse:BitmapData, alpha:BitmapData):BitmapData {
var res:BitmapData = new BitmapData(diffuse.width, diffuse.height);
res.copyPixels(diffuse, diffuse.rect, new Point());
if (diffuse.width != alpha.width || diffuse.height != alpha.height) {
diffuse.draw(alpha, new Matrix(diffuse.width/alpha.width, 0, 0, diffuse.height/alpha.height), null, BlendMode.NORMAL, null, true);
alpha.dispose();
alpha = diffuse;
} else {
diffuse.dispose();
}
res.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
alpha.dispose();
return res;
}
private function getStub():BitmapData {
if (stub == null) {
var size:uint = 16;
stub = new BitmapData(size, size, false, 0);
for (var i:uint = 0; i < size; i++) {
for (var j:uint = 0; j < size; j += 2) {
stub.setPixel((i % 2) ? j : (j + 1), i, 0xFF00FF);
}
}
}
return stub;
}
}
}

View File

@@ -0,0 +1,529 @@
package alternativa.engine3d.loaders {
import flash.geom.Matrix;
import flash.geom.Vector3D;
import flash.utils.ByteArray;
import flash.utils.Endian;
public class Parser3DS {
private static const CHUNK_MAIN:int = 0x4D4D;
private static const CHUNK_VERSION:int = 0x0002;
private static const CHUNK_SCENE:int = 0x3D3D;
private static const CHUNK_ANIMATION:int = 0xB000;
private static const CHUNK_OBJECT:int = 0x4000;
private static const CHUNK_TRIMESH:int = 0x4100;
private static const CHUNK_VERTICES:int = 0x4110;
private static const CHUNK_FACES:int = 0x4120;
private static const CHUNK_FACESMATERIAL:int = 0x4130;
private static const CHUNK_MAPPINGCOORDS:int = 0x4140;
//private static const CHUNK_OBJECTCOLOR:int = 0x4165;
private static const CHUNK_TRANSFORMATION:int = 0x4160;
//private static const CHUNK_MESHANIMATION:int = 0xB002;
private static const CHUNK_MATERIAL:int = 0xAFFF;
private var data:ByteArray;
private var objectDatas:Object;
private var animationDatas:Array;
private var materialDatas:Object;
public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1):void {
if (data.bytesAvailable < 6) return;
this.data = data;
data.endian = Endian.LITTLE_ENDIAN;
parse3DSChunk(data.position, data.bytesAvailable);
data = null;
//objectDatas = null;
//animationDatas = null;
//materialDatas = null;
}
private function readChunkInfo(dataPosition:int):ChunkInfo {
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:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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:int, bytesAvailable:int):void {
while (bytesAvailable >= 6) {
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
switch (chunkInfo.id) {
// Материал
case CHUNK_MATERIAL:
// Парсим материал
var material:MaterialData = new MaterialData();
parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize);
break;
// Объект
case CHUNK_OBJECT:
parseObject(chunkInfo);
break;
}
dataPosition = chunkInfo.nextChunkPosition;
bytesAvailable -= chunkInfo.size;
}
}
private function parseObject(chunkInfo:ChunkInfo):void {
// Создаём список объектов, если надо
if (objectDatas == null) {
objectDatas = new Object();
}
// Создаём данные объекта
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:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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:int = data.readUnsignedShort();
object.vertices = new Vector.<Number>();
for (var i:int = 0, j:int = 0; i < num; i++) {
object.vertices[j++] = data.readFloat();
object.vertices[j++] = data.readFloat();
object.vertices[j++] = data.readFloat();
}
}
private function parseUVs(object:ObjectData):void {
var num:int = data.readUnsignedShort();
object.uvs = new Vector.<Number>();
for (var i:int = 0, j:int = 0; i < num; i++) {
object.uvs[j++] = data.readFloat();
object.uvs[j++] = 1 - data.readFloat();
}
}
private function parseMatrix(object:ObjectData):void {
object.a = data.readFloat();
object.e = data.readFloat();
object.i = data.readFloat();
object.b = data.readFloat();
object.f = data.readFloat();
object.j = data.readFloat();
object.c = data.readFloat();
object.g = data.readFloat();
object.k = data.readFloat();
object.d = data.readFloat();
object.h = data.readFloat();
object.l = data.readFloat();
}
private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void {
var num:int = data.readUnsignedShort();
object.faces = new Vector.<uint>();
for (var i:int = 0, j:int = 0; i < num; i++) {
object.faces[j++] = data.readUnsignedShort();
object.faces[j++] = data.readUnsignedShort();
object.faces[j++] = data.readUnsignedShort();
data.position += 2; // Пропускаем флаг отрисовки рёбер
}
var offset:int = 2 + 8*num;
parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset);
}
private function parseFacesChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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 Object();
}
// Создаём данные поверхности
var surface:Vector.<int> = new Vector.<int>;
// Помещаем данные поверхности в список
object.surfaces[getString(data.position)] = surface;
// Получаем грани поверхности
var num:int = data.readUnsignedShort();
for (var i:int = 0; i < num; i++) {
surface[i] = data.readUnsignedShort();
}
}
private function parseAnimationChunk(dataPosition:int, bytesAvailable:int):void {
while (bytesAvailable >= 6) {
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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;
}
dataPosition = chunkInfo.nextChunkPosition;
bytesAvailable -= chunkInfo.size;
}
}
private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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 Vector3D(data.readFloat(), data.readFloat(), data.readFloat());
break;
// Смещение объекта относительно родителя
case 0xB020:
data.position += 20;
animation.position = new Vector3D(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 Vector3D(data.readFloat(), data.readFloat(), data.readFloat());
break;
}
parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
}
private function parseMaterialChunk(material:MaterialData, dataPosition:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
switch (chunkInfo.id) {
// Имя материала
case 0xA000:
parseMaterialName(material);
break;
// Ambient color
case 0xA010:
break;
// Diffuse color
case 0xA020:
data.position = chunkInfo.dataPosition + 6;
material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + 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);
break;
// Texture map 2
case 0xA33A:
break;
// Opacity map
case 0xA210:
material.opacityMap = new MapData();
parseMapChunk(material.name, material.opacityMap, chunkInfo.dataPosition, chunkInfo.dataSize);
break;
// Bump map
case 0xA230:
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 parseMaterialName(material:MaterialData):void {
// Создаём список материалов, если надо
if (materialDatas == null) {
materialDatas = new Object();
}
// Получаем название материала
material.name = getString(data.position);
// Помещаем данные материала в список
materialDatas[material.name] = material;
}
private function parseMapChunk(materialName:String, map:MapData, dataPosition:int, bytesAvailable:int):void {
if (bytesAvailable < 6) return;
var chunkInfo:ChunkInfo = readChunkInfo(dataPosition);
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 getString(index:int):String {
data.position = index;
var charCode:int;
var res:String = "";
while ((charCode = data.readByte()) != 0) {
res += String.fromCharCode(charCode);
}
return res;
}
private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D {
var res:Vector3D = new Vector3D();
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;
}
public function getVertices(objectName:String):Vector.<Number> {
return ObjectData(objectDatas[objectName]).vertices;
}
public function getIndices(objectName:String):Vector.<uint> {
return ObjectData(objectDatas[objectName]).faces;
}
public function getUVs(objectName:String):Vector.<Number> {
return ObjectData(objectDatas[objectName]).uvs;
}
}
}
import flash.geom.Matrix;
import flash.geom.Vector3D;
class MaterialData {
public var name:String;
public var color:int;
public var specular:int;
public var glossiness:int;
public var transparency:int;
public var diffuseMap:MapData;
public var opacityMap: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:Vector.<Number>;
public var uvs:Vector.<Number>;
public var faces:Vector.<uint>;
public var surfaces:Object;
public var a:Number;
public var b:Number;
public var c:Number;
public var d:Number;
public var e:Number;
public var f:Number;
public var g:Number;
public var h:Number;
public var i:Number;
public var j:Number;
public var k:Number;
public var l:Number;
}
class AnimationData {
public var objectName:String;
public var parentIndex:int;
public var pivot:Vector3D;
public var position:Vector3D;
public var rotation:Vector3D;
public var scale:Vector3D;
public var isInstance:Boolean;
}
class ChunkInfo {
public var id:int;
public var size:int;
public var dataSize:int;
public var dataPosition:int;
public var nextChunkPosition:int;
}

View File

@@ -0,0 +1,466 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.animation.Animation;
import alternativa.engine3d.animation.AnimationGroup;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Object3DContainer;
import alternativa.engine3d.loaders.collada.DaeDocument;
import alternativa.engine3d.loaders.collada.DaeMaterial;
import alternativa.engine3d.loaders.collada.DaeNode;
import alternativa.engine3d.loaders.collada.DaeObject;
import alternativa.engine3d.materials.CommonMaterial;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.materials.TextureMaterial;
import flash.utils.Dictionary;
/**
* Класс выполняет парсинг xml коллады.
*/
public class ParserCollada {
/**
* Список объектов.
*/
public var objects:Vector.<Object3D>;
/**
* Список родителей объектов. Количество и порядок расположения элементов соответствует массиву objects.
*
* @see #objects
*/
public var parents:Vector.<Object3D>;
/**
* Список корневых (без родителей) объектов.
*/
public var hierarchy:Vector.<Object3D>;
/**
* Список всех материалов.
*
* @see #textureMaterials
*/
public var materials:Vector.<Material>;
/**
* Список текстурных материалов.
* Можно использовать класс MaterialLoader для загрузки текстур этих материалов.
*
* @see #materials
* @see MaterialLoader
*/
public var textureMaterials:Vector.<TextureMaterial>;
/**
* Массив анимаций.
*/
public var animations:Vector.<Animation>;
/**
* Объект -> слой
*/
private var layers:Dictionary;
/**
* Создает экземпляр парсера
*/
public function ParserCollada() {
}
/**
* Зачищает все ссылки на внешние объекты.
*/
public function clean():void {
objects = null;
parents = null;
hierarchy = null;
animations = null;
materials = null;
textureMaterials = null;
layers = null;
}
/**
* Возвращает слой объекта.
* @param object Объект.
* @return Слой.
*/
public function getObjectLayer(object:Object3D):String {
return layers[object];
}
/**
* Инициализация перед парсингом.
*/
private function init(data:XML):DaeDocument {
clean();
objects = new Vector.<Object3D>();
parents = new Vector.<Object3D>();
hierarchy = new Vector.<Object3D>();
animations = new Vector.<Animation>();
materials = new Vector.<Material>();
textureMaterials = new Vector.<TextureMaterial>();
layers = new Dictionary();
return new DaeDocument(data);
}
/**
* Метод распарсивает <code>xml</code> коллады и заполняет массивы <code>objects</code>, <code>parents</code>, <code>hierarchy</code>, <code>cameras</code>, <code>materials</code>, <code>textureMaterials</code>, <code>animations</code>.
* Для загрузки текстур сцены можно использовать класс <code>MaterialLoader</code>.
* После парсинга для объектов, которые содержат детей, но не являются контейнерами, будут созданы контейнеры с аналогичными именами.
*
* @param data <code>xml</code> содержимое коллады
* @param baseURL адрес относительно которого выполняется поиск текстур. Путь к файлу коллады.
* Должен соответствовать спецификации <code>URI</code>. Например, <code>file:///C:/test.dae</code> или <code>/C:/test.dae</code> для полных путей или <code>test.dae</code>, <code>./test.dae</code> для относительных.
* @param skipEmptyObjects при значении <code>true</code> объекты без геометрии или детей не будут создаваться.
*
* @see alternativa.engine3d.loaders.MaterialLoader
* @see #objects
* @see #parents
* @see #hierarchy
* @see #cameras
* @see #materials
* @see #textureMaterials
*/
public function parse(data:XML, baseURL:String = null):void {
var document:DaeDocument = init(data);
if (document.scene != null) {
parseNodes(document.scene.nodes, null, false);
parseMaterials(document.materials, baseURL);
}
}
/**
* Проверяет есть ли в иерархии объекты с заданными параметрами
*
* @param skipEmptyObjects Если установлен в <code>true</code>, будет пропускать ноды без объектов
* @param skinsOnly Если установлен в <code>true</code>, будут пропускаться все ноды кроме тех, что содержат скин.
*/
private function hasSignifiantChildren(node:DaeNode, skipEmptyObjects:Boolean, skinsOnly:Boolean):Boolean {
var nodes:Vector.<DaeNode> = node.nodes;
for (var i:int = 0, count:int = nodes.length; i < count; i++) {
var child:DaeNode = nodes[i];
child.parse();
if (child.skins != null) {
return true;
} else {
if (!skinsOnly && !node.skinOrRootJoint && (!skipEmptyObjects || child.objects != null)) {
return true;
}
}
if (hasSignifiantChildren(child, skipEmptyObjects, skinsOnly)) {
return true;
}
}
return false;
}
/**
* Добавляет компоненты анимированного объекта в списки objects, parents, hierarchy, cameras, animations и в родительский контейнер.
*/
private function addObject(animatedObject:DaeObject, parent:Object3D, layer:String):Object3D {
var object:Object3D = animatedObject.object as Object3D;
this.objects.push(object);
this.parents.push(parent);
if (parent == null) {
this.hierarchy.push(object);
}
var container:Object3DContainer = parent as Object3DContainer;
if (container != null) {
container.addChild(object);
// var lod:LODContainer = container as LODContainer;
// if (lod != null) {
// var distance:Number = animatedObject.lodDistance;
// if (distance != 0) {
// lod.setChildDistance(object, distance);
// }
// }
}
if (animatedObject.animation != null) {
animatedObject.animation.updateLength();
this.animations.push(animatedObject.animation);
}
if (layer) {
layers[object] = layer;
}
return object;
}
/**
* Добавляет объекты в списки objects, parents, hierarchy, cameras, animations и в родительский контейнер.
*
* @return первый объект
*/
private function addObjects(animatedObjects:Vector.<DaeObject>, parent:Object3D, layer:String):Object3D {
var first:Object3D = addObject(animatedObjects[0], parent, layer);
for (var i:int = 1, count:int = animatedObjects.length; i < count; i++) {
addObject(animatedObjects[i], parent, layer);
}
return first;
}
private function parseNodes(nodes:Vector.<DaeNode>, parent:Object3DContainer, skipEmptyObjects:Boolean, skinsOnly:Boolean = false):void {
for (var i:int = 0, count:int = nodes.length; i < count; i++) {
var node:DaeNode = nodes[i];
node.parse();
var container:Object3DContainer = null;
var isEmpty:Boolean = false;
if (node.skins != null) {
// Основная кость скина
addObjects(node.skins, parent, node.layer);
} else {
if (!node.skinOrRootJoint && !skinsOnly) {
if (node.objects != null) {
container = addObjects(node.objects, parent, node.layer) as Object3DContainer;
} else {
isEmpty = true;
}
} // else нода используется в качестве кости в активном скине или является описателем скина
}
// Парсим детей
// Если это кость или скин, парсим только скины у детей
skinsOnly = skinsOnly || node.skinOrRootJoint;
if (container == null) {
if (hasSignifiantChildren(node, skipEmptyObjects, skinsOnly)) {
container = new Object3DContainer();
container.name = node.name;
addObject(node.applyAnimation(node.applyTransformations(container)), parent, node.layer);
parseNodes(node.nodes, container, skipEmptyObjects, skinsOnly);
container.calculateBounds();
} else {
if (isEmpty && !skipEmptyObjects) {
var object:Object3D = new Object3D();
object.name = node.name;
addObject(node.applyAnimation(node.applyTransformations(object)), parent, node.layer);
}
}
} else {
parseNodes(node.nodes, container, skipEmptyObjects, skinsOnly);
container.calculateBounds();
}
}
}
private function parseMaterials(materials:Object, baseURL:String):void {
var tmaterial:TextureMaterial;
var commonMaterial:CommonMaterial;
var mat:Material;
for each (var material:DaeMaterial in materials) {
if (material.used) {
material.parse();
this.materials.push(material.material);
tmaterial = material.material as TextureMaterial;
if (tmaterial != null) {
textureMaterials.push(tmaterial);
}
}
}
if (baseURL != null) {
baseURL = fixURL(baseURL);
var end:int = baseURL.lastIndexOf("/");
var base:String = (end < 0) ? "" : baseURL.substring(0, end + 1);
for each (tmaterial in textureMaterials) {
if (tmaterial.diffuseMapURL != null) {
tmaterial.diffuseMapURL = resolveURL(fixURL(tmaterial.diffuseMapURL), base);
}
if (tmaterial.opacityMapURL != null) {
tmaterial.opacityMapURL = resolveURL(fixURL(tmaterial.opacityMapURL), base);
}
}
for each (mat in this.materials) {
commonMaterial = mat as CommonMaterial;
if (commonMaterial != null) {
if (commonMaterial.normalMapURL != null) {
commonMaterial.normalMapURL = resolveURL(fixURL(commonMaterial.normalMapURL), base);
}
if (commonMaterial.specularMapURL != null) {
commonMaterial.specularMapURL = resolveURL(fixURL(commonMaterial.specularMapURL), base);
}
if (commonMaterial.emissionMapURL != null) {
commonMaterial.emissionMapURL = resolveURL(fixURL(commonMaterial.emissionMapURL), base);
}
}
}
} else {
for each (tmaterial in textureMaterials) {
if (tmaterial.diffuseMapURL != null) {
tmaterial.diffuseMapURL = fixURL(tmaterial.diffuseMapURL);
}
if (tmaterial.opacityMapURL != null) {
tmaterial.opacityMapURL = fixURL(tmaterial.opacityMapURL);
}
}
for each (mat in this.materials) {
commonMaterial = mat as CommonMaterial;
if (commonMaterial != null) {
if (commonMaterial.normalMapURL != null) {
commonMaterial.normalMapURL = fixURL(commonMaterial.normalMapURL);
}
if (commonMaterial.specularMapURL != null) {
commonMaterial.specularMapURL = fixURL(commonMaterial.specularMapURL);
}
if (commonMaterial.emissionMapURL != null) {
commonMaterial.emissionMapURL = fixURL(commonMaterial.emissionMapURL);
}
}
}
}
}
/**
* @private
* Приводит урл к следующему виду:
* Обратные слеши в пути заменяет на прямые
* Три прямых слеша после схемы file:
*/
private function fixURL(url:String):String {
var pathStart:int = url.indexOf("://");
pathStart = (pathStart < 0) ? 0 : pathStart + 3;
var pathEnd:int = url.indexOf("?", pathStart);
pathEnd = (pathEnd < 0) ? url.indexOf("#", pathStart) : pathEnd;
var path:String = url.substring(pathStart, (pathEnd < 0) ? 0x7FFFFFFF : pathEnd);
path = path.replace(/\\/g, "/");
var fileIndex:int = url.indexOf("file://");
if (fileIndex >= 0) {
if (url.charAt(pathStart) == "/") {
return "file://" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : "");
}
return "file:///" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : "");
}
return url.substring(0, pathStart) + path + ((pathEnd >= 0) ? url.substring(pathEnd) : "");
}
// public function resolveTest(url:String, base:String):void {
// trace('was::"' + base + '" "' + url + '"');
// trace('now::"' + resolveURL(fixURL(url), fixURL(base)) + '"');
// }
/**
* @private
*/
private function mergePath(path:String, base:String, relative:Boolean = false):String {
var baseParts:Array = base.split("/");
var parts:Array = path.split("/");
for (var i:int = 0, count:int = parts.length; i < count; i++) {
var part:String = parts[i];
if (part == "..") {
var basePart:String = baseParts.pop();
while (basePart == "." || basePart == "" && basePart != null) basePart = baseParts.pop();
if (relative) {
if (basePart == "..") {
baseParts.push("..", "..");
} else if (basePart == null) {
baseParts.push("..");
}
}
} else {
baseParts.push(part);
}
}
return baseParts.join("/");
}
/**
* @private
* Конвертирует относительные пути в полные
*/
private function resolveURL(url:String, base:String):String {
// http://labs.apache.org/webarch/uri/rfc/rfc3986.html
if (url.charAt(0) == "." && url.charAt(1) == "/") {
// Файл в той же папке
return base + url.substring(2);
} else if (url.charAt(0) == "/") {
// Полный путь
return url;
} else if (url.charAt(0) == "." && url.charAt(1) == ".") {
// Выше по уровню
var queryAndFragmentIndex:int = url.indexOf("?");
queryAndFragmentIndex = (queryAndFragmentIndex < 0) ? url.indexOf("#") : queryAndFragmentIndex;
var path:String;
var queryAndFragment:String;
if (queryAndFragmentIndex < 0) {
queryAndFragment = "";
path = url;
} else {
queryAndFragment = url.substring(queryAndFragmentIndex);
path = url.substring(0, queryAndFragmentIndex);
}
// Делим базовый урл на составные части
var bPath:String;
var bSlashIndex:int = base.indexOf("/");
var bShemeIndex:int = base.indexOf(":");
var bAuthorityIndex:int = base.indexOf("//");
if (bAuthorityIndex < 0 || bAuthorityIndex > bSlashIndex) {
if (bShemeIndex >= 0 && bShemeIndex < bSlashIndex) {
// Присутствует схема, нет домена
var bSheme:String = base.substring(0, bShemeIndex + 1);
bPath = base.substring(bShemeIndex + 1);
if (bPath.charAt(0) == "/") {
return bSheme + "/" + mergePath(path, bPath.substring(1), false) + queryAndFragment;
} else {
return bSheme + mergePath(path, bPath, false) + queryAndFragment;
}
} else {
// Нет схемы, нет домена
if (base.charAt(0) == "/") {
return "/" + mergePath(path, base.substring(1), false) + queryAndFragment;
} else {
return mergePath(path, base, true) + queryAndFragment;
}
}
} else {
bSlashIndex = base.indexOf("/", bAuthorityIndex + 2);
var bAuthority:String;
if (bSlashIndex >= 0) {
bAuthority = base.substring(0, bSlashIndex + 1);
bPath = base.substring(bSlashIndex + 1);
return bAuthority + mergePath(path, bPath, false) + queryAndFragment;
} else {
bAuthority = base;
return bAuthority + "/" + mergePath(path, "", false);
}
}
}
var shemeIndex:int = url.indexOf(":");
var slashIndex:int = url.indexOf("/");
if (shemeIndex >= 0 && (shemeIndex < slashIndex || slashIndex < 0)) {
// Содержит схему
return url;
}
return base + "/" + url;
}
/**
* Возвращает объект из массива object по его имени.
*/
public function getObjectByName(name:String):Object3D {
for (var i:int = 0, count:int = objects.length; i < count; i++) {
var object:Object3D = objects[i];
if (object.name == name) {
return object;
}
}
return null;
}
/**
* Возвращает анимацию из массива animation по объекту на который она ссылается.
*/
public function getAnimationByObject(object:Object3D):Animation {
for (var i:int = 0, count:int = animations.length; i < count; i++) {
var animation:Animation = animations[i];
if (animation.object == object) {
return animation;
}
var group:AnimationGroup = animation as AnimationGroup;
if (group != null) {
for (var j:int = 0, numChildren:int = group.numAnimations; j < numChildren; j++) {
var child:Animation = group.getAnimationAt(j);
if (child.object == object) {
return child;
}
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,167 @@
package alternativa.engine3d.loaders {
import __AS3__.vec.Vector;
import alternativa.engine3d.loaders.events.TexturesLoaderEvent;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display3D.Context3D;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Texture3D;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.filters.ConvolutionFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
public class TexturesLoader extends EventDispatcher{
private var textures3D:Object = new Object();
private var bitmapDatas:Object = new Object();
private var currentBitmapDatas:Vector.<BitmapData>;
private var currentTextures3D:Vector.<Texture3D>;
private var currentUrl:String;
private var loader:Loader;
private var urlList:Vector.<String>;
private var counter:int = 0;
private var context3D:Context3D;
public function TexturesLoader() {
}
public function loadTexture(url:String, context3D:Context3D = null):void {
urlList = Vector.<String>([url]);
this.context3D = context3D;
currentBitmapDatas = new Vector.<BitmapData>(1);
currentTextures3D = new Vector.<Texture3D>(1);
loadNext();
}
public function loadTextures(urls:Vector.<String>, context3D:Context3D = null):void {
urlList = urls;
this.context3D = context3D;
currentBitmapDatas = new Vector.<BitmapData>(urlList.length);
currentTextures3D = new Vector.<Texture3D>(urlList.length);
loadNext();
}
public function getTexture3D(url:String):Texture3D {
return textures3D[url];
}
private function loadNext(e:Event = null):void {
var bitmapData:BitmapData;
var texture3D:Texture3D;
if (e != null && !(e is ErrorEvent)) {
bitmapData = e.target.content.bitmapData;
bitmapDatas[currentUrl] = bitmapData;
currentBitmapDatas[counter - 1] = bitmapData;
if (context3D) {
texture3D = createTexture(bitmapData);
currentTextures3D[counter - 1] = texture3D;
}
} else if (e is ErrorEvent) {
trace("Missing: " + currentUrl);
}
if (counter < urlList.length) {
currentUrl = urlList[counter++];
bitmapData = bitmapDatas[currentUrl];
if (bitmapData) {
currentBitmapDatas[counter - 1] = bitmapData;
if (context3D) {
texture3D = textures3D[currentUrl];
if (texture3D) {
currentTextures3D[counter - 1] = texture3D;
} else {
texture3D = createTexture(bitmapData);
currentTextures3D.push(texture3D);
}
}
loadNext();
} else {
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.DISK_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, loadNext);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext);
loader.load(new URLRequest(currentUrl));
}
} else {
onTexturesLoad();
}
}
private function onTexturesLoad():void {
counter = 0;
dispatchEvent(new TexturesLoaderEvent(Event.COMPLETE, currentBitmapDatas, currentTextures3D));
}
public function clean():void {
textures3D = new Object();
bitmapDatas = new Object();
currentBitmapDatas = null;
currentTextures3D = null;
}
private function createTexture(value:BitmapData):Texture3D {
if (value == null) return null;
var texture:Texture3D = context3D.createTexture(value.width, value.height, Context3DTextureFormat.BGRA, false);
uploadMipMaps(texture, value.clone());
textures3D[currentUrl] = texture;
return texture;
}
protected function uploadMipMaps(texture:Texture3D, bmp:BitmapData):void {
var level:int = 0;
texture.upload(bmp, level++);
if (bmp.width % 2 > 0 || bmp.height % 2 > 0) return;
var filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true);
filter.preserveAlpha = false;
var m:Matrix = new Matrix(0.5, 0, 0, 0.5);
var rect:Rectangle = new Rectangle();
var point:Point = new Point();
var current:BitmapData = bmp;
rect.width = bmp.width;
rect.height = bmp.height;
do {
bmp.applyFilter(current, rect, point, filter);
if (rect.width > 1) {
rect.width >>= 1;
}
if (rect.height > 1) {
rect.height >>= 1;
}
current = new BitmapData(rect.width, rect.height, false, 0);
current.draw(bmp, m, null, null, null, false);
texture.upload(current, level++);
} while (rect.width != 1 || rect.height != 1);
// current.fillRect(rect, 0xffc000);
// trace("S");
// rect.width >>= 1;
// rect.height >>= 1;
// texture.upload(current, level++);
// }
bmp.dispose();
current.dispose();
}
}
}

View File

@@ -0,0 +1,41 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeArray extends DaeElement {
use namespace collada;
/**
* Массив значений типа String.
* Перед использованием вызвать parse().
*/
public var array:Array;
public function DaeArray(data:XML, document:DaeDocument) {
super(data, document);
}
public function get type():String {
return String(data.localName());
}
override protected function parseImplementation():Boolean {
array = parseStringArray(data);
var countXML:XML = data.@count[0];
if (countXML != null) {
var count:int = parseInt(countXML.toString(), 10);
if (array.length < count) {
document.logger.logNotEnoughDataError(data.@count[0]);
return false;
} else {
array.length = count;
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,189 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.animation.Track;
import alternativa.engine3d.animation.keys.Key;
import alternativa.engine3d.animation.keys.ValueKey;
/**
* @private
*/
public class DaeChannel extends DaeElement {
static public const PARAM_UNDEFINED:int = -1;
static public const PARAM_TRANSLATE_X:int = 0;
static public const PARAM_TRANSLATE_Y:int = 1;
static public const PARAM_TRANSLATE_Z:int = 2;
static public const PARAM_SCALE_X:int = 3;
static public const PARAM_SCALE_Y:int = 4;
static public const PARAM_SCALE_Z:int = 5;
static public const PARAM_ROTATION_X:int = 6;
static public const PARAM_ROTATION_Y:int = 7;
static public const PARAM_ROTATION_Z:int = 8;
static public const PARAM_TRANSLATE:int = 9;
static public const PARAM_SCALE:int = 10;
static public const PARAM_MATRIX:int = 11;
/**
* Анимационный трек с ключами.
* Перед использованием вызвать parse().
*/
public var track:Track;
/**
* Тип анимированного параметра, принимает одно из значений DaeChannel.PARAM_*.
* Перед использованием вызвать parse().
*/
public var animatedParam:int = PARAM_UNDEFINED;
public function DaeChannel(data:XML, document:DaeDocument) {
super(data, document);
}
/**
* Возвращает ноду которой предназначена анимация.
*/
public function get node():DaeNode {
var targetXML:XML = data.@target[0];
if (targetXML != null) {
var targetParts:Array = targetXML.toString().split("/");
// Первая часть это id элемента
var node:DaeNode = document.findNodeByID(targetParts[0]);
if (node != null) {
// Последняя часть это трансформируемый элемент
targetParts.pop();
for (var i:int = 1, count:int = targetParts.length; i < count; i++) {
var sid:String = targetParts[i];
node = node.getNodeBySid(sid);
if (node == null) {
return null;
}
}
return node;
}
}
return null;
}
override protected function parseImplementation():Boolean {
parseTransformationType();
parseSampler();
return true;
}
private function parseTransformationType():void {
var targetXML:XML = data.@target[0];
if (targetXML == null) return;
// Разбиваем путь на части
var targetParts:Array = targetXML.toString().split("/");
var sid:String = targetParts.pop();
var sidParts:Array = sid.split(".");
var sidPartsCount:int = sidParts.length;
// Определяем тип свойства
var transformationXML:XML;
var node:DaeNode = this.node;
if (node == null) {
return;
}
var children:XMLList = node.data.children();
for (var i:int = 0, count:int = children.length(); i < count; i++) {
var child:XML = children[i];
var attr:XML = child.@sid[0];
if (attr != null && attr.toString() == sidParts[0]) {
transformationXML = child;
break;
}
}
// TODO:: вариант со скобками на всякий случай
var transformationName:String = (transformationXML != null) ? transformationXML.localName() as String : null;
if (sidPartsCount > 1) {
var componentName:String = sidParts[1];
switch (transformationName) {
case "translate":
switch (componentName) {
case "X":
animatedParam = PARAM_TRANSLATE_X;
break;
case "Y":
animatedParam = PARAM_TRANSLATE_Y;
break;
case "Z":
animatedParam = PARAM_TRANSLATE_Z;
break;
}
break;
case "rotate": {
var axis:Array = parseNumbersArray(transformationXML);
// TODO:: искать максимальное значение, а не единицу
switch (axis.indexOf(1)) {
case 0:
animatedParam = PARAM_ROTATION_X;
break;
case 1:
animatedParam = PARAM_ROTATION_Y;
break;
case 2:
animatedParam = PARAM_ROTATION_Z;
break;
}
break;
}
case "scale":
switch (componentName) {
case "X":
animatedParam = PARAM_SCALE_X;
break;
case "Y":
animatedParam = PARAM_SCALE_Y;
break;
case "Z":
animatedParam = PARAM_SCALE_Z;
break;
}
break;
}
} else {
switch (transformationName) {
case "translate":
animatedParam = PARAM_TRANSLATE;
break;
case "scale":
animatedParam = PARAM_SCALE;
break;
case "matrix":
animatedParam = PARAM_MATRIX;
break;
}
}
}
private function parseSampler():void {
var sampler:DaeSampler = document.findSampler(data.@source[0]);
if (sampler != null) {
sampler.parse();
if (animatedParam == PARAM_MATRIX) {
track = sampler.parseMatrixTrack();
return;
}
if (animatedParam == PARAM_TRANSLATE || animatedParam == PARAM_SCALE) {
track = sampler.parsePointsTrack();
return;
}
track = sampler.parseValuesTrack();
if (animatedParam == PARAM_ROTATION_X || animatedParam == PARAM_ROTATION_Y || animatedParam == PARAM_ROTATION_Z) {
// Переводим углы в радианы
var toRad:Number = Math.PI/180;
for (var key:Key = track.keyList; key != null; key = key.next) {
var valueKey:ValueKey = ValueKey(key);
valueKey.value *= toRad;
}
}
} else {
document.logger.logNotFoundError(data.@source[0]);
}
}
}
}

View File

@@ -0,0 +1,412 @@
package alternativa.engine3d.loaders.collada {
import __AS3__.vec.Vector;
import alternativa.engine3d.*;
import alternativa.engine3d.animation.Animation;
import alternativa.engine3d.animation.AnimationGroup;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.objects.Joint;
import alternativa.engine3d.objects.Skin;
import flash.geom.Matrix3D;
import flash.utils.Dictionary;
/**
* @private
*/
public class DaeController extends DaeElement {
use namespace collada;
use namespace alternativa3d;
private var jointsBindMatrices:Vector.<Vector.<Number> >;
private var vcounts:Array;
private var indices:Array;
private var jointsInput:DaeInput;
private var weightsInput:DaeInput;
private var inputsStride:int;
public function DaeController(data:XML, document:DaeDocument) {
super(data, document);
// sources мы создаем внутри DaeDocument, здесь не нужно.
}
override protected function parseImplementation():Boolean {
var vertexWeightsXML:XML = data.skin.vertex_weights[0];
if (vertexWeightsXML == null) {
return false;
}
var vcountsXML:XML = vertexWeightsXML.vcount[0];
if (vcountsXML == null) {
return false;
}
vcounts = parseIntsArray(vcountsXML);
var indicesXML:XML = vertexWeightsXML.v[0];
if (indicesXML == null) {
return false;
}
indices = parseIntsArray(indicesXML);
parseInputs();
parseJointsBindMatrices();
return true;
}
private function parseInputs():void {
var inputsList:XMLList = data.skin.vertex_weights.input;
var maxInputOffset:int = 0;
for (var i:int = 0, count:int = inputsList.length(); i < count; i++) {
var input:DaeInput = new DaeInput(inputsList[i], document);
var semantic:String = input.semantic;
if (semantic != null) {
switch (semantic) {
case "JOINT" :
if (jointsInput == null) {
jointsInput = input;
}
break;
case "WEIGHT" :
if (weightsInput == null) {
weightsInput = input;
}
break;
}
}
var offset:int = input.offset;
maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset;
}
inputsStride = maxInputOffset + 1;
}
/**
* Парсит инверсные матрицы для костей и сохраняет их в вектор.
*/
private function parseJointsBindMatrices():void {
var jointsXML:XML = data.skin.joints.input.(@semantic == "INV_BIND_MATRIX")[0];
if (jointsXML != null) {
var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]);
if (jointsSource != null) {
if (jointsSource.parse() && jointsSource.numbers != null && jointsSource.stride >= 16) {
var stride:int = jointsSource.stride;
var count:int = jointsSource.numbers.length/stride;
jointsBindMatrices = new Vector.<Vector.<Number> >(count);
for (var i:int = 0; i < count; i++) {
var index:int = stride*i;
var matrix:Vector.<Number> = new Vector.<Number>(16);
jointsBindMatrices[i] = matrix;
for (var j:int = 0; j < 16; j++) {
matrix[j] = jointsSource.numbers[int(index + j)];
}
}
}
} else {
document.logger.logNotFoundError(jointsXML.@source[0]);
}
}
}
private function get geometry():DaeGeometry {
var geom:DaeGeometry = document.findGeometry(data.skin.@source[0]);
if (geom == null) {
document.logger.logNotFoundError(data.@source[0]);
}
return geom;
}
/**
* Возвращает геометрию с костями и контроллер для костей.
* Перед использованием вызвать parse().
*/
public function parseSkin(materials:Object, topmostJoints:Vector.<DaeNode>, skeletons:Vector.<DaeNode>):DaeObject {
var skinXML:XML = data.skin[0];
if (skinXML != null) {
var skin:Skin = new Skin();
var geom:DaeGeometry = geometry;
if (geom != null) {
geom.parse();
var skinGeometry:Geometry = new Geometry();
var vertices:Vector.<Vertex> = geom.fillInMesh(skinGeometry, materials);
applyBindShapeMatrix(skinGeometry);
var joints:Vector.<DaeObject> = addJointsToSkin(skin, topmostJoints, findNodes(skeletons));
setJointsBindMatrices(joints);
linkVerticesToJoints(joints, vertices);
geom.cleanVertices(skinGeometry);
skin.geometry = skinGeometry;
skin.renderedJoints = collectRenderedJoints(joints, jointsBindMatrices.length);
skin.material = geom.getAnyMaterial(materials);
skin.calculateBounds();
return new DaeObject(skin, mergeJointsAnimations(skin, joints));
} else {
return new DaeObject(skin);
}
}
return null;
}
private function collectRenderedJoints(joints:Vector.<DaeObject>, numJoints:int):Vector.<Joint> {
var result:Vector.<Joint> = new Vector.<Joint>();
for (var i:int = 0; i < numJoints; i++) {
result[i] = Joint(joints[i].object);
}
return result;
}
/**
* Объединяет анимацию костей в одну анимацию, если требуется
*/
private function mergeJointsAnimations(skin:Skin, joints:Vector.<DaeObject>):Animation {
var complex:AnimationGroup = new AnimationGroup(skin);
for (var i:int = 0, count:int = joints.length; i < count; i++) {
var animatedObject:DaeObject = joints[i];
if (animatedObject.animation != null) {
complex.addAnimation(animatedObject.animation);
}
}
return (complex.numAnimations > 0) ? complex : null;
}
/**
* Задает костям их инверсные матрицы.
*/
private function setJointsBindMatrices(animatedJoints:Vector.<DaeObject>):void {
for (var i:int = 0, count:int = jointsBindMatrices.length; i < count; i++) {
var animatedJoint:DaeObject = animatedJoints[i];
var bindMatrix:Vector.<Number> = jointsBindMatrices[i];
Joint(animatedJoint.object).setBindingMatrix(new Matrix3D(Vector.<Number>([bindMatrix[0], bindMatrix[4], bindMatrix[8], bindMatrix[12],
bindMatrix[1], bindMatrix[5], bindMatrix[9], bindMatrix[13],
bindMatrix[2], bindMatrix[6], bindMatrix[10], bindMatrix[14],
bindMatrix[3] ,bindMatrix[7], bindMatrix[11], bindMatrix[15]])));
}
}
/**
* Связывает вершину и все ее дубликаты с костью
*/
private function linkVertexToJoint(jointIndex:int, vertex:Vertex, weight:Number):void {
if (vertex._jointsIndices == null) {
vertex._jointsIndices = new Vector.<uint>();
vertex._jointsWeights = new Vector.<Number>();
}
vertex._jointsIndices.push(jointIndex);
vertex._jointsWeights.push(weight);
// Цепляем дубликаты
while ((vertex = vertex.value) != null) {
if (vertex._jointsIndices == null) {
vertex._jointsIndices = new Vector.<uint>();
vertex._jointsWeights = new Vector.<Number>();
}
vertex._jointsIndices.push(jointIndex);
vertex._jointsWeights.push(weight);
}
}
/**
* Связывает вершины с костями
*/
private function linkVerticesToJoints(animatedJoints:Vector.<DaeObject>, vertices:Vector.<Vertex>):void {
var jointsOffset:int = jointsInput.offset;
var weightsOffset:int = weightsInput.offset;
var weightsSource:DaeSource = weightsInput.prepareSource(1);
var weights:Vector.<Number> = weightsSource.numbers;
var weightsStride:int = weightsSource.stride;
var vertexIndex:int = 0;
for (var i:int = 0, numVertices:int = vertices.length; i < numVertices; i++) {
var vertex:Vertex = vertices[i];
var count:int = vcounts[i];
for (var j:int = 0; j < count; j++) {
var index:int = inputsStride*(vertexIndex + j);
var jointIndex:int = indices[int(index + jointsOffset)];
if (jointIndex >= 0) {
var weightIndex:int = indices[int(index + weightsOffset)];
var weight:Number = weights[int(weightsStride*weightIndex)];
linkVertexToJoint(jointIndex, vertex, weight);
}
}
vertexIndex += count;
}
}
/**
* Создает иерархию костей и добавляет к скину.
*
* @return вектор добавленых к скину костей с анимацией.
* Если были добавлены вспомогательные кости, длина вектора будет отличаться от длины вектора nodes
*/
private function addJointsToSkin(skin:Skin, topmostJoints:Vector.<DaeNode>, nodes:Vector.<DaeNode>):Vector.<DaeObject> {
// Словарь, в котором ключ-нода, значение-позиция в векторе nodes
var nodesDictionary:Dictionary = new Dictionary();
var count:int = nodes.length;
var i:int;
for (i = 0; i < count; i++) {
nodesDictionary[nodes[i]] = i;
}
var animatedJoints:Vector.<DaeObject> = new Vector.<DaeObject>(count);
var numTopmostJoints:int = topmostJoints.length;
for (i = 0; i < numTopmostJoints; i++) {
var topmostJoint:DaeNode = topmostJoints[i];
var animatedJoint:DaeObject = addRootJointToSkin(skin, topmostJoint, animatedJoints, nodesDictionary);
addJointChildren(Joint(animatedJoint.object), animatedJoints, topmostJoint, nodesDictionary);
}
return animatedJoints;
}
/**
* Добавляет рутовую кость к скину
*/
private function addRootJointToSkin(skin:Skin, node:DaeNode, animatedJoints:Vector.<DaeObject>, nodes:Dictionary):DaeObject {
var joint:Joint = new Joint();
joint.name = node.name;
skin.addJoint(joint);
var animatedJoint:DaeObject = node.applyAnimation(node.applyTransformations(joint));
if (node in nodes) {
animatedJoints[nodes[node]] = animatedJoint;
} else {
// Добавляем в конец
animatedJoints.push(animatedJoint);
}
return animatedJoint;
}
/**
* Создает иерархию дочерних костей и добавляет к родительской кости.
*
* @param parent родительская кость
* @param animatedJoints вектор костей в который положить созданные кости.
* В конец вектора будут добавлены вспомогательные кости, если понадобятся.
* @param parentNode нода родительской кости
* @param nodes словарь, в котором ключ это нода кости, а значение это индекс кости в векторе animatedJoints
*/
private function addJointChildren(parent:Joint, animatedJoints:Vector.<DaeObject>, parentNode:DaeNode, nodes:Dictionary):void {
var children:Vector.<DaeNode> = parentNode.nodes;
for (var i:int = 0, count:int = children.length; i < count; i++) {
var child:DaeNode = children[i];
var joint:Joint;
if (child in nodes) {
joint = new Joint();
joint.name = child.name;
animatedJoints[nodes[child]] = child.applyAnimation(child.applyTransformations(joint));
parent.addJoint(joint);
addJointChildren(joint, animatedJoints, child, nodes);
} else {
// Нода не является костью
if (hasJointInDescendants(child, nodes)) {
// Если среди ее потомков есть кость, нужно создать вспомогательную кость вместо этой ноды.
joint = new Joint();
joint.name = child.name;
// Добавляем в конец новую кость
animatedJoints.push(child.applyAnimation(child.applyTransformations(joint)));
parent.addJoint(joint);
addJointChildren(joint, animatedJoints, child, nodes);
}
}
}
}
private function hasJointInDescendants(parentNode:DaeNode, nodes:Dictionary):Boolean {
var children:Vector.<DaeNode> = parentNode.nodes;
for (var i:int = 0, count:int = children.length; i < count; i++) {
var child:DaeNode = children[i];
if (child in nodes || hasJointInDescendants(child, nodes)) {
return true;
}
}
return false;
}
/**
* Трансформирует все вершины объекта при помощи BindShapeMatrix из коллады
*/
private function applyBindShapeMatrix(geometry:Geometry):void {
var matrixXML:XML = data.skin.bind_shape_matrix[0];
if (matrixXML != null) {
var matrix:Array = parseNumbersArray(matrixXML);
if (matrix.length >= 16) {
geometry.transform(new Matrix3D(Vector.<Number>([matrix[0], matrix[4], matrix[8], matrix[12],
matrix[1], matrix[5], matrix[9], matrix[13],
matrix[2], matrix[6], matrix[10], matrix[14],
matrix[3] ,matrix[7], matrix[11], matrix[15]])));
}
}
}
/**
* Возвращает <code>true</code> если у кости нет родительской кости
* @param node нода кости
* @param nodes словарь, в котором ключи это ноды всех костей
*/
private function isRootJointNode(node:DaeNode, nodes:Dictionary):Boolean {
for (var parent:DaeNode = node.parent; parent != null; parent = parent.parent) {
if (parent in nodes) {
return false;
}
}
return true;
}
public function findRootJointNodes(skeletons:Vector.<DaeNode>):Vector.<DaeNode> {
var nodes:Vector.<DaeNode> = findNodes(skeletons);
var i:int = 0;
var count:int = nodes.length;
if (count > 0) {
var nodesDictionary:Dictionary = new Dictionary();
for (i = 0; i < count; i++) {
nodesDictionary[nodes[i]] = i;
}
var rootNodes:Vector.<DaeNode> = new Vector.<DaeNode>();
for (i = 0; i < count; i++) {
var node:DaeNode = nodes[i];
if (isRootJointNode(node, nodesDictionary)) {
rootNodes.push(node);
}
}
return rootNodes;
}
return null;
}
/**
* Находит ноду по ее сиду в векторе скелетов
*/
private function findNode(nodeName:String, skeletons:Vector.<DaeNode>):DaeNode {
var count:int = skeletons.length;
for (var i:int = 0; i < count; i++) {
var node:DaeNode = skeletons[i].getNodeBySid(nodeName);
if (node != null) {
return node;
}
}
return null;
}
/**
* Возвращает вектор нод костей.
*/
private function findNodes(skeletons:Vector.<DaeNode>):Vector.<DaeNode> {
var jointsXML:XML = data.skin.joints.input.(@semantic == "JOINT")[0];
if (jointsXML != null) {
var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]);
if (jointsSource != null) {
if (jointsSource.parse() && jointsSource.names != null) {
var stride:int = jointsSource.stride;
var count:int = jointsSource.names.length/stride;
var nodes:Vector.<DaeNode> = new Vector.<DaeNode>(count);
for (var i:int = 0; i < count; i++) {
var node:DaeNode = findNode(jointsSource.names[int(stride*i)], skeletons);
if (node == null) {
// Ошибка, нет ноды
}
nodes[i] = node;
}
return nodes;
}
} else {
document.logger.logNotFoundError(jointsXML.@source[0]);
}
}
return null;
}
}
}

View File

@@ -0,0 +1,233 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeDocument {
use namespace collada;
// use namespace alternativa3d;
public var scene:DaeVisualScene;
/**
* Файл коллады
*/
private var data:XML;
// Словари для хранения соответствий id->DaeElement
internal var sources:Object;
internal var arrays:Object;
internal var vertices:Object;
internal var geometries:Object;
internal var nodes:Object;
internal var images:Object;
internal var effects:Object;
internal var controllers:Object;
internal var samplers:Object;
public var materials:Object;
internal var logger:DaeLogger;
public var versionMajor:uint;
public var versionMinor:uint;
public function DaeDocument(document:XML) {
this.data = document;
var versionComponents:Array = data.@version[0].toString().split(/[.,]/);
versionMajor = parseInt(versionComponents[1], 10);
versionMinor = parseInt(versionComponents[2], 10);
logger = new DaeLogger();
constructStructures();
constructScenes();
constructInstanceControllers();
constructAnimations();
}
private function getLocalID(url:XML):String {
var path:String = url.toString();
if (path.charAt(0) == "#") {
return path.substr(1);
} else {
logger.logExternalError(url);
return null;
}
}
// Ищем объявления всех элементов и заполняем словари
private function constructStructures():void {
var element:XML;
sources = new Object();
arrays = new Object();
for each (element in data..source) {
// Собираем все <source>. В конструкторах заполняется словарь arrays
var source:DaeSource = new DaeSource(element, this);
if (source.id != null) {
sources[source.id] = source;
}
}
images = new Object();
for each (element in data.library_images.image) {
// Собираем все <image>.
var image:DaeImage = new DaeImage(element, this);
if (image.id != null) {
images[image.id] = image;
}
}
effects = new Object();
for each (element in data.library_effects.effect) {
// Собираем все <effect>. В конструкторах заполняется словарь images
var effect:DaeEffect = new DaeEffect(element, this);
if (effect.id != null) {
effects[effect.id] = effect;
}
}
materials = new Object();
for each (element in data.library_materials.material) {
// Собираем все <material>.
var material:DaeMaterial = new DaeMaterial(element, this);
if (material.id != null) {
materials[material.id] = material;
}
}
geometries = new Object();
vertices = new Object();
for each (element in data.library_geometries.geometry) {
// Собираем все <geometry>. В конструкторах заполняется словарь vertices
var geom:DaeGeometry = new DaeGeometry(element, this);
if (geom.id != null) {
geometries[geom.id] = geom;
}
}
controllers = new Object();
for each (element in data.library_controllers.controller) {
// Собираем все <controllers>
var controller:DaeController = new DaeController(element, this);
if (controller.id != null) {
controllers[controller.id] = controller;
}
}
nodes = new Object();
for each (element in data.library_nodes.node) {
// Создаем только корневые ноды, остальные создаются рекурсивно в конструкторах
var node:DaeNode = new DaeNode(element, this);
if (node.id != null) {
nodes[node.id] = node;
}
}
}
private function constructInstanceControllers():void {
for each (var node:DaeNode in nodes) {
var instanceControllerXML:XML = node.data.instance_controller[0];
if (instanceControllerXML != null) {
node.skinOrRootJoint = true;
var instanceController:DaeInstanceController = new DaeInstanceController(instanceControllerXML, this, node);
if (instanceController.parse()) {
var jointNodes:Vector.<DaeNode> = instanceController.topmostJoints;
var numNodes:int = jointNodes.length;
if (numNodes > 0) {
var jointNode:DaeNode = jointNodes[0];
jointNode.addInstanceController(instanceController);
node.rootJoint = jointNode;
for (var i:int = 0; i < numNodes; i++) {
jointNodes[i].skinOrRootJoint = true;
}
}
}
}
}
}
private function constructScenes():void {
var vsceneURL:XML = data.scene.instance_visual_scene.@url[0];
var vsceneID:String = getLocalID(vsceneURL);
for each (var element:XML in data.library_visual_scenes.visual_scene) {
// Создаем visual_scene, в конструкторах создаются node
var vscene:DaeVisualScene = new DaeVisualScene(element, this);
if (vscene.id == vsceneID) {
this.scene = vscene;
}
}
if (vsceneID != null && scene == null) {
logger.logNotFoundError(vsceneURL);
}
}
private function constructAnimations():void {
var element:XML;
samplers = new Object();
for each (element in data.library_animations..sampler) {
// Собираем все <sampler>
var sampler:DaeSampler = new DaeSampler(element, this);
if (sampler.id != null) {
samplers[sampler.id] = sampler;
}
}
for each (element in data.library_animations..channel) {
var channel:DaeChannel = new DaeChannel(element, this);
var node:DaeNode = channel.node;
if (node != null) {
node.addChannel(channel);
}
}
}
public function findArray(url:XML):DaeArray {
return arrays[getLocalID(url)];
}
public function findSource(url:XML):DaeSource {
return sources[getLocalID(url)];
}
public function findImage(url:XML):DaeImage {
return images[getLocalID(url)];
}
public function findImageByID(id:String):DaeImage {
return images[id];
}
public function findEffect(url:XML):DaeEffect {
return effects[getLocalID(url)];
}
public function findMaterial(url:XML):DaeMaterial {
return materials[getLocalID(url)];
}
public function findVertices(url:XML):DaeVertices {
return vertices[getLocalID(url)];
}
public function findGeometry(url:XML):DaeGeometry {
return geometries[getLocalID(url)];
}
public function findNode(url:XML):DaeNode {
return nodes[getLocalID(url)];
}
public function findNodeByID(id:String):DaeNode {
return nodes[id];
}
public function findController(url:XML):DaeController {
return controllers[getLocalID(url)];
}
public function findSampler(url:XML):DaeSampler {
return samplers[getLocalID(url)];
}
}
}

View File

@@ -0,0 +1,214 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.materials.CommonMaterial;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.materials.TextureMaterial;
/**
* @private
*/
public class DaeEffect extends DaeElement {
public static var commonAlways:Boolean = false;
use namespace collada;
private var effectParams:Object;
private var commonParams:Object;
private var techniqueParams:Object;
private var diffuse:DaeEffectParam;
private var transparent:DaeEffectParam;
private var transparency:DaeEffectParam;
private var bump:DaeEffectParam;
private var emission:DaeEffectParam;
private var specular:DaeEffectParam;
public function DaeEffect(data:XML, document:DaeDocument) {
super(data, document);
// Внтри <effect> объявляются image.
constructImages();
}
private function constructImages():void {
var list:XMLList = data..image;
for each (var element:XML in list) {
var image:DaeImage = new DaeImage(element, document);
if (image.id != null) {
document.images[image.id] = image;
}
}
}
override protected function parseImplementation():Boolean {
var element:XML;
var param:DaeParam;
effectParams = new Object();
for each (element in data.newparam) {
param = new DaeParam(element, document);
effectParams[param.sid] = param;
}
commonParams = new Object();
for each (element in data.profile_COMMON.newparam) {
param = new DaeParam(element, document);
commonParams[param.sid] = param;
}
techniqueParams = new Object();
var technique:XML = data.profile_COMMON.technique[0];
if (technique != null) {
for each (element in technique.newparam) {
param = new DaeParam(element, document);
techniqueParams[param.sid] = param;
}
}
var shader:XML = data.profile_COMMON.technique.*.(localName() == "constant" || localName() == "lambert" || localName() == "phong" || localName() == "blinn")[0];
if (shader != null) {
var diffuseXML:XML = null;
if (shader.localName() == "constant") {
diffuseXML = shader.emission[0];
} else {
diffuseXML = shader.diffuse[0];
var emissionXML:XML = shader.emission[0];
if (emissionXML != null) {
emission = new DaeEffectParam(emissionXML, this);
}
}
if (diffuseXML != null) {
diffuse = new DaeEffectParam(diffuseXML, this);
}
if (shader.localName() == "phong" || shader.localName() == "blinn") {
var specularXML:XML = shader.specular[0];
if (specularXML != null) {
specular = new DaeEffectParam(specularXML, this);
}
}
var transparentXML:XML = shader.transparent[0];
if (transparentXML != null) {
transparent = new DaeEffectParam(transparentXML, this);
}
var transparencyXML:XML = shader.transparency[0];
if (transparencyXML != null) {
transparency = new DaeEffectParam(transparencyXML, this);
}
}
var bumpXML:XML = data.profile_COMMON.technique.extra.technique.(hasOwnProperty("@profile") && @profile == "OpenCOLLADA3dsMax").bump[0];
if (bumpXML != null) {
bump = new DaeEffectParam(bumpXML, this);
}
return true;
}
internal function getParam(name:String, setparams:Object):DaeParam {
var param:DaeParam = setparams[name];
if (param != null) {
return param;
}
param = techniqueParams[name];
if (param != null) {
return param;
}
param = commonParams[name];
if (param != null) {
return param;
}
return effectParams[name];
}
private function float4ToUint(value:Array, alpha:Boolean = true):uint {
var r:uint = (value[0] * 255);
var g:uint = (value[1] * 255);
var b:uint = (value[2] * 255);
if (alpha) {
var a:uint = (value[3] * 255);
return (a << 24) | (r << 16) | (g << 8) | b;
} else {
return (r << 16) | (g << 8) | b;
}
}
/**
* Возвращает материал движка с заданными параметрами.
* Перед использованием вызвать parse().
*/
public function getMaterial(setparams:Object):Material {
var bumpURL:String = null;
var transparentImage:DaeImage;
if (bump != null || commonAlways) {
var bumpImage:DaeImage = (bump != null) ? bump.getImage(setparams) : null;
if (bumpImage != null) {
bumpURL = bumpImage.init_from;
}
var emissionURL:String = null;
if (emission != null) {
var emissionImage:DaeImage = emission.getImage(setparams);
if (emissionImage != null) {
emissionURL = emissionImage.init_from;
}
}
var specularURL:String = null;
if (specular != null) {
var specularImage:DaeImage = specular.getImage(setparams);
if (specularImage != null) {
specularURL = specularImage.init_from;
}
}
var commonMaterial:CommonMaterial = new CommonMaterial();
var diffuseImage:DaeImage = (diffuse == null) ? null : diffuse.getImage(setparams);
if (diffuseImage != null) {
commonMaterial.diffuseMapURL = diffuseImage.init_from;
}
transparentImage = (transparent == null) ? null : transparent.getImage(setparams);
if (transparentImage != null) {
commonMaterial.opacityMapURL = transparentImage.init_from;
}
commonMaterial.normalMapURL = bumpURL;
commonMaterial.emissionMapURL = emissionURL;
commonMaterial.specularMapURL = specularURL;
return commonMaterial;
}
if (diffuse != null) {
var color:Array = diffuse.getColor(setparams);
if (color != null) {
var fillMaterial:FillMaterial = new FillMaterial(float4ToUint(color, false), color[3]);
if (transparency != null) {
var value:Number = transparency.getFloat(setparams);
if (!isNaN(value)) {
fillMaterial.alpha = value;
}
}
return fillMaterial;
} else {
var image:DaeImage = diffuse.getImage(setparams);
if (image != null) {
var sampler:DaeParam = diffuse.getSampler(setparams);
var textureMaterial:TextureMaterial = new TextureMaterial();
textureMaterial.repeat = (sampler == null) ? true : (sampler.wrap_s == null || sampler.wrap_s == "WRAP");
textureMaterial.diffuseMapURL = image.init_from;
transparentImage = (transparent == null) ? null : transparent.getImage(setparams);
if (transparentImage != null) {
textureMaterial.opacityMapURL = transparentImage.init_from;
}
return textureMaterial;
}
}
}
return null;
}
/**
* Имя текстурного канала для основной карты объекта.
* Перед использованием вызвать parse().
*/
public function get mainTexCoords():String {
var channel:String = null;
channel = (channel == null && diffuse != null) ? diffuse.texCoord : channel;
channel = (channel == null && transparent != null) ? transparent.texCoord : channel;
channel = (channel == null && bump != null) ? bump.texCoord : channel;
channel = (channel == null && emission != null) ? emission.texCoord : channel;
channel = (channel == null && specular != null) ? specular.texCoord : channel;
return channel;
}
}
}

View File

@@ -0,0 +1,85 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeEffectParam extends DaeElement {
use namespace collada;
private var effect:DaeEffect;
public function DaeEffectParam(data:XML, effect:DaeEffect) {
super(data, effect.document);
this.effect = effect;
}
public function getFloat(setparams:Object):Number {
var floatXML:XML = data.float[0];
if (floatXML != null) {
return parseNumber(floatXML);
}
var paramRef:XML = data.param.@ref[0];
if (paramRef != null) {
var param:DaeParam = effect.getParam(paramRef.toString(), setparams);
if (param != null) {
return param.getFloat();
}
}
return NaN;
}
public function getColor(setparams:Object):Array {
var colorXML:XML = data.color[0];
if (colorXML != null) {
return parseNumbersArray(colorXML);
}
var paramRef:XML = data.param.@ref[0];
if (paramRef != null) {
var param:DaeParam = effect.getParam(paramRef.toString(), setparams);
if (param != null) {
return param.getFloat4();
}
}
return null;
}
private function get texture():String {
var attr:XML = data.texture.@texture[0];
return (attr == null) ? null : attr.toString();
}
public function getSampler(setparams:Object):DaeParam {
var sid:String = texture;
if (sid != null) {
return effect.getParam(sid, setparams);
}
return null;
}
public function getImage(setparams:Object):DaeImage {
var sampler:DaeParam = getSampler(setparams);
if (sampler != null) {
var surfaceSID:String = sampler.surfaceSID;
if (surfaceSID != null) {
var surface:DaeParam = effect.getParam(surfaceSID, setparams);
if (surface != null) {
return surface.image;
}
} else {
return sampler.image;
}
} else {
// Возможно файл был экспортирован стандартным экспортом макса, который забивает на спецификацию и хранит ссылку прямо на image.
return document.findImageByID(texture);
}
return null;
}
public function get texCoord():String {
var attr:XML = data.texture.@texcoord[0];
return (attr == null) ? null : attr.toString();
}
}
}

View File

@@ -0,0 +1,94 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeElement {
use namespace collada;
public var document:DaeDocument;
public var data:XML;
/**
* -1 - not parsed, 0 - parsed with error, 1 - parsed without error.
*/
private var _parsed:int = -1;
public function DaeElement(data:XML, document:DaeDocument) {
this.document = document;
this.data = data;
}
/**
* Выполняет предварительную настройку объекта.
*
* @return <code>false</code> в случае ошибки.
*/
public function parse():Boolean {
// -1 - not parsed, 0 - parsed with error, 1 - parsed without error.
if (_parsed < 0) {
_parsed = parseImplementation() ? 1 : 0;
return _parsed != 0;
}
return _parsed != 0;
}
/**
* Переопределяемый метод parse()
*/
protected function parseImplementation():Boolean {
return true;
}
/**
* Возвращает массив значений типа String.
*/
protected function parseStringArray(element:XML):Array {
return element.text().toString().split(/\s+/);
}
protected function parseNumbersArray(element:XML):Array {
var arr:Array = element.text().toString().split(/\s+/);
for (var i:int = 0, count:int = arr.length; i < count; i++) {
var value:String = arr[i];
if (value.indexOf(",") != -1) {
value = value.replace(/,/, ".");
}
arr[i] = parseFloat(value);
}
return arr;
}
protected function parseIntsArray(element:XML):Array {
var arr:Array = element.text().toString().split(/\s+/);
for (var i:int = 0, count:int = arr.length; i < count; i++) {
var value:String = arr[i];
arr[i] = parseInt(value, 10);
}
return arr;
}
protected function parseNumber(element:XML):Number {
var value:String = element.toString().replace(/,/, ".");
return parseFloat(value);
}
public function get id():String {
var idXML:XML = data.@id[0];
return (idXML == null) ? null : idXML.toString();
}
public function get sid():String {
var attr:XML = data.@sid[0];
return (attr == null) ? null : attr.toString();
}
public function get name():String {
var nameXML:XML = data.@name[0];
return (nameXML == null) ? null : nameXML.toString();
}
}
}

View File

@@ -0,0 +1,164 @@
package alternativa.engine3d.loaders.collada {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Mesh;
/**
* @private
*/
public class DaeGeometry extends DaeElement {
use namespace collada;
use namespace alternativa3d;
private var primitives:Vector.<DaePrimitive>;
private var vertices:DaeVertices;
public function DaeGeometry(data:XML, document:DaeDocument) {
super(data, document);
// Внутри <geometry> объявляются элементы: sources, vertices.
// sources мы создаем внутри DaeDocument, здесь не нужно.
constructVertices();
}
private function constructVertices():void {
var verticesXML:XML = data.mesh.vertices[0];
if (verticesXML != null) {
vertices = new DaeVertices(verticesXML, document);
document.vertices[vertices.id] = vertices;
}
}
override protected function parseImplementation():Boolean {
if (vertices != null) {
return parsePrimitives();
}
return false;
}
private function parsePrimitives():Boolean {
primitives = new Vector.<DaePrimitive>();
var children:XMLList = data.mesh.children();
for (var i:int = 0, count:int = children.length(); i < count; i++) {
var child:XML = children[i];
switch (child.localName()) {
case "polygons":
case "polylist":
case "triangles":
case "trifans":
case "tristrips":
primitives.push(new DaePrimitive(child, document));
break;
}
}
return true;
}
/**
* Возвращает материал первого примитива
* Перед использованием вызвать parse().
*/
public function getAnyMaterial(materials:Object):Material {
if (primitives.length > 0) {
var instanceMaterial:DaeInstanceMaterial = materials[primitives[0].materialSymbol];
var mat:DaeMaterial = (instanceMaterial == null) ? null : instanceMaterial.material;
if (mat != null) {
mat.parse();
return mat.material;
}
}
return null;
}
/**
* Создает геометрию и возвращает в виде меша.
* Перед использованием вызвать parse().
*
* @param materials словарь материалов.
*/
public function parseMesh(materials:Object):Mesh {
if (data.mesh.length() > 0) {
var geometry:Geometry = new Geometry();
fillInMesh(geometry, materials);
cleanVertices(geometry);
// mesh.calculateNormals(true);
var mesh:Mesh = new Mesh();
mesh.geometry = geometry;
mesh.material = getAnyMaterial(materials);
mesh.calculateBounds();
return mesh;
}
return null;
}
public function parseByPrimitives(materials:Object):Vector.<Mesh> {
if (data.mesh.length() > 0) {
var result:Vector.<Mesh> = new Vector.<Mesh>();
vertices.parse();
for (var i:int = 0, count:int = primitives.length; i < count; i++) {
var geometry:Geometry = new Geometry();
var createdVertices:Vector.<Vertex> = vertices.fillInMesh(geometry);
var primitive:DaePrimitive = primitives[i];
primitive.parse();
var material:DaeInstanceMaterial = null;
if (primitive.verticesEquals(vertices)) {
material = materials[primitive.materialSymbol];
primitive.fillInMesh(geometry, createdVertices, material);
cleanVertices(geometry);
var mesh:Mesh = new Mesh();
mesh.geometry = geometry;
mesh.material = (material == null) ? null : material.material.material;
mesh.calculateBounds();
result.push(mesh);
} else {
// Ошибка, нельзя использовать вершины из другой геометрии
}
}
return result;
}
return null;
}
/**
* Заполняет заданный объект геометрией и возвращает массив вершин с индексами.
* Перед использованием вызвать parse().
* Некоторые вершины в поле value содержат ссылку на дубликат вершины.
* После использования нужно вызвать cleanVertices для зачистки вершин от временных данных.
*
* @return массив вершин с индексами. У вершины в поле value задается дубликат вершины.
*/
public function fillInMesh(geometry:Geometry, materials:Object):Vector.<Vertex> {
vertices.parse();
var createdVertices:Vector.<Vertex> = vertices.fillInMesh(geometry);
for (var i:int = 0, count:int = primitives.length; i < count; i++) {
var primitive:DaePrimitive = primitives[i];
primitive.parse();
if (primitive.verticesEquals(vertices)) {
primitive.fillInMesh(geometry, createdVertices, materials[primitive.materialSymbol]);
} else {
// Ошибка, нельзя использовать вершины из другой геометрии
}
}
return createdVertices;
}
/**
* Зачищает вершины от временных данных
*/
public function cleanVertices(geometry:Geometry):void {
for each (var vertex:Vertex in geometry._vertices) {
vertex.index = 0;
vertex._attributes = null;
vertex.value = null;
}
}
}
}

View File

@@ -0,0 +1,27 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeImage extends DaeElement {
use namespace collada;
public function DaeImage(data:XML, document:DaeDocument) {
super(data, document);
}
public function get init_from():String {
var element:XML = data.init_from[0];
if (element != null) {
if (document.versionMajor > 4) {
var refXML:XML = element.ref[0];
return (refXML == null) ? null : refXML.text().toString();
}
return element.text().toString();
}
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeInput extends DaeElement {
use namespace collada;
public function DaeInput(data:XML, document:DaeDocument) {
super(data, document);
}
public function get semantic():String {
var attribute:XML = data.@semantic[0];
return (attribute == null) ? null : attribute.toString();
}
public function get source():XML {
return data.@source[0];
}
public function get offset():int {
var attr:XML = data.@offset[0];
return (attr == null) ? 0 : parseInt(attr.toString(), 10);
}
public function get setNum():int {
var attr:XML = data.@set[0];
return (attr == null) ? 0 : parseInt(attr.toString(), 10);
}
/**
* Если DaeSource по ссылке source имеет тип значений Number и
* количество компонент не меньше заданного, то этот метод его вернет.
*/
public function prepareSource(minComponents:int):DaeSource {
var source:DaeSource = document.findSource(this.source);
if (source != null) {
source.parse();
if (source.numbers != null && source.stride >= minComponents) {
return source;
} else {
// document.logger.logNotEnoughDataError();
}
} else {
document.logger.logNotFoundError(data.@source[0]);
}
return null;
}
}
}

View File

@@ -0,0 +1,102 @@
package alternativa.engine3d.loaders.collada {
import flash.utils.Dictionary;
/**
* @private
*/
public class DaeInstanceController extends DaeElement {
use namespace collada;
public var node:DaeNode;
/**
* Список верхнеуровневых костей, которые имеют общего предка.
* Перед использованием вызвать parse().
*/
public var topmostJoints:Vector.<DaeNode>;
public function DaeInstanceController(data:XML, document:DaeDocument, node:DaeNode) {
super(data, document);
this.node = node;
}
override protected function parseImplementation():Boolean {
var controller:DaeController = this.controller;
if (controller != null) {
topmostJoints = controller.findRootJointNodes(this.skeletons);
if (topmostJoints != null && topmostJoints.length > 1) {
replaceNodesByTopmost(topmostJoints);
}
}
return topmostJoints != null;
}
/**
* Заменяет каждую ноду в списке на ее родителя, у которого родитель является общим для всех остальных нод или является сценой.
* @param nodes не пустой массив нод
*/
private function replaceNodesByTopmost(nodes:Vector.<DaeNode>):void {
var i:int;
var node:DaeNode, parent:DaeNode;
var numNodes:int = nodes.length;
var parents:Dictionary = new Dictionary();
for (i = 0; i < numNodes; i++) {
node = nodes[i];
for (parent = node.parent; parent != null; parent = parent.parent) {
if (parents[parent]) {
parents[parent]++;
} else {
parents[parent] = 1;
}
}
}
// Заменяем на родителей нод, которые имеют общего родителя с остальными нодами или не имеют родителя вообще.
for (i = 0; i < numNodes; i++) {
node = nodes[i];
while ((parent = node.parent) != null && (parents[parent] != numNodes)) {
node = node.parent;
}
nodes[i] = node;
}
}
private function get controller():DaeController {
var controller:DaeController = document.findController(data.@url[0]);
if (controller == null) {
document.logger.logNotFoundError(data.@url[0]);
}
return controller;
}
private function get skeletons():Vector.<DaeNode> {
var list:XMLList = data.skeleton;
if (list.length() > 0) {
var skeletons:Vector.<DaeNode> = new Vector.<DaeNode>();
for (var i:int = 0, count:int = list.length(); i < count; i++) {
var skeletonXML:XML = list[i];
var skel:DaeNode = document.findNode(skeletonXML.text()[0]);
if (skel != null) {
skeletons.push(skel);
} else {
document.logger.logNotFoundError(skeletonXML);
}
}
return skeletons;
}
return null;
}
public function parseSkin(materials:Object):DaeObject {
var controller:DaeController = this.controller;
if (controller != null) {
controller.parse();
return controller.parseSkin(materials, topmostJoints, this.skeletons);
}
return null;
}
}
}

View File

@@ -0,0 +1,39 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeInstanceMaterial extends DaeElement {
use namespace collada;
public function DaeInstanceMaterial(data:XML, document:DaeDocument) {
super(data, document);
}
public function get symbol():String {
var attribute:XML = data.@symbol[0];
return (attribute == null) ? null : attribute.toString();
}
private function get target():XML {
return data.@target[0];
}
public function get material():DaeMaterial {
var mat:DaeMaterial = document.findMaterial(target);
if (mat == null) {
document.logger.logNotFoundError(target);
}
return mat;
}
public function getBindVertexInputSetNum(semantic:String):int {
var bindVertexInputXML:XML = data.bind_vertex_input.(@semantic == semantic)[0];
if (bindVertexInputXML == null) return 0;
var setNumXML:XML = bindVertexInputXML.@input_set[0];
return (setNumXML == null) ? 0 : parseInt(setNumXML.toString(), 10);
}
}
}

View File

@@ -0,0 +1,53 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeLogger {
public function DaeLogger() {
}
private function logMessage(message:String, element:XML):void {
// var index:int = element.childIndex();
var index:int = 0;
var name:String = (element.nodeKind() == "attribute") ? "@" + element.localName() : element.localName() + ((index > 0) ? "[" + index + "]" : "");
var parent:* = element.parent();
while (parent != null) {
// index = parent.childIndex();
name = parent.localName() + ((index > 0) ? "[" + index + "]" : "") + "." + name;
parent = parent.parent();
}
trace(message, '| "' + name + '"');
}
private function logError(message:String, element:XML):void {
logMessage("[ERROR] " + message, element);
}
public function logExternalError(element:XML):void {
logError("External urls don't supported", element);
}
public function logSkewError(element:XML):void {
logError("<skew> don't supported", element);
}
public function logJointInAnotherSceneError(element:XML):void {
logError("Joints in different scenes don't supported", element);
}
public function logInstanceNodeError(element:XML):void {
logError("<instance_node> don't supported", element);
}
public function logNotFoundError(element:XML):void {
logError("Element with url \"" + element.toString() + "\" not found", element);
}
public function logNotEnoughDataError(element:XML):void {
logError("Not enough data", element);
}
}
}

View File

@@ -0,0 +1,61 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.materials.Material;
/**
* @private
*/
public class DaeMaterial extends DaeElement {
use namespace collada;
/**
* Материал движка.
* Перед использованием вызвать parse().
*/
public var material:Material;
/**
* Имя текстурного канала для карты цвета объекта
* Перед использованием вызвать parse().
*/
public var mainTexCoords:String;
/**
* Материал используется.
*/
public var used:Boolean = false;
public function DaeMaterial(data:XML, document:DaeDocument) {
super(data, document);
}
private function parseSetParams():Object {
var params:Object = new Object();
var list:XMLList = data.instance_effect.setparam;
for each (var element:XML in list) {
var param:DaeParam = new DaeParam(element, document);
params[param.ref] = param;
}
return params;
}
private function get effectURL():XML {
return data.instance_effect.@url[0];
}
override protected function parseImplementation():Boolean {
var effect:DaeEffect = document.findEffect(effectURL);
if (effect != null) {
effect.parse();
material = effect.getMaterial(parseSetParams());
mainTexCoords = effect.mainTexCoords;
if (material != null) {
material.name = name;
}
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,372 @@
package alternativa.engine3d.loaders.collada {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.animation.Animation;
import alternativa.engine3d.animation.MatrixAnimation;
import alternativa.engine3d.animation.TransformAnimation;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Skin;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
/**
* @private
*/
public class DaeNode extends DaeElement {
use namespace collada;
use namespace alternativa3d;
public var scene:DaeVisualScene;
public var parent:DaeNode;
// Скин или рутовая кость
public var skinOrRootJoint:Boolean = false;
// Ссылка на рутовую кость скина, если есть
public var rootJoint:DaeNode;
/**
* Анимационные каналы этой ноды
*/
private var channels:Vector.<DaeChannel>;
/**
* Вектор контроллеров, которые ссылаются на эту ноду
*/
private var instanceControllers:Vector.<DaeInstanceController>;
/**
* Массив нод в этой ноде.
*/
public var nodes:Vector.<DaeNode>;
/**
* Массив объектов в этой ноде.
* Перед использованием вызвать parse().
*/
public var objects:Vector.<DaeObject>;
/**
* Вектор скинов в этой ноде.
* Перед использованием вызвать parse().
*/
public var skins:Vector.<DaeObject>;
/**
* Создание ноды из xml. Рекурсивно создаются дочерние ноды.
*/
public function DaeNode(data:XML, document:DaeDocument, scene:DaeVisualScene = null, parent:DaeNode = null) {
super(data, document);
this.scene = scene;
this.parent = parent;
// Внутри <node> объявляются другие node.
constructNodes();
}
private function constructNodes():void {
var nodesList:XMLList = data.node;
var count:int = nodesList.length();
nodes = new Vector.<DaeNode>(count);
for (var i:int = 0; i < count; i++) {
var node:DaeNode = new DaeNode(nodesList[i], document, scene, this);
if (node.id != null) {
document.nodes[node.id] = node;
}
nodes[i] = node;
}
}
public function addChannel(channel:DaeChannel):void {
if (channels == null) {
channels = new Vector.<DaeChannel>();
}
channels.push(channel);
}
public function addInstanceController(controller:DaeInstanceController):void {
if (instanceControllers == null) {
instanceControllers = new Vector.<DaeInstanceController>();
}
instanceControllers.push(controller);
}
override protected function parseImplementation():Boolean {
this.skins = parseSkins();
this.objects = parseObjects();
return true;
}
private function parseInstanceMaterials(geometry:XML):Object {
var instances:Object = new Object();
var list:XMLList = geometry.bind_material.technique_common.instance_material;
for (var i:int = 0, count:int = list.length(); i < count; i++) {
var instance:DaeInstanceMaterial = new DaeInstanceMaterial(list[i], document);
instances[instance.symbol] = instance;
}
return instances;
}
/**
* Возвращает ноду по сиду.
*/
public function getNodeBySid(sid:String):DaeNode {
if (sid == this.sid) {
return this;
}
var levelNodes:Vector.<Vector.<DaeNode> > = new Vector.<Vector.<DaeNode> >;
var levelNodes2:Vector.<Vector.<DaeNode> > = new Vector.<Vector.<DaeNode> >;
levelNodes.push(nodes);
var len:int = levelNodes.length;
while (len > 0) {
for (var i:int = 0; i < len; i++) {
var children:Vector.<DaeNode> = levelNodes[i];
var count:int = children.length;
for (var j:int = 0; j < count; j++) {
var node:DaeNode = children[j];
if (node.sid == sid) {
return node;
}
if (node.nodes.length > 0) {
levelNodes2.push(node.nodes);
}
}
}
var temp:Vector.<Vector.<DaeNode> > = levelNodes;
levelNodes = levelNodes2;
levelNodes2 = temp;
levelNodes2.length = 0;
len = levelNodes.length;
}
return null;
}
/**
* Парсит и возвращает массив скинов, связанных с этой нодой.
*/
public function parseSkins():Vector.<DaeObject> {
if (instanceControllers == null) {
return null;
}
var skins:Vector.<DaeObject> = new Vector.<DaeObject>();
for (var i:int = 0, count:int = instanceControllers.length; i < count; i++) {
var instanceController:DaeInstanceController = instanceControllers[i];
instanceController.parse();
var skinAndAnimatedJoints:DaeObject = instanceController.parseSkin(parseInstanceMaterials(instanceController.data));
if (skinAndAnimatedJoints != null) {
var skin:Skin = Skin(skinAndAnimatedJoints.object);
// Имя берем из ноды, содержащей instance_controller
skin.name = instanceController.node.name;
// По новой схеме не применяем трансформаций и анимацию к скину. Все это задается в рутовых костях
skins.push(skinAndAnimatedJoints);
}
}
return (skins.length > 0) ? skins : null;
}
/**
* Парсит и возвращает массив объектов, связанных с этой нодой.
* Может быть Mesh или Object3D, если неизвестен тип объекта.
*/
public function parseObjects():Vector.<DaeObject> {
var objects:Vector.<DaeObject> = new Vector.<DaeObject>();
var children:XMLList = data.children();
for (var i:int = 0, count:int = children.length(); i < count; i++) {
var child:XML = children[i];
switch (child.localName()) {
case "instance_geometry":
var geom:DaeGeometry = document.findGeometry(child.@url[0]);
if (geom != null) {
geom.parse();
// var mesh:Mesh = geom.parseMesh(parseInstanceMaterials(child));
// if (mesh != null) {
// mesh.name = name;
// objects.push(applyAnimation(applyTransformations(mesh)));
// }
var meshes:Vector.<Mesh> = geom.parseByPrimitives(parseInstanceMaterials(child));
for (var j:int = 0; j < meshes.length; j++) {
var mesh:Mesh = meshes[j];
mesh.name = name;
objects.push(applyAnimation(applyTransformations(mesh)));
}
} else {
document.logger.logNotFoundError(child.@url[0]);
}
break;
//case "instance_controller":
// Парсится в методе parseSkins();
// break;
case "instance_node":
document.logger.logInstanceNodeError(child);
break;
}
}
return (objects.length > 0) ? objects : null;
}
/**
* Возвращает трансформацию ноды в виде матрицы
*
* @param initialMatrix матрица, к которой будет добавлена трансформация ноды
*/
public function getMatrix(initialMatrix:Matrix3D = null):Matrix3D {
var matrix:Matrix3D = (initialMatrix == null) ? new Matrix3D() : initialMatrix;
var components:Array;
var children:XMLList = data.children();
for (var i:int = children.length() - 1; i >= 0; i--) {
// Трансформации накладываются с конца в начало
var child:XML = children[i];
var sid:XML = child.@sid[0];
if (sid != null && sid.toString() == "post-rotationY") {
// Стандартный экспорт макса записал какой-то хлам, игнорируем
continue;
}
switch (child.localName()) {
case "scale" : {
components = parseNumbersArray(child);
matrix.appendScale(components[0], components[1], components[2]);
break;
}
case "rotate" : {
components = parseNumbersArray(child);
matrix.appendRotation(components[3], new Vector3D(components[0], components[1], components[2]));
break;
}
case "translate" : {
components = parseNumbersArray(child);
matrix.appendTranslation(components[0], components[1], components[2]);
break;
}
case "matrix" : {
components = parseNumbersArray(child);
matrix.append(new Matrix3D(Vector.<Number>([components[0], components[4], components[8], components[12],
components[1], components[5], components[9], components[13],
components[2], components[6], components[10], components[14],
components[3] ,components[7], components[11], components[15]])));
break;
}
case "lookat" : {
// components = parseNumbersArray(child);
break;
}
case "skew" : {
document.logger.logSkewError(child);
break;
}
}
}
return matrix;
}
/**
* Назначает контроллер анимации к объекту.
*
* @param animation анимация которую следует применить к объекту,
* если <code>null</code>, будет создана новая анимация из ноды.
*/
public function applyAnimation(object:Object3D, animation:Animation = null):DaeObject {
animation = (animation == null) ? parseAnimation() : animation;
if (animation != null) {
animation.object = object;
}
return new DaeObject(object, animation);
}
/**
* Применяет трансформацию к объекту.
*
* @param prepend если не равен <code>null</code>, трансформация добавляется к этой матрице.
*/
public function applyTransformations(object:Object3D, prepend:Matrix3D = null, append:Matrix3D = null):Object3D {
var matrix:Matrix3D = getMatrix(prepend);
if (append != null) {
matrix.append(append);
}
var v:Vector.<Vector3D> = matrix.decompose();
object.x = v[0].x;
object.y = v[0].y;
object.z = v[0].z;
object.rotationX = v[1].x;
object.rotationY = v[1].y;
object.rotationZ = v[1].z;
object.scaleX = v[2].x;
object.scaleY = v[2].y;
object.scaleZ = v[2].z;
return object;
}
/**
* Возвращает анимацию ноды.
*/
public function parseAnimation():Animation {
if (channels == null) {
return null;
}
var channel:DaeChannel = channels[0];
channel.parse();
if (channel.animatedParam == DaeChannel.PARAM_MATRIX) {
// Анимация матрицы
var matrixAnimation:MatrixAnimation = new MatrixAnimation();
matrixAnimation.matrix = channel.track;
return matrixAnimation;
}
// Это не анимация матрицы, значит покомпонентная анимация
var animation:TransformAnimation = new TransformAnimation();
var count:int = channels.length;
for (var i:int = 0; i < count; i++) {
channel = channels[i];
channel.parse();
switch (channel.animatedParam) {
case DaeChannel.PARAM_TRANSLATE:
animation.translation = channel.track;
break;
case DaeChannel.PARAM_TRANSLATE_X:
animation.x = channel.track;
break;
case DaeChannel.PARAM_TRANSLATE_Y:
animation.y = channel.track;
break;
case DaeChannel.PARAM_TRANSLATE_Z:
animation.z = channel.track;
break;
case DaeChannel.PARAM_ROTATION_X:
animation.rotationX = channel.track;
break;
case DaeChannel.PARAM_ROTATION_Y:
animation.rotationY = channel.track;
break;
case DaeChannel.PARAM_ROTATION_Z:
animation.rotationZ = channel.track;
break;
case DaeChannel.PARAM_SCALE:
animation.scale = channel.track;
break;
case DaeChannel.PARAM_SCALE_X:
animation.scaleX = channel.track;
break;
case DaeChannel.PARAM_SCALE_Y:
animation.scaleY = channel.track;
break;
case DaeChannel.PARAM_SCALE_Z:
animation.scaleZ = channel.track;
break;
}
}
return animation;
}
public function get layer():String {
var layerXML:XML = data.@layer[0];
return (layerXML == null) ? null : layerXML.toString();
}
}
}

View File

@@ -0,0 +1,20 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.animation.Animation;
import alternativa.engine3d.core.Object3D;
/**
* @private
*/
public class DaeObject {
public var object:Object3D;
public var animation:Animation;
public function DaeObject(object:Object3D, animation:Animation = null) {
this.object = object;
this.animation = animation;
}
}
}

View File

@@ -0,0 +1,79 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeParam extends DaeElement {
use namespace collada;
public function DaeParam(data:XML, document:DaeDocument) {
super(data, document);
}
public function get ref():String {
var attribute:XML = data.@ref[0];
return (attribute == null) ? null : attribute.toString();
}
public function getFloat():Number {
var floatXML:XML = data.float[0];
if (floatXML != null) {
return parseNumber(floatXML);
}
return NaN;
}
public function getFloat4():Array {
var element:XML = data.float4[0];
var components:Array;
if (element == null) {
element = data.float3[0];
if (element != null) {
components = parseNumbersArray(element);
components[3] = 1.0;
}
} else {
components = parseNumbersArray(element);
}
return components;
}
/**
* Возвращает sid параметра с типом surface. Только если тип этого элемента sampler2D и версия коллады 1.4.
*/
public function get surfaceSID():String {
var element:XML = data.sampler2D.source[0];
return (element == null) ? null : element.text().toString();
}
public function get wrap_s():String {
var element:XML = data.sampler2D.wrap_s[0];
return (element == null) ? null : element.text().toString();
}
public function get image():DaeImage {
var surface:XML = data.surface[0];
var image:DaeImage;
if (surface != null) {
// Collada 1.4
var init_from:XML = surface.init_from[0];
if (init_from == null) {
// Error
return null;
}
image = document.findImageByID(init_from.text().toString());
} else {
// Collada 1.5
var imageIDXML:XML = data.instance_image.@url[0];
if (imageIDXML == null) {
// error
return null;
}
image = document.findImage(imageIDXML);
}
return image;
}
}
}

View File

@@ -0,0 +1,568 @@
package alternativa.engine3d.loaders.collada {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.core.Wrapper;
import alternativa.engine3d.materials.Material;
import flash.geom.Point;
import flash.geom.Vector3D;
use namespace alternativa3d;
/**
* @private
*/
public class DaePrimitive extends DaeElement {
use namespace collada;
private var verticesInput:DaeInput;
private var texCoordsInputs:Vector.<DaeInput>;
private var normalsInput:DaeInput;
private var normals:Vector.<Number>;
private var normalsStride:int;
private var biNormalsInputs:Vector.<DaeInput>;
private var tangentsInputs:Vector.<DaeInput>;
private var biNormalsInput:DaeInput;
private var tangentsInput:DaeInput;
private var biNormals:Vector.<Number>;
private var biNormalsStride:int;
private var tangents:Vector.<Number>;
private var tangentsStride:int;
private var inputsStride:int;
public function DaePrimitive(data:XML, document:DaeDocument) {
super(data, document);
}
override protected function parseImplementation():Boolean {
parseInputs();
return true;
}
private function parseInputs():void {
texCoordsInputs = new Vector.<DaeInput>();
tangentsInputs = new Vector.<DaeInput>();
biNormalsInputs = new Vector.<DaeInput>();
var inputsList:XMLList = data.input;
var maxInputOffset:int = 0;
for (var i:int = 0, count:int = inputsList.length(); i < count; i++) {
var input:DaeInput = new DaeInput(inputsList[i], document);
var semantic:String = input.semantic;
if (semantic != null) {
switch (semantic) {
case "VERTEX" :
if (verticesInput == null) {
verticesInput = input;
}
break;
case "TEXCOORD" :
texCoordsInputs.push(input);
break;
case "NORMAL":
if (normalsInput == null) {
normalsInput = input;
}
break;
case "TEXTANGENT":
tangentsInputs.push(input);
break;
case "TEXBINORMAL":
biNormalsInputs.push(input);
break;
}
}
var offset:int = input.offset;
maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset;
}
inputsStride = maxInputOffset + 1;
}
private function getTexCoordsDatas(mainSetNum:int):Vector.<TexCoordsData> {
var mainInput:DaeInput = null;
var texCoordsInput:DaeInput;
var i:int;
var numInputs:int = texCoordsInputs.length;
for (i = 0; i < numInputs; i++) {
texCoordsInput = texCoordsInputs[i];
if (texCoordsInput.setNum == mainSetNum) {
mainInput = texCoordsInput;
break;
}
}
var datas:Vector.<TexCoordsData> = new Vector.<TexCoordsData>();
for (i = 0; i < numInputs; i++) {
texCoordsInput = texCoordsInputs[i];
var texCoordsSource:DaeSource = texCoordsInput.prepareSource(2);
if (texCoordsSource != null) {
var data:TexCoordsData = new TexCoordsData(texCoordsSource.numbers, texCoordsSource.stride, texCoordsInput.offset, texCoordsInput.setNum);
if (texCoordsInput == mainInput) {
datas.unshift(data);
} else {
datas.push(data);
}
}
}
return (datas.length > 0) ? datas : null;
}
private function get type():String {
return data.localName() as String;
}
/**
* Заполняет заданный меш геометрией этого примитива, используя заданные вершины.
* На вершины накладывается uv маппинг.
* Перед использованием вызвать parse().
*/
public function fillInMesh(geometry:Geometry, vertices:Vector.<Vertex>, instanceMaterial:DaeInstanceMaterial = null):void {
var countXML:XML = data.@count[0];
if (countXML == null) {
document.logger.logNotEnoughDataError(data);
return;
}
var numPrimitives:int = parseInt(countXML.toString(), 10);
var texCoordsDatas:Vector.<TexCoordsData>;
var material:Material;
if (instanceMaterial != null) {
var dmat:DaeMaterial = instanceMaterial.material;
dmat.parse();
if (dmat.mainTexCoords != null) {
texCoordsDatas = getTexCoordsDatas(instanceMaterial.getBindVertexInputSetNum(dmat.mainTexCoords));
} else {
texCoordsDatas = getTexCoordsDatas(-1);
}
dmat.used = true;
material = dmat.material;
} else {
texCoordsDatas = getTexCoordsDatas(-1);
}
var i:int;
if (texCoordsDatas != null) {
var numTexCoords:int = texCoordsDatas.length;
if (numTexCoords > 32) {
numTexCoords = 32;
texCoordsDatas.length = 32;
// Предупреждение
}
// Находим тангенты и бинормали для основного канала
if (biNormalsInputs.length > 0 && tangentsInputs.length > 0) {
var mainSet:int = (texCoordsDatas[0] as TexCoordsData).inputSet;
var bt:DaeInput;
var bn:DaeInput;
for (i = 0; i < numTexCoords;i++) {
bn = biNormalsInputs[i];
if (bn.setNum == mainSet) {
biNormalsInput = bn;
}
bt = tangentsInputs[i];
if (bt.setNum == mainSet) {
tangentsInput = bt;
}
if (tangentsInput && biNormalsInput) {
break;
}
}
if (biNormalsInput == null || tangentsInput == null) {
biNormalsInput = biNormalsInputs[0];
tangentsInput = tangentsInputs[0];
}
var biTangentsSource:DaeSource = tangentsInput.prepareSource(3);
var biNormalsSource:DaeSource = biNormalsInput.prepareSource(3);
tangents = biTangentsSource.numbers;
tangentsStride = biTangentsSource.stride;
biNormals = biNormalsSource.numbers;
biNormalsStride = biNormalsSource.stride;
geometry.hasTangents = true;
}
// Создаем новые канналы и сохраняем ссылки на них
if (numTexCoords > 1) {
if (geometry.uvChannels == null) {
geometry.uvChannels = [];
}
for (i = 1; i < numTexCoords; i++) {
var texCoordsData:TexCoordsData = texCoordsDatas[i];
// В массиве uvChannels первый каннал - нулевой индекс
var channel:Vector.<Point> = geometry.uvChannels[int(i - 1)];
if (channel == null) {
channel = new Vector.<Point>(geometry.vertexIdCounter + 1);
geometry.uvChannels[int(i - 1)] = channel;
geometry.numAdditionalUVChannels++;
}
texCoordsData.channel = channel;
}
}
for each (var vertex:Vertex in vertices) {
var attributes:Vector.<Number> = vertex._attributes;
if (attributes != null) {
// Устанавливаем для вершин attributes[texIndex] в -2, чтобы создался дубликат вершины
while (vertex != null) {
attributes = vertex._attributes;
var numAttributes:int = attributes.length;
for (i = 0; i < numTexCoords; i++) {
attributes[i] = -2;
}
vertex = vertex.value;
}
}
}
}
var indicesXML:XML;
var indices:Array;
// var normals:Vector.<Vector3D>;
if (normalsInput) {
// normals = fillNormals(normalsInput.prepareSource(3));
// geometry.needCalculateNormals = false;
var normalsSource:DaeSource = normalsInput.prepareSource(3);
normals = normalsSource.numbers;
normalsStride = normalsSource.stride;
geometry.hasNormals = true;
}
switch (this.type) {
case "polygons" : {
if (data.ph.length() > 0) {
// Полигоны с дырками не поддерживаются
// document.logger.lo
}
var indicesList:XMLList = data.p;
var count:int = indicesList.length()
for (i = 0; i < count; i++) {
indices = parseIntsArray(indicesList[i]);
fillInPolygon(geometry, material, vertices, verticesInput.offset, indices.length/inputsStride, indices, texCoordsDatas);
}
break;
}
case "polylist" : {
indicesXML = data.p[0];
if (indicesXML == null) {
document.logger.logNotEnoughDataError(data);
return;
}
indices = parseIntsArray(indicesXML);
var vcountsXML:XML = data.vcount[0];
var vcounts:Array;
if (vcountsXML != null) {
vcounts = parseIntsArray(vcountsXML);
if (vcounts.length < numPrimitives) {
return;
}
fillInPolylist(geometry, material, vertices, verticesInput.offset, numPrimitives, indices, vcounts, texCoordsDatas);
} else {
fillInPolygon(geometry, material, vertices, verticesInput.offset, numPrimitives, indices, texCoordsDatas);
}
break;
}
case "triangles" : {
indicesXML = data.p[0];
if (indicesXML == null) {
document.logger.logNotEnoughDataError(data);
return;
}
indices = parseIntsArray(indicesXML);
fillInTriangles(geometry, material, vertices, verticesInput.offset, numPrimitives, indices, texCoordsDatas);
break;
}
}
}
private function setUVChannels(geometry:Geometry, vertex:Vertex, texCoordsDatas:Vector.<TexCoordsData>):void {
var numTexCoords:int = texCoordsDatas.length;
var attributes:Vector.<Number> = new Vector.<Number>(numTexCoords);
vertex._attributes = attributes;
for (var i:int = 0; i < numTexCoords; i++) {
var texCoordsData:TexCoordsData = texCoordsDatas[i];
attributes[i] = texCoordsData.index;
var index:int = texCoordsData.stride*texCoordsData.index;
var values:Vector.<Number> = texCoordsData.values;
var u:Number = values[index];
var v:Number = 1 - values[int(index + 1)];
if (i > 0) {
texCoordsData.channel[vertex.index] = new Point(u, v);
} else {
vertex._u = u;
vertex._v = v;
}
}
}
/**
* Добавляет uv координаты вершине или создает новую вершину, если нельзя добавить в эту.
* Новая вершина добавляется в список value этой вершины.
* @return вершина с заданными uv координатами
*/
private function applyUV(geometry:Geometry, vertex:Vertex, texCoordsDatas:Vector.<TexCoordsData>):Vertex {
var attributes:Vector.<Number> = vertex._attributes;
var i:int;
var numTexCoords:int = texCoordsDatas.length;
var texCoordsData:TexCoordsData;
if (attributes == null) {
// Свободная вершина
setUVChannels(geometry, vertex, texCoordsDatas);
return vertex;
}
for (i = 0; i < numTexCoords; i++) {
texCoordsData = texCoordsDatas[i];
if (attributes[i] != texCoordsData.index) {
// Ищем дубликат, который соответствует вершине
while (vertex.value != null) {
vertex = vertex.value;
for (i = 0; i < numTexCoords; i++) {
texCoordsData = texCoordsDatas[i];
if (vertex._attributes[i] != texCoordsData.index) {
break;
}
}
if (i == numTexCoords) {
// Идентичный вертекс
return vertex;
}
}
// Последний элемент, создаем дубликат и возвращаем
var newVertex:Vertex = geometry.addVertex(vertex._x, vertex._y, vertex._z);
vertex.value = newVertex;
newVertex.index = geometry.vertexIdCounter;
if (normalsInput) {
newVertex.normal = vertex.normal.clone();
if (tangentsInput) {
newVertex.tangent = vertex.tangent.clone();
}
}
// Копируем канналы
setUVChannels(geometry, newVertex, texCoordsDatas);
return newVertex;
}
}
// Вертекс уже имеет идентичный маппинг
return vertex;
}
/**
* Создает один полигон.
*/
private function fillInPolygon(geometry:Geometry, material:Material, vertices:Vector.<Vertex>, verticesOffset:int, numIndices:int, indices:Array, texCoordsDatas:Vector.<TexCoordsData> = null):void {
var faceVertices:Vector.<Vertex> = new Vector.<Vertex>(numIndices);
for (var i:int = 0; i < numIndices; i++) {
var index:int = inputsStride*i;
var vertex:Vertex = vertices[indices[int(index + verticesOffset)]];
if (normalsInput && vertex.normal == null) {
var normalIndex:int = indices[index + normalsInput.offset]*normalsStride;
vertex.normal = new Vector3D(normals[normalIndex], normals[normalIndex + 1], normals[normalIndex + 2]);
}
if (texCoordsDatas != null) {
var numTexCoordsDatas:int = texCoordsDatas.length;
for (var t:int = 0; t < numTexCoordsDatas; t++) {
var texCoords:TexCoordsData = texCoordsDatas[t];
texCoords.index = indices[int(inputsStride*i + texCoords.offset)];
}
vertex = applyUV(geometry, vertex, texCoordsDatas);
}
faceVertices[i] = vertex;
}
addFaceToGeometry(geometry, faceVertices);
}
private function fillInPolylist(geometry:Geometry, material:Material, vertices:Vector.<Vertex>, verticesOffset:int, numFaces:int, indices:Array, vcounts:Array, texCoordsDatas:Vector.<TexCoordsData> = null):void {
var polyIndex:int = 0;
for (var i:int = 0; i < numFaces; i++) {
var count:int = vcounts[i];
if (count >= 3) {
var faceVertices:Vector.<Vertex> = new Vector.<Vertex>(count);
for (var j:int = 0; j < count; j++) {
var vertexIndex:int = inputsStride*(polyIndex + j);
var vertex:Vertex = vertices[indices[int(vertexIndex + verticesOffset)]];
if (normalsInput && vertex.normal == null) {
var normalIndex:int = indices[vertexIndex + normalsInput.offset]*normalsStride;
vertex.normal = new Vector3D(normals[normalIndex], normals[normalIndex + 1], normals[normalIndex + 2]);
}
if (texCoordsDatas != null) {
var numTexCoordsDatas:int = texCoordsDatas.length;
for (var t:int = 0; t < numTexCoordsDatas; t++) {
var texCoords:TexCoordsData = texCoordsDatas[t];
texCoords.index = indices[int(vertexIndex + texCoords.offset)];
}
vertex = applyUV(geometry, vertex, texCoordsDatas);
}
faceVertices[j] = vertex;
}
addFaceToGeometry(geometry, faceVertices);
polyIndex += count;
}
}
}
private function fillBiNormalDirectional(normal:Vector3D, tangent:Vector3D, biNormalX:Number, biNormalY:Number, biNormalZ:Number):void {
var crossX:Number = normal.y*tangent.z - normal.z*tangent.y;
var crossY:Number = normal.z*tangent.x - normal.x*tangent.z;
var crossZ:Number = normal.x*tangent.y - normal.y*tangent.x;
var dot:Number = crossX*biNormalX + crossY*biNormalY + crossZ*biNormalZ;
tangent.w = dot < 0 ? -1 : 1;
}
private function fillInTriangles(geometry:Geometry, material:Material, vertices:Vector.<Vertex>, verticesOffset:int, numFaces:int, indices:Array, texCoordsDatas:Vector.<TexCoordsData> = null):void {
for (var i:int = 0; i < numFaces; i++) {
var index:int = 3*inputsStride*i;
var vertexIndex:int = index + verticesOffset;
var a:Vertex = vertices[indices[int(vertexIndex)]];
var b:Vertex = vertices[indices[int(vertexIndex + inputsStride)]];
var c:Vertex = vertices[indices[int(vertexIndex + 2*inputsStride)]];
if (normalsInput) {
var normalIndex:int = index + normalsInput.offset;
var nIndex:int;
if (a.normal == null) {
nIndex = indices[normalIndex]*normalsStride;
a.normal = new Vector3D(normals[nIndex], normals[nIndex + 1], normals[nIndex + 2]);
}
if (b.normal == null) {
nIndex = indices[normalIndex + inputsStride]*normalsStride;
b.normal = new Vector3D(normals[nIndex], normals[nIndex + 1], normals[nIndex + 2]);
}
if (c.normal == null) {
nIndex = indices[normalIndex + 2*inputsStride]*normalsStride;
c.normal = new Vector3D(normals[nIndex], normals[nIndex + 1], normals[nIndex + 2]);
}
// if (a.normal == null || b.normal == null || c.normal == null) {
// trace("NRM:");
// }
if (tangentsInput && biNormalsInput) {
var tangentIndex:int = index + tangentsInput.offset;
var btIndex:int;
var biNormalIndex:int = index + biNormalsInput.offset;
var bnIndex:int;
if (a.tangent == null) {
btIndex = indices[tangentIndex]*tangentsStride;
a.tangent = new Vector3D(tangents[btIndex], tangents[btIndex + 1], tangents[btIndex + 2]);
bnIndex = indices[biNormalIndex]*biNormalsStride;
fillBiNormalDirectional(a.normal, a.tangent, biNormals[bnIndex], biNormals[bnIndex + 1], biNormals[bnIndex + 2]);
}
if (b.tangent == null) {
btIndex = indices[tangentIndex + inputsStride]*tangentsStride;
b.tangent = new Vector3D(tangents[btIndex], tangents[btIndex + 1], tangents[btIndex + 2]);
bnIndex = indices[biNormalIndex + inputsStride]*biNormalsStride;
fillBiNormalDirectional(b.normal, b.tangent, biNormals[bnIndex], biNormals[bnIndex + 1], biNormals[bnIndex + 2]);
}
if (c.tangent == null) {
btIndex = indices[tangentIndex + 2*inputsStride]*tangentsStride;
c.tangent = new Vector3D(tangents[btIndex], tangents[btIndex + 1], tangents[btIndex + 2]);
bnIndex = indices[biNormalIndex + 2*inputsStride]*biNormalsStride;
fillBiNormalDirectional(c.normal, c.tangent, biNormals[bnIndex], biNormals[bnIndex + 1], biNormals[bnIndex + 2]);
}
}
}
if (texCoordsDatas != null) {
var t:int;
var texCoords:TexCoordsData;
var numTexCoordsDatas:int = texCoordsDatas.length;
for (t = 0; t < numTexCoordsDatas; t++) {
texCoords = texCoordsDatas[t];
texCoords.index = indices[int(index + texCoords.offset)];
}
a = applyUV(geometry, a, texCoordsDatas);
for (t = 0; t < numTexCoordsDatas; t++) {
texCoords = texCoordsDatas[t];
texCoords.index = indices[int(index + texCoords.offset + inputsStride)];
}
b = applyUV(geometry, b, texCoordsDatas);
for (t = 0; t < numTexCoordsDatas; t++) {
texCoords = texCoordsDatas[t];
texCoords.index = indices[int(index + texCoords.offset + 2*inputsStride)];
}
c = applyUV(geometry, c, texCoordsDatas);
}
addTriFaceToGeometry(geometry, a, b, c);
}
}
private function addTriFaceToGeometry(geometry:Geometry, a:Vertex, b:Vertex, c:Vertex):void {
var face:Face = new Face();
var aWrapper:Wrapper = new Wrapper();
aWrapper.vertex = a;
var bWrapper:Wrapper = new Wrapper();
bWrapper.vertex = b;
var cWrapper:Wrapper = new Wrapper();
cWrapper.vertex = c;
aWrapper.next = bWrapper;
bWrapper.next = cWrapper;
cWrapper.next = null;
face.wrapper = aWrapper;
face.geometry = geometry;
geometry._faces[int(geometry.faceIdCounter++)] = face;
}
private function addFaceToGeometry(geometry:Geometry, value:Vector.<Vertex>):void {
var face:Face = new Face();
var last:Wrapper = null;
for (var i:int = 0, count:int = value.length; i < count; i++) {
var newWrapper:Wrapper = new Wrapper();
newWrapper.vertex = value[i];
if (last != null) {
last.next = newWrapper;
} else {
face.wrapper = newWrapper;
}
last = newWrapper;
}
face.geometry = geometry;
geometry._faces[int(geometry.faceIdCounter++)] = face;
}
/**
* Сравнивает вершины, используемые в примитиве с указанными
* Перед использованием вызвать parse().
*/
public function verticesEquals(otherVertices:DaeVertices):Boolean {
var vertices:DaeVertices = document.findVertices(verticesInput.source);
if (vertices == null) {
document.logger.logNotFoundError(verticesInput.source);
}
return vertices == otherVertices;
}
public function get materialSymbol():String {
var attr:XML = data.@material[0];
return (attr == null) ? null : attr.toString();
}
}
}
import __AS3__.vec.Vector;
import flash.geom.Point;
class TexCoordsData {
public var values:Vector.<Number>;
public var stride:int;
public var offset:int;
public var index:int;
public var channel:Vector.<Point>;
public var inputSet:int;
public function TexCoordsData(values:Vector.<Number>, stride:int, offset:int, inputSet:int) {
this.values = values;
this.stride = stride;
this.offset = offset;
this.inputSet = inputSet;
}
}

View File

@@ -0,0 +1,104 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.animation.Track;
import alternativa.engine3d.animation.keys.MatrixKey;
import alternativa.engine3d.animation.keys.PointKey;
import alternativa.engine3d.animation.keys.ValueKey;
import flash.geom.Matrix3D;
/**
* @private
*/
public class DaeSampler extends DaeElement {
use namespace collada;
private var times:Vector.<Number>;
private var values:Vector.<Number>;
private var timesStride:int;
private var valuesStride:int;
public function DaeSampler(data:XML, document:DaeDocument) {
super(data, document);
}
override protected function parseImplementation():Boolean {
var inputsList:XMLList = data.input;
var inputSource:DaeSource;
var outputSource:DaeSource;
for (var i:int = 0, count:int = inputsList.length(); i < count; i++) {
var input:DaeInput = new DaeInput(inputsList[i], document);
var semantic:String = input.semantic;
if (semantic != null) {
switch (semantic) {
case "INPUT" :
inputSource = input.prepareSource(1);
if (inputSource != null) {
times = inputSource.numbers;
timesStride = inputSource.stride;
}
break;
case "OUTPUT" :
outputSource = input.prepareSource(1);
if (outputSource != null) {
values = outputSource.numbers;
valuesStride = outputSource.stride;
}
break;
}
}
}
return true;
}
public function parseValuesTrack():Track {
if (times != null && values != null && timesStride > 0) {
var track:Track = new Track();
var count:int = times.length/timesStride;
for (var i:int = 0; i < count; i++) {
track.addKey(new ValueKey(times[int(timesStride*i)], values[int(valuesStride*i)]));
}
track.sortKeys();
// TODO:: Всякие исключительные ситуации с индексами
return track;
}
return null;
}
public function parseMatrixTrack():Track {
if (times != null && values != null && timesStride != 0) {
var track:Track = new Track();
var count:int = times.length/timesStride;
for (var i:int = 0; i < count; i++) {
var index:int = valuesStride*i;
var matrix:Matrix3D = new Matrix3D(Vector.<Number>([values[index], values[index + 4], values[index + 8], values[index + 12],
values[index + 1], values[index + 5], values[index + 9], values[index + 13],
values[index + 2], values[index + 6], values[index + 10], values[index + 14],
values[index + 3] ,values[index + 7], values[index + 11], values[index + 15]]));
track.addKey(new MatrixKey(times[i*timesStride], matrix));
}
track.sortKeys();
return track;
}
return null;
}
public function parsePointsTrack():Track {
if (times != null && values != null && timesStride != 0) {
var track:Track = new Track();
var count:int = times.length/timesStride;
for (var i:int = 0; i < count; i++) {
var index:int = i*valuesStride;
track.addKey(new PointKey(times[i*timesStride], values[index], values[index + 1], values[index + 2]));
}
track.sortKeys();
return track;
// TODO:: Всякие исключительные ситуации с индексами
}
return null;
}
}
}

View File

@@ -0,0 +1,153 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeSource extends DaeElement {
use namespace collada;
/**
* Типы массивов
*/
private const FLOAT_ARRAY:String = "float_array";
private const INT_ARRAY:String = "int_array";
private const NAME_ARRAY:String = "Name_array";
/**
* Массив элементов типа Number.
* Перед использованием вызвать parse().
*/
public var numbers:Vector.<Number>;
/**
* Массив элементов типа int.
* Перед использованием вызвать parse().
*/
public var ints:Vector.<int>;
/**
* Массив элементов типа string.
* Перед использованием вызвать parse().
*/
public var names:Vector.<String>;
/**
* Размерность типов в массиве numbers или ints.
* Перед использованием вызвать parse().
*/
public var stride:int;
public function DaeSource(data:XML, document:DaeDocument) {
super(data, document);
// Внутри <source> объявляются arrays.
constructArrays();
}
private function constructArrays():void {
var children:XMLList = data.children();
for (var i:int = 0, count:int = children.length(); i < count; i++) {
var child:XML = children[i];
switch (child.localName()) {
case FLOAT_ARRAY :
case INT_ARRAY :
case NAME_ARRAY :
var array:DaeArray = new DaeArray(child, document);
if (array.id != null) {
document.arrays[array.id] = array;
}
break;
}
}
}
private function get accessor():XML {
return data.technique_common.accessor[0];
}
override protected function parseImplementation():Boolean {
var accessor:XML = this.accessor;
if (accessor != null) {
var arrayXML:XML = accessor.@source[0];
var array:DaeArray = (arrayXML == null) ? null : document.findArray(arrayXML);
if (array != null) {
var countXML:String = accessor.@count[0];
if (countXML != null) {
var count:int = parseInt(countXML.toString(), 10);
var offsetXML:XML = accessor.@offset[0];
var strideXML:XML = accessor.@stride[0];
var offset:int = (offsetXML == null) ? 0 : parseInt(offsetXML.toString(), 10);
var stride:int = (strideXML == null) ? 1 : parseInt(strideXML.toString(), 10);
array.parse();
if (array.array.length < (offset + (count*stride))) {
document.logger.logNotEnoughDataError(accessor);
return false;
}
this.stride = parseArray(offset, count, stride, array.array, array.type);
return true;
}
} else {
document.logger.logNotFoundError(arrayXML);
}
}
return false;
}
private function numValidParams(params:XMLList):int {
var res:int = 0;
for (var i:int = 0, count:int = params.length(); i < count; i++) {
if (params[i].@name[0] != null) {
res++;
}
}
return res;
}
private function parseArray(offset:int, count:int, stride:int, array:Array, type:String):int {
var params:XMLList = this.accessor.param;
var arrStride:int = Math.max(numValidParams(params), stride);
switch (type) {
case FLOAT_ARRAY:
numbers = new Vector.<Number>(int(arrStride*count));
break;
case INT_ARRAY:
ints = new Vector.<int>(int(arrStride*count));
break;
case NAME_ARRAY:
names = new Vector.<String>(int(arrStride*count));
break;
}
var curr:int = 0;
for (var i:int = 0; i < arrStride; i++) {
// Только param, у которого установлен name, должен быть считан
var param:XML = params[i];
if (param == null || param.hasOwnProperty("@name")) {
var j:int;
switch (type) {
case FLOAT_ARRAY:
for (j = 0; j < count; j++) {
var value:String = array[int(offset + stride*j + i)];
if (value.indexOf(",") != -1) {
value = value.replace(/,/, ".");
}
numbers[int(arrStride*j + curr)] = parseFloat(value);
}
break;
case INT_ARRAY:
for (j = 0; j < count; j++) {
ints[int(arrStride*j + curr)] = parseInt(array[int(offset + stride*j + i)], 10);
}
break;
case NAME_ARRAY:
for (j = 0; j < count; j++) {
names[int(arrStride*j + curr)] = array[int(offset + stride*j + i)];
}
break;
}
curr++;
}
}
return arrStride;
}
}
}

View File

@@ -0,0 +1,62 @@
package alternativa.engine3d.loaders.collada {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
use namespace alternativa3d;
/**
* @private
*/
public class DaeVertices extends DaeElement {
use namespace collada;
/**
* Источник данных координат вершин. Содержит координаты в массиве numbers.
* Свойство stride источника не меньше трех.
* Перед использованием вызвать parse().
*/
private var positions:DaeSource;
//private var texCoords:Vector.<DaeSource>;
public function DaeVertices(data:XML, document:DaeDocument) {
super(data, document);
}
override protected function parseImplementation():Boolean {
// Получаем массив координат вершин
var inputXML:XML = data.input.(@semantic == "POSITION")[0];
if (inputXML != null) {
positions = (new DaeInput(inputXML, document)).prepareSource(3);
if (positions != null) {
return true;
}
}
return false;
}
/**
* Создает вершины в меше. У каждой вершины index устанавливается в значение позиции в массиве.
* Перед использованием вызвать parse().
*
* @return вектор вершин и их индексов
*/
public function fillInMesh(geometry:Geometry):Vector.<Vertex> {
var stride:int = positions.stride;
var coords:Vector.<Number> = positions.numbers;
var numVerts:int = positions.numbers.length/stride;
var createdVertices:Vector.<Vertex> = new Vector.<Vertex>(numVerts);
var i:int;
for (i = 0; i < numVerts; i++) {
var offset:int = stride*i;
var newVertex:Vertex = geometry.addVertex(coords[offset], coords[int(offset + 1)], coords[int(offset + 2)], 0, 0, i);
newVertex.index = i;
createdVertices[i] = newVertex;
}
geometry.vertexIdCounter = numVerts - 1;
return createdVertices;
}
}
}

View File

@@ -0,0 +1,33 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public class DaeVisualScene extends DaeElement {
use namespace collada;
public var nodes:Vector.<DaeNode>;
public function DaeVisualScene(data:XML, document:DaeDocument) {
super(data, document);
// Внутри <visual_scene> объявляются node.
constructNodes();
}
public function constructNodes():void {
var nodesList:XMLList = data.node;
var count:int = nodesList.length();
nodes = new Vector.<DaeNode>(count);
for (var i:int = 0; i < count; i++) {
var node:DaeNode = new DaeNode(nodesList[i], document, this);
if (node.id != null) {
document.nodes[node.id] = node;
}
nodes[i] = node;
}
}
}
}

View File

@@ -0,0 +1,7 @@
package alternativa.engine3d.loaders.collada {
/**
* @private
*/
public namespace collada = "http://www.collada.org/2005/11/COLLADASchema";
}

View File

@@ -0,0 +1,54 @@
package alternativa.engine3d.loaders.events {
import flash.events.ErrorEvent;
import flash.events.Event;
/**
* Событие, рассылаемое при ошибке загрузки.
*/
public class LoaderErrorEvent extends ErrorEvent {
/**
* Событие рассылается, когда происходит ошибка загрузки.
*/
public static const LOADER_ERROR:String = "loaderError";
private var _url:String;
/**
* Создаёт новый экземпляр.
* @param type Тип события.
* @param url Адрес файла, при загрузке которого произошла проблема.
* @param text Описание ошибки.
*/
public function LoaderErrorEvent(type:String, url:String, text:String) {
super(type);
this.text = text;
_url = url;
}
/**
* Адрес файла, при загрузке которого произошла проблема.
*/
public function get url():String {
return _url;
}
/**
* Клонирует объект.
* @return Точная копия объекта.
*/
override public function clone():Event {
return new LoaderErrorEvent(type, _url, text);
}
/**
* Создаёт строковое представление объекта.
* @return Строковое представление объекта.
*/
override public function toString():String {
return "[LoaderErrorEvent url=" + _url + ", text=" + text + "]";
}
}
}

View File

@@ -0,0 +1,75 @@
package alternativa.engine3d.loaders.events {
import flash.events.Event;
/**
* Событие загрузчиков ресурсов, состоящих из нескольких частей.
*/
public class LoaderEvent extends Event {
/**
* Событие начала загрузки очередной части ресурса.
*/
public static const PART_OPEN:String = "partOpen";
/**
* Событие окончания загрузки очередной части ресурса.
*/
public static const PART_COMPLETE:String = "partComplete";
private var _partsTotal:int;
private var _currentPart:int;
private var _target:Object;
/**
* Создаёт новый экземпляр.
* @param type Тип события.
* @param partsTotal Общее количество загружаемых частей.
* @param currentPart Номер части, к которому относится событие. Нумерация начинается с нуля.
* @param target Объект, к которому относится событие.
*/
public function LoaderEvent(type:String, partsTotal:int, currentPart:int, target:Object = null) {
super(type);
_partsTotal = partsTotal;
_currentPart = currentPart;
_target = target;
}
/**
* Общее количество загружаемых частей.
*/
public function get partsTotal():int {
return _partsTotal;
}
/**
* Номер части, к которому относится событие. Нумерация начинается с нуля.
*/
public function get currentPart():int {
return _currentPart;
}
/**
* Объект, содержащийся в событии.
*/
override public function get target():Object {
return _target;
}
/**
* Клонирует объект.
* @return Точная копия объекта.
*/
override public function clone():Event {
return new LoaderEvent(type, _partsTotal, _currentPart, _target);
}
/**
* Создаёт строкове представление объекта.
* @return Строкове представление объекта.
*/
override public function toString():String {
return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", target=" + _target + "]";
}
}
}

View File

@@ -0,0 +1,73 @@
package alternativa.engine3d.loaders.events {
import flash.events.Event;
import flash.events.ProgressEvent;
/**
* Событие прогресса загрузки ресурсов, состоящих из нескольких частей.
*/
public class LoaderProgressEvent extends ProgressEvent {
/**
* Событие прогресса загрузки очередной части ресурса.
*/
public static const LOADER_PROGRESS:String = "loaderProgress";
private var _filesTotal:int;
private var _filesLoaded:int;
private var _totalProgress:Number = 0;
/**
* Создаёт новый экземпляр.
* @param type Тип события.
* @param filesTotal Общее количество загружаемых файлов.
* @param filesLoaded Количество полностью загруженных файлов.
* @param totalProgress Общий прогресс загрузки, выраженный числом в интервале <code>[0, 1]</code>.
* @param bytesLoaded Количество загруженных байт загружаемого в данный момент файла.
* @param bytesTotal Объём загружаемого в данный момент файла.
*/
public function LoaderProgressEvent(type:String, filesTotal:int, filesLoaded:int, totalProgress:Number = 0, bytesLoaded:uint = 0, bytesTotal:uint = 0) {
super(type, false, false, bytesLoaded, bytesTotal);
_filesTotal = filesTotal;
_filesLoaded = filesLoaded;
_totalProgress = totalProgress;
}
/**
* Общее количество загружаемых файлов.
*/
public function get filesTotal():int {
return _filesTotal;
}
/**
* Количество полностью загруженных файлов.
*/
public function get filesLoaded():int {
return _filesLoaded;
}
/**
* Общий прогресс загрузки, выраженный числом в интервале <code>[0, 1]</code>.
*/
public function get totalProgress():Number {
return _totalProgress;
}
/**
* Клонирует объект.
* @return Точная копия объекта.
*/
override public function clone():Event {
return new LoaderProgressEvent(type, _filesTotal, _filesLoaded, _totalProgress, bytesLoaded, bytesTotal);
}
/**
* Создаёт строкове представление объекта.
* @return Строкове представление объекта.
*/
override public function toString():String {
return "[LoaderProgressEvent filesTotal=" + _filesTotal + ", filesLoaded=" + _filesLoaded + ", totalProgress=" + _totalProgress.toFixed(2) + "]";
}
}
}

View File

@@ -0,0 +1,28 @@
package alternativa.engine3d.loaders.events {
import __AS3__.vec.Vector;
import flash.display.BitmapData;
import flash.display3D.Texture3D;
import flash.events.Event;
public class TexturesLoaderEvent extends Event {
private var bitmapDatas:Vector.<BitmapData>;
private var textures3D:Vector.<Texture3D>;
public function TexturesLoaderEvent(type:String, bitmapDatas:Vector.<BitmapData>, textures3D:Vector.<Texture3D> = null) {
this.bitmapDatas = bitmapDatas;
this.textures3D = textures3D;
super(type, false, false);
}
public function getBitmapDatas():Vector.<BitmapData> {
return bitmapDatas;
}
public function getTextures3D():Vector.<Texture3D> {
return textures3D;
}
}
}

View File

@@ -0,0 +1,718 @@
// ================================================================================
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2010 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
// ================================================================================
package alternativa.engine3d.materials
{
// ===========================================================================
// Imports
// ---------------------------------------------------------------------------
import flash.utils.*;
import flash.display3D.*;
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
public class AGALMiniAssembler
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
// AGAL bytes and error buffer
private var _agalcode:ByteArray = new ByteArray();
private var _error:String = "";
private var debugEnabled:Boolean = false;
private static var initialized:Boolean = false;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get error():String { return _error; }
public function get agalcode():ByteArray { return _agalcode; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function AGALMiniAssembler( debugging:Boolean = false ):void
{
debugEnabled = debugging;
if ( !initialized )
init();
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function assemble( mode:String, source:String, verbose:Boolean = false ):ByteArray
{
var start:uint = getTimer();
agalcode.length = 0;
_error = "";
var isFrag:Boolean = false;
if ( mode == FRAGMENT )
isFrag = true
else if ( mode != VERTEX )
_error = "error: mode needs to be FRAGMENT or VERTEX but is " + mode + ".";
agalcode.endian = Endian.LITTLE_ENDIAN;
agalcode.writeByte( 0xa0 ); // tag version
agalcode.writeUnsignedInt( 0x1 ); // AGAL version, big endian, bit pattern will be 0x01000000
agalcode.writeByte( 0xa1 ); // tag program id
agalcode.writeByte( isFrag ? 1 : 0 ); // vertex or fragment
var lines:Array = source.replace( /[\f\n\r\v]+/g, "\n" ).split( "\n" );
var nest:int = 0;
var nops:int = 0;
var i:int;
var lng:int = lines.length;
for ( i = 0; i < lng && _error == ""; i++ )
{
var line:String = new String( lines[i] );
// remove comments
var startcomment:int = line.search( "//" );
if ( startcomment != -1 )
line = line.slice( 0, startcomment );
// grab options
var optsi:int = line.search( /<.*>/g );
var opts:Array;
if ( optsi != -1 )
{
opts = line.slice( optsi ).match( /(\w+)/gi );
line = line.slice( 0, optsi );
}
// find opcode
var opCode:Array = line.match( /^\w{3}/ig );
var opFound:OpCode = OPMAP[ opCode[0] ];
// if debug is enabled, output the opcodes
if ( debugEnabled )
trace( opFound );
if ( opFound == null )
{
if ( line.length >= 3 )
trace( "warning: bad line "+i+": "+lines[i] );
continue;
}
line = line.slice( line.search( opFound.name ) + opFound.name.length );
// nesting check
if ( opFound.flags & OP_DEC_NEST )
{
nest--;
if ( nest < 0 )
{
_error = "error: conditional closes without open.";
break;
}
}
if ( opFound.flags & OP_INC_NEST )
{
nest++;
if ( nest > MAX_NESTING )
{
_error = "error: nesting to deep, maximum allowed is "+MAX_NESTING+".";
break;
}
}
if ( ( opFound.flags & OP_FRAG_ONLY ) && !isFrag )
{
_error = "error: opcode is only allowed in fragment programs.";
break;
}
if ( verbose )
trace( "emit opcode=" + opFound );
agalcode.writeUnsignedInt( opFound.emitCode );
nops++;
if ( nops > MAX_OPCODES )
{
_error = "error: too many opcodes. maximum is "+MAX_OPCODES+".";
break;
}
// get operands, use regexp
var regs:Array = line.match( /vc\[([vof][actps]?)(\d*)?(\.[xyzw](\+\d{1,3})?)?\](\.[xyzw]{1,4})?|([vof][actps]?)(\d*)?(\.[xyzw]{1,4})?/gi );
if ( regs.length != opFound.numRegister )
{
_error = "error: wrong number of operands. found "+regs.length+" but expected "+opFound.numRegister+".";
break;
}
var badreg:Boolean = false;
var pad:uint = 64 + 64 + 32;
var regLength:uint = regs.length;
for ( var j:int = 0; j < regLength; j++ )
{
var isRelative:Boolean = false;
var relreg:Array = regs[ j ].match( /\[.*\]/ig );
if ( relreg.length > 0 )
{
regs[ j ] = regs[ j ].replace( relreg[ 0 ], "0" );
if ( verbose )
trace( "IS REL" );
isRelative = true;
}
var res:Array = regs[j].match( /^\b[A-Za-z]{1,2}/ig );
var regFound:Register = REGMAP[ res[ 0 ] ];
// if debug is enabled, output the registers
if ( debugEnabled )
trace( regFound );
if ( regFound == null )
{
_error = "error: could not parse operand "+j+" ("+regs[j]+").";
badreg = true;
break;
}
if ( isFrag )
{
if ( !( regFound.flags & REG_FRAG ) )
{
_error = "error: register operand "+j+" ("+regs[j]+") only allowed in vertex programs.";
badreg = true;
break;
}
if ( isRelative )
{
_error = "error: register operand "+j+" ("+regs[j]+") relative adressing not allowed in fragment programs.";
badreg = true;
break;
}
}
else
{
if ( !( regFound.flags & REG_VERT ) )
{
_error = "error: register operand "+j+" ("+regs[j]+") only allowed in fragment programs.";
badreg = true;
break;
}
}
regs[j] = regs[j].slice( regs[j].search( regFound.name ) + regFound.name.length );
//trace( "REGNUM: " +regs[j] );
var idxmatch:Array = isRelative ? relreg[0].match( /\d+/ ) : regs[j].match( /\d+/ );
var regidx:uint = 0;
if ( idxmatch )
regidx = uint( idxmatch[0] );
if ( regFound.range < regidx )
{
_error = "error: register operand "+j+" ("+regs[j]+") index exceeds limit of "+(regFound.range+1)+".";
badreg = true;
break;
}
var regmask:uint = 0;
var maskmatch:Array = regs[j].match( /(\.[xyzw]{1,4})/ );
var isDest:Boolean = ( j == 0 && !( opFound.flags & OP_NO_DEST ) );
var isSampler:Boolean = ( j == 2 && ( opFound.flags & OP_SPECIAL_TEX ) );
var reltype:uint = 0;
var relsel:uint = 0;
var reloffset:int = 0;
if ( isDest && isRelative )
{
_error = "error: relative can not be destination";
badreg = true;
break;
}
if ( maskmatch )
{
regmask = 0;
var cv:uint;
var maskLength:uint = maskmatch[0].length;
for ( var k:int = 1; k < maskLength; k++ )
{
cv = maskmatch[0].charCodeAt(k) - "x".charCodeAt(0);
if ( cv > 2 )
cv = 3;
if ( isDest )
regmask |= 1 << cv;
else
regmask |= cv << ( ( k - 1 ) << 1 );
}
if ( !isDest )
for ( ; k <= 4; k++ )
regmask |= cv << ( ( k - 1 ) << 1 ) // repeat last
}
else
{
regmask = isDest ? 0xf : 0xe4; // id swizzle or mask
}
if ( isRelative )
{
var relname:Array = relreg[0].match( /[A-Za-z]{1,2}/ig );
var regFoundRel:Register = REGMAP[ relname[0]];
if ( regFoundRel == null )
{
_error = "error: bad index register";
badreg = true;
break;
}
reltype = regFoundRel.emitCode;
var selmatch:Array = relreg[0].match( /(\.[xyzw]{1,1})/ );
if ( selmatch.length==0 )
{
_error = "error: bad index register select";
badreg = true;
break;
}
relsel = selmatch[0].charCodeAt(1) - "x".charCodeAt(0);
if ( relsel > 2 )
relsel = 3;
var relofs:Array = relreg[0].match( /\+\d{1,3}/ig );
if ( relofs.length > 0 )
reloffset = relofs[0];
if ( reloffset < 0 || reloffset > 255 )
{
_error = "error: index offset "+reloffset+" out of bounds. [0..255]";
badreg = true;
break;
}
if ( verbose )
trace( "RELATIVE: type="+reltype+"=="+relname[0]+" sel="+relsel+"=="+selmatch[0]+" idx="+regidx+" offset="+reloffset );
}
if ( verbose )
trace( " emit argcode="+regFound+"["+regidx+"]["+regmask+"]" );
if ( isDest )
{
agalcode.writeShort( regidx );
agalcode.writeByte( regmask );
agalcode.writeByte( regFound.emitCode );
pad -= 32;
} else
{
if ( isSampler )
{
if ( verbose )
trace( " emit sampler" );
var samplerbits:uint = 5; // type 5
var optsLength:uint = opts.length;
for ( k = 0; k<optsLength; k++ )
{
if ( verbose )
trace( " opt: "+opts[k] );
var optfound:Sampler = SAMPLEMAP [opts[k]];
if ( optfound == null )
{
trace( "Warning, unknown sampler option: "+opts[k] );
}
else
{
if ( optfound.flag != SAMPLER_SPECIAL_SHIFT )
samplerbits &= ~( 0xf << optfound.flag );
samplerbits |= uint( optfound.mask ) << uint( optfound.flag );
}
}
agalcode.writeShort( regidx );
agalcode.writeShort( 0 );
agalcode.writeUnsignedInt( samplerbits );
if ( verbose )
trace( " bits: " + ( samplerbits - 5 ) );
pad -= 64;
}
else
{
if ( j == 0 )
{
agalcode.writeUnsignedInt( 0 );
pad -= 32;
}
agalcode.writeShort( regidx );
agalcode.writeByte( reloffset );
agalcode.writeByte( regmask );
agalcode.writeByte( regFound.emitCode );
agalcode.writeByte( reltype );
agalcode.writeShort( isRelative ? ( relsel | ( 1 << 15 ) ) : 0 );
pad -= 64;
}
}
}
// pad unused regs
for ( j = 0; j < pad; j += 8 )
agalcode.writeByte( 0 );
if ( badreg )
break;
}
if ( _error != "" )
{
_error += "\n at line " + i + " " + lines[i];
agalcode.length = 0;
trace( _error );
}
// trace the bytecode bytes if debugging is enabled
if ( debugEnabled )
{
var dbgLine:String = "generated bytecode:";
var agalLength:uint = agalcode.length;
for ( var index:uint = 0; index < agalLength; index++ )
{
if ( !( index % 16 ) )
dbgLine += "\n";
if ( !( index % 4 ) )
dbgLine += " ";
var byteStr:String = agalcode[ index ].toString( 16 );
if ( byteStr.length < 2 )
byteStr = "0" + byteStr;
dbgLine += byteStr;
}
trace( dbgLine );
}
if ( verbose )
trace( "AGALMiniAssembler.assemble time: " + ( ( getTimer() - start ) / 1000 ) + "s" );
return agalcode;
}
static private function init():void
{
initialized = true;
// Fill the dictionaries with opcodes and registers
OPMAP[ MOV ] = new OpCode( MOV, 2, 0x00, 0 );
OPMAP[ ADD ] = new OpCode( ADD, 3, 0x01, 0 );
OPMAP[ SUB ] = new OpCode( SUB, 3, 0x02, 0 );
OPMAP[ MUL ] = new OpCode( MUL, 3, 0x03, 0 );
OPMAP[ DIV ] = new OpCode( DIV, 3, 0x04, 0 );
OPMAP[ RCP ] = new OpCode( RCP, 2, 0x05, 0 );
OPMAP[ MIN ] = new OpCode( MIN, 3, 0x06, 0 );
OPMAP[ MAX ] = new OpCode( MAX, 3, 0x07, 0 );
OPMAP[ FRC ] = new OpCode( FRC, 2, 0x08, 0 );
OPMAP[ SQT ] = new OpCode( SQT, 2, 0x09, 0 );
OPMAP[ RSQ ] = new OpCode( RSQ, 2, 0x0a, 0 );
OPMAP[ POW ] = new OpCode( POW, 3, 0x0b, 0 );
OPMAP[ LOG ] = new OpCode( LOG, 2, 0x0c, 0 );
OPMAP[ EXP ] = new OpCode( EXP, 2, 0x0d, 0 );
OPMAP[ NRM ] = new OpCode( NRM, 2, 0x0e, 0 );
OPMAP[ SIN ] = new OpCode( SIN, 2, 0x0f, 0 );
OPMAP[ COS ] = new OpCode( COS, 2, 0x10, 0 );
OPMAP[ CRS ] = new OpCode( CRS, 3, 0x11, 0 );
OPMAP[ DP3 ] = new OpCode( DP3, 3, 0x12, 0 );
OPMAP[ DP4 ] = new OpCode( DP4, 3, 0x13, 0 );
OPMAP[ ABS ] = new OpCode( ABS, 2, 0x14, 0 );
OPMAP[ NEG ] = new OpCode( NEG, 2, 0x15, 0 );
OPMAP[ SAT ] = new OpCode( SAT, 2, 0x16, 0 );
OPMAP[ M33 ] = new OpCode( M33, 3, 0x17, OP_SPECIAL_MATRIX );
OPMAP[ M44 ] = new OpCode( M44, 3, 0x18, OP_SPECIAL_MATRIX );
OPMAP[ M34 ] = new OpCode( M34, 3, 0x19, OP_SPECIAL_MATRIX );
OPMAP[ IFZ ] = new OpCode( IFZ, 1, 0x1a, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ INZ ] = new OpCode( INZ, 1, 0x1b, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ IFE ] = new OpCode( IFE, 2, 0x1c, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ INE ] = new OpCode( INE, 2, 0x1d, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ IFG ] = new OpCode( IFG, 2, 0x1e, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ IFL ] = new OpCode( IFL, 2, 0x1f, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ IEG ] = new OpCode( IEG, 2, 0x20, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ IEL ] = new OpCode( IEL, 2, 0x21, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ ELS ] = new OpCode( ELS, 0, 0x22, OP_NO_DEST | OP_INC_NEST | OP_DEC_NEST );
OPMAP[ EIF ] = new OpCode( EIF, 0, 0x23, OP_NO_DEST | OP_DEC_NEST );
OPMAP[ REP ] = new OpCode( REP, 1, 0x24, OP_NO_DEST | OP_INC_NEST | OP_SCALAR );
OPMAP[ ERP ] = new OpCode( ERP, 0, 0x25, OP_NO_DEST | OP_DEC_NEST );
OPMAP[ BRK ] = new OpCode( BRK, 0, 0x26, OP_NO_DEST );
OPMAP[ KIL ] = new OpCode( KIL, 1, 0x27, OP_NO_DEST | OP_FRAG_ONLY );
OPMAP[ TEX ] = new OpCode( TEX, 3, 0x28, OP_FRAG_ONLY | OP_SPECIAL_TEX );
OPMAP[ SGE ] = new OpCode( SGE, 3, 0x29, 0 );
OPMAP[ SLT ] = new OpCode( SLT, 3, 0x2a, 0 );
OPMAP[ SGN ] = new OpCode( SGN, 2, 0x2b, 0 );
REGMAP[ VA ] = new Register( VA, "vertex attribute", 0x0, 7, REG_VERT | REG_READ );
REGMAP[ VC ] = new Register( VC, "vertex constant", 0x1, 127, REG_VERT | REG_READ );
REGMAP[ VT ] = new Register( VT, "vertex temporary", 0x2, 31, REG_VERT | REG_WRITE | REG_READ );
REGMAP[ OP ] = new Register( OP, "vertex output", 0x3, 0, REG_VERT | REG_WRITE );
REGMAP[ V ] = new Register( V, "varying", 0x4, 7, REG_VERT | REG_FRAG | REG_READ | REG_WRITE );
REGMAP[ FC ] = new Register( FC, "fragment constant", 0x1, 15, REG_FRAG | REG_READ );
REGMAP[ FT ] = new Register( FT, "fragment temporary", 0x2, 15, REG_FRAG | REG_WRITE | REG_READ );
REGMAP[ FS ] = new Register( FS, "texture sampler", 0x5, 7, REG_FRAG | REG_READ );
REGMAP[ OC ] = new Register( OC, "fragment output", 0x3, 0, REG_FRAG | REG_WRITE );
SAMPLEMAP[ D2 ] = new Sampler( D2, SAMPLER_DIM_SHIFT, 0 );
SAMPLEMAP[ D3 ] = new Sampler( D3, SAMPLER_DIM_SHIFT, 2 );
SAMPLEMAP[ CUBE ] = new Sampler( CUBE, SAMPLER_DIM_SHIFT, 1 );
SAMPLEMAP[ MIPNEAREST ] = new Sampler( MIPNEAREST, SAMPLER_MIPMAP_SHIFT, 1 );
SAMPLEMAP[ MIPLINEAR ] = new Sampler( MIPLINEAR, SAMPLER_MIPMAP_SHIFT, 2 );
SAMPLEMAP[ MIPNONE ] = new Sampler( MIPNONE, SAMPLER_MIPMAP_SHIFT, 0 );
SAMPLEMAP[ NOMIP ] = new Sampler( NOMIP, SAMPLER_MIPMAP_SHIFT, 0 );
SAMPLEMAP[ NEAREST ] = new Sampler( NEAREST, SAMPLER_FILTER_SHIFT, 0 );
SAMPLEMAP[ LINEAR ] = new Sampler( LINEAR, SAMPLER_FILTER_SHIFT, 1 );
SAMPLEMAP[ CENTROID ] = new Sampler( CENTROID, SAMPLER_SPECIAL_SHIFT, 1 << 0 );
SAMPLEMAP[ SINGLE ] = new Sampler( SINGLE, SAMPLER_SPECIAL_SHIFT, 1 << 1 );
SAMPLEMAP[ DEPTH ] = new Sampler( DEPTH, SAMPLER_SPECIAL_SHIFT, 1 << 2 );
SAMPLEMAP[ REPEAT ] = new Sampler( REPEAT, SAMPLER_REPEAT_SHIFT, 1 );
SAMPLEMAP[ WRAP ] = new Sampler( WRAP, SAMPLER_REPEAT_SHIFT, 1 );
SAMPLEMAP[ CLAMP ] = new Sampler( CLAMP, SAMPLER_REPEAT_SHIFT, 0 );
}
// ======================================================================
// Constants
// ----------------------------------------------------------------------
private static const OPMAP:Dictionary = new Dictionary();
private static const REGMAP:Dictionary = new Dictionary();
private static const SAMPLEMAP:Dictionary = new Dictionary();
private static const MAX_NESTING:int = 4;
private static const MAX_OPCODES:int = 256;
private static const FRAGMENT:String = Context3DProgramType.FRAGMENT;
private static const VERTEX:String = Context3DProgramType.VERTEX;
// masks and shifts
private static const SAMPLER_DIM_SHIFT:uint = 12;
private static const SAMPLER_SPECIAL_SHIFT:uint = 16;
private static const SAMPLER_REPEAT_SHIFT:uint = 20;
private static const SAMPLER_MIPMAP_SHIFT:uint = 24;
private static const SAMPLER_FILTER_SHIFT:uint = 28;
// regmap flags
private static const REG_WRITE:uint = 0x1;
private static const REG_READ:uint = 0x2;
private static const REG_FRAG:uint = 0x20;
private static const REG_VERT:uint = 0x40;
// opmap flags
private static const OP_SCALAR:uint = 0x1;
private static const OP_INC_NEST:uint = 0x2;
private static const OP_DEC_NEST:uint = 0x4;
private static const OP_SPECIAL_TEX:uint = 0x8;
private static const OP_SPECIAL_MATRIX:uint = 0x10;
private static const OP_FRAG_ONLY:uint = 0x20;
private static const OP_VERT_ONLY:uint = 0x40;
private static const OP_NO_DEST:uint = 0x80;
// opcodes
private static const MOV:String = "mov";
private static const ADD:String = "add";
private static const SUB:String = "sub";
private static const MUL:String = "mul";
private static const DIV:String = "div";
private static const RCP:String = "rcp";
private static const MIN:String = "min";
private static const MAX:String = "max";
private static const FRC:String = "frc";
private static const SQT:String = "sqt";
private static const RSQ:String = "rsq";
private static const POW:String = "pow";
private static const LOG:String = "log";
private static const EXP:String = "exp";
private static const NRM:String = "nrm";
private static const SIN:String = "sin";
private static const COS:String = "cos";
private static const CRS:String = "crs";
private static const DP3:String = "dp3";
private static const DP4:String = "dp4";
private static const ABS:String = "abs";
private static const NEG:String = "neg";
private static const SAT:String = "sat";
private static const M33:String = "m33";
private static const M44:String = "m44";
private static const M34:String = "m34";
private static const IFZ:String = "ifz";
private static const INZ:String = "inz";
private static const IFE:String = "ife";
private static const INE:String = "ine";
private static const IFG:String = "ifg";
private static const IFL:String = "ifl";
private static const IEG:String = "ieg";
private static const IEL:String = "iel";
private static const ELS:String = "els";
private static const EIF:String = "eif";
private static const REP:String = "rep";
private static const ERP:String = "erp";
private static const BRK:String = "brk";
private static const KIL:String = "kil";
private static const TEX:String = "tex";
private static const SGE:String = "sge";
private static const SLT:String = "slt";
private static const SGN:String = "sgn";
// registers
private static const VA:String = "va";
private static const VC:String = "vc";
private static const VT:String = "vt";
private static const OP:String = "op";
private static const V:String = "v";
private static const FC:String = "fc";
private static const FT:String = "ft";
private static const FS:String = "fs";
private static const OC:String = "oc";
// samplers
private static const D2:String = "2d";
private static const D3:String = "3d";
private static const CUBE:String = "cube";
private static const MIPNEAREST:String = "mipnearest";
private static const MIPLINEAR:String = "miplinear";
private static const MIPNONE:String = "mipnone";
private static const NOMIP:String = "nomip";
private static const NEAREST:String = "nearest";
private static const LINEAR:String = "linear";
private static const CENTROID:String = "centroid";
private static const SINGLE:String = "single";
private static const DEPTH:String = "depth";
private static const REPEAT:String = "repeat";
private static const WRAP:String = "wrap";
private static const CLAMP:String = "clamp";
}
}
// ================================================================================
// Helper Classes
// --------------------------------------------------------------------------------
{
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class OpCode
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _emitCode:uint;
private var _flags:uint;
private var _name:String;
private var _numRegister:uint;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get emitCode():uint { return _emitCode; }
public function get flags():uint { return _flags; }
public function get name():String { return _name; }
public function get numRegister():uint { return _numRegister; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function OpCode( name:String, numRegister:uint, emitCode:uint, flags:uint)
{
_name = name;
_numRegister = numRegister;
_emitCode = emitCode;
_flags = flags;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[OpCode name=\""+_name+"\", numRegister="+_numRegister+", emitCode="+_emitCode+", flags="+_flags+"]";
}
}
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class Register
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _emitCode:uint;
private var _name:String;
private var _longName:String;
private var _flags:uint;
private var _range:uint;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get emitCode():uint { return _emitCode; }
public function get longName():String { return _longName; }
public function get name():String { return _name; }
public function get flags():uint { return _flags; }
public function get range():uint { return _range; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function Register( name:String, longName:String, emitCode:uint, range:uint, flags:uint)
{
_name = name;
_longName = longName;
_emitCode = emitCode;
_range = range;
_flags = flags;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[Register name=\""+_name+"\", longName=\""+_longName+"\", emitCode="+_emitCode+", range="+_range+", flags="+ _flags+"]";
}
}
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class Sampler
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _flag:uint;
private var _mask:uint;
private var _name:String;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get flag():uint { return _flag; }
public function get mask():uint { return _mask; }
public function get name():String { return _name; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function Sampler( name:String, flag:uint, mask:uint )
{
_name = name;
_flag = flag;
_mask = mask;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[Sampler name=\""+_name+"\", flag=\""+_flag+"\", mask="+mask+"]";
}
}
}

View File

@@ -0,0 +1,450 @@
package alternativa.engine3d.materials {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.objects.Mesh;
import flash.display.BitmapData;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendMode;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexFormat;
import flash.display3D.Program3D;
import flash.display3D.Texture3D;
import flash.display3D.TextureCube3D;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.ByteArray;
use namespace alternativa3d;
public class CommonMaterial extends Material {
/**
* URL диффузной карты.
* Это свойство нужно при загрузке текстур ипользуя класс <code>MaterialLoader</code>.
* @see alternativa.engine3d.materials.MaterialLoader
*/
public var diffuseMapURL:String;
/**
* URL карты прозрачности.
* Это свойство нужно при загрузке текстур ипользуя класс <code>MaterialLoader</code>.
* @see alternativa.engine3d.materials.MaterialLoader
*/
public var opacityMapURL:String;
/**
* URL карты нормалей.
*/
public var normalMapURL:String;
/**
* URL карты самосвечения.
*/
public var emissionMapURL:String;
/**
* URL карты блика.
*/
public var specularMapURL:String;
public var repeat:Boolean = true;
public var diffuse:BitmapData;
public var opacity:BitmapData;
public var normals:BitmapData;
public var specular:BitmapData;
public var emission:BitmapData;
public var emissionChannel:uint = 0;
public var shadows:Boolean = false;
public var ambientCube:TextureCube3D;
public var diffuse3D:Texture3D;
public var opacity3D:Texture3D;
public var normals3D:Texture3D;
public var specular3D:Texture3D;
public var emission3D:Texture3D;
public var glossiness:Number = 50;
public var useSpecularMap:Boolean = true;
public var useTangents:Boolean = true;
public var flipX:Boolean = false;
public var flipY:Boolean = false;
public var ambient:Number = 0;
public function CommonMaterial() {
}
private function getMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var key:String = "Cmesh" +
((repeat) ? "R" : "r") +
((useTangents) ? "T" : "t") +
((opacity3D != null) ? "O" : "o") +
((emission3D != null) ? "E" : "e") +
((useSpecularMap && specular3D != null) ? "S" : "s") +
((flipX) ? "X" : "x") +
((flipY) ? "Y" : "y") +
((emissionChannel > 0) ? "E" : "e") +
((ambientCube != null) ? "A" : "a") +
((makeShadows) ? "1" : "0");
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initMeshProgram(camera, context3d, makeShadows);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function initMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var shadowCaster:DirectionalLight;
if (makeShadows) {
shadowCaster = camera.shadowCaster;
}
/*
* va0 - vertex xyz in object space
* va1 - vertex uv
* [useTangents==true]: va2.xyz - vertex tangent
* [useTangents==true]: va2.w - vertex bitangent direction
* [useTangents==true]: va3 - vertex normal
* [emissionChannel>0] - va4 - emission uv
* vc0 - vc3 projection matrix
* vc4 - camera xyz in object space
* [useTangents==true]: vc5 - inverted light direction in object space
* [useTangents==true]: vc5.w - 1.0
* [ambientCube!=null] vc8 - vc11 - normal toWorld transform matrix
* [shadows] vc12 - vc15 - shadows
*/
var vertexShader:String =
meshProjectionShader("op") +
"mov v0, va1 \n";
if (useTangents) {
vertexShader +=
"mov vt2, va3 \n" +
"crs vt0.xyz, vt2, va2 \n" + // vt0 - biTangent
"mov vt0.w, va2.w \n" +
"sub vt1, vc4, va0 \n" + // vt1 - view vector
"dp3 v1.x, vt1, va2 \n" +
"dp3 v1.y, vt1, vt0 \n" +
"dp3 v1.z, vt1, va3 \n" +
"mov v1.w, vc5.w \n" +
// calculate light in tangent space
"dp3 v2.x, vc5, va2 \n" +
"dp3 v2.y, vc5, vt0 \n" +
"dp3 v2.z, vc5, va3 \n" +
"mov v2.w, vc5.w \n";
} else {
vertexShader +=
"sub v1, vc4, va0 \n";
}
if (emissionChannel > 0) {
vertexShader +=
"mov v4, va4 \n";
}
if (makeShadows) {
vertexShader += shadowCaster.attenuateVertexShader("v3", 12);
}
if (ambientCube != null) {
vertexShader +=
"dp3 vt0.x, va3, vc8 \n" + // vt0 - world normal
"dp3 vt0.y, va3, vc9 \n" +
"dp3 vt0.z, va3, vc10 \n" +
"dp3 vt0.w, va3, vc11 \n" +
"neg vt0.y, vt0.y\n" +
"mov v5, vt0 \n";
}
/*
* v0 - uv
* v1 - view vector (unnormalized)
* [useTangents==true] v2 - inverted light direction (unnormalized)
* [makeShadows==true] v3 - shadows
* [emissionChannel>0] v4 - emission uv
* [ambientCube!=null] v5 - world normal
* fs0 - diffuse
* fs1 - normalmap
* fs2 - opacity
* fs3 - emission
* fs4 - specular
* [makeShadows==true] fs5 - shadow map
* [ambientCube!=null] fs6 - ambient cube map
* fc0.x - 0.5
* fc0.y - glossiness
* fc0.z - ambient
* fc0.w - 1.0
* [useTangents==false] fc1 - inverted light direction (normalized)
* [makeShadows==true] fc2 - shadow constant
* fc3 : light color
* ft0 - result
*/
var fragmentShader:String =
"tex ft0, v0, fs0 <2d, " + ((repeat) ? "repeat" : "clamp") + ", linear, miplinear> \n" + // ft0 - diffuse
// normal
"tex ft1, v0, fs1 <2d, " + ((repeat) ? "repeat" : "clamp") + ", linear, miplinear> \n" +
"sub ft1, ft1, fc0.xxxx \n" +
// x2
"add ft1, ft1, ft1 \n"; // ft1 - normal
if (flipX || flipY) {
fragmentShader +=
((flipX && flipY) ?
"neg ft1.xy, ft1.xy \n"
: ((flipX) ?
"neg ft1.x, ft1.x \n" :
"neg ft1.y, ft1.y \n"));
}
// diffuse lighting
if (useTangents) {
fragmentShader +=
"nrm ft4.xyz, v2.xyz \n" + // ft4 - inverted light (normalized)
"mov ft4.w, fc0.w \n" +
"dp3 ft2, ft1, ft4 \n";
} else {
fragmentShader +=
"dp3 ft2, ft1, fc1 \n";
}
fragmentShader +=
"sat ft2.x, ft2.x \n"; // ft2.x - diffuse lighting
if (makeShadows) {
fragmentShader += shadowCaster.attenuateFragmentShader("ft5", "v3", 5, 2) + // ft5 - shadow
"mul ft2.x, ft2.x, ft5.x \n";
}
if (ambient > 0) {
fragmentShader +=
"add ft2.x, ft2.x, fc0.z \n";
}
if (emission3D != null) {
// emission
fragmentShader +=
"tex ft3, " + ((emissionChannel > 0) ? "v4" : "v0") + ", fs3 <2d, " + ((repeat) ? "repeat" : "clamp") + ", linear, miplinear> \n" + // ft3 - emission color
"add ft3, ft3, ft3 \n" +
"add ft2.x, ft2.x, ft3.x \n"; // ft2.x - diffuse + emission lighting
}
fragmentShader +=
"mul ft2, ft2.xxxx, fc3 \n";
if (ambientCube != null) {
fragmentShader +=
"tex ft3, v5, fs6 <cube, linear, mipnone>\n" + // ft3 - ambient cube map color
"add ft2, ft2, ft3 \n" +
"mul ft0.xyz, ft0.xyz, ft2.xyz \n"; // ft0 - diffuse*lighting
} else {
fragmentShader +=
"mul ft0.xyz, ft0.xyz, ft2.xyz \n"; // ft0 - diffuse*lighting
}
fragmentShader +=
// specular lighting
// level = pow(max(dot(halfWay, normal), 0), gloss)
// 1. calc halfway vector
"nrm ft2.xyz, v1.xyz \n"; // ft2 - halfWay vector
if (useTangents) {
fragmentShader +=
"add ft2, ft2, ft4 \n";
} else {
fragmentShader +=
"add ft2, ft2, fc1 \n";
}
fragmentShader +=
"nrm ft2.xyz, ft2.xyz \n" +
// 2. dot(halfWay, normal)
"dp3 ft2, ft2, ft1 \n" +
"sat ft2.x, ft2.x \n" +
"pow ft2.x, ft2.x, fc0.y \n"; // ft2.x - specular highlight
// dot/(dot - dot*n + n) По Шлику замена pow
// "mul ft2.z, ft2.x, fc0.y \n" +
// "sub ft2.y, ft2.x, ft2.z \n" +
// "add ft2.y, ft2.y, fc0.y \n" +
// "div ft2.x, ft2.x, ft2.y \n";
fragmentShader +=
"mul ft2, ft2.x, fc3 \n";
if (useSpecularMap && specular3D != null) {
// specular level
fragmentShader +=
"tex ft1, v0, fs4 <2d, " + ((repeat) ? "repeat" : "clamp") + ", linear, miplinear> \n" + // ft1 - specular level
"mul ft2, ft2, ft1.x \n"; // ft2.x - specular highlight
}
if (makeShadows) {
fragmentShader +=
"mul ft2, ft2, ft5.x \n";
}
fragmentShader +=
"add ft0.xyz, ft0.xyz, ft2.xxx \n"; // ft0.xyz - diffuse*lighting + specular
if (opacity3D != null) {
// alpha
fragmentShader +=
"tex ft1, v0, fs2 <2d, " + ((repeat) ? "repeat" : "clamp") + ", linear, miplinear> \n" + // ft1 - opacity level
"mov ft0.w, ft1.x \n"; // ft0.w - alpha
}
fragmentShader +=
"mov oc, ft0";
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexShader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentShader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
override alternativa3d function drawMesh(mesh:Mesh, camera:Camera3D):void {
if (diffuse3D == null || normals3D == null) {
return;
}
var context3d:Context3D = camera.view._context3d;
var makeShadows:Boolean = mesh.useShadows && camera.shadowCaster != null && camera.shadowCaster.currentSplitHaveShadow && shadows;
context3d.setProgram(getMeshProgram(camera, context3d, makeShadows));
if (opacity3D != null) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
}
meshProjectionShaderSetup(context3d, mesh);
// uv
context3d.setVertexStream(1, mesh.geometry.vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
if (useTangents) {
// tangent
context3d.setVertexStream(2, mesh.geometry.vertexBuffer, 5, Context3DVertexFormat.FLOAT_4);
// normal
context3d.setVertexStream(3, mesh.geometry.vertexBuffer, 9, Context3DVertexFormat.FLOAT_3);
}
if (emissionChannel > 0) {
context3d.setVertexStream(4, mesh.geometry.additionalUVChannels, 0, Context3DVertexFormat.FLOAT_2);
}
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 0, 1, Vector.<Number>([0.5, glossiness, ambient, 1]));
var shadowCaster:DirectionalLight;
if (makeShadows) {
shadowCaster = camera.shadowCaster;
shadowCaster.attenuateProgramSetup(context3d, mesh, 12, 5, 2);
}
if (camera.numDirectionalLights > 0) {
var light:DirectionalLight = camera.directionalLights[0];
var matrix:Matrix3D = mesh.cameraMatrix.clone();
matrix.append(light.lightMatrix);
matrix.invert();
var direction:Vector3D = matrix.deltaTransformVector(Vector3D.Z_AXIS);
direction.normalize();
if (useTangents) {
context3d.setProgramConstants(Context3DProgramType.VERTEX, 5, 1, Vector.<Number>([-direction.x, -direction.y, -direction.z, 1]));
// context3d.setProgramConstants("VERTEX", 5, 1, Vector.<Number>([direction.x, direction.y, direction.z, 1]));
} else {
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 1, 1, Vector.<Number>([-direction.x, -direction.y, -direction.z, 1]));
// context3d.setProgramConstants("FRAGMENT", 1, 1, Vector.<Number>([direction.x, direction.y, direction.z, 1]));
}
matrix.identity();
matrix.append(mesh.cameraMatrix);
matrix.invert();
var coords:Vector3D = matrix.position;
context3d.setProgramConstants(Context3DProgramType.VERTEX, 4, 1, Vector.<Number>([coords.x, coords.y, coords.z, 1]));
context3d.setProgramConstants(Context3DProgramType.VERTEX, 6, 1, Vector.<Number>([0, 1, 0, 1]));
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 3, 1, light.color);
} else {
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 1, 1, Vector.<Number>([0, 0, 1, 1]));
}
context3d.setTexture(0, diffuse3D);
context3d.setTexture(1, normals3D);
if (opacity3D != null) {
context3d.setTexture(2, opacity3D);
}
if (emission3D != null) {
context3d.setTexture(3, emission3D);
}
if (useSpecularMap && specular3D != null) {
context3d.setTexture(4, specular3D);
}
if (ambientCube != null) {
var toWorld:Matrix3D = mesh.cameraMatrix.clone();
toWorld.append(camera.globalMatrix);
toWorld.invert();
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, 8, toWorld);
context3d.setTexture(6, ambientCube);
}
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
} else {
context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
}
if (makeShadows) {
shadowCaster.attenuateProgramClean(context3d, 5);
}
context3d.setVertexStream(1, null, 0, Context3DVertexFormat.DISABLED);
if (useTangents) {
// tangent
context3d.setVertexStream(2, null, 0, Context3DVertexFormat.DISABLED);
// normal
context3d.setVertexStream(3, null, 0, Context3DVertexFormat.DISABLED);
}
if (emissionChannel > 0) {
context3d.setVertexStream(4, null, 0, Context3DVertexFormat.DISABLED);
}
if (opacity3D != null) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
// override alternativa3d function update(context3d:Context3D):void {
// if (diffuse3D == null && diffuse != null) {
// diffuse3D = context3d.createTexture(diffuse.width, diffuse.height, Context3DTextureFormat.BGRA, false);
// uploadTextureWithMipmaps(diffuse3D, diffuse);
// }
// if (opacity3D == null && opacity != null) {
// opacity3D = context3d.createTexture(opacity.width, opacity.height, Context3DTextureFormat.BGRA, false);
// uploadTextureWithMipmaps(opacity3D, opacity);
// }
// if (normals3D == null && normals != null) {
// normals3D = context3d.createTexture(normals.width, normals.height, Context3DTextureFormat.BGRA, false);
// uploadTextureWithMipmaps(normals3D, normals);
// }
// if (emission3D == null && emission != null) {
// emission3D = context3d.createTexture(emission.width, emission.height, Context3DTextureFormat.BGRA, false);
// uploadTextureWithMipmaps(emission3D, emission);
// }
// if (specular3D == null && useSpecularMap && specular != null) {
// specular3D = context3d.createTexture(specular.width, specular.height, Context3DTextureFormat.BGRA, false);
// uploadTextureWithMipmaps(specular3D, specular);
// }
// }
// public function setDiffuse3D(value:Texture3D):void {
// diffuse3D = value;
// }
//
// public function setOpacity3D(value:Texture3D):void {
// opacity3D = value;
// }
//
// public function setNormals3D(value:Texture3D):void {
// normals3D = value;
// }
//
// public function setEmission3D(value:Texture3D):void {
// emission3D = value;
// }
//
// public function setSpecular3D(value:Texture3D):void {
// specular3D = value;
// }
public static function uploadTextureWithMipmaps( dest:Texture3D, src:BitmapData ):void {
var ws:int = src.width;
var hs:int = src.height;
var level:int = 0;
var tmp:BitmapData = new BitmapData( src.width, src.height );
var transform:Matrix = new Matrix();
while ( ws > 1 && hs > 1 ) {
tmp.draw( src, transform, null, null, null, true );
dest.upload( tmp, level );
transform.scale( 0.5, 0.5 );
level++;
ws >>= 1;
hs >>= 1;
}
tmp.dispose();
}
override alternativa3d function get isTransparent():Boolean {
return opacity != null;
}
}
}

View File

@@ -0,0 +1,240 @@
package alternativa.engine3d.materials {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.objects.Joint; Joint;
import alternativa.engine3d.objects.Mesh;
import flash.utils.ByteArray;
import alternativa.engine3d.lights.DirectionalLight;
import flash.geom.Matrix3D;
import alternativa.engine3d.objects.Skin;
import flash.display3D.Program3D;
import flash.display3D.Context3D;
import flash.display3D.VertexBuffer3D;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DBlendMode;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DTriangleFace;
use namespace alternativa3d;
/**
* Материал, заполняющий полигон сплошной одноцветной заливкой.
* Помимо заливки цветом, материал может рисовать границу полигона линией заданной толщины и цвета.
*/
public class FillMaterial extends Material {
private var _color:int;
private var _colorVector:Vector.<Number> = new Vector.<Number>(4);
// /**
// * Толщина линий.
// * Линии отрисовываются только если толщина больше или равна <code>0</code>.
// * Значение по умолчанию <code>-1</code>.
// */
// public var lineThickness:Number;
//
// /**
// * Цвет линий.
// * Линии отрисовываются только если толщина больше или равна <code>0</code>.
// * Значение по умолчанию <code>0xFFFFFF</code>.
// */
// public var lineColor:int;
/**
* Создаёт новый экземпляр.
* @param color Цвет заливки.
* @param alpha Альфа-прозрачность заливки
* @param lineThickness Толщина линий.
* @param lineColor Цвет линий.
*/
public function FillMaterial(color:int = 0x7F7F7F, alpha:Number = 1, lineThickness:Number = -1, lineColor:int = 0xFFFFFF) {
_color = color;
_colorVector[0] = ((color >> 16) & 0xFF)/255;
_colorVector[1] = ((color >> 8) & 0xFF)/255;
_colorVector[2] = (color & 0xFF)/255;
_colorVector[3] = alpha;
// this.lineThickness = lineThickness;
// this.lineColor = lineColor;
}
private function getSkinProgram(camera:Camera3D, context3d:Context3D, numJoints:uint):Program3D {
var key:String = "Fskin" + numJoints.toString() + ":" + camera.numDirectionalLights.toString();
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initSkinProgram(camera, context3d, numJoints);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function initSkinProgram(camera:Camera3D, context3d:Context3D, numJoints:uint):Program3D {
var vertexShader:String = skinProjectionShader(numJoints, "op", "vt0", "vt1");
var fragmentShader:String;
// if (camera.numLights > 0) {
// var light:DirectionalLight = camera.lights[0];
// vertexShader += light.attenuateVertexShader("v0", numJoints * 4);
// fragmentShader = light.attenuateFragmentShader("ft0", "v0", 0, 1);
// fragmentShader += "mul ft0, ft0, fc0 \n" +
// "mov oc, ft0";
// } else {
fragmentShader = "mov oc, fc0";
// }
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexShader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentShader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
override alternativa3d function drawSkin(skin:Skin, camera:Camera3D, joints:Vector.<Joint>, numJoints:int, maxJoints:int, vertexBuffer:VertexBuffer3D, indexBuffer:IndexBuffer3D, numTriangles:int):void {
if (numJoints < 1) return;
var context3d:Context3D = camera.view._context3d;
context3d.setProgram(getSkinProgram(camera, context3d, numJoints));
if (_colorVector[3] < 1) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
} else {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
}
skinProjectionShaderSetup(context3d, joints, numJoints, vertexBuffer);
// if (camera.numLights > 0) {
// var light:DirectionalLight = camera.lights[0];
// var lightMatrix:Matrix3D = skin.cameraMatrix.clone();
// lightMatrix.append(camera.globalMatrix);
// lightMatrix.append(light.lightMatrix);
// light.attenuateProgramSetup(context3d, lightMatrix, numJoints * 4, 0, 1);
// }
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 0, 1, _colorVector);
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(indexBuffer, 0, numTriangles);
} else {
context3d.drawTriangles(indexBuffer, 0, numTriangles);
}
skinProjectionShaderClean(context3d, numJoints);
if (_colorVector[3] < 1) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
private function getMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var key:String;
if (makeShadows) {
key = "Fmesh" + camera.shadowCaster.getKey();
} else {
key = "Fmesh";
}
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initMeshProgram(camera, context3d, makeShadows);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function initMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var vertexShader:String = meshProjectionShader("op");
var fragmentShader:String;
if (makeShadows) {
var shadowCaster:DirectionalLight = camera.shadowCaster;
vertexShader += shadowCaster.attenuateVertexShader("v0", 4);
fragmentShader = shadowCaster.attenuateFragmentShader("ft0", "v0", 0, 1);
fragmentShader += "mul ft0, ft0, fc0 \n" +
"mov oc, ft0";
} else {
fragmentShader = "mov oc, fc0";
}
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexShader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentShader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
/**
* Отрисовывает меш
* @param view
* @param projection матрица для проецирования вершин
* @param vertexBuffer буффер данных вершин, в котором в диапазоне индексов 0 - 3 хранятся координаты вершин, а в индексах 3 - 5 uv координаты.
* @param indexBuffer буффер индексов треугольников
* @param numTriangles количество треугольников для отрисовки
*/
override alternativa3d function drawMesh(mesh:Mesh, camera:Camera3D):void {
var context3d:Context3D = camera.view._context3d;
var makeShadows:Boolean = mesh.useShadows && camera.shadowCaster != null && camera.shadowCaster.currentSplitHaveShadow;
context3d.setProgram(getMeshProgram(camera, context3d, makeShadows));
if (_colorVector[3] < 1) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
} else {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
}
meshProjectionShaderSetup(context3d, mesh);
var shadowCaster:DirectionalLight;
if (makeShadows) {
shadowCaster = camera.shadowCaster;
shadowCaster.attenuateProgramSetup(context3d, mesh, 4, 0, 1);
}
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 0, 1, _colorVector);
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
} else {
context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
}
if (makeShadows) {
shadowCaster.attenuateProgramClean(context3d, 0);
}
if (_colorVector[3] < 1) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
/**
* Цвет заливки.
* Значение по умолчанию <code>0x7F7F7F</code>.
*/
public function get color():int {
return _color;
}
/**
* @private
*/
public function set color(value:int):void {
_color = value;
_colorVector[0] = ((color >> 16) & 0xFF)/255;
_colorVector[1] = ((color >> 8) & 0xFF)/255;
_colorVector[2] = (color & 0xFF)/255;
}
/**
* Значение альфа-прозрачности.
* Допустимые значения находятся в диапазоне от <code>0</code> до <code>1</code>.
* Значение по умолчанию <code>1</code>.
*/
public function get alpha():Number {
return _colorVector[3];
}
/**
* @private
*/
public function set alpha(value:Number):void {
_colorVector[3] = value;
}
override alternativa3d function get isTransparent():Boolean {
return _colorVector[3] < 1;
}
}
}

View File

@@ -0,0 +1,193 @@
package alternativa.engine3d.materials {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.objects.Mesh;
import flash.display.BitmapData;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexFormat;
import flash.display3D.Program3D;
import flash.display3D.Texture3D;
import flash.utils.ByteArray;
use namespace alternativa3d;
/**
* Рисует сетку
*/
public class GridMaterial extends Material {
private var tilesColor:uint;
private var groutColor:uint;
public var countWidth:Number;
public var countHeight:Number;
public var groutWidth:Number;
private var tilesTexture:Texture3D;
public function GridMaterial(tilesColor:uint = 0x0, groutColor:uint = 0xFFFFFF, countWidth:Number = 4, countHeight:Number = 4, groutWidth:Number = 0.1) {
// this.tilesColor = Vector.<Number>([((tilesColor >>> 16) & 0xFF)/255, ((tilesColor >>> 8) & 0xFF)/255, (tilesColor & 0xFF)/255, 1]);
// this.groutColor = Vector.<Number>([((groutColor >>> 16) & 0xFF)/255, ((groutColor >>> 8) & 0xFF)/255, (groutColor & 0xFF)/255, 1]);
this.tilesColor = tilesColor;
this.groutColor = groutColor;
this.countWidth = countWidth;
this.countHeight = countHeight;
this.groutWidth = groutWidth;
}
alternativa3d function evaluateFragmentShaderSimple(result:String, uv:String, const0:uint, texture:uint):String {
return evaluateFragmentShader(result, uv, const0, const0, const0, texture);
}
alternativa3d function evaluateFragmentShader(result:String, uv:String, const0:uint, const1:uint, const2:uint, texture:uint):String {
return "" +
// X
"mul " + result + ".x, " + uv + ".x, fc" + const0.toString() + ".x \n" +
"frc " + result + ".x, " + result + ".x \n" +
"add " + result + ".x, " + result + ".x, fc" + const0.toString() + ".y \n" +
"mul " + result + ".x, " + result + ".x, fc" + const0.toString() + ".z \n" +
// Y
"mul " + result + ".y, " + uv + ".y, fc" + const1.toString() + ".x \n" +
"frc " + result + ".y, " + result + ".y \n" +
"add " + result + ".y, " + result + ".y, fc" + const1.toString() + ".y \n" +
"mul " + result + ".y, " + result + ".y, fc" + const1.toString() + ".z \n" +
"mov " + result + ".zw, fc" + const2.toString() + ".zw \n" +
"tex " + result + ", " + result + ", fs" + texture.toString() + " <2d, clamp, nearest, mipnone> \n";
}
private static var tempVector:Vector.<Number> = Vector.<Number>([0, 0, 0, 1]);
alternativa3d function evaluateProgramSetupSimple(context3d:Context3D, const0:uint, texture:uint):void {
var gx:Number = groutWidth*countWidth;
tempVector[0] = countWidth;
tempVector[1] = 0.5 - gx;
tempVector[2] = 1/(2 - 2*gx);
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, const0, 1, tempVector);
context3d.setTexture(texture, tilesTexture);
}
alternativa3d function evaluateProgramSetup(context3d:Context3D, const0:uint, const1:uint, const2:uint, texture:uint):void {
var gx:Number = groutWidth*countWidth;
var gy:Number = groutWidth*countHeight;
tempVector[0] = countWidth;
tempVector[1] = 0.5 - gx;
tempVector[2] = 1/(2 - 2*gx);
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, const0, 1, tempVector);
tempVector[0] = countHeight;
tempVector[1] = 0.5 - gy;
tempVector[2] = 1/(2 - 2*gy);
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, const1, 1, tempVector);
tempVector[2] = 0;
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, const2, 1, tempVector);
context3d.setTexture(texture, tilesTexture);
}
private function getMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var key:String = "Gmesh";
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initMeshProgram(camera, context3d, makeShadows);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function initMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
/*
* va0 : vertex coords
* va1 : uv coords
* vc0-vc3 : projection matrix
*/
var vertexShader:String =
"dp4 op.x, va0, vc0 \n" +
"dp4 op.y, va0, vc1 \n" +
"dp4 op.z, va0, vc2 \n" +
"dp4 op.w, va0, vc3 \n" +
"mov v0, va1 \n";
/*
* v0 - uv coords
* fc0 : diffuse color
* fc1.x : count width
* fc1.y : x offset
* fc1.z : x mult
* fc2.x : count height
* fc2.y : y offset
* fc2.z : y mult
* fs0 : tile texture
*/
var fragmentShader:String;
fragmentShader = evaluateFragmentShader("ft0", "v0", 0, 1, 2, 0);
if (makeShadows) {
// var light:DirectionalLight = camera.lights[0];
// vertexShader += light.attenuateVertexShader("v1", 4);
// fragmentShader += light.attenuateFragmentShader("ft1", "v1", 1, 0);
// fragmentShader += "mul ft0, ft1, ft0 \n" +
// "mov oc, ft0";
fragmentShader += "mov oc, ft0"
} else {
fragmentShader += "mov oc, ft0";
}
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexShader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentShader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
/**
* Отрисовывает меш
* @param projection матрица для проецирования вершин
* @param vertexBuffer буффер данных вершин, в котором в диапазоне индексов 0 - 3 хранятся координаты вершин, а в индексах 3 - 5 uv координаты.
* @param indexBuffer буффер индексов треугольников
* @param numTriangles количество треугольников для отрисовки
*/
override alternativa3d function drawMesh(mesh:Mesh, camera:Camera3D):void {
var context3d:Context3D = camera.view._context3d;
var makeShadows:Boolean = mesh.useShadows && camera.numDirectionalLights > 0;
context3d.setProgram(getMeshProgram(camera, context3d, makeShadows));
context3d.setVertexStream(0, mesh.geometry.vertexBuffer, 0, Context3DVertexFormat.FLOAT_3);
context3d.setVertexStream(1, mesh.geometry.vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, 0, mesh.projectionMatrix, true);
// if (makeShadows) {
// var light:DirectionalLight = camera.lights[0];
// light.attenuateProgramSetup(view, mesh, 4, 1, 0);
// }
evaluateProgramSetup(context3d, 0, 1, 2, 0);
context3d.setCulling(Context3DTriangleFace.NONE);
if (camera.debug) {
context3d.drawTrianglesSynchronized(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
} else {
context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
}
// if (makeShadows) {
// light.clean
// }
context3d.setVertexStream(1, null, 0, Context3DVertexFormat.DISABLED);
}
override alternativa3d function update(context3d:Context3D):void {
if (tilesTexture == null) {
tilesTexture = context3d.createTexture(4, 4, Context3DTextureFormat.BGRA, false);
var txt:BitmapData = new BitmapData(4, 4, false, groutColor);
txt.setPixel(1, 1, tilesColor);
txt.setPixel(1, 2, tilesColor);
txt.setPixel(2, 1, tilesColor);
txt.setPixel(2, 2, tilesColor);
tilesTexture.upload(txt);
}
}
override alternativa3d function get isTransparent():Boolean {
return false;
}
}
}

View File

@@ -0,0 +1,149 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.objects.Joint; Joint;
import alternativa.engine3d.objects.Mesh;
import flash.utils.ByteArray;
import alternativa.engine3d.objects.Skin;
import flash.media.Camera;
import flash.display3D.VertexBuffer3D;
import flash.display3D.Context3D;
import flash.display3D.Program3D;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexFormat;
import alternativa.engine3d.lights.DirectionalLight;
use namespace alternativa3d;
public class Material {
/**
* Имя материала.
*/
public var name:String;
alternativa3d function update(context3d:Context3D):void {
}
alternativa3d function reset():void {
}
alternativa3d function drawSkin(skin:Skin, camera:Camera3D, joints:Vector.<Joint>, numJoints:int, maxJoints:int, vertexBuffer:VertexBuffer3D, indexBuffer:IndexBuffer3D, numTriangles:int):void {
}
alternativa3d function drawMesh(mesh:Mesh, camera:Camera3D):void {
}
private function initMeshInShadowMapProgram(context3d:Context3D):Program3D {
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX,
meshProjectionShader("vt0") +
"mov v0, vt0 \n" +
"mov op, vt0 \n", false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT,
"mov ft0, v0.z \n" +
"mov oc, ft0", false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
alternativa3d function drawMeshInShadowMap(mesh:Mesh, camera:Camera3D, light:DirectionalLight):void {
var context3d:Context3D = camera.view._context3d;
var program:Program3D = camera.context3dCachedPrograms["Smesh"];
if (program == null) {
program = initMeshInShadowMapProgram(context3d);
camera.context3dCachedPrograms["Smesh"] = program;
}
context3d.setProgram(program);
meshProjectionShaderSetup(context3d, mesh);
// context3d.setCulling("FRONT");
context3d.setCulling(Context3DTriangleFace.BACK);
if (camera.debug) {
context3d.drawTrianglesSynchronized(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
} else {
context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
}
}
alternativa3d function skinProjectionShader(numJoints:uint, result4:String, vt4_1:String, vt4_2:String):String {
var shader:String = "dp4 " + vt4_1 + ".x, va0, vc0 \n" +
"dp4 " + vt4_1 + ".y, va0, vc1 \n" +
"dp4 " + vt4_1 + ".z, va0, vc2 \n" +
"dp4 " + vt4_1 + ".w, va0, vc3 \n" +
"mul " + vt4_2 + ", vt0, va2.x \n";
const chars:Array = ["x", "y", "z", "w"];
var charIndex:int = 0;
for (var i:int = 1; i < numJoints; i ++) {
var index:int = 4 * i;
shader += "dp4 " + vt4_1 + ".x, va0, vc" + index + " \n"
shader += "dp4 " + vt4_1 + ".y, va0, vc" + int(index + 1) + " \n";
shader += "dp4 " + vt4_1 + ".z, va0, vc" + int(index + 2) + " \n";
shader += "dp4 " + vt4_1 + ".w, va0, vc" + int(index + 3) + " \n";
shader += "mul " + vt4_1 + ", vt0, va" + int(int(i/4) + 2) + "." + chars[int(i % 4)] + " \n";
shader += "add " + vt4_2 + ", vt1, vt0 \n";
}
shader += "mov " + result4 + ", " + vt4_2 + " \n";
return shader;
}
alternativa3d function skinProjectionShaderSetup(context3d:Context3D, joints:Vector.<Joint>, numJoints:uint, vertexBuffer:VertexBuffer3D):void {
context3d.setVertexStream(0, vertexBuffer, 0, Context3DVertexFormat.FLOAT_3);
var i:int;
var count:int = int(numJoints/4);
for (i = 0; i < count; i++) {
context3d.setVertexStream(2 + i, vertexBuffer, 5 + 4*i, Context3DVertexFormat.FLOAT_4);
}
if (4*count < numJoints) {
var vertexFormat:String;
switch (numJoints - (4 * count)) {
case 1:
vertexFormat = Context3DVertexFormat.FLOAT_1;
break;
case 2:
vertexFormat = Context3DVertexFormat.FLOAT_2;
break;
case 3:
vertexFormat = Context3DVertexFormat.FLOAT_3;
break;
}
context3d.setVertexStream(2 + count, vertexBuffer, 5 + 4 * count, vertexFormat);
}
for (i = 0; i < numJoints; i++) {
var joint:Joint = joints[i];
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, 4 * i, joint.cameraMatrix, true);
}
}
alternativa3d function skinProjectionShaderClean(context3d:Context3D, numJoints:uint):void {
var count:int = int(numJoints/4);
for (var i:int = 0; i < count; i++) {
context3d.setVertexStream(2 + i, null, 0, Context3DVertexFormat.DISABLED);
}
if (4*count < numJoints) {
context3d.setVertexStream(2 + count, null, 0, Context3DVertexFormat.DISABLED);
}
}
alternativa3d function meshProjectionShader(result4:String):String {
var shader:String = "dp4 " + result4 + ".x, va0, vc0 \n" +
"dp4 " + result4 + ".y, va0, vc1 \n" +
"dp4 " + result4 + ".z, va0, vc2 \n" +
"dp4 " + result4 + ".w, va0, vc3 \n";
return shader;
}
alternativa3d function meshProjectionShaderSetup(context3d:Context3D, mesh:Mesh):void {
context3d.setVertexStream(0, mesh.geometry.vertexBuffer, 0, Context3DVertexFormat.FLOAT_3);
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, 0, mesh.projectionMatrix, true);
}
alternativa3d function get isTransparent():Boolean {
return false;
}
}
}

View File

@@ -0,0 +1,263 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.View;
import alternativa.engine3d.materials.AGALMiniAssembler;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Mesh;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendMode;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexFormat;
import flash.display3D.Program3D;
import flash.display3D.Texture3D;
import flash.utils.ByteArray;
import flash.display3D.VertexBuffer3D;
import flash.display3D.IndexBuffer3D;
import alternativa.engine3d.objects.Skin;
import alternativa.engine3d.objects.Joint;
import flash.geom.Matrix3D;
use namespace alternativa3d;
public class SkinMaterial extends Material {
static private var programs:Object = new Object();
public var transparent:Boolean;
private var diffuse3D:Texture3D;
private var normal3D:Texture3D;
private var specular3D:Texture3D;
public function SkinMaterial(diffuse3D:Texture3D, normal3D:Texture3D, specular3D:Texture3D, transparent:Boolean) {
this.diffuse3D = diffuse3D;
this.normal3D = normal3D;
this.specular3D = specular3D;
this.transparent = transparent;
}
override alternativa3d function get isTransparent():Boolean {
return transparent;
}
override alternativa3d function drawSkin(skin:Skin, camera:Camera3D, joints:Vector.<Joint>, numJoints:int, maxJoints:int, vertexBuffer:VertexBuffer3D, indexBuffer:IndexBuffer3D, numTriangles:int):void {
var context3d:Context3D = camera.view.context3d;
if (transparent) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
} else {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
}
//trace(maxJoints);
var numOmnies:int = skin.omniesLength/8;
var key:String = numJoints + "_" + maxJoints + "_" + numOmnies;
var program:Program3D = programs[key];
if (program == null) {
program = initProgram(context3d, numJoints, maxJoints, numOmnies);
programs[key] = program;
}
context3d.setProgram(program);
var i:int;
// Вершины
context3d.setVertexStream(0, vertexBuffer, 0, Context3DVertexFormat.FLOAT_3);
// UV
context3d.setVertexStream(1, vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
// Нормали
context3d.setVertexStream(2, vertexBuffer, 5, Context3DVertexFormat.FLOAT_3);
// Тангенты
context3d.setVertexStream(3, vertexBuffer, 8, Context3DVertexFormat.FLOAT_4);
// Индексы и веса
for (i = 0; i < Math.floor(maxJoints/2); i++) {
context3d.setVertexStream(4 + i, vertexBuffer, 12 + i*4, Context3DVertexFormat.FLOAT_4);
}
// Джоинты
for (i = 0; i < numJoints; i++) {
var joint:Joint = joints[i];
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, i*4, joint.localMatrix, true);
}
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, numJoints*4, skin.projectionMatrix, true);
// Источники света
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 0, 1, Vector.<Number>([0, 0, 0.5, 1]));
context3d.setProgramConstants(Context3DProgramType.FRAGMENT, 1, numOmnies*2, skin.omnies);
// Текстуры
context3d.setTexture(0, diffuse3D);
context3d.setTexture(1, normal3D);
context3d.setTexture(2, specular3D);
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(indexBuffer, 0, numTriangles);
} else {
context3d.drawTriangles(indexBuffer, 0, numTriangles);
}
context3d.setVertexStream(1, null, 0, Context3DVertexFormat.DISABLED);
context3d.setVertexStream(2, null, 0, Context3DVertexFormat.DISABLED);
context3d.setVertexStream(3, null, 0, Context3DVertexFormat.DISABLED);
for (i = 0; i < Math.floor(maxJoints/2); i++) {
context3d.setVertexStream(4 + i, null, 0, Context3DVertexFormat.DISABLED);
}
if (transparent) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
private function initProgram(context3d:Context3D, numJoints:int, maxJoints:int, numOmnies:int):Program3D {
var i:int;
var index:int;
var vshader:String = "";
// Вершина
vshader += "m44 vt1, va0, vc[va4.x] \n"
vshader += "mul vt1, vt1, va4.y \n"
vshader += "m44 vt0, va0, vc[va4.z] \n"
vshader += "mul vt0, vt0, va4.w \n";
vshader += "add vt1, vt1, vt0 \n";
// Нормаль
vshader += "m33 vt2.xyz, va2.xyz, vc[va4.x] \n"
vshader += "mul vt2.xyz, vt2.xyz, va4.y \n"
vshader += "m33 vt0.xyz, va2.xyz, vc[va4.z] \n"
vshader += "mul vt0.xyz, vt0.xyz, va4.w \n";
vshader += "add vt2.xyz, vt2.xyz, vt0.xyz \n";
vshader += "mov vt2.w, va2.w \n"
// Тангент
vshader += "m33 vt3.xyz, va3.xyz, vc[va4.x] \n"
vshader += "mul vt3.xyz, vt3.xyz, va4.y \n"
vshader += "m33 vt0.xyz, va3.xyz, vc[va4.z] \n"
vshader += "mul vt0.xyz, vt0.xyz, va4.w \n";
vshader += "add vt3.xyz, vt3.xyz, vt0.xyz \n";
vshader += "mov vt3.w, va3.w \n"
for (i = 1; i < Math.floor(maxJoints/2); i++) {
index = i + 4;
// Вершина
vshader += "m44 vt0, va0, vc[va" + index + ".x] \n"
vshader += "mul vt0, vt0, va" + index + ".y \n";
vshader += "add vt1, vt1, vt0 \n";
vshader += "m44 vt0, va0, vc[va" + index + ".z] \n"
vshader += "mul vt0, vt0, va" + index + ".w \n";
vshader += "add vt1, vt1, vt0 \n";
// Нормаль
vshader += "m33 vt0.xyz, va2.xyz, vc[va" +index + ".x] \n"
vshader += "mul vt0.xyz, vt0.xyz, va" + index + ".y \n";
vshader += "add vt2.xyz, vt2.xyz, vt0.xyz \n";
vshader += "m33 vt0.xyz, va2.xyz, vc[va" +index + ".z] \n"
vshader += "mul vt0.xyz, vt0.xyz, va" + index + ".w \n";
vshader += "add vt2.xyz, vt2.xyz, vt0.xyz \n";
// Тангент
vshader += "m33 vt0.xyz, va3.xyz, vc[va" + index + ".x] \n"
vshader += "mul vt0.xyz, vt0.xyz, va" + index + ".y \n";
vshader += "add vt3.xyz, vt3.xyz, vt0.xyz \n";
vshader += "m33 vt0.xyz, va3.xyz, vc[va" + index + ".z] \n"
vshader += "mul vt0.xyz, vt0.xyz, va" + index + ".w \n";
vshader += "add vt3.xyz, vt3.xyz, vt0.xyz \n";
}
// Проецирование
vshader += "dp4 vt5.x, vt1, vc" + int(numJoints*4) + " \n"
vshader += "dp4 vt5.y, vt1, vc" + int(numJoints*4 + 1) + " \n"
vshader += "dp4 vt5.z, vt1, vc" + int(numJoints*4 + 2) + " \n"
vshader += "dp4 vt5.w, vt1, vc" + int(numJoints*4 + 3) + " \n"
vshader += "mov op, vt5 \n";
// Битангент
vshader += "crs vt4.xyz, vt3, vt2 \n";
vshader += "mul vt4.xyz, vt4.xyz, va3.w \n";
vshader += "mov vt4.w, va3.w \n";
// Коррекция тангента
vshader += "crs vt3.xyz, vt2, vt4 \n";
// Транспонирование
vshader += "mov vt5.w, vt3.y \n";
vshader += "mov vt3.y, vt4.x \n";
vshader += "mov vt4.x, vt5.w \n";
vshader += "mov vt5.w, vt3.z \n";
vshader += "mov vt3.z, vt2.x \n";
vshader += "mov vt2.x, vt5.w \n";
vshader += "mov vt5.w, vt4.z \n";
vshader += "mov vt4.z, vt2.y \n";
vshader += "mov vt2.y, vt5.w \n";
// Передача
vshader += "mov v1, vt1 \n";
vshader += "mov v2, vt2 \n";
vshader += "mov v3, vt3 \n";
vshader += "mov v4, vt4 \n";
vshader += "mov v0, va1";
var fshader:String = "";
// Диффузия
fshader += "tex ft0, v0, fs0 <2d,clamp,linear,miplinear> \n"
// Нормали
fshader += "tex ft1, v0, fs1 <2d,clamp,linear,miplinear> \n"
// Спекулар
fshader += "tex ft2, v0, fs2 <2d,clamp,linear,miplinear> \n"
// Трансформация нормали в локальное пространство из тангента
fshader += "dp3 ft3.x, ft1, v3 \n"
fshader += "dp3 ft3.y, ft1, v4 \n"
fshader += "dp3 ft3.z, ft1, v2 \n"
fshader += "mov ft3.w, ft0.w \n"
// Нулевой вектор
fshader += "mov ft4, fc0.x \n";
for (i = 0; i < numOmnies; i++) {
// Вектор от вершины к омнику
fshader += "sub ft5, fc" + int(1 + 2*i) + ", v1 \n"
// Обратная длина вектора
fshader += "dp3 ft6.x, ft5, ft5 \n"
fshader += "rsq ft6.x, ft6.x \n"
// Длина вектора
fshader += "div ft7.x, fc0.w, ft6.x \n"
// Нормализация
fshader += "mul ft6, ft5, ft6.x \n"
// Угол
fshader += "dp3 ft6.x, ft3, ft6 \n"
fshader += "sat ft6.x, ft6.x \n"
// Расстояние
fshader += "mul ft7.x, ft7.x, fc" + int(1 + 2*i) + ".w \n"
fshader += "sub ft7.x, fc0.w, ft7.x \n"
fshader += "sat ft7.x, ft7.x \n"
fshader += "mul ft6.x, ft6.x, ft7.x \n"
// Цвет
fshader += "mul ft6.xyz, fc" + int(2 + 2*i) + ".xyz, ft6.x \n"
// Сила
fshader += "mul ft6.xyz, fc" + int(2 + 2*i) + ".w, ft6.xyz \n"
// Добавление
fshader += "add ft4.xyz, ft4.xyz, ft6.xyz \n"
}
// Амбиент
//fshader += "add ft4.xyz, ft4.xyz, fc0.z \n"
// Спекулар
fshader += "mul ft4.xyz, ft4.xyz, ft2.x \n"
// Умножение на 2 как в лайтмапе
fshader += "add ft4.xyz, ft4.xyz, ft4.xyz \n"
fshader += "mul ft0.xyz, ft0.xyz, ft4.xyz \n"
fshader += "mov oc, ft0";
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vshader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fshader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
}
}

View File

@@ -0,0 +1,332 @@
package alternativa.engine3d.materials {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.objects.Joint; Joint;
import alternativa.engine3d.objects.Mesh;
import flash.filters.ConvolutionFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
import alternativa.engine3d.objects.Skin;
import alternativa.engine3d.lights.DirectionalLight;
import flash.display3D.Context3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Texture3D;
import flash.display3D.Context3DVertexFormat;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DBlendMode;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DTextureFormat;
import flash.display.BitmapData;
use namespace alternativa3d;
public class TextureMaterial extends Material {
static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true);
static private const matrix:Matrix = new Matrix(0.5, 0, 0, 0.5);
static private const rect:Rectangle = new Rectangle();
static private const point:Point = new Point();
public var useMipmapping:Boolean = true;
/**
* URL диффузной карты.
* Это свойство нужно при загрузке текстур ипользуя класс <code>MaterialLoader</code>.
* @see alternativa.engine3d.materials.MaterialLoader
*/
public var diffuseMapURL:String;
/**
* URL карты прозрачности.
* Это свойство нужно при загрузке текстур ипользуя класс <code>MaterialLoader</code>.
* @see alternativa.engine3d.materials.MaterialLoader
*/
public var opacityMapURL:String;
/**
* Флаг повторения текстуры при отрисовке.
*/
public var repeat:Boolean = false;
/**
* Флаг сглаживания текстуры при отрисовке.
*/
public var smooth:Boolean = true;
/**
* @private
*/
alternativa3d var _texture:BitmapData;
/**
* @private
*/
alternativa3d var texture3d:Texture3D;
/**
* Создаёт новый экземпляр.
* @param texture Текстура.
* @param repeat Флаг повторения текстуры.
* @param smooth Флаг сглаживания текстуры.
*/
public function TextureMaterial(texture:BitmapData = null, repeat:Boolean = false, smooth:Boolean = true) {
_texture = texture;
this.repeat = repeat;
this.smooth = smooth;
}
private function getSkinProgram(camera:Camera3D, context3d:Context3D, numJoints:uint):Program3D {
var key:String = "Tskin" + ((repeat) ? "R" : "r") + ((smooth) ? "S" : "s") + numJoints.toString();
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initSkinProgram(context3d, numJoints);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function getMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var key:String = "Tmesh" +
((useMipmapping) ? "M" : "m") +
((repeat) ? "R" : "r") +
((smooth) ? "S" : "s") +
((makeShadows) ? "1" : "0");
var program:Program3D = camera.context3dCachedPrograms[key];
if (program == null) {
program = initMeshProgram(camera, context3d, makeShadows);
camera.context3dCachedPrograms[key] = program;
}
return program;
}
private function initMeshProgram(camera:Camera3D, context3d:Context3D, makeShadows:Boolean):Program3D {
var vertexShader:String = meshProjectionShader("op") +
"mov v0, va1 \n";
var fragmentShader:String = "tex ft0, v0, fs0 <2d," + ((repeat) ? "repeat" : "clamp") + "," + ((smooth) ? "linear" : "nearest") + "," + ((useMipmapping) ? "miplinear" : "mipnone") + "> \n";
if (makeShadows) {
var shadowCaster:DirectionalLight = camera.shadowCaster;
vertexShader += shadowCaster.attenuateVertexShader("v1", 4);
fragmentShader += shadowCaster.attenuateFragmentShader("ft1", "v1", 1, 0);
fragmentShader += "mul ft0, ft1, ft0 \n" +
"mov oc, ft0";
} else {
fragmentShader += "mov oc, ft0";
}
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexShader, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentShader, false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
private function initSkinProgram(context3d:Context3D, numJoints:uint):Program3D {
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX,
skinProjectionShader(numJoints, "op", "vt0", "vt1") +
"mov v0, va1" , false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT,
"tex ft0, v0, fs0 <2d," + ((repeat) ? "repeat" : "clamp") + "," + ((smooth) ? "linear" : "nearest") + ",miplinear> \n" +
"mov oc, ft0", false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
return program;
}
override alternativa3d function drawSkin(skin:Skin, camera:Camera3D, joints:Vector.<Joint>, numJoints:int, maxJoints:int, vertexBuffer:VertexBuffer3D, indexBuffer:IndexBuffer3D, numTriangles:int):void {
if (texture3d == null || numJoints < 1) return;
var context3d:Context3D = camera.view._context3d;
context3d.setProgram(getSkinProgram(camera, context3d, numJoints));
if (_texture.transparent) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
} else {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
}
skinProjectionShaderSetup(context3d, joints, numJoints, vertexBuffer);
context3d.setVertexStream(1, vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
context3d.setTexture(0, texture3d);
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(indexBuffer, 0, numTriangles);
} else {
context3d.drawTriangles(indexBuffer, 0, numTriangles);
}
skinProjectionShaderClean(context3d, numJoints);
context3d.setVertexStream(1, null, 0, Context3DVertexFormat.DISABLED);
if (_texture.transparent) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
/**
* Отрисовывает меш
* @param view
* @param projection матрица для проецирования вершин
* @param vertexBuffer буффер данных вершин, в котором в диапазоне индексов 0 - 3 хранятся координаты вершин, а в индексах 3 - 5 uv координаты.
* @param indexBuffer буффер индексов треугольников
* @param numTriangles количество треугольников для отрисовки
*/
override alternativa3d function drawMesh(mesh:Mesh, camera:Camera3D):void {
if (texture3d == null) return;
var context3d:Context3D = camera.view._context3d;
var makeShadows:Boolean = mesh.useShadows && camera.shadowCaster != null && camera.shadowCaster.currentSplitHaveShadow;
if (_texture.transparent) {
context3d.setBlending(Context3DBlendMode.SOURCE_ALPHA, Context3DBlendMode.ONE_MINUS_SOURCE_ALPHA);
context3d.setDepthTest(false, Context3DCompareMode.LESS);
} else {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
}
var shadowCaster:DirectionalLight;
if (mesh.omniesLength == 0) {
context3d.setProgram(getMeshProgram(camera, context3d, makeShadows));
meshProjectionShaderSetup(context3d, mesh);
context3d.setVertexStream(1, mesh.geometry.vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
if (makeShadows) {
shadowCaster = camera.shadowCaster;
shadowCaster.attenuateProgramSetup(context3d, mesh, 4, 1, 0);
}
} else {
context3d.setVertexStream(0, mesh.geometry.vertexBuffer, 0, Context3DVertexFormat.FLOAT_3);
context3d.setVertexStream(1, mesh.geometry.vertexBuffer, 3, Context3DVertexFormat.FLOAT_2);
context3d.setVertexStream(2, mesh.geometry.vertexBuffer, 5, Context3DVertexFormat.FLOAT_3);
context3d.setProgramConstantsMatrix(Context3DProgramType.VERTEX, 0, mesh.projectionMatrix, true);
context3d.setProgramConstants(Context3DProgramType.VERTEX, 4, 1, Vector.<Number>([0, 0, 0, 1]));
context3d.setProgramConstants(Context3DProgramType.VERTEX, 5, mesh.omniesLength/4, mesh.omnies);
//var vertexProgram:ByteArray = new AGALMiniAssembler().assemble("VERTEX",
var res:String =
"dp4 op.x, va0, vc0 \n" +
"dp4 op.y, va0, vc1 \n" +
"dp4 op.z, va0, vc2 \n" +
"dp4 op.w, va0, vc3 \n" +
// Нулевой вектор
"mov vt3, vc4.x \n";
for (var i:int = 0; i < mesh.omniesLength/8; i++) {
res +=
// Вектор от вершины к омнику
"sub vt0, vc" + int(5 + 2*i) + ", va0 \n" +
// Обратная длина вектора
"dp3 vt1.x, vt0, vt0 \n" +
"rsq vt1.x, vt1.x \n" +
// Длина вектора
"div vt2.x, vc4.w, vt1.x \n" +
// Нормализация
"mul vt1, vt0, vt1.x \n" +
// Угол
"dp3 vt1.x, va2, vt1 \n" +
"sat vt1.x, vt1.x \n" +
// Расстояние
"mul vt2.x, vt2.x, vc" + int(5 + 2*i) + ".w \n" +
"sub vt2.x, vc4.w, vt2.x \n" +
"sat vt2.x, vt2.x \n" +
"mul vt1.x, vt1.x, vt2.x \n" +
// Цвет
"mul vt1.xyz, vc" + int(6 + 2*i) + ".xyz, vt1.x \n" +
// Сила
"mul vt1.xyz, vc" + int(6 + 2*i) + ".w, vt1.xyz \n" +
// Добавление
"add vt3.xyz, vt3.xyz, vt1.xyz \n"
}
res +=
// Умножение на 2 как в лайтмапе
"add vt3.xyz, vt3.xyz, vt3.xyz \n" +
"mov v1, vt3 \n" +
"mov v0, va1";
var vertexProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, res, false);
var fragmentProgram:ByteArray = new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT,
"tex ft0, v0, fs0 <2d,clamp,linear,miplinear> \n" +
"mul ft0.xyz, ft0.xyz, v1.xyz \n" +
"mov oc, ft0", false);
var program:Program3D = context3d.createProgram();
program.upload(vertexProgram, fragmentProgram);
context3d.setProgram(program);
}
context3d.setTexture(0, texture3d);
context3d.setCulling(Context3DTriangleFace.FRONT);
if (camera.debug) {
context3d.drawTrianglesSynchronized(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
} else {
context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles);
}
if (makeShadows) {
shadowCaster.attenuateProgramClean(context3d, 1);
}
context3d.setVertexStream(1, null, 0, Context3DVertexFormat.DISABLED);
if (_texture.transparent) {
context3d.setBlending(Context3DBlendMode.ONE, Context3DBlendMode.ZERO);
context3d.setDepthTest(true, Context3DCompareMode.LESS);
}
}
override alternativa3d function update(context3d:Context3D):void {
if (texture3d != null) return;
if (_texture != null) {
var level:int = 0;
texture3d = context3d.createTexture(_texture.width, _texture.height, Context3DTextureFormat.BGRA, false);
texture3d.upload(_texture, level++);
filter.preserveAlpha = !_texture.transparent;
var bmp:BitmapData = (_texture.width*_texture.height > 16777215) ? _texture.clone() : new BitmapData(_texture.width, _texture.height, _texture.transparent);
var current:BitmapData = _texture;
rect.width = _texture.width;
rect.height = _texture.height;
while (rect.width % 2 == 0 && rect.height % 2 == 0) {
bmp.applyFilter(current, rect, point, filter);
rect.width >>= 1;
rect.height >>= 1;
current = new BitmapData(rect.width, rect.height, _texture.transparent, 0);
current.draw(bmp, matrix, null, null, null, false);
texture3d.upload(current, level++);
}
bmp.dispose();
}
}
override alternativa3d function reset():void {
texture3d = null;
}
/**
* Ткстура материала.
*/
public function get texture():BitmapData {
return _texture;
}
/**
* @private
*/
public function set texture(value:BitmapData):void {
//if (value.width % 2 > 0 || value.height % 2 > 0
_texture = value;
reset();
}
override alternativa3d function get isTransparent():Boolean {
return (_texture != null) ? _texture.transparent : false;
}
}
}

View File

@@ -0,0 +1,86 @@
package alternativa.engine3d.objects {
import __AS3__.vec.Vector;
import flash.display.BitmapData;
import flash.geom.ColorTransform;
public class HeightMap {
// private var map:Vector.<Vector.<uint>>;
private var mapWidth:uint;
private var mapHeight:uint;
private var normalMap:BitmapData;
private var heightMap:BitmapData;
public function HeightMap(heightMap:BitmapData) {
this.heightMap = heightMap;
mapWidth = heightMap.width;
mapHeight = heightMap.height;
// map = new Vector.<Vector.<uint>>();
// for (var i:int = 0; i < mapWidth; i++) {
// var vector:Vector.<uint> = new Vector.<uint>();
// map[i] = vector;
// for (var j:int = 0; j < mapHeight;j++) {
// var color:uint = heightMap.getPixel(i, j);
// vector[j] = ((color >> 16) & 0xFF)/255;
// }
// }
}
public function getHeight(x:uint, y:uint):Number {
// if (heightMap == null) {
// trace("getHeight::heightMap == null");
// return null;
// }
if (x < 0 || y < 0 || x >= mapWidth || y >= mapHeight) {
trace("getHeight::incorrect x or y ", x, y);
return -1;
}
// return map[x][y];
return ((heightMap.getPixel(x, y) >> 16) & 0xFF)/255;
}
public function get width():uint {
return mapWidth;
}
public function get height():uint {
return mapHeight;
}
public function generateNormalMap():BitmapData {
var normalMap:BitmapData = new BitmapData(mapWidth, mapHeight);
var ct:ColorTransform = new ColorTransform();
var scale:Number = 16;
for (var i:int = 0; i < mapWidth - 1; i++) {
for (var j:int = 0; j < mapHeight - 1; j++) {
var c:Number = getHeight(i, j);
var dx:Number = (c - getHeight(i, j+1))*scale;
var dy:Number = (c - getHeight(i+1, j))*scale;
// var c1:Number = getHeight(i+1, j+1)/255;
// var dx1:Number = (c1 - getHeight(i, j+1)/255)*scale;
// var dy1:Number = (c1 - getHeight(i+1, j)/255)*scale;
var len:Number = Math.sqrt(dx*dx + dy*dy + 1);
// var len1:Number = Math.sqrt(dx1*dx1 + dy1*dy1 + 1);
var nx:Number = dy/len;
var ny:Number = dx/len;
var nz:Number = 1/len;
ct.redOffset = 128 + 127*nx;
ct.greenOffset = 128 + 127*ny;
// if (nz < 0.8) trace("nz", nz);
ct.blueOffset = 128 + 127*nz;
normalMap.setPixel(i, j, ct.color);
}
}
return normalMap;
}
public function getNormalMap():BitmapData {
if (normalMap != null) return normalMap;
return generateNormalMap();
}
}
}

View File

@@ -0,0 +1,104 @@
package alternativa.engine3d.objects {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Object3D;
import flash.geom.Matrix3D;
use namespace alternativa3d;
public class Joint extends Object3D {
private var _joints:Vector.<Joint>;
private var _numJoints:int = 0;
private var bindingMatrix:Matrix3D;
alternativa3d var localMatrix:Matrix3D = new Matrix3D();
alternativa3d var index:int;
public function setBindingMatrix(matrix:Matrix3D):void {
bindingMatrix = matrix;
}
public function calculateMatrix():void {
for (var i:int = 0; i < _numJoints; i++) {
var joint:Joint = _joints[i];
joint.composeMatrix();
joint.projectionMatrix.identity();
joint.projectionMatrix.append(joint.cameraMatrix);
joint.projectionMatrix.append(projectionMatrix);
joint.localMatrix.identity();
joint.localMatrix.append(joint.cameraMatrix);
joint.localMatrix.append(localMatrix);
joint.calculateMatrix();
if (joint.bindingMatrix != null) {
joint.projectionMatrix.prepend(joint.bindingMatrix);
joint.localMatrix.prepend(joint.bindingMatrix);
}
}
}
public function addJoint(joint:Joint):Joint {
if (joint == null) {
throw new Error("Joint cannot be null");
}
if (_joints == null) {
_joints = new Vector.<Joint>();
}
_joints[_numJoints++] = joint;
return joint;
}
public function removeJoint(joint:Joint):Joint {
var index:int = _joints.indexOf(joint);
if (index < 0) throw new ArgumentError("Joint not found");
_numJoints--;
var j:int = index + 1;
while (index < _numJoints) {
_joints[index] = _joints[j];
index++;
j++;
}
if (_numJoints <= 0) {
_joints = null;
} else {
_joints.length = _numJoints;
}
return joint;
}
public function get numJoints():int {
return _numJoints;
}
public function getJointAt(index:int):Joint {
return _joints[index];
}
override public function clone():Object3D {
var res:Joint = new Joint();
res.cloneBaseProperties(this);
return res;
}
override protected function cloneBaseProperties(source:Object3D):void {
super.cloneBaseProperties(source);
var sourceJoint:Joint = Joint(source);
_numJoints = sourceJoint._numJoints;
if (_numJoints > 0) {
_joints = new Vector.<Joint>(_numJoints);
for (var i:int = 0; i < _numJoints; i++) {
_joints[i] = Joint(sourceJoint._joints[i].clone());
}
}
if (sourceJoint.bindingMatrix != null) {
bindingMatrix = sourceJoint.bindingMatrix.clone();
}
}
}
}

View File

@@ -0,0 +1,184 @@
package alternativa.engine3d.objects {
import flash.display.BitmapData;
import flash.filters.ConvolutionFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.utils.getTimer;
public class LightMapGenerator {
private var heightMap:HeightMap;
private var mapWidth:uint;
private var mapHeight:uint;
private var step:uint;
private var ambientColor:uint;
private var ambientR:uint;
private var ambientG:uint;
private var ambientB:uint;
public function LightMapGenerator(heightMap:HeightMap, step:uint, ambientColor:uint) {
this.heightMap = heightMap;
mapWidth = heightMap.width;
mapHeight = heightMap.height;
this.ambientColor = ambientColor;
this.ambientR = (ambientColor >> 16) & 0xFF;
this.ambientG = (ambientColor >> 8) & 0xFF;
this.ambientB = (ambientColor) & 0xFF;
this.step = step;
}
public function generateLightMap(lightVector:Vector3D):BitmapData {
var normalMap:BitmapData = heightMap.getNormalMap();
if (normalMap == null) return null;
var lightMap:BitmapData = new BitmapData(mapWidth, mapHeight);
var ct:ColorTransform = new ColorTransform();
var i:int;
var j:int;
var time:Number = getTimer();
// Считаем свет
var lLen:Number = Math.sqrt(lightVector.x*lightVector.x + lightVector.y*lightVector.y + lightVector.z*lightVector.z);
lightVector.x = lightVector.x/lLen;
lightVector.y = lightVector.y/lLen;
lightVector.z = lightVector.z/lLen;
for (i = 0; i < mapWidth; i++) {
for (j = 0; j < mapHeight; j++) {
ct.color = normalMap.getPixel(i, j);
var normal:Vector3D = new Vector3D((ct.redOffset - 128)/127, (ct.greenOffset - 128)/127, (ct.blueOffset - 128)/127);
// var nLen:Number = Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
// var dot:Number = (normal.x/nLen)*lightVector.x + (normal.y/nLen)*lightVector.y + (normal.z/nLen)*lightVector.z;
var dot:Number = (normal.x)*lightVector.x + (normal.y)*lightVector.y + (normal.z)*lightVector.z;
var l:Number;
if (dot < 0) {
l = 0;
} else {
l = dot*255;
}
ct.redOffset = l;// + ambientR;
ct.greenOffset = l;// + ambientG;
ct.blueOffset = l;// + ambientB;
// dot = dot*127 + 128;
// ct.redOffset = dot;// + ambientR;
// ct.greenOffset = dot;// + ambientG;
// ct.blueOffset = dot;// + ambientB;
lightMap.setPixel(i, j, ct.color);
}
}
trace("light", getTimer() - time);
if (lightVector.x == 0 && lightVector.y == 0) return lightMap;
// Тени
lightMap = generateShadows(lightMap, lightVector);
//
// lightMap.applyFilter(lightMap, new Rectangle(0, 0, mapWidth, mapHeight), new Point(0, 0), gaussianFilter);
// lightMap.applyFilter(lightMap, new Rectangle(0, 0, mapWidth, mapHeight), new Point(0, 0), gaussianFilter);
// lightMap.applyFilter(lightMap, new Rectangle(0, 0, mapWidth, mapHeight), new Point(0, 0), blurFilter);
return lightMap;
}
private function generateShadows(lightMap:BitmapData, lightVector:Vector3D):BitmapData {
var scale:Number = 1;
// mapWidth *= scale;
// mapHeight *= scale;
// var tmp:BitmapData = new BitmapData(mapWidth, mapHeight);
// tmp.draw(lightMap, new Matrix(scale, 0, 0, scale, 0, 0), null, null, null, false);
// lightMap = tmp;
//
var time:Number = getTimer();
var ct:ColorTransform = new ColorTransform();
ct.redOffset = 0;
ct.greenOffset = 0;
ct.blueOffset = 0;
var shadowColor:uint = ct.color;
// var shadowColor:uint = ambientColor;
var curColor:uint;
if (lightVector.x != 0) {
lightVector.y = lightVector.y/lightVector.x;
lightVector.z = lightVector.z/lightVector.x;
lightVector.x = 1;
} else if (lightVector.y != 0) {
lightVector.x = lightVector.x/lightVector.y;
lightVector.z = lightVector.z/lightVector.y;
lightVector.y = 1;
}
lightVector.z *= step;
var point:Vector3D = new Vector3D();
for (var i:uint = 0; i < mapWidth; i++) {
for (var j:uint = 0; j < mapHeight; j++) {
curColor = lightMap.getPixel(i, j);
if (curColor > shadowColor) {
var flag:Boolean = true;
//var point:Vector3D = new Vector3D(i, j, getHeight(i/scale, j/scale));
point.x = i;
point.y = j;
point.z = heightMap.getHeight(uint(i/scale), uint(j/scale));
while (flag) {
point = getPointOnLightRay(lightVector, point, 0.1);
if (point == null) {
flag = false;
point = new Vector3D();
} else {
if (point.z <= heightMap.getHeight(uint(point.x/scale), uint(point.y/scale))) {
flag = false;
} else {
lightMap.setPixel(point.x, point.y, shadowColor);
}
}
}
}
}
}
trace("shadow", getTimer() - time);
var array:Array = [0, 1, 0, 1, 4, 1, 0, 1, 0];
var gaussianFilter:ConvolutionFilter = new ConvolutionFilter(3, 3, array, 8);
lightMap.applyFilter(lightMap, new Rectangle(0, 0, mapWidth, mapHeight), new Point(0, 0), gaussianFilter);
lightMap.applyFilter(lightMap, new Rectangle(0, 0, mapWidth, mapHeight), new Point(0, 0), gaussianFilter);
// scale = 1/scale;
// mapWidth *= scale;
// mapHeight *= scale;
// tmp = new BitmapData(mapWidth, mapHeight);
// tmp.draw(lightMap, new Matrix(scale, 0, 0, scale, 0, 0), null, null, null, false);
// lightMap = tmp;
return lightMap;
}
private function getPointOnLightRay(vector:Vector3D, startPoint:Vector3D, threshold:Number = 0.1):Vector3D {
var x:Number = startPoint.x - vector.x;
var y:Number = startPoint.y - vector.y;
var z:Number = startPoint.z - vector.z;
startPoint.x = uint(x + 0.5);//Math.round(x);
startPoint.y = uint(y + 0.5);//Math.round(y);
startPoint.z = z;
while (x < mapWidth && y < mapHeight && x >=0 && y >= 0 && z >=0) {
var deltaX:Number = x - startPoint.x;
deltaX = deltaX > 0 ? deltaX : -deltaX;
var deltaY:Number = y - startPoint.y;
deltaY = deltaY > 0 ? deltaY : -deltaY;
if (deltaX < threshold && deltaY < threshold) {
return startPoint;
}
// if (Math.abs(x - startPoint.x) < threshold && Math.abs(y - startPoint.y) < threshold) {
// return startPoint;
// }
x -= vector.x;
y -= vector.y;
z -= vector.z;
startPoint.x = uint(x + 0.5);//Math.round(x);
startPoint.y = uint(y + 0.5);//Math.round(y);
startPoint.z = z;
}
return null;
}
}
}

View File

@@ -0,0 +1,237 @@
package alternativa.engine3d.objects {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.RayIntersectionData;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.core.Wrapper;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.lights.OmniLight;
import alternativa.engine3d.materials.Material;
import flash.display3D.Context3D;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
public class Mesh extends Object3D {
public var material:Material;
public var geometry:Geometry;
alternativa3d var omnies:Vector.<Number> = new Vector.<Number>();
alternativa3d var omniesLength:int = 0;
override alternativa3d function get isTransparent():Boolean {
return (material == null) ? false : material.isTransparent;
}
/**
* @inheritDoc
*/
override public function clone():Object3D {
var mesh:Mesh = new Mesh();
mesh.cloneBaseProperties(this);
mesh.geometry = geometry;
mesh.material = material;
return mesh;
}
/**
* @private
*/
override alternativa3d function draw(camera:Camera3D):void {
if (geometry == null || material == null) return;
calculateOmnies(camera);
projectionMatrix.identity();
projectionMatrix.append(cameraMatrix);
projectionMatrix.append(camera.projectionMatrix);
geometry.update(camera.view._context3d);
material.update(camera.view._context3d);
material.drawMesh(this, camera);
camera.numDraws++;
camera.numTriangles += geometry.numTriangles;
}
alternativa3d function calculateOmnies(camera:Camera3D):void {
inverseCameraMatrix.identity();
inverseCameraMatrix.append(cameraMatrix);
inverseCameraMatrix.invert();
var omniLocalCoords:Vector.<Number> = new Vector.<Number>(3);
omniesLength = 0;
for each (var omni:OmniLight in camera.omniLights) {
inverseCameraMatrix.transformVectors(omni.cameraCoords, omniLocalCoords);
var ox:Number = omniLocalCoords[0];
var oy:Number = omniLocalCoords[1];
var oz:Number = omniLocalCoords[2];
if (ox + omni.radius > boundMinX && ox - omni.radius < boundMaxX && oy + omni.radius > boundMinY && oy - omni.radius < boundMaxY && oz + omni.radius > boundMinZ && oz - omni.radius < boundMaxZ) {
omnies[omniesLength++] = ox;
omnies[omniesLength++] = oy;
omnies[omniesLength++] = oz;
omnies[omniesLength++] = 1/omni.radius;
omnies[omniesLength++] = ((omni.color >> 16) & 0xFF)/255;
omnies[omniesLength++] = ((omni.color >> 8) & 0xFF)/255;
omnies[omniesLength++] = (omni.color & 0xFF)/255;
omnies[omniesLength++] = omni.strength;
}
}
}
/**
* @private
*/
override alternativa3d function drawInShadowMap(camera:Camera3D, light:DirectionalLight):void {
if (geometry == null || material == null) return;
projectionMatrix.identity();
projectionMatrix.append(cameraMatrix);
projectionMatrix.append(light.projectionMatrix);
var context3d:Context3D = camera.view._context3d;
geometry.update(context3d);
material.update(context3d);
light.predraw(context3d);
material.drawMeshInShadowMap(this, camera, light);
camera.numDraws++;
camera.numTriangles += geometry.numTriangles;
}
/**
* @private
*/
override alternativa3d function updateBounds(bounds:Object3D, matrix:Matrix3D = null):void {
if (geometry != null) {
var vertex:Vertex;
if (matrix != null) {
var rawData:Vector.<Number> = matrix.rawData;
var ma:Number = rawData[0];
var mb:Number = rawData[4];
var mc:Number = rawData[8];
var md:Number = rawData[12];
var me:Number = rawData[1];
var mf:Number = rawData[5];
var mg:Number = rawData[9];
var mh:Number = rawData[13];
var mi:Number = rawData[2];
var mj:Number = rawData[6];
var mk:Number = rawData[10];
var ml:Number = rawData[14];
for each (vertex in geometry._vertices) {
var x:Number = ma*vertex.x + mb*vertex.y + mc*vertex.z + md;
var y:Number = me*vertex.x + mf*vertex.y + mg*vertex.z + mh;
var z:Number = mi*vertex.x + mj*vertex.y + mk*vertex.z + ml;
if (x < bounds.boundMinX) bounds.boundMinX = x;
if (x > bounds.boundMaxX) bounds.boundMaxX = x;
if (y < bounds.boundMinY) bounds.boundMinY = y;
if (y > bounds.boundMaxY) bounds.boundMaxY = y;
if (z < bounds.boundMinZ) bounds.boundMinZ = z;
if (z > bounds.boundMaxZ) bounds.boundMaxZ = z;
}
} else {
for each (vertex in geometry._vertices) {
if (vertex.x < bounds.boundMinX) bounds.boundMinX = vertex.x;
if (vertex.x > bounds.boundMaxX) bounds.boundMaxX = vertex.x;
if (vertex.y < bounds.boundMinY) bounds.boundMinY = vertex.y;
if (vertex.y > bounds.boundMaxY) bounds.boundMaxY = vertex.y;
if (vertex.z < bounds.boundMinZ) bounds.boundMinZ = vertex.z;
if (vertex.z > bounds.boundMaxZ) bounds.boundMaxZ = vertex.z;
}
}
}
}
/**
* @inheritDoc
*/
override public function intersectRay(origin:Vector3D, direction:Vector3D, exludedObjects:Dictionary = null, camera:Camera3D = null):RayIntersectionData {
if (exludedObjects != null && exludedObjects[this]) return null;
if (!boundIntersectRay(origin, direction, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ)) return null;
var ox:Number = origin.x;
var oy:Number = origin.y;
var oz:Number = origin.z;
var dx:Number = direction.x;
var dy:Number = direction.y;
var dz:Number = direction.z;
var point:Vector3D;
var face:Face;
var minTime:Number = 1e+22;
for each (var f:Face in geometry._faces) {
var w:Wrapper = f.wrapper;
var a:Vertex = w.vertex; w = w.next;
var b:Vertex = w.vertex; w = w.next;
var c:Vertex = w.vertex;
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 normalX:Number = acz*aby - acy*abz;
var normalY:Number = acx*abz - acz*abx;
var normalZ:Number = acy*abx - acx*aby;
var len:Number = normalX*normalX + normalY*normalY + normalZ*normalZ;
if (len > 0.001) {
len = 1/Math.sqrt(len);
normalX *= len;
normalY *= len;
normalZ *= len;
}
var dot:Number = dx*normalX + dy*normalY + dz*normalZ;
if (dot < 0) {
var offset:Number = ox*normalX + oy*normalY + oz*normalZ - (a._x*normalX + a._y*normalY + a._z*normalZ);
if (offset > 0) {
var time:Number = -offset/dot;
if (point == null || time < minTime) {
var cx:Number = ox + dx*time;
var cy:Number = oy + dy*time;
var cz:Number = oz + dz*time;
var wrapper:Wrapper;
for (wrapper = f.wrapper; wrapper != null; wrapper = wrapper.next) {
a = wrapper.vertex;
b = (wrapper.next != null) ? wrapper.next.vertex : f.wrapper.vertex;
abx = b._x - a._x;
aby = b._y - a._y;
abz = b._z - a._z;
acx = cx - a._x;
acy = cy - a._y;
acz = cz - a._z;
if ((acz*aby - acy*abz)*normalX + (acx*abz - acz*abx)*normalY + (acy*abx - acx*aby)*normalZ < 0) break;
}
if (wrapper == null) {
if (time < minTime) {
minTime = time;
if (point == null) point = new Vector3D();
point.x = cx;
point.y = cy;
point.z = cz;
face = f;
}
}
}
}
}
}
if (point != null) {
var res:RayIntersectionData = new RayIntersectionData();
res.object = this;
res.face = face;
res.point = point;
res.time = minTime;
return res;
} else {
return null;
}
}
}
}

View File

@@ -0,0 +1,553 @@
package alternativa.engine3d.objects {
import __AS3__.vec.Vector;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.RayIntersectionData;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.core.Wrapper;
import flash.display3D.Context3D;
import flash.display3D.IndexBuffer3D;
import flash.display3D.VertexBuffer3D;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
public class Skin extends Mesh {
public var renderedJoints:Vector.<Joint>;
private var rootJoint:Joint = new Joint();
private var jointLists:Vector.<Vector.<Joint>>;
private var vertexBuffers:Vector.<VertexBuffer3D>;
private var indexBuffers:Vector.<IndexBuffer3D>;
private var numsTriangles:Vector.<int>;
private var maxesJoints:Vector.<int>;
private function divide(context3d:Context3D, limit:int):void {
var i:int;
var j:int;
var k:*;
var key:*;
var joint:Joint;
var sumJoints:int;
var index:int;
var face:Face;
var wrapper:Wrapper;
var siblings:Dictionary = new Dictionary();
var fcs:Dictionary = new Dictionary();
var heap:Dictionary = new Dictionary();
var groups:Vector.<Dictionary> = new Vector.<Dictionary>();
var groupsSiblings:Vector.<Dictionary> = new Vector.<Dictionary>();
geometry.calculateTBNs(true, false);
var renderedJointsLength:int = renderedJoints.length;
for (i = 0; i < renderedJointsLength; i++) {
joint = renderedJoints[i];
siblings[joint] = new Dictionary();
fcs[joint] = new Dictionary();
heap[joint] = true;
}
// Определение соседних по граням джоинтов
for each (face in geometry._faces) {
for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) {
for each (index in wrapper.vertex._jointsIndices) {
joint = renderedJoints[index];
fcs[joint][face] = true;
var dict:Dictionary = siblings[joint];
for (var w:Wrapper = face.wrapper; w != null; w = w.next) {
for each (var ind:uint in w.vertex._jointsIndices) {
dict[renderedJoints[ind]] = true;
}
}
}
}
}
for (key in siblings) {
joint = key;
delete siblings[joint][joint];
sumJoints = 1;
for (k in siblings[joint]) sumJoints++;
if (sumJoints > limit) throw new Error("Cannot devide skin.");
}
// Группировка
while (true) {
key = null;
for (key in heap) break;
if (key == null) break;
joint = key;
delete heap[key];
var groupSum:int = 1;
var group:Dictionary = new Dictionary();
groups.push(group);
group[joint] = true;
var groupSiblings:Dictionary = new Dictionary();
groupsSiblings.push(groupSiblings);
for (key in siblings[joint]) {
groupSiblings[key] = true;
groupSum++;
}
while (true) {
var found:Boolean = false;
for (key in groupSiblings) {
var jt:Joint = key;
sumJoints = 0;
for (k in siblings[jt]) {
if (!group[k] && !groupSiblings[k]) sumJoints++;
if (groupSum + sumJoints > limit) break;
}
if (groupSum + sumJoints <= limit) {
group[jt] = true;
delete heap[jt];
delete groupSiblings[jt];
for (k in siblings[jt]) {
if (!group[k] && !groupSiblings[k]) {
groupSiblings[k] = true;
groupSum++;
}
}
found = true;
break;
}
}
if (!found) break;
}
}
// Перебор групп
var num:int = 0;
jointLists = new Vector.<Vector.<Joint>>();
vertexBuffers = new Vector.<VertexBuffer3D>();
indexBuffers = new Vector.<IndexBuffer3D>();
numsTriangles = new Vector.<int>();
maxesJoints = new Vector.<int>();
for (i = 0; i < groups.length; i++) {
//trace("----");
var verticesSet:Dictionary = new Dictionary();
var facesSet:Dictionary = new Dictionary();
var joints:Vector.<Joint> = new Vector.<Joint>();
for (key in groups[i]) {
joint = key;
for (k in fcs[joint]) {
facesSet[k] = true;
delete fcs[joint][k];
}
}
for (key in groupsSiblings[i]) {
joint = key;
// Удаление грани дрокола из соседей
for (k in facesSet) {
delete fcs[joint][k];
}
}
// Вершины
for (k in facesSet) {
face = k;
for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) {
verticesSet[wrapper.vertex] = true;
}
}
var vertex:Vertex;
//var numAttributes:uint;
var maxAttributes:int = 0;
var verticesLength:int = 0;
var enJoints:Dictionary = new Dictionary();
var maxJoints:int = 0;
for (k in verticesSet) {
vertex = k;
verticesLength++;
/*if (vertex._attributes != null) {
numAttributes = vertex._attributes.length;
if (numAttributes > maxAttributes) {
maxAttributes = numAttributes;
}
}*/
var len:int = vertex._jointsIndices.length;
if (len > maxJoints) maxJoints = len;
for (j = 0; j < len; j++) {
enJoints[renderedJoints[vertex._jointsIndices[j]]] = true;
}
}
var numJoints:int = 0;
for (key in enJoints) {
joint = key;
//trace("j", joint);
joint.index = numJoints;
joints[numJoints] = joint;
numJoints++;
}
maxAttributes += numJoints*2 + 5 + 7;
var vertices:Vector.<Number> = new Vector.<Number>(verticesLength*maxAttributes);
var n:int = 0;
for (k in verticesSet) {
vertex = k;
index = maxAttributes*n;
vertices[int(index++)] = vertex._x;
vertices[int(index++)] = vertex._y;
vertices[int(index++)] = vertex._z;
vertices[int(index++)] = vertex._u;
vertices[int(index++)] = vertex._v;
if (vertex.normal == null) {
vertices[int(index++)] = 0;
vertices[int(index++)] = 0
vertices[int(index++)] = 1;
} else {
vertices[int(index++)] = vertex.normal.x;
vertices[int(index++)] = vertex.normal.y;
vertices[int(index++)] = vertex.normal.z;
}
if (vertex.tangent == null) {
vertices[int(index++)] = 0;
vertices[int(index++)] = 1;
vertices[int(index++)] = 0;
vertices[int(index++)] = 1;
} else {
vertices[int(index++)] = vertex.tangent.x;
vertices[int(index++)] = vertex.tangent.y;
vertices[int(index++)] = vertex.tangent.z;
vertices[int(index++)] = vertex.tangent.w;
}
var sum:Number = 0;
var nJ:int = vertex._jointsIndices.length;
for (j = 0; j < nJ; j++) {
sum += vertex._jointsWeights[j];
}
if (sum > 0) {
for (j = 0; j < nJ; j++) {
vertices[int(index++)] = renderedJoints[vertex._jointsIndices[j]].index*4;
vertices[int(index++)] = vertex._jointsWeights[j]/sum;
}
}
for (j = nJ; j < maxJoints; j++) {
vertices[int(index++)] = 0;
vertices[int(index++)] = 0;
}
/*if (vertex._attributes != null) {
numAttributes = vertex._attributes.length;
for (j = 0; j < numAttributes; j++) {
vertices[int(index++)] = vertex._attributes[j];
}
}*/
vertex.index = n;
n++;
}
// Грани
var indices:Vector.<uint> = new Vector.<uint>();
var numTriangles:int = 0;
for (k in facesSet) {
face = k;
var a:Wrapper = face.wrapper;
var b:Wrapper = a.next;
for (var c:Wrapper = b.next; c != null; c = c.next) {
indices.push(a.vertex.index);
indices.push(b.vertex.index);
indices.push(c.vertex.index);
b = c;
numTriangles++;
}
}
//trace(numTriangles);
// Создание буферов
if (numTriangles > 0) {
jointLists[num] = joints;
numsTriangles[num] = numTriangles;
maxesJoints[num] = maxJoints;
vertexBuffers[num] = context3d.createVertexBuffer(verticesLength, maxAttributes);
vertexBuffers[num].upload(vertices, 0, verticesLength);
indexBuffers[num] = context3d.createIndexBuffer(indices.length);
indexBuffers[num].upload(indices, 0, indices.length);
num++;
}
}
}
override alternativa3d function draw(camera:Camera3D):void {
if (geometry == null || material == null || renderedJoints == null) return;
calculateOmnies(camera);
projectionMatrix.identity();
projectionMatrix.append(cameraMatrix);
projectionMatrix.append(camera.projectionMatrix);
material.update(camera.view._context3d);
rootJoint.projectionMatrix.identity();
rootJoint.projectionMatrix.append(projectionMatrix);
rootJoint.localMatrix.identity();
rootJoint.calculateMatrix();
var numJoints:uint = renderedJoints.length;
//if (numJoints > 24 && indexBuffers == null) divide(camera.view._context3d, 24);
if (indexBuffers == null) divide(camera.view._context3d, 24);
if (indexBuffers != null) {
var numDraws:int = indexBuffers.length;
for (var i:int = 0; i < numDraws; i++) {
material.drawSkin(this, camera, jointLists[i], jointLists[i].length, maxesJoints[i], vertexBuffers[i], indexBuffers[i], numsTriangles[i]);
camera.numDraws++;
camera.numTriangles += numsTriangles[i];
}
} else {
geometry.update(camera.view._context3d);
numJoints = (numJoints <= geometry.numJointsInGeometry) ? numJoints : geometry.numJointsInGeometry;
material.drawSkin(this, camera, renderedJoints, numJoints, 0, geometry.vertexBuffer, geometry.indexBuffer, geometry.numTriangles);
camera.numDraws++;
camera.numTriangles += geometry.numTriangles;
}
}
// -- Расчет вершин на CPU
// private function transformVertices():void {
// var vertices:Vector.<Number> = new Vector.<Number>(verts.length);
// var numVerts:uint = verts.length/dwordsPerVertex;
// for (var i:int = 0; i < numVerts; i++) {
// var coords:Vector3D = new Vector3D();
// var index:int = dwordsPerVertex*i;
// for (var j:int = 0, numJoints:int = renderedJoints.length; j < numJoints; j++) {
// var weight:Number = verts[int(index + 5 + j)];
// if (weight > 0) {
// var joint:Joint = renderedJoints[j];
// var v:Vector3D = joint.renderMatrix.transformVector(new Vector3D(verts[index], verts[int(index + 1)], verts[int(index + 2)]));
// v.scaleBy(weight);
// coords.incrementBy(v);
// }
// }
// vertices[index] = coords.x;
// vertices[int(index + 1)] = coords.y;
// vertices[int(index + 2)] = coords.z;
// vertices[int(index + 3)] = verts[int(index + 3)];
// vertices[int(index + 4)] = verts[int(index + 4)];
// }
// vertexBuffer.upload(vertices, 0, numVerts);
// }
public function addJoint(joint:Joint):Joint {
return rootJoint.addJoint(joint);
}
public function removeJoint(joint:Joint):Joint {
return rootJoint.removeJoint(joint);
}
public function get numJoints():int {
return rootJoint.numJoints;
}
public function getJointAt(index:int):Joint {
return rootJoint.getJointAt(index);
}
/**
* @private
*/
override alternativa3d function updateBounds(bounds:Object3D, matrix:Matrix3D = null):void {
if (geometry != null) {
rootJoint.composeMatrix();
if (matrix != null) {
rootJoint.cameraMatrix.append(matrix);
}
rootJoint.calculateMatrix();
var i:int;
var coordsIn:Vector.<Number> = new Vector.<Number>(3);
var coordsOut:Vector.<Number> = new Vector.<Number>(3);
for each (var vertex:Vertex in geometry._vertices) {
var x:Number = 0;
var y:Number = 0;
var z:Number = 0;
coordsIn[0] = vertex.x;
coordsIn[1] = vertex.y;
coordsIn[2] = vertex.z;
var num:int = vertex._jointsIndices.length;
var sumWeights:Number = 0;
for (i = 0; i < num; i++) {
sumWeights += vertex._jointsWeights[i];
}
for (i = 0; i < num; i++) {
var index:int = vertex._jointsIndices[i];
var joint:Joint = renderedJoints[index];
joint.cameraMatrix.transformVectors(coordsIn, coordsOut);
var weight:Number = vertex._jointsWeights[i]/sumWeights;
x += coordsOut[0]*weight;
y += coordsOut[1]*weight;
z += coordsOut[2]*weight;
}
if (x < bounds.boundMinX) bounds.boundMinX = x;
if (x > bounds.boundMaxX) bounds.boundMaxX = x;
if (y < bounds.boundMinY) bounds.boundMinY = y;
if (y > bounds.boundMaxY) bounds.boundMaxY = y;
if (z < bounds.boundMinZ) bounds.boundMinZ = z;
if (z > bounds.boundMaxZ) bounds.boundMaxZ = z;
}
}
}
/**
* @inheritDoc
*/
override public function intersectRay(origin:Vector3D, direction:Vector3D, exludedObjects:Dictionary = null, camera:Camera3D = null):RayIntersectionData {
if (exludedObjects != null && exludedObjects[this]) return null;
rootJoint.composeMatrix();
rootJoint.calculateMatrix();
var i:int;
var vertex:Vertex;
var map:Dictionary = new Dictionary();
var coordsIn:Vector.<Number> = new Vector.<Number>(3);
var coordsOut:Vector.<Number> = new Vector.<Number>(3);
for each (vertex in geometry._vertices) {
var value:Vector3D = new Vector3D();
map[vertex] = value;
coordsIn[0] = vertex.x;
coordsIn[1] = vertex.y;
coordsIn[2] = vertex.z;
var num:int = vertex._jointsIndices.length;
var sumWeights:Number = 0;
for (i = 0; i < num; i++) {
sumWeights += vertex._jointsWeights[i];
}
for (i = 0; i < num; i++) {
var index:int = vertex._jointsIndices[i];
var joint:Joint = renderedJoints[index];
joint.cameraMatrix.transformVectors(coordsIn, coordsOut);
var weight:Number = vertex._jointsWeights[i]/sumWeights;
value.x += coordsOut[0]*weight;
value.y += coordsOut[1]*weight;
value.z += coordsOut[2]*weight;
}
}
var ox:Number = origin.x;
var oy:Number = origin.y;
var oz:Number = origin.z;
var dx:Number = direction.x;
var dy:Number = direction.y;
var dz:Number = direction.z;
var point:Vector3D;
var face:Face;
var minTime:Number = 1e+22;
for each (var f:Face in geometry._faces) {
var w:Wrapper = f.wrapper;
var a:Vector3D = map[w.vertex]; w = w.next;
var b:Vector3D = map[w.vertex]; w = w.next;
var c:Vector3D = map[w.vertex];
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 normalX:Number = acz*aby - acy*abz;
var normalY:Number = acx*abz - acz*abx;
var normalZ:Number = acy*abx - acx*aby;
var len:Number = normalX*normalX + normalY*normalY + normalZ*normalZ;
if (len > 0.001) {
len = 1/Math.sqrt(len);
normalX *= len;
normalY *= len;
normalZ *= len;
}
var dot:Number = dx*normalX + dy*normalY + dz*normalZ;
if (dot < 0) {
var offset:Number = ox*normalX + oy*normalY + oz*normalZ - (a.x*normalX + a.y*normalY + a.z*normalZ);
if (offset > 0) {
var time:Number = -offset/dot;
if (point == null || time < minTime) {
var cx:Number = ox + dx*time;
var cy:Number = oy + dy*time;
var cz:Number = oz + dz*time;
var wrapper:Wrapper;
for (wrapper = f.wrapper; wrapper != null; wrapper = wrapper.next) {
a = map[wrapper.vertex];
b = (wrapper.next != null) ? map[wrapper.next.vertex] : map[f.wrapper.vertex];
abx = b.x - a.x;
aby = b.y - a.y;
abz = b.z - a.z;
acx = cx - a.x;
acy = cy - a.y;
acz = cz - a.z;
if ((acz*aby - acy*abz)*normalX + (acx*abz - acz*abx)*normalY + (acy*abx - acx*aby)*normalZ < 0) break;
}
if (wrapper == null) {
if (time < minTime) {
minTime = time;
if (point == null) point = new Vector3D();
point.x = cx;
point.y = cy;
point.z = cz;
face = f;
}
}
}
}
}
}
if (point != null) {
var res:RayIntersectionData = new RayIntersectionData();
res.object = this;
res.face = face;
res.point = point;
res.time = minTime;
return res;
} else {
return null;
}
}
override public function clone():Object3D {
var res:Skin = new Skin();
res.cloneBaseProperties(this);
return res;
}
override protected function cloneBaseProperties(source:Object3D):void {
super.cloneBaseProperties(source);
var sourceSkin:Skin = Skin(source);
rootJoint = Joint(sourceSkin.rootJoint.clone());
if (sourceSkin.renderedJoints != null) {
renderedJoints = new Vector.<Joint>().concat(sourceSkin.renderedJoints);
remapRenderedJoints(rootJoint, sourceSkin.rootJoint);
}
}
private function remapRenderedJoints(joint:Joint, sourceJoint:Joint):void {
for (var i:int = 0, count:int = joint.numJoints; i < count; i++) {
var child:Joint = joint.getJointAt(i);
var sourceChild:Joint = sourceJoint.getJointAt(i);
var index:int = renderedJoints.indexOf(sourceChild);
if (index >= 0) {
renderedJoints[index] = child;
}
remapRenderedJoints(child, sourceChild);
}
}
}
}

View File

@@ -0,0 +1,232 @@
package alternativa.engine3d.objects {
import __AS3__.vec.Vector;
import alternativa.engine3d.core.Geometry;
import alternativa.engine3d.core.Vertex;
import flash.display.BitmapData;
import flash.geom.ColorTransform;
import flash.geom.Vector3D;
import flash.utils.getTimer;
public class Terrain extends Mesh {
// шаг сетки
public const step:uint = 10;
private var map:Vector.<Vector.<Vertex>>;
private var heightMap:HeightMap;
private var width:uint;
private var height:uint;
public function Terrain() {
}
public function build(heightMapData:BitmapData):void {
if (heightMapData == null) return;
this.heightMap = new HeightMap(heightMapData);
geometry = new Geometry();
width = heightMapData.width;
height = heightMapData.height;
map = new Vector.<Vector.<Vertex>>(width);
var halfW:Number = width >> 1;
var halfH:Number = height >> 1;
var a:Number = halfW*halfW;
var b:Number = halfH*halfH;
var i:int;
var j:int;
for (i = 0; i < width; i++) {
map[i] = new Vector.<Vertex>(height);
for (j = 0; j < height; j++) {
var h:uint = heightMap.getHeight(i, j);
// if (((i - halfW)*(i - halfW)/a + (j - halfH)*(j - halfH)/b) <= 1) {
map[i][j] = geometry.addVertex(i*step, j*step, h , i, j);
// } else {
// var y:Number = Math.sqrt(b*(a - (i - halfW)*(i - halfW))/a);
// if (y != 0) {
// if (j > halfH) {
// y += halfH;
// } else {
// y = -y + halfH;
// }
// if (Math.abs(j - y) < step) {
// map[i][j] = geometry.addVertex(i*step, y*step, h, i, j);
// }
// }
// }
}
}
var time:Number = getTimer();
for (i = 0; i < width - 1; i++) {
for (j = 0; j < height - 1; j++) {
buildFaces(i, j);
}
}
trace("build", getTimer() - time);
map = null;
}
private function buildFaces(i:uint, j:uint):void {
var vert:Vertex = map[i][j];
var rightVert:Vertex = map[i][j+1];
var bottomVert:Vertex = map[i+1][j];
var bottomRightVert:Vertex = map[i+1][j+1];
if (vert != null) {
if ((rightVert == null && bottomVert == null) || (rightVert == null && bottomRightVert == null) || (bottomVert == null && bottomRightVert == null)) {
return;
}
if (bottomRightVert == null) {
geometry.addTriFace(vert, bottomVert, rightVert);
return;
}
if (bottomVert == null) {
geometry.addTriFace(vert, bottomRightVert, rightVert);
return;
}
if (rightVert == null) {
geometry.addTriFace(vert, bottomVert, bottomRightVert);
return;
}
//var highestVert:Vertex = getHighestVertex(Vector.<Vertex>([vert, rightVert, bottomVert, bottomRightVert]));
//if (highestVert == vert || highestVert == bottomRightVert) {
geometry.addTriFace(vert, bottomVert, rightVert);
geometry.addTriFace(bottomVert, bottomRightVert, rightVert);
//} else {
// geometry.addTriFace(vert, bottomRightVert, rightVert);
// geometry.addTriFace(bottomVert, bottomRightVert, vert);
//}
} else {
if (rightVert != null && bottomVert != null && bottomRightVert != null) {
geometry.addTriFace(bottomVert, bottomRightVert, rightVert);
}
}
}
private function getHighestVertex(vertices:Vector.<Vertex>):Vertex {
if (vertices == null || vertices.length == 0) return null;
var maxVertex:Vertex = vertices[0];
for (var i:int = 1; i < vertices.length; i++) {
if (vertices[i].z > maxVertex.z) {
maxVertex = vertices[i];
}
}
return maxVertex;
}
public function generate(width:uint, height:uint, maxHeight:Number):void {
this.width = width;
this.height = height;
init(maxHeight);
fracture(0, 0, width - 1, height - 1, 0, height - 1, maxHeight);
fracture(0, 0, width - 1, 0, width - 1, height - 1, maxHeight);
smooth();
smooth();
smooth();
smooth();
for (var i:int = 0; i < width - 1; i++) {
for (var j:int = 0; j < height - 1; j++) {
buildFaces(i, j);
}
}
map = null;
}
private function fracture(aX:uint, aY:uint, bX:uint, bY:uint, cX:uint, cY:uint, maxHeight:Number):void {
if ((Math.abs(aX - bX) <= 1) && (Math.abs(aY - bY) <= 1)) return;
var x1:uint = (aX - bX)/2 + bX;
var y1:uint = (aY - bY)/2 + bY;
var x2:uint = (aX - cX)/2 + cX;
var y2:uint = (aY - cY)/2 + cY;
var x3:uint = (bX - cX)/2 + cX;
var y3:uint = (bY - cY)/2 + cY;
(map[x1][y1] as Vertex).z += Math.random()*maxHeight;
(map[x2][y2] as Vertex).z += Math.random()*maxHeight;
(map[x3][y3] as Vertex).z += Math.random()*maxHeight;
maxHeight *= 0.9;
fracture(aX, aY, x2, y2, x1, y1, maxHeight);
fracture(cX, cY, x2, y2, x3, y3, maxHeight);
fracture(bX, bY, x3, y3, x1, y1, maxHeight);
fracture(x1, y1, x2, y2, x3, y3, maxHeight);
}
private function init(maxHeight:Number):void {
geometry = new Geometry();
map = new Vector.<Vector.<Vertex>>(width);
var h:Number = maxHeight << 1;
for (var i:int = 0; i < width; i++) {
map[i] = new Vector.<Vertex>(height);
for (var j:int = 0; j < width; j++) {
map[i][j] = geometry.addVertex(i*step, j*step, 0, i, j);
}
}
}
private function smooth():void {
for (var i:int = 0; i < width ; i++) {
for (var j:int = 0; j < height; j++) {
(map[i][j] as Vertex).z = getAverageZ(i, j);
}
}
}
private function getAverageZ(i:uint, j:uint):Number {
return (getVertexZ(i - 1, j - 1) +
getVertexZ(i - 1, j) +
getVertexZ(i - 1, j + 1) +
getVertexZ(i, j - 1) +
getVertexZ(i, j) +
getVertexZ(i, j + 1) +
getVertexZ(i + 1, j - 1) +
getVertexZ(i + 1, j) +
getVertexZ(i + 1, j + 1))/9;
}
private function getVertexZ(i:uint, j:uint):Number {
if (i < 0 || j < 0 || i > width - 1 || j > height - 1) {
return 0;
}
return map[i][j].z;
}
public function getLightMap(lightVector:Vector3D):BitmapData {
var ct:ColorTransform = new ColorTransform();
ct.redOffset = 128;//248;
ct.greenOffset = 128;//229;
ct.blueOffset = 128;//195;
var gen:LightMapGenerator = new LightMapGenerator(heightMap, step, ct.color);
return gen.generateLightMap(lightVector);
}
public function getNormalMap():BitmapData {
return heightMap.getNormalMap();
}
}
}

View File

@@ -0,0 +1,233 @@
package alternativa.engine3d.primitives {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.core.Wrapper;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.core.Geometry;
use namespace alternativa3d;
/**
* Прямоугольный параллелепипед.
*/
public class Box extends Mesh {
/**
* Создание нового параллелепипеда.
* @param width Ширина. Размерность по оси X. Не может быть меньше нуля.
* @param length Длина. Размерность по оси Y. Не может быть меньше нуля.
* @param height Высота. Размерность по оси Z. Не может быть меньше нуля.
* @param widthSegments Количество сегментов по ширине.
* @param lengthSegments Количество сегментов по длине.
* @param heightSegments Количество сегментов по по высоте.
* @param reverse Флаг инвертирования нормалей. Если указано значение <code>true</code>, то нормали будут направлены внутрь фигуры.
* @param triangulate Флаг триангуляции. Если указано значение <code>true</code>, четырехугольники в параллелепипеде будут триангулированы.
* @param left Материал для левой стороны.
* @param right Материал для правой стороны.
* @param back Материал для задней стороны.
* @param front Материал для передней стороны.
* @param bottom Материал для нижней стороны.
* @param top Материал для верхней стороны.
*/
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, material:Material = null) {
if (widthSegments <= 0 || lengthSegments <= 0 || heightSegments <= 0) return;
this.material = material;
geometry = new Geometry();
var x:int;
var y:int;
var z:int;
var wp:int = widthSegments + 1;
var lp:int = lengthSegments + 1;
var hp:int = heightSegments + 1;
var wh:Number = width*0.5;
var lh:Number = length*0.5;
var hh:Number = height*0.5;
var wd:Number = 1/widthSegments;
var ld:Number = 1/lengthSegments;
var hd:Number = 1/heightSegments;
var ws:Number = width/widthSegments;
var ls:Number = length/lengthSegments;
var hs:Number = height/heightSegments;
var vertices:Vector.<Vertex> = new Vector.<Vertex>();
var verticesLength:int = 0;
// Нижняя грань
for (x = 0; x < wp; x++) {
for (y = 0; y < lp; y++) {
vertices[verticesLength++] = createVertex(x*ws - wh, y*ls - lh, -hh, (widthSegments - x)*wd, (lengthSegments - y)*ld);
}
}
for (x = 0; x < wp; x++) {
for (y = 0; y < lp; y++) {
if (x < widthSegments && y < lengthSegments) {
createFace(vertices[(x + 1)*lp + y + 1], vertices[(x + 1)*lp + y], vertices[x*lp + y], vertices[x*lp + y + 1], 0, 0, -1, hh, reverse, triangulate);
}
}
}
var o:uint = wp*lp;
// Верхняя грань
for (x = 0; x < wp; x++) {
for (y = 0; y < lp; y++) {
vertices[verticesLength++] = createVertex(x*ws - wh, y*ls - lh, hh, x*wd, (lengthSegments - y)*ld);
}
}
for (x = 0; x < wp; x++) {
for (y = 0; y < lp; y++) {
if (x < widthSegments && y < lengthSegments) {
createFace(vertices[o + x*lp + y], vertices[o + (x + 1)*lp + y], vertices[o + (x + 1)*lp + y + 1], vertices[o + x*lp + y + 1], 0, 0, 1, hh, reverse, triangulate);
}
}
}
o += wp*lp;
// Задняя грань
for (x = 0; x < wp; x++) {
for (z = 0; z < hp; z++) {
vertices[verticesLength++] = createVertex(x*ws - wh, -lh, z*hs - hh, x*wd, (heightSegments - z)*hd);
}
}
for (x = 0; x < wp; x++) {
for (z = 0; z < hp; z++) {
if (x < widthSegments && z < heightSegments) {
createFace(vertices[o + x*hp + z], vertices[o + (x + 1)*hp + z], vertices[o + (x + 1)*hp + z + 1], vertices[o + x*hp + z + 1], 0, -1, 0, lh, reverse, triangulate);
}
}
}
o += wp*hp;
// Передняя грань
for (x = 0; x < wp; x++) {
for (z = 0; z < hp; z++) {
vertices[verticesLength++] = createVertex(x*ws - wh, lh, z*hs - hh, (widthSegments - x)*wd, (heightSegments - z)*hd);
}
}
for (x = 0; x < wp; x++) {
for (z = 0; z < hp; z++) {
if (x < widthSegments && z < heightSegments) {
createFace(vertices[o + x*hp + z], vertices[o + x*hp + z + 1], vertices[o + (x + 1)*hp + z + 1], vertices[o + (x + 1)*hp + z], 0, 1, 0, lh, reverse, triangulate);
}
}
}
o += wp*hp;
// Левая грань
for (y = 0; y < lp; y++) {
for (z = 0; z < hp; z++) {
vertices[verticesLength++] = createVertex(-wh, y*ls - lh, z*hs - hh, (lengthSegments - y)*ld, (heightSegments - z)*hd);
}
}
for (y = 0; y < lp; y++) {
for (z = 0; z < hp; z++) {
if (y < lengthSegments && z < heightSegments) {
createFace(vertices[o + y*hp + z], vertices[o + y*hp + z + 1], vertices[o + (y + 1)*hp + z + 1], vertices[o + (y + 1)*hp + z], -1, 0, 0, wh, reverse, triangulate);
}
}
}
o += lp*hp;
// Правая грань
for (y = 0; y < lp; y++) {
for (z = 0; z < hp; z++) {
vertices[verticesLength++] = createVertex(wh, y*ls - lh, z*hs - hh, y*ld, (heightSegments - z)*hd);
}
}
for (y = 0; y < lp; y++) {
for (z = 0; z < hp; z++) {
if (y < lengthSegments && z < heightSegments) {
createFace(vertices[o + y*hp + z], vertices[o + (y + 1)*hp + z], vertices[o + (y + 1)*hp + z + 1], vertices[o + y*hp + z + 1], 1, 0, 0, wh, reverse, triangulate);
}
}
}
// Установка границ
boundMinX = -wh;
boundMinY = -lh;
boundMinZ = -hh;
boundMaxX = wh;
boundMaxY = lh;
boundMaxZ = hh;
}
private function createVertex(x:Number, y:Number, z:Number, u:Number, v:Number):Vertex {
var vertex:Vertex = new Vertex();
vertex._x = x;
vertex._y = y;
vertex._z = z;
vertex._u = u;
vertex._v = v;
vertex.geometry = geometry;
geometry._vertices[geometry.vertexIdCounter++] = vertex;
return vertex;
}
private function createFace(a:Vertex, b:Vertex, c:Vertex, d:Vertex, nx:Number, ny:Number, nz:Number, no:Number, reverse:Boolean, triangulate:Boolean):void {
var v:Vertex;
var face:Face;
if (reverse) {
nx = -nx;
ny = -ny;
nz = -nz;
no = -no;
v = a;
a = d;
d = v;
v = b;
b = c;
c = v;
}
if (triangulate) {
face = new Face();
face.geometry = geometry;
face.wrapper = new Wrapper();
face.wrapper.vertex = a;
face.wrapper.next = new Wrapper();
face.wrapper.next.vertex = b;
face.wrapper.next.next = new Wrapper();
face.wrapper.next.next.vertex = c;
face.normalX = nx;
face.normalY = ny;
face.normalZ = nz;
face.offset = no;
geometry._faces[geometry.faceIdCounter++] = face;
face = new Face();
face.geometry = geometry;
face.wrapper = new Wrapper();
face.wrapper.vertex = a;
face.wrapper.next = new Wrapper();
face.wrapper.next.vertex = c;
face.wrapper.next.next = new Wrapper();
face.wrapper.next.next.vertex = d;
face.normalX = nx;
face.normalY = ny;
face.normalZ = nz;
face.offset = no;
geometry._faces[geometry.faceIdCounter++] = face;
} else {
face = new Face();
face.geometry = geometry;
face.wrapper = new Wrapper();
face.wrapper.vertex = a;
face.wrapper.next = new Wrapper();
face.wrapper.next.vertex = b;
face.wrapper.next.next = new Wrapper();
face.wrapper.next.next.vertex = c;
face.wrapper.next.next.next = new Wrapper();
face.wrapper.next.next.next.vertex = d;
face.normalX = nx;
face.normalY = ny;
face.normalZ = nz;
face.offset = no;
geometry._faces[geometry.faceIdCounter++] = face;
}
}
/**
* @inheritDoc
*/
override public function clone():Object3D {
var box:Box = new Box();
box.cloneBaseProperties(this);
box.material = material;
box.geometry = geometry;
return box;
}
}
}

View File

@@ -0,0 +1,362 @@
package alternativa.engine3d.primitives {
import __AS3__.vec.Vector;
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.core.Wrapper;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.core.Geometry;
use namespace alternativa3d;
/**
* Геосфера.
*/
public class GeoSphere extends Mesh {
/**
* Создание новой геосферы.
* @param radius Радиус геосферы. Не может быть меньше нуля.
* @param segments Количество сегментов геосферы.
* @param reverse Флаг инверирования нормалей. При значении <code>true</code> нормали направлены внуть геосферы.
* @param material Материал. При использовании <code>TextureMaterial</code> нужно установить его свойство <code>repeat</code> в <code>true</code>.
*/
public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false, material:Material = null) {
if (segments == 0) return;
geometry = new Geometry();
this.material = material;
radius = (radius < 0) ? 0 : radius;
var sections:uint = 20;
var deg180:Number = Math.PI;
var deg360:Number = Math.PI*2;
var vertices:Vector.<Vertex> = new Vector.<Vertex>();
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;
vertices.push(createVertex(0, 0, radius));
// Создание вершин верхней крышки
for (i = 0; i < 5; i++) {
theta = deg360*i/5;
sin = Math.sin(theta);
cos = Math.cos(theta);
vertices.push(createVertex(subrad*cos, subrad*sin, subz));
}
// Создание вершин нижней крышки
for (i = 0; i < 5; i++) {
theta = deg180*((i << 1) + 1)/5;
sin = Math.sin(theta);
cos = Math.cos(theta);
vertices.push(createVertex(subrad*cos, subrad*sin, -subz));
}
vertices.push(createVertex(0, 0, -radius));
for (i = 1; i < 6; i++) {
interpolate(0, i, segments, vertices);
}
for (i = 1; i < 6; i++) {
interpolate(i, i % 5 + 1, segments, vertices);
}
for (i = 1; i < 6; i++) {
interpolate(i, i + 5, segments, vertices);
}
for (i = 1; i < 6; i++) {
interpolate(i, (i + 3) % 5 + 6, segments, vertices);
}
for (i = 1; i < 6; i++) {
interpolate(i + 5, i % 5 + 6, segments, vertices);
}
for (i = 6; i < 11; i++) {
interpolate(11, i, segments, vertices);
}
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, vertices);
}
}
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, vertices);
}
}
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, vertices);
}
}
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, vertices);
}
}
// Создание граней
for (f = 0; f < sections; f++) {
for (var row:uint = 0; row < segments; row++) {
for (var column:uint = 0; column <= row; column++) {
var aIndex:uint = findVertices(segments, f, row, column);
var bIndex:uint = findVertices(segments, f, row + 1, column);
var cIndex:uint = findVertices(segments, f, row + 1, column + 1);
var a:Vertex = vertices[aIndex];
var b:Vertex = vertices[bIndex];
var c:Vertex = vertices[cIndex];
var au:Number;
var av:Number;
var bu:Number;
var bv:Number;
var cu:Number;
var cv:Number;
if (a.y >= 0 && (a.x < 0) && (b.y < 0 || c.y < 0)) {
au = Math.atan2(a.y, a.x)/deg360 - 0.5;
} else {
au = Math.atan2(a.y, a.x)/deg360 + 0.5;
}
av = -Math.asin(a.z/radius)/deg180 + 0.5;
if (b.y >= 0 && (b.x < 0) && (a.y < 0 || c.y < 0)) {
bu = Math.atan2(b.y, b.x)/deg360 - 0.5;
} else {
bu = Math.atan2(b.y, b.x)/deg360 + 0.5;
}
bv = -Math.asin(b.z/radius)/deg180 + 0.5;
if (c.y >= 0 && (c.x < 0) && (a.y < 0 || b.y < 0)) {
cu = Math.atan2(c.y, c.x)/deg360 - 0.5;
} else {
cu = Math.atan2(c.y, c.x)/deg360 + 0.5;
}
cv = -Math.asin(c.z/radius)/deg180 + 0.5;
// полюс
if (aIndex == 0 || aIndex == 11) {
au = bu + (cu - bu)*0.5;
}
if (bIndex == 0 || bIndex == 11) {
bu = au + (cu - au)*0.5;
}
if (cIndex == 0 || cIndex == 11) {
cu = au + (bu - au)*0.5;
}
// Дублирование
if (a.offset > 0 && a.u != au) {
a = createVertex(a.x, a.y, a.z);
}
a._u = au;
a._v = av;
a.offset = 1;
if (b.offset > 0 && b.u != bu) {
b = createVertex(b.x, b.y, b.z);
}
b._u = bu;
b._v = bv;
b.offset = 1;
if (c.offset > 0 && c.u != cu) {
c = createVertex(c.x, c.y, c.z);
}
c._u = cu;
c._v = cv;
c.offset = 1;
if (reverse) {
createFace(a, c, b);
} else {
createFace(a, b, c);
}
if (column < row) {
bIndex = findVertices(segments, f, row, column + 1);
b = vertices[bIndex];
if (a.y >= 0 && (a.x < 0) && (b.y < 0 || c.y < 0)) {
au = Math.atan2(a.y, a.x)/deg360 - 0.5;
} else {
au = Math.atan2(a.y, a.x)/deg360 + 0.5;
}
av = -Math.asin(a.z/radius)/deg180 + 0.5;
if (b.y >= 0 && (b.x < 0) && (a.y < 0 || c.y < 0)) {
bu = Math.atan2(b.y, b.x)/deg360 - 0.5;
} else {
bu = Math.atan2(b.y, b.x)/deg360 + 0.5;
}
bv = -Math.asin(b.z/radius)/deg180 + 0.5;
if (c.y >= 0 && (c.x < 0) && (a.y < 0 || b.y < 0)) {
cu = Math.atan2(c.y, c.x)/deg360 - 0.5;
} else {
cu = Math.atan2(c.y, c.x)/deg360 + 0.5;
}
cv = -Math.asin(c.z/radius)/deg180 + 0.5;
if (aIndex == 0 || aIndex == 11) {
au = bu + (cu - bu)*0.5;
}
if (bIndex == 0 || bIndex == 11) {
bu = au + (cu - au)*0.5;
}
if (cIndex == 0 || cIndex == 11) {
cu = au + (bu - au)*0.5;
}
// Дублирование
if (a.offset > 0 && a.u != au) {
a = createVertex(a.x, a.y, a.z);
}
a._u = au;
a._v = av;
a.offset = 1;
if (b.offset > 0 && b.u != bu) {
b = createVertex(b.x, b.y, b.z);
}
b._u = bu;
b._v = bv;
b.offset = 1;
if (c.offset > 0 && c.u != cu) {
c = createVertex(c.x, c.y, c.z);
}
c._u = cu;
c._v = cv;
c.offset = 1;
if (reverse) {
createFace(a, b, c);
} else {
createFace(a, c, b);
}
}
}
}
}
// Установка границ
boundMinX = -radius;
boundMinY = -radius;
boundMinZ = -radius;
boundMaxX = radius;
boundMaxY = radius;
boundMaxZ = radius;
}
private function createVertex(x:Number, y:Number, z:Number):Vertex {
var vertex:Vertex = new Vertex();
vertex._x = x;
vertex._y = y;
vertex._z = z;
vertex.offset = -1;
vertex.geometry = geometry;
geometry._vertices[geometry.vertexIdCounter++] = vertex;
return vertex;
}
private function createFace(a:Vertex, b:Vertex, c:Vertex):void {
var face:Face = new Face();
face.geometry = geometry;
face.wrapper = new Wrapper();
face.wrapper.vertex = a;
face.wrapper.next = new Wrapper();
face.wrapper.next.vertex = b;
face.wrapper.next.next = new Wrapper();
face.wrapper.next.next.vertex = c;
geometry._faces[geometry.faceIdCounter++] = face;
}
private function interpolate(v1:uint, v2:uint, num:uint, vertices:Vector.<Vertex>):void {
if (num < 2) {
return;
}
var a:Vertex = Vertex(vertices[v1]);
var b:Vertex = Vertex(vertices[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);
vertices.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
*/
override public function clone():Object3D {
var geoSphere:GeoSphere = new GeoSphere();
geoSphere.cloneBaseProperties(this);
geoSphere.geometry = geometry;
geoSphere.material = material;
return geoSphere;
}
}
}