mirror of
https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
synced 2025-10-27 18:29:07 -07:00
Add A3D8
This commit is contained in:
14
Alternativa3D8/8.0/alternativa/Alternativa3D.as
Normal file
14
Alternativa3D8/8.0/alternativa/Alternativa3D.as
Normal file
@@ -0,0 +1,14 @@
|
||||
package alternativa {
|
||||
|
||||
/**
|
||||
* Класс содержит информацию о версии библиотеки.
|
||||
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
|
||||
*/
|
||||
public class Alternativa3D {
|
||||
|
||||
/**
|
||||
* Версия библиотеки в формате: поколение.feature-версия.fix-версия
|
||||
*/
|
||||
public static const version:String = "8.0.0";
|
||||
}
|
||||
}
|
||||
3
Alternativa3D8/8.0/alternativa/engine3d/alternativa3d.as
Normal file
3
Alternativa3D8/8.0/alternativa/engine3d/alternativa3d.as
Normal file
@@ -0,0 +1,3 @@
|
||||
package alternativa.engine3d {
|
||||
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
|
||||
}
|
||||
180
Alternativa3D8/8.0/alternativa/engine3d/animation/Animation.as
Normal file
180
Alternativa3D8/8.0/alternativa/engine3d/animation/Animation.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
176
Alternativa3D8/8.0/alternativa/engine3d/animation/Track.as
Normal file
176
Alternativa3D8/8.0/alternativa/engine3d/animation/Track.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 + ":" + + "]";
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 + "]]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
788
Alternativa3D8/8.0/alternativa/engine3d/core/Camera3D.as
Normal file
788
Alternativa3D8/8.0/alternativa/engine3d/core/Camera3D.as
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
261
Alternativa3D8/8.0/alternativa/engine3d/core/Face.as
Normal file
261
Alternativa3D8/8.0/alternativa/engine3d/core/Face.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
1659
Alternativa3D8/8.0/alternativa/engine3d/core/Geometry.as
Normal file
1659
Alternativa3D8/8.0/alternativa/engine3d/core/Geometry.as
Normal file
File diff suppressed because it is too large
Load Diff
720
Alternativa3D8/8.0/alternativa/engine3d/core/Object3D.as
Normal file
720
Alternativa3D8/8.0/alternativa/engine3d/core/Object3D.as
Normal 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;
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
267
Alternativa3D8/8.0/alternativa/engine3d/core/Utils.as
Normal file
267
Alternativa3D8/8.0/alternativa/engine3d/core/Utils.as
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
229
Alternativa3D8/8.0/alternativa/engine3d/core/Vertex.as
Normal file
229
Alternativa3D8/8.0/alternativa/engine3d/core/Vertex.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
94
Alternativa3D8/8.0/alternativa/engine3d/core/View.as
Normal file
94
Alternativa3D8/8.0/alternativa/engine3d/core/View.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
43
Alternativa3D8/8.0/alternativa/engine3d/core/Wrapper.as
Normal file
43
Alternativa3D8/8.0/alternativa/engine3d/core/Wrapper.as
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
26
Alternativa3D8/8.0/alternativa/engine3d/lights/OmniLight.as
Normal file
26
Alternativa3D8/8.0/alternativa/engine3d/lights/OmniLight.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
529
Alternativa3D8/8.0/alternativa/engine3d/loaders/Parser3DS.as
Normal file
529
Alternativa3D8/8.0/alternativa/engine3d/loaders/Parser3DS.as
Normal 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;
|
||||
}
|
||||
466
Alternativa3D8/8.0/alternativa/engine3d/loaders/ParserCollada.as
Normal file
466
Alternativa3D8/8.0/alternativa/engine3d/loaders/ParserCollada.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package alternativa.engine3d.loaders.collada {
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public namespace collada = "http://www.collada.org/2005/11/COLLADASchema";
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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+"]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
149
Alternativa3D8/8.0/alternativa/engine3d/materials/Material.as
Normal file
149
Alternativa3D8/8.0/alternativa/engine3d/materials/Material.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
86
Alternativa3D8/8.0/alternativa/engine3d/objects/HeightMap.as
Normal file
86
Alternativa3D8/8.0/alternativa/engine3d/objects/HeightMap.as
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
104
Alternativa3D8/8.0/alternativa/engine3d/objects/Joint.as
Normal file
104
Alternativa3D8/8.0/alternativa/engine3d/objects/Joint.as
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Alternativa3D8/8.0/alternativa/engine3d/objects/Mesh.as
Normal file
237
Alternativa3D8/8.0/alternativa/engine3d/objects/Mesh.as
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
553
Alternativa3D8/8.0/alternativa/engine3d/objects/Skin.as
Normal file
553
Alternativa3D8/8.0/alternativa/engine3d/objects/Skin.as
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
232
Alternativa3D8/8.0/alternativa/engine3d/objects/Terrain.as
Normal file
232
Alternativa3D8/8.0/alternativa/engine3d/objects/Terrain.as
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
233
Alternativa3D8/8.0/alternativa/engine3d/primitives/Box.as
Normal file
233
Alternativa3D8/8.0/alternativa/engine3d/primitives/Box.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
362
Alternativa3D8/8.0/alternativa/engine3d/primitives/GeoSphere.as
Normal file
362
Alternativa3D8/8.0/alternativa/engine3d/primitives/GeoSphere.as
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user