commit 93fff1889a95030f649e0f34577aa822df3b7ca7 Author: maltsev Date: Wed Mar 28 17:37:50 2012 +0600 Test commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9aa9d49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.idea/* +.idea/**/* +.settings/* +*.iml +*.svn* +*.DS_store* + +# Build and Release Folders +bin-debug/ +bin-release/ +target/ +out/ + +# Project property files +.actionScriptProperties +.flexProperties +.flexLibProperties +.settings/ +.project \ No newline at end of file diff --git a/libs/A3DModelsBase-2.5.1.swc b/libs/A3DModelsBase-2.5.1.swc new file mode 100644 index 0000000..b851e7a Binary files /dev/null and b/libs/A3DModelsBase-2.5.1.swc differ diff --git a/libs/A3DModelsBase-2.5.2.swc b/libs/A3DModelsBase-2.5.2.swc new file mode 100644 index 0000000..a57e645 Binary files /dev/null and b/libs/A3DModelsBase-2.5.2.swc differ diff --git a/libs/AlternativaProtocol-2.53.0.swc b/libs/AlternativaProtocol-2.53.0.swc new file mode 100644 index 0000000..d8c2c46 Binary files /dev/null and b/libs/AlternativaProtocol-2.53.0.swc differ diff --git a/libs/OSGIBase.swc b/libs/OSGIBase.swc new file mode 100644 index 0000000..797e09b Binary files /dev/null and b/libs/OSGIBase.swc differ diff --git a/libs/ProtocolTypes.swc b/libs/ProtocolTypes.swc new file mode 100644 index 0000000..d306cbd Binary files /dev/null and b/libs/ProtocolTypes.swc differ diff --git a/libs/apparat-ersatz-1.0-RC9.swc b/libs/apparat-ersatz-1.0-RC9.swc new file mode 100644 index 0000000..46bdb50 Binary files /dev/null and b/libs/apparat-ersatz-1.0-RC9.swc differ diff --git a/pom-standalone.xml b/pom-standalone.xml new file mode 100644 index 0000000..d16c79c --- /dev/null +++ b/pom-standalone.xml @@ -0,0 +1,56 @@ + + 4.0.0 + platform.clients.fp10.libraries + Alternativa3D + swc + 8.5.0.0-SNAPSHOT + + platform.clients.fp11.tools.maven + BasePom + 2.11.1.0 + + + + scm:svn:https://svndev.alternativaplatform.com/platform/clients/fp11/libraries/Alternativa3D/trunk/ + + + + + platform.client + A3DModelsBase + 0.0.1.0 + swc + merged + + + platform.clients.fp10 + OSGiBase + 2.0.2.0 + swc + merged + + + + + + + + + + platform.clients.fp10.libraries + AlternativaProtocol + 2.0.15.0 + swc + merged + + + platform.clients.fp10.libraries + ProtocolTypes + 1.0.1.0 + swc + merged + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f860c34 --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + platform.clients.fp11.libraries + Alternativa3D + swc + 8.28.0-SNAPSHOT + + platform.clients.fp11.tools.maven + BasePom + 2.58.0 + + + + scm:svn:https://svndev.alternativaplatform.com/platform/clients/fp11/libraries/Alternativa3D/trunk/ + + + + + + + platform.client.formats + A3DModelsBase + 2.5.2 + swc + external + + + platform.clients.fp10.libraries + AlternativaProtocol + 2.53.0 + swc + external + + + + + + + platform.client.formats + A3DModelsBase + swc + external + + + platform.clients.fp10 + OSGiBase + swc + external + + + platform.clients.fp10.libraries + AlternativaProtocol + swc + external + + + diff --git a/src/alternativa/Alternativa3D.as b/src/alternativa/Alternativa3D.as new file mode 100644 index 0000000..1a4e40e --- /dev/null +++ b/src/alternativa/Alternativa3D.as @@ -0,0 +1,24 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa { + + /** + * Class contains information about version of a library. + * Also used for integration of a library to development tool of Adobe Flash. + */ + public class Alternativa3D { + + /** + * Library version in the format: generation.feature-version.fix-version. + */ + public static const version:String = "8.27.0"; + } +} diff --git a/src/alternativa/engine3d/alternativa3d.as b/src/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..3daceb2 --- /dev/null +++ b/src/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,13 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/src/alternativa/engine3d/animation/AnimationClip.as b/src/alternativa/engine3d/animation/AnimationClip.as new file mode 100644 index 0000000..4cc5259 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationClip.as @@ -0,0 +1,495 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.keys.Track; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * + * Plays complex animation which consists of a set of animation tracks. + * Every animated property of every model's element presented by separated track. + * Track is somewhat similar to separate animated layer in Flash, but the layer stores animation of all properties for some element at once. + * In opposite, track stores animation for every property (for an example, separate track for a scale and separate track for a coordinates). + * Track also can contain keyframes in arbitrary positions. Frames, contained between keyframes, are linearly interpolated + * (i.e. behave themselves like a timeline frames in flash, for which motion twin was created ). + * Animation clip connects each track with a specific object. + * Animation clip stores information about animation for the whole model, i.e. for any element state at any time moment. + * Animation works handled by AnimationController. + * + * @see alternativa.engine3d.animation.keys.Track + * @see alternativa.engine3d.animation.keys.TransformTrack + * @see alternativa.engine3d.animation.keys.NumberTrack + * @see alternativa.engine3d.animation.AnimationController + */ + public class AnimationClip extends AnimationNode { + + /** + * @private + */ + alternativa3d var _objects:Array; + + /** + * Name of the animation clip. + */ + public var name:String; + + /** + * Defines if animation should be repeated. + */ + public var loop:Boolean = true; + + /** + * Length of animation in seconds. If length of any animation track is changed, updateLength() + * method should be called to recalculate the length of the clip. + * + * @see #updateLength() + */ + public var length:Number = 0; + /** + * Handles the active animation execution. Plays animation if value is true. + * + * @see AnimationNode#isActive + */ + public var animated:Boolean = true; + + /** + * @private + * Current value of time. + */ + private var _time:Number = 0; + + /** + * @private + */ + private var _numTracks:int = 0; + /** + * @private + */ + private var _tracks:Vector. = new Vector.(); + + /** + * @private + */ + private var _notifiersList:AnimationNotify; + + /** + * Creates a AnimationClip object. + * + * @param name name of the clip + */ + public function AnimationClip(name:String = null) { + this.name = name; + } + + /** + * The list of animated objects. Animation tracks are bound to the objects by object names. + * + * @see Track#object + */ + public function get objects():Array { + return (_objects == null) ? null : [].concat(_objects); + } + + /** + * @private + */ + public function set objects(value:Array):void { + updateObjects(_objects, controller, value, controller); + _objects = (value == null) ? null : [].concat(value); + } + + /** + * @private + */ + override alternativa3d function setController(value:AnimationController):void { + updateObjects(_objects, controller, _objects, value); + this.controller = value; + } + + /** + * @private + */ + private function addObject(object:Object):void { + if (_objects == null) { + _objects = [object]; + } else { + _objects.push(object); + } + if (controller != null) { + controller.addObject(object); + } + } + + /** + * @private + */ + private function updateObjects(oldObjects:Array, oldController:AnimationController, newObjects:Array, newController:AnimationController):void { + var i:int, count:int; + if (oldController != null && oldObjects != null) { + for (i = 0, count = _objects.length; i < count; i++) { + oldController.removeObject(oldObjects[i]); + } + } + if (newController != null && newObjects != null) { + for (i = 0, count = newObjects.length; i < count; i++) { + newController.addObject(newObjects[i]); + } + } + } + + /** + * Updates the length of the clip in order to match with length of longest track. + * Should be called after track was changed. + */ + public function updateLength():void { + for (var i:int = 0; i < _numTracks; i++) { + var track:Track = _tracks[i]; + var len:Number = track.length; + if (len > length) { + length = len; + } + } + } + + /** + * Adds a new track to the animation clip. + * The total length of the clip is recalculated automatically. + * + * @param track track which should be added. + * @return added track. + * + * @see #length + */ + public function addTrack(track:Track):Track { + if (track == null) { + throw new Error("Track can not be null"); + } + _tracks[_numTracks++] = track; + if (track.length > length) { + length = track.length; + } + return track; + } + + /** + * Removes the specified track from the clip. The clip length is automatically recalculated. + * + * @param track track which should be removed. + * @return removed track. + * + * @see #length + * @throw Error if the AnimationClip does not include the track. + */ + public function removeTrack(track:Track):Track { + var index:int = _tracks.indexOf(track); + if (index < 0) throw new ArgumentError("Track not found"); + _numTracks--; + var j:int = index + 1; + while (index < _numTracks) { + _tracks[index] = _tracks[j]; + index++; + j++; + } + _tracks.length = _numTracks; + length = 0; + for (var i:int = 0; i < _numTracks; i++) { + var t:Track = _tracks[i]; + if (t.length > length) { + length = t.length; + } + } + return track; + } + + /** + * Returns the track object instance that exists at the specified index. + * + * @param index index. + * @return the track object instance that exists at the specified index. + */ + public function getTrackAt(index:int):Track { + return _tracks[index]; + } + + /** + * Number of tracks in the AnimationClip. + */ + public function get numTracks():int { + return _numTracks; + } + + /** + * @private + */ + override alternativa3d function update(interval:Number, weight:Number):void { + var oldTime:Number = _time; + if (animated) { + _time += interval*speed; + if (loop) { + if (_time < 0) { + // _position = (length <= 0) ? 0 : _position % length; + _time = 0; + } else { + if (_time >= length) { + collectNotifiers(oldTime, length); + _time = (length <= 0) ? 0 : _time % length; + collectNotifiers(0, _time); + } else { + collectNotifiers(oldTime, _time); + } + } + } else { + if (_time < 0) { + _time = 0; + } else if (_time >= length) { + _time = length; + } + collectNotifiers(oldTime, _time); + } + } + if (weight > 0) { + for (var i:int = 0; i < _numTracks; i++) { + var track:Track = _tracks[i]; + if (track.object != null) { + var state:AnimationState = controller.getState(track.object); + if (state != null) { + track.blend(_time, weight, state); + } + } + } + } + } + + /** + * Current time of animation. + */ + public function get time():Number { + return _time; + } + + /** + * @private + */ + public function set time(value:Number):void { + _time = value; + } + + /** + * Current normalized time in the interval [0, 1]. + */ + public function get normalizedTime():Number { + return (length == 0) ? 0 : _time/length; + } + + /** + * @private + */ + public function set normalizedTime(value:Number):void { + _time = value*length; + } + + /** + * @private + */ + private function getNumChildren(object:Object):int { + if (object is Object3D) { + return Object3D(object).numChildren; + } + return 0; + } + + /** + * @private + */ + private function getChildAt(object:Object, index:int):Object { + if (object is Object3D) { + return Object3D(object).getChildAt(index); + } + return null; + } + + /** + * @private + */ + private function addChildren(object:Object):void { + for (var i:int = 0, numChildren:int = getNumChildren(object); i < numChildren; i++) { + var child:Object = getChildAt(object, i); + addObject(child); + addChildren(child); + } + } + + /** + * Binds tracks from the animation clip to given object. Only those tracks which have object property equal to the object's name are bound. + * + * @param object The object to which tracks are bound. + * @param includeDescendants If true, the whole tree of the object's children (if any) is processed. + * + * @see #objects + * @see alternativa.engine3d.animation.keys.Track#object + */ + public function attach(object:Object, includeDescendants:Boolean):void { + updateObjects(_objects, controller, null, controller); + _objects = null; + addObject(object); + if (includeDescendants) { + addChildren(object); + } + } + + /** + * @private + */ + alternativa3d function collectNotifiers(start:Number, end:Number):void { + var notify:AnimationNotify = _notifiersList; + while (notify != null) { + if (notify._time > start) { + if (notify._time > end) { + notify = notify.next; + continue; + } + notify.processNext = controller.nearestNotifyers; + controller.nearestNotifyers = notify; + } + notify = notify.next; + } + } + + /** + * Creates an AnimationNotify instance which is capable of firing notification events when playback reaches the specified time on the time line. + * + * @param time The time in seconds to which the notification trigger will be bound. + * @param name The name of AnimationNotify instance. + * + * @return A new instance of AnimationNotify class bound to specified time counting from start of the time line. + * + * @see AnimationNotify + */ + public function addNotify(time:Number, name:String = null):AnimationNotify { + time = (time <= 0) ? 0 : ((time >= length) ? length : time); + var notify:AnimationNotify = new AnimationNotify(name); + notify._time = time; + if (_notifiersList == null) { + _notifiersList = notify; + return notify; + } else { + if (_notifiersList._time > time) { + // Replaces the first key + notify.next = _notifiersList; + _notifiersList = notify; + return notify; + } else { + // Search for appropriate place + var n:AnimationNotify = _notifiersList; + while (n.next != null && n.next._time <= time) { + n = n.next; + } + if (n.next == null) { + // Places at the end + n.next = notify; + } else { + notify.next = n.next; + n.next = notify; + } + } + } + return notify; + } + + /** + * Creates an AnimationNotify instance which is capable of firing notification events when playback reaches + * the specified time on the time line. The time is specified as an offset from the end of time line towards its start. + * + * @param offsetFromEnd The offset in seconds from the end of the time line towards its start, where the event object will be set in. + * @param name The name of notification trigger. + * + * @return A new instance of AnimationNotify class bound to specified time. + * + * @see AnimationNotify + */ + // TODO: name of method (addNotifyAtEnd) is incomprehensible. Rename to addNotifyFromFinish (or something else). + public function addNotifyAtEnd(offsetFromEnd:Number = 0, name:String = null):AnimationNotify { + return addNotify(length - offsetFromEnd, name); + } + + /** + * Removes specified notification trigger. + * + * @param notify The notification trigger to remove. + * @return The removed notification trigger. + */ + public function removeNotify(notify:AnimationNotify):AnimationNotify { + if (_notifiersList != null) { + if (_notifiersList == notify) { + _notifiersList = _notifiersList.next; + return notify; + } + var n:AnimationNotify = _notifiersList; + while (n.next != null && n.next != notify) { + n = n.next; + } + if (n.next == notify) { + // removes + n.next = notify.next; + return notify; + } + } + throw new Error("Notify not found"); + } + + /** + * The list of notification triggers. + */ + public function get notifiers():Vector. { + var result:Vector. = new Vector.(); + var i:int = 0; + for (var notify:AnimationNotify = _notifiersList; notify != null; notify = notify.next) { + result[i] = notify; + i++; + } + return result; + } + + /** + * Returns a fragment of the clip between specified bounds. + * + * @param start The start time of a fragment in seconds. + * @param end The end time of a fragment in seconds. + * @return The clip fragment. + */ + public function slice(start:Number, end:Number = Number.MAX_VALUE):AnimationClip { + var sliced:AnimationClip = new AnimationClip(name); + sliced._objects = (_objects == null) ? null : [].concat(_objects); + for (var i:int = 0; i < _numTracks; i++) { + sliced.addTrack(_tracks[i].slice(start, end)); + } + return sliced; + } + + /** + * Clones the clip. Both the clone and the original reference the same tracks. + */ + public function clone():AnimationClip { + var cloned:AnimationClip = new AnimationClip(name); + cloned._objects = (_objects == null) ? null : [].concat(_objects); + for (var i:int = 0; i < _numTracks; i++) { + cloned.addTrack(_tracks[i]); + } + cloned.length = length; + return cloned; + } + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationController.as b/src/alternativa/engine3d/animation/AnimationController.as new file mode 100644 index 0000000..7642281 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationController.as @@ -0,0 +1,220 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.events.NotifyEvent; + import alternativa.engine3d.core.Object3D; + + import flash.utils.Dictionary; + import flash.utils.getTimer; + + use namespace alternativa3d; + /** + * Controls animation playback and blending. I.e. it animates model using information + * stored in AnimationClip-s and generated by AnimationSwitcher + * and AnimationCouple blenders. + * You have to call method update() each frame, + * which refreshes all child animation clips and blenders, which return + * list of properties and values to controller after that. You can use this list + * to set those properties. Controller sets those values and as a result + * the animation goes on. Animation control is carried out with the + * help of animated flag, and with AnimationSwitcher blender, + * which can transfer clip from active state to passive and vice versa. + * + * + * @see alternativa.engine3d.animation.AnimationClip + * @see alternativa.engine3d.animation.AnimationCouple + * @see alternativa.engine3d.animation.AnimationSwitcher + */ + public class AnimationController { + + /** + * @private + */ + private var _root:AnimationNode; + + /** + * @private + */ + private var _objects:Vector.; + /** + * @private + */ + private var _object3ds:Vector. = new Vector.(); + /** + * @private + */ + private var objectsUsedCount:Dictionary = new Dictionary(); + + /** + * @private + */ + private var states:Object = new Object(); +// private var datasList:BlendedData; + + /** + * @private + */ + private var lastTime:int = -1; + + /** + * @private + */ + alternativa3d var nearestNotifyers:AnimationNotify; + + /** + * Creates a AnimationController object. + */ + public function AnimationController() { + } + + /** + * Root of the animation tree. + */ + public function get root():AnimationNode { + return _root; + } + + /** + * @private + */ + public function set root(value:AnimationNode):void { + if (_root != value) { + if (_root != null) { + _root.setController(null); + _root._isActive = false; + } + if (value != null) { + value.setController(this); + value._isActive = true; + } + this._root = value; + } + } + + /** + * Plays animations on the time interval passed since the last update() call. + * If freeze() method was called after the last update(), + * animation will continue from that moment. + */ + public function update():void { + var interval:Number; + if (lastTime < 0) { + lastTime = getTimer(); + interval = 0; + } else { + var time:int = getTimer(); + interval = 0.001*(time - lastTime); + lastTime = time; + } + if (_root == null) { + return; + } + var data:AnimationState; + // Cleaning + for each (data in states) { + data.reset(); + } + _root.update(interval, 1); + // Apply the animation + for (var i:int = 0, count:int = _object3ds.length; i < count; i++) { + var object:Object3D = _object3ds[i]; + data = states[object.name]; + if (data != null) { + data.apply(object); + } + } + // Calls the notifications + for (var notify:AnimationNotify = nearestNotifyers; notify != null; notify = notify.processNext) { + if (notify.willTrigger(NotifyEvent.NOTIFY)) { + notify.dispatchEvent(new NotifyEvent(notify)); + } + } + nearestNotifyers = null; + } + + /** + * @private + */ + alternativa3d function addObject(object:Object):void { + if (object in objectsUsedCount) { + objectsUsedCount[object]++; + } else { + if (object is Object3D) { + _object3ds.push(object); + } else { + _objects.push(object); + } + objectsUsedCount[object] = 1; + } + } + + /** + * @private + */ + alternativa3d function removeObject(object:Object):void { + var used:int = objectsUsedCount[object]; + used--; + if (used <= 0) { + var index:int; + var j:int; + var count:int; + if (object is Object3D) { + index = _object3ds.indexOf(object); + count = _object3ds.length - 1; + j = index + 1; + while (index < count) { + _object3ds[index] = _object3ds[j]; + index++; + j++; + } + _object3ds.length = count; + } else { + index = _objects.indexOf(object); + count = _objects.length - 1; + j = index + 1; + while (index < count) { + _objects[index] = _objects[j]; + index++; + j++; + } + _objects.length = count; + } + delete objectsUsedCount[object]; + } else { + objectsUsedCount[object] = used; + } + } + + /** + * @private + */ + alternativa3d function getState(name:String):AnimationState { + var state:AnimationState = states[name]; + if (state == null) { + state = new AnimationState(); + states[name] = state; + } + return state; + } + + /** + * Freezes internal time counter till the next update() call. + * + * @see #update + */ + public function freeze():void { + lastTime = -1; + } + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationCouple.as b/src/alternativa/engine3d/animation/AnimationCouple.as new file mode 100644 index 0000000..5ab0232 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationCouple.as @@ -0,0 +1,138 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Blends two animations according to the balance value. + * Mixes two animations with the given percentage. Any of AnimationClip, + * AnimationSwitcher, AnimationCouple classes can be blended. + */ + public class AnimationCouple extends AnimationNode { + + /** + * @private + */ + private var _left:AnimationNode; + /** + * @private + */ + private var _right:AnimationNode; + + /** + * The balance is a value in [0, 1] interval which specifies weight coefficient for each animation. + * The first (left) animation gets weight of (1 - balance) and the second (right) one gets weigth of balance. + */ + public var balance:Number = 0.5; + + /** + * @private + */ + override alternativa3d function update(elapsed:Number, weight:Number):void { + var w:Number = (balance <= 0) ? 0 : ((balance >= 1) ? 1 : balance); + if (_left == null) { + _right.update(elapsed*speed, weight); + } else if (_right == null) { + _left.update(elapsed*speed, weight); + } else { + _left.update(elapsed*speed, (1 - w)*weight); + _right.update(elapsed*speed, w*weight); + } + } + + /** + * @private + */ + override alternativa3d function setController(value:AnimationController):void { + this.controller = value; + if (_left != null) { + _left.setController(value); + } + if (_right != null) { + _right.setController(value); + } + } + + /** + * @private + */ + override alternativa3d function addNode(node:AnimationNode):void { + super.addNode(node); + node._isActive = true; + } + + /** + * @private + */ + override alternativa3d function removeNode(node:AnimationNode):void { + if (_left == node) { + _left = null; + } else { + _right = null; + } + super.removeNode(node); + } + + /** + * The first animation. + */ + public function get left():AnimationNode { + return _left; + } + + /** + * @private + */ + public function set left(value:AnimationNode):void { + if (value != _left) { + if (value._parent == this) { + throw new Error("Animation already exists in blender"); + } + if (_left != null) { + removeNode(_left); + } + _left = value; + if (value != null) { + addNode(value); + } + } + } + + /** + * The second animation. + */ + public function get right():AnimationNode { + return _right; + } + + /** + * @private + */ + public function set right(value:AnimationNode):void { + if (value != _right) { + if (value._parent == this) { + throw new Error("Animation already exists in blender"); + } + if (_right != null) { + removeNode(_right); + } + _right = value; + if (value != null) { + addNode(value); + } + } + } + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationNode.as b/src/alternativa/engine3d/animation/AnimationNode.as new file mode 100644 index 0000000..6b6e5c0 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationNode.as @@ -0,0 +1,96 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Animation tree node. Animation in Alternativa3D is built over the blend tree. + * This tree is intended for combining a set of animations and keeping unambiguous status + * of each property being animated in every frame. E.g. there can be independent animations + * for legs and hands, that will be presented by the nodes of blend tree. With the help of blenders, + * derived from AnimationNode you can change or blend nodes of blend tree. Every tree animation + * is controlled by AnimationController. AnimationNode instance have to be a root element of the tree. + * + */ + public class AnimationNode { + + /** + * @private + */ + alternativa3d var _isActive:Boolean = false; + + /** + * @private + */ + alternativa3d var _parent:AnimationNode; + /** + * @private + */ + alternativa3d var controller:AnimationController; + + /** + * Animation speed. + */ + public var speed:Number = 1; + + /** + * Determines if the animation is active. + */ + public function get isActive():Boolean { + return _isActive && controller != null; + } + + /** + * Parent of this node in animation tree hierarchy. + */ + public function get parent():AnimationNode { + return _parent; + } + + + /** + * @private + */ + alternativa3d function update(elapsed:Number, weight:Number):void { + } + + /** + * @private + */ + alternativa3d function setController(value:AnimationController):void { + this.controller = value; + } + + /** + * @private + */ + alternativa3d function addNode(node:AnimationNode):void { + if (node._parent != null) { + node._parent.removeNode(node); + } + node._parent = this; + node.setController(controller); + } + + /** + * @private + */ + alternativa3d function removeNode(node:AnimationNode):void { + node.setController(null); + node._isActive = false; + node._parent = null; + } + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationNotify.as b/src/alternativa/engine3d/animation/AnimationNotify.as new file mode 100644 index 0000000..505fa53 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationNotify.as @@ -0,0 +1,78 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + import flash.events.EventDispatcher; + + use namespace alternativa3d; + + /** + * The notification trigger bound to certain time on an animation time line. + * AnimationNotify instance subscribes to NotifyEvent.When animation + * playback reaches the given time, an event is dispatched by the trigger. + * + * + * animationClip.addNotify(30).addEventListener(NotifyEvent.NOTIFY, notifyHandler) + * … + * private function notifyHandler(e:NotifyEvent):void{ + * trace("Animation time is " + e.notify.time + " seconds now") + *} + * + * + * @see AnimationClip#addNotify() + * @see AnimationClip#addNotifyAtEnd() + */ + public class AnimationNotify extends EventDispatcher { + + /** + * The name of notification trigger. + */ + public var name:String; + + /** + * @private + */ + alternativa3d var _time:Number = 0; + /** + * @private + */ + alternativa3d var next:AnimationNotify; + + /** + * @private + */ + alternativa3d var updateTime:Number; + /** + * @private + */ + alternativa3d var processNext:AnimationNotify; + + /** + * A new instance should not be created directly. Instead, use AnimationClip.addNotify() or AnimationClip.addNotifyAtEnd() methods. + * + * @see AnimationClip#addNotify() + * @see AnimationClip#addNotifyAtEnd() + */ + public function AnimationNotify(name:String) { + this.name = name; + } + + /** + * The time in seconds on the time line to which the trigger is bound. + */ + public function get time():Number { + return _time; + } + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationState.as b/src/alternativa/engine3d/animation/AnimationState.as new file mode 100644 index 0000000..1694b28 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationState.as @@ -0,0 +1,262 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.keys.TransformKey; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class AnimationState { + + public var useCount:int = 0; + + public var transform:TransformKey = new TransformKey(); + public var transformWeightSum:Number = 0; + + public var numbers:Object = new Object(); + public var numberWeightSums:Object = new Object(); + + + public function AnimationState() { + } + + public function reset():void { + transformWeightSum = 0; + for (var key:String in numbers) { + delete numbers[key]; + delete numberWeightSums[key]; + } + } + + public function addWeightedTransform(key:TransformKey, weight:Number):void { + transformWeightSum += weight; + transform.interpolate(transform, key, weight/transformWeightSum); + } + + public function addWeightedNumber(property:String, value:Number, weight:Number):void { + var sum:Number = numberWeightSums[property]; + if (sum == sum) { + sum += weight; + weight /= sum; + var current:Number = numbers[property]; + numbers[property] = (1 - weight)*current + weight*value; + numberWeightSums[property] = sum; + } else { + numbers[property] = value; + numberWeightSums[property] = weight; + } + } + + public function apply(object:Object3D):void { + if (transformWeightSum > 0) { + object._x = transform.x; + object._y = transform.y; + object._z = transform.z; + setEulerAngles(transform.rotation, object); + object._scaleX = transform.scaleX; + object._scaleY = transform.scaleY; + object._scaleZ = transform.scaleZ; + object.transformChanged = true; + } + + var sum:Number, weight:Number; + for (var key:String in numbers) { + switch (key) { + case 'x': + sum = numberWeightSums['x']; + weight = sum/(sum + transformWeightSum); + object.x = (1 - weight)*object.x + weight*numbers['x']; + break; + case 'y': + sum = numberWeightSums['y']; + weight = sum/(sum + transformWeightSum); + object.y = (1 - weight)*object.y + weight*numbers['y']; + break; + case 'z': + sum = numberWeightSums['z']; + weight = sum/(sum + transformWeightSum); + object.z = (1 - weight)*object.z + weight*numbers['z']; + break; + case 'rotationX': + sum = numberWeightSums['rotationX']; + weight = sum/(sum + transformWeightSum); + object.rotationX = (1 - weight)*object.rotationX + weight*numbers['rotationX']; + break; + case 'rotationY': + sum = numberWeightSums['rotationY']; + weight = sum/(sum + transformWeightSum); + object.rotationY = (1 - weight)*object.rotationY + weight*numbers['rotationY']; + break; + case 'rotationZ': + sum = numberWeightSums['rotationZ']; + weight = sum/(sum + transformWeightSum); + object.rotationZ = (1 - weight)*object.rotationZ + weight*numbers['rotationZ']; + break; + case 'scaleX': + sum = numberWeightSums['scaleX']; + weight = sum/(sum + transformWeightSum); + object.scaleX = (1 - weight)*object.scaleX + weight*numbers['scaleX']; + break; + case 'scaleY': + sum = numberWeightSums['scaleY']; + weight = sum/(sum + transformWeightSum); + object.scaleY = (1 - weight)*object.scaleY + weight*numbers['scaleY']; + break; + case 'scaleZ': + sum = numberWeightSums['scaleZ']; + weight = sum/(sum + transformWeightSum); + object.scaleZ = (1 - weight)*object.scaleZ + weight*numbers['scaleZ']; + break; + default : + object[key] = numbers[key]; + break; + } + } + } + + public function applyObject(object:Object):void { + if (transformWeightSum > 0) { + object.x = transform.x; + object.y = transform.y; + object.z = transform.z; + setEulerAnglesObject(transform.rotation, object); + object.scaleX = transform.scaleX; + object.scaleY = transform.scaleY; + object.scaleZ = transform.scaleZ; + } + + var sum:Number, weight:Number; + for (var key:String in numbers) { + switch (key) { + case 'x': + sum = numberWeightSums['x']; + weight = sum/(sum + transformWeightSum); + object.x = (1 - weight)*object.x + weight*numbers['x']; + break; + case 'y': + sum = numberWeightSums['y']; + weight = sum/(sum + transformWeightSum); + object.y = (1 - weight)*object.y + weight*numbers['y']; + break; + case 'z': + sum = numberWeightSums['z']; + weight = sum/(sum + transformWeightSum); + object.z = (1 - weight)*object.z + weight*numbers['z']; + break; + case 'rotationX': + sum = numberWeightSums['rotationX']; + weight = sum/(sum + transformWeightSum); + object.rotationX = (1 - weight)*object.rotationX + weight*numbers['rotationX']; + break; + case 'rotationY': + sum = numberWeightSums['rotationY']; + weight = sum/(sum + transformWeightSum); + object.rotationY = (1 - weight)*object.rotationY + weight*numbers['rotationY']; + break; + case 'rotationZ': + sum = numberWeightSums['rotationZ']; + weight = sum/(sum + transformWeightSum); + object.rotationZ = (1 - weight)*object.rotationZ + weight*numbers['rotationZ']; + break; + case 'scaleX': + sum = numberWeightSums['scaleX']; + weight = sum/(sum + transformWeightSum); + object.scaleX = (1 - weight)*object.scaleX + weight*numbers['scaleX']; + break; + case 'scaleY': + sum = numberWeightSums['scaleY']; + weight = sum/(sum + transformWeightSum); + object.scaleY = (1 - weight)*object.scaleY + weight*numbers['scaleY']; + break; + case 'scaleZ': + sum = numberWeightSums['scaleZ']; + weight = sum/(sum + transformWeightSum); + object.scaleZ = (1 - weight)*object.scaleZ + weight*numbers['scaleZ']; + break; + default : + object[key] = numbers[key]; + break; + } + } + } + + private function setEulerAngles(quat:Vector3D, object:Object3D):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) { + object._rotationX = Math.atan2(jj, kk); + object._rotationY = -Math.asin(ii); + object._rotationZ = Math.atan2(ee, aa); + } else { + object._rotationX = 0; + object._rotationY = (ii <= -1) ? Math.PI : -Math.PI; + object._rotationY *= 0.5; + object._rotationZ = Math.atan2(-bb, ff); + } + } + + private function setEulerAnglesObject(quat:Vector3D, object:Object):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) { + object.rotationX = Math.atan2(jj, kk); + object.rotationY = -Math.asin(ii); + object.rotationZ = Math.atan2(ee, aa); + } else { + object.rotationX = 0; + object.rotationY = (ii <= -1) ? Math.PI : -Math.PI; + object.rotationY *= 0.5; + object.rotationZ = Math.atan2(-bb, ff); + } + } + + + } +} diff --git a/src/alternativa/engine3d/animation/AnimationSwitcher.as b/src/alternativa/engine3d/animation/AnimationSwitcher.as new file mode 100644 index 0000000..5291749 --- /dev/null +++ b/src/alternativa/engine3d/animation/AnimationSwitcher.as @@ -0,0 +1,198 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * The animation switcher performs animation blending and active animation switching. + * + */ + public class AnimationSwitcher extends AnimationNode { + + /** + * @private + */ + private var _numAnimations:int = 0; + /** + * @private + */ + private var _animations:Vector. = new Vector.(); + /** + * @private + */ + private var _weights:Vector. = new Vector.(); + /** + * @private + */ + private var _active:AnimationNode; + /** + * @private + */ + private var fadingSpeed:Number = 0; + + /** + * @private + */ + override alternativa3d function update(elapsed:Number, weight:Number):void { + // TODO : make fade if it required only + var interval:Number = speed * elapsed; + var fade:Number = fadingSpeed * interval; + for (var i:int = 0; i < _numAnimations; i++) { + var animation:AnimationNode = _animations[i]; + var w:Number = _weights[i]; + if (animation == _active) { + w += fade; + w = (w >= 1) ? 1 : w; + animation.update(interval, weight * w); + _weights[i] = w; + } else { + w -= fade; + if (w > 0) { + animation.update(interval, weight * w); + _weights[i] = w; + } else { + animation._isActive = false; + _weights[i] = 0; + } + } + } + } + + /** + * The current active animation. To change active animation use activate(). + * + * @see #activate() + */ + public function get active():AnimationNode { + return _active; + } + + /** + * Activates specified animation during given time interval. All the rest animations fade out. + * + * @param animation Animation which is set as active. + * @param time The time interval during which the animation becomes fully active (i.e. has full weight). + */ + public function activate(animation:AnimationNode, time:Number = 0):void { + if (animation._parent != this) { + throw new Error("Animation is not child of this blender"); + } + _active = animation; + animation._isActive = true; + if (time <= 0) { + for (var i:int = 0; i < _numAnimations; i++) { + if (_animations[i] == animation) { + _weights[i] = 1; + } else { + _weights[i] = 0; + _animations[i]._isActive = false; + } + } + fadingSpeed = 0; + } else { + fadingSpeed = 1/time; + } + } + + /** + * @private + */ + override alternativa3d function setController(value:AnimationController):void { + this.controller = value; + for (var i:int = 0; i < _numAnimations; i++) { + var animation:AnimationNode = _animations[i]; + animation.setController(controller); + } + } + + /** + * @private + */ + override alternativa3d function removeNode(node:AnimationNode):void { + removeAnimation(node); + } + + /** + * Adds a new animation. + * + * @param animation The animation node to add. + * @return Added animation. + */ + public function addAnimation(animation:AnimationNode):AnimationNode { + if (animation == null) { + throw new Error("Animation cannot be null"); + } + if (animation._parent == this) { + throw new Error("Animation already exist in blender"); + } + _animations[_numAnimations] = animation; + if (_numAnimations == 0) { + _active = animation; + animation._isActive = true; + _weights[_numAnimations] = 1; + } else { + _weights[_numAnimations] = 0; + } + _numAnimations++; + addNode(animation); + return animation; + } + + /** + * Removes child animation node. + * + * @param animation Animation node to remove. + */ + public function removeAnimation(animation:AnimationNode):AnimationNode { + 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; + _weights.length = _numAnimations; + if (_active == animation) { + if (_numAnimations > 0) { + _active = _animations[int(_numAnimations - 1)]; + _weights[int(_numAnimations - 1)] = 1; + } else { + _active = null; + } + } + super.removeNode(animation); + return animation; + } + + /** + * Returns the child animation that exists at the specified index. + * + * @param index The index position of the child object. + */ + public function getAnimationAt(index:int):AnimationNode { + return _animations[index]; + } + + /** + * Returns number of animations. + */ + public function numAnimations():int { + return _numAnimations; + } + + } +} diff --git a/src/alternativa/engine3d/animation/events/NotifyEvent.as b/src/alternativa/engine3d/animation/events/NotifyEvent.as new file mode 100644 index 0000000..cb5c44c --- /dev/null +++ b/src/alternativa/engine3d/animation/events/NotifyEvent.as @@ -0,0 +1,44 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.events { + + import alternativa.engine3d.animation.AnimationNotify; + + import flash.events.Event; + + /** + * This event is fired by an AnimationNotify instance when certain point of AnimationClip time line is reached. + * + * @see alternativa.engine3d.animation.AnimationNotify + */ + public class NotifyEvent extends Event { + + /** + *NotifyEvent.NOTIFY is specified as the type property for transfer to the addEventListener alert for the event. + */ + public static const NOTIFY:String = "notify"; + + /** + * The source of the event. Actually this property returns AnimationNotify(target) value. + */ + public function get notify():AnimationNotify { + return AnimationNotify(target); + } + + /** + * Creates a Notyfy object. + */ + public function NotifyEvent(notify:AnimationNotify) { + super(NOTIFY); + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/Keyframe.as b/src/alternativa/engine3d/animation/keys/Keyframe.as new file mode 100644 index 0000000..2aecba0 --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/Keyframe.as @@ -0,0 +1,84 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Keyframe of the animation. Sets object property at given time. + * Keyframe animation can be defined with NumberTrack and TransformTrack classes. + * + * @see TransformTrack + * @see NumberTrack + */ + public class Keyframe { + + /** + * @private + * Key frame time in seconds. + */ + alternativa3d var _time:Number = 0; + + /** + * Creates a new Keyframe instance. + */ + public function Keyframe() { + } + + /** + * Key frame time in seconds. + */ + public function get time():Number { + return _time; + } + + /** + * The value of animated property kept by the keyframe. + * Can be Number or Matrix3D depends on + * NumberTrack or TransformTrack belongs to. + * + * @see NumberTrack + * @see TransformTrack + */ + public function get value():Object { + return null; + } + + /** + * @private + */ + public function set value(v:Object):void { + } + + /** + * @private + */ + alternativa3d function get nextKeyFrame():Keyframe { + return null; + } + + /** + * @private + */ + alternativa3d function set nextKeyFrame(value:Keyframe):void { + } + + /** + * Returns string representation of the object. + */ + public function toString():String { + return '[Keyframe time = ' + _time.toFixed(2) + ' value = ' + value + ']'; + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/NumberKey.as b/src/alternativa/engine3d/animation/keys/NumberKey.as new file mode 100644 index 0000000..2ecf935 --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/NumberKey.as @@ -0,0 +1,73 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * @private + */ + public class NumberKey extends Keyframe { + + /** + * @private + */ + alternativa3d var _value:Number = 0; + /** + * @private + */ + alternativa3d var next:NumberKey; + + /** + * Creates a NumberKey object. + */ + public function NumberKey() { + } + + /** + * Sets interpolated value. + */ + public function interpolate(a:NumberKey, b:NumberKey, c:Number):void { + _value = (1 - c)*a._value + c*b._value; + } + + /** + * @inheritDoc + */ + override public function get value():Object { + return _value; + } + + /** + * @inheritDoc + */ + override public function set value(v:Object):void { + _value = Number(v); + } + + /** + * @inheritDoc + */ + override alternativa3d function get nextKeyFrame():Keyframe { + return next; + } + + /** + * @inheritDoc + */ + override alternativa3d function set nextKeyFrame(value:Keyframe):void { + next = NumberKey(value); + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/NumberTrack.as b/src/alternativa/engine3d/animation/keys/NumberTrack.as new file mode 100644 index 0000000..949aebe --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/NumberTrack.as @@ -0,0 +1,160 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationState; + + use namespace alternativa3d; + + /** + * + * Keyframe track for animating numeric properties. Each keyframe keeps its own value of the property. + * The value interpolates for in between keyframes. + */ + public class NumberTrack extends Track { + + /** + * @private + * Head of keyframe list. + */ + alternativa3d var keyList:NumberKey; + + private var lastKey:NumberKey; + + /** + * @private + */ + override alternativa3d function get keyFramesList():Keyframe { + return keyList; + } + + /** + * @private + */ + override alternativa3d function set keyFramesList(value:Keyframe):void { + keyList = NumberKey(value); + } + + + /** + * @private + */ + override alternativa3d function get lastKey():Keyframe { + return lastKey; + } + + + /** + * @private + */ + override alternativa3d function set lastKey(value:Keyframe):void { + lastKey = NumberKey(value); + } + + /** + * Defines the name of object property which will be animated. + */ + public var property:String; + + /** + * Creates a NumberTrack object. + * + * @param object name of animating object. + * @param property name of animating property. + */ + public function NumberTrack(object:String, property:String) { + this.property = property; + this.object = object; + } + + /** + * Adds new keyframe. Keyframes stores ordered by its time property. + * + * @param time time of the new keyframe. + * @param value value of property for the new keyframe. + * @return added keyframe. + */ + public function addKey(time:Number, value:Number = 0):Keyframe { + var key:NumberKey = new NumberKey(); + key._time = time; + key.value = value; + addKeyToList(key); + return key; + } + + /** + * @private + */ + private static var temp:NumberKey = new NumberKey(); + + private var recentKey:NumberKey = null; + + /** + * @private + */ + override alternativa3d function blend(time:Number, weight:Number, state:AnimationState):void { + if (property == null) { + return; + } + var prev:NumberKey; + var next:NumberKey; + + if (recentKey != null && recentKey.time < time) { + prev = recentKey; + next = recentKey.next; + } else { + next = keyList; + } + while (next != null && next._time < time) { + prev = next; + next = next.next; + } + if (prev != null) { + if (next != null) { + temp.interpolate(prev, next, (time - prev._time)/(next._time - prev._time)); + state.addWeightedNumber(property, temp._value, weight); + } else { + state.addWeightedNumber(property, prev._value, weight); + } + recentKey = prev; + } else { + if (next != null) { + state.addWeightedNumber(property, next._value, weight); + } + } + } + + /** + * @private + */ + override alternativa3d function createKeyFrame():Keyframe { + return new NumberKey(); + } + + /** + * @private + */ + override alternativa3d function interpolateKeyFrame(dest:Keyframe, a:Keyframe, b:Keyframe, value:Number):void { + NumberKey(dest).interpolate(NumberKey(a), NumberKey(b), value); + } + + /** + * @inheritDoc + */ + override public function slice(start:Number, end:Number = Number.MAX_VALUE):Track { + var track:NumberTrack = new NumberTrack(object, property); + sliceImplementation(track, start, end); + return track; + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/Track.as b/src/alternativa/engine3d/animation/keys/Track.as new file mode 100644 index 0000000..4226111 --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/Track.as @@ -0,0 +1,261 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationState; + + use namespace alternativa3d; + + /** + * Keyframe track baseclass. + * + * @see alternativa.engine3d.animation.AnimationClip + */ + public class Track { + + /** + * Name of the object which is animated. + */ + public var object:String; + + /** + * @private + */ + alternativa3d var _length:Number = 0; + + /** + * Creates a Track object. + */ + public function Track() { + } + + /** + * The length of animation in seconds.. + */ + public function get length():Number { + return _length; + } + + /** + * @private + */ + alternativa3d function get keyFramesList():Keyframe { + return null; + } + + /** + * @private + */ + alternativa3d function set keyFramesList(value:Keyframe):void { + } + + /** + * @private + */ + alternativa3d function get lastKey():Keyframe { + return null; + } + + /** + * @private + */ + alternativa3d function set lastKey(value:Keyframe):void { + + } + + /** + * @private + */ + alternativa3d function addKeyToList(key:Keyframe):void { + var time:Number = key._time; + if (keyFramesList == null) { + keyFramesList = key; + lastKey = key; + _length = (time <= 0) ? 0 : time; + return; + } else { + if (keyFramesList._time > time) { + // replace head of the keyframe list + key.nextKeyFrame = keyFramesList; + keyFramesList = key; + return; + } else { + // adds to the end of list + if (lastKey._time < time) { + lastKey.nextKeyFrame = key; + lastKey = key; + _length = (time <= 0) ? 0 : time; + } else { + // search for appropriate place + var k:Keyframe = keyFramesList; + while (k.nextKeyFrame != null && k.nextKeyFrame._time <= time) { + k = k.nextKeyFrame; + } + if (k.nextKeyFrame == null) { + // adds to the end + k.nextKeyFrame = key; + _length = (time <= 0) ? 0 : time; + } else { + key.nextKeyFrame = k.nextKeyFrame; + k.nextKeyFrame = key; + } + } + + } + } + } + + /** + * Removes the supplied key frame. + * + * @param key the key frame to remove. + * @return removed key frame. + */ + public function removeKey(key:Keyframe):Keyframe { + if (keyFramesList != null) { + if (keyFramesList == key) { + keyFramesList = keyFramesList.nextKeyFrame; + if (keyFramesList == null) { + lastKey = null; + _length = 0; + } + return key; + } + var k:Keyframe = keyFramesList; + while (k.nextKeyFrame != null && k.nextKeyFrame != key) { + k = k.nextKeyFrame; + } + if (k.nextKeyFrame == key) { + // Remove + if (key.nextKeyFrame == null) { + lastKey = k; + // Last item + _length = (k._time <= 0) ? 0 : k._time; + } + k.nextKeyFrame = key.nextKeyFrame; + return key; + } + } + throw new Error("Key not found"); + } + + /** + * Time-sorted list of key frames. + */ + public function get keys():Vector. { + var result:Vector. = new Vector.(); + var i:int = 0; + for (var key:Keyframe = keyFramesList; key != null; key = key.nextKeyFrame) { + result[i] = key; + i++; + } + return result; + } + + /** + * @private + */ + alternativa3d function blend(time:Number, weight:Number, state:AnimationState):void { + } + + /** + * Returns a fragment of animation track between start and end time. + * + * @param start Fragment's start time. + * @param end Fragment's end time. + * @return Track fragment. + */ + public function slice(start:Number, end:Number = Number.MAX_VALUE):Track { + return null; + } + + /** + * @private + */ + alternativa3d function createKeyFrame():Keyframe { + return null; + } + + /** + * @private + */ + alternativa3d function interpolateKeyFrame(dest:Keyframe, a:Keyframe, b:Keyframe, value:Number):void { + } + + /** + * @private + */ + alternativa3d function sliceImplementation(dest:Track, start:Number, end:Number):void { + var shiftTime:Number = (start > 0) ? start : 0; + var prev:Keyframe; + var next:Keyframe = keyFramesList; + // the first keyframe + var key:Keyframe = createKeyFrame(); + var nextKey:Keyframe; + while (next != null && next._time <= start) { + prev = next; + next = next.nextKeyFrame; + } + if (prev != null) { + if (next != null) { + interpolateKeyFrame(key, prev, next, (start - prev._time)/(next._time - prev._time)); + key._time = start - shiftTime; + } else { + // last keyframe + interpolateKeyFrame(key, key, prev, 1); + } + } else { + if (next != null) { + // time before the start of animation + interpolateKeyFrame(key, key, next, 1); + key._time = next._time - shiftTime; + prev = next; + next = next.nextKeyFrame; + } else { + // empty track + return; + } + } + dest.keyFramesList = key; + if (next == null || end <= start) { + // one key frame + dest._length = (key._time <= 0) ? 0 : key._time; + return; + } + // copies intermediate keys + while (next != null && next._time <= end) { + nextKey = createKeyFrame(); + interpolateKeyFrame(nextKey, nextKey, next, 1); + nextKey._time = next._time - shiftTime; + key.nextKeyFrame = nextKey; + key = nextKey; + prev = next; + next = next.nextKeyFrame; + } + // move last key + if (next != null) { + // time to end of the track + nextKey = createKeyFrame(); + interpolateKeyFrame(nextKey, prev, next, (end - prev._time)/(next._time - prev._time)); + nextKey._time = end - shiftTime; + key.nextKeyFrame = nextKey; + } else { + // time after current key + } + if (nextKey != null) { + dest._length = (nextKey._time <= 0) ? 0 : nextKey._time; + } + return; + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/TransformKey.as b/src/alternativa/engine3d/animation/keys/TransformKey.as new file mode 100644 index 0000000..f6aed07 --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/TransformKey.as @@ -0,0 +1,164 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class TransformKey extends Keyframe { + + /** + * @private + */ + alternativa3d var x:Number = 0; + /** + * @private + */ + alternativa3d var y:Number = 0; + /** + * @private + */ + alternativa3d var z:Number = 0; + /** + * @private + */ + alternativa3d var rotation:Vector3D = new Vector3D(0, 0, 0, 1); + /** + * @private + */ + alternativa3d var scaleX:Number = 1; + /** + * @private + */ + alternativa3d var scaleY:Number = 1; + /** + * @private + */ + alternativa3d var scaleZ:Number = 1; + + /** + * @private + */ + alternativa3d var next:TransformKey; + + /** + * Creates a TransformKey object. + */ + public function TransformKey() { + } + + /** + * @inheritDoc + */ + override public function get value():Object { + var m:Matrix3D = new Matrix3D(); + m.recompose(Vector.([new Vector3D(x, y, z), rotation, new Vector3D(scaleX, scaleY, scaleZ)]), Orientation3D.QUATERNION); + return m; + } + + /** + * @inheritDoc + */ + override public function set value(v:Object):void { + var m:Matrix3D = Matrix3D(v); + var components:Vector. = m.decompose(Orientation3D.QUATERNION); + x = components[0].x; + y = components[0].y; + z = components[0].z; + rotation = components[1]; + scaleX = components[2].x; + scaleY = components[2].y; + scaleZ = components[2].z; + } + + /** + * Sets interpolated value. + */ + public function interpolate(a:TransformKey, b:TransformKey, c:Number):void { + var c2:Number = 1 - c; + x = c2*a.x + c*b.x; + y = c2*a.y + c*b.y; + z = c2*a.z + c*b.z; + slerp(a.rotation, b.rotation, c, rotation); + scaleX = c2*a.scaleX + c*b.scaleX; + scaleY = c2*a.scaleY + c*b.scaleY; + scaleZ = c2*a.scaleZ + c*b.scaleZ; + } + + /** + * @private + * + * Performs spherical interpolation between two given quaternions by min distance + * + * @param a first quaternion. + * @param b second quaternion. + * @param t interpolation parameter, usually defines in [0, 1] range. + * @return this + */ + private function slerp(a:Vector3D, b:Vector3D, t:Number, result:Vector3D):void { + var flip:Number = 1; + // Since one orientation represents by two values q and -q, we should invert the sign of one of quaternions + // in case of negative value of the dot product. Otherwise the interpolation results by max distance. + 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) { + // Linear interpolation used near zero + 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; + } + } + + /** + * @inheritDoc + */ + override alternativa3d function get nextKeyFrame():Keyframe { + return next; + } + + /** + * @inheritDoc + */ + override alternativa3d function set nextKeyFrame(value:Keyframe):void { + next = TransformKey(value); + } + + } +} diff --git a/src/alternativa/engine3d/animation/keys/TransformTrack.as b/src/alternativa/engine3d/animation/keys/TransformTrack.as new file mode 100644 index 0000000..6552391 --- /dev/null +++ b/src/alternativa/engine3d/animation/keys/TransformTrack.as @@ -0,0 +1,240 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationState; + + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * A track which animates object transformation. + */ + public class TransformTrack extends Track { + + private var keyList:TransformKey; + + private var lastKey:TransformKey; + + /** + * @private + */ + override alternativa3d function get keyFramesList():Keyframe { + return keyList; + } + + /** + * @private + */ + override alternativa3d function set keyFramesList(value:Keyframe):void { + keyList = TransformKey(value); + } + + + /** + * @private + */ + override alternativa3d function get lastKey():Keyframe { + return lastKey; + } + + /** + * @private + */ + override alternativa3d function set lastKey(value:Keyframe):void { + lastKey = TransformKey(value); + } + + /** + * Creates a TransformTrack object. + */ + public function TransformTrack(object:String) { + this.object = object; + } + + /** + * Adds new keyframe. Keyframes stores ordered by its time property. + * + * @param time time of the new keyframe. + * @param matrix value of property for the new keyframe. + * @return added keyframe. + */ + public function addKey(time:Number, matrix:Matrix3D):TransformKey { + var key:TransformKey = new TransformKey(); + key._time = time; + var components:Vector. = matrix.decompose(Orientation3D.QUATERNION); + key.x = components[0].x; + key.y = components[0].y; + key.z = components[0].z; + key.rotation = components[1]; + key.scaleX = components[2].x; + key.scaleY = components[2].y; + key.scaleZ = components[2].z; + addKeyToList(key); + return key; + } + + /** + * Adds new keyframe and initialize it by transformation components. + * Keyframes stores ordered by its time property. + * + * @param time time of the new keyframe. + * @return added keyframe. + */ + public function addKeyComponents(time:Number, x:Number = 0, y:Number = 0, z:Number = 0, rotationX:Number = 0, rotationY:Number = 0, rotationZ:Number = 0, scaleX:Number = 1, scaleY:Number = 1, scaleZ:Number = 1):TransformKey { + var key:TransformKey = new TransformKey(); + key._time = time; + key.x = x; + key.y = y; + key.z = z; + key.rotation = createQuatFromEuler(rotationX, rotationY, rotationZ); + key.scaleX = scaleX; + key.scaleY = scaleY; + key.scaleZ = scaleZ; + addKeyToList(key); + return key; + } + + /** + * @private + * + * Multiplies quat by additive from right: quat = quat * additive. + * + */ + private function appendQuat(quat:Vector3D, additive:Vector3D):void { + var ww:Number = additive.w*quat.w - additive.x*quat.x - additive.y*quat.y - additive.z*quat.z; + var xx:Number = additive.w*quat.x + additive.x*quat.w + additive.y*quat.z - additive.z*quat.y; + var yy:Number = additive.w*quat.y + additive.y*quat.w + additive.z*quat.x - additive.x*quat.z; + var zz:Number = additive.w*quat.z + additive.z*quat.w + additive.x*quat.y - additive.y*quat.x; + quat.w = ww; + quat.x = xx; + quat.y = yy; + quat.z = zz; + } + + /** + * @private + */ + private function normalizeQuat(quat:Vector3D):void { + var d:Number = quat.w*quat.w + quat.x*quat.x + quat.y*quat.y + quat.z*quat.z; + if (d == 0) { + quat.w = 1; + } else { + d = 1/Math.sqrt(d); + quat.w *= d; + quat.x *= d; + quat.y *= d; + quat.z *= d; + } + } + + /** + * @private + */ + private function setQuatFromAxisAngle(quat:Vector3D, x:Number, y:Number, z:Number, angle:Number):void { + quat.w = Math.cos(0.5*angle); + var k:Number = Math.sin(0.5*angle)/Math.sqrt(x*x + y*y + z*z); + quat.x = x*k; + quat.y = y*k; + quat.z = z*k; + } + + /** + * @private + */ + private static var tempQuat:Vector3D = new Vector3D(); + + /** + * @private + */ + private function createQuatFromEuler(x:Number, y:Number, z:Number):Vector3D { + var result:Vector3D = new Vector3D(); + setQuatFromAxisAngle(result, 1, 0, 0, x); + + setQuatFromAxisAngle(tempQuat, 0, 1, 0, y); + appendQuat(result, tempQuat); + normalizeQuat(result); + + setQuatFromAxisAngle(tempQuat, 0, 0, 1, z); + appendQuat(result, tempQuat); + normalizeQuat(result); + return result; + } + + /** + * @private + */ + private static var temp:TransformKey = new TransformKey(); + + private var recentKey:TransformKey = null; + + /** + * @private + */ + override alternativa3d function blend(time:Number, weight:Number, state:AnimationState):void { + var prev:TransformKey; + var next:TransformKey; + + if (recentKey != null && recentKey.time < time) { + prev = recentKey; + next = recentKey.next; + } else { + next = keyList; + } + while (next != null && next._time < time) { + prev = next; + next = next.next; + } + + if (prev != null) { + if (next != null) { + temp.interpolate(prev, next, (time - prev._time)/(next._time - prev._time)); + state.addWeightedTransform(temp, weight); + } else { + state.addWeightedTransform(prev, weight); + } + recentKey = prev; + } else { + if (next != null) { + state.addWeightedTransform(next, weight); + } + } + } + + /** + * @private + */ + override alternativa3d function createKeyFrame():Keyframe { + return new TransformKey(); + } + + /** + * @private + */ + override alternativa3d function interpolateKeyFrame(dest:Keyframe, a:Keyframe, b:Keyframe, value:Number):void { + TransformKey(dest).interpolate(TransformKey(a), TransformKey(b), value); + } + + /** + * @inheritDoc + */ + override public function slice(start:Number, end:Number = Number.MAX_VALUE):Track { + var track:TransformTrack = new TransformTrack(object); + sliceImplementation(track, start, end); + return track; + } + + } +} diff --git a/src/alternativa/engine3d/collisions/EllipsoidCollider.as b/src/alternativa/engine3d/collisions/EllipsoidCollider.as new file mode 100644 index 0000000..b0b19f3 --- /dev/null +++ b/src/alternativa/engine3d/collisions/EllipsoidCollider.as @@ -0,0 +1,576 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.collisions { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.*; + import alternativa.engine3d.resources.Geometry; + + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * The class implements the algorithm of the continuous collision of an ellipsoid with the faces. + */ + public class EllipsoidCollider { + + /** + * Ellipsoid radius along X axis. + */ + public var radiusX:Number; + + /** + * Ellipsoid radius along Y axis. + */ + public var radiusY:Number; + + /** + * Ellipsoid radius along Z axis. + */ + public var radiusZ:Number; + + /** + * Geometric error. Minimum absolute difference between two values + * when they are considered to be different. Default value is 0.001. + */ + public var threshold:Number = 0.001; + + private var matrix:Transform3D = new Transform3D(); + private var inverseMatrix:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var geometries:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var transforms:Vector. = new Vector.(); + + private var vertices:Vector. = new Vector.(); + private var normals:Vector. = new Vector.(); + private var indices:Vector. = new Vector.(); + private var numTriangles:int; + + private var radius:Number; + private var src:Vector3D = new Vector3D(); + private var displ:Vector3D = new Vector3D(); + private var dest:Vector3D = new Vector3D(); + + private var collisionPoint:Vector3D = new Vector3D(); + private var collisionPlane:Vector3D = new Vector3D(); + + /** + * @private + */ + alternativa3d var sphere:Vector3D = new Vector3D(); + private var cornerA:Vector3D = new Vector3D(); + private var cornerB:Vector3D = new Vector3D(); + private var cornerC:Vector3D = new Vector3D(); + private var cornerD:Vector3D = new Vector3D(); + + /** + * Creates a EllipsoidCollider object. + * + * @param radiusX Ellipsoid radius along X axis. + * @param radiusY Ellipsoid radius along Y axis. + * @param radiusZ Ellipsoid radius along Z axis. + */ + public function EllipsoidCollider(radiusX:Number, radiusY:Number, radiusZ:Number) { + this.radiusX = radiusX; + this.radiusY = radiusY; + this.radiusZ = radiusZ; + } + + /** + * @private + */ + alternativa3d function calculateSphere(transform:Transform3D):void { + sphere.x = transform.d; + sphere.y = transform.h; + sphere.z = transform.l; + var sax:Number = transform.a*cornerA.x + transform.b*cornerA.y + transform.c*cornerA.z + transform.d; + var say:Number = transform.e*cornerA.x + transform.f*cornerA.y + transform.g*cornerA.z + transform.h; + var saz:Number = transform.i*cornerA.x + transform.j*cornerA.y + transform.k*cornerA.z + transform.l; + var sbx:Number = transform.a*cornerB.x + transform.b*cornerB.y + transform.c*cornerB.z + transform.d; + var sby:Number = transform.e*cornerB.x + transform.f*cornerB.y + transform.g*cornerB.z + transform.h; + var sbz:Number = transform.i*cornerB.x + transform.j*cornerB.y + transform.k*cornerB.z + transform.l; + var scx:Number = transform.a*cornerC.x + transform.b*cornerC.y + transform.c*cornerC.z + transform.d; + var scy:Number = transform.e*cornerC.x + transform.f*cornerC.y + transform.g*cornerC.z + transform.h; + var scz:Number = transform.i*cornerC.x + transform.j*cornerC.y + transform.k*cornerC.z + transform.l; + var sdx:Number = transform.a*cornerD.x + transform.b*cornerD.y + transform.c*cornerD.z + transform.d; + var sdy:Number = transform.e*cornerD.x + transform.f*cornerD.y + transform.g*cornerD.z + transform.h; + var sdz:Number = transform.i*cornerD.x + transform.j*cornerD.y + transform.k*cornerD.z + transform.l; + var dx:Number = sax - sphere.x; + var dy:Number = say - sphere.y; + var dz:Number = saz - sphere.z; + sphere.w = dx*dx + dy*dy + dz*dz; + dx = sbx - sphere.x; + dy = sby - sphere.y; + dz = sbz - sphere.z; + var dxyz:Number = dx*dx + dy*dy + dz*dz; + if (dxyz > sphere.w) sphere.w = dxyz; + dx = scx - sphere.x; + dy = scy - sphere.y; + dz = scz - sphere.z; + dxyz = dx*dx + dy*dy + dz*dz; + if (dxyz > sphere.w) sphere.w = dxyz; + dx = sdx - sphere.x; + dy = sdy - sphere.y; + dz = sdz - sphere.z; + dxyz = dx*dx + dy*dy + dz*dz; + if (dxyz > sphere.w) sphere.w = dxyz; + sphere.w = Math.sqrt(sphere.w); + } + + private function prepare(source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary):void { + + // Radius of the sphere + radius = radiusX; + if (radiusY > radius) radius = radiusY; + if (radiusZ > radius) radius = radiusZ; + + // The matrix of the collider + matrix.compose(source.x, source.y, source.z, 0, 0, 0, radiusX/radius, radiusY/radius, radiusZ/radius); + inverseMatrix.copy(matrix); + inverseMatrix.invert(); + + // Local coordinates + src.x = 0; + src.y = 0; + src.z = 0; + // Local offset + displ.x = inverseMatrix.a*displacement.x + inverseMatrix.b*displacement.y + inverseMatrix.c*displacement.z; + displ.y = inverseMatrix.e*displacement.x + inverseMatrix.f*displacement.y + inverseMatrix.g*displacement.z; + displ.z = inverseMatrix.i*displacement.x + inverseMatrix.j*displacement.y + inverseMatrix.k*displacement.z; + // Local destination point + dest.x = src.x + displ.x; + dest.y = src.y + displ.y; + dest.z = src.z + displ.z; + + // Bound defined by movement of the sphere + var rad:Number = radius + displ.length; + cornerA.x = -rad; + cornerA.y = -rad; + cornerA.z = -rad; + cornerB.x = rad; + cornerB.y = -rad; + cornerB.z = -rad; + cornerC.x = rad; + cornerC.y = rad; + cornerC.z = -rad; + cornerD.x = -rad; + cornerD.y = rad; + cornerD.z = -rad; + + // Gathering the faces which with collision can occur + if (excludedObjects == null || !excludedObjects[object]) { + if (object.transformChanged) object.composeTransforms(); + object.globalToLocalTransform.combine(object.inverseTransform, matrix); + // Check collision with the bound + var intersects:Boolean = true; + if (object.boundBox != null) { + calculateSphere(object.globalToLocalTransform); + intersects = object.boundBox.checkSphere(sphere); + } + if (intersects) { + object.localToGlobalTransform.combine(inverseMatrix, object.transform); + object.collectGeometry(this, excludedObjects); + } + // Check children + if (object.childrenList != null) object.collectChildrenGeometry(this, excludedObjects); + } + + numTriangles = 0; + var indicesLength:int = 0; + var normalsLength:int = 0; + + // Loop geometries + var j:int; + var mapOffset:int = 0; + var verticesLength:int = 0; + var geometriesLength:int = geometries.length; + for (var i:int = 0; i < geometriesLength; i++) { + var geometry:Geometry = geometries[i]; + var transform:Transform3D = transforms[i]; + var geometryIndicesLength:int = geometry._indices.length; + if (geometry._numVertices == 0 || geometryIndicesLength == 0) continue; + // Transform vertices + var vBuffer:VertexStream = (VertexAttributes.POSITION < geometry._attributesStreams.length) ? geometry._attributesStreams[VertexAttributes.POSITION] : null; + if (vBuffer != null) { + var attributesOffset:int = geometry._attributesOffsets[VertexAttributes.POSITION]; + var numMappings:int = vBuffer.attributes.length; + var data:ByteArray = vBuffer.data; + for (j = 0; j < geometry._numVertices; j++) { + data.position = 4*(numMappings*j + attributesOffset); + var vx:Number = data.readFloat(); + var vy:Number = data.readFloat(); + var vz:Number = data.readFloat(); + vertices[verticesLength] = transform.a*vx + transform.b*vy + transform.c*vz + transform.d; verticesLength++; + vertices[verticesLength] = transform.e*vx + transform.f*vy + transform.g*vz + transform.h; verticesLength++; + vertices[verticesLength] = transform.i*vx + transform.j*vy + transform.k*vz + transform.l; verticesLength++; + } + } + // Loop triangles + var geometryIndices:Vector. = geometry._indices; + for (j = 0; j < geometryIndicesLength;) { + var a:int = geometryIndices[j] + mapOffset; j++; + var index:int = a*3; + var ax:Number = vertices[index]; index++; + var ay:Number = vertices[index]; index++; + var az:Number = vertices[index]; + var b:int = geometryIndices[j] + mapOffset; j++; + index = b*3; + var bx:Number = vertices[index]; index++; + var by:Number = vertices[index]; index++; + var bz:Number = vertices[index]; + var c:int = geometryIndices[j] + mapOffset; j++; + index = c*3; + var cx:Number = vertices[index]; index++; + var cy:Number = vertices[index]; index++; + var cz:Number = vertices[index]; + // Exclusion by bound + if (ax > rad && bx > rad && cx > rad || ax < -rad && bx < -rad && cx < -rad) continue; + if (ay > rad && by > rad && cy > rad || ay < -rad && by < -rad && cy < -rad) continue; + if (az > rad && bz > rad && cz > rad || az < -rad && bz < -rad && cz < -rad) continue; + // The normal + var abx:Number = bx - ax; + var aby:Number = by - ay; + var abz:Number = bz - az; + var acx:Number = cx - ax; + var acy:Number = cy - ay; + var acz:Number = cz - az; + 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) continue; + len = 1/Math.sqrt(len); + normalX *= len; + normalY *= len; + normalZ *= len; + var offset:Number = ax*normalX + ay*normalY + az*normalZ; + if (offset > rad || offset < -rad) continue; + indices[indicesLength] = a; indicesLength++; + indices[indicesLength] = b; indicesLength++; + indices[indicesLength] = c; indicesLength++; + normals[normalsLength] = normalX; normalsLength++; + normals[normalsLength] = normalY; normalsLength++; + normals[normalsLength] = normalZ; normalsLength++; + normals[normalsLength] = offset; normalsLength++; + numTriangles++; + } + // Offset by nomber of vertices + mapOffset += geometry._numVertices; + } + geometries.length = 0; + transforms.length = 0; + } + + /** + * Calculates destination point from given start position and displacement vector. + * @param source Starting point. + * @param displacement Displacement vector. + * @param object An object at crossing which will be checked. If this is a container, the application will participate and its child objects + * @param excludedObjects An associative array whose keys are instances of Object3D and its children. + * The objects that are keys of this dictionary will be excluded from intersection test. + * @return Destination point. + */ + public function calculateDestination(source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary = null):Vector3D { + + if (displacement.length <= threshold) return source.clone(); + + prepare(source, displacement, object, excludedObjects); + + if (numTriangles > 0) { + var limit:int = 50; + for (var i:int = 0; i < limit; i++) { + if (checkCollision()) { + // Offset destination point from behind collision plane by radius of the sphere over plane, along the normal + var offset:Number = radius + threshold + collisionPlane.w - dest.x*collisionPlane.x - dest.y*collisionPlane.y - dest.z*collisionPlane.z; + dest.x += collisionPlane.x*offset; + dest.y += collisionPlane.y*offset; + dest.z += collisionPlane.z*offset; + // Fixing up the current sphere coordinates for the next iteration + src.x = collisionPoint.x + collisionPlane.x*(radius + threshold); + src.y = collisionPoint.y + collisionPlane.y*(radius + threshold); + src.z = collisionPoint.z + collisionPlane.z*(radius + threshold); + // Fixing up velocity vector. The result ordered along plane of collision. + displ.x = dest.x - src.x; + displ.y = dest.y - src.y; + displ.z = dest.z - src.z; + if (displ.length < threshold) break; + } else break; + } + // Setting the coordinates + return new Vector3D(matrix.a*dest.x + matrix.b*dest.y + matrix.c*dest.z + matrix.d, matrix.e*dest.x + matrix.f*dest.y + matrix.g*dest.z + matrix.h, matrix.i*dest.x + matrix.j*dest.y + matrix.k*dest.z + matrix.l); + } else { + return new Vector3D(source.x + displacement.x, source.y + displacement.y, source.z + displacement.z); + } + } + + /** + * Finds first collision from given starting point aling displacement vector. + * @param source Starting point. + * @param displacement Displacement vector. + * @param resCollisionPoint Collision point will be written into this variable. + * @param resCollisionPlane Collision plane (defines by normal) parameters will be written into this variable. + * @param object The object to use in collision detection. If a container is specified, all its children will be tested for collison with ellipsoid. + * @param excludedObjects An associative array whose keys are instances of Object3D and its children. + * @return true if collision detected and false otherwise. + */ + public function getCollision(source:Vector3D, displacement:Vector3D, resCollisionPoint:Vector3D, resCollisionPlane:Vector3D, object:Object3D, excludedObjects:Dictionary = null):Boolean { + + if (displacement.length <= threshold) return false; + + prepare(source, displacement, object, excludedObjects); + + if (numTriangles > 0) { + if (checkCollision()) { + + // Transform the point to the global space + resCollisionPoint.x = matrix.a*collisionPoint.x + matrix.b*collisionPoint.y + matrix.c*collisionPoint.z + matrix.d; + resCollisionPoint.y = matrix.e*collisionPoint.x + matrix.f*collisionPoint.y + matrix.g*collisionPoint.z + matrix.h; + resCollisionPoint.z = matrix.i*collisionPoint.x + matrix.j*collisionPoint.y + matrix.k*collisionPoint.z + matrix.l; + + // Transform the plane to the global space + var abx:Number; + var aby:Number; + var abz:Number; + if (collisionPlane.x < collisionPlane.y) { + if (collisionPlane.x < collisionPlane.z) { + abx = 0; + aby = -collisionPlane.z; + abz = collisionPlane.y; + } else { + abx = -collisionPlane.y; + aby = collisionPlane.x; + abz = 0; + } + } else { + if (collisionPlane.y < collisionPlane.z) { + abx = collisionPlane.z; + aby = 0; + abz = -collisionPlane.x; + } else { + abx = -collisionPlane.y; + aby = collisionPlane.x; + abz = 0; + } + } + var acx:Number = collisionPlane.z*aby - collisionPlane.y*abz; + var acy:Number = collisionPlane.x*abz - collisionPlane.z*abx; + var acz:Number = collisionPlane.y*abx - collisionPlane.x*aby; + + var abx2:Number = matrix.a*abx + matrix.b*aby + matrix.c*abz; + var aby2:Number = matrix.e*abx + matrix.f*aby + matrix.g*abz; + var abz2:Number = matrix.i*abx + matrix.j*aby + matrix.k*abz; + var acx2:Number = matrix.a*acx + matrix.b*acy + matrix.c*acz; + var acy2:Number = matrix.e*acx + matrix.f*acy + matrix.g*acz; + var acz2:Number = matrix.i*acx + matrix.j*acy + matrix.k*acz; + + resCollisionPlane.x = abz2*acy2 - aby2*acz2; + resCollisionPlane.y = abx2*acz2 - abz2*acx2; + resCollisionPlane.z = aby2*acx2 - abx2*acy2; + resCollisionPlane.normalize(); + resCollisionPlane.w = resCollisionPoint.x*resCollisionPlane.x + resCollisionPoint.y*resCollisionPlane.y + resCollisionPoint.z*resCollisionPlane.z; + + return true; + } else { + return false; + } + } + return false; + } + + private function checkCollision():Boolean { + var minTime:Number = 1; + var displacementLength:Number = displ.length; + // Loop triangles + var indicesLength:int = numTriangles*3; + for (var i:int = 0, j:int = 0; i < indicesLength;) { + // Points + var index:int = indices[i]*3; i++; + var ax:Number = vertices[index]; index++; + var ay:Number = vertices[index]; index++; + var az:Number = vertices[index]; + index = indices[i]*3; i++; + var bx:Number = vertices[index]; index++; + var by:Number = vertices[index]; index++; + var bz:Number = vertices[index]; + index = indices[i]*3; i++; + var cx:Number = vertices[index]; index++; + var cy:Number = vertices[index]; index++; + var cz:Number = vertices[index]; + // Normal + var normalX:Number = normals[j]; j++; + var normalY:Number = normals[j]; j++; + var normalZ:Number = normals[j]; j++; + var offset:Number = normals[j]; j++; + var distance:Number = src.x*normalX + src.y*normalY + src.z*normalZ - offset; + // The intersection of plane and sphere + var pointX:Number; + var pointY:Number; + var pointZ:Number; + if (distance < radius) { + pointX = src.x - normalX*distance; + pointY = src.y - normalY*distance; + pointZ = src.z - normalZ*distance; + } else { + var t:Number = (distance - radius)/(distance - dest.x*normalX - dest.y*normalY - dest.z*normalZ + offset); + pointX = src.x + displ.x*t - normalX*radius; + pointY = src.y + displ.y*t - normalY*radius; + pointZ = src.z + displ.z*t - normalZ*radius; + } + // Closest polygon vertex + var faceX:Number; + var faceY:Number; + var faceZ:Number; + var min:Number = 1e+22; + // Loop edges + var inside:Boolean = true; + for (var k:int = 0; k < 3; k++) { + var p1x:Number; + var p1y:Number; + var p1z:Number; + var p2x:Number; + var p2y:Number; + var p2z:Number; + if (k == 0) { + p1x = ax; + p1y = ay; + p1z = az; + p2x = bx; + p2y = by; + p2z = bz; + } else if (k == 1) { + p1x = bx; + p1y = by; + p1z = bz; + p2x = cx; + p2y = cy; + p2z = cz; + } else { + p1x = cx; + p1y = cy; + p1z = cz; + p2x = ax; + p2y = ay; + p2z = az; + } + var abx:Number = p2x - p1x; + var aby:Number = p2y - p1y; + var abz:Number = p2z - p1z; + var acx:Number = pointX - p1x; + var acy:Number = pointY - p1y; + var acz:Number = pointZ - p1z; + var crx:Number = acz*aby - acy*abz; + var cry:Number = acx*abz - acz*abx; + var crz:Number = acy*abx - acx*aby; + // Case of the point is outside of the polygon + if (crx*normalX + cry*normalY + crz*normalZ < 0) { + var edgeLength:Number = abx*abx + aby*aby + abz*abz; + var edgeDistanceSqr:Number = (crx*crx + cry*cry + crz*crz)/edgeLength; + if (edgeDistanceSqr < min) { + // Edge normalization + edgeLength = Math.sqrt(edgeLength); + abx /= edgeLength; + aby /= edgeLength; + abz /= edgeLength; + // Distance to intersecion of normal along theedge + t = abx*acx + aby*acy + abz*acz; + var acLen:Number; + if (t < 0) { + // Closest point is the first one + acLen = acx*acx + acy*acy + acz*acz; + if (acLen < min) { + min = acLen; + faceX = p1x; + faceY = p1y; + faceZ = p1z; + } + } else if (t > edgeLength) { + // Closest point is the second one + acx = pointX - p2x; + acy = pointY - p2y; + acz = pointZ - p2z; + acLen = acx*acx + acy*acy + acz*acz; + if (acLen < min) { + min = acLen; + faceX = p2x; + faceY = p2y; + faceZ = p2z; + } + } else { + // Closest point is on edge + min = edgeDistanceSqr; + faceX = p1x + abx*t; + faceY = p1y + aby*t; + faceZ = p1z + abz*t; + } + } + inside = false; + } + } + // Case of point is inside polygon + if (inside) { + faceX = pointX; + faceY = pointY; + faceZ = pointZ; + } + // Vector pointed from closest point to the center of sphere + var deltaX:Number = src.x - faceX; + var deltaY:Number = src.y - faceY; + var deltaZ:Number = src.z - faceZ; + // If movement directed to point + if (deltaX*displ.x + deltaY*displ.y + deltaZ*displ.z <= 0) { + // reversed vector + var backX:Number = -displ.x/displacementLength; + var backY:Number = -displ.y/displacementLength; + var backZ:Number = -displ.z/displacementLength; + // Length of Vector pointed from closest point to the center of sphere + var deltaLength:Number = deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ; + // Projection Vector pointed from closest point to the center of sphere on reversed vector + var projectionLength:Number = deltaX*backX + deltaY*backY + deltaZ*backZ; + var projectionInsideLength:Number = radius*radius - deltaLength + projectionLength*projectionLength; + if (projectionInsideLength > 0) { + // Time of the intersection + var time:Number = (projectionLength - Math.sqrt(projectionInsideLength))/displacementLength; + // Collision with closest point occurs + if (time < minTime) { + minTime = time; + collisionPoint.x = faceX; + collisionPoint.y = faceY; + collisionPoint.z = faceZ; + if (inside) { + collisionPlane.x = normalX; + collisionPlane.y = normalY; + collisionPlane.z = normalZ; + collisionPlane.w = offset; + } else { + deltaLength = Math.sqrt(deltaLength); + collisionPlane.x = deltaX/deltaLength; + collisionPlane.y = deltaY/deltaLength; + collisionPlane.z = deltaZ/deltaLength; + collisionPlane.w = collisionPoint.x*collisionPlane.x + collisionPoint.y*collisionPlane.y + collisionPoint.z*collisionPlane.z; + } + } + } + } + } + return minTime < 1; + } + + } +} diff --git a/src/alternativa/engine3d/controllers/SimpleObjectController.as b/src/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..99533e6 --- /dev/null +++ b/src/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,486 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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; + + /** + * Controller for Object3D. Allow to handle the object with a keyboard and mouse. + * + * @see alternativa.engine3d.core.Object3D + */ + public class SimpleObjectController { + + /** + * Name of action for binding "forward" action. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + + /** + * Name of action for binding "back" action. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + + /** + * Name of action for binding "left" action. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + + /** + * Name of action for binding "right" action. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + + /** + * Name of action for binding "up" action. + */ + public static const ACTION_UP:String = "ACTION_UP"; + + /** + * Name of action for binding "down" action. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + + /** + * Name of action for binding "pitch up" action. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + + /** + * Name of action for binding "pitch down" action. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + + /** + * Name of action for binding "yaw left" action. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + + /** + * Name of action for binding "yaw right" action. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + + /** + * Name of action for binding "accelerate" action. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + + /** + * ИName of action for binding "mouse look" action. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + /** + * Speed. + */ + public var speed:Number; + + /** + * Speed multiplier for acceleration mode. + */ + public var speedMultiplier:Number; + + /** + * Mouse sensitivity. + */ + public var mouseSensitivity:Number; + + /** + * The maximal slope in the vertical plane in radians. + */ + public var maxPitch:Number = 1e+22; + + /** + * The minimal slope in the vertical plane in radians. + */ + 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.; + + private var time:int; + + /** + * The hash for binding names of action and functions. The functions should be at a form are follows: + * + * function(value:Boolean):void + * + * + * value argument defines if bound key pressed down or up. + */ + private var actionBindings:Object = {}; + + /** + * The hash for binding key codes and action names. + */ + protected var keyBindings:Object = {}; + + /** + * Creates a SimpleObjectController object. + * @param eventSource Source for event listening. + * @param speed Speed of movement. + * @param mouseSensitivity Mouse sensitivity, i.e. number of degrees per each pixel of mouse movement. + */ + 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(); + } + + /** + * Enables the controler. + */ + 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); + } + + /** + * Disables the controller. + */ + 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(); + } + + /** + * Enables mouse look mode. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Disables mouse look mode. + */ + 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); + } + + /** + * Target of handling. + */ + public function get object():Object3D { + return _object; + } + + /** + * @private + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * Refreshes controller state from state of handled object. Should be called if object was moved without the controller (i.e. object.x = 100;). + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.matrix.decompose(); + } + + /** + * Calculates and sets new object position. + */ + 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; + } + } + + /** + * Sets object at given position. + * @param pos The position. + */ + 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; + } + } + + /** + * Sets object at given position. + * @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; + } + } + + /** + * Sets direction of Z-axis of handled object to pointed at given place. If object is a camera, it will look to this direction. + * @param point Point to look at. + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * Sets direction of Z-axis of handled object to pointed at given place. If object is a camera, it will look to this direction. + * @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. = new Vector.(3); + private var _vout:Vector. = new Vector.(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; + } + + /** + * Starts and stops move forward according to true or false was passed. + * @param value Action switcher. + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Starts and stops move backward according to true or false was passed. + * @param value Action switcher. + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Starts and stops move to left according to true or false was passed. + * @param value Action switcher. + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Starts and stops move to right according to true or false was passed. + * @param value Action switcher. + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Starts and stops move up according to true or false was passed. + * @param value Action switcher. + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Starts and stops move down according to true or false was passed. + * @param value Action switcher. + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Switches acceleration mode. + * @param value true turns acceleration on, false turns off. + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Binds key and action. Only one action can be assigned to one key. + * @param keyCode Key code. + * @param action Action name. + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * Binds keys and actions. Only one action can be assigned to one key. + * @param bindings Array which consists of sequence of couples of key code and action. An example are follows: [ keyCode1, action1, keyCode2, action2 ] . + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Clear binding for given keyCode. + * @param keyCode Key code. + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Clear binding of all keys. + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:String in keyBindings) delete keyBindings[key]; + } + + /** + * Sets default binding. + * @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); + } + + } +} diff --git a/src/alternativa/engine3d/core/BoundBox.as b/src/alternativa/engine3d/core/BoundBox.as new file mode 100644 index 0000000..a90134a --- /dev/null +++ b/src/alternativa/engine3d/core/BoundBox.as @@ -0,0 +1,299 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * Class stores object's bounding box object's local space. Generally, position of child objects isn't considered at BoundBox calculation. + * Ray intersection always made boundBox check at first, but it's possible to check on crossing boundBox only. + * + */ + public class BoundBox { + /** + * Left face. + */ + public var minX:Number = 1e+22; + /** + * Back face. + */ + public var minY:Number = 1e+22; + /** + * Bottom face. + */ + public var minZ:Number = 1e+22; + /** + * Right face. + */ + public var maxX:Number = -1e+22; + /** + * Ftont face. + */ + public var maxY:Number = -1e+22; + /** + * Top face. + */ + public var maxZ:Number = -1e+22; + + + /** + * Resets all bounds values to its initial state. + */ + public function reset():void { + minX = 1e+22; + minY = 1e+22; + minZ = 1e+22; + maxX = -1e+22; + maxY = -1e+22; + maxZ = -1e+22; + } + + /** + * @private + */ + alternativa3d function checkFrustumCulling(frustum:CullingPlane, culling:int):int { + var side:int = 1; + for (var plane:CullingPlane = frustum; plane != null; plane = plane.next) { + if (culling & side) { + if (plane.x >= 0) if (plane.y >= 0) if (plane.z >= 0) { + if (maxX*plane.x + maxY*plane.y + maxZ*plane.z <= plane.offset) return -1; + if (minX*plane.x + minY*plane.y + minZ*plane.z > plane.offset) culling &= (63 & ~side); + } else { + if (maxX*plane.x + maxY*plane.y + minZ*plane.z <= plane.offset) return -1; + if (minX*plane.x + minY*plane.y + maxZ*plane.z > plane.offset) culling &= (63 & ~side); + } else if (plane.z >= 0) { + if (maxX*plane.x + minY*plane.y + maxZ*plane.z <= plane.offset) return -1; + if (minX*plane.x + maxY*plane.y + minZ*plane.z > plane.offset) culling &= (63 & ~side); + } else { + if (maxX*plane.x + minY*plane.y + minZ*plane.z <= plane.offset) return -1; + if (minX*plane.x + maxY*plane.y + maxZ*plane.z > plane.offset) culling &= (63 & ~side); + } else if (plane.y >= 0) if (plane.z >= 0) { + if (minX*plane.x + maxY*plane.y + maxZ*plane.z <= plane.offset) return -1; + if (maxX*plane.x + minY*plane.y + minZ*plane.z > plane.offset) culling &= (63 & ~side); + } else { + if (minX*plane.x + maxY*plane.y + minZ*plane.z <= plane.offset) return -1; + if (maxX*plane.x + minY*plane.y + maxZ*plane.z > plane.offset) culling &= (63 & ~side); + } else if (plane.z >= 0) { + if (minX*plane.x + minY*plane.y + maxZ*plane.z <= plane.offset) return -1; + if (maxX*plane.x + maxY*plane.y + minZ*plane.z > plane.offset) culling &= (63 & ~side); + } else { + if (minX*plane.x + minY*plane.y + minZ*plane.z <= plane.offset) return -1; + if (maxX*plane.x + maxY*plane.y + maxZ*plane.z > plane.offset) culling &= (63 & ~side); + } + } + side <<= 1; + } + return culling; + } + + /** + * @private + */ + alternativa3d function checkOcclusion(occluders:Vector., occludersLength:int, transform:Transform3D):Boolean { + var ax:Number = transform.a*minX + transform.b*minY + transform.c*minZ + transform.d; + var ay:Number = transform.e*minX + transform.f*minY + transform.g*minZ + transform.h; + var az:Number = transform.i*minX + transform.j*minY + transform.k*minZ + transform.l; + var bx:Number = transform.a*maxX + transform.b*minY + transform.c*minZ + transform.d; + var by:Number = transform.e*maxX + transform.f*minY + transform.g*minZ + transform.h; + var bz:Number = transform.i*maxX + transform.j*minY + transform.k*minZ + transform.l; + var cx:Number = transform.a*minX + transform.b*maxY + transform.c*minZ + transform.d; + var cy:Number = transform.e*minX + transform.f*maxY + transform.g*minZ + transform.h; + var cz:Number = transform.i*minX + transform.j*maxY + transform.k*minZ + transform.l; + var dx:Number = transform.a*maxX + transform.b*maxY + transform.c*minZ + transform.d; + var dy:Number = transform.e*maxX + transform.f*maxY + transform.g*minZ + transform.h; + var dz:Number = transform.i*maxX + transform.j*maxY + transform.k*minZ + transform.l; + var ex:Number = transform.a*minX + transform.b*minY + transform.c*maxZ + transform.d; + var ey:Number = transform.e*minX + transform.f*minY + transform.g*maxZ + transform.h; + var ez:Number = transform.i*minX + transform.j*minY + transform.k*maxZ + transform.l; + var fx:Number = transform.a*maxX + transform.b*minY + transform.c*maxZ + transform.d; + var fy:Number = transform.e*maxX + transform.f*minY + transform.g*maxZ + transform.h; + var fz:Number = transform.i*maxX + transform.j*minY + transform.k*maxZ + transform.l; + var gx:Number = transform.a*minX + transform.b*maxY + transform.c*maxZ + transform.d; + var gy:Number = transform.e*minX + transform.f*maxY + transform.g*maxZ + transform.h; + var gz:Number = transform.i*minX + transform.j*maxY + transform.k*maxZ + transform.l; + var hx:Number = transform.a*maxX + transform.b*maxY + transform.c*maxZ + transform.d; + var hy:Number = transform.e*maxX + transform.f*maxY + transform.g*maxZ + transform.h; + var hz:Number = transform.i*maxX + transform.j*maxY + transform.k*maxZ + transform.l; + for (var i:int = 0; i < occludersLength; i++) { + var occluder:Occluder = occluders[i]; + for (var plane:CullingPlane = occluder.planeList; plane != null; plane = plane.next) { + if (plane.x*ax + plane.y*ay + plane.z*az > plane.offset || + plane.x*bx + plane.y*by + plane.z*bz > plane.offset || + plane.x*cx + plane.y*cy + plane.z*cz > plane.offset || + plane.x*dx + plane.y*dy + plane.z*dz > plane.offset || + plane.x*ex + plane.y*ey + plane.z*ez > plane.offset || + plane.x*fx + plane.y*fy + plane.z*fz > plane.offset || + plane.x*gx + plane.y*gy + plane.z*gz > plane.offset || + plane.x*hx + plane.y*hy + plane.z*hz > plane.offset) break; + } + if (plane == null) return true; + } + return false; + } + + /** + * @private + */ + alternativa3d function checkRays(origins:Vector., directions:Vector., raysLength:int):Boolean { + for (var i:int = 0; i < raysLength; i++) { + var origin:Vector3D = origins[i]; + var direction:Vector3D = directions[i]; + if (origin.x >= minX && origin.x <= maxX && origin.y >= minY && origin.y <= maxY && origin.z >= minZ && origin.z <= maxZ) return true; + if (origin.x < minX && direction.x <= 0 || origin.x > maxX && direction.x >= 0 || origin.y < minY && direction.y <= 0 || origin.y > maxY && direction.y >= 0 || origin.z < minZ && direction.z <= 0 || origin.z > maxZ && direction.z >= 0) continue; + var a:Number; + var b:Number; + var c:Number; + var d:Number; + var threshold:Number = 0.000001; + // Intersection of X and Y projection + if (direction.x > threshold) { + a = (minX - origin.x)/direction.x; + b = (maxX - origin.x)/direction.x; + } else if (direction.x < -threshold) { + a = (maxX - origin.x)/direction.x; + b = (minX - origin.x)/direction.x; + } else { + a = 0; + b = 1e+22; + } + if (direction.y > threshold) { + c = (minY - origin.y)/direction.y; + d = (maxY - origin.y)/direction.y; + } else if (direction.y < -threshold) { + c = (maxY - origin.y)/direction.y; + d = (minY - origin.y)/direction.y; + } else { + c = 0; + d = 1e+22; + } + if (c >= b || d <= a) continue; + if (c < a) { + if (d < b) b = d; + } else { + a = c; + if (d < b) b = d; + } + // Intersection of XY and Z projections + if (direction.z > threshold) { + c = (minZ - origin.z)/direction.z; + d = (maxZ - origin.z)/direction.z; + } else if (direction.z < -threshold) { + c = (maxZ - origin.z)/direction.z; + d = (minZ - origin.z)/direction.z; + } else { + c = 0; + d = 1e+22; + } + if (c >= b || d <= a) continue; + return true; + } + return false; + } + + /** + * @private + */ + alternativa3d function checkSphere(sphere:Vector3D):Boolean { + return sphere.x + sphere.w > minX && sphere.x - sphere.w < maxX && sphere.y + sphere.w > minY && sphere.y - sphere.w < maxY && sphere.z + sphere.w > minZ && sphere.z - sphere.w < maxZ; + } + + /** + * Checks if the ray crosses the BoundBox. + * + * @param origin Ray origin. + * @param direction Ray direction. + * @return true if intersection was found and false otherwise. + */ + public function intersectRay(origin:Vector3D, direction:Vector3D):Boolean { + if (origin.x >= minX && origin.x <= maxX && origin.y >= minY && origin.y <= maxY && origin.z >= minZ && origin.z <= maxZ) return true; + if (origin.x < minX && direction.x <= 0) return false; + if (origin.x > maxX && direction.x >= 0) return false; + if (origin.y < minY && direction.y <= 0) return false; + if (origin.y > maxY && direction.y >= 0) return false; + if (origin.z < minZ && direction.z <= 0) return false; + if (origin.z > maxZ && direction.z >= 0) return false; + var a:Number; + var b:Number; + var c:Number; + var d:Number; + var threshold:Number = 0.000001; + // Intersection of X and Y projection + if (direction.x > threshold) { + a = (minX - origin.x) / direction.x; + b = (maxX - origin.x) / direction.x; + } else if (direction.x < -threshold) { + a = (maxX - origin.x) / direction.x; + b = (minX - origin.x) / direction.x; + } else { + a = -1e+22; + b = 1e+22; + } + if (direction.y > threshold) { + c = (minY - origin.y) / direction.y; + d = (maxY - origin.y) / direction.y; + } else if (direction.y < -threshold) { + c = (maxY - origin.y) / direction.y; + d = (minY - 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; + } + // Intersection of XY and Z projections + if (direction.z > threshold) { + c = (minZ - origin.z) / direction.z; + d = (maxZ - origin.z) / direction.z; + } else if (direction.z < -threshold) { + c = (maxZ - origin.z) / direction.z; + d = (minZ - origin.z) / direction.z; + } else { + c = -1e+22; + d = 1e+22; + } + if (c >= b || d <= a) return false; + return true; + } + + /** + * Duplicates an instance of BoundBox. + * @return New BoundBox instance with same set of properties. + */ + public function clone():BoundBox { + var res:BoundBox = new BoundBox(); + res.minX = minX; + res.minY = minY; + res.minZ = minZ; + res.maxX = maxX; + res.maxY = maxY; + res.maxZ = maxZ; + return res; + } + + /** + * Returns a string representation of BoundBox. + * @return A string representation of BoundBox. + */ + public function toString():String { + return "[BoundBox " + "X:[" + minX.toFixed(2) + ", " + maxX.toFixed(2) + "] Y:[" + minY.toFixed(2) + ", " + maxY.toFixed(2) + "] Z:[" + minZ.toFixed(2) + ", " + maxZ.toFixed(2) + "]]"; + } + + } +} diff --git a/src/alternativa/engine3d/core/Camera3D.as b/src/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..4bfba4f --- /dev/null +++ b/src/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,1188 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.Sprite; + import flash.display.Stage3D; + import flash.display.StageAlign; + import flash.display3D.Context3D; + import flash.events.Event; + 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.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + import flash.utils.getTimer; + + use namespace alternativa3d; + +/** + * + * Camera - it's three-dimensional object without its own visual representation and intended for visualising hierarchy of objects. + * For resource optimization camera draws only visible objects(objects in frustum). The view frustum is the volume that contains + * everything that is potentially visible on the screen. This volume takes the shape of a truncated pyramid, which defines + * by 6 planes. The apex of the pyramid is the camera position and the base of the pyramid is the farClipping. + * The pyramid is truncated at the nearClipping. Current version of Alternativa3D uses Z-Buffer for sorting objects, + * accuracy of sorting depends on distance between farClipping and nearClipping. That's why necessary to set a minimum + * distance between them for current scene. nearClipping mustn't be equal zero. + * + */ +public class Camera3D extends Object3D { + + /** + * The viewport defines part of screen to which renders image seen by the camera. + * If viewport is not defined, the camera would not draws anything. + */ + public var view:View; + + /** + * Field if view. Defines in radians. Default value is Math.PI/2 which considered with 90 degrees. + */ + public var fov:Number = Math.PI / 2; + + /** + * Near clipping distance. Default value 0. It should be as big as possible. + */ + public var nearClipping:Number; + + /** + * Far distance of clipping. Default value Number.MAX_VALUE. + */ + public var farClipping:Number; + + /** + * Determines whether orthographic (true) or perspective (false) projection is used. The default value is false. + */ + public var orthographic:Boolean = false; + + /** + * @private + */ + alternativa3d var focalLength:Number; + /** + * @private + */ + alternativa3d var m0:Number; + /** + * @private + */ + alternativa3d var m5:Number; + /** + * @private + */ + alternativa3d var m10:Number; + /** + * @private + */ + alternativa3d var m14:Number; + /** + * @private + */ + alternativa3d var correctionX:Number; + /** + * @private + */ + alternativa3d var correctionY:Number; + + /** + * @private + */ + alternativa3d var lights:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var lightsLength:int = 0; + /** + * @private + */ + alternativa3d var ambient:Vector. = new Vector.(4); + /** + * @private + */ + alternativa3d var childLights:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var frustum:CullingPlane; + + /** + * @private + */ + alternativa3d var origins:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var directions:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var raysLength:int = 0; + + /** + * @private + */ + alternativa3d var occluders:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var occludersLength:int = 0; + + /** + * @private + * Context3D which is used for rendering. + */ + alternativa3d var context3D:Context3D; + + /** + * @private + * Camera's renderer. If is not defined, the camera will no draw anything. + */ + public var renderer:Renderer = new Renderer(); + + /** + * @private + */ + alternativa3d var numDraws:int; + + /** + * @private + */ + alternativa3d var numTriangles:int; + + /** + * Creates a Camera3D object. + * + * @param nearClipping Near clipping distance. + * @param farClipping Far clipping distance. + */ + public function Camera3D(nearClipping:Number, farClipping:Number) { + this.nearClipping = nearClipping; + this.farClipping = farClipping; + frustum = new CullingPlane(); + frustum.next = new CullingPlane(); + frustum.next.next = new CullingPlane(); + frustum.next.next.next = new CullingPlane(); + frustum.next.next.next.next = new CullingPlane(); + frustum.next.next.next.next.next = new CullingPlane(); + } + + /** + * Rendering of objects hierarchy to the given Stage3D. + * + * @param stage3D Stage3D to which image will be rendered. + */ + public function render(stage3D:Stage3D):void { + var i:int; + var j:int; + var light:Light3D; + var occluder:Occluder; + // Error checking + if (stage3D == null) throw new TypeError("Parameter stage3D must be non-null."); + if (cpuTimer == -1) cpuTimer = getTimer(); + // Reset the counters + numDraws = 0; + numTriangles = 0; + // Reset the occluders + occludersLength = 0; + // Reset the lights + lightsLength = 0; + ambient[0] = 0; + ambient[1] = 0; + ambient[2] = 0; + ambient[3] = 1; + // Receiving the context + context3D = stage3D.context3D; + if (context3D != null && view != null && renderer != null && (view.stage != null || view._canvas != null)) { + renderer.camera = this; + // Projection argument calculating + calculateProjection(view._width, view._height); + // Preparing to rendering + view.prepareToRender(stage3D, context3D); + // Transformations calculating + if (transformChanged) composeTransforms(); + localToGlobalTransform.copy(transform); + globalToLocalTransform.copy(inverseTransform); + var root:Object3D = this; + while (root.parent != null) { + root = root.parent; + if (root.transformChanged) root.composeTransforms(); + localToGlobalTransform.append(root.transform); + globalToLocalTransform.prepend(root.inverseTransform); + } + // Calculating the rays of mouse events + view.calculateRays(this); + for (i = origins.length; i < view.raysLength; i++) { + origins[i] = new Vector3D(); + directions[i] = new Vector3D(); + } + raysLength = view.raysLength; + // Check if object of hierarchy is visible + if (root.visible) { + // Calculating the matrix to transform from the camera space to local space + root.cameraToLocalTransform.combine(root.inverseTransform, localToGlobalTransform); + // Calculating the matrix to transform from local space to the camera space + root.localToCameraTransform.combine(globalToLocalTransform, root.transform); + // Checking the culling + if (root.boundBox != null) { + calculateFrustum(root.cameraToLocalTransform); + root.culling = root.boundBox.checkFrustumCulling(frustum, 63); + } else { + root.culling = 63; + } + // Calculations of conent visibility + if (root.culling >= 0) root.calculateVisibility(this); + // Calculations visibility of children + root.calculateChildrenVisibility(this); + // Calculations of transformations from occluder space to the camera space + for (i = 0; i < occludersLength; i++) { + occluder = occluders[i]; + occluder.localToCameraTransform.calculateInversion(occluder.cameraToLocalTransform); + occluder.transformVertices(correctionX, correctionY); + occluder.distance = orthographic ? occluder.localToCameraTransform.l : (occluder.localToCameraTransform.d * occluder.localToCameraTransform.d + occluder.localToCameraTransform.h * occluder.localToCameraTransform.h + occluder.localToCameraTransform.l * occluder.localToCameraTransform.l); + occluder.enabled = true; + } + // Sorting the occluders by disance + if (occludersLength > 1) sortOccluders(); + // Constructing the volumes of occluders, their intersections, starts from closest + for (i = 0; i < occludersLength; i++) { + occluder = occluders[i]; + if (occluder.enabled) { + occluder.calculatePlanes(this); + if (occluder.planeList != null) { + for (j = i + 1; j < occludersLength; j++) { // It is possible, that start value should be 0 + var compared:Occluder = occluders[j]; + if (compared.enabled && compared != occluder && compared.checkOcclusion(occluder, correctionX, correctionY)) compared.enabled = false; + } + } else { + occluder.enabled = false; + } + } + // Reset of culling + occluder.culling = -1; + } + // Gather the occluders which will affects now + for (i = 0, j = 0; i < occludersLength; i++) { + occluder = occluders[i]; + if (occluder.enabled) { + // Debug + occluder.collectDraws(this, null, 0); + if (debug && occluder.boundBox != null && (checkInDebug(occluder) & Debug.BOUNDS)) Debug.drawBoundBox(this, occluder.boundBox, occluder.localToCameraTransform); + occluders[j] = occluder; + j++; + } + } + occludersLength = j; + occluders.length = j; + // Check light influence (?) + for (i = 0, j = 0; i < lightsLength; i++) { + light = lights[i]; + light.localToCameraTransform.calculateInversion(light.cameraToLocalTransform); + if (light.boundBox == null || occludersLength == 0 || !light.boundBox.checkOcclusion(occluders, occludersLength, light.localToCameraTransform)) { + light.red = ((light.color >> 16) & 0xFF) * light.intensity / 255; + light.green = ((light.color >> 8) & 0xFF) * light.intensity / 255; + light.blue = (light.color & 0xFF) * light.intensity / 255; + // Debug + light.collectDraws(this, null, 0); + if (debug && light.boundBox != null && (checkInDebug(light) & Debug.BOUNDS)) Debug.drawBoundBox(this, light.boundBox, light.localToCameraTransform); + + // Shadows preparing + if (light.shadow != null) { + light.shadow._light = light; + light.shadow.process(this); + } + lights[j] = light; + j++; + } + light.culling = -1; + } + lightsLength = j; + lights.length = j; + // Check getting in frustum and occluding + if (root.culling >= 0 && (root.boundBox == null || occludersLength == 0 || !root.boundBox.checkOcclusion(occluders, occludersLength, root.localToCameraTransform))) { + // Check if the ray crossing the bounding box + if (root.boundBox != null) { + calculateRays(root.cameraToLocalTransform); + root.listening = root.boundBox.checkRays(origins, directions, raysLength); + } else { + root.listening = true; + } + // Check if object needs in lightning + if (lightsLength > 0 && root.useLights) { + // Pass the lights to children and calculate appropriate transformations + if (root.boundBox != null) { + var childLightsLength:int = 0; + for (i = 0; i < lightsLength; i++) { + light = lights[i]; + light.lightToObjectTransform.combine(root.cameraToLocalTransform, light.localToCameraTransform); + // Detect influence + if (light.boundBox == null || light.checkBound(root)) { + childLights[childLightsLength] = light; + childLightsLength++; + } + } + root.collectDraws(this, childLights, childLightsLength); + } else { + // Calculate transformation from light space to object space + for (i = 0; i < lightsLength; i++) { + light = lights[i]; + light.lightToObjectTransform.combine(root.cameraToLocalTransform, light.localToCameraTransform); + } + root.collectDraws(this, lights, lightsLength); + } + } else { + root.collectDraws(this, null, 0); + } + // Debug the boundbox + if (debug && root.boundBox != null && (checkInDebug(root) & Debug.BOUNDS)) Debug.drawBoundBox(this, root.boundBox, root.localToCameraTransform); + } + // Gather the draws for children + root.collectChildrenDraws(this, lights, lightsLength); + } + cpuTimeSum += getTimer() - cpuTimer; + cpuTimeCount++; + // Mouse events prosessing + view.processMouseEvents(context3D, this); + // Render + renderer.render(context3D); + // Output + if (view._canvas == null) { + context3D.present(); + } else { + context3D.drawToBitmapData(view._canvas); + context3D.present(); + } + } else { + cpuTimeSum += getTimer() - cpuTimer; + cpuTimeCount++; + } + // Clearing + lights.length = 0; + childLights.length = 0; + occluders.length = 0; + context3D = null; + cpuTimer = -1; + } + + /** + * Transforms point from global space to screen space. The view property should be defined. + * @param point Point in global space. + * @return A Vector3D object containing screen coordinates. + */ + public function projectGlobal(point:Vector3D):Vector3D { + 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; + var focalLength:Number = Math.sqrt(viewSizeX * viewSizeX + viewSizeY * viewSizeY) / Math.tan(fov * 0.5); + var res:Vector3D = globalToLocal(point); + res.x = res.x * focalLength / res.z + viewSizeX; + res.y = res.y * focalLength / res.z + viewSizeY; + return res; + } + + /** + * Calculates a ray in global space. The ray defines by its origin and direction. + * The ray goes like from the global camera position + * trough the point corresponding to the viewport point with coordinates viewX и viewY. + * The ray origin placed within nearClipping plane. + * This ray can be used in the Object3D.intersectRay() method. The result writes to passed arguments. + * + * @param origin Ray origin will wrote here. + * @param direction Ray direction will wrote here. + * @param viewX Horizontal coordinate in view plane, through which the ray should go. + * @param viewY Vertical coordinate in view plane, through which the ray should go. + */ + 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; + var focalLength:Number = Math.sqrt(viewSizeX * viewSizeX + viewSizeY * viewSizeY) / Math.tan(fov * 0.5); + var dx:Number = viewX - viewSizeX; + var dy:Number = viewY - viewSizeY; + var ox:Number = dx * nearClipping / focalLength; + var oy:Number = dy * nearClipping / focalLength; + var oz:Number = nearClipping; + if (transformChanged) composeTransforms(); + trm.copy(transform); + var root:Object3D = this; + while (root.parent != null) { + root = root.parent; + if (root.transformChanged) root.composeTransforms(); + trm.append(root.transform); + } + origin.x = trm.a * ox + trm.b * oy + trm.c * oz + trm.d; + origin.y = trm.e * ox + trm.f * oy + trm.g * oz + trm.h; + origin.z = trm.i * ox + trm.j * oy + trm.k * oz + trm.l; + direction.x = trm.a * dx + trm.b * dy + trm.c * focalLength; + direction.y = trm.e * dx + trm.f * dy + trm.g * focalLength; + direction.z = trm.i * dx + trm.j * dy + trm.k * focalLength; + var directionL:Number = 1 / Math.sqrt(direction.x * direction.x + direction.y * direction.y + direction.z * direction.z); + direction.x *= directionL; + direction.y *= directionL; + direction.z *= directionL; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Camera3D = new Camera3D(nearClipping, farClipping); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:Camera3D = source as Camera3D; + fov = src.fov; + view = src.view; + nearClipping = src.nearClipping; + farClipping = src.farClipping; + orthographic = src.orthographic; + } + + /** + * @private + */ + alternativa3d function calculateProjection(width:Number, height:Number):void { + var viewSizeX:Number = width * 0.5; + var viewSizeY:Number = height * 0.5; + focalLength = Math.sqrt(viewSizeX * viewSizeX + viewSizeY * viewSizeY) / Math.tan(fov * 0.5); + if (!orthographic) { + m0 = focalLength / viewSizeX; + m5 = -focalLength / viewSizeY; + m10 = farClipping / (farClipping - nearClipping); + m14 = -nearClipping * m10; + } else { + m0 = 1 / viewSizeX; + m5 = -1 / viewSizeY; + m10 = 1 / (farClipping - nearClipping); + m14 = -nearClipping * m10; + } + correctionX = viewSizeX / focalLength; + correctionY = viewSizeY / focalLength; + } + + /** + * @private + */ + alternativa3d function calculateFrustum(transform:Transform3D):void { + var nearPlane:CullingPlane = frustum; + var farPlane:CullingPlane = nearPlane.next; + var leftPlane:CullingPlane = farPlane.next; + var rightPlane:CullingPlane = leftPlane.next; + var topPlane:CullingPlane = rightPlane.next; + var bottomPlane:CullingPlane = topPlane.next; + if (!orthographic) { + var fa:Number = transform.a * correctionX; + var fe:Number = transform.e * correctionX; + var fi:Number = transform.i * correctionX; + var fb:Number = transform.b * correctionY; + var ff:Number = transform.f * correctionY; + var fj:Number = transform.j * correctionY; + nearPlane.x = fj * fe - ff * fi; + nearPlane.y = fb * fi - fj * fa; + nearPlane.z = ff * fa - fb * fe; + nearPlane.offset = (transform.d + transform.c * nearClipping) * nearPlane.x + (transform.h + transform.g * nearClipping) * nearPlane.y + (transform.l + transform.k * nearClipping) * nearPlane.z; + + farPlane.x = -nearPlane.x; + farPlane.y = -nearPlane.y; + farPlane.z = -nearPlane.z; + farPlane.offset = (transform.d + transform.c * farClipping) * farPlane.x + (transform.h + transform.g * farClipping) * farPlane.y + (transform.l + transform.k * farClipping) * farPlane.z; + + var ax:Number = -fa - fb + transform.c; + var ay:Number = -fe - ff + transform.g; + var az:Number = -fi - fj + transform.k; + var bx:Number = fa - fb + transform.c; + var by:Number = fe - ff + transform.g; + var bz:Number = fi - fj + transform.k; + topPlane.x = bz * ay - by * az; + topPlane.y = bx * az - bz * ax; + topPlane.z = by * ax - bx * ay; + topPlane.offset = transform.d * topPlane.x + transform.h * topPlane.y + transform.l * topPlane.z; + // Right plane. + ax = bx; + ay = by; + az = bz; + bx = fa + fb + transform.c; + by = fe + ff + transform.g; + bz = fi + fj + transform.k; + rightPlane.x = bz * ay - by * az; + rightPlane.y = bx * az - bz * ax; + rightPlane.z = by * ax - bx * ay; + rightPlane.offset = transform.d * rightPlane.x + transform.h * rightPlane.y + transform.l * rightPlane.z; + // Bottom plane. + ax = bx; + ay = by; + az = bz; + bx = -fa + fb + transform.c; + by = -fe + ff + transform.g; + bz = -fi + fj + transform.k; + bottomPlane.x = bz*ay - by*az; + bottomPlane.y = bx*az - bz*ax; + bottomPlane.z = by*ax - bx*ay; + bottomPlane.offset = transform.d*bottomPlane.x + transform.h*bottomPlane.y + transform.l*bottomPlane.z; + // Left plane. + ax = bx; + ay = by; + az = bz; + bx = -fa - fb + transform.c; + by = -fe - ff + transform.g; + bz = -fi - fj + transform.k; + leftPlane.x = bz*ay - by*az; + leftPlane.y = bx*az - bz*ax; + leftPlane.z = by*ax - bx*ay; + leftPlane.offset = transform.d*leftPlane.x + transform.h*leftPlane.y + transform.l*leftPlane.z; + } else { + var viewSizeX:Number = view._width*0.5; + var viewSizeY:Number = view._height*0.5; + // Near plane. + nearPlane.x = transform.j*transform.e - transform.f*transform.i; + nearPlane.y = transform.b*transform.i - transform.j*transform.a; + nearPlane.z = transform.f*transform.a - transform.b*transform.e; + nearPlane.offset = (transform.d + transform.c*nearClipping)*nearPlane.x + (transform.h + transform.g*nearClipping)*nearPlane.y + (transform.l + transform.k*nearClipping)*nearPlane.z; + // Far plane. + farPlane.x = -nearPlane.x; + farPlane.y = -nearPlane.y; + farPlane.z = -nearPlane.z; + farPlane.offset = (transform.d + transform.c*farClipping)*farPlane.x + (transform.h + transform.g*farClipping)*farPlane.y + (transform.l + transform.k*farClipping)*farPlane.z; + // Top plane. + topPlane.x = transform.i*transform.g - transform.e*transform.k; + topPlane.y = transform.a*transform.k - transform.i*transform.c; + topPlane.z = transform.e*transform.c - transform.a*transform.g; + topPlane.offset = (transform.d - transform.b*viewSizeY)*topPlane.x + (transform.h - transform.f*viewSizeY)*topPlane.y + (transform.l - transform.j*viewSizeY)*topPlane.z; + // Bottom plane. + bottomPlane.x = -topPlane.x; + bottomPlane.y = -topPlane.y; + bottomPlane.z = -topPlane.z; + bottomPlane.offset = (transform.d + transform.b*viewSizeY)*bottomPlane.x + (transform.h + transform.f*viewSizeY)*bottomPlane.y + (transform.l + transform.j*viewSizeY)*bottomPlane.z; + // Left plane. + leftPlane.x = transform.k*transform.f - transform.g*transform.j; + leftPlane.y = transform.c*transform.j - transform.k*transform.b; + leftPlane.z = transform.g*transform.b - transform.c*transform.f; + leftPlane.offset = (transform.d - transform.a*viewSizeX)*leftPlane.x + (transform.h - transform.e*viewSizeX)*leftPlane.y + (transform.l - transform.i*viewSizeX)*leftPlane.z; + // Right plane. + rightPlane.x = -leftPlane.x; + rightPlane.y = -leftPlane.y; + rightPlane.z = -leftPlane.z; + rightPlane.offset = (transform.d + transform.a*viewSizeX)*rightPlane.x + (transform.h + transform.e*viewSizeX)*rightPlane.y + (transform.l + transform.i*viewSizeX)*rightPlane.z; + } + } + + /** + * @private + */ + alternativa3d function calculateRays(transform:Transform3D):void { + for (var i:int = 0; i < raysLength; i++) { + var o:Vector3D = view.raysOrigins[i]; + var d:Vector3D = view.raysDirections[i]; + var origin:Vector3D = origins[i]; + var direction:Vector3D = directions[i]; + origin.x = transform.a * o.x + transform.b * o.y + transform.c * o.z + transform.d; + origin.y = transform.e * o.x + transform.f * o.y + transform.g * o.z + transform.h; + origin.z = transform.i * o.x + transform.j * o.y + transform.k * o.z + transform.l; + direction.x = transform.a * d.x + transform.b * d.y + transform.c * d.z; + direction.y = transform.e * d.x + transform.f * d.y + transform.g * d.z; + direction.z = transform.i * d.x + transform.j * d.y + transform.k * d.z; + } + } + + static private const stack:Vector. = new Vector.(); + + private function sortOccluders():void { + stack[0] = 0; + stack[1] = occludersLength - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + var j:int = r; + index--; + var l:int = stack[index]; + var i:int = l; + var occluder:Occluder = occluders[(r + l) >> 1]; + var median:Number = occluder.distance; + while (i <= j) { + var left:Occluder = occluders[i]; + while (left.distance < median) { + i++; + left = occluders[i]; + } + var right:Occluder = occluders[j]; + while (right.distance > median) { + j--; + right = occluders[j]; + } + if (i <= j) { + occluders[i] = right; + occluders[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + } + + // DEBUG + + /** + * Turns debug mode on if true and off otherwise. + * The default value is false. + * + * @see #addToDebug() + * @see #removeFromDebug() + */ + public var debug:Boolean = false; + + private var debugSet:Object = new Object(); + + /** + * Adds an object or a class to list of debug drawing. + * In case of class, all object of this type will drawn in debug mode. + * + * @param debug The component of object which will draws in debug mode. Should be Debug.BOUND for now. Check Debug for updates. + * @param objectOrClass Object3D or class extended Object3D. + * @see alternativa.engine3d.core.Debug + * @see #debug + * @see #removeFromDebug() + */ + public function addToDebug(debug:int, objectOrClass:*):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + debugSet[debug][objectOrClass] = true; + } + + /** + * Removed an object or a class from list of debug drawing. + * + * @param debug The component of object which will draws in debug mode. Should be Debug.BOUND for now. Check Debug for updates. + * @param objectOrClass Object3D or class extended Object3D. + * + * @see alternativa.engine3d.core.Debug + * @see #debug + * @see #addToDebug() + */ + public function removeFromDebug(debug:int, objectOrClass:*):void { + if (debugSet[debug]) { + delete debugSet[debug][objectOrClass]; + var key:*; + for (key in debugSet[debug]) break; + if (!key) delete debugSet[debug]; + } + } + + /** + * @private + * + * Check if the object or its class is in list of debug drawing. + */ + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 512; debug <<= 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + + private var _diagram:Sprite = createDiagram(); + + /** + * The amount of frames which determines the period of FPS value update in diagram. + * @see #diagram + */ + public var fpsUpdatePeriod:int = 10; + + /** + * The amount of frames which determines the period of MS value update in diagram. + * @see #diagram + */ + public var timerUpdatePeriod:int = 10; + + private var fpsTextField:TextField; + private var frameTextField:TextField; + private var cpuTextField: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 methodTimeSum:int; + private var methodTimeCount:int; + private var methodTimer:int; + private var cpuTimeSum:int = 0; + private var cpuTimeCount:int = 0; + private var cpuTimer:int = -1; + + public function startCPUTimer():void { + cpuTimer = getTimer(); + } + + /** + * Starts time count. startTimer()and stopTimer() are necessary to measure time for code part executing. + * The result is displayed in the field MS of the diagram. + * + * @see #diagram + * @see #stopTimer() + */ + public function startTimer():void { + methodTimer = getTimer(); + } + + /** + * Stops time count. startTimer() and stopTimer() are necessary to measure time for code part executing. + * The result is displayed in the field MS of the diagram. + * @see #diagram + * @see #startTimer() + */ + public function stopTimer():void { + methodTimeSum += getTimer() - methodTimer; + methodTimeCount++; + } + + /** + * Diagram where debug information is displayed. To display diagram, you need to add it on the screen. + * FPS is an average amount of frames per second. + * MS is an average time of executing the code part in milliseconds. This code part is measured with startTimer - stopTimer. + * MEM is an amount of memory reserved by player (in megabytes). + * DRW is an amount of draw calls in the current frame. + * PLG is an amount of visible polygons in the current frame. + * TRI is an amount of drawn triangles in the current frame. + * + * @see #fpsUpdatePeriod + * @see #timerUpdatePeriod + * @see #startTimer() + * @see #stopTimer() + */ + public function get diagram():DisplayObject { + return _diagram; + } + + /** + * Diagram alignment relatively to working space. You can use constants of StageAlign class. + * + */ + public function get diagramAlign():String { + return _diagramAlign; + } + + /** + * @private + */ + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + /** + * Diagram margin from the edge of working space in horizontal axis. + */ + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + + /** + * @private + */ + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + /** + * Diagram margin from the edge of working space in vertical axis. + */ + 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; + // 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); + // time of frame + frameTextField = new TextField(); + frameTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + frameTextField.autoSize = TextFieldAutoSize.LEFT; + frameTextField.text = "TME:"; + frameTextField.selectable = false; + frameTextField.x = -3; + frameTextField.y = 4; + diagram.addChild(frameTextField); + // cpu time + cpuTextField = new TextField(); + cpuTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + cpuTextField.autoSize = TextFieldAutoSize.LEFT; + cpuTextField.text = "CPU:"; + cpuTextField.selectable = false; + cpuTextField.x = -3; + cpuTextField.y = 13; + diagram.addChild(cpuTextField); + // time of method execution + 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 = 22; + diagram.addChild(timerTextField); + // memory + 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 = 31; + diagram.addChild(memoryTextField); + // debug draws + 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 = 40; + diagram.addChild(drawsTextField); + // triangles + 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 = 49; + diagram.addChild(trianglesTextField); + // diagram initialization + diagram.addEventListener(Event.ADDED_TO_STAGE, function ():void { + // FPS + 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); + // Frame time + frameTextField = new TextField(); + frameTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + frameTextField.autoSize = TextFieldAutoSize.RIGHT; + frameTextField.text = Number(1000 / diagram.stage.frameRate).toFixed(2); + frameTextField.selectable = false; + frameTextField.x = -3; + frameTextField.y = 4; + frameTextField.width = 85; + diagram.addChild(frameTextField); + // CPU time + cpuTextField = new TextField(); + cpuTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + cpuTextField.autoSize = TextFieldAutoSize.RIGHT; + cpuTextField.text = ""; + cpuTextField.selectable = false; + cpuTextField.x = -3; + cpuTextField.y = 13; + cpuTextField.width = 85; + diagram.addChild(cpuTextField); + // Time of method performing + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.RIGHT; + timerTextField.text = ""; + timerTextField.selectable = false; + timerTextField.x = -3; + timerTextField.y = 22; + timerTextField.width = 85; + diagram.addChild(timerTextField); + // Memory + 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 = 31; + memoryTextField.width = 85; + diagram.addChild(memoryTextField); + // Draw calls + 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 = 40; + drawsTextField.width = 72; + diagram.addChild(drawsTextField); + // Number of triangles + 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 = 49; + trianglesTextField.width = 72; + diagram.addChild(trianglesTextField); + // Graph + graph = new Bitmap(new BitmapData(80, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40); + graph.x = 0; + graph.y = 63; + diagram.addChild(graph); + // Reset of parameters + previousPeriodTime = getTimer(); + previousFrameTime = previousPeriodTime; + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + methodTimeSum = 0; + methodTimeCount = 0; + // Subscription + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Deinitialization of diagram + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function ():void { + // Reset + diagram.removeChild(fpsTextField); + diagram.removeChild(frameTextField); + diagram.removeChild(cpuTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(drawsTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(timerTextField); + diagram.removeChild(graph); + fpsTextField = null; + frameTextField = null; + cpuTextField = null; + memoryTextField = null; + drawsTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Unsubscribe + 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 text + 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")); + value = 1000 / value; + mod = value * 100 % 100; + frameTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS plot + 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; + + // time text + if (++timerUpdateCounter == timerUpdatePeriod) { + if (methodTimeCount > 0) { + value = methodTimeSum / methodTimeCount; + mod = value * 100 % 100; + timerTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = ""; + } + if (cpuTimeCount > 0) { + value = cpuTimeSum / cpuTimeCount; + mod = value * 100 % 100; + cpuTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + cpuTextField.text = ""; + } + timerUpdateCounter = 0; + methodTimeSum = 0; + methodTimeCount = 0; + cpuTimeSum = 0; + cpuTimeCount = 0; + } + + // memory text + var memory:int = System.totalMemory; + value = memory / 1048576; + mod = value * 100 % 100; + memoryTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + + // memory plot + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40 * (1 - memory / maxMemory), 0xFFCCCC00); + + // debug text + drawsTextField.text = formatInt(numDraws); + + // Triangles (text) + 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"; + } +} +} diff --git a/src/alternativa/engine3d/core/CullingPlane.as b/src/alternativa/engine3d/core/CullingPlane.as new file mode 100644 index 0000000..52c01d7 --- /dev/null +++ b/src/alternativa/engine3d/core/CullingPlane.as @@ -0,0 +1,50 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + /** + * @private + */ + public class CullingPlane { + + public var x:Number; + public var y:Number; + public var z:Number; + public var offset:Number; + + public var next:CullingPlane; + + static public var collector:CullingPlane; + + static public function create():CullingPlane { + if (collector != null) { + var res:CullingPlane = collector; + collector = res.next; + res.next = null; + return res; + } else { + return new CullingPlane(); + } + } + + public function create():CullingPlane { + if (collector != null) { + var res:CullingPlane = collector; + collector = res.next; + res.next = null; + return res; + } else { + return new CullingPlane(); + } + } + + } +} diff --git a/src/alternativa/engine3d/core/Debug.as b/src/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..8198cbc --- /dev/null +++ b/src/alternativa/engine3d/core/Debug.as @@ -0,0 +1,311 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.WireFrame; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Class stores values, that are passed to camera methods addToDebug() and removeFromDebug(). + * + * @see alternativa.engine3d.core.Camera3D#addToDebug() + * @see alternativa.engine3d.core.Camera3D#removeFromDebug() + */ + public class Debug { + + //static public const NAMES:int = 1; + + //static public const AXES:int = 2; + + //static public const CENTERS:int = 4; + + /** + * Display of objects bound boxes. + */ + static public const BOUNDS:int = 8; + + /** + * Display of content, that is depended on object type: wireframe for Mesh, schematic display for light sources. + * Now has been implemented the support of not all classes + */ + static public const CONTENT:int = 16; + + //static public const VERTICES:int = 32; + + //static public const NORMALS:int = 64; + +// /** +// * Display of object NODES, that contains tree structure. +// */ +// static public const NODES:int = 128; + +// /** +// * Display of light sources. +// */ +// static public const LIGHTS:int = 256; + +// /** +// * Display of objects joints, that contains skeletal hierarchy. +// */ +// static public const BONES:int = 512; + + static private var boundWires:Dictionary = new Dictionary(); + + static private function createBoundWire():WireFrame { + var res:WireFrame = new WireFrame(); + res.geometry.addLine(-0.5,-0.5,-0.5, 0.5,-0.5,-0.5); + res.geometry.addLine(0.5,-0.5,-0.5, 0.5,0.5,-0.5); + res.geometry.addLine(0.5,0.5,-0.5, -0.5,0.5,-0.5); + res.geometry.addLine(-0.5,0.5,-0.5, -0.5,-0.5,-0.5); + + res.geometry.addLine(-0.5,-0.5,0.5, 0.5,-0.5,0.5); + res.geometry.addLine(0.5,-0.5,0.5, 0.5,0.5,0.5); + res.geometry.addLine(0.5,0.5,0.5, -0.5,0.5,0.5); + res.geometry.addLine(-0.5,0.5,0.5, -0.5,-0.5,0.5); + + res.geometry.addLine(-0.5,-0.5,-0.5, -0.5,-0.5,0.5); + res.geometry.addLine(0.5,-0.5,-0.5, 0.5,-0.5,0.5); + res.geometry.addLine(0.5,0.5,-0.5, 0.5,0.5,0.5); + res.geometry.addLine(-0.5,0.5,-0.5, -0.5,0.5,0.5); + return res; + } + + /** + * @private + */ + static alternativa3d function drawBoundBox(camera:Camera3D, boundBox:BoundBox, transform:Transform3D, color:int = -1):void { + var boundWire:WireFrame = boundWires[camera.context3D]; + if (boundWire == null) { + boundWire = createBoundWire(); + boundWires[camera.context3D] = boundWire; + boundWire.geometry.upload(camera.context3D); + } + boundWire.color = color >= 0 ? color : 0x99FF00; + boundWire.thickness = 1; + + boundWire.transform.compose((boundBox.minX + boundBox.maxX)*0.5, (boundBox.minY + boundBox.maxY)*0.5, (boundBox.minZ + boundBox.maxZ)*0.5, 0, 0, 0, boundBox.maxX - boundBox.minX, boundBox.maxY - boundBox.minY, boundBox.maxZ - boundBox.minZ); + boundWire.localToCameraTransform.combine(transform, boundWire.transform); + boundWire.collectDraws(camera, null, 0); + } + + + /** + * @private + */ + /*static alternativa3d function drawEdges(camera:Camera3D, canvas:Canvas, list:Face, color:int):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var t:Number; + canvas.gfx.lineStyle(0, color); + for (var face:Face = list; face != null; face = face.processNext) { + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + var x:Number = vertex.cameraX*viewSizeX*t; + var y:Number = vertex.cameraY*viewSizeY*t; + canvas.gfx.moveTo(x, y); + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX*t, vertex.cameraY*viewSizeY*t); + } + canvas.gfx.lineTo(x, y); + } + }*/ + + //static private const boundVertexList:Vertex = Vertex.createList(8); + + /** + * @private + */ + /*static alternativa3d function drawBounds(camera:Camera3D, canvas:Canvas, transformation:Object3D, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, color:int = -1, alpha:Number = 1):void { + var vertex:Vertex; + // Fill + var a:Vertex = boundVertexList; + a.x = boundMinX; + a.y = boundMinY; + a.z = boundMinZ; + var b:Vertex = a.next; + b.x = boundMaxX; + b.y = boundMinY; + b.z = boundMinZ; + var c:Vertex = b.next; + c.x = boundMinX; + c.y = boundMaxY; + c.z = boundMinZ; + var d:Vertex = c.next; + d.x = boundMaxX; + d.y = boundMaxY; + d.z = boundMinZ; + var e:Vertex = d.next; + e.x = boundMinX; + e.y = boundMinY; + e.z = boundMaxZ; + var f:Vertex = e.next; + f.x = boundMaxX; + f.y = boundMinY; + f.z = boundMaxZ; + var g:Vertex = f.next; + g.x = boundMinX; + g.y = boundMaxY; + g.z = boundMaxZ; + var h:Vertex = g.next; + h.x = boundMaxX; + h.y = boundMaxY; + h.z = boundMaxZ; + // Transformation to camera + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + if (vertex.cameraZ <= 0) return; + } + // Projection + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Rendering + canvas.gfx.lineStyle(0, (color < 0) ? ((transformation.culling > 0) ? 0xFFFF00 : 0x00FF00) : color, alpha); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + canvas.gfx.moveTo(e.cameraX, e.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.moveTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.moveTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + }*/ + + //static private const nodeVertexList:Vertex = Vertex.createList(4); + + /** + * @private + */ + /*static alternativa3d function drawKDNode(camera:Camera3D, canvas:Canvas, transformation:Object3D, axis:int, coord:Number, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, alpha:Number):void { + var vertex:Vertex; + // Fill + var a:Vertex = nodeVertexList; + var b:Vertex = a.next; + var c:Vertex = b.next; + var d:Vertex = c.next; + if (axis == 0) { + a.x = coord; + a.y = boundMinY; + a.z = boundMaxZ; + b.x = coord; + b.y = boundMaxY; + b.z = boundMaxZ; + c.x = coord; + c.y = boundMaxY; + c.z = boundMinZ; + d.x = coord; + d.y = boundMinY; + d.z = boundMinZ; + } else if (axis == 1) { + a.x = boundMaxX; + a.y = coord; + a.z = boundMaxZ; + b.x = boundMinX; + b.y = coord; + b.z = boundMaxZ; + c.x = boundMinX; + c.y = coord; + c.z = boundMinZ; + d.x = boundMaxX; + d.y = coord; + d.z = boundMinZ; + } else { + a.x = boundMinX; + a.y = boundMinY; + a.z = coord; + b.x = boundMaxX; + b.y = boundMinY; + b.z = coord; + c.x = boundMaxX; + c.y = boundMaxY; + c.z = coord; + d.x = boundMinX; + d.y = boundMaxY; + d.z = coord; + } + // Transformation to camera + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + if (vertex.cameraZ <= 0) return; + } + // Projection + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Rendering + canvas.gfx.lineStyle(0, (axis == 0) ? 0xFF0000 : ((axis == 1) ? 0x00FF00 : 0x0000FF), alpha); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + }*/ + + /** + * @private + */ + /*static alternativa3d function drawBone(canvas:Canvas, x1:Number, y1:Number, x2:Number, y2:Number, size:Number, color:int):void { + var nx:Number = x2 - x1; + var ny:Number = y2 - y1; + var nl:Number = Math.sqrt(nx*nx + ny*ny); + if (nl > 0.001) { + nx /= nl; + ny /= nl; + var lx:Number = ny*size; + var ly:Number = -nx*size; + var rx:Number = -ny*size; + var ry:Number = nx*size; + if (nl > size*2) { + nl = size; + } else { + nl = nl/2; + } + canvas.gfx.lineStyle(1, color); + canvas.gfx.beginFill(color, 0.6); + canvas.gfx.moveTo(x1, y1); + canvas.gfx.lineTo(x1 + nx*nl + lx, y1 + ny*nl + ly); + canvas.gfx.lineTo(x2, y2); + canvas.gfx.lineTo(x1 + nx*nl + rx, y1 + ny*nl + ry); + canvas.gfx.lineTo(x1, y1); + } + }*/ + + } +} diff --git a/src/alternativa/engine3d/core/DebugDrawUnit.as b/src/alternativa/engine3d/core/DebugDrawUnit.as new file mode 100644 index 0000000..b09de4e --- /dev/null +++ b/src/alternativa/engine3d/core/DebugDrawUnit.as @@ -0,0 +1,150 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.compiler.Variable; + import alternativa.engine3d.materials.compiler.VariableType; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class DebugDrawUnit extends DrawUnit { + + alternativa3d var shader:ShaderProgram; + + alternativa3d var vertexConstantsIndexes:Dictionary = new Dictionary(false); + alternativa3d var fragmentConstantsIndexes:Dictionary = new Dictionary(false); + + override alternativa3d function clear():void { + var k:*; + for (k in vertexConstantsIndexes) { + delete vertexConstantsIndexes[k]; + } + for (k in fragmentConstantsIndexes) { + delete fragmentConstantsIndexes[k]; + } + super.clear(); + } + + override alternativa3d function setVertexConstantsFromVector(firstRegister:int, data:Vector., numRegisters:int):void { + super.setVertexConstantsFromVector(firstRegister, data, numRegisters); + for (var i:int = 0; i < numRegisters; i++) { + vertexConstantsIndexes[int(firstRegister + i)] = true; + } + } + + override alternativa3d function setVertexConstantsFromNumbers(firstRegister:int, x:Number, y:Number, z:Number, w:Number = 1):void { + super.setVertexConstantsFromNumbers(firstRegister, x, y, z, w); + vertexConstantsIndexes[firstRegister] = true; + } + + override alternativa3d function setVertexConstantsFromTransform(firstRegister:int, transform:Transform3D):void { + super.setVertexConstantsFromTransform(firstRegister, transform); + vertexConstantsIndexes[firstRegister] = true; + vertexConstantsIndexes[int(firstRegister + 1)] = true; + vertexConstantsIndexes[int(firstRegister + 2)] = true; + } + + alternativa3d override function setProjectionConstants(camera:Camera3D, firstRegister:int, transform:Transform3D = null):void { + super.setProjectionConstants(camera, firstRegister, transform); + vertexConstantsIndexes[firstRegister] = true; + vertexConstantsIndexes[int(firstRegister + 1)] = true; + vertexConstantsIndexes[int(firstRegister + 2)] = true; + vertexConstantsIndexes[int(firstRegister + 3)] = true; + } + + override alternativa3d function setFragmentConstantsFromVector(firstRegister:int, data:Vector., numRegisters:int):void { + super.setFragmentConstantsFromVector(firstRegister, data, numRegisters); + for (var i:int = 0; i < numRegisters; i++) { + fragmentConstantsIndexes[int(firstRegister + i)] = true; + } + } + + override alternativa3d function setFragmentConstantsFromNumbers(firstRegister:int, x:Number, y:Number, z:Number, w:Number = 1):void { + super.setFragmentConstantsFromNumbers(firstRegister, x, y, z, w); + fragmentConstantsIndexes[firstRegister] = true; + } + + override alternativa3d function setFragmentConstantsFromTransform(firstRegister:int, transform:Transform3D):void { + super.setFragmentConstantsFromTransform(firstRegister, transform); + fragmentConstantsIndexes[firstRegister] = true; + fragmentConstantsIndexes[int(firstRegister + 1)] = true; + fragmentConstantsIndexes[int(firstRegister + 2)] = true; + } + + public function check():void { + if (object == null) throw new Error("Object not set."); + if (program == null) throw new Error("Program not set."); + if (indexBuffer == null) throw new Error("IndexBuffer not set."); + + if (shader == null) return; + var index:int; + var variable:Variable; + for each (variable in shader.vertexShader._linkedVariables) { + index = variable.index; + if (index >= 0) { + switch (variable.type) { + case VariableType.ATTRIBUTE: + if (!hasVertexBuffer(index)) { + throw new Error("VertexBuffer " + index + " with variable name " + variable.name + " not set."); + } + break; + case VariableType.CONSTANT: + if (!vertexConstantsIndexes[index]) { + throw new Error("Vertex Constant " + index + " with variable name " + variable.name + " not set."); + } + break; + } + } + } + for each (variable in shader.fragmentShader._linkedVariables) { + index = variable.index; + if (index >= 0) { + switch (variable.type) { + case VariableType.SAMPLER: + if (!hasTexture(index)) { + throw new Error("Sampler " + index + " with variable name " + variable.name + " not set."); + } + break; + case VariableType.CONSTANT: + if (!fragmentConstantsIndexes[index]) { + throw new Error("Fragment Constant " + index + " with variable name " + variable.name + " not set."); + } + break; + } + } + } + } + + private function hasVertexBuffer(index:int):Boolean { + for (var i:int = 0; i < vertexBuffersLength; i++) { + if (vertexBuffersIndexes[i] == index) { + return true; + } + } + return false; + } + private function hasTexture(index:int):Boolean { + for (var i:int = 0; i < texturesLength; i++) { + if (texturesSamplers[i] == index) { + return true; + } + } + return false; + } + + } +} diff --git a/src/alternativa/engine3d/core/DebugMaterialsRenderer.as b/src/alternativa/engine3d/core/DebugMaterialsRenderer.as new file mode 100644 index 0000000..0238a27 --- /dev/null +++ b/src/alternativa/engine3d/core/DebugMaterialsRenderer.as @@ -0,0 +1,50 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.ShaderProgram; + + import flash.display3D.IndexBuffer3D; + import flash.display3D.Program3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class DebugMaterialsRenderer extends Renderer { + + override alternativa3d function createDrawUnit(object:Object3D, program:Program3D, indexBuffer:IndexBuffer3D, firstIndex:int, numTriangles:int, debugShader:ShaderProgram = null):DrawUnit { + var res:DebugDrawUnit; + if (collector != null) { + res = DebugDrawUnit(collector); + collector = collector.next; + res.next = null; + } else { + res = new DebugDrawUnit(); + } + res.shader = debugShader; + res.object = object; + res.program = program; + res.indexBuffer = indexBuffer; + res.firstIndex = firstIndex; + res.numTriangles = numTriangles; + return res; + } + + override alternativa3d function addDrawUnit(drawUnit:DrawUnit, renderPriority:int):void { + DebugDrawUnit(drawUnit).check(); + super.addDrawUnit(drawUnit, renderPriority); + } + + } +} diff --git a/src/alternativa/engine3d/core/DrawUnit.as b/src/alternativa/engine3d/core/DrawUnit.as new file mode 100644 index 0000000..45a029d --- /dev/null +++ b/src/alternativa/engine3d/core/DrawUnit.as @@ -0,0 +1,248 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.IndexBuffer3D; + import flash.display3D.Program3D; + import flash.display3D.VertexBuffer3D; + import flash.display3D.textures.TextureBase; + + use namespace alternativa3d; + + /** + * @private + */ + public class DrawUnit { + + alternativa3d var next:DrawUnit; + + // Required parameters + alternativa3d var object:Object3D; + alternativa3d var program:Program3D; + alternativa3d var indexBuffer:IndexBuffer3D; + alternativa3d var firstIndex:int; + alternativa3d var numTriangles:int; + + // Additional parameters + alternativa3d var blendSource:String = Context3DBlendFactor.ONE; + alternativa3d var blendDestination:String = Context3DBlendFactor.ZERO; + alternativa3d var culling:String = Context3DTriangleFace.FRONT; + + // Textures + alternativa3d var textures:Vector. = new Vector.(); + alternativa3d var texturesSamplers:Vector. = new Vector.(); + alternativa3d var texturesLength:int = 0; + + // Vertex buffers + alternativa3d var vertexBuffers:Vector. = new Vector.(); + alternativa3d var vertexBuffersIndexes:Vector. = new Vector.(); + alternativa3d var vertexBuffersOffsets:Vector. = new Vector.(); + alternativa3d var vertexBuffersFormats:Vector. = new Vector.(); + alternativa3d var vertexBuffersLength:int = 0; + + // Constants + alternativa3d var vertexConstants:Vector. = new Vector.(); + alternativa3d var vertexConstantsRegistersCount:int = 0; + alternativa3d var fragmentConstants:Vector. = new Vector.(28*4, true); + alternativa3d var fragmentConstantsRegistersCount:int = 0; + + public function DrawUnit() { + } + + alternativa3d function clear():void { + object = null; + program = null; + indexBuffer = null; + blendSource = Context3DBlendFactor.ONE; + blendDestination = Context3DBlendFactor.ZERO; + culling = Context3DTriangleFace.FRONT; + textures.length = 0; + texturesLength = 0; + vertexBuffers.length = 0; + vertexBuffersLength = 0; + vertexConstantsRegistersCount = 0; + fragmentConstantsRegistersCount = 0; + } + + alternativa3d function setTextureAt(sampler:int, texture:TextureBase):void { + if (uint(sampler) > 8) throw new Error("Sampler index " + sampler + " is out of bounds."); + if (texture == null) throw new Error("Texture is null"); + texturesSamplers[texturesLength] = sampler; + textures[texturesLength] = texture; + texturesLength++; + } + + alternativa3d function setVertexBufferAt(index:int, buffer:VertexBuffer3D, bufferOffset:int, format:String):void { + if (uint(index) > 8) throw new Error("VertexBuffer index " + index + " is out of bounds."); + if (buffer == null) throw new Error("Buffer is null"); + vertexBuffersIndexes[vertexBuffersLength] = index; + vertexBuffers[vertexBuffersLength] = buffer; + vertexBuffersOffsets[vertexBuffersLength] = bufferOffset; + vertexBuffersFormats[vertexBuffersLength] = format; + vertexBuffersLength++; + } + + alternativa3d function setVertexConstantsFromVector(firstRegister:int, data:Vector., numRegisters:int):void { + if (uint(firstRegister) > (128 - numRegisters)) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + numRegisters > vertexConstantsRegistersCount) { + vertexConstantsRegistersCount = firstRegister + numRegisters; + vertexConstants.length = vertexConstantsRegistersCount << 2; + } + for (var i:int = 0, len:int = numRegisters << 2; i < len; i++) { + vertexConstants[offset] = data[i]; + offset++; + } + } + + alternativa3d function setVertexConstantsFromNumbers(firstRegister:int, x:Number, y:Number, z:Number, w:Number = 1):void { + if (uint(firstRegister) > 127) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + 1 > vertexConstantsRegistersCount) { + vertexConstantsRegistersCount = firstRegister + 1; + vertexConstants.length = vertexConstantsRegistersCount << 2; + } + vertexConstants[offset] = x; offset++; + vertexConstants[offset] = y; offset++; + vertexConstants[offset] = z; offset++; + vertexConstants[offset] = w; + } + + alternativa3d function setVertexConstantsFromTransform(firstRegister:int, transform:Transform3D):void { + if (uint(firstRegister) > 125) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + 3 > vertexConstantsRegistersCount) { + vertexConstantsRegistersCount = firstRegister + 3; + vertexConstants.length = vertexConstantsRegistersCount << 2; + } + vertexConstants[offset] = transform.a; offset++; + vertexConstants[offset] = transform.b; offset++; + vertexConstants[offset] = transform.c; offset++; + vertexConstants[offset] = transform.d; offset++; + vertexConstants[offset] = transform.e; offset++; + vertexConstants[offset] = transform.f; offset++; + vertexConstants[offset] = transform.g; offset++; + vertexConstants[offset] = transform.h; offset++; + vertexConstants[offset] = transform.i; offset++; + vertexConstants[offset] = transform.j; offset++; + vertexConstants[offset] = transform.k; offset++; + vertexConstants[offset] = transform.l; + } + + /** + * @private + */ + alternativa3d function setProjectionConstants(camera:Camera3D, firstRegister:int, transform:Transform3D = null):void { + if (uint(firstRegister) > 124) throw new Error("Register index is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + 4 > vertexConstantsRegistersCount) { + vertexConstantsRegistersCount = firstRegister + 4; + vertexConstants.length = vertexConstantsRegistersCount << 2; + } + if (transform != null) { + vertexConstants[offset] = transform.a*camera.m0; offset++; + vertexConstants[offset] = transform.b*camera.m0; offset++; + vertexConstants[offset] = transform.c*camera.m0; offset++; + vertexConstants[offset] = transform.d*camera.m0; offset++; + vertexConstants[offset] = transform.e*camera.m5; offset++; + vertexConstants[offset] = transform.f*camera.m5; offset++; + vertexConstants[offset] = transform.g*camera.m5; offset++; + vertexConstants[offset] = transform.h*camera.m5; offset++; + vertexConstants[offset] = transform.i*camera.m10; offset++; + vertexConstants[offset] = transform.j*camera.m10; offset++; + vertexConstants[offset] = transform.k*camera.m10; offset++; + vertexConstants[offset] = transform.l*camera.m10 + camera.m14; offset++; + if (!camera.orthographic) { + vertexConstants[offset] = transform.i; offset++; + vertexConstants[offset] = transform.j; offset++; + vertexConstants[offset] = transform.k; offset++; + vertexConstants[offset] = transform.l; + } else { + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 1; + } + } else { + vertexConstants[offset] = camera.m0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = camera.m5; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = camera.m10; offset++; + vertexConstants[offset] = camera.m14; offset++; + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 0; offset++; + if (!camera.orthographic) { + vertexConstants[offset] = 1; offset++; + vertexConstants[offset] = 0; + } else { + vertexConstants[offset] = 0; offset++; + vertexConstants[offset] = 1; + } + } + } + + alternativa3d function setFragmentConstantsFromVector(firstRegister:int, data:Vector., numRegisters:int):void { + if (uint(firstRegister) > (28 - numRegisters)) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + numRegisters > fragmentConstantsRegistersCount) { + fragmentConstantsRegistersCount = firstRegister + numRegisters; + } + for (var i:int = 0, len:int = numRegisters << 2; i < len; i++) { + fragmentConstants[offset] = data[i]; + offset++; + } + } + + alternativa3d function setFragmentConstantsFromNumbers(firstRegister:int, x:Number, y:Number, z:Number, w:Number = 1):void { + if (uint(firstRegister) > 27) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + 1 > fragmentConstantsRegistersCount) { + fragmentConstantsRegistersCount = firstRegister + 1; + } + fragmentConstants[offset] = x; offset++; + fragmentConstants[offset] = y; offset++; + fragmentConstants[offset] = z; offset++; + fragmentConstants[offset] = w; + } + + alternativa3d function setFragmentConstantsFromTransform(firstRegister:int, transform:Transform3D):void { + if (uint(firstRegister) > 25) throw new Error("Register index " + firstRegister + " is out of bounds."); + var offset:int = firstRegister << 2; + if (firstRegister + 3 > fragmentConstantsRegistersCount) { + fragmentConstantsRegistersCount = firstRegister + 3; + } + fragmentConstants[offset] = transform.a; offset++; + fragmentConstants[offset] = transform.b; offset++; + fragmentConstants[offset] = transform.c; offset++; + fragmentConstants[offset] = transform.d; offset++; + fragmentConstants[offset] = transform.e; offset++; + fragmentConstants[offset] = transform.f; offset++; + fragmentConstants[offset] = transform.g; offset++; + fragmentConstants[offset] = transform.h; offset++; + fragmentConstants[offset] = transform.i; offset++; + fragmentConstants[offset] = transform.j; offset++; + fragmentConstants[offset] = transform.k; offset++; + fragmentConstants[offset] = transform.l; + } + + } +} diff --git a/src/alternativa/engine3d/core/Light3D.as b/src/alternativa/engine3d/core/Light3D.as new file mode 100644 index 0000000..162802a --- /dev/null +++ b/src/alternativa/engine3d/core/Light3D.as @@ -0,0 +1,116 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.shadows.Shadow; + + use namespace alternativa3d; + + /** + * Base class for light sources. Light sources are involved in the hierarchy of 3d objects, + * have transformation and bounding boxes (BoundBox). + * Light source influences on objects, boundboxes of which intersect with boundbox of the given light source. + * + * Light3D does not meant for instantiating, use subclasses instead. + * + * @see alternativa.engine3d.core.BoundBox + */ + public class Light3D extends Object3D { + + public var shadow:Shadow; + + /** + * Color of the light. + */ + public var color:uint; + + /** + * Intensity. + */ + public var intensity:Number = 1; + + /** + * @private + */ + alternativa3d var lightToObjectTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var lightID:String; + /** + * @private + */ + alternativa3d var red:Number; + /** + * @private + */ + alternativa3d var green:Number; + /** + * @private + */ + alternativa3d var blue:Number; + + /** + * @private + */ + private static var lastLightNumber:uint = 0; + /** + * @private + */ + public function Light3D() { + lightID = "l" + lastLightNumber.toString(16); + name = "L" + (lastLightNumber++).toString(); + } + + /** + * @private + */ + override alternativa3d function calculateVisibility(camera:Camera3D):void { + if (intensity != 0 && color > 0) { + camera.lights[camera.lightsLength] = this; + camera.lightsLength++; + } + } + + /** + * @private + * Check if given object placed in field of influence of the light. + * @param targetObject Object for checking. + * @return True + */ + alternativa3d function checkBound(targetObject:Object3D):Boolean { + // this check is implemented in subclasses + return true; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Light3D = new Light3D(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:Light3D = source as Light3D; + color = src.color; + intensity = src.intensity; + } + + } +} diff --git a/src/alternativa/engine3d/core/Object3D.as b/src/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..925eade --- /dev/null +++ b/src/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,1453 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.collisions.EllipsoidCollider; + import alternativa.engine3d.core.events.Event3D; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.objects.Surface; + + import flash.events.Event; + import flash.events.EventPhase; + import flash.events.IEventDispatcher; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Dispatches when an Object3D is added as a child to another Object3D. + * Following methods generate this event: Object3D.addChild(), Object3D.addChildAt(). + * + * @see #addChild() + * @see #addChildAt() + * + * @eventType alternativa.engine3d.core.events.Event3D.ADDED + */ + [Event(name="added",type="alternativa.engine3d.core.events.Event3D")] + + /** + * Dispatched when a Object3D is about to be removed from the children list. + * Following methods generate this event: Object3D.removeChild() and Object3D.removeChildAt(). + * + * @see #removeChild() + * @see #removeChildAt() + * @eventType alternativa.engine3d.core.events.Event3D.REMOVED + */ + [Event(name="removed",type="alternativa.engine3d.core.events.Event3D")] + + /** + * Dispatched when a user presses and releases the main button + * of the user's pointing device over the same Object3D. + * Any other evens can occur between pressing and releasing the button. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when a user presses and releases the main button of + * a pointing device twice in rapid succession over the same Object3D. + * + * @eventType alternativa.engine3d.events.MouseEvent3D.DOUBLE_CLICK + */ + [Event (name="doubleClick", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when a user presses the pointing device button over an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when a user releases the pointing device button over an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when the user moves a pointing device over an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when the user moves a pointing device away from an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when the user moves a pointing device over an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OVER + */ + [Event (name="rollOver", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when the user moves a pointing device away from an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OUT + */ + [Event (name="rollOut", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when a user moves the pointing device while it is over an Object3D. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Dispatched when a mouse wheel is spun over an Object3D instance. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.core.events.MouseEvent3D")] + + /** + * Object3D class ia a base class for all 3D objects. Any Object3D has a property + * of transformation that defines its position in space, the property boundBox, + * which describes the rectangular parallelepiped into which fits this 3D object. + * The last feature of this class is the one place in the 3d hierarchy like + * DisplayObject has its own place in Display List. + * Unlike the previous version Alternativa3D, an instance of this class can contain many children, + * so it can act as a container. This also applies to all the inheritors Object3D . + * + * @see alternativa.engine3d.objects.Mesh + * @see alternativa.engine3d.core.BoundBox + */ + public class Object3D implements IEventDispatcher { + + /** + * Custom data available to store within Object3D by user. + */ + public var userData:Object; + + /** + * @private + */ + public var useShadow:Boolean = true; + + /** + * @private + */ + alternativa3d static const trm:Transform3D = new Transform3D(); + + /** + * Name of the object. + */ + public var name:String; + + /** + * Whether or not the display object is visible. + */ + public var visible:Boolean = true; + + /** + * Specifies whether this object receives mouse, or other user input, messages. + * The default value is true. + * + * The behaviour is consistent with behaviour of flash.display.InteractiveObject. + * + */ + public var mouseEnabled:Boolean = true; + + /** + * Determines whether or not the children of the object are mouse, or user input device, enabled. + * In case of false, the value of target property of the event + * will be the self Object3D wether mouse pointed on it or on its child. + * The default value is true. + */ + public var mouseChildren:Boolean = true; + + /** + * Specifies whether the object receives doubleClick events. + * The default value is false, which means that by default an Object3D + * instance does not receive doubleClick events. + * + * The doubleClickEnabled property of current stage also should be true. + */ + public var doubleClickEnabled:Boolean = false; + + /** + * A Boolean value that indicates whether the pointing hand (hand cursor) + * appears when the pointer rolls over a Object3D. + */ + public var useHandCursor:Boolean = false; + + /** + * Bounds of the object described as rectangular parallelepiped. + */ + public var boundBox:BoundBox; + + /** + * @private + */ + alternativa3d var _x:Number = 0; + + /** + * @private + */ + alternativa3d var _y:Number = 0; + + /** + * @private + */ + alternativa3d var _z:Number = 0; + + /** + * @private + */ + alternativa3d var _rotationX:Number = 0; + + /** + * @private + */ + alternativa3d var _rotationY:Number = 0; + + /** + * @private + */ + alternativa3d var _rotationZ:Number = 0; + + /** + * @private + */ + alternativa3d var _scaleX:Number = 1; + + /** + * @private + */ + alternativa3d var _scaleY:Number = 1; + + /** + * @private + */ + alternativa3d var _scaleZ:Number = 1; + + /** + * @private + */ + alternativa3d var _parent:Object3D; + + /** + * @private + */ + alternativa3d var childrenList:Object3D; + + /** + * @private + */ + alternativa3d var next:Object3D; + + /** + * @private + */ + alternativa3d var transform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var inverseTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var transformChanged:Boolean = true; + + /** + * @private + */ + alternativa3d var cameraToLocalTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var localToCameraTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var localToGlobalTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var globalToLocalTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var culling:int; + + /** + * @private + */ + alternativa3d var listening:Boolean; + + /** + * @private + */ + alternativa3d var distance:Number; + + /** + * @private + */ + alternativa3d var bubbleListeners:Object; + + /** + * @private + */ + alternativa3d var captureListeners:Object; + + /** + * @private + */ + alternativa3d var transformProcedure:Procedure; + + /** + * @private + */ + alternativa3d var deltaTransformProcedure:Procedure; + + /** + * X coordinate. + */ + public function get x():Number { + return _x; + } + + /** + * @private + */ + public function set x(value:Number):void { + if (_x != value) { + _x = value; + transformChanged = true; + } + } + + /** + * Y coordinate. + */ + public function get y():Number { + return _y; + } + + /** + * @private + */ + public function set y(value:Number):void { + if (_y != value) { + _y = value; + transformChanged = true; + } + } + + /** + * Z coordinate. + */ + public function get z():Number { + return _z; + } + + /** + * @private + */ + public function set z(value:Number):void { + if (_z != value) { + _z = value; + transformChanged = true; + } + } + + /** + * The angle of rotation of Object3D around the X-axis expressed in radians. + */ + public function get rotationX():Number { + return _rotationX; + } + + /** + * @private + */ + public function set rotationX(value:Number):void { + if (_rotationX != value) { + _rotationX = value; + transformChanged = true; + } + } + + /** + * The angle of rotation of Object3D around the Y-axis expressed in radians. + */ + public function get rotationY():Number { + return _rotationY; + } + + /** + * @private + */ + public function set rotationY(value:Number):void { + if (_rotationY != value) { + _rotationY = value; + transformChanged = true; + } + } + + /** + * The angle of rotation of Object3D around the Z-axis expressed in radians. + */ + public function get rotationZ():Number { + return _rotationZ; + } + + /** + * @private + */ + public function set rotationZ(value:Number):void { + if (_rotationZ != value) { + _rotationZ = value; + transformChanged = true; + } + } + + /** + * The scale of the Object3D along the X-axis. + */ + public function get scaleX():Number { + return _scaleX; + } + + /** + * @private + */ + public function set scaleX(value:Number):void { + if (_scaleX != value) { + _scaleX = value; + transformChanged = true; + } + } + + /** + * The scale of the Object3D along the Y-axis. + */ + public function get scaleY():Number { + return _scaleY; + } + + /** + * @private + */ + public function set scaleY(value:Number):void { + if (_scaleY != value) { + _scaleY = value; + transformChanged = true; + } + } + + /** + * The scale of the Object3D along the Z-axis. + */ + public function get scaleZ():Number { + return _scaleZ; + } + + /** + * @private + */ + public function set scaleZ(value:Number):void { + if (_scaleZ != value) { + _scaleZ = value; + transformChanged = true; + } + } + + /** + * The matrix property represents a transformation matrix that determines the position + * and orientation of an Object3D. + */ + public function get matrix():Matrix3D { + if (transformChanged) composeTransforms(); + return new Matrix3D(Vector.([transform.a, transform.e, transform.i, 0, transform.b, transform.f, transform.j, 0, transform.c, transform.g, transform.k, 0, transform.d, transform.h, transform.l, 1])); + } + + /** + * @private + */ + public function set matrix(value:Matrix3D):void { + var v:Vector. = 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; + transformChanged = true; + } + + /** + * Searches for the intersection of an Object3D and given ray, defined by origin and direction. + * + * @param origin Origin of the ray. + * @param direction Direction of the ray. + * @return The result of searching given as RayIntersectionData. null will returned in case of intersection was not found. + * @see RayIntersectionData + * @see alternativa.engine3d.objects.Sprite3D + * @see alternativa.engine3d.core.Camera3D#calculateRay() + */ + public function intersectRay(origin:Vector3D, direction:Vector3D):RayIntersectionData { + return intersectRayChildren(origin, direction); + } + + /** + * @private + */ + alternativa3d function intersectRayChildren(origin:Vector3D, direction:Vector3D):RayIntersectionData { + var minTime:Number = 1e22; + var minData:RayIntersectionData = null; + var childOrigin:Vector3D; + var childDirection:Vector3D; + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + if (childOrigin == null) { + childOrigin = new Vector3D(); + childDirection = new Vector3D(); + } + childOrigin.x = child.inverseTransform.a*origin.x + child.inverseTransform.b*origin.y + child.inverseTransform.c*origin.z + child.inverseTransform.d; + childOrigin.y = child.inverseTransform.e*origin.x + child.inverseTransform.f*origin.y + child.inverseTransform.g*origin.z + child.inverseTransform.h; + childOrigin.z = child.inverseTransform.i*origin.x + child.inverseTransform.j*origin.y + child.inverseTransform.k*origin.z + child.inverseTransform.l; + childDirection.x = child.inverseTransform.a*direction.x + child.inverseTransform.b*direction.y + child.inverseTransform.c*direction.z; + childDirection.y = child.inverseTransform.e*direction.x + child.inverseTransform.f*direction.y + child.inverseTransform.g*direction.z; + childDirection.z = child.inverseTransform.i*direction.x + child.inverseTransform.j*direction.y + child.inverseTransform.k*direction.z; + var data:RayIntersectionData = child.intersectRay(childOrigin, childDirection); + if (data != null && data.time < minTime) { + minData = data; + minTime = data.time; + } + } + return minData; + } + + /** + * A Matrix3D object representing the combined transformation matrices of the Object3D + * and all of its parent objects, back to the root level. + */ + public function get concatenatedMatrix():Matrix3D { + if (transformChanged) composeTransforms(); + trm.copy(transform); + var root:Object3D = this; + while (root.parent != null) { + root = root.parent; + if (root.transformChanged) root.composeTransforms(); + trm.append(root.transform); + } + return new Matrix3D(Vector.([trm.a, trm.e, trm.i, 0, trm.b, trm.f, trm.j, 0, trm.c, trm.g, trm.k, 0, trm.d, trm.h, trm.l, 1])); + } + + /** + * Converts the Vector3D object from the Object3D's own (local) coordinates to the root Object3D (global) coordinates. + * @param point Point in local coordinates of Object3D. + * @return Point in coordinates of root Object3D. + */ + public function localToGlobal(point:Vector3D):Vector3D { + if (transformChanged) composeTransforms(); + trm.copy(transform); + var root:Object3D = this; + while (root.parent != null) { + root = root.parent; + if (root.transformChanged) root.composeTransforms(); + trm.append(root.transform); + } + var res:Vector3D = new Vector3D(); + res.x = trm.a*point.x + trm.b*point.y + trm.c*point.z + trm.d; + res.y = trm.e*point.x + trm.f*point.y + trm.g*point.z + trm.h; + res.z = trm.i*point.x + trm.j*point.y + trm.k*point.z + trm.l; + return res; + } + + /** + * Converts the Vector3D object from the root Object3D (global) coordinates to the local Object3D's own coordinates. + * @param point Point in coordinates of root Object3D. + * @return Point in local coordinates of Object3D. + */ + public function globalToLocal(point:Vector3D):Vector3D { + if (transformChanged) composeTransforms(); + trm.copy(inverseTransform); + var root:Object3D = this; + while (root.parent != null) { + root = root.parent; + if (root.transformChanged) root.composeTransforms(); + trm.prepend(root.inverseTransform); + } + var res:Vector3D = new Vector3D(); + res.x = trm.a*point.x + trm.b*point.y + trm.c*point.z + trm.d; + res.y = trm.e*point.x + trm.f*point.y + trm.g*point.z + trm.h; + res.z = trm.i*point.x + trm.j*point.y + trm.k*point.z + trm.l; + return res; + } + + /** + * @private + */ + alternativa3d function get useLights():Boolean { + return false; + } + + /** + * Calculates object's bounds in its own coordinates + */ + public function calculateBoundBox():void { + if (boundBox != null) { + boundBox.reset(); + } else { + boundBox = new BoundBox(); + } + // Fill values of th boundBox + updateBoundBox(boundBox, null); + } + + /** + * @private + */ + alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + } + + /** + * Registers an event listener object with an EventDispatcher object + * so that the listener receives notification of an event. + * @param type The type of event. + * @param listener The listener function that processes the event. + * @param useCapture Determines whether the listener works in the capture phase or the target and bubbling phases. + * @param priority The priority level of the event listener. + * @param useWeakReference Does not used. + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { + if (listener == null) throw new TypeError("Parameter listener must be non-null."); + var listeners:Object; + if (useCapture) { + if (captureListeners == null) captureListeners = new Object(); + listeners = captureListeners; + } else { + if (bubbleListeners == null) bubbleListeners = new Object(); + listeners = bubbleListeners; + } + var vector:Vector. = listeners[type]; + if (vector == null) { + vector = new Vector.(); + listeners[type] = vector; + } + if (vector.indexOf(listener) < 0) { + vector.push(listener); + } + } + + /** + * Removes a listener from the EventDispatcher object. + * @param type The type of event. + * @param listener The listener object to remove. + * @param useCapture Specifies whether the listener was registered for the capture phase or the target and bubbling phases. + */ + public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { + if (listener == null) throw new TypeError("Parameter listener must be non-null."); + var listeners:Object = useCapture ? captureListeners : bubbleListeners; + if (listeners != null) { + var vector:Vector. = listeners[type]; + if (vector != null) { + var i:int = vector.indexOf(listener); + if (i >= 0) { + var length:int = vector.length; + for (var j:int = i + 1; j < length; j++,i++) { + vector[i] = vector[j]; + } + if (length > 1) { + vector.length = length - 1; + } else { + delete listeners[type]; + var key:*; + for (key in listeners) break; + if (!key) { + if (listeners == captureListeners) { + captureListeners = null; + } else { + bubbleListeners = null; + } + } + } + } + } + } + } + + /** + * Checks whether the EventDispatcher object has any listeners registered for a specific type of event. + * @param type The type of event. + * @return A value of true if a listener of the specified type is registered; false otherwise. + */ + public function hasEventListener(type:String):Boolean { + return captureListeners != null && captureListeners[type] || bubbleListeners != null && bubbleListeners[type]; + } + + /** + * Checks whether an event listener is registered with this EventDispatcher object or any of its ancestors for the specified event type. + * @param type The type of event. + * @return A value of true if a listener of the specified type will be triggered; false otherwise. + */ + public function willTrigger(type:String):Boolean { + for (var object:Object3D = this; object != null; object = object._parent) { + if (object.captureListeners != null && object.captureListeners[type] || object.bubbleListeners != null && object.bubbleListeners[type]) return true; + } + return false; + } + + /** + * Dispatches an event into the event flow. In case of dispatched event extends Event class, properties target and currentTarget + * will not be set. They will be set if dispatched event extends Event3D oe subclasses. + * @param event The Event object that is dispatched into the event flow. + * @return A value of true if the event was successfully dispatched. Otherwise returns false. + */ + public function dispatchEvent(event:Event):Boolean { + if (event == null) throw new TypeError("Parameter event must be non-null."); + var event3D:Event3D = event as Event3D; + if (event3D != null) { + event3D._target = this; + } + var branch:Vector. = new Vector.(); + var branchLength:int = 0; + var object:Object3D; + var i:int; + var j:int; + var length:int; + var vector:Vector.; + var functions:Vector.; + for (object = this; object != null; object = object._parent) { + branch[branchLength] = object; + branchLength++; + } + // capture phase + for (i = branchLength - 1; i > 0; i--) { + object = branch[i]; + if (event3D != null) { + event3D._currentTarget = object; + event3D._eventPhase = EventPhase.CAPTURING_PHASE; + } + + if (object.captureListeners != null) { + vector = object.captureListeners[event.type]; + if (vector != null) { + length = vector.length; + functions = new Vector.(); + for (j = 0; j < length; j++) functions[j] = vector[j]; + for (j = 0; j < length; j++) (functions[j] as Function).call(null, event); + } + } + } + if (event3D != null) { + event3D._eventPhase = EventPhase.AT_TARGET; + } + // target + bubbles phases + for (i = 0; i < branchLength; i++) { + object = branch[i]; + if (event3D != null) { + event3D._currentTarget = object; + if (i > 0) { + event3D._eventPhase = EventPhase.BUBBLING_PHASE; + } + } + if (object.bubbleListeners != null) { + vector = object.bubbleListeners[event.type]; + if (vector != null) { + length = vector.length; + functions = new Vector.(); + for (j = 0; j < length; j++) functions[j] = vector[j]; + for (j = 0; j < length; j++) (functions[j] as Function).call(null, event); + } + } + if (!event.bubbles) break; + } + return true; + } + + /** + * Object3D, to which this object was added as a child. + */ + public function get parent():Object3D { + return _parent; + } + + /** + * @private + */ + alternativa3d function removeFromParent():void { + if (_parent != null) { + _parent.removeFromList(this); + _parent = null; + } + } + + /** + * Adds given Object3D instance as a child to the end of this Object3D's children list. + * If the given object was added to another Object3D already, it removes from it's old place. + * @param child The Object3D instance to add. + * @return The Object3D instance that you pass in the child parameter. + */ + public function addChild(child:Object3D):Object3D { + // Error checking + 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:Object3D = _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.)."); + } + // Adding + if (child._parent != this) { + // Removing from old place + if (child._parent != null) child._parent.removeChild(child); + // Adding + addToList(child); + child._parent = this; + // Dispatching the event + if (child.willTrigger(Event3D.ADDED)) child.dispatchEvent(new Event3D(Event3D.ADDED, true)); + } else { + child = removeFromList(child); + if (child == null) throw new ArgumentError("Cannot add child."); + // Adding + addToList(child); + } + return child; + } + + /** + * Removes the specified child Object3D instance from the child list of the + * this Object3D instance. The parent property of the removed child is set to null. + * + * @param child The Object3D instance to remove. + * @return The Object3D instance that you pass in the child parameter. + */ + public function removeChild(child:Object3D):Object3D { + // Error checking + 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."); + child = removeFromList(child); + if (child == null) throw new ArgumentError("Cannot remove child."); + // Dispatching the event + if (child.willTrigger(Event3D.REMOVED)) child.dispatchEvent(new Event3D(Event3D.REMOVED, true)); + child._parent = null; + return child; + } + + /** + * Adds a child Object3D instance to this Object3D instance. The child is added at the index position specified. + * @param child The Object3D instance to add as a child of this Object3D instance. + * @param index The index position to which the child is added. + * @return The Object3D instance that you pass in the child parameter. + */ + public function addChildAt(child:Object3D, index:int):Object3D { + // Error checking + 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:Object3D = _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.)."); + } + // Search for element by index + 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; + } + // Adding + if (child._parent != this) { + // Removing from old parent + if (child._parent != null) child._parent.removeChild(child); + // Adding + addToList(child, current); + child._parent = this; + // Dispatching the event + if (child.willTrigger(Event3D.ADDED)) child.dispatchEvent(new Event3D(Event3D.ADDED, true)); + } else { + child = removeFromList(child); + if (child == null) throw new ArgumentError("Cannot add child."); + // Adding + addToList(child, current); + } + return child; + } + + /** + * Removes a child Object3D from the specified index position in the child list of + * the Object3D. The parent property of the removed child is set to null. + * + * @param index The child index of the Object3D to remove. + * @return The Object3D instance that was removed. + */ + public function removeChildAt(index:int):Object3D { + // Error checking + if (index < 0) throw new RangeError("The supplied index is out of bounds."); + // Search for element by index + var child:Object3D = childrenList; + for (var i:int = 0; i < index; i++) { + if (child == null) throw new RangeError("The supplied index is out of bounds."); + child = child.next; + } + if (child == null) throw new RangeError("The supplied index is out of bounds."); + // Removing + removeFromList(child); + // Dispatching the event + if (child.willTrigger(Event3D.REMOVED)) child.dispatchEvent(new Event3D(Event3D.REMOVED, true)); + child._parent = null; + return child; + } + + /** + * Removes child objects in given range of indexes. + * @param beginIndex Index, starts from which objects should be removed. + * @param endIndex Index, till which objects should be removed. + */ + public function removeChildren(beginIndex:int = 0, endIndex:int = 2147483647):void { + // Error checking + if (beginIndex < 0) throw new RangeError("The supplied index is out of bounds."); + if (endIndex < beginIndex) throw new RangeError("The supplied index is out of bounds."); + var i:int = 0; + var prev:Object3D = null; + var begin:Object3D = childrenList; + while (i < beginIndex) { + if (begin == null) { + if (endIndex < 2147483647) { + throw new RangeError("The supplied index is out of bounds."); + } else { + return; + } + } + prev = begin; + begin = begin.next; + i++; + } + if (begin == null) { + if (endIndex < 2147483647) { + throw new RangeError("The supplied index is out of bounds."); + } else { + return; + } + } + var end:Object3D = null; + if (endIndex < 2147483647) { + end = begin; + while (i <= endIndex) { + if (end == null) throw new RangeError("The supplied index is out of bounds."); + end = end.next; + i++; + } + } + if (prev != null) { + prev.next = end; + } else { + childrenList = end; + } + // Removing + while (begin != end) { + var next:Object3D = begin.next; + begin.next = null; + if (begin.willTrigger(Event3D.REMOVED)) begin.dispatchEvent(new Event3D(Event3D.REMOVED, true)); + begin._parent = null; + begin = next; + } + } + + /** + * Returns the child Object3D instance that exists at the specified index. + * @param index Position of wished child. + * @return Child object at given position. + */ + public function getChildAt(index:int):Object3D { + // Error checking + if (index < 0) throw new RangeError("The supplied index is out of bounds."); + // Search for element by index + 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; + } + + /** + * Returns index of given child Object3D instance. + * @param child Child Object3D instance. + * @return Index of given child Object3D instance. + */ + public function getChildIndex(child:Object3D):int { + // Error checking + 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."); + // Search for index + 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."); + } + + /** + * Sets index for child Object3D instance. + * @param child Child Object3D instance. + * @param index Index should be set. + */ + public function setChildIndex(child:Object3D, index:int):void { + // Error checking + 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."); + // Search for element by index + 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; + } + // Removing + child = removeFromList(child); + if (child == null) throw new ArgumentError("Cannot set child index."); + // Adding + addToList(child, current); + } + + /** + * Swaps index positions of two specified child objects. + * @param child1 The first object to swap. + * @param child2 The second object to swap. + */ + public function swapChildren(child1:Object3D, child2:Object3D):void { + // Error checking + 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."); + // Swapping + if (child1 != child2) { + if (child1.next == child2) { + child2 = removeFromList(child2); + if (child2 == null) throw new ArgumentError("Cannot swap children."); + addToList(child2, child1); + } else if (child2.next == child1) { + child1 = removeFromList(child1); + if (child1 == null) throw new ArgumentError("Cannot swap children."); + addToList(child1, child2); + } else { + var count:int = 0; + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child == child1) count++; + if (child == child2) count++; + if (count == 2) break; + } + if (count < 2) throw new ArgumentError("Cannot swap children."); + var nxt:Object3D = child1.next; + removeFromList(child1); + addToList(child1, child2); + removeFromList(child2); + addToList(child2, nxt); + } + } + } + + /** + * Swaps index positions of two child objects by its index. + * @param index1 Index of the first object to swap. + * @param index2 Index of the second object to swap. + */ + public function swapChildrenAt(index1:int, index2:int):void { + // Error checking + if (index1 < 0 || index2 < 0) throw new RangeError("The supplied index is out of bounds."); + // Swapping + if (index1 != index2) { + // Search for element by index + 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) { + removeFromList(child2); + addToList(child2, child1); + } else if (child2.next == child1) { + removeFromList(child1); + addToList(child1, child2); + } else { + var nxt:Object3D = child1.next; + removeFromList(child1); + addToList(child1, child2); + removeFromList(child2); + addToList(child2, nxt); + } + } + } + } + + /** + * Returns child Object3D instance with given name. + * In case of there are several objects with same name, the first of them will returned. + * If there are no objects with given name, null will returned. + * + * @param name The name of child object. + * @return Child Object3D with given name. + */ + public function getChildByName(name:String):Object3D { + // Error checking + if (name == null) throw new TypeError("Parameter name must be non-null."); + // Search for object + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.name == name) return child; + } + return null; + } + + /** + * Check if given object is child of this Object3D. + * @param child Child Object3D instance. + * @return true if given instance is this Object3D or one of its children or false otherwise. + */ + public function contains(child:Object3D):Boolean { + // Error checking + if (child == null) throw new TypeError("Parameter child must be non-null."); + // Search for object + if (child == this) return true; + for (var object:Object3D = childrenList; object != null; object = object.next) { + if (object.contains(child)) return true; + } + return false; + } + + /** + * Returns the number of children of this object. + */ + public function get numChildren():int { + var num:int = 0; + for (var current:Object3D = childrenList; current != null; current = current.next) num++; + return num; + } + + private function addToList(child:Object3D, item:Object3D = null):void { + child.next = item; + if (item == childrenList) { + childrenList = child; + } else { + for (var current:Object3D = childrenList; current != null; current = current.next) { + if (current.next == item) { + current.next = child; + break; + } + } + } + } + + /** + * @private + */ + alternativa3d function removeFromList(child:Object3D):Object3D { + var prev:Object3D; + for (var current:Object3D = childrenList; current != null; current = current.next) { + if (current == child) { + if (prev != null) { + prev.next = current.next; + } else { + childrenList = current.next; + } + current.next = null; + return child; + } + prev = current; + } + return null; + } + + /** + * Gather the resources of this Object3D. This resources should be uploaded in the Context3D in order to Object3D can be rendered. + * + * @param hierarchy If true, the resources of all children will be gathered too. + * @param resourceType If defined, only resources of this type will be gathered. + * @return Vector consists of gathered resources + * @see flash.display.Stage3D + */ + public function getResources(hierarchy:Boolean = false, resourceType:Class = null):Vector. { + var res:Vector. = new Vector.(); + var dict:Dictionary = new Dictionary(); + var count:int = 0; + fillResources(dict, hierarchy, resourceType); + for (var key:* in dict) { + res[count++] = key as Resource; + } + return res; + } + + /** + * @private + */ + alternativa3d function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (hierarchy) { + for (var child:Object3D = childrenList; child != null; child = child.next) { + child.fillResources(resources, hierarchy, resourceType); + } + } + } + + /** + * @private + */ + alternativa3d function composeTransforms():void { + // Matrix + 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; + transform.a = cosZ*cosYscaleX; + transform.b = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + transform.c = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + transform.d = _x; + transform.e = sinZ*cosYscaleX; + transform.f = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + transform.g = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + transform.h = _y; + transform.i = -sinY*_scaleX; + transform.j = cosY*sinXscaleY; + transform.k = cosY*cosXscaleZ; + transform.l = _z; + // Inverse matrix + var sinXsinY:Number = sinX*sinY; + cosYscaleX = cosY/_scaleX; + cosXscaleY = cosX/_scaleY; + sinXscaleZ = -sinX/_scaleZ; + cosXscaleZ = cosX/_scaleZ; + inverseTransform.a = cosZ*cosYscaleX; + inverseTransform.b = sinZ*cosYscaleX; + inverseTransform.c = -sinY/_scaleX; + inverseTransform.d = -inverseTransform.a*_x - inverseTransform.b*_y - inverseTransform.c*_z; + inverseTransform.e = sinXsinY*cosZ/_scaleY - sinZ*cosXscaleY; + inverseTransform.f = cosZ*cosXscaleY + sinXsinY*sinZ/_scaleY; + inverseTransform.g = sinX*cosY/_scaleY; + inverseTransform.h = -inverseTransform.e*_x - inverseTransform.f*_y - inverseTransform.g*_z; + inverseTransform.i = cosZ*sinY*cosXscaleZ - sinZ*sinXscaleZ; + inverseTransform.j = cosZ*sinXscaleZ + sinY*sinZ*cosXscaleZ; + inverseTransform.k = cosY*cosXscaleZ; + inverseTransform.l = -inverseTransform.i*_x - inverseTransform.j*_y - inverseTransform.k*_z; + transformChanged = false; + } + + /** + * @private + */ + alternativa3d function calculateVisibility(camera:Camera3D):void { + } + + /** + * @private + */ + alternativa3d function calculateChildrenVisibility(camera:Camera3D):void { + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.visible) { + // Compose matrix and inverse matrix + if (child.transformChanged) child.composeTransforms(); + // Calculating matrix for converting from camera coordinates to local coordinates + child.cameraToLocalTransform.combine(child.inverseTransform, cameraToLocalTransform); + // Calculating matrix for converting from local coordinates to camera coordinates + child.localToCameraTransform.combine(localToCameraTransform, child.transform); + // Culling checking + if (child.boundBox != null) { + camera.calculateFrustum(child.cameraToLocalTransform); + child.culling = child.boundBox.checkFrustumCulling(camera.frustum, 63); + } else { + child.culling = 63; + } + // Calculating visibility of the self content + if (child.culling >= 0) child.calculateVisibility(camera); + // Calculating visibility of children + if (child.childrenList != null) child.calculateChildrenVisibility(camera); + } + } + } + + /** + * @private + */ + alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + } + + /** + * @private + */ + alternativa3d function collectChildrenDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + var i:int; + var light:Light3D; + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.visible) { + // Check getting in frustum and occluding + if (child.culling >= 0 && (child.boundBox == null || camera.occludersLength == 0 || !child.boundBox.checkOcclusion(camera.occluders, camera.occludersLength, child.localToCameraTransform))) { + // Check if the ray crossing the bounding box + if (child.boundBox != null) { + camera.calculateRays(child.cameraToLocalTransform); + child.listening = child.boundBox.checkRays(camera.origins, camera.directions, camera.raysLength); + } else { + child.listening = true; + } + // Check if object needs in lightning + if (lightsLength > 0 && child.useLights) { + // Pass the lights to children and calculate appropriate transformations + if (child.boundBox != null) { + var childLightsLength:int = 0; + for (i = 0; i < lightsLength; i++) { + light = lights[i]; + light.lightToObjectTransform.combine(child.cameraToLocalTransform, light.localToCameraTransform); + // Detect influence + if (light.boundBox == null || light.checkBound(child)) { + camera.childLights[childLightsLength] = light; + childLightsLength++; + } + } + child.collectDraws(camera, camera.childLights, childLightsLength); + } else { + // Calculate transformation from light space to object space + for (i = 0; i < lightsLength; i++) { + light = lights[i]; + light.lightToObjectTransform.combine(child.cameraToLocalTransform, light.localToCameraTransform); + } + child.collectDraws(camera, lights, lightsLength); + } + } else { + child.collectDraws(camera, null, 0); + } + // Debug the boundbox + if (camera.debug && child.boundBox != null && (camera.checkInDebug(child) & Debug.BOUNDS)) Debug.drawBoundBox(camera, child.boundBox, child.localToCameraTransform); + } + // Gather the draws for children + if (child.childrenList != null) child.collectChildrenDraws(camera, lights, lightsLength); + } + } + } + + /** + * @private + */ + alternativa3d function collectGeometry(collider:EllipsoidCollider, excludedObjects:Dictionary):void { + } + + /** + * @private + */ + alternativa3d function collectChildrenGeometry(collider:EllipsoidCollider, excludedObjects:Dictionary):void { + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (excludedObjects == null || !excludedObjects[child]) { + // Compose matrix and inverse matrix if it needed + if (child.transformChanged) child.composeTransforms(); + // Calculating matrix for converting from collider coordinates to local coordinates + child.globalToLocalTransform.combine(child.inverseTransform, globalToLocalTransform); + // Check boundbox intersecting + var intersects:Boolean = true; + if (child.boundBox != null) { + collider.calculateSphere(child.globalToLocalTransform); + intersects = child.boundBox.checkSphere(collider.sphere); + } + // Adding the geometry of self content + if (intersects) { + // Calculating matrix for converting from local coordinates to callider coordinates + child.localToGlobalTransform.combine(localToGlobalTransform, child.transform); + child.collectGeometry(collider, excludedObjects); + } + // Check for children + if (child.childrenList != null) child.collectChildrenGeometry(collider, excludedObjects); + } + } + } + + /** + * @private + */ + alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + } + + + /** + * Returns a copy of this Object3D. + * @return A copy of this Object3D. + */ + public function clone():Object3D { + var res:Object3D = new Object3D(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * Copies basic properties of Object3D. This method calls from clone() method. + * @param source Object3D, properties of which will be copied. + */ + protected function clonePropertiesFrom(source:Object3D):void { + userData = source.userData; + + name = source.name; + visible = source.visible; + mouseEnabled = source.mouseEnabled; + mouseChildren = source.mouseChildren; + doubleClickEnabled = source.doubleClickEnabled; + useHandCursor = source.useHandCursor; + boundBox = source.boundBox ? source.boundBox.clone() : null; + _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; + for (var child:Object3D = source.childrenList, lastChild:Object3D; child != null; child = child.next) { + var newChild:Object3D = child.clone(); + if (childrenList != null) { + lastChild.next = newChild; + } else { + childrenList = newChild; + } + lastChild = newChild; + newChild._parent = this; + } + } + + /** + * Returns the string representation of the specified object. + * @return The string representation of the specified object. + */ + public function toString():String { + var className:String = getQualifiedClassName(this); + return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]"; + } + + } +} diff --git a/src/alternativa/engine3d/core/Occluder.as b/src/alternativa/engine3d/core/Occluder.as new file mode 100644 index 0000000..335f03f --- /dev/null +++ b/src/alternativa/engine3d/core/Occluder.as @@ -0,0 +1,1386 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.WireFrame; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Polygonal Object3D meant for excluding from the rendering process those objects, which it shields from the camera. + * The Occluder has no visual representation and does not be render. + * The geometry should be a convex polygon. + */ + public class Occluder extends Object3D { + + private var faceList:Face; + + private var edgeList:Edge; + + private var vertexList:Vertex; + + private var debugWire:WireFrame; + + /** + * @private + */ + alternativa3d var planeList:CullingPlane; + + /** + * @private + */ + alternativa3d var enabled:Boolean; + + /** + * Minimal ratio of overlap area of viewport by occluder to viewport area. + * This property can has value from 0 to 1. + */ + public var minSize:Number = 0; + + /** + * Creates form of overlap on base of re-created geometry. + * Geometry must be solid, closed and convex. + * @param geometry passed Geometry + * @param distanceThreshold Accuracy, within which the coordinates of the vertices are the same. + * @param weldTriangles If true, then related triangles, that lie in one plane, will be united in one polygon. + * @param angleThreshold Permissible angle in radians between normals, that allows to unite faces in one plane. + * @param convexThreshold Value, that decrease allowable angle between related edges of united faces. + * @see #destroyForm() + */ + public function createForm(geometry:Geometry, distanceThreshold:Number = 0, weldTriangles:Boolean = true, angleThreshold:Number = 0, convexThreshold:Number = 0):void { + destroyForm(); + // Checking for the errors + var geometryIndicesLength:int = geometry._indices.length; + if (geometry._numVertices == 0 || geometryIndicesLength == 0) throw new Error("The supplied geometry is empty."); + var vBuffer:VertexStream = (VertexAttributes.POSITION < geometry._attributesStreams.length) ? geometry._attributesStreams[VertexAttributes.POSITION] : null; + if (vBuffer == null) throw new Error("The supplied geometry is empty."); + var i:int; + // Create vertices + var vertices:Vector. = new Vector.(); + var attributesOffset:int = geometry._attributesOffsets[VertexAttributes.POSITION]; + var numMappings:int = vBuffer.attributes.length; + var data:ByteArray = vBuffer.data; + for (i = 0; i < geometry._numVertices; i++) { + data.position = 4*(numMappings*i + attributesOffset); + var vertex:Vertex = new Vertex(); + vertex.x = data.readFloat(); + vertex.y = data.readFloat(); + vertex.z = data.readFloat(); + vertices[i] = vertex; + } + // Create faces + for (i = 0; i < geometryIndicesLength;) { + var a:int = geometry._indices[i]; i++; + var b:int = geometry._indices[i]; i++; + var c:int = geometry._indices[i]; i++; + var face:Face = new Face(); + face.wrapper = new Wrapper(); + face.wrapper.vertex = vertices[a]; + face.wrapper.next = new Wrapper(); + face.wrapper.next.vertex = vertices[b]; + face.wrapper.next.next = new Wrapper(); + face.wrapper.next.next.vertex = vertices[c]; + face.calculateBestSequenceAndNormal(); + face.next = faceList; + faceList = face; + } + // Unite vertices + vertexList = weldVertices(vertices, distanceThreshold); + // Unite faces + if (weldTriangles) weldFaces(angleThreshold, convexThreshold); + // Calculation of edges and checking for the validity + var error:String = calculateEdges(); + if (error != null) { + destroyForm(); + throw new ArgumentError(error); + } + calculateBoundBox(); + } + + /** + * Destroys form of overlap. + * @see #createForm() + */ + public function destroyForm():void { + faceList = null; + edgeList = null; + vertexList = null; + if (debugWire != null) { + debugWire.geometry.dispose(); + debugWire = null; + } + } + + /** + * @private + */ + override alternativa3d function calculateVisibility(camera:Camera3D):void { + camera.occluders[camera.occludersLength] = this; + camera.occludersLength++; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + // Debug + if (camera.debug) { + if (camera.checkInDebug(this) & Debug.CONTENT) { + if (debugWire == null) { + debugWire = new WireFrame(0xFF00FF, 1, 2); + for (var edge:Edge = edgeList; edge != null; edge = edge.next) { + debugWire.geometry.addLine(edge.a.x, edge.a.y, edge.a.z, edge.b.x, edge.b.y, edge.b.z); + } + debugWire.geometry.upload(camera.context3D); + } + debugWire.localToCameraTransform.copy(localToCameraTransform); + debugWire.collectDraws(camera, null, 0); + } + } + } + + private function calculateEdges():String { + var face:Face; + var wrapper:Wrapper; + var edge:Edge; + // Create edges + for (face = faceList; face != null; face = face.next) { + // Loop of edge segments + var a:Vertex; + var b:Vertex; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next, a = b) { + a = wrapper.vertex; + b = (wrapper.next != null) ? wrapper.next.vertex : face.wrapper.vertex; + // Loop of created edges + for (edge = edgeList; edge != null; edge = edge.next) { + // If geometry is incorrect + if (edge.a == a && edge.b == b) { + return "The supplied geometry is not valid."; + } + // If found created edges with these vertices + if (edge.a == b && edge.b == a) break; + } + if (edge != null) { + edge.right = face; + } else { + edge = new Edge(); + edge.a = a; + edge.b = b; + edge.left = face; + edge.next = edgeList; + edgeList = edge; + } + } + } + // Checking for the validity + for (edge = edgeList; edge != null; edge = edge.next) { + // If edge consists of one face + if (edge.left == null || edge.right == null) { + return "The supplied geometry is non whole."; + } + var abx:Number = edge.b.x - edge.a.x; + var aby:Number = edge.b.y - edge.a.y; + var abz:Number = edge.b.z - edge.a.z; + var crx:Number = edge.right.normalZ*edge.left.normalY - edge.right.normalY*edge.left.normalZ; + var cry:Number = edge.right.normalX*edge.left.normalZ - edge.right.normalZ*edge.left.normalX; + var crz:Number = edge.right.normalY*edge.left.normalX - edge.right.normalX*edge.left.normalY; + // If bend inside + if (abx*crx + aby*cry + abz*crz < 0) { + //return "The supplied geometry is non convex."; + trace("Warning: " + this + ": geometry is non convex."); + } + } + return null; + } + + private function weldVertices(vertices:Vector., distanceThreshold:Number):Vertex { + var vertex:Vertex; + var verticesLength:int = vertices.length; + // Group + group(vertices, 0, verticesLength, 0, distanceThreshold, new Vector.()); + // Change vertices + for (var face:Face = faceList; face != null; face = face.next) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + if (wrapper.vertex.value != null) { + wrapper.vertex = wrapper.vertex.value; + } + } + } + // Create new list of vertices + var res:Vertex; + for (var i:int = 0; i < verticesLength; i++) { + vertex = vertices[i]; + if (vertex.value == null) { + vertex.next = res; + res = vertex; + } + } + return res; + } + + private function group(verts:Vector., begin:int, end:int, depth:int, threshold:Number, stack:Vector.):void { + var i:int; + var j:int; + var vertex:Vertex; + var threshold:Number; + switch (depth) { + case 0: // x + for (i = begin; i < end; i++) { + vertex = verts[i]; + vertex.offset = vertex.x; + } + break; + case 1: // y + for (i = begin; i < end; i++) { + vertex = verts[i]; + vertex.offset = vertex.y; + } + break; + case 2: // z + for (i = begin; i < end; i++) { + vertex = verts[i]; + vertex.offset = vertex.z; + } + break; + } + // Sorting + stack[0] = begin; + stack[1] = end - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + vertex = verts[(r + l) >> 1]; + var median:Number = vertex.offset; + while (i <= j) { + var left:Vertex = verts[i]; + while (left.offset > median) { + i++; + left = verts[i]; + } + var right:Vertex = verts[j]; + while (right.offset < median) { + j--; + right = verts[j]; + } + if (i <= j) { + verts[i] = right; + verts[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Divide on groups further + i = begin; + vertex = verts[i]; + var compared:Vertex; + for (j = i + 1; j <= end; j++) { + if (j < end) compared = verts[j]; + if (j == end || vertex.offset - compared.offset > threshold) { + if (depth < 2 && j - i > 1) { + group(verts, i, j, depth + 1, threshold, stack); + } + if (j < end) { + i = j; + vertex = verts[i]; + } + } else if (depth == 2) { + compared.value = vertex; + } + } + } + + private function weldFaces(angleThreshold:Number = 0, convexThreshold:Number = 0):void { + var i:int; + var j:int; + var key:*; + var sibling:Face; + var face:Face; + var next:Face; + var wp:Wrapper; + var sp:Wrapper; + var w:Wrapper; + var s:Wrapper; + var wn:Wrapper; + var sn:Wrapper; + var wm:Wrapper; + var sm:Wrapper; + var vertex:Vertex; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var abx:Number; + var aby:Number; + var abz:Number; + var acx:Number; + var acy:Number; + var acz:Number; + var nx:Number; + var ny:Number; + var nz:Number; + var nl:Number; + var dictionary:Dictionary; + // Accuracy + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + // Faces + var faceSet:Dictionary = new Dictionary(); + // Map of matching vertex:faces(dictionary) + var map:Dictionary = new Dictionary(); + for (face = faceList; face != null; face = next) { + next = face.next; + face.next = null; + faceSet[face] = true; + for (wn = face.wrapper; wn != null; wn = wn.next) { + vertex = wn.vertex; + dictionary = map[vertex]; + if (dictionary == null) { + dictionary = new Dictionary(); + map[vertex] = dictionary; + } + dictionary[face] = true; + } + } + faceList = null; + // Island + var island:Vector. = new Vector.(); + // Neighbors of current edge + var siblings:Dictionary = new Dictionary(); + // Edges, that are not included to current island + var unfit:Dictionary = new Dictionary(); + while (true) { + // Get of first face + face = null; + for (key in faceSet) { + face = key; + delete faceSet[key]; + break; + } + if (face == null) break; + // Create island + var num:int = 0; + island[num] = face; + num++; + nx = face.normalX; + ny = face.normalY; + nz = face.normalZ; + for (key in unfit) { + delete unfit[key]; + } + for (i = 0; i < num; i++) { + face = island[i]; + for (key in siblings) { + delete siblings[key]; + } + // Collect potential neighbors of face + for (w = face.wrapper; w != null; w = w.next) { + for (key in map[w.vertex]) { + if (faceSet[key] && !unfit[key]) { + siblings[key] = true; + } + } + } + for (key in siblings) { + sibling = key; + // If they match along the normals + if (nx*sibling.normalX + ny*sibling.normalY + nz*sibling.normalZ >= angleThreshold) { + // Checking on the neighborhood + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // Add to island + if (w != null) { + island[num] = sibling; + num++; + delete faceSet[sibling]; + } + } else { + unfit[sibling] = true; + } + } + } + // If island has one face + if (num == 1) { + face = island[0]; + face.next = faceList; + faceList = face; + // Unite of island + } else { + while (true) { + var weld:Boolean = false; + // Loop of island faces + for (i = 0; i < num - 1; i++) { + face = island[i]; + if (face != null) { + // Try to unite current faces with others + for (j = 1; j < num; j++) { + sibling = island[j]; + if (sibling != null) { + // Search for the common face + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // If faces is not found + if (w != null) { + // Expansion of union faces + while (true) { + wm = (wn.next != null) ? wn.next : face.wrapper; + //for (sp = sibling.wrapper; sp.next != s && sp.next != null; sp = sp.next); + sp = sibling.wrapper; + while (sp.next != s && sp.next != null) sp = sp.next; + if (wm.vertex == sp.vertex) { + wn = wm; + s = sp; + } else break; + } + while (true) { + //for (wp = face.wrapper; wp.next != w && wp.next != null; wp = wp.next); + wp = face.wrapper; + while (wp.next != w && wp.next != null) wp = wp.next; + sm = (sn.next != null) ? sn.next : sibling.wrapper; + if (wp.vertex == sm.vertex) { + w = wp; + sn = sm; + } else break; + } + // First bend + a = w.vertex; + b = sm.vertex; + c = wp.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; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Second bend + a = s.vertex; + b = wm.vertex; + c = sp.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; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Unite + weld = true; + var newFace:Face = new Face(); + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + wm = null; + for (; wn != w; wn = (wn.next != null) ? wn.next : face.wrapper) { + sm = new Wrapper(); + sm.vertex = wn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + for (; sn != s; sn = (sn.next != null) ? sn.next : sibling.wrapper) { + sm = new Wrapper(); + sm.vertex = sn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + island[i] = newFace; + island[j] = null; + face = newFace; + // Если, то собираться будет парами, иначе к одной прицепляется максимально (это чуть быстрее) + //if (pairWeld) break; + + } + } + } + } + } + if (!weld) break; + } + // Collect of united faces + for (i = 0; i < num; i++) { + face = island[i]; + if (face != null) { + // Calculate the best sequence of vertices + face.calculateBestSequenceAndNormal(); + // Add + face.next = faceList; + faceList = face; + } + } + } + } + } + + /** + * @private + */ + alternativa3d function transformVertices(correctionX:Number, correctionY:Number):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = (localToCameraTransform.a*vertex.x + localToCameraTransform.b*vertex.y + localToCameraTransform.c*vertex.z + localToCameraTransform.d)/correctionX; + vertex.cameraY = (localToCameraTransform.e*vertex.x + localToCameraTransform.f*vertex.y + localToCameraTransform.g*vertex.z + localToCameraTransform.h)/correctionY; + vertex.cameraZ = localToCameraTransform.i*vertex.x + localToCameraTransform.j*vertex.y + localToCameraTransform.k*vertex.z + localToCameraTransform.l; + } + } + + /** + * @private + */ + alternativa3d function checkOcclusion(occluder:Occluder, correctionX:Number, correctionY:Number):Boolean { + for (var plane:CullingPlane = occluder.planeList; plane != null; plane = plane.next) { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (plane.x*vertex.cameraX*correctionX + plane.y*vertex.cameraY*correctionY + plane.z*vertex.cameraZ > plane.offset) return false; + } + } + return true; + } + + /** + * @private + */ + alternativa3d function calculatePlanes(camera:Camera3D):void { + var a:Vertex; + var b:Vertex; + var c:Vertex; + var face:Face; + var plane:CullingPlane; + // Clear of planes + if (planeList != null) { + plane = planeList; + while (plane.next != null) plane = plane.next; + plane.next = CullingPlane.collector; + CullingPlane.collector = planeList; + planeList = null; + } + if (faceList == null || edgeList == null) return; + // Visibility of faces + if (!camera.orthographic) { + var cameraInside:Boolean = true; + for (face = faceList; face != null; face = face.next) { + if (face.normalX*cameraToLocalTransform.d + face.normalY*cameraToLocalTransform.h + face.normalZ*cameraToLocalTransform.l > face.offset) { + face.visible = true; + cameraInside = false; + } else { + face.visible = false; + } + } + if (cameraInside) return; + } else { + for (a = vertexList; a != null; a = a.next) if (a.cameraZ < camera.nearClipping) return; + for (face = faceList; face != null; face = face.next) { + face.visible = face.normalX*cameraToLocalTransform.c + face.normalY*cameraToLocalTransform.g + face.normalZ*cameraToLocalTransform.k < 0; + } + } + // Create planes by contour + var viewSizeX:Number = camera.view._width*0.5; + var viewSizeY:Number = camera.view._width*0.5; + var right:Number = viewSizeX/camera.correctionX; + var left:Number = -right; + var bottom:Number = viewSizeY/camera.correctionY; + var top:Number = -bottom; + var t:Number; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var ox:Number; + var oy:Number; + var lineList:CullingPlane = null; + var square:Number = 0; + var viewSquare:Number = viewSizeX*viewSizeY*4*2; + var occludeAll:Boolean = true; + for (var edge:Edge = edgeList; edge != null; edge = edge.next) { + // If face is into the contour + if (edge.left.visible != edge.right.visible) { + // Define the direction (counterclockwise) + if (edge.left.visible) { + a = edge.a; + b = edge.b; + } else { + a = edge.b; + b = edge.a; + } + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + bx = b.cameraX; + by = b.cameraY; + bz = b.cameraZ; + // Clipping + if (culling > 3) { + if (!camera.orthographic) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + // Orthographic mode + } else { + if (ax <= left && bx <= left) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bx > left && ax <= left) { + t = (left - ax)/(bx - ax); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bx <= left && ax > left) { + t = (left - ax)/(bx - ax); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ax >= right && bx >= right) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bx < right && ax >= right) { + t = (right - ax)/(bx - ax); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bx >= right && ax < right) { + t = (right - ax)/(bx - ax); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ay <= top && by <= top) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (by > top && ay <= top) { + t = (top - ay)/(by - ay); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (by <= top && ay > top) { + t = (top - ay)/(by - ay); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ay >= bottom && by >= bottom) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (by < bottom && ay >= bottom) { + t = (bottom - ay)/(by - ay); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (by >= bottom && ay < bottom) { + t = (bottom - ay)/(by - ay); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + } + occludeAll = false; + } + // Create plane by edge + plane = CullingPlane.create(); + plane.next = planeList; + planeList = plane; + if (!camera.orthographic) { + plane.x = (b.cameraZ*a.cameraY - b.cameraY*a.cameraZ)*camera.correctionY; + plane.y = (b.cameraX*a.cameraZ - b.cameraZ*a.cameraX)*camera.correctionX; + plane.z = (b.cameraY*a.cameraX - b.cameraX*a.cameraY)*camera.correctionX*camera.correctionY; + plane.offset = 0; + if (minSize > 0 && square/viewSquare < minSize) { + ax = ax*viewSizeX/az; + ay = ay*viewSizeY/az; + bx = bx*viewSizeX/bz; + by = by*viewSizeY/bz; + if (planeList.next == null) { + ox = ax; + oy = ay; + } + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + plane = plane.create(); + plane.x = ay - by; + plane.y = bx - ax; + plane.offset = plane.x*ax + plane.y*ay; + plane.next = lineList; + lineList = plane; + } + } else { + plane.x = (a.cameraY - b.cameraY)*camera.correctionY; + plane.y = (b.cameraX - a.cameraX)*camera.correctionX; + plane.z = 0; + plane.offset = plane.x*a.cameraX*camera.correctionX + plane.y*a.cameraY*camera.correctionY; + if (minSize > 0 && square/viewSquare < minSize) { + ax = ax*camera.correctionX; + ay = ay*camera.correctionY; + bx = bx*camera.correctionX; + by = by*camera.correctionY; + if (planeList.next == null) { + ox = ax; + oy = ay; + } + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + plane = plane.create(); + plane.x = ay - by; + plane.y = bx - ax; + plane.offset = plane.x*ax + plane.y*ay; + plane.next = lineList; + lineList = plane; + } + } + } + } + if (planeList == null && !occludeAll) return; + // Checking size on the display + if (planeList != null && minSize > 0 && square/viewSquare < minSize && (culling <= 3 || !checkSquare(lineList, ox, oy, square, viewSquare, viewSizeX, viewSizeY))) { + plane = planeList; + while (plane.next != null) plane = plane.next; + plane.next = CullingPlane.collector; + CullingPlane.collector = planeList; + planeList = null; + if (lineList != null) { + plane = lineList; + while (plane.next != null) plane = plane.next; + plane.next = CullingPlane.collector; + CullingPlane.collector = lineList; + } + return; + } else if (lineList != null) { + plane = lineList; + while (plane.next != null) plane = plane.next; + plane.next = CullingPlane.collector; + CullingPlane.collector = lineList; + } + // Create planes by faces. + for (face = faceList; face != null; face = face.next) { + if (!face.visible) continue; + if (culling > 3) { + occludeAll = true; + var wrapper:Wrapper; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + a = wrapper.vertex; + b = (wrapper.next != null) ? wrapper.next.vertex : face.wrapper.vertex; + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + bx = b.cameraX; + by = b.cameraY; + bz = b.cameraZ; + if (!camera.orthographic) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + // Orthographic mode + } else { + if (ax <= left && bx <= left) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bx > left && ax <= left) { + t = (left - ax)/(bx - ax); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bx <= left && ax > left) { + t = (left - ax)/(bx - ax); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ax >= right && bx >= right) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bx < right && ax >= right) { + t = (right - ax)/(bx - ax); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bx >= right && ax < right) { + t = (right - ax)/(bx - ax); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ay <= top && by <= top) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (by > top && ay <= top) { + t = (top - ay)/(by - ay); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (by <= top && ay > top) { + t = (top - ay)/(by - ay); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (ay >= bottom && by >= bottom) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (by < bottom && ay >= bottom) { + t = (bottom - ay)/(by - ay); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (by >= bottom && ay < bottom) { + t = (bottom - ay)/(by - ay); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + } + occludeAll = false; + break; + } + if (wrapper == null && !occludeAll) continue; + } + // Create plane by face + plane = CullingPlane.create(); + plane.next = planeList; + planeList = plane; + a = face.wrapper.vertex; + b = face.wrapper.next.vertex; + c = face.wrapper.next.next.vertex; + ax = b.cameraX - a.cameraX; + ay = b.cameraY - a.cameraY; + az = b.cameraZ - a.cameraZ; + bx = c.cameraX - a.cameraX; + by = c.cameraY - a.cameraY; + bz = c.cameraZ - a.cameraZ; + plane.x = (bz*ay - by*az)*camera.correctionY; + plane.y = (bx*az - bz*ax)*camera.correctionX; + plane.z = (by*ax - bx*ay)*camera.correctionX*camera.correctionY; + plane.offset = a.cameraX*plane.x*camera.correctionX + a.cameraY*plane.y*camera.correctionY + a.cameraZ*plane.z; + } + } + + private function checkSquare(lineList:CullingPlane, ox:Number, oy:Number, square:Number, viewSquare:Number, viewSizeX:Number, viewSizeY:Number):Boolean { + var t:Number; + var ax:Number; + var ay:Number; + var ao:Number; + var bx:Number; + var by:Number; + var bo:Number; + var plane:CullingPlane; + // Clipping of viewport frame by projected contour edges + if (culling & 4) { + ax = -viewSizeX; + ay = -viewSizeY; + bx = -viewSizeX; + by = viewSizeY; + for (plane = lineList; plane != null; plane = plane.next) { + ao = ax*plane.x + ay*plane.y - plane.offset; + bo = bx*plane.x + by*plane.y - plane.offset; + if (ao < 0 || bo < 0) { + if (ao >= 0 && bo < 0) { + t = ao/(ao - bo); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (ao < 0 && bo >= 0) { + t = ao/(ao - bo); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (plane == null) { + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + if (square/viewSquare >= minSize) return true; + } + } + if (culling & 8) { + ax = viewSizeX; + ay = viewSizeY; + bx = viewSizeX; + by = -viewSizeY; + for (plane = lineList; plane != null; plane = plane.next) { + ao = ax*plane.x + ay*plane.y - plane.offset; + bo = bx*plane.x + by*plane.y - plane.offset; + if (ao < 0 || bo < 0) { + if (ao >= 0 && bo < 0) { + t = ao/(ao - bo); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (ao < 0 && bo >= 0) { + t = ao/(ao - bo); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (plane == null) { + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + if (square/viewSquare >= minSize) return true; + } + } + if (culling & 16) { + ax = viewSizeX; + ay = -viewSizeY; + bx = -viewSizeX; + by = -viewSizeY; + for (plane = lineList; plane != null; plane = plane.next) { + ao = ax*plane.x + ay*plane.y - plane.offset; + bo = bx*plane.x + by*plane.y - plane.offset; + if (ao < 0 || bo < 0) { + if (ao >= 0 && bo < 0) { + t = ao/(ao - bo); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (ao < 0 && bo >= 0) { + t = ao/(ao - bo); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (plane == null) { + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + if (square/viewSquare >= minSize) return true; + } + } + if (culling & 32) { + ax = -viewSizeX; + ay = viewSizeY; + bx = viewSizeX; + by = viewSizeY; + for (plane = lineList; plane != null; plane = plane.next) { + ao = ax*plane.x + ay*plane.y - plane.offset; + bo = bx*plane.x + by*plane.y - plane.offset; + if (ao < 0 || bo < 0) { + if (ao >= 0 && bo < 0) { + t = ao/(ao - bo); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (ao < 0 && bo >= 0) { + t = ao/(ao - bo); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (plane == null) { + square += (bx - ox)*(ay - oy) - (by - oy)*(ax - ox); + if (square/viewSquare >= minSize) return true; + } + } + return false; + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + var x:Number; + var y:Number; + var z:Number; + if (transform != null) { + x = transform.a*vertex.x + transform.b*vertex.y + transform.c*vertex.z + transform.d; + y = transform.e*vertex.x + transform.f*vertex.y + transform.g*vertex.z + transform.h; + z = transform.i*vertex.x + transform.j*vertex.y + transform.k*vertex.z + transform.l; + } else { + x = vertex.x; + y = vertex.y; + z = vertex.z; + } + if (x < boundBox.minX) boundBox.minX = x; + if (x > boundBox.maxX) boundBox.maxX = x; + if (y < boundBox.minY) boundBox.minY = y; + if (y > boundBox.maxY) boundBox.maxY = y; + if (z < boundBox.minZ) boundBox.minZ = z; + if (z > boundBox.maxZ) boundBox.maxZ = z; + } + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Occluder = new Occluder(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:Occluder = source as Occluder; + minSize = src.minSize; + // Clone vertices + var vertex:Vertex; + var face:Face; + var lastVertex:Vertex; + for (vertex = src.vertexList; vertex != null; vertex = vertex.next) { + var newVertex:Vertex = new Vertex(); + newVertex.x = vertex.x; + newVertex.y = vertex.y; + newVertex.z = vertex.z; + vertex.value = newVertex; + if (lastVertex != null) { + lastVertex.next = newVertex; + } else { + vertexList = newVertex; + } + lastVertex = newVertex; + } + // Clone faces + var lastFace:Face; + for (face = src.faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + face.processNext = newFace; + // Clone wrappers + var lastWrapper:Wrapper = null; + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + newWrapper.vertex = wrapper.vertex.value; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + if (lastFace != null) { + lastFace.next = newFace; + } else { + faceList = newFace; + } + lastFace = newFace; + } + // Clone edges + var lastEdge:Edge; + for (var edge:Edge = src.edgeList; edge != null; edge = edge.next) { + var newEdge:Edge = new Edge(); + newEdge.a = edge.a.value; + newEdge.b = edge.b.value; + newEdge.left = edge.left.processNext; + newEdge.right = edge.right.processNext; + if (lastEdge != null) { + lastEdge.next = newEdge; + } else { + edgeList = newEdge; + } + lastEdge = newEdge; + } + // Reset after remapping + for (vertex = src.vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + for (face = src.faceList; face != null; face = face.next) { + face.processNext = null; + } + } + + } +} + +class Vertex { + + public var next:Vertex; + public var value:Vertex; + + public var x:Number; + public var y:Number; + public var z:Number; + + public var offset:Number; + + public var cameraX:Number; + public var cameraY:Number; + public var cameraZ:Number; + +} + +class Face { + + public var next:Face; + public var processNext:Face; + + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + public var wrapper:Wrapper; + + public var visible:Boolean; + + public 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; + } + +} + +class Wrapper { + + public var next:Wrapper; + + public var vertex:Vertex; + +} + +class Edge { + + public var next:Edge; + + public var a:Vertex; + public var b:Vertex; + + public var left:Face; + public var right:Face; + +} diff --git a/src/alternativa/engine3d/core/RayIntersectionData.as b/src/alternativa/engine3d/core/RayIntersectionData.as new file mode 100644 index 0000000..23c480e --- /dev/null +++ b/src/alternativa/engine3d/core/RayIntersectionData.as @@ -0,0 +1,59 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.objects.Surface; + + import flash.geom.Point; + import flash.geom.Vector3D; + + /** + * A result of searching for intersection of an Object3D and a ray with intersectRay() method of Object3D. + * + * @see Object3D#intersectRay() + */ + public class RayIntersectionData { + + /** + * First object intersected by the ray. + */ + public var object:Object3D; + + /** + * The point of intersection il local coordinates of object. + */ + public var point:Vector3D; + + /** + * Surface of object on which intersection occurred. + */ + public var surface:Surface; + + /** + * Distance from ray's origin to intersection point expressed in length of localDirection vector. + */ + public var time:Number; + + /** + * Texture coordinates of intersection point. + */ + public var uv:Point; + + /** + * Returns the string representation of the specified object. + * @return The string representation of the specified object. + */ + public function toString():String { + return "[RayIntersectionData " + object + ", " + point + ", " + uv + ", " + time + "]"; + } + + } +} diff --git a/src/alternativa/engine3d/core/Renderer.as b/src/alternativa/engine3d/core/Renderer.as new file mode 100644 index 0000000..566f661 --- /dev/null +++ b/src/alternativa/engine3d/core/Renderer.as @@ -0,0 +1,238 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.ShaderProgram; + + import flash.display3D.Context3D; + import flash.display3D.Context3DCompareMode; + import flash.display3D.Context3DProgramType; + import flash.display3D.IndexBuffer3D; + import flash.display3D.Program3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class Renderer { + + public static const SKY:int = 10; + + public static const OPAQUE:int = 20; + + public static const DECALS:int = 30; + + public static const TRANSPARENT_SORT:int = 40; + + public static const NEXT_LAYER:int = 50; + + // Collector + protected var collector:DrawUnit; + + alternativa3d var camera:Camera3D; + + alternativa3d var drawUnits:Vector. = new Vector.(); + + // Key - context, value - properties. + protected static var properties:Dictionary = new Dictionary(true); + + protected var _context3D:Context3D; + protected var _contextProperties:RendererContext3DProperties; + + alternativa3d function render(context3D:Context3D):void { + updateContext3D(context3D); + + var drawUnitsLength:int = drawUnits.length; + for (var i:int = 0; i < drawUnitsLength; i++) { + var list:DrawUnit = drawUnits[i]; + if (list != null) { + switch (i) { + case SKY: + _context3D.setDepthTest(false, Context3DCompareMode.ALWAYS); + break; + case OPAQUE: + _context3D.setDepthTest(true, Context3DCompareMode.LESS); + break; + case DECALS: + _context3D.setDepthTest(false, Context3DCompareMode.LESS_EQUAL); + break; + case TRANSPARENT_SORT: + if (list.next != null) list = sortByAverageZ(list); + _context3D.setDepthTest(false, Context3DCompareMode.LESS); + break; + case NEXT_LAYER: + _context3D.setDepthTest(false, Context3DCompareMode.ALWAYS); + break; + } + // Rendering + while (list != null) { + var next:DrawUnit = list.next; + renderDrawUnit(list, _context3D, camera); + // Send to collector + list.clear(); + list.next = collector; + collector = list; + list = next; + } + } + } + // Clear + drawUnits.length = 0; + } + + alternativa3d function createDrawUnit(object:Object3D, program:Program3D, indexBuffer:IndexBuffer3D, firstIndex:int, numTriangles:int, debugShader:ShaderProgram = null):DrawUnit { + var res:DrawUnit; + if (collector != null) { + res = collector; + collector = collector.next; + res.next = null; + } else { + //trace("new DrawUnit"); + res = new DrawUnit(); + } + res.object = object; + res.program = program; + res.indexBuffer = indexBuffer; + res.firstIndex = firstIndex; + res.numTriangles = numTriangles; + return res; + } + + alternativa3d function addDrawUnit(drawUnit:DrawUnit, renderPriority:int):void { + // Increase array of priorities, if it is necessary + if (renderPriority >= drawUnits.length) drawUnits.length = renderPriority + 1; + // Add + drawUnit.next = drawUnits[renderPriority]; + drawUnits[renderPriority] = drawUnit; + } + + protected function renderDrawUnit(drawUnit:DrawUnit, context:Context3D, camera:Camera3D):void { + context.setBlendFactors(drawUnit.blendSource, drawUnit.blendDestination); + context.setCulling(drawUnit.culling); + var _usedBuffers:uint = _contextProperties.usedBuffers; + var _usedTextures:uint = _contextProperties.usedTextures; + + var bufferIndex:int; + var bufferBit:int; + var currentBuffers:int; + var textureSampler:int; + var textureBit:int; + var currentTextures:int; + for (var i:int = 0; i < drawUnit.vertexBuffersLength; i++) { + bufferIndex = drawUnit.vertexBuffersIndexes[i]; + bufferBit = 1 << bufferIndex; + currentBuffers |= bufferBit; + _usedBuffers &= ~bufferBit; + context.setVertexBufferAt(bufferIndex, drawUnit.vertexBuffers[i], drawUnit.vertexBuffersOffsets[i], drawUnit.vertexBuffersFormats[i]); + } + if (drawUnit.vertexConstantsRegistersCount > 0) { + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, drawUnit.vertexConstants, drawUnit.vertexConstantsRegistersCount); + } + if (drawUnit.fragmentConstantsRegistersCount > 0) { + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, drawUnit.fragmentConstants, drawUnit.fragmentConstantsRegistersCount); + } + for (i = 0; i < drawUnit.texturesLength; i++) { + textureSampler = drawUnit.texturesSamplers[i]; + textureBit = 1 << textureSampler; + currentTextures |= textureBit; + _usedTextures &= ~textureBit; + context.setTextureAt(textureSampler, drawUnit.textures[i]); + } + context.setProgram(drawUnit.program); + for (bufferIndex = 0; _usedBuffers > 0; bufferIndex++) { + bufferBit = _usedBuffers & 1; + _usedBuffers >>= 1; + if (bufferBit) context.setVertexBufferAt(bufferIndex, null); + } + for (textureSampler = 0; _usedTextures > 0; textureSampler++) { + textureBit = _usedTextures & 1; + _usedTextures >>= 1; + if (textureBit) context.setTextureAt(textureSampler, null); + } + context.drawTriangles(drawUnit.indexBuffer, drawUnit.firstIndex, drawUnit.numTriangles); + _contextProperties.usedBuffers = currentBuffers; + _contextProperties.usedTextures = currentTextures; + camera.numDraws++; + camera.numTriangles += drawUnit.numTriangles; + } + + protected function updateContext3D(value:Context3D):void { + if (_context3D != value) { + _contextProperties = properties[value]; + if (_contextProperties == null) { + _contextProperties = new RendererContext3DProperties(); + properties[value] = _contextProperties; + } + _context3D = value; + } + } + + alternativa3d function sortByAverageZ(list:DrawUnit, direction:Boolean = true):DrawUnit { + var left:DrawUnit = list; + var right:DrawUnit = 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 = sortByAverageZ(left, direction); + } + if (right.next != null) { + right = sortByAverageZ(right, direction); + } + var flag:Boolean = direction ? (left.object.localToCameraTransform.l > right.object.localToCameraTransform.l) : (left.object.localToCameraTransform.l < right.object.localToCameraTransform.l); + if (flag) { + list = left; + left = left.next; + } else { + list = right; + right = right.next; + } + var last:DrawUnit = list; + while (true) { + if (left == null) { + last.next = right; + return list; + } else if (right == null) { + last.next = left; + return list; + } + if (flag) { + if (direction ? (left.object.localToCameraTransform.l > right.object.localToCameraTransform.l) : (left.object.localToCameraTransform.l < right.object.localToCameraTransform.l)) { + last = left; + left = left.next; + } else { + last.next = right; + last = right; + right = right.next; + flag = false; + } + } else { + if (direction ? (left.object.localToCameraTransform.l < right.object.localToCameraTransform.l) : (left.object.localToCameraTransform.l > right.object.localToCameraTransform.l)) { + last = right; + right = right.next; + } else { + last.next = left; + last = left; + left = left.next; + flag = true; + } + } + } + return null; + } + } +} diff --git a/src/alternativa/engine3d/core/RendererContext3DProperties.as b/src/alternativa/engine3d/core/RendererContext3DProperties.as new file mode 100644 index 0000000..a344625 --- /dev/null +++ b/src/alternativa/engine3d/core/RendererContext3DProperties.as @@ -0,0 +1,23 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + /** + * @private + * Stores settings of context. + */ + public class RendererContext3DProperties { + + public var usedBuffers:uint = 0; + public var usedTextures:uint = 0; + + } +} diff --git a/src/alternativa/engine3d/core/Resource.as b/src/alternativa/engine3d/core/Resource.as new file mode 100644 index 0000000..c66d25a --- /dev/null +++ b/src/alternativa/engine3d/core/Resource.as @@ -0,0 +1,57 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3D; + + use namespace alternativa3d; + + /** + * Base class for GPU data. GPU data can be divided in 2 groups: geometry data and texture data. + * The type of resources for uploading geometry data in GPU is Geometry. + * BitmapTextureResource allows to use textures of type is BitmapData and ATFTextureResource deals with ByteArray consists of ATF data, + * ExternalTextureResource should be used with TexturesLoader, which loads textures from files and automatically uploads in GPU. + * + * + * @see alternativa.engine3d.resources.Geometry + * @see alternativa.engine3d.resources.TextureResource + * @see alternativa.engine3d.resources.BitmapTextureResource + * @see alternativa.engine3d.resources.ATFTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public class Resource { + + /** + * Defines if this resource is uploaded inti a Context3D. + */ + public function get isUploaded():Boolean { + return false; + } + + /** + * Uploads resource into given Context3D. + * + * @param context3D Context3D to which resource will uploaded. + */ + public function upload(context3D:Context3D):void { + throw new Error("Cannot upload without data"); + } + + /** + * Removes this resource from Context3D to which it was uploaded. + */ + public function dispose():void { + } + + } +} diff --git a/src/alternativa/engine3d/core/Transform3D.as b/src/alternativa/engine3d/core/Transform3D.as new file mode 100644 index 0000000..ba1e0f4 --- /dev/null +++ b/src/alternativa/engine3d/core/Transform3D.as @@ -0,0 +1,269 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * @private + */ + public class Transform3D { + + public var a:Number = 1; + public var b:Number = 0; + public var c:Number = 0; + public var d:Number = 0; + + public var e:Number = 0; + public var f:Number = 1; + public var g:Number = 0; + public var h:Number = 0; + + public var i:Number = 0; + public var j:Number = 0; + public var k:Number = 1; + public var l:Number = 0; + + public function identity():void { + a = 1; + b = 0; + c = 0; + d = 0; + e = 0; + f = 1; + g = 0; + h = 0; + i = 0; + j = 0; + k = 1; + l = 0; + } + + public function compose(x:Number, y:Number, z:Number, rotationX:Number, rotationY:Number, rotationZ:Number, scaleX:Number, scaleY:Number, scaleZ:Number):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; + a = cosZ*cosYscaleX; + b = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + c = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + d = x; + e = sinZ*cosYscaleX; + f = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + g = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + h = y; + i = -sinY*scaleX; + j = cosY*sinXscaleY; + k = cosY*cosXscaleZ; + l = z; + } + + public function composeInverse(x:Number, y:Number, z:Number, rotationX:Number, rotationY:Number, rotationZ:Number, scaleX:Number, scaleY:Number, scaleZ:Number):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 sinXsinY:Number = sinX*sinY; + var cosYscaleX:Number = cosY/scaleX; + var cosXscaleY:Number = cosX/scaleY; + var sinXscaleZ:Number = sinX/scaleZ; + var cosXscaleZ:Number = cosX/scaleZ; + a = cosZ*cosYscaleX; + b = -sinZ*cosYscaleX; + c = sinY/scaleX; + d = -a*x - b*y - c*z; + e = sinZ*cosXscaleY + sinXsinY*cosZ/scaleY; + f = cosZ*cosXscaleY - sinXsinY*sinZ/scaleY; + g = -sinX*cosY/scaleY; + h = -e*x - f*y - g*z; + i = sinZ*sinXscaleZ - cosZ*sinY*cosXscaleZ; + j = cosZ*sinXscaleZ + sinY*sinZ*cosXscaleZ; + k = cosY*cosXscaleZ; + l = -i*x - j*y - k*z; + } + + public function invert():void { + var ta:Number = a; + var tb:Number = b; + var tc:Number = c; + var td:Number = d; + var te:Number = e; + var tf:Number = f; + var tg:Number = g; + var th:Number = h; + var ti:Number = i; + var tj:Number = j; + var tk:Number = k; + var tl:Number = l; + var det:Number = 1/(-tc*tf*ti + tb*tg*ti + tc*te*tj - ta*tg*tj - tb*te*tk + ta*tf*tk); + a = (-tg*tj + tf*tk)*det; + b = (tc*tj - tb*tk)*det; + c = (-tc*tf + tb*tg)*det; + d = (td*tg*tj - tc*th*tj - td*tf*tk + tb*th*tk + tc*tf*tl - tb*tg*tl)*det; + e = (tg*ti - te*tk)*det; + f = (-tc*ti + ta*tk)*det; + g = (tc*te - ta*tg)*det; + h = (tc*th*ti - td*tg*ti + td*te*tk - ta*th*tk - tc*te*tl + ta*tg*tl)*det; + i = (-tf*ti + te*tj)*det; + j = (tb*ti - ta*tj)*det; + k = (-tb*te + ta*tf)*det; + l = (td*tf*ti - tb*th*ti - td*te*tj + ta*th*tj + tb*te*tl - ta*tf*tl)*det; + } + + public function initFromVector(vector:Vector.):void { + a = vector[0]; + b = vector[1]; + c = vector[2]; + d = vector[3]; + e = vector[4]; + f = vector[5]; + g = vector[6]; + h = vector[7]; + i = vector[8]; + j = vector[9]; + k = vector[10]; + l = vector[11]; + } + + public function append(transform:Transform3D):void { + var ta:Number = a; + var tb:Number = b; + var tc:Number = c; + var td:Number = d; + var te:Number = e; + var tf:Number = f; + var tg:Number = g; + var th:Number = h; + var ti:Number = i; + var tj:Number = j; + var tk:Number = k; + var tl:Number = l; + a = transform.a*ta + transform.b*te + transform.c*ti; + b = transform.a*tb + transform.b*tf + transform.c*tj; + c = transform.a*tc + transform.b*tg + transform.c*tk; + d = transform.a*td + transform.b*th + transform.c*tl + transform.d; + e = transform.e*ta + transform.f*te + transform.g*ti; + f = transform.e*tb + transform.f*tf + transform.g*tj; + g = transform.e*tc + transform.f*tg + transform.g*tk; + h = transform.e*td + transform.f*th + transform.g*tl + transform.h; + i = transform.i*ta + transform.j*te + transform.k*ti; + j = transform.i*tb + transform.j*tf + transform.k*tj; + k = transform.i*tc + transform.j*tg + transform.k*tk; + l = transform.i*td + transform.j*th + transform.k*tl + transform.l; + } + + public function prepend(transform:Transform3D):void { + var ta:Number = a; + var tb:Number = b; + var tc:Number = c; + var td:Number = d; + var te:Number = e; + var tf:Number = f; + var tg:Number = g; + var th:Number = h; + var ti:Number = i; + var tj:Number = j; + var tk:Number = k; + var tl:Number = l; + a = ta*transform.a + tb*transform.e + tc*transform.i; + b = ta*transform.b + tb*transform.f + tc*transform.j; + c = ta*transform.c + tb*transform.g + tc*transform.k; + d = ta*transform.d + tb*transform.h + tc*transform.l + td; + e = te*transform.a + tf*transform.e + tg*transform.i; + f = te*transform.b + tf*transform.f + tg*transform.j; + g = te*transform.c + tf*transform.g + tg*transform.k; + h = te*transform.d + tf*transform.h + tg*transform.l + th; + i = ti*transform.a + tj*transform.e + tk*transform.i; + j = ti*transform.b + tj*transform.f + tk*transform.j; + k = ti*transform.c + tj*transform.g + tk*transform.k; + l = ti*transform.d + tj*transform.h + tk*transform.l + tl; + + } + + public function combine(transformA:Transform3D, transformB:Transform3D):void { + a = transformA.a*transformB.a + transformA.b*transformB.e + transformA.c*transformB.i; + b = transformA.a*transformB.b + transformA.b*transformB.f + transformA.c*transformB.j; + c = transformA.a*transformB.c + transformA.b*transformB.g + transformA.c*transformB.k; + d = transformA.a*transformB.d + transformA.b*transformB.h + transformA.c*transformB.l + transformA.d; + e = transformA.e*transformB.a + transformA.f*transformB.e + transformA.g*transformB.i; + f = transformA.e*transformB.b + transformA.f*transformB.f + transformA.g*transformB.j; + g = transformA.e*transformB.c + transformA.f*transformB.g + transformA.g*transformB.k; + h = transformA.e*transformB.d + transformA.f*transformB.h + transformA.g*transformB.l + transformA.h; + i = transformA.i*transformB.a + transformA.j*transformB.e + transformA.k*transformB.i; + j = transformA.i*transformB.b + transformA.j*transformB.f + transformA.k*transformB.j; + k = transformA.i*transformB.c + transformA.j*transformB.g + transformA.k*transformB.k; + l = transformA.i*transformB.d + transformA.j*transformB.h + transformA.k*transformB.l + transformA.l; + } + + public function calculateInversion(source:Transform3D):void { + var ta:Number = source.a; + var tb:Number = source.b; + var tc:Number = source.c; + var td:Number = source.d; + var te:Number = source.e; + var tf:Number = source.f; + var tg:Number = source.g; + var th:Number = source.h; + var ti:Number = source.i; + var tj:Number = source.j; + var tk:Number = source.k; + var tl:Number = source.l; + var det:Number = 1/(-tc*tf*ti + tb*tg*ti + tc*te*tj - ta*tg*tj - tb*te*tk + ta*tf*tk); + a = (-tg*tj + tf*tk)*det; + b = (tc*tj - tb*tk)*det; + c = (-tc*tf + tb*tg)*det; + d = (td*tg*tj - tc*th*tj - td*tf*tk + tb*th*tk + tc*tf*tl - tb*tg*tl)*det; + e = (tg*ti - te*tk)*det; + f = (-tc*ti + ta*tk)*det; + g = (tc*te - ta*tg)*det; + h = (tc*th*ti - td*tg*ti + td*te*tk - ta*th*tk - tc*te*tl + ta*tg*tl)*det; + i = (-tf*ti + te*tj)*det; + j = (tb*ti - ta*tj)*det; + k = (-tb*te + ta*tf)*det; + l = (td*tf*ti - tb*th*ti - td*te*tj + ta*th*tj + tb*te*tl - ta*tf*tl)*det; + } + + public function copy(source:Transform3D):void { + a = source.a; + b = source.b; + c = source.c; + d = source.d; + e = source.e; + f = source.f; + g = source.g; + h = source.h; + i = source.i; + j = source.j; + k = source.k; + l = source.l; + } + + public function toString():String { + return "[Transform3D" + + " a:" + a.toFixed(3) + " b:" + b.toFixed(3) + " c:" + a.toFixed(3) + " d:" + d.toFixed(3) + + " e:" + e.toFixed(3) + " f:" + f.toFixed(3) + " g:" + a.toFixed(3) + " h:" + h.toFixed(3) + + " i:" + i.toFixed(3) + " j:" + j.toFixed(3) + " k:" + a.toFixed(3) + " l:" + l.toFixed(3) + "]"; + } + + } +} diff --git a/src/alternativa/engine3d/core/VertexAttributes.as b/src/alternativa/engine3d/core/VertexAttributes.as new file mode 100644 index 0000000..11c0ff9 --- /dev/null +++ b/src/alternativa/engine3d/core/VertexAttributes.as @@ -0,0 +1,112 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3DVertexBufferFormat; + + use namespace alternativa3d; + + /** + * Types of attributes which defines format of vertex streams. It can be used as values of array, + * passed to geometry.addVertexStream(attributes) as an argument. + * + * @see alternativa.engine3d.resources.Geometry + */ + public class VertexAttributes { + /** + * Coordinates in 3D space. Defines by sequence of three floats. + * + * @see alternativa.engine3d.resources.Geometry + * @see #getAttributeStride() + */ + public static const POSITION:uint = 1; + /** + * Vertex normal. Defines by sequence of three floats. + * + * @see alternativa.engine3d.resources.Geometry + * @see #getAttributeStride() + */ + public static const NORMAL:uint = 2; + /** + * This data type combines values of vertex tangent and binormal within one sequence of four floats. + * The first three values defines tangent direction and the fourth can be 1 or -1 which defines to what side binormal is ordered. + * + * @see alternativa.engine3d.resources.Geometry + */ + public static const TANGENT4:uint = 3; + /** + * Data of linking of two Joints with vertex. Defines by sequence of four floats in following order: + * id of the first Joint multiplied with 3, power of influence of the first Joint, + * id of the second Joint multiplied with 3, power of influence of the second Joint. + * There are a four 'slots' for this data type, so influence of 8 Joints can be described. + * @see alternativa.engine3d.resources.Geometry + * @see alternativa.engine3d.objects.Skin + */ + public static const JOINTS:Vector. = Vector.([4,5,6,7]); + + /** + * Texture coordinates data type. There are a 8 independent channels. Coordinates defines by the couples (u, v). + * + * @see alternativa.engine3d.resources.Geometry + */ + public static const TEXCOORDS:Vector. = Vector.([8,9,10,11,12,13,14,15]); + + /** + * @private + */ + alternativa3d static const FORMATS:Array = [ + Context3DVertexBufferFormat.FLOAT_1, //NONE + Context3DVertexBufferFormat.FLOAT_3, //POSITION + Context3DVertexBufferFormat.FLOAT_3, //NORMAL + Context3DVertexBufferFormat.FLOAT_4, //TANGENT4 + Context3DVertexBufferFormat.FLOAT_4, //JOINTS[0] + Context3DVertexBufferFormat.FLOAT_4, //JOINTS[1] + Context3DVertexBufferFormat.FLOAT_4, //JOINTS[2] + Context3DVertexBufferFormat.FLOAT_4, //JOINTS[3] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[0] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[1] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[2] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[3] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[4] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[5] + Context3DVertexBufferFormat.FLOAT_2, //TEXCOORDS[6] + Context3DVertexBufferFormat.FLOAT_2 //TEXCOORDS[7] + ]; + + /** + * Returns a dimensions of given attribute type (Number of floats by which defines given type) + * + * @param attribute Type of the attribute. + * @return + */ + public static function getAttributeStride(attribute:int):int { + switch(FORMATS[attribute]) { + case Context3DVertexBufferFormat.FLOAT_1: + return 1; + break; + case Context3DVertexBufferFormat.FLOAT_2: + return 2; + break; + case Context3DVertexBufferFormat.FLOAT_3: + return 3; + break; + case Context3DVertexBufferFormat.FLOAT_4: + return 4; + break; + } + return 0; + } + + + } +} diff --git a/src/alternativa/engine3d/core/VertexStream.as b/src/alternativa/engine3d/core/VertexStream.as new file mode 100644 index 0000000..54afeaa --- /dev/null +++ b/src/alternativa/engine3d/core/VertexStream.as @@ -0,0 +1,24 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import flash.display3D.VertexBuffer3D; + import flash.utils.ByteArray; + + /** + * @private + */ + public class VertexStream { + public var buffer:VertexBuffer3D; + public var attributes:Array; + public var data:ByteArray; + } +} diff --git a/src/alternativa/engine3d/core/View.as b/src/alternativa/engine3d/core/View.as new file mode 100644 index 0000000..3a5e0a0 --- /dev/null +++ b/src/alternativa/engine3d/core/View.as @@ -0,0 +1,1432 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core { + + import alternativa.Alternativa3D; + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.events.MouseEvent3D; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.Sprite; + import flash.display.Stage3D; + import flash.display.StageAlign; + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DCompareMode; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.VertexBuffer3D; + import flash.events.ContextMenuEvent; + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.net.URLRequest; + import flash.net.navigateToURL; + import flash.ui.ContextMenu; + import flash.ui.ContextMenuItem; + import flash.ui.Keyboard; + import flash.ui.Mouse; + import flash.utils.Dictionary; + import flash.utils.setTimeout; + + use namespace alternativa3d; + + /** + * A viewport. Though GPU can render to one of Stage3D only, View still extends DisplayObject and should be in DisplayList. + * Since 8 version of Alternativa3D view used as wrapper for configuring Stage3D properties for first. Main task of view is defining + * rectangular field of screen to which image will be rendered. Another opportunity is render image to Bitmap. In this case the view + * will have this Bitmap as a child. The size of View should be 50x50 at least. + * In case of size will be more than 2048 and anti-aliasing is turned on, usage of MouseEvents will cause of crash. + * + * @see alternativa.engine3d.core.Camera3D + */ + public class View extends Sprite { + + private static const renderEvent:MouseEvent = new MouseEvent("render"); + + private static var properties:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var context3DViewProperties:Context3DViewProperties; + + static private var drawDistanceFragment:Linker; + static private var drawDistanceVertexProcedure:Procedure; + + static private const drawUnit:DrawUnit = new DrawUnit(); + static private const pixels:Dictionary = new Dictionary(); + static private const stack:Vector. = new Vector.(); + + static private const point:Point = new Point(); + static private const scissor:Rectangle = new Rectangle(0, 0, 1, 1); + static private const localCoords:Vector3D = new Vector3D(); + + static private const branch:Vector. = new Vector.(); + static private const overedBranch:Vector. = new Vector.(); + static private const changedBranch:Vector. = new Vector.(); + static private const functions:Vector. = new Vector.(); + + private static const drawColoredRectConst:Vector. = Vector.([0, 0, -1, 1]); + private static const drawRectColor:Vector. = new Vector.(4); + + /** + * Background color. + */ + public var backgroundColor:uint; + + /** + * Background transparency. + */ + public var backgroundAlpha:Number; + + /** + * Level of anti-aliasing. + */ + public var antiAlias:int; + + /** + * @private + */ + alternativa3d var _width:int; + + /** + * @private + */ + alternativa3d var _height:int; + + private var backBufferContext3D:Context3D; + private var backBufferWidth:int = -1; + private var backBufferHeight:int = -1; + private var backBufferAntiAlias:int = -1; + + /** + * @private + */ + alternativa3d var _canvas:BitmapData = null; + + /** + * Mouse events occurred over this View since last render. + */ + private var events:Vector. = new Vector.(); + /** + * Indices of rays in the raysOrigins array for each mouse event. + */ + private var indices:Vector. = new Vector.(); + private var eventsLength:int = 0; + + // Surfaces of objects which can be crossed by mouse and procedures of transformation their coordinates + + private var surfaces:Vector. = new Vector.(); + private var geometries:Vector. = new Vector.(); + private var procedures:Vector. = new Vector.(); + private var surfacesLength:int = 0; + + /** + * @private + */ + alternativa3d var raysOrigins:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var raysDirections:Vector. = new Vector.(); + private var raysCoefficients:Vector. = new Vector.(); + private var raysSurfaces:Vector.> = new Vector.>(); + private var raysDepths:Vector.> = new Vector.>(); + private var raysIs:Vector. = new Vector.(); + private var raysJs:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var raysLength:int = 0; + + private var lastEvent:MouseEvent; + + private var target:Object3D; + private var targetSurface:Surface; + private var targetDepth:Number; + private var pressedTarget:Object3D; + private var clickedTarget:Object3D; + private var overedTarget:Object3D; + private var overedTargetSurface:Surface; + + private var altKey:Boolean; + private var ctrlKey:Boolean; + private var shiftKey:Boolean; + + private var container:Bitmap; + private var area:Sprite; + private var logo:Logo; + private var bitmap:Bitmap; + private var _logoAlign:String = "BR"; + private var _logoHorizontalMargin:Number = 0; + private var _logoVerticalMargin:Number = 0; + private var _renderToBitmap:Boolean; + + /** + * Creates a View object. + * @param width Width of a view, should be 50 at least. + * @param height Height of a view, should be 50 at least. + * @param renderToBitmap If true, image will render to Bitmap object which will included into the view as a child. It also will available through canvas property. + * @param backgroundColor Background color. + * @param backgroundAlpha BAckground transparency. + * @param antiAlias Level of anti-aliasing. + * + * @see #canvas + */ + public function View(width:int, height:int, renderToBitmap:Boolean = false, backgroundColor:uint = 0, backgroundAlpha:Number = 1, antiAlias:int = 0) { + if (width < 50) width = 50; + if (height < 50) height = 50; + _width = width; + _height = height; + _renderToBitmap = renderToBitmap; + this.backgroundColor = backgroundColor; + this.backgroundAlpha = backgroundAlpha; + this.antiAlias = antiAlias; + + mouseEnabled = true; + mouseChildren = true; + doubleClickEnabled = true; + + buttonMode = true; + useHandCursor = false; + + tabEnabled = false; + tabChildren = false; + + // Context menu + var item:ContextMenuItem = new ContextMenuItem("Powered by Alternativa3D " + Alternativa3D.version); + item.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function(e:ContextMenuEvent):void { + try { + navigateToURL(new URLRequest("http://alternativaplatform.com"), "_blank"); + } catch (e:Error) { + } + }); + var menu:ContextMenu = new ContextMenu(); + menu.customItems = [item]; + contextMenu = menu; + + // Canvas + container = new Bitmap(); + if (renderToBitmap) { + createRenderBitmap(); + } + super.addChild(container); + + // Hit area + area = new Sprite(); + area.graphics.beginFill(0xFF0000); + area.graphics.drawRect(0, 0, 100, 100); + area.mouseEnabled = false; + area.visible = false; + area.width = _width; + area.height = _height; + hitArea = area; + super.addChild(hitArea); + + // Logo + showLogo(); + + if (drawDistanceFragment == null) { + drawDistanceVertexProcedure = Procedure.compileFromArray([ + // Declaraion + "#v0=distance", + "#c0=transform0", + "#c1=transform1", + "#c2=transform2", + "#c3=coefficient", + "#c4=projection", + // Convert to the camera coordinates + "dp4 t0.x, i0, c0", + "dp4 t0.y, i0, c1", + "dp4 t0.z, i0, c2", + // Passing the depth + "mul v0.x, t0.z, c3.z", + "mov v0.y, i0.x", + "mov v0.z, i0.x", + "mov v0.w, i0.x", + // Projection + "mul t1.x, t0.x, c4.x", + "mul t1.y, t0.y, c4.y", + "mul t0.w, t0.z, c4.z", + "add t1.z, t0.w, c4.w", + // Get last line + "mov t3.z, c4.x", + "div t3.z, t3.z, c4.x", + "sub t3.z, t3.z, c3.w", + // Finding W + "mul t1.w, t0.z, t3.z", + "add t1.w, t1.w, c3.w", + // Offset + "mul t0.x, c3.x, t1.w", + "mul t0.y, c3.y, t1.w", + "add t1.x, t1.x, t0.x", + "add t1.y, t1.y, t0.y", + "mov o0, t1", + ], "mouseEventsVertex"); + drawDistanceFragment = new Linker(Context3DProgramType.FRAGMENT); + drawDistanceFragment.addProcedure(new Procedure([ + // Id + "mov t0.z, c0.z", + // An unit + "mov t0.w, c0.w", + // Remainder + "frc t0.y, v0.x", + // A whole part + "sub t0.x, v0.x, t0.y", + "mul t0.x, t0.x, c0.x", + "mov o0, ft0", + // Declaration + "#v0=distance", + "#c0=code", + ], "mouseEventsFragment")); + } + + // Listeners + addEventListener(MouseEvent.MOUSE_DOWN, onMouse); + addEventListener(MouseEvent.CLICK, onMouse); + addEventListener(MouseEvent.DOUBLE_CLICK, onMouse); + addEventListener(MouseEvent.MOUSE_MOVE, onMouse); + addEventListener(MouseEvent.MOUSE_OVER, onMouse); + addEventListener(MouseEvent.MOUSE_WHEEL, onMouse); + addEventListener(MouseEvent.MOUSE_OUT, onLeave); + addEventListener(Event.ADDED_TO_STAGE, onAddToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemoveFromStage); + } + + private function onMouse(mouseEvent:MouseEvent):void { + var prev:int = eventsLength - 1; + // case of mouseMove repeats + if (eventsLength > 0 && mouseEvent.type == "mouseMove" && (events[prev] as MouseEvent).type == "mouseMove") { + events[prev] = mouseEvent; + } else { + events[eventsLength] = mouseEvent; + eventsLength++; + } + lastEvent = mouseEvent; + } + + private function onLeave(mouseEvent:MouseEvent):void { + events[eventsLength] = mouseEvent; + eventsLength++; + lastEvent = null; + } + + private function createRenderBitmap():void { + _canvas = new BitmapData(_width, _height, backgroundAlpha < 1, backgroundColor); + container.bitmapData = _canvas; + container.smoothing = true; + } + + private function onAddToStage(e:Event):void { + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); + } + + private function onRemoveFromStage(e:Event):void { + stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp); + altKey = false; + ctrlKey = false; + shiftKey = false; + } + + private function onKeyDown(keyboardEvent:KeyboardEvent):void { + altKey = keyboardEvent.altKey; + ctrlKey = keyboardEvent.ctrlKey; + shiftKey = keyboardEvent.shiftKey; + if (ctrlKey && shiftKey && keyboardEvent.keyCode == Keyboard.F1 && bitmap == null) { + bitmap = new Bitmap(Logo.image); + bitmap.x = Math.round((_width - bitmap.width)/2); + bitmap.y = Math.round((_height - bitmap.height)/2); + super.addChild(bitmap); + setTimeout(removeBitmap, 2048); + } + } + + private function onKeyUp(keyboardEvent:KeyboardEvent):void { + altKey = keyboardEvent.altKey; + ctrlKey = keyboardEvent.ctrlKey; + shiftKey = keyboardEvent.shiftKey; + } + + private function removeBitmap():void { + if (bitmap != null) { + super.removeChild(bitmap); + bitmap = null; + } + } + + /** + * @private + */ + alternativa3d function calculateRays(camera:Camera3D):void { + var i:int; + var mouseEvent:MouseEvent; + // Case of last coordinates fits in the view. + if (lastEvent != null) { + // Detecting mouse movement within the frame + var mouseMoved:Boolean = false; + for (i = 0; i < eventsLength; i++) { + mouseEvent = events[i]; + if (mouseEvent.type == "mouseMove" || mouseEvent.type == "mouseOut") { + mouseMoved = true; + break; + } + } + // Add event of checking if content over mouse was changed + if (!mouseMoved) { + renderEvent.localX = lastEvent.localX; + renderEvent.localY = lastEvent.localY; + renderEvent.ctrlKey = ctrlKey; + renderEvent.altKey = altKey; + renderEvent.shiftKey = shiftKey; + renderEvent.buttonDown = lastEvent.buttonDown; + renderEvent.delta = 0; + events[eventsLength] = renderEvent; + eventsLength++; + } + } + + // Creation of exclusive rays + var mouseX:Number = 1e+22; + var mouseY:Number = 1e+22; + for (i = 0; i < eventsLength; i++) { + mouseEvent = events[i]; + if (mouseEvent.type != "mouseOut") { + // Calculation of ray within the camera + if (mouseEvent.localX != mouseX || mouseEvent.localY != mouseY) { + mouseX = mouseEvent.localX; + mouseY = mouseEvent.localY; + // Creation + var origin:Vector3D; + var direction:Vector3D; + var coefficient:Point; + if (raysLength < raysOrigins.length) { + origin = raysOrigins[raysLength]; + direction = raysDirections[raysLength]; + coefficient = raysCoefficients[raysLength]; + } else { + origin = new Vector3D(); + direction = new Vector3D(); + coefficient = new Point(); + raysOrigins[raysLength] = origin; + raysDirections[raysLength] = direction; + raysCoefficients[raysLength] = coefficient; + raysSurfaces[raysLength] = new Vector.(); + raysDepths[raysLength] = new Vector.(); + } + // Filling + if (!camera.orthographic) { + direction.x = mouseX - _width*0.5; + direction.y = mouseY - _height*0.5; + direction.z = camera.focalLength; + origin.x = direction.x*camera.nearClipping/camera.focalLength; + origin.y = direction.y*camera.nearClipping/camera.focalLength; + origin.z = camera.nearClipping; + direction.normalize(); + coefficient.x = mouseX*2/_width; + coefficient.y = mouseY*2/_height; + } else { + direction.x = 0; + direction.y = 0; + direction.z = 1; + origin.x = mouseX - _width*0.5; + origin.y = mouseY - _height*0.5; + origin.z = camera.nearClipping; + coefficient.x = mouseX*2/_width; + coefficient.y = mouseY*2/_height; + } + raysLength++; + } + // Considering event with the ray + indices[i] = raysLength - 1; + } else { + indices[i] = -1; + } + } + } + + /** + * @private + */ + alternativa3d function addSurfaceToMouseEvents(surface:Surface, geometry:Geometry, procedure:Procedure):void { + surfaces[surfacesLength] = surface; + geometries[surfacesLength] = geometry; + procedures[surfacesLength] = procedure; + surfacesLength++; + } + + /** + * @private + */ + alternativa3d function prepareToRender(stage3D:Stage3D, context:Context3D):void { + if (_canvas == null) { + var vis:Boolean = this.visible; + for (var parent:DisplayObject = this.parent; parent != null; parent = parent.parent) { + vis &&= parent.visible; + } + var coords:Point; + point.x = 0; + point.y = 0; + coords = localToGlobal(point); + stage3D.x = coords.x; + stage3D.y = coords.y; + stage3D.visible = vis; + } else { + stage3D.visible = false; + if (_width != _canvas.width || _height != _canvas.height || (backgroundAlpha < 1) != _canvas.transparent) { + _canvas.dispose(); + createRenderBitmap(); + } + } + if (_width != backBufferWidth || _height != backBufferHeight || antiAlias != backBufferAntiAlias || context != backBufferContext3D) { + backBufferWidth = _width; + backBufferHeight = _height; + backBufferAntiAlias = antiAlias; + backBufferContext3D = context; + context.configureBackBuffer(_width, _height, antiAlias); + } + var r:Number = ((backgroundColor >> 16) & 0xff)/0xff; + var g:Number = ((backgroundColor >> 8) & 0xff)/0xff; + var b:Number = (backgroundColor & 0xff)/0xff; + if (canvas != null) { + r *= backgroundAlpha; + g *= backgroundAlpha; + b *= backgroundAlpha; + } + context.clear(r, g, b, backgroundAlpha); + } + + /** + * @private + */ + alternativa3d function processMouseEvents(context:Context3D, camera:Camera3D):void { + var i:int; + // Mouse events + if (eventsLength > 0) { + if (surfacesLength > 0) { + // Calculating the depth + calculateSurfacesDepths(context, camera, _width, _height); + // Sorting by decreasing the depth + for (i = 0; i < raysLength; i++) { + var raySurfaces:Vector. = raysSurfaces[i]; + var rayDepths:Vector. = raysDepths[i]; + var raySurfacesLength:int = raySurfaces.length; + if (raySurfacesLength > 1) { + sort(raySurfaces, rayDepths, raySurfacesLength); + } + } + } + // Event handling + targetDepth = camera.farClipping; + for (i = 0; i < eventsLength; i++) { + var mouseEvent:MouseEvent = events[i]; + var index:int = indices[i]; + // Check event type + switch (mouseEvent.type) { + case "mouseDown": + defineTarget(index); + if (target != null) { + propagateEvent(MouseEvent3D.MOUSE_DOWN, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + } + pressedTarget = target; + break; + case "mouseWheel": + defineTarget(index); + if (target != null) { + propagateEvent(MouseEvent3D.MOUSE_WHEEL, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + } + break; + case "click": + defineTarget(index); + if (target != null) { + propagateEvent(MouseEvent3D.MOUSE_UP, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + if (pressedTarget == target) { + clickedTarget = target; + propagateEvent(MouseEvent3D.CLICK, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + } + } + pressedTarget = null; + break; + case "doubleClick": + defineTarget(index); + if (target != null) { + propagateEvent(MouseEvent3D.MOUSE_UP, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + if (pressedTarget == target) { + propagateEvent(clickedTarget == target && target.doubleClickEnabled ? MouseEvent3D.DOUBLE_CLICK : MouseEvent3D.CLICK, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + } + } + clickedTarget = null; + pressedTarget = null; + break; + case "mouseMove": + defineTarget(index); + if (target != null) { + propagateEvent(MouseEvent3D.MOUSE_MOVE, mouseEvent, camera, target, targetSurface, branchToVector(target, branch)); + } + if (overedTarget != target) { + processOverOut(mouseEvent, camera); + } + break; + case "mouseOut": + lastEvent = null; + target = null; + targetSurface = null; + if (overedTarget != target) { + processOverOut(mouseEvent, camera); + } + break; + case "render": + defineTarget(index); + if (overedTarget != target) { + processOverOut(mouseEvent, camera); + } + break; + } + target = null; + targetSurface = null; + targetDepth = camera.farClipping; + } + } + // Reset surfaces + surfaces.length = 0; + surfacesLength = 0; + // Reset events + events.length = 0; + eventsLength = 0; + // Reset rays + for (i = 0; i < raysLength; i++) { + raysSurfaces[i].length = 0; + raysDepths[i].length = 0; + } + raysLength = 0; + } + + /** + * Calculates depth of every ray to every surface and writes it to the rayDepths property + * + * @param context + * @param camera + * @param contextWidth + * @param contextHeight + */ + private function calculateSurfacesDepths(context:Context3D, camera:Camera3D, contextWidth:int, contextHeight:int):void { + // Clear + context.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO); + context.setCulling(Context3DTriangleFace.FRONT); + context.setTextureAt(0, null); + context.setTextureAt(1, null); + context.setTextureAt(2, null); + context.setTextureAt(3, null); + context.setTextureAt(4, null); + context.setTextureAt(5, null); + context.setTextureAt(6, null); + context.setTextureAt(7, null); + context.setVertexBufferAt(0, null); + context.setVertexBufferAt(1, null); + context.setVertexBufferAt(2, null); + context.setVertexBufferAt(3, null); + context.setVertexBufferAt(4, null); + context.setVertexBufferAt(5, null); + context.setVertexBufferAt(6, null); + context.setVertexBufferAt(7, null); + + if (context != cachedContext3D) { + // Get properties. + cachedContext3D = context; + context3DViewProperties = properties[cachedContext3D]; + if (context3DViewProperties == null) { + context3DViewProperties = new Context3DViewProperties(); + var rectGeometry:Geometry = new Geometry(4); + rectGeometry.addVertexStream([VertexAttributes.POSITION, VertexAttributes.POSITION, VertexAttributes.POSITION, VertexAttributes.TEXCOORDS[0], VertexAttributes.TEXCOORDS[0]]); + rectGeometry.setAttributeValues(VertexAttributes.POSITION, Vector.([0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1])); + rectGeometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + rectGeometry.indices = Vector.([0, 1, 3, 2, 3, 1]); + rectGeometry.upload(context); + vLinker = new Linker(Context3DProgramType.VERTEX); + vLinker.addProcedure(Procedure.compileFromArray([ + "#a0=a0", + "#c0=c0", + "mul t0.x, a0.x, c0.x", + "mul t0.y, a0.y, c0.y", + "add o0.x, t0.x, c0.z", + "add o0.y, t0.y, c0.w", + "mov o0.z, a0.z", + "mov o0.w, a0.z", + ])); + fLinker = new Linker(Context3DProgramType.FRAGMENT); + fLinker.addProcedure(Procedure.compileFromArray([ + "#c0=c0", + "mov o0, c0", + ])); + var coloredRectProgram:ShaderProgram = new ShaderProgram(vLinker, fLinker); + coloredRectProgram.upload(context); + + context3DViewProperties.drawRectGeometry = rectGeometry; + context3DViewProperties.drawColoredRectProgram = coloredRectProgram; + properties[cachedContext3D] = context3DViewProperties; + } + } + var drawRectGeometry:Geometry = context3DViewProperties.drawRectGeometry; + var drawColoredRectProgram:ShaderProgram = context3DViewProperties.drawColoredRectProgram; + + // Rectangle + var vLinker:Linker, fLinker:Linker; + + // Constants + var m0:Number = camera.m0; + var m5:Number = camera.m5; + var m10:Number = camera.m10; + var m11:Number = camera.m14; + var kZ:Number = 255/camera.farClipping; + var fragmentConst:Number = 1/255; + + // Loop the unique rays + var i:int; + var j:int; + var pixelIndex:int = 0; + + for (i = 0; i < raysLength; i++) { + var rayCoefficients:Point = raysCoefficients[i]; + // Draws the surface of the ray + for (j = 0; j < surfacesLength; j++) { + if (pixelIndex == 0) { + // Set constants + drawColoredRectConst[0] = raysLength*surfacesLength*2/contextWidth; + drawColoredRectConst[1] = -2/contextHeight; + // Fill background with blue color + context.setDepthTest(false, Context3DCompareMode.ALWAYS); + context.setProgram(drawColoredRectProgram.program); + context.setVertexBufferAt(0, drawRectGeometry.getVertexBuffer(VertexAttributes.POSITION), drawRectGeometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, drawColoredRectConst); + drawRectColor[0] = 0; + drawRectColor[1] = 0; + drawRectColor[2] = 1; + drawRectColor[3] = 1; + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, drawRectColor); + context.drawTriangles(drawRectGeometry._indexBuffer, 0, 2); + context.setVertexBufferAt(0, null); + context.setDepthTest(true, Context3DCompareMode.LESS); + } + scissor.x = pixelIndex; + context.setScissorRectangle(scissor); + drawSurface(context, camera, j, m0, m5, m10, m11, (pixelIndex*2/contextWidth - rayCoefficients.x), rayCoefficients.y, kZ, fragmentConst, camera.orthographic); + raysIs[pixelIndex] = i; + raysJs[pixelIndex] = j; + pixelIndex++; + if (pixelIndex >= contextWidth || i >= raysLength - 1 && j >= surfacesLength - 1) { + // get + var pixel:BitmapData = pixels[pixelIndex]; + if (pixel == null) { + pixel = new BitmapData(pixelIndex, 1, false, 0xFF); + pixels[pixelIndex] = pixel; + } + context.drawToBitmapData(pixel); + for (var k:int = 0; k < pixelIndex; k++) { + var color:int = pixel.getPixel(k, 0); + var red:int = (color >> 16) & 0xFF; + var green:int = (color >> 8) & 0xFF; + var blue:int = color & 0xFF; + if (blue == 0) { + var ind:int = raysIs[k]; + var raySurfaces:Vector. = raysSurfaces[ind]; + var rayDepths:Vector. = raysDepths[ind]; + ind = raysJs[k]; + raySurfaces.push(surfaces[ind]); + rayDepths.push((red + green/255)/kZ); + } + } + pixelIndex = 0; + } + } + } + context.setScissorRectangle(null); + + // Overlaying by background color + context.setDepthTest(true, Context3DCompareMode.ALWAYS); + context.setProgram(drawColoredRectProgram.program); + context.setVertexBufferAt(0, drawRectGeometry.getVertexBuffer(VertexAttributes.POSITION), drawRectGeometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawColoredRectConst[0] = raysLength*surfacesLength*2/contextWidth; + drawColoredRectConst[1] = -2/contextHeight; + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, drawColoredRectConst); + var r:Number = ((backgroundColor >> 16) & 0xff)/0xff; + var g:Number = ((backgroundColor >> 8) & 0xff)/0xff; + var b:Number = (backgroundColor & 0xff)/0xff; + if (canvas != null) { + drawRectColor[0] = backgroundAlpha*r; + drawRectColor[1] = backgroundAlpha*g; + drawRectColor[2] = backgroundAlpha*b; + } else { + drawRectColor[0] = r; + drawRectColor[1] = g; + drawRectColor[2] = b; + } + drawRectColor[3] = backgroundAlpha; + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, drawRectColor); + context.drawTriangles(drawRectGeometry._indexBuffer, 0, 2); + context.setVertexBufferAt(0, null); + } + + private function drawSurface(context:Context3D, camera:Camera3D, index:int, m0:Number, m5:Number, m10:Number, m14:Number, xOffset:Number, yOffset:Number, vertexConst:Number, fragmentConst:Number, orthographic:Boolean):void { + // Surface + var surface:Surface = surfaces[index]; + var geometry:Geometry = geometries[index]; + var procedure:Procedure = procedures[index]; + var object:Object3D = surface.object; + // Program + var drawDistanceProgram:ShaderProgram = context3DViewProperties.drawDistancePrograms[procedure]; + if (drawDistanceProgram == null) { + // Assembling the vertex shader + var vertex:Linker = new Linker(Context3DProgramType.VERTEX); + var position:String = "position"; + vertex.declareVariable(position, VariableType.ATTRIBUTE); + if (procedure != null) { + vertex.addProcedure(procedure); + vertex.declareVariable("localPosition", VariableType.TEMPORARY); + vertex.setInputParams(procedure, position); + vertex.setOutputParams(procedure, "localPosition"); + position = "localPosition"; + } + vertex.addProcedure(drawDistanceVertexProcedure); + vertex.setInputParams(drawDistanceVertexProcedure, position); + // Assembling the prgram + drawDistanceProgram = new ShaderProgram(vertex, drawDistanceFragment); + drawDistanceProgram.fragmentShader.varyings = drawDistanceProgram.vertexShader.varyings; + drawDistanceProgram.upload(context); + context3DViewProperties.drawDistancePrograms[procedure] = drawDistanceProgram; + } + var buffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + if (buffer == null) return; + // Draw call (it is required for setting constants only) + drawUnit.vertexBuffersLength = 0; + drawUnit.vertexConstantsRegistersCount = 0; + drawUnit.fragmentConstantsRegistersCount = 0; + object.setTransformConstants(drawUnit, surface, drawDistanceProgram.vertexShader, camera); + drawUnit.setVertexConstantsFromTransform(drawDistanceProgram.vertexShader.getVariableIndex("transform0"), object.localToCameraTransform); + drawUnit.setVertexConstantsFromNumbers(drawDistanceProgram.vertexShader.getVariableIndex("coefficient"), xOffset, yOffset, vertexConst, orthographic ? 1 : 0); + drawUnit.setVertexConstantsFromNumbers(drawDistanceProgram.vertexShader.getVariableIndex("projection"), m0, m5, m10, m14); + drawUnit.setFragmentConstantsFromNumbers(drawDistanceProgram.fragmentShader.getVariableIndex("code"), fragmentConst, 0, 0, 1); + context.setProgram(drawDistanceProgram.program); + // Buffers + var i:int; + context.setVertexBufferAt(0, buffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + for (i = 0; i < drawUnit.vertexBuffersLength; i++) { + context.setVertexBufferAt(drawUnit.vertexBuffersIndexes[i], drawUnit.vertexBuffers[i], drawUnit.vertexBuffersOffsets[i], drawUnit.vertexBuffersFormats[i]); + } + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, drawUnit.vertexConstants, drawUnit.vertexConstantsRegistersCount); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, drawUnit.fragmentConstants, drawUnit.fragmentConstantsRegistersCount); + context.drawTriangles(geometry._indexBuffer, surface.indexBegin, surface.numTriangles); + // Clearing + context.setVertexBufferAt(0, null); + for (i = 0; i < drawUnit.vertexBuffersLength; i++) { + context.setVertexBufferAt(drawUnit.vertexBuffersIndexes[i], null); + } + } + + private function sort(surfaces:Vector., depths:Vector., length:int):void { + stack[0] = 0; + stack[1] = length - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + var j:int = r; + index--; + var l:int = stack[index]; + var i:int = l; + var median:Number = depths[(r + l) >> 1]; + while (i <= j) { + var left:Number = depths[i]; + while (left > median) { + i++; + left = depths[i]; + } + var right:Number = depths[j]; + while (right < median) { + j--; + right = depths[j]; + } + if (i <= j) { + depths[i] = right; + depths[j] = left; + var surface:Surface = surfaces[i]; + surfaces[i] = surfaces[j]; + surfaces[j] = surface; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + } + + private function processOverOut(mouseEvent:MouseEvent, camera:Camera3D):void { + branchToVector(target, branch); + branchToVector(overedTarget, overedBranch); + var branchLength:int = branch.length; + var overedBranchLength:int = overedBranch.length; + var changedBranchLength:int; + var i:int; + var j:int; + var object:Object3D; + if (overedTarget != null) { + propagateEvent(MouseEvent3D.MOUSE_OUT, mouseEvent, camera, overedTarget, overedTargetSurface, overedBranch, true, target); + changedBranchLength = 0; + for (i = 0; i < overedBranchLength; i++) { + object = overedBranch[i]; + for (j = 0; j < branchLength; j++) if (object == branch[j]) break; + if (j == branchLength) { + changedBranch[changedBranchLength] = object; + changedBranchLength++; + } + } + if (changedBranchLength > 0) { + changedBranch.length = changedBranchLength; + propagateEvent(MouseEvent3D.ROLL_OUT, mouseEvent, camera, overedTarget, overedTargetSurface, changedBranch, false, target); + } + } + if (target != null) { + changedBranchLength = 0; + for (i = 0; i < branchLength; i++) { + object = branch[i]; + for (j = 0; j < overedBranchLength; j++) if (object == overedBranch[j]) break; + if (j == overedBranchLength) { + changedBranch[changedBranchLength] = object; + changedBranchLength++; + } + } + if (changedBranchLength > 0) { + changedBranch.length = changedBranchLength; + propagateEvent(MouseEvent3D.ROLL_OVER, mouseEvent, camera, target, targetSurface, changedBranch, false, overedTarget); + } + propagateEvent(MouseEvent3D.MOUSE_OVER, mouseEvent, camera, target, targetSurface, branch, true, overedTarget); + useHandCursor = target.useHandCursor; + } else { + useHandCursor = false; + } + Mouse.cursor = Mouse.cursor; + overedTarget = target; + overedTargetSurface = targetSurface; + } + + private function branchToVector(object:Object3D, vector:Vector.):Vector. { + var len:int = 0; + while (object != null) { + vector[len] = object; + len++; + object = object._parent; + } + vector.length = len; + return vector; + } + + private function propagateEvent(type:String, mouseEvent:MouseEvent, camera:Camera3D, target:Object3D, targetSurface:Surface, objects:Vector., bubbles:Boolean = true, relatedObject:Object3D = null):void { + var oblectsLength:int = objects.length; + var object:Object3D; + var vector:Vector.; + var length:int; + var i:int; + var j:int; + var mouseEvent3D:MouseEvent3D; + // Capture + for (i = oblectsLength - 1; i > 0; i--) { + object = objects[i]; + if (object.captureListeners != null) { + vector = object.captureListeners[type]; + if (vector != null) { + if (mouseEvent3D == null) { + calculateLocalCoords(camera, target.cameraToLocalTransform, targetDepth, mouseEvent); + mouseEvent3D = new MouseEvent3D(type, bubbles, localCoords.x, localCoords.y, localCoords.z, relatedObject, mouseEvent.ctrlKey, mouseEvent.altKey, mouseEvent.shiftKey, mouseEvent.buttonDown, mouseEvent.delta); + mouseEvent3D._target = target; + mouseEvent3D._surface = targetSurface; + } + mouseEvent3D._currentTarget = object; + mouseEvent3D._eventPhase = 1; + length = vector.length; + for (j = 0; j < length; j++) functions[j] = vector[j]; + for (j = 0; j < length; j++) { + (functions[j] as Function).call(null, mouseEvent3D); + if (mouseEvent3D.stopImmediate) return; + } + if (mouseEvent3D.stop) return; + } + } + } + // Bubble + for (i = 0; i < oblectsLength; i++) { + object = objects[i]; + if (object.bubbleListeners != null) { + vector = object.bubbleListeners[type]; + if (vector != null) { + if (mouseEvent3D == null) { + calculateLocalCoords(camera, target.cameraToLocalTransform, targetDepth, mouseEvent); + mouseEvent3D = new MouseEvent3D(type, bubbles, localCoords.x, localCoords.y, localCoords.z, relatedObject, mouseEvent.ctrlKey, mouseEvent.altKey, mouseEvent.shiftKey, mouseEvent.buttonDown, mouseEvent.delta); + mouseEvent3D._target = target; + mouseEvent3D._surface = targetSurface; + } + mouseEvent3D._currentTarget = object; + mouseEvent3D._eventPhase = (i == 0) ? 2 : 3; + length = vector.length; + for (j = 0; j < length; j++) functions[j] = vector[j]; + for (j = 0; j < length; j++) { + (functions[j] as Function).call(null, mouseEvent3D); + if (mouseEvent3D.stopImmediate) return; + } + if (mouseEvent3D.stop) return; + } + } + } + } + + private function calculateLocalCoords(camera:Camera3D, transform:Transform3D, z:Number, mouseEvent:MouseEvent):void { + var x:Number; + var y:Number; + if (!camera.orthographic) { + x = z*(mouseEvent.localX - _width*0.5)/camera.focalLength; + y = z*(mouseEvent.localY - _height*0.5)/camera.focalLength; + } else { + x = mouseEvent.localX - _width*0.5; + y = mouseEvent.localY - _height*0.5; + } + localCoords.x = transform.a*x + transform.b*y + transform.c*z + transform.d; + localCoords.y = transform.e*x + transform.f*y + transform.g*z + transform.h; + localCoords.z = transform.i*x + transform.j*y + transform.k*z + transform.l; + } + + private function defineTarget(index:int):void { + var source:Object3D; + // Get surfaces + var surfaces:Vector. = raysSurfaces[index]; + var depths:Vector. = raysDepths[index]; + // Loop surfaces + for (var i:int = surfaces.length - 1; i >= 0; i--) { + var surface:Surface = surfaces[i]; + var depth:Number = depths[i]; + var object:Object3D = surface.object; + var potentialTarget:Object3D = null; + var obj:Object3D; + // Get possible target + for (obj = object; obj != null; obj = obj._parent) { + if (!obj.mouseChildren) potentialTarget = null; + if (potentialTarget == null && obj.mouseEnabled) potentialTarget = obj; + } + // If possible target found + if (potentialTarget != null) { + if (target != null) { + for (obj = potentialTarget; obj != null; obj = obj._parent) { + if (obj == target) { + source = object; + if (target != potentialTarget) { + target = potentialTarget; + targetSurface = surface; + targetDepth = depth; + } + break; + } + } + } else { + source = object; + target = potentialTarget; + targetSurface = surface; + targetDepth = depth; + } + if (source == target) break; + } + } + } + + /** + * If true, image will render to Bitmap object which will included into the view as a child. It also will available through canvas property. + * + * @see #canvas + */ + public function get renderToBitmap():Boolean { + return _canvas != null; + } + + /** + * @private + */ + public function set renderToBitmap(value:Boolean):void { + if (value) { + if (_canvas == null) createRenderBitmap(); + } else { + if (_canvas != null) { + container.bitmapData = null; + _canvas.dispose(); + _canvas = null; + } + } + } + + /** + * BitmapData with rendered image in case of renderToBitmap turned on. + * + * @see #renderToBitmap + */ + public function get canvas():BitmapData { + return _canvas; + } + + /** + * Places Alternativa3D logo into the view. + */ + public function showLogo():void { + if (logo == null) { + logo = new Logo(); + super.addChild(logo); + resizeLogo(); + } + } + + /** + * Places Alternativa3D logo from the view. + */ + public function hideLogo():void { + if (logo != null) { + super.removeChild(logo); + logo = null; + } + } + + /** + * Alinging the logo. Constants of StageAlign class can be used as a value to set. + */ + public function get logoAlign():String { + return _logoAlign; + } + + /** + * @private + */ + public function set logoAlign(value:String):void { + _logoAlign = value; + resizeLogo(); + } + + /** + * Horizontal margin. + */ + public function get logoHorizontalMargin():Number { + return _logoHorizontalMargin; + } + + /** + * @private + */ + public function set logoHorizontalMargin(value:Number):void { + _logoHorizontalMargin = value; + resizeLogo(); + } + + /** + * Vertical margin. + */ + public function get logoVerticalMargin():Number { + return _logoVerticalMargin; + } + + /** + * @private + */ + public function set logoVerticalMargin(value:Number):void { + _logoVerticalMargin = value; + resizeLogo(); + } + + private function resizeLogo():void { + if (logo != null) { + if (_logoAlign == StageAlign.TOP_LEFT || _logoAlign == StageAlign.LEFT || _logoAlign == StageAlign.BOTTOM_LEFT) { + logo.x = Math.round(_logoHorizontalMargin); + } + if (_logoAlign == StageAlign.TOP || _logoAlign == StageAlign.BOTTOM) { + logo.x = Math.round((_width - logo.width)/2); + } + if (_logoAlign == StageAlign.TOP_RIGHT || _logoAlign == StageAlign.RIGHT || _logoAlign == StageAlign.BOTTOM_RIGHT) { + logo.x = Math.round(_width - _logoHorizontalMargin - logo.width); + } + if (_logoAlign == StageAlign.TOP_LEFT || _logoAlign == StageAlign.TOP || _logoAlign == StageAlign.TOP_RIGHT) { + logo.y = Math.round(_logoVerticalMargin); + } + if (_logoAlign == StageAlign.LEFT || _logoAlign == StageAlign.RIGHT) { + logo.y = Math.round((_height - logo.height)/2); + } + if (_logoAlign == StageAlign.BOTTOM_LEFT || _logoAlign == StageAlign.BOTTOM || _logoAlign == StageAlign.BOTTOM_RIGHT) { + logo.y = Math.round(_height - _logoVerticalMargin - logo.height); + } + } + } + + /** + * Width of this View. Should be 50 at least. + */ + override public function get width():Number { + return _width; + } + + /** + * @private + */ + override public function set width(value:Number):void { + if (value < 50) value = 50; + _width = value; + area.width = value; + resizeLogo(); + } + + /** + * Height of this View. Should be 50 at least. + */ + override public function get height():Number { + return _height; + } + + /** + * @private + */ + override public function set height(value:Number):void { + if (value < 50) value = 50; + _height = value; + area.height = value; + resizeLogo(); + } + + /** + * @private + */ + override public function addChild(child:DisplayObject):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function removeChild(child:DisplayObject):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function addChildAt(child:DisplayObject, index:int):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function removeChildAt(index:int):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function removeChildren(beginIndex:int = 0, endIndex:int = 2147483647):void { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function getChildAt(index:int):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function getChildIndex(child:DisplayObject):int { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function setChildIndex(child:DisplayObject, index:int):void { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function swapChildren(child1:DisplayObject, child2:DisplayObject):void { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function swapChildrenAt(index1:int, index2:int):void { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function get numChildren():int { + return 0; + } + + /** + * @private + */ + override public function getChildByName(name:String):DisplayObject { + throw new Error("Unsupported operation."); + } + + /** + * @private + */ + override public function contains(child:DisplayObject):Boolean { + throw new Error("Unsupported operation."); + } + + } +} + +import alternativa.engine3d.materials.ShaderProgram; +import alternativa.engine3d.resources.Geometry; + +import flash.display.BitmapData; +import flash.display.Sprite; +import flash.events.MouseEvent; +import flash.geom.ColorTransform; +import flash.geom.Matrix; +import flash.net.URLRequest; +import flash.net.navigateToURL; +import flash.utils.Dictionary; + +class Logo extends Sprite { + + static public const image:BitmapData = createBMP(); + + static private function createBMP():BitmapData { + + var bmp:BitmapData = new BitmapData(165, 27, true, 0); + + bmp.setVector(bmp.rect, Vector.([ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,134217728,503316480,721420288,503316480,134217728,134217728,503316480,721420288,503316480,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100663296,419430400,721420288,788529152,536870912,234881024,50331648,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1677721600,503316480,503316480,1677721600,2348810240,1677721600,503316480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,67108864,301989888,822083584,1677721600,2365587456,2483027968,1996488704,1241513984,536870912,117440512,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16777216,167772160,520093696,822083584,905969664,822083584,520093696,301989888,520093696,822083584,905969664,822083584,620756992,620756992,721420288,620756992,620756992,721420288,620756992,620756992,721420288,620756992,620756992,822083584,905969664,822083584,520093696,218103808,234881024,536870912,721420288,620756992,620756992,822083584,905969664,822083584,520093696,301989888,520093696,822083584,1493172224,2768240640,4292467161,2533359616,822083584,822083584,2533359616,4292467161,2768240640,1493172224,822083584,620756992,620756992,721420288,503316480,268435456,503316480,721420288,503316480,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,134217728,620756992,1392508928,2248146944,3514129719,4192520610,4277921461,3886715221,2905283846,1778384896,788529152,234881024,50331648,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,167772160,822083584,1845493760,2533359616,2734686208,2533359616,1845493760,1325400064,1845493760,2533359616,2734686208,2533359616,2164260864,2164260864,2348810240,2164260864,2164260864,2348810240,2164260864,2164260864,2348810240,2164260864,2164260864,2533359616,2734686208,2533359616,1845493760,1056964608,1107296256,1895825408,2348810240,2164260864,2164260864,2533359616,2734686208,2533359616,1845493760,1325400064,1845493760,2533359616,2952790016,3730463322,4292467161,2734686208,905969664,905969664,2734686208,4292467161,3730463322,2952790016,2533359616,2164260864,2164260864,2348810240,1677721600,989855744,1677721600,2348810240,1677721600,503316480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,16777216,167772160,754974720,1828716544,3022988562,4022445697,4294959283,4294953296,4294953534,4294961056,4226733479,3463135252,2130706432,1224736768,486539264,83886080,0,0,0,0,0,0,0,0,0,0,0,0,0,0,520093696,1845493760,3665591420,4292467161,4292467161,4292467161,3665591420,2650800128,3665591420,4292467161,4292467161,4292467161,3816191606,3355443200,4292467161,3355443200,3355443200,4292467161,3355443200,3355443200,4292467161,3355443200,3816191606,4292467161,4292467161,4292467161,3665591420,2382364672,2415919104,3801125008,4292467161,3355443200,3816191606,4292467161,4292467161,4292467161,3495911263,2650800128,3665591420,4292467161,4292467161,4292467161,4292467161,2533359616,822083584,822083584,2533359616,4292467161,4292467161,4292467161,4292467161,3816191606,3355443200,4292467161,2533359616,1627389952,2533359616,4292467161,2533359616,822083584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,50331648,251658240,889192448,1962934272,3463338042,4260681651,4294955128,4294949388,4294949120,4294948864,4294948864,4294953816,4294960063,3903219779,2701722370,1627389952,620756992,100663296,0,0,0,0,0,0,0,0,0,0,0,0,0,822083584,2533359616,4292467161,3730463322,3187671040,3730463322,4292467161,3456106496,4292467161,3849680245,3221225472,3849680245,4292467161,3640655872,4292467161,3640655872,3640655872,4292467161,3640655872,3640655872,4292467161,3640655872,4292467161,3966923378,3640655872,3966923378,4292467161,3355443200,3918236555,4292467161,3763951961,3539992576,4292467161,3966923378,3640655872,3966923378,4292467161,3456106496,4292467161,3849680245,3221225472,3422552064,3456106496,2348810240,721420288,721420288,2348810240,3456106496,3422552064,3221225472,3849680245,4292467161,3640655872,4292467161,2734686208,1828716544,2734686208,4292467161,2734686208,905969664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,50331648,318767104,1006632960,2080374784,3683940948,4294958002,4294949951,4294946816,4294946048,4294944256,4294944256,4294945536,4294944512,4294944799,4294954914,4123823487,3056010753,1778384896,671088640,117440512,0,0,0,0,0,0,0,0,0,0,0,0,822083584,2533359616,4292467161,3187671040,2734686208,3187671040,4292467161,3640655872,4292467161,3221225472,2801795072,3221225472,4292467161,3640655872,4292467161,3966923378,3640655872,4292467161,3966923378,3640655872,4292467161,3640655872,4292467161,3640655872,4292467161,4292467161,4292467161,3640655872,4292467161,3613154396,2818572288,3221225472,4292467161,3640655872,4292467161,4292467161,4292467161,3640655872,4292467161,3221225472,2801795072,3221225472,4292467161,2533359616,822083584,822083584,2533359616,4292467161,3221225472,2801795072,3221225472,4292467161,3640655872,4292467161,2952790016,2264924160,2952790016,4292467161,2533359616,822083584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,50331648,318767104,1056964608,2147483648,3819605095,4294955172,4294944795,4294943744,4294941184,4294939392,4294940672,4294940160,4294938624,4294941440,4294940672,4294936323,4294815095,4208955271,3208382211,1845493760,721420288,134217728,0,0,0,0,0,0,0,0,0,0,0,721420288,2348810240,3456106496,3405774848,3187671040,3730463322,4292467161,3456106496,4292467161,3849680245,3221225472,3849680245,4292467161,3355443200,3816191606,4292467161,3966923378,3966923378,4292467161,3966923378,4292467161,3640655872,4292467161,3966923378,3640655872,3640655872,3640655872,3640655872,4292467161,2868903936,1996488704,2684354560,4292467161,3966923378,3640655872,3640655872,3539992576,3456106496,4292467161,3849680245,3221225472,3849680245,4292467161,2533359616,822083584,822083584,2533359616,4292467161,3849680245,3221225472,3849680245,4292467161,3456106496,4292467161,3730463322,3187671040,3405774848,3456106496,2348810240,721420288,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,16777216,234881024,989855744,2147483648,3836647021,4294952084,4294939916,4294939392,4294936064,4294935808,4294939907,3970992676,3783616794,4260594952,4294933248,4294937088,4294937088,4294865664,4294676569,4243165579,3292924164,1862270976,721420288,134217728,0,0,0,0,0,0,0,0,0,0,822083584,2533359616,4292467161,4292467161,4292467161,4292467161,3665591420,2650800128,3665591420,4292467161,4292467161,4292467161,3665591420,2348810240,2348810240,3665591420,4292467161,3355443200,3816191606,4292467161,4292467161,3355443200,3816191606,4292467161,4292467161,4292467161,3696908890,3355443200,4292467161,2533359616,1325400064,1845493760,3665591420,4292467161,4292467161,4292467161,3665591420,2650800128,3665591420,4292467161,4292467161,4292467161,3665591420,1845493760,520093696,520093696,1845493760,3665591420,4292467161,4292467161,4292467161,3665591420,2650800128,3665591420,4292467161,4292467161,4292467161,4292467161,2533359616,822083584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,150994944,855638016,2063597568,3785853032,4294949263,4294935301,4294934528,4294931200,4294865408,4294739211,3598869795,2348810240,2248146944,3157861897,4158024716,4294930432,4294934272,4294934016,4294796032,4294604868,4260400774,3309963524,1862270976,704643072,117440512,0,0,0,0,0,0,0,0,0,905969664,2734686208,4292467161,3730463322,2952790016,2533359616,1845493760,1325400064,1845493760,2533359616,2734686208,2533359616,1845493760,1006632960,1006632960,1845493760,2348810240,2164260864,2164260864,2533359616,2533359616,2164260864,2164260864,2533359616,2734686208,2533359616,2164260864,2164260864,2348810240,1677721600,671088640,822083584,1845493760,2533359616,2734686208,2533359616,1845493760,1325400064,1845493760,2533359616,2734686208,2533359616,1845493760,822083584,167772160,167772160,822083584,1845493760,2533359616,2734686208,2533359616,1845493760,1325400064,1845493760,2533359616,2952790016,3730463322,4292467161,2734686208,905969664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,117440512,738197504,1962934272,3632951638,4294947982,4294931462,4294930176,4294794752,4294662144,4260327185,3378071325,1946157056,922746880,822083584,1677721600,2785937666,3954400527,4294929408,4294931968,4294931712,4294661120,4294469180,4260200571,3208316675,1795162112,620756992,83886080,0,0,0,0,0,0,0,0,822083584,2533359616,4292467161,2768240640,1493172224,822083584,520093696,301989888,520093696,822083584,905969664,822083584,520093696,184549376,184549376,520093696,721420288,620756992,620756992,822083584,822083584,620756992,620756992,822083584,905969664,822083584,620756992,620756992,721420288,503316480,150994944,167772160,520093696,822083584,905969664,822083584,520093696,301989888,520093696,822083584,905969664,822083584,520093696,167772160,16777216,16777216,167772160,520093696,822083584,905969664,822083584,520093696,301989888,520093696,822083584,1493172224,2768240640,4292467161,2533359616,822083584,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,67108864,620756992,1811939328,3429059385,4294882972,4294796301,4294727936,4294526208,4294325760,4226241553,3242276118,1862270976,738197504,150994944,100663296,520093696,1325400064,2264924160,3768667144,4294928385,4294929408,4294796800,4294460416,4294335293,4225986666,3055813377,1644167168,503316480,50331648,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1677721600,503316480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1677721600,503316480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,16777216,335544320,1459617792,3005750036,4243500445,4294661403,4294524672,4294258432,4294121728,4259985678,3259118102,1845493760,704643072,134217728,0,0,50331648,335544320,1006632960,2080374784,3751757574,4294794241,4294794240,4294592771,4294323463,4294400588,4123811671,2769158144,1275068416,251658240,0,0,0,0,0,0,0,134217728,503316480,721420288,503316480,134217728,0,0,0,0,134217728,503316480,721420288,503316480,268435456,503316480,721420288,503316480,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,134217728,503316480,721420288,503316480,134217728,0,0,0,0,134217728,503316480,721420288,503316480,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,134217728,503316480,721420288,520093696,167772160,16777216,0,0,0,0,0,0,0,0,134217728,503316480,721420288,520093696,234881024,285212672,570425344,687865856,436207616,117440512,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,150994944,922746880,2348810240,4056321414,4294197820,4294119936,4294056448,4293921536,4293991688,3394978333,1879048192,704643072,117440512,0,0,0,0,33554432,268435456,1023410176,2248146944,3869450497,4293927168,4293661957,4293331976,4293330946,4293609799,3936365867,2181038080,822083584,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1744830464,1140850688,1744830464,2348810240,1744830464,637534208,67108864,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1744830464,637534208,67108864,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1811939328,771751936,150994944,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1811939328,1040187392,1207959552,1979711488,2248146944,1509949440,436207616,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,50331648,620756992,1879048192,3649264467,4294272360,4293853184,4293920000,4293920000,4293918720,3649195041,1979711488,754974720,134217728,0,0,0,0,0,67108864,335544320,1023410176,2080374784,3036676096,4088070144,4292476928,4292608000,4292739072,4292804608,4293347915,3581022738,1879048192,654311424,83886080,0,0,0,0,0,0,0,0,50331648,201326592,335544320,201326592,50331648,0,822083584,2533359616,4294967295,3261885548,2080374784,2768240640,4294967295,3261885548,1258291200,234881024,117440512,402653184,671088640,687865856,469762048,184549376,33554432,0,83886080,318767104,419430400,352321536,469762048,620756992,620756992,520093696,335544320,150994944,50331648,0,0,50331648,201326592,335544320,201326592,50331648,0,822083584,2533359616,4294967295,3295439980,1610612736,872415232,520093696,318767104,301989888,167772160,33554432,0,33554432,167772160,301989888,167772160,33554432,50331648,201326592,335544320,201326592,50331648,0,0,0,50331648,184549376,469762048,704643072,704643072,469762048,184549376,855638016,2533359616,4294809856,3566287616,1493172224,335544320,0,0,50331648,234881024,402653184,234881024,50331648,0,822083584,2550136832,4294809856,3583064832,2147483648,2382364672,3921236224,4209802240,2181038080,687865856,184549376,469762048,704643072,704643072,469762048,184549376,50331648,50331648,234881024,520093696,671088640,704643072,822083584,889192448,771751936,721420288,805306368,771751936,520093696,234881024,50331648,0, + 0,0,0,268435456,1358954496,3023117852,4260334217,4293854213,4293919488,4293921024,4293853184,4055516443,2348810240,939524096,150994944,0,0,0,0,33554432,201326592,671088640,1442840576,2264924160,3513790764,3356295425,3473866752,4207017984,4292673536,4292804608,4292870144,4292937479,4276240705,3174499075,1610612736,419430400,0,0,0,0,0,0,0,83886080,452984832,1157627904,1577058304,1174405120,486539264,83886080,905969664,2734686208,4294967295,3479528805,2533359616,3087007744,4294967295,3429394536,1543503872,520093696,754974720,1610612736,2248146944,2298478592,1845493760,1107296256,385875968,150994944,587202560,1409286144,1644167168,1442840576,1761607680,2147483648,2147483648,1962934272,1593835520,1040187392,385875968,50331648,83886080,452984832,1157627904,1577058304,1174405120,486539264,234881024,1191182336,2868903936,4294967295,3630326370,2734686208,2432696320,1962934272,1526726656,1392508928,822083584,167772160,0,167772160,822083584,1392508928,1073741824,436207616,503316480,1157627904,1577058304,1174405120,486539264,83886080,0,83886080,452984832,1140850688,1845493760,2315255808,2315255808,1845493760,1140850688,1375731712,2818572288,4294804480,3666292992,1744830464,419430400,0,100663296,520093696,1275068416,1694498816,1291845632,536870912,234881024,1191182336,2868903936,4294804480,3783471360,2952790016,3768006912,4294606336,3681495040,2130706432,1023410176,1140850688,1845493760,2315255808,2315255808,1845493760,1140850688,469762048,335544320,1006632960,1879048192,2248146944,2298478592,2533359616,2667577344,2449473536,2332033024,2499805184,2449473536,1962934272,1191182336,419430400,50331648, + 0,0,83886080,754974720,2181038080,3971250292,4293995053,4293853184,4293855488,4293591040,4208406034,2938050314,1426063360,335544320,16777216,0,0,50331648,234881024,620756992,1207959552,2013265920,3107396370,4055000155,4293554803,4003672881,3221225472,3544186880,4292673536,4292804608,4292870144,4292870144,4293006099,4122616354,2600796160,1023410176,134217728,0,0,0,0,0,67108864,520093696,1560281088,2685209869,3768886436,2736133654,1644167168,603979776,1006632960,2734686208,4294967295,3630326370,2952790016,3630326370,4294967295,3429394536,1711276032,1191182336,1979711488,3531768450,4140814287,4140945873,3801519766,2584349194,1493172224,1006632960,1728053248,3380576127,3769610159,2734686208,3752175013,4022386880,4022386880,3886721706,3513806960,2739028546,1140850688,285212672,520093696,1560281088,2685209869,3768886436,2736133654,1644167168,1107296256,1996488704,3579994722,4294967295,3780992349,3456106496,4294967295,3596837731,3173130786,3600916897,1828716544,553648128,50331648,553648128,1828716544,3600916897,2620009002,1509949440,1694498816,2685209869,3768886436,2736133654,1644167168,603979776,150994944,503316480,1543503872,2889486848,3767873792,4175254272,4175254272,3767873792,2889486848,2399141888,3120562176,4294798592,3632408576,1694498816,402653184,83886080,587202560,1644167168,2786133248,3870512384,2870872320,1711276032,1140850688,2013265920,3682543616,4294798592,3866764800,3456106496,4294798592,4054399232,3338665984,2516582400,2097152000,2889486848,3767873792,4175254272,4175254272,3767873792,2889486848,1660944384,1275068416,2097152000,3836689152,4192360448,3422552064,4294798592,4294798592,4192360448,3970513152,4294798592,4192360448,3903601152,2788757760,1174405120,234881024, + 0,0,335544320,1493172224,3259970090,4294206305,4293591040,4293263872,4292935936,4292806915,3648730389,1862270976,620756992,117440512,117440512,285212672,553648128,922746880,1392508928,2063597568,2938704652,3834466116,4276189301,4292948534,4293067009,4293740360,3732214294,3187671040,3918004480,4293132288,4293066752,4292935680,4292870144,4293073434,3681683208,1879048192,536870912,33554432,0,0,0,16777216,402653184,1509949440,2869890831,4106733511,4277729528,4157920468,3022135842,1644167168,1392508928,2751463424,4294967295,3730726494,3271557120,4294967295,4294967295,3429394536,2030043136,2147483648,3768820643,4209699562,3832574064,3815599469,4260820726,3970410407,2936999695,2499805184,3464001656,4022189501,3578678862,3355443200,4294967295,3747569503,3426828609,3426828609,4004030632,3784347792,1996488704,922746880,1509949440,2869890831,4106733511,4277729528,4157920468,3022135842,2348810240,2768240640,4294967295,4294967295,3847969627,3640655872,4294967295,3847969627,4020084125,4260820726,2888510251,1157627904,335544320,1157627904,2888510251,4260820726,3852772516,2734686208,3004174352,4106733511,4277729528,4157920468,3022135842,1644167168,721420288,1275068416,3058897920,4106697728,4294726912,4020186368,4020186368,4294726912,4106697728,3561427200,3489660928,4294792448,3598458880,1660944384,402653184,452984832,1577058304,2937455616,4158017536,4277752576,4192228608,3090025216,2382364672,2801795072,4294792448,4294792448,3916570368,3640655872,4294792448,4294792448,4294792448,3170893824,3460961024,4106697728,4294726912,4020186368,4020186368,4294726912,4106697728,3259962368,2617245696,3649906432,4277752576,3730839552,3539992576,4294792448,3849592320,3679458560,4277752576,4037094656,3813479168,4277752576,3886493184,1996488704,536870912, + 0,67108864,788529152,2281701376,4072894821,4293201424,4292870144,4292804608,4292608000,4156898324,2634022912,1392508928,822083584,905969664,1140850688,1476395008,2013265920,2617573888,3292798484,3902098491,4241976422,4292888908,4292806923,4293001216,4293263360,4293525760,4294260515,3510376966,3305308160,4191289856,4293394432,4293066752,4292935680,4293001473,4242215445,2871133184,1191182336,201326592,0,0,0,268435456,1291845632,2701723913,4072652735,4260754933,3782702967,4175355614,4158446812,2870877726,2264924160,3019898880,4294967295,3630326370,2952790016,3305111552,4294967295,3462817382,2399141888,3243660886,4277795321,3764675684,3372220416,3405774848,3780071247,4174829270,3729542220,3456106496,4294967295,3801256594,2885681152,3271557120,4294967295,3546506083,2298478592,2348810240,3648616825,4294967295,2818572288,2097152000,2701723913,4072652735,4260754933,3782702967,4175355614,4158446812,3289979161,3137339392,3456106496,4294967295,3847969627,3640655872,4294967295,3780992349,3645064003,4226674157,3767833748,1996488704,1174405120,1996488704,3767833748,4243385580,3712107074,3473278470,4072652735,4260754933,3782702967,4175355614,4158446812,2870877726,1711276032,1811939328,3685360896,4124521216,3410759424,2920416000,2920416000,3427536640,4157944576,4105775616,3640655872,4294787072,3564509184,1627389952,637534208,1342177280,2785739264,4106956544,4260773120,3867745792,4209325824,4192286208,3323987200,3137339392,3456106496,4294787072,3899463168,3640655872,4294787072,3798931456,3204448256,3221225472,4055509504,4157944576,3427536640,2920416000,2920416000,3427536640,4157944576,4072286720,3456106496,4294787072,3886162944,2969567232,3305111552,4294787072,3698464768,2952790016,3783794432,4294787072,3640655872,3951172864,4294787072,2550136832,822083584, + 0,285212672,1426063360,3277007389,4293737023,4293066752,4292870144,4292739072,4241565196,3423471106,2717908992,2197815296,2264924160,2685010432,3005286916,3377536014,3851039012,4139536965,4292959323,4292818747,4292742414,4292804608,4292804608,4292935680,4293725440,4294590464,3954466066,2871071238,2466250752,3445623808,4294119168,4293525504,4293132288,4293001216,4293462024,3835439624,2030043136,654311424,67108864,0,50331648,788529152,2348941826,3851061898,4260886519,3529005144,3120562176,3525452322,4243451373,3902446234,3288926473,3439329280,4294967295,3513017444,2634022912,3137339392,4294967295,3496306021,2583691264,3717567893,4209041632,3489660928,4141406424,4141538010,4124168657,4192461795,3868365458,3523215360,4294967295,3462817382,2499805184,3070230528,4294967295,3429394536,1811939328,1845493760,3446040166,4294967295,3422552064,3221225472,3851061898,4260886519,3529005144,3120562176,3525452322,4243451373,3952580503,3490318858,3539992576,4294967295,3847969627,3640655872,4294967295,3630326370,2952790016,3869483939,4260689140,3123917619,2415919104,3123917619,4260689140,4020084125,3640655872,3968239238,4260886519,3529005144,3120562176,3525452322,4243451373,3902446234,2735475724,2113929216,2600468480,3019898880,2583691264,2063597568,2063597568,2667577344,3714912256,4294781952,3640655872,4294781952,3547336960,1728053248,1191182336,2382495744,3902150144,4277545472,3580038656,3120562176,3559458048,4243531776,3986889472,3490382080,3539992576,4294781952,3882225408,3640655872,4294781952,3614249216,2634022912,2919235584,4294781952,3714912256,2667577344,2063597568,2063597568,2667577344,3714912256,4294781952,3640655872,4294781952,3547336960,2583691264,3120562176,4294781952,3547336960,2566914048,3547336960,4294781952,3640655872,3882225408,4294781952,2734686208,905969664, + 50331648,687865856,2164260864,4004986156,4293526023,4293132288,4292804608,4292739072,4190049031,3866102791,3816952331,3868467732,4003800346,4156500256,4275644710,4292683559,4292682277,4292679193,4292739073,4292739072,4292608000,4292411392,4292673536,4293661184,4174923273,3446360857,2164260864,1291845632,1191182336,2332033024,4073140736,4294119168,4293525504,4293066752,4293263360,4276230916,3260750080,1392508928,318767104,16777216,184549376,1275068416,3157340465,4274439878,3678486849,3120562176,3289452817,3848232799,4120681628,4291611852,3711251765,3456106496,4291611852,3799348597,2919235584,3288334336,4291611852,3461435729,2499805184,3410446151,4206212533,3778952766,3591574291,3388997632,3305111552,3170893824,3036676096,3372220416,4291611852,3427947090,2415919104,3019898880,4291611852,3427947090,1795162112,1795162112,3427947090,4291611852,3640655872,3760793897,4274439878,3678486849,3120562176,3289452817,3848232799,4120681628,4291611852,3795137845,3640655872,4291611852,3846785353,3640655872,4291611852,3478212945,2399141888,3073322799,4172394929,4121339558,3491832097,4121339558,4172394929,3542690089,3643353385,4274439878,3678486849,3120562176,3289452817,3848232799,4120681628,4291611852,3510188345,2785017856,3582069760,4090368512,3376612608,2920218624,2920218624,3393389824,4123791872,4037807872,3456106496,4294514688,3835038720,2231369728,1862270976,3191800832,4277278208,3696690944,3137339392,3323461888,3883600384,4140044544,4294514688,3813017088,3640655872,4294514688,3865118720,3640655872,4294514688,3496610048,2130706432,2399141888,3954183936,4123791872,3393389824,2920218624,2920218624,3393389824,4123791872,4071296768,3640655872,4294514688,3496610048,2466250752,3053453312,4294514688,3496610048,2466250752,3496610048,4294514688,3640655872,3865118720,4294514688,2734686208,905969664, + 201326592,1224736768,3023639812,4277017613,4293656576,4293263360,4292870144,4292804608,4292870144,4292871176,4292939794,4292939796,4292873232,4292871689,4292739587,4292804608,4292804608,4292673536,4292542464,4292345856,4292542464,4293133568,4157219336,3665638928,2651785731,1677721600,771751936,251658240,385875968,1526726656,3327729408,4294721792,4294119168,4293591040,4293197824,4293925893,3987551235,2248146944,872415232,134217728,385875968,1677721600,3630721128,4291546059,3813757265,3849088108,4189172145,4154893990,3881063508,4154762404,3781387107,3070230528,3629931612,4257728455,3661512254,3539992576,4291611852,3427947090,2231369728,2769885465,3934158462,4205949361,3847311697,3643419178,3729081669,4037585064,2902458368,3288334336,4291611852,3427947090,2415919104,3019898880,4291611852,3427947090,1795162112,1795162112,3427947090,4291611852,3640655872,3932250465,4291546059,3813757265,3849088108,4189172145,4154893990,3881063508,4154762404,3915275870,3640655872,4291611852,3846785353,3640655872,4291611852,3427947090,1929379840,1946157056,3309980234,4206541498,4274374085,4206541498,3427289160,2835349504,3731187045,4291546059,3813757265,3849088108,4189172145,4154893990,3881063508,4154762404,3865075808,3456106496,4294511104,4088530432,4294380032,3968205824,3968205824,4294380032,4038395392,3158180096,2533359616,3531015424,4260563200,3310682880,2583691264,3665954048,4294445568,3815047936,3867608064,4191881216,4157409024,3899130624,4157277952,3933602816,3640655872,4294511104,3864789504,3640655872,4294511104,3462923264,1778384896,1577058304,2957115648,4038395392,4294380032,3968205824,3968205824,4294380032,4038395392,3493396736,3472883712,4294511104,3462923264,2449473536,3036676096,4294511104,3462923264,2449473536,3462923264,4294511104,3640655872,3864789504,4294511104,2734686208,905969664, + 436207616,1795162112,3784914178,4294453760,4293985792,4293525504,4293263360,4293066752,4293001216,4292870144,4292870144,4292870144,4292804608,4292739072,4292608000,4292411392,4292411392,4292411392,4292804608,4276096257,4055439621,3462337541,2617967874,1778384896,1006632960,436207616,100663296,0,83886080,822083584,2332033024,4107292928,4294722560,4294251776,4293656576,4293856768,4276101633,3515097344,1342177280,234881024,436207616,1711276032,3817968017,4206673084,4223647679,4019952539,3613812326,3157077293,2869101315,3461238350,4037716650,2516582400,2365587456,3614865014,4104759721,3405774848,4291611852,3260503895,1560281088,1526726656,2703500324,3630786921,4036861341,4240490688,3867575942,2973514812,2231369728,2885681152,4291611852,3260503895,2080374784,2768240640,4291611852,3260503895,1493172224,1493172224,3260503895,4291611852,3372220416,3951922573,4206673084,4223647679,4019952539,3613812326,3157077293,2869101315,3461238350,4087982505,3405774848,4291611852,3662499149,3355443200,4291611852,3260503895,1358954496,872415232,1795162112,3224646708,4257662662,3224646708,2147483648,2147483648,3817968017,4206673084,4223647679,4019952539,3613812326,3157077293,2869101315,3461238350,4104628135,3590324224,4294508544,3815702528,3698589696,4073127936,4073127936,3598123008,2720661504,1543503872,1107296256,1929379840,3616538624,4057071616,2801795072,3870294016,4209246208,4226351104,4022140928,3615162368,3157852160,2869100544,3462266880,4090298368,3405774848,4294508544,3663659008,3355443200,4294508544,3261661184,1325400064,687865856,1476395008,2720661504,3598123008,4073127936,4073127936,3598123008,2720661504,2231369728,2902458368,4294508544,3261661184,2080374784,2768240640,4294508544,3261661184,2080374784,3261661184,4294508544,3355443200,3663659008,4294508544,2533359616,822083584, + 520093696,1962934272,4022817026,4294132225,4294521600,4294253056,4293920768,4293591296,4293197824,4293132288,4292935680,4292804608,4292804608,4292804608,4292935936,4293135104,4242084099,4072214529,3597801734,3023440387,2231369728,1577058304,956301312,452984832,117440512,16777216,0,0,0,352321536,1627389952,3530502912,4294929408,4294791936,4294592513,4158276864,3444975876,2535986688,1006632960,150994944,234881024,1006632960,1929379840,2449473536,2483027968,2164260864,1694498816,1258291200,1174405120,1610612736,1929379840,1459617792,1124073472,1660944384,2130706432,2264924160,2348810240,1744830464,687865856,436207616,1023410176,1694498816,2197815296,2348810240,1962934272,1224736768,989855744,1761607680,2348810240,1744830464,1140850688,1744830464,2348810240,1744830464,721420288,721420288,1744830464,2348810240,2197815296,2164260864,2449473536,2483027968,2164260864,1694498816,1258291200,1174405120,1610612736,2063597568,2248146944,2348810240,2164260864,2164260864,2348810240,1744830464,637534208,184549376,704643072,1728053248,2281701376,1728053248,939524096,1124073472,1929379840,2449473536,2483027968,2164260864,1694498816,1258291200,1174405120,1610612736,2483027968,3305111552,4294508544,3563126784,2449473536,2214592512,2147483648,1677721600,1023410176,402653184,234881024,788529152,1660944384,1996488704,1828716544,2030043136,2449473536,2483027968,2164260864,1694498816,1258291200,1174405120,1610612736,2063597568,2248146944,2348810240,2164260864,2164260864,2348810240,1744830464,637534208,150994944,402653184,1023410176,1677721600,2147483648,2147483648,1677721600,1023410176,905969664,1744830464,2348810240,1744830464,1140850688,1744830464,2348810240,1744830464,1140850688,1744830464,2348810240,2164260864,2164260864,2348810240,1677721600,503316480, + 318767104,1375731712,3059226113,3699846145,3869130506,4022230030,4141306627,4226171904,4260378112,4260178176,4259914240,4191428864,4089652224,3936955141,3648853506,3361147392,2887846915,2248146944,1694498816,1191182336,721420288,335544320,83886080,16777216,0,0,0,0,0,117440512,989855744,2585332736,4039860480,3784984577,3226543360,2382364672,1728053248,989855744,318767104,33554432,50331648,234881024,536870912,771751936,788529152,620756992,385875968,167772160,134217728,352321536,520093696,369098752,234881024,436207616,620756992,671088640,721420288,503316480,134217728,33554432,150994944,385875968,637534208,721420288,520093696,234881024,184549376,503316480,721420288,503316480,268435456,503316480,721420288,503316480,134217728,134217728,503316480,721420288,637534208,620756992,771751936,788529152,620756992,385875968,167772160,134217728,352321536,587202560,671088640,721420288,620756992,620756992,721420288,503316480,134217728,0,134217728,469762048,687865856,469762048,167772160,234881024,536870912,771751936,788529152,620756992,385875968,167772160,134217728,352321536,1258291200,2734686208,4294508544,3278503936,1476395008,754974720,620756992,385875968,150994944,33554432,16777216,150994944,436207616,570425344,503316480,587202560,771751936,788529152,620756992,385875968,167772160,134217728,352321536,587202560,671088640,721420288,620756992,620756992,721420288,503316480,134217728,0,33554432,150994944,385875968,620756992,620756992,385875968,150994944,150994944,503316480,721420288,503316480,268435456,503316480,721420288,503316480,268435456,503316480,721420288,620756992,620756992,721420288,503316480,134217728, + 67108864,503316480,1224736768,1744830464,1979711488,2181038080,2382364672,2533359616,2634022912,2634022912,2600468480,2466250752,2298478592,2046820352,1728053248,1409286144,1073741824,704643072,385875968,167772160,50331648,0,0,0,0,0,0,0,0,16777216,419430400,1342177280,1979711488,1862270976,1342177280,822083584,419430400,150994944,33554432,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,503316480,1677721600,2348810240,1744830464,637534208,67108864,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,50331648,234881024,419430400,536870912,637534208,738197504,822083584,855638016,872415232,838860800,788529152,687865856,570425344,419430400,251658240,117440512,33554432,0,0,0,0,0,0,0,0,0,0,0,0,67108864,335544320,536870912,469762048,234881024,50331648,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,134217728,503316480,721420288,503316480,134217728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + ])); + return bmp; + } + + private var border:int = 5; + + public function Logo() { + graphics.beginFill(0xFF0000, 0); + graphics.drawRect(0, 0, image.width + border + border, image.height + border + border); + graphics.drawRect(border, border, image.width, image.height); + graphics.beginBitmapFill(image, new Matrix(1, 0, 0, 1, border, border), false, true); + graphics.drawRect(border, border, image.width, image.height); + + tabEnabled = false; + buttonMode = true; + useHandCursor = true; + + addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + addEventListener(MouseEvent.CLICK, onClick); + addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); + addEventListener(MouseEvent.MOUSE_OVER, onMouseMove); + addEventListener(MouseEvent.MOUSE_OUT, onMouseOut); + addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + } + + private function onMouseDown(e:MouseEvent):void { + e.stopPropagation(); + } + + private function onClick(e:MouseEvent):void { + e.stopPropagation(); + try { + navigateToURL(new URLRequest("http://alternativaplatform.com"), "_blank"); + } catch (e:Error) { + } + } + + private function onDoubleClick(e:MouseEvent):void { + e.stopPropagation(); + } + + private static const normal:ColorTransform = new ColorTransform(); + private static const highlighted:ColorTransform = new ColorTransform(1.1, 1.1, 1.1, 1); + + private function onMouseMove(e:MouseEvent):void { + e.stopPropagation(); + transform.colorTransform = highlighted; + } + + private function onMouseOut(e:MouseEvent):void { + e.stopPropagation(); + transform.colorTransform = normal; + } + + private function onMouseWheel(e:MouseEvent):void { + e.stopPropagation(); + } + +} + +class Context3DViewProperties { + // Key - vertex program of object, value - program. + public var drawDistancePrograms:Dictionary = new Dictionary(); + public var drawColoredRectProgram:ShaderProgram; + public var drawRectGeometry:Geometry; + +} diff --git a/src/alternativa/engine3d/core/events/Event3D.as b/src/alternativa/engine3d/core/events/Event3D.as new file mode 100644 index 0000000..c0c32f5 --- /dev/null +++ b/src/alternativa/engine3d/core/events/Event3D.as @@ -0,0 +1,140 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core.events { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.*; + + import flash.events.Event; + + use namespace alternativa3d; + public class Event3D extends Event { + + /** + * Defines the value of the type property of a added event object. + * @eventType added + */ + public static const ADDED:String = "added3D"; + + /** + * Defines the value of the type property of a removed event object. + + * @eventType removed + */ + public static const REMOVED:String = "removed3D"; + + + /** + * This class should be used as base class for all events, which can have Object3D as an event target. + * @param type + * @param bubbles + */ + public function Event3D(type:String, bubbles:Boolean = true) { + super(type, bubbles); + _bubbles = bubbles; + } + + /** + * @private + */ + alternativa3d var _target:Object3D; + + /** + * @private + */ + alternativa3d var _currentTarget:Object3D; + + /** + * @private + */ + alternativa3d var _bubbles:Boolean; + + /** + * @private + */ + alternativa3d var _eventPhase:uint = 3; + + /** + * @private + */ + alternativa3d var stop:Boolean = false; + + /** + * @private + */ + alternativa3d var stopImmediate:Boolean = false; + + /** + * Indicates whether an event is a bubbling event. If the event can bubble, this value is true; otherwise it is false. + */ + override public function get bubbles():Boolean { + return _bubbles; + } + + /** + * The current phase in the event flow. + */ + override public function get eventPhase():uint { + return _eventPhase; + } + + /** + * The event target. This property contains the target node. + */ + override public function get target():Object { + return _target; + } + + /** + * The object that is actively processing the Event object with an event listener. + */ + override public function get currentTarget():Object { + return _currentTarget; + } + + /** + * Prevents processing of any event listeners in nodes subsequent to the current node in the event flow. + * Does not affect on receiving events in listeners of (currentTarget). + */ + override public function stopPropagation():void { + stop = true; + } + + /** + * Prevents processing of any event listeners in the current node and any subsequent nodes in the event flow. + */ + override public function stopImmediatePropagation():void { + stopImmediate = true; + } + + /** + * Duplicates an instance of an Event subclass. + * Returns a new Event3D object that is a copy of the original instance of the Event object. + * @return A new Event3D object that is identical to the original. + */ + override public function clone():Event { + var result:Event3D = new Event3D(type, _bubbles); + result._target = _target; + result._currentTarget = _currentTarget; + result._eventPhase = _eventPhase; + return result; + } + + /** + * Returns a string containing all the properties of the Event3D object. + * @return A string containing all the properties of the Event3D object + */ + override public function toString():String { + return formatToString("Event3D", "type", "bubbles", "eventPhase"); + } + + } +} diff --git a/src/alternativa/engine3d/core/events/MouseEvent3D.as b/src/alternativa/engine3d/core/events/MouseEvent3D.as new file mode 100644 index 0000000..f494884 --- /dev/null +++ b/src/alternativa/engine3d/core/events/MouseEvent3D.as @@ -0,0 +1,187 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.core.events { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.*; + import alternativa.engine3d.objects.Surface; + + import flash.events.Event; + + use namespace alternativa3d; + + /** + * + * Event MouseEvent3D dispatches by Object3D, in cases when MouseEvent dispatches by DisplayObject. + */ + public class MouseEvent3D extends Event3D { + + /** + * Defines the value of the type property of a click3D event object. + * @eventType click3D + */ + public static const CLICK:String = "click3D"; + + /** + * Defines the value of the type property of a doubleClick3D event object. + * @eventType doubleClick3D + */ + public static const DOUBLE_CLICK:String = "doubleClick3D"; + + /** + * Defines the value of the type property of a mouseDown3D event object. + * @eventType mouseDown3D + */ + public static const MOUSE_DOWN:String = "mouseDown3D"; + + /** + * Defines the value of the type property of a mouseUp3D event object. + * @eventType mouseUp3D + */ + public static const MOUSE_UP:String = "mouseUp3D"; + + /** + * Defines the value of the type property of a mouseOver3D event object. + * @eventType mouseOver3D + */ + public static const MOUSE_OVER:String = "mouseOver3D"; + + /** + * Defines the value of the type property of a mouseOut3D event object. + * @eventType mouseOut3D + */ + public static const MOUSE_OUT:String = "mouseOut3D"; + + /** + * Defines the value of the type property of a rollOver3D event object. + * @eventType rollOver3D + */ + public static const ROLL_OVER:String = "rollOver3D"; + + /** + * Defines the value of the type property of a rollOut3D event object. + * @eventType rollOut3D + */ + public static const ROLL_OUT:String = "rollOut3D"; + + /** + * Defines the value of the type property of a mouseMove3D event object. + * @eventType mouseMove3D + */ + public static const MOUSE_MOVE:String = "mouseMove3D"; + + /** + * Defines the value of the type property of a mouseWheel3D event object. + * @eventType mouseWheel3D + */ + public static const MOUSE_WHEEL:String = "mouseWheel3D"; + + /** + * On Windows or Linux, indicates whether the Ctrl key is active (true) or inactive (false). On Macintosh, indicates whether either the Control key or the Command key is activated. + */ + public var ctrlKey:Boolean; + /** + * Indicates whether the Alt key is active (true) or inactive (false). + */ + public var altKey:Boolean; + /** + * Indicates whether the Shift key is active (true) or inactive (false). + */ + public var shiftKey:Boolean; + /** + * Indicates whether the main mouse button is active (true) or inactive (false). + */ + public var buttonDown:Boolean; + /** + * Indicates how many lines should be scrolled for each unit the user rotates the mouse wheel. + */ + public var delta:int; + + /** + * A reference to an object that is related to the event. This property applies to the mouseOut, mouseOver, rollOut и rollOver. + * For example, when mouseOut occurs, relatedObject point to the object over which mouse cursor placed now. + */ + public var relatedObject:Object3D; + + /** + * X coordinate of the event at local target object's space. + */ + public var localX:Number; + + /** + * Y coordinate of the event at local target object's space. + */ + public var localY:Number; + + /** + * Z coordinate of the event at local target object's space. + */ + public var localZ:Number; + + /** + * @private + */ + alternativa3d var _surface:Surface; + + /** + * Creates a MouseEvent3D object. + * @param type Type. + * @param bubbles Indicates whether an event is a bubbling event. + * @param localY Y coordinate of the event at local target object's space. + * @param localX X coordinate of the event at local target object's space. + * @param localZ Z coordinate of the event at local target object's space. + * @param relatedObject Object3D, eelated to the MouseEvent3D. + * @param altKey Indicates whether the Alt key is active. + * @param ctrlKey Indicates whether the Control key is active. + * @param shiftKey Indicates whether the Shift key is active. + * @param buttonDown Indicates whether the main mouse button is active . + * @param delta Indicates how many lines should be scrolled for each unit the user rotates the mouse wheel. + */ + public function MouseEvent3D(type:String, bubbles:Boolean = true, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, relatedObject:Object3D = null, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, buttonDown:Boolean = false, delta:int = 0) { + super(type, bubbles); + this.localX = localX; + this.localY = localY; + this.localZ = localZ; + this.relatedObject = relatedObject; + this.ctrlKey = ctrlKey; + this.altKey = altKey; + this.shiftKey = shiftKey; + this.buttonDown = buttonDown; + this.delta = delta; + } + + /** + * Surface on which event has been received. The object that owns the surface, can differs from the target event. + * + */ + public function get surface():Surface { + return _surface; + } + + /** + * Duplicates an instance of an Event subclass. + * Returns a new MouseEvent3D object that is a copy of the original instance of the Event object. + * @return A new MouseEvent3D object that is identical to the original. + */ + override public function clone():Event { + return new MouseEvent3D(type, _bubbles, localX, localY, localZ, relatedObject, ctrlKey, altKey, shiftKey, buttonDown, delta); + } + + /** + * Returns a string containing all the properties of the MouseEvent3D object. + * @return A string containing all the properties of the MouseEvent3D object + */ + override public function toString():String { + return formatToString("MouseEvent3D", "type", "bubbles", "eventPhase", "localX", "localY", "localZ", "relatedObject", "altKey", "ctrlKey", "shiftKey", "buttonDown", "delta"); + } + + } +} diff --git a/src/alternativa/engine3d/effects/AGALMiniAssembler.as b/src/alternativa/engine3d/effects/AGALMiniAssembler.as new file mode 100644 index 0000000..a867090 --- /dev/null +++ b/src/alternativa/engine3d/effects/AGALMiniAssembler.as @@ -0,0 +1,724 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.effects { + // =========================================================================== + // Imports + // --------------------------------------------------------------------------- + //import flash.display3D.*; + + import flash.utils.*; + + /** + * @private + */ + public class AGALMiniAssembler + { + // ====================================================================== + // Properties + // ---------------------------------------------------------------------- + // AGAL bytes and error buffer + private var _agalcode:ByteArray = null; + 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 = new ByteArray(); + _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; + var bias:Number = 0; + for ( k = 0; k = new Vector.(); + protected var positionKeys:Vector. = new Vector.(); + protected var directionKeys:Vector. = new Vector.(); + protected var scriptKeys:Vector. = new Vector.(); + protected var keysCount:int = 0; + + private static var randomNumbers:Vector.; + private static const randomNumbersCount:int = 1000; + + private static const vector:Vector3D = new Vector3D(); + + private var randomOffset:int; + private var randomCounter:int; + + private var _position:Vector3D = new Vector3D(0, 0, 0); + private var _direction:Vector3D = new Vector3D(0, 0, 1); + + public function ParticleEffect() { + if (randomNumbers == null) { + randomNumbers = new Vector.(); + for (var i:int = 0; i < randomNumbersCount; i++) randomNumbers[i] = Math.random(); + } + randomOffset = Math.random()*randomNumbersCount; + } + + public function get position():Vector3D { + return _position.clone(); + } + + public function set position(value:Vector3D):void { + _position.x = value.x; + _position.y = value.y; + _position.z = value.z; + _position.w = value.w; + if (system != null) setPositionKeys(system.getTime() - startTime); + } + + public function get direction():Vector3D { + return _direction.clone(); + } + + public function set direction(value:Vector3D):void { + _direction.x = value.x; + _direction.y = value.y; + _direction.z = value.z; + _direction.w = value.w; + if (system != null) setDirectionKeys(system.getTime() - startTime); + } + + public function stop():void { + var time:Number = system.getTime() - startTime; + for (var i:int = 0; i < keysCount; i++) { + if (time < timeKeys[i]) break; + } + keysCount = i; + } + + protected function get particleSystem():ParticleSystem { + return system; + } + + protected function get cameraTransform():Transform3D { + return system.cameraToLocalTransform; + } + + protected function random():Number { + var res:Number = randomNumbers[randomCounter]; randomCounter++; + if (randomCounter == randomNumbersCount) randomCounter = 0; + return res; + } + + protected function addKey(time:Number, script:Function):void { + timeKeys[keysCount] = time; + positionKeys[keysCount] = new Vector3D(); + directionKeys[keysCount] = new Vector3D(); + scriptKeys[keysCount] = script; + keysCount++; + } + + protected function setLife(time:Number):void { + lifeTime = time; + } + + alternativa3d function calculateAABB():void { + aabb.minX = boundBox.minX*scale + _position.x; + aabb.minY = boundBox.minY*scale + _position.y; + aabb.minZ = boundBox.minZ*scale + _position.z; + aabb.maxX = boundBox.maxX*scale + _position.x; + aabb.maxY = boundBox.maxY*scale + _position.y; + aabb.maxZ = boundBox.maxZ*scale + _position.z; + } + + alternativa3d function setPositionKeys(time:Number):void { + for (var i:int = 0; i < keysCount; i++) { + if (time <= timeKeys[i]) { + var pos:Vector3D = positionKeys[i]; + pos.x = _position.x; + pos.y = _position.y; + pos.z = _position.z; + } + } + } + + alternativa3d function setDirectionKeys(time:Number):void { + vector.x = _direction.x; + vector.y = _direction.y; + vector.z = _direction.z; + vector.normalize(); + for (var i:int = 0; i < keysCount; i++) { + if (time <= timeKeys[i]) { + var dir:Vector3D = directionKeys[i]; + dir.x = vector.x; + dir.y = vector.y; + dir.z = vector.z; + } + } + } + + alternativa3d function calculate(time:Number):Boolean { + randomCounter = randomOffset; + for (var i:int = 0; i < keysCount; i++) { + var keyTime:Number = timeKeys[i]; + if (time >= keyTime) { + keyPosition = positionKeys[i]; + keyDirection = directionKeys[i]; + var script:Function = scriptKeys[i]; + script.call(this, keyTime, time - keyTime); + } else break; + } + return i < keysCount || particleList != null; + } + + } +} diff --git a/src/alternativa/engine3d/effects/ParticlePrototype.as b/src/alternativa/engine3d/effects/ParticlePrototype.as new file mode 100644 index 0000000..bf5f1d8 --- /dev/null +++ b/src/alternativa/engine3d/effects/ParticlePrototype.as @@ -0,0 +1,139 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.effects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Transform3D; + + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class ParticlePrototype { + + // Atlas + public var atlas:TextureAtlas; + + // Blend + private var blendSource:String; + private var blendDestination:String; + + // If true, then play animation + private var animated:Boolean; + + // Size + private var width:Number; + private var height:Number; + + // Key frames of animation. + private var timeKeys:Vector. = new Vector.(); + private var rotationKeys:Vector. = new Vector.(); + private var scaleXKeys:Vector. = new Vector.(); + private var scaleYKeys:Vector. = new Vector.(); + private var redKeys:Vector. = new Vector.(); + private var greenKeys:Vector. = new Vector.(); + private var blueKeys:Vector. = new Vector.(); + private var alphaKeys:Vector. = new Vector.(); + private var keysCount:int = 0; + + public function ParticlePrototype(width:Number, height:Number, atlas:TextureAtlas, animated:Boolean = false, blendSource:String = "sourceAlpha", blendDestination:String = "oneMinusSourceAlpha") { + this.width = width; + this.height = height; + this.atlas = atlas; + this.animated = animated; + this.blendSource = blendSource; + this.blendDestination = blendDestination; + } + + public function addKey(time:Number, rotation:Number = 0, scaleX:Number = 1, scaleY:Number = 1, red:Number = 1, green:Number = 1, blue:Number = 1, alpha:Number = 1):void { + var lastIndex:int = keysCount - 1; + if (keysCount > 0 && time <= timeKeys[lastIndex]) throw new Error("Keys must be successively."); + timeKeys[keysCount] = time; + rotationKeys[keysCount] = rotation; + scaleXKeys[keysCount] = scaleX; + scaleYKeys[keysCount] = scaleY; + redKeys[keysCount] = red; + greenKeys[keysCount] = green; + blueKeys[keysCount] = blue; + alphaKeys[keysCount] = alpha; + keysCount++; + } + + public function createParticle(effect:ParticleEffect, time:Number, position:Vector3D, rotation:Number = 0, scaleX:Number = 1, scaleY:Number = 1, alpha:Number = 1, firstFrame:int = 0):void { + var b:int = keysCount - 1; + if (atlas.diffuse._texture != null && keysCount > 1 && time >= timeKeys[0] && time < timeKeys[b]) { + + for (b = 1; b < keysCount; b++) { + if (time < timeKeys[b]) { + var systemScale:Number = effect.system.scale; + var effectScale:Number = effect.scale; + var transform:Transform3D = effect.system.localToCameraTransform; + var wind:Vector3D = effect.system.wind; + var gravity:Vector3D = effect.system.gravity; + // Interpolation + var a:int = b - 1; + var t:Number = (time - timeKeys[a])/(timeKeys[b] - timeKeys[a]); + // Frame calculation + var pos:int = firstFrame + (animated ? time*atlas.fps : 0); + if (atlas.loop) { + pos = pos%atlas.rangeLength; + if (pos < 0) pos += atlas.rangeLength; + } else { + if (pos < 0) pos = 0; + if (pos >= atlas.rangeLength) pos = atlas.rangeLength - 1; + } + pos += atlas.rangeBegin; + var col:int = pos%atlas.columnsCount; + var row:int = pos/atlas.columnsCount; + // Particle creation + var particle:Particle = Particle.create(); + particle.diffuse = atlas.diffuse._texture; + particle.opacity = (atlas.opacity != null) ? atlas.opacity._texture : null; + particle.blendSource = blendSource; + particle.blendDestination = blendDestination; + var cx:Number = effect.keyPosition.x + position.x*effectScale; + var cy:Number = effect.keyPosition.y + position.y*effectScale; + var cz:Number = effect.keyPosition.z + position.z*effectScale; + particle.x = cx*transform.a + cy*transform.b + cz*transform.c + transform.d; + particle.y = cx*transform.e + cy*transform.f + cz*transform.g + transform.h; + particle.z = cx*transform.i + cy*transform.j + cz*transform.k + transform.l; + var rot:Number = rotationKeys[a] + (rotationKeys[b] - rotationKeys[a])*t; + particle.rotation = (scaleX*scaleY > 0) ? (rotation + rot) : (rotation - rot); + particle.width = systemScale*effectScale*scaleX*width*(scaleXKeys[a] + (scaleXKeys[b] - scaleXKeys[a])*t); + particle.height = systemScale*effectScale*scaleY*height*(scaleYKeys[a] + (scaleYKeys[b] - scaleYKeys[a])*t); + particle.originX = atlas.originX; + particle.originY = atlas.originY; + particle.uvScaleX = 1/atlas.columnsCount; + particle.uvScaleY = 1/atlas.rowsCount; + particle.uvOffsetX = col/atlas.columnsCount; + particle.uvOffsetY = row/atlas.rowsCount; + particle.red = redKeys[a] + (redKeys[b] - redKeys[a])*t; + particle.green = greenKeys[a] + (greenKeys[b] - greenKeys[a])*t; + particle.blue = blueKeys[a] + (blueKeys[b] - blueKeys[a])*t; + particle.alpha = alpha*(alphaKeys[a] + (alphaKeys[b] - alphaKeys[a])*t); + particle.next = effect.particleList; + effect.particleList = particle; + break; + } + } + } + } + + public function get lifeTime():Number { + var lastIndex:int = keysCount - 1; + return timeKeys[lastIndex]; + } + + } +} diff --git a/src/alternativa/engine3d/effects/ParticleSystem.as b/src/alternativa/engine3d/effects/ParticleSystem.as new file mode 100644 index 0000000..c7099a5 --- /dev/null +++ b/src/alternativa/engine3d/effects/ParticleSystem.as @@ -0,0 +1,481 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.effects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.materials.compiler.Procedure; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.Context3DVertexBufferFormat; + import flash.display3D.IndexBuffer3D; + import flash.display3D.Program3D; + import flash.display3D.VertexBuffer3D; + import flash.display3D.textures.TextureBase; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * @private + */ + public class ParticleSystem extends Object3D { + + static private const limit:int = 31; + static private var vertexBuffer:VertexBuffer3D; + static private var indexBuffer:IndexBuffer3D; + static private var diffuseProgram:Program3D; + static private var opacityProgram:Program3D; + static private var diffuseBlendProgram:Program3D; + static private var opacityBlendProgram:Program3D; + + public var resolveByAABB:Boolean = true; + + public var gravity:Vector3D = new Vector3D(0, 0, -1); + public var wind:Vector3D = new Vector3D(); + + public var fogColor:int = 0; + public var fogMaxDensity:Number = 0; + public var fogNear:Number = 0; + public var fogFar:Number = 0; + + alternativa3d var scale:Number = 1; + + alternativa3d var effectList:ParticleEffect; + + private var drawUnit:DrawUnit = null; + private var diffuse:TextureBase = null; + private var opacity:TextureBase = null; + private var blendSource:String = null; + private var blendDestination:String = null; + private var counter:int; + + private var za:Number; + private var zb:Number; + private var fake:Vector. = new Vector.(); + private var fakeCounter:int = 0; + + public function ParticleSystem() { + super(); + } + + private var pause:Boolean = false; + private var stopTime:Number; + private var subtractiveTime:Number = 0; + + public function stop():void { + if (!pause) { + stopTime = getTimer()*0.001; + pause = true; + } + } + + public function play():void { + if (pause) { + subtractiveTime += getTimer()*0.001 - stopTime; + pause = false; + } + } + + public function prevFrame():void { + stopTime -= 0.001; + } + + public function nextFrame():void { + stopTime += 0.001; + } + + public function addEffect(effect:ParticleEffect):ParticleEffect { + // Checking on belonging + if (effect.system != null) throw new Error("Cannot add the same effect twice."); + // Set parameters + effect.startTime = getTime(); + effect.system = this; + effect.setPositionKeys(0); + effect.setDirectionKeys(0); + // Add + effect.nextInSystem = effectList; + effectList = effect; + return effect; + } + + public function getEffectByName(name:String):ParticleEffect { + for (var effect:ParticleEffect = effectList; effect != null; effect = effect.nextInSystem) { + if (effect.name == name) return effect; + } + return null; + } + + alternativa3d function getTime():Number { + return pause ? (stopTime - subtractiveTime) : (getTimer()*0.001 - subtractiveTime); + } + + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + // Create geometry and program + if (vertexBuffer == null) createAndUpload(camera.context3D); + // Average size + scale = Math.sqrt(localToCameraTransform.a*localToCameraTransform.a + localToCameraTransform.e*localToCameraTransform.e + localToCameraTransform.i*localToCameraTransform.i); + scale += Math.sqrt(localToCameraTransform.b*localToCameraTransform.b + localToCameraTransform.f*localToCameraTransform.f + localToCameraTransform.j*localToCameraTransform.j); + scale += Math.sqrt(localToCameraTransform.c*localToCameraTransform.c + localToCameraTransform.g*localToCameraTransform.g + localToCameraTransform.k*localToCameraTransform.k); + scale /= 3; + // TODO: add rotation on slope of the Z-axis in local space of camera + // Calculate frustrum + camera.calculateFrustum(cameraToLocalTransform); + // Loop items + var visibleEffectList:ParticleEffect; + var conflictAnyway:Boolean = false; + var time:Number = getTime(); + for (var effect:ParticleEffect = effectList, prev:ParticleEffect = null; effect != null;) { + // Check if actual + var effectTime:Number = time - effect.startTime; + if (effectTime <= effect.lifeTime) { + // Check bounds + var culling:int = 63; + if (effect.boundBox != null) { + effect.calculateAABB(); + culling = effect.aabb.checkFrustumCulling(camera.frustum, 63); + } + if (culling >= 0) { + // Gather the particles + if (effect.calculate(effectTime)) { + // Add + if (effect.particleList != null) { + effect.next = visibleEffectList; + visibleEffectList = effect; + conflictAnyway ||= effect.boundBox == null; + } + // Go to next effect + prev = effect; + effect = effect.nextInSystem; + } else { + // Removing + if (prev != null) { + prev.nextInSystem = effect.nextInSystem; + effect = prev.nextInSystem; + } else { + effectList = effect.nextInSystem; + effect = effectList; + } + } + } else { + // Go to next effect + prev = effect; + effect = effect.nextInSystem; + } + } else { + // Removing + if (prev != null) { + prev.nextInSystem = effect.nextInSystem; + effect = prev.nextInSystem; + } else { + effectList = effect.nextInSystem; + effect = effectList; + } + } + } + // Gather draws + if (visibleEffectList != null) { + if (visibleEffectList.next != null) { + /*if (resolveByAABB && !conflictAnyway) { + drawAABBEffects(camera, visibleEffectList); + } else {*/ + drawConflictEffects(camera, visibleEffectList); + //} + } else { + drawParticleList(camera, visibleEffectList.particleList); + visibleEffectList.particleList = null; + if (camera.debug && visibleEffectList.boundBox != null && (camera.checkInDebug(this) & Debug.BOUNDS)) Debug.drawBoundBox(camera, visibleEffectList.aabb, localToCameraTransform); + } + // Reset + flush(camera); + drawUnit = null; + diffuse = null; + opacity = null; + blendSource = null; + blendDestination = null; + fakeCounter = 0; + } + } + + private function createAndUpload(context:Context3D):void { + var vertices:Vector. = new Vector.(); + var indices:Vector. = new Vector.(); + for (var i:int = 0; i < limit; i++) { + vertices.push(0,0,0, 0,0,i*4, 0,1,0, 0,1,i*4, 1,1,0, 1,1,i*4, 1,0,0, 1,0,i*4); + indices.push(i*4, i*4 + 1, i*4 + 3, i*4 + 2, i*4 + 3, i*4 + 1); + } + vertexBuffer = context.createVertexBuffer(limit*4, 6); + vertexBuffer.uploadFromVector(vertices, 0, limit*4); + indexBuffer = context.createIndexBuffer(limit*6); + indexBuffer.uploadFromVector(indices, 0, limit*6); + var vertexProgram:Array = [ + // Pivot + "mov t2, c[a1.z]", // originX, originY, width, height + "sub t0.z, a0.x, t2.x", + "sub t0.w, a0.y, t2.y", + // Width and height + "mul t0.z, t0.z, t2.z", + "mul t0.w, t0.w, t2.w", + // Rotation + "mov t2, c[a1.z+1]", // x, y, z, rotation + "mov t1.z, t2.w", + "sin t1.x, t1.z", // sin + "cos t1.y, t1.z", // cos + "mul t1.z, t0.z, t1.y", // x*cos + "mul t1.w, t0.w, t1.x", // y*sin + "sub t0.x, t1.z, t1.w", // X + "mul t1.z, t0.z, t1.x", // x*sin + "mul t1.w, t0.w, t1.y", // y*cos + "add t0.y, t1.z, t1.w", // Y + // Translation + "add t0.x, t0.x, t2.x", + "add t0.y, t0.y, t2.y", + "add t0.z, a0.z, t2.z", + "mov t0.w, a0.w", + // Projection + "dp4 o0.x, t0, c124", + "dp4 o0.y, t0, c125", + "dp4 o0.z, t0, c126", + "dp4 o0.w, t0, c127", + // UV correction and passing out + "mov t2, c[a1.z+2]", // uvScaleX, uvScaleY, uvOffsetX, uvOffsetY + "mul t1.x, a1.x, t2.x", + "mul t1.y, a1.y, t2.y", + "add t1.x, t1.x, t2.z", + "add t1.y, t1.y, t2.w", + "mov v0, t1", + // Passing color + "mov v1, c[a1.z+3]", // red, green, blue, alpha + // Passing coordinates in the camera space + "mov v2, t0", + ]; + var fragmentDiffuseProgram:Array = [ + "tex t0, v0, s0 <2d,clamp,linear,miplinear>", + "mul t0, t0, v1", + // Fog + "sub t1.w, v2.z, c1.x", + "div t1.w, t1.w, c1.y", + "max t1.w, t1.w, c1.z", + "min t1.w, t1.w, c0.w", + "sub t1.xyz, c0.xyz, t0.xyz", + "mul t1.xyz, t1.xyz, t1.w", + "add t0.xyz, t0.xyz, t1.xyz", + "mov o0, t0", + ]; + var fragmentOpacityProgram:Array = [ + "tex t0, v0, s0 <2d,clamp,linear,miplinear>", + "tex t1, v0, s1 <2d,clamp,linear,miplinear>", + "mov t0.w, t1.x", + "mul t0, t0, v1", + // Fog + "sub t1.w, v2.z, c1.x", + "div t1.w, t1.w, c1.y", + "max t1.w, t1.w, c1.z", + "min t1.w, t1.w, c0.w", + "sub t1.xyz, c0.xyz, t0.xyz", + "mul t1.xyz, t1.xyz, t1.w", + "add t0.xyz, t0.xyz, t1.xyz", + "mov o0, t0", + ]; + var fragmentDiffuseBlendProgram:Array = [ + "tex t0, v0, s0 <2d,clamp,linear,miplinear>", + "mul t0, t0, v1", + // Fog + "sub t1.w, v2.z, c1.x", + "div t1.w, t1.w, c1.y", + "max t1.w, t1.w, c1.z", + "min t1.w, t1.w, c0.w", + "sub t1.w, c1.w, t1.w", + "mul t0.w, t0.w, t1.w", + "mov o0, t0", + ]; + var fragmentOpacityBlendProgram:Array = [ + "tex t0, v0, s0 <2d,clamp,linear,miplinear>", + "tex t1, v0, s1 <2d,clamp,linear,miplinear>", + "mov t0.w, t1.x", + "mul t0, t0, v1", + // Fog + "sub t1.w, v2.z, c1.x", + "div t1.w, t1.w, c1.y", + "max t1.w, t1.w, c1.z", + "min t1.w, t1.w, c0.w", + "sub t1.w, c1.w, t1.w", + "mul t0.w, t0.w, t1.w", + "mov o0, t0", + ]; + diffuseProgram = context.createProgram(); + opacityProgram = context.createProgram(); + diffuseBlendProgram = context.createProgram(); + opacityBlendProgram = context.createProgram(); + var compiledVertexProgram:ByteArray = compileProgram(Context3DProgramType.VERTEX, vertexProgram); + diffuseProgram.upload(compiledVertexProgram, compileProgram(Context3DProgramType.FRAGMENT, fragmentDiffuseProgram)); + opacityProgram.upload(compiledVertexProgram, compileProgram(Context3DProgramType.FRAGMENT, fragmentOpacityProgram)); + diffuseBlendProgram.upload(compiledVertexProgram, compileProgram(Context3DProgramType.FRAGMENT, fragmentDiffuseBlendProgram)); + opacityBlendProgram.upload(compiledVertexProgram, compileProgram(Context3DProgramType.FRAGMENT, fragmentOpacityBlendProgram)); + } + + private function compileProgram(mode:String, program:Array):ByteArray { + /*var string:String = ""; + var length:int = program.length; + for (var i:int = 0; i < length; i++) { + var line:String = program[i]; + string += line + ((i < length - 1) ? " \n" : ""); + }*/ + var proc:Procedure = new Procedure(program); + return proc.getByteCode(mode); + } + + private function flush(camera:Camera3D):void { + if (fakeCounter == fake.length) fake[fakeCounter] = new Object3D(); + var object:Object3D = fake[fakeCounter]; + fakeCounter++; + object.localToCameraTransform.l = (za + zb)/2; + // Fill + drawUnit.object = object; + drawUnit.numTriangles = counter << 1; + if (blendDestination == Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA) { + drawUnit.program = (opacity != null) ? opacityProgram : diffuseProgram; + } else { + drawUnit.program = (opacity != null) ? opacityBlendProgram : diffuseBlendProgram; + } + // Set streams + drawUnit.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); + drawUnit.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); + // Set constants + drawUnit.setProjectionConstants(camera, 124); + drawUnit.setFragmentConstantsFromNumbers(0, ((fogColor >> 16) & 0xFF)/0xFF, ((fogColor >> 8) & 0xFF)/0xFF, (fogColor & 0xFF)/0xFF, fogMaxDensity); + drawUnit.setFragmentConstantsFromNumbers(1, fogNear, fogFar - fogNear, 0, 1); + // Set textures + drawUnit.setTextureAt(0, diffuse); + if (opacity != null) drawUnit.setTextureAt(1, opacity); + // Set blending + drawUnit.blendSource = blendSource; + drawUnit.blendDestination = blendDestination; + drawUnit.culling = Context3DTriangleFace.NONE; + // Send to render + camera.renderer.addDrawUnit(drawUnit, Renderer.TRANSPARENT_SORT); + } + + private function drawParticleList(camera:Camera3D, list:Particle):void { + // Sorting + if (list.next != null) list = sortParticleList(list); + // Gather draws + var last:Particle; + for (var particle:Particle = list; particle != null; particle = particle.next) { + if (counter >= limit || particle.diffuse != diffuse || particle.opacity != opacity || particle.blendSource != blendSource || particle.blendDestination != blendDestination) { + if (drawUnit != null) flush(camera); + drawUnit = camera.renderer.createDrawUnit(null, null, indexBuffer, 0, 0); + diffuse = particle.diffuse; + opacity = particle.opacity; + blendSource = particle.blendSource; + blendDestination = particle.blendDestination; + counter = 0; + za = particle.z; + } + // Write constants + var offset:int = counter << 2; + drawUnit.setVertexConstantsFromNumbers(offset++, particle.originX, particle.originY, particle.width, particle.height); + drawUnit.setVertexConstantsFromNumbers(offset++, particle.x, particle.y, particle.z, particle.rotation); + drawUnit.setVertexConstantsFromNumbers(offset++, particle.uvScaleX, particle.uvScaleY, particle.uvOffsetX, particle.uvOffsetY); + drawUnit.setVertexConstantsFromNumbers(offset++, particle.red, particle.green, particle.blue, particle.alpha); + counter++; + zb = particle.z; + last = particle; + } + // Send to the collector + last.next = Particle.collector; + Particle.collector = list; + } + + private function sortParticleList(list:Particle):Particle { + var left:Particle = list; + var right:Particle = 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 = sortParticleList(left); + } + if (right.next != null) { + right = sortParticleList(right); + } + var flag:Boolean = left.z > right.z; + if (flag) { + list = left; + left = left.next; + } else { + list = right; + right = right.next; + } + var last:Particle = list; + while (true) { + if (left == null) { + last.next = right; + return list; + } else if (right == null) { + last.next = left; + return list; + } + if (flag) { + if (left.z > right.z) { + last = left; + left = left.next; + } else { + last.next = right; + last = right; + right = right.next; + flag = false; + } + } else { + if (right.z > left.z) { + last = right; + right = right.next; + } else { + last.next = left; + last = left; + left = left.next; + flag = true; + } + } + } + return null; + } + + private function drawConflictEffects(camera:Camera3D, effectList:ParticleEffect):void { + var particleList:Particle; + for (var effect:ParticleEffect = effectList; effect != null; effect = next) { + var next:ParticleEffect = effect.next; + effect.next = null; + var last:Particle = effect.particleList; + while (last.next != null) last = last.next; + last.next = particleList; + particleList = effect.particleList; + effect.particleList = null; + if (camera.debug && effect.boundBox != null && (camera.checkInDebug(this) & Debug.BOUNDS)) Debug.drawBoundBox(camera, effect.aabb, localToCameraTransform, 0xFF0000); + } + drawParticleList(camera, particleList); + } + + } +} diff --git a/src/alternativa/engine3d/effects/TextureAtlas.as b/src/alternativa/engine3d/effects/TextureAtlas.as new file mode 100644 index 0000000..2c2283a --- /dev/null +++ b/src/alternativa/engine3d/effects/TextureAtlas.as @@ -0,0 +1,45 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.effects { + + import alternativa.engine3d.resources.TextureResource; + + /** + * @private + */ + public class TextureAtlas { + + public var diffuse:TextureResource; + public var opacity:TextureResource; + public var columnsCount:int; + public var rowsCount:int; + public var rangeBegin:int; + public var rangeLength:int; + public var fps:int; + public var loop:Boolean; + public var originX:Number; + public var originY:Number; + + public function TextureAtlas(diffuse:TextureResource, opacity:TextureResource = null, columnsCount:int = 1, rowsCount:int = 1, rangeBegin:int = 0, rangeLength:int = 1, fps:int = 30, loop:Boolean = false, originX:Number = 0.5, originY:Number = 0.5) { + this.diffuse = diffuse; + this.opacity = opacity; + this.columnsCount = columnsCount; + this.rowsCount = rowsCount; + this.rangeBegin = rangeBegin; + this.rangeLength = rangeLength; + this.fps = fps; + this.loop = loop; + this.originX = originX; + this.originY = originY; + } + + } +} diff --git a/src/alternativa/engine3d/lights/AmbientLight.as b/src/alternativa/engine3d/lights/AmbientLight.as new file mode 100644 index 0000000..fdfb522 --- /dev/null +++ b/src/alternativa/engine3d/lights/AmbientLight.as @@ -0,0 +1,64 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.lights { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * An ambient light source represents a fixed-intensity and fixed-color light source + * that affects all objects in the scene equally. Upon rendering, all objects in + * the scene are brightened with the specified intensity and color. + * This type of light source is mainly used to provide the scene with a basic view of the different objects in it. + * + * This description taken from http://en.wikipedia.org/wiki/Shading#Ambient_lighting + */ + public class AmbientLight extends Light3D { + + /** + * Creates a AmbientLight object. + * @param color Light color. + */ + public function AmbientLight(color:uint) { + this.color = color; + } + + /** + * Does not do anything. + * + */ + override public function calculateBoundBox():void { + } + + /** + * @private + */ + override alternativa3d function calculateVisibility(camera:Camera3D):void { + camera.ambient[0] += ((color >> 16) & 0xFF)*intensity/255; + camera.ambient[1] += ((color >> 8) & 0xFF)*intensity/255; + camera.ambient[2] += (color & 0xFF)*intensity/255; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:AmbientLight = new AmbientLight(color); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/lights/DirectionalLight.as b/src/alternativa/engine3d/lights/DirectionalLight.as new file mode 100644 index 0000000..583814f --- /dev/null +++ b/src/alternativa/engine3d/lights/DirectionalLight.as @@ -0,0 +1,67 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.lights { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * A directional light source illuminates all objects equally from a given direction, + * like an area light of infinite size and infinite distance from the scene; + * there is shading, but cannot be any distance falloff. + * + * This description taken from http://en.wikipedia.org/wiki/Shading#Directional_lighting + * + * Lightning direction defines by z-axis of DirectionalLight. + * You can use lookAt() to make DirectionalLight point at given coordinates. + */ + public class DirectionalLight extends Light3D { + + /** + * Creates a new instance. + * @param color Color of light source. + */ + public function DirectionalLight(color:uint) { + this.color = color; + } + + /** + * Sets direction of DirectionalLight to given coordinates. + */ + public function lookAt(x:Number, y:Number, z:Number):void { + var dx:Number = x - this.x; + var dy:Number = y - this.y; + var dz:Number = z - this.z; + rotationX = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)) - Math.PI/2; + rotationY = 0; + rotationZ = -Math.atan2(dx, dy); + } + + /** + * Does not do anything. + */ + override public function calculateBoundBox():void { + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:DirectionalLight = new DirectionalLight(color); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/lights/OmniLight.as b/src/alternativa/engine3d/lights/OmniLight.as new file mode 100644 index 0000000..2009edc --- /dev/null +++ b/src/alternativa/engine3d/lights/OmniLight.as @@ -0,0 +1,206 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.lights { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + + use namespace alternativa3d; + + /** + * OmniLight is an attenuated light source placed at one point and spreads outward in all directions. + * + */ + public class OmniLight extends Light3D { + + /** + * Distance from which falloff starts. + */ + public var attenuationBegin:Number; + + /** + * Distance from at which falloff is complete. + */ + public var attenuationEnd:Number; + + /** + * Creates a OmniLight object. + * @param color Light color. + * @param attenuationBegin Distance from which falloff starts. + * @param attenuationEnd Distance from at which falloff is complete. + */ + public function OmniLight(color:uint, attenuationBegin:Number, attenuationEnd:Number) { + this.color = color; + this.attenuationBegin = attenuationBegin; + this.attenuationEnd = attenuationEnd; + calculateBoundBox(); + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + if (transform != null) { + + } else { + if (-attenuationEnd < boundBox.minX) boundBox.minX = -attenuationEnd; + if (attenuationEnd > boundBox.maxX) boundBox.maxX = attenuationEnd; + if (-attenuationEnd < boundBox.minY) boundBox.minY = -attenuationEnd; + if (attenuationEnd > boundBox.maxY) boundBox.maxY = attenuationEnd; + if (-attenuationEnd < boundBox.minZ) boundBox.minZ = -attenuationEnd; + if (attenuationEnd > boundBox.maxZ) boundBox.maxZ = attenuationEnd; + } + } + + /** + * @private + */ + override alternativa3d function checkBound(targetObject:Object3D):Boolean { + var rScale:Number = Math.sqrt(lightToObjectTransform.a*lightToObjectTransform.a + lightToObjectTransform.e*lightToObjectTransform.e + lightToObjectTransform.i*lightToObjectTransform.i); + rScale += Math.sqrt(lightToObjectTransform.b*lightToObjectTransform.b + lightToObjectTransform.f*lightToObjectTransform.f + lightToObjectTransform.j*lightToObjectTransform.j); + rScale += Math.sqrt(lightToObjectTransform.c*lightToObjectTransform.c + lightToObjectTransform.g*lightToObjectTransform.g + lightToObjectTransform.k*lightToObjectTransform.k); + rScale /= 3; + rScale *= attenuationEnd; + rScale *= rScale; + var len:Number = 0; + var bb:BoundBox = targetObject.boundBox; + var minX:Number = bb.minX; + var minY:Number = bb.minY; + var minZ:Number = bb.minZ; + var maxX:Number = bb.maxX; + var px:Number = lightToObjectTransform.d; + var py:Number = lightToObjectTransform.h; + var pz:Number = lightToObjectTransform.l; + + var maxY:Number = bb.maxY; + var maxZ:Number = bb.maxZ; + if (px < minX) { + if (py < minY) { + if (pz < minZ) { + len = (minX - px)*(minX - px) + (minY - py)*(minY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (minX - px)*(minX - px) + (minY - py)*(minY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (minX - px)*(minX - px) + (minY - py)*(minY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py < maxY) { + if (pz < minZ) { + len = (minX - px)*(minX - px) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (minX - px)*(minX - px); + return len < rScale; + } else if (pz > maxZ) { + len = (minX - px)*(minX - px) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py > maxY) { + if (pz < minZ) { + len = (minX - px)*(minX - px) + (maxY - py)*(maxY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (minX - px)*(minX - px) + (maxY - py)*(maxY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (minX - px)*(minX - px) + (maxY - py)*(maxY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } + } else if (px < maxX) { + if (py < minY) { + if (pz < minZ) { + len = (minY - py)*(minY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (minY - py)*(minY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (minY - py)*(minY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py < maxY) { + if (pz < minZ) { + len = (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + return true; + } else if (pz > maxZ) { + len = (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py > maxY) { + if (pz < minZ) { + len = (maxY - py)*(maxY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (maxY - py)*(maxY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (maxY - py)*(maxY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } + } else if (px > maxX) { + if (py < minY) { + if (pz < minZ) { + len = (maxX - px)*(maxX - px) + (minY - py)*(minY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (maxX - px)*(maxX - px) + (minY - py)*(minY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (maxX - px)*(maxX - px) + (minY - py)*(minY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py < maxY) { + if (pz < minZ) { + len = (maxX - px)*(maxX - px) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (maxX - px)*(maxX - px); + return len < rScale; + } else if (pz > maxZ) { + len = (maxX - px)*(maxX - px) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } else if (py > maxY) { + if (pz < minZ) { + len = (maxX - px)*(maxX - px) + (maxY - py)*(maxY - py) + (minZ - pz)*(minZ - pz); + return len < rScale; + } else if (pz < maxZ) { + len = (maxX - px)*(maxX - px) + (maxY - py)*(maxY - py); + return len < rScale; + } else if (pz > maxZ) { + len = (maxX - px)*(maxX - px) + (maxY - py)*(maxY - py) + (maxZ - pz)*(maxZ - pz); + return len < rScale; + } + } + } + return true; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:OmniLight = new OmniLight(color, attenuationBegin, attenuationEnd); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/lights/SpotLight.as b/src/alternativa/engine3d/lights/SpotLight.as new file mode 100644 index 0000000..303d093 --- /dev/null +++ b/src/alternativa/engine3d/lights/SpotLight.as @@ -0,0 +1,211 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.lights { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + + use namespace alternativa3d; + + /** + * OmniLight is an attenuated light source placed at one point and spreads outward in a coned direction. + * + * Lightning direction defines by z-axis of OmniLight. + * You can use lookAt() to make DirectionalLight point at given coordinates. + */ + public class SpotLight extends Light3D { + + /** + * Distance from which falloff starts. + */ + public var attenuationBegin:Number; + + /** + * Distance from at which falloff is complete. + */ + public var attenuationEnd:Number; + + /** + * Adjusts the angle of a light's cone. + */ + public var hotspot:Number; + + /** + * Adjusts the angle of a light's falloff. For photometric lights, the Field angle is comparable + * to the Falloff angle. It is the angle at which the light's intensity has fallen to zero. + */ + public var falloff:Number; + + /** + * Creates a new SpotLight instance. + * @param color Light color. + * @param attenuationBegin Distance from which falloff starts. + * @param attenuationEnd Distance from at which falloff is complete. + * @param hotspot Adjusts the angle of a light's cone. The Hotspot value is measured in radians. + * @param falloff Adjusts the angle of a light's falloff. The Falloff value is measured in radians. + */ + public function SpotLight(color:uint, attenuationBegin:Number, attenuationEnd:Number, hotspot:Number, falloff:Number) { + this.color = color; + this.attenuationBegin = attenuationBegin; + this.attenuationEnd = attenuationEnd; + this.hotspot = hotspot; + this.falloff = falloff; + calculateBoundBox(); + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + var r:Number = (falloff < Math.PI) ? Math.sin(falloff*0.5)*attenuationEnd : attenuationEnd; + var bottom:Number = (falloff < Math.PI) ? 0 : Math.cos(falloff*0.5)*attenuationEnd; + boundBox.minX = -r; + boundBox.minY = -r; + boundBox.minZ = bottom; + boundBox.maxX = r; + boundBox.maxY = r; + boundBox.maxZ = attenuationEnd; + } + + /** + * Set direction of the light direction to the given coordinates.. + */ + public function lookAt(x:Number, y:Number, z:Number):void { + var dx:Number = x - this.x; + var dy:Number = y - this.y; + var dz:Number = z - this.z; + rotationX = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)) - Math.PI/2; + rotationY = 0; + rotationZ = -Math.atan2(dx, dy); + } + + /** + * @private + */ + override alternativa3d function checkBound(targetObject:Object3D):Boolean { + var minX:Number = boundBox.minX; + var minY:Number = boundBox.minY; + var minZ:Number = boundBox.minZ; + var maxX:Number = boundBox.maxX; + var maxY:Number = boundBox.maxY; + var maxZ:Number = boundBox.maxZ; + var sum:Number; + var pro:Number; + // Half sizes of the source's boundbox + var w:Number = (maxX - minX)*0.5; + var l:Number = (maxY - minY)*0.5; + var h:Number = (maxZ - minZ)*0.5; + // Half-vectors of the source's boundbox + var ax:Number = lightToObjectTransform.a*w; + var ay:Number = lightToObjectTransform.e*w; + var az:Number = lightToObjectTransform.i*w; + var bx:Number = lightToObjectTransform.b*l; + var by:Number = lightToObjectTransform.f*l; + var bz:Number = lightToObjectTransform.j*l; + var cx:Number = lightToObjectTransform.c*h; + var cy:Number = lightToObjectTransform.g*h; + var cz:Number = lightToObjectTransform.k*h; + // Half sizes of the boundboxes + var objectBB:BoundBox = targetObject.boundBox; + var hw:Number = (objectBB.maxX - objectBB.minX)*0.5; + var hl:Number = (objectBB.maxY - objectBB.minY)*0.5; + var hh:Number = (objectBB.maxZ - objectBB.minZ)*0.5; + // Vector between centers of the bounboxes + var dx:Number = lightToObjectTransform.a*(minX + w) + lightToObjectTransform.b*(minY + l) + lightToObjectTransform.c*(minZ + h) + lightToObjectTransform.d - objectBB.minX - hw; + var dy:Number = lightToObjectTransform.e*(minX + w) + lightToObjectTransform.f*(minY + l) + lightToObjectTransform.g*(minZ + h) + lightToObjectTransform.h - objectBB.minY - hl; + var dz:Number = lightToObjectTransform.i*(minX + w) + lightToObjectTransform.j*(minY + l) + lightToObjectTransform.k*(minZ + h) + lightToObjectTransform.l - objectBB.minZ - hh; + + // X of the object + sum = 0; + if (ax >= 0) sum += ax; else sum -= ax; + if (bx >= 0) sum += bx; else sum -= bx; + if (cx >= 0) sum += cx; else sum -= cx; + sum += hw; + if (dx >= 0) sum -= dx; + sum += dx; + if (sum <= 0) return false; + // Y of the object + sum = 0; + if (ay >= 0) sum += ay; else sum -= ay; + if (by >= 0) sum += by; else sum -= by; + if (cy >= 0) sum += cy; else sum -= cy; + sum += hl; + if (dy >= 0) sum -= dy; else sum += dy; + if (sum <= 0) return false; + // Z of the object + sum = 0; + if (az >= 0) sum += az; else sum -= az; + if (bz >= 0) sum += bz; else sum -= bz; + if (cz >= 0) sum += cz; else sum -= cz; + sum += hl; + if (dz >= 0) sum -= dz; else sum += dz; + if (sum <= 0) return false; + // X of the source + sum = 0; + pro = lightToObjectTransform.a*ax + lightToObjectTransform.e*ay + lightToObjectTransform.i*az; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.a*bx + lightToObjectTransform.e*by + lightToObjectTransform.i*bz; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.a*cx + lightToObjectTransform.e*cy + lightToObjectTransform.i*cz; + if (pro >= 0) sum += pro; else sum -= pro; + if (lightToObjectTransform.a >= 0) sum += lightToObjectTransform.a*hw; else sum -= lightToObjectTransform.a*hw; + if (lightToObjectTransform.e >= 0) sum += lightToObjectTransform.e*hl; else sum -= lightToObjectTransform.e*hl; + if (lightToObjectTransform.i >= 0) sum += lightToObjectTransform.i*hh; else sum -= lightToObjectTransform.i*hh; + pro = lightToObjectTransform.a*dx + lightToObjectTransform.e*dy + lightToObjectTransform.i*dz; + if (pro >= 0) sum -= pro; else sum += pro; + if (sum <= 0) return false; + // Y of the source + sum = 0; + pro = lightToObjectTransform.b*ax + lightToObjectTransform.f*ay + lightToObjectTransform.j*az; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.b*bx + lightToObjectTransform.f*by + lightToObjectTransform.j*bz; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.b*cx + lightToObjectTransform.f*cy + lightToObjectTransform.j*cz; + if (pro >= 0) sum += pro; else sum -= pro; + if (lightToObjectTransform.b >= 0) sum += lightToObjectTransform.b*hw; else sum -= lightToObjectTransform.b*hw; + if (lightToObjectTransform.f >= 0) sum += lightToObjectTransform.f*hl; else sum -= lightToObjectTransform.f*hl; + if (lightToObjectTransform.j >= 0) sum += lightToObjectTransform.j*hh; else sum -= lightToObjectTransform.j*hh; + pro = lightToObjectTransform.b*dx + lightToObjectTransform.f*dy + lightToObjectTransform.j*dz; + if (pro >= 0) sum -= pro; + sum += pro; + if (sum <= 0) return false; + // Z of the source + sum = 0; + pro = lightToObjectTransform.c*ax + lightToObjectTransform.g*ay + lightToObjectTransform.k*az; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.c*bx + lightToObjectTransform.g*by + lightToObjectTransform.k*bz; + if (pro >= 0) sum += pro; else sum -= pro; + pro = lightToObjectTransform.c*cx + lightToObjectTransform.g*cy + lightToObjectTransform.k*cz; + if (pro >= 0) sum += pro; else sum -= pro; + if (lightToObjectTransform.c >= 0) sum += lightToObjectTransform.c*hw; else sum -= lightToObjectTransform.c*hw; + if (lightToObjectTransform.g >= 0) sum += lightToObjectTransform.g*hl; else sum -= lightToObjectTransform.g*hl; + if (lightToObjectTransform.k >= 0) sum += lightToObjectTransform.k*hh; else sum -= lightToObjectTransform.k*hh; + pro = lightToObjectTransform.c*dx + lightToObjectTransform.g*dy + lightToObjectTransform.k*dz; + if (pro >= 0) sum -= pro; else sum += pro; + if (sum <= 0) return false; + // TODO: checking on random axises + return true; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:SpotLight = new SpotLight(color, attenuationBegin, attenuationEnd, hotspot, falloff); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/ExporterA3D.as b/src/alternativa/engine3d/loaders/ExporterA3D.as new file mode 100644 index 0000000..35a3dfb --- /dev/null +++ b/src/alternativa/engine3d/loaders/ExporterA3D.as @@ -0,0 +1,633 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.animation.keys.TransformKey; + import alternativa.engine3d.animation.keys.TransformTrack; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.core.VertexStream; + import alternativa.engine3d.lights.AmbientLight; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.materials.LightMapMaterial; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.StandardMaterial; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + import alternativa.osgi.OSGi; + import alternativa.osgi.service.clientlog.IClientLog; + import alternativa.protocol.CompressionType; + import alternativa.protocol.ICodec; + import alternativa.protocol.IProtocol; + import alternativa.protocol.OptionalMap; + import alternativa.protocol.ProtocolBuffer; + import alternativa.protocol.impl.PacketHelper; + import alternativa.protocol.impl.Protocol; + import alternativa.protocol.info.TypeCodecInfo; + import alternativa.protocol.osgi.ProtocolActivator; + import alternativa.types.Long; + + import commons.A3DMatrix; + + import flash.geom.Matrix3D; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + + import platform.client.formats.a3d.osgi.Activator; + import platform.clients.fp10.libraries.alternativaprotocol.Activator; + + import versions.version2.a3d.A3D2; + import versions.version2.a3d.animation.A3D2AnimationClip; + import versions.version2.a3d.animation.A3D2Keyframe; + import versions.version2.a3d.animation.A3D2Track; + import versions.version2.a3d.geometry.A3D2IndexBuffer; + import versions.version2.a3d.geometry.A3D2VertexAttributes; + import versions.version2.a3d.geometry.A3D2VertexBuffer; + import versions.version2.a3d.materials.A3D2Image; + import versions.version2.a3d.materials.A3D2Map; + import versions.version2.a3d.materials.A3D2Material; + import versions.version2.a3d.objects.A3D2AmbientLight; + import versions.version2.a3d.objects.A3D2Box; + import versions.version2.a3d.objects.A3D2DirectionalLight; + import versions.version2.a3d.objects.A3D2Joint; + import versions.version2.a3d.objects.A3D2JointBindTransform; + import versions.version2.a3d.objects.A3D2Mesh; + import versions.version2.a3d.objects.A3D2Object; + import versions.version2.a3d.objects.A3D2OmniLight; + import versions.version2.a3d.objects.A3D2Skin; + import versions.version2.a3d.objects.A3D2SpotLight; + import versions.version2.a3d.objects.A3D2Surface; + import versions.version2.a3d.objects.A3D2Transform; + + use namespace alternativa3d; + + /** + * An object which allows to convert hierarchy of three-dimensional objects to binary A3D format. + */ + public class ExporterA3D { + + private var wasInit:Boolean = false; + private var protocol:Protocol; + private var parents:Dictionary; + private var geometries:Dictionary; + private var images:Dictionary; + + private var indexBufferID:int; + private var materialID:int; + private var mapID:int; + private var imageID:int; + private var animationID:int; + private var trackID:int; + private var vertexBufferID:int; + private var boxID:int; + + private var tracksMap:Dictionary; + private var materialsMap:Dictionary; + private var mapsMap:Dictionary; + + alternativa3d var idGenerator:IIDGenerator = new IncrementalIDGenerator(); + + /** + * Creates an instance of ExporterA3D. + */ + public function ExporterA3D() { + init(); + } + + private function init():void { + if (wasInit) return; + if (OSGi.getInstance() != null) { + protocol = Protocol(OSGi.getInstance().getService(IProtocol)); + return; + } + OSGi.clientLog = new DummyClientLog(); + var osgi:OSGi = new OSGi(); + osgi.registerService(IClientLog, new DummyClientLog()); + + new ProtocolActivator().start(osgi); + new platform.client.formats.a3d.osgi.Activator().start(osgi); + new platform.clients.fp10.libraries.alternativaprotocol.Activator().start(osgi); + protocol = Protocol(osgi.getService(IProtocol)); + wasInit = true; + } + + /** + * Exports a scene to A3D format. + * @param root Root object of scene. + * @return Data in A3D format. + */ + public function export(root:Object3D = null, animations:Vector. = null):ByteArray { + + boxID = 0; + indexBufferID = 0; + vertexBufferID = 0; + materialID = 0; + mapID = 0; + imageID = 0; + animationID = 0; + materialsMap = new Dictionary(); + mapsMap = new Dictionary(); + geometries = new Dictionary(); + tracksMap = new Dictionary(); + + images = new Dictionary(); + + parents = new Dictionary(); + + var a3D:A3D2 = new A3D2( + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + if (root != null) { + exportHierarchy(root, a3D); + } + if (animations != null) { + for each (var animation:AnimationClip in animations) { + exportAnimation(animation, a3D); + } + } + + var data:ByteArray = new ByteArray(); + var result:ByteArray = new ByteArray(); + var codec:ICodec = protocol.getCodec(new TypeCodecInfo(A3D2, false)); + + var protocolBuffer:ProtocolBuffer = new ProtocolBuffer(data, data, new OptionalMap()); + data.writeShort(2); + data.writeShort(0); + codec.encode(protocolBuffer, a3D); + data.position = 0; + PacketHelper.wrapPacket(result, protocolBuffer, CompressionType.DEFLATE); + return result; + } + + private function exportAnimation(source:AnimationClip, dest:A3D2):void { + var anim:A3D2AnimationClip = new A3D2AnimationClip(animationID, source.loop, source.name, null, exportTracks(source, dest)); + if (dest.animationClips == null) dest.animationClips = new Vector.(); + dest.animationClips[animationID] = anim; animationID++; + } + + private function exportTracks(source:AnimationClip, dest:A3D2):Vector. { + var id:int; + var result:Vector. = new Vector.(); + for (var i:int = 0; i < source.numTracks; i++) { + var t:TransformTrack = source.getTrackAt(i) as TransformTrack; + if (t != null && tracksMap[t] == null) { + id = trackID++; + var exportTrack:A3D2Track = new A3D2Track(id, exportKeyframes(t), t.object); + tracksMap[t] = id; + if (dest.animationTracks == null) dest.animationTracks = new Vector.(); + dest.animationTracks[id] = exportTrack; + } else { + id = tracksMap[t]; + } + result.push(id); + } + return result; + } + + private function exportKeyframes(source:TransformTrack):Vector. { + var result:Vector. = new Vector.(); + for (var key:TransformKey = TransformKey(source.keyFramesList); key.next != null; key = key.next) { + var exportKey:A3D2Keyframe = new A3D2Keyframe(key._time, exportTransformFromKeyframe(key)); + result.push(exportKey); + } + return result; + } + + private function exportTransformFromKeyframe(key:TransformKey):A3D2Transform { + var m:Matrix3D = Matrix3D(key.value); + var vec:Vector. = m.rawData; + var exportTransform:A3D2Transform = new A3D2Transform( + new A3DMatrix( + vec[0], vec[4], vec[8], vec[12], + vec[1], vec[5], vec[9], vec[13], + vec[2], vec[6], vec[10], vec[14] + )); + return exportTransform; + } + + + + private function exportHierarchy(source:Object3D, dest:A3D2):void { + var id:Long = idGenerator.getID(source); + if (source.transformChanged) { + source.composeTransforms(); + } + if (source is SpotLight) { + exportSpotLight(id, source as SpotLight, dest); + } else if (source is OmniLight) { + exportOmniLight(id, source as OmniLight, dest); + } else if (source is DirectionalLight) { + exportDirLight(id, source as DirectionalLight, dest); + } else if (source is AmbientLight) { + exportAmbientLight(id, source as AmbientLight, dest); + } else if (source is Skin) { + exportSkin(id, source as Skin, dest); + } else if (source is Mesh) { + exportMesh(id, source as Mesh, dest); + } else if (source is Joint) { + exportJoint(id, source as Joint, dest); + } else if (source is Object3D) { + exportObject3D(id, source, dest); + } else { + trace("Unsupported object type", source); + } + parents[source] = id; + + for (var child:Object3D = source.childrenList; child != null; child = child.next) { + exportHierarchy(child, dest); + } + + } + + private function exportJoint(id:Long, source:Joint, dest:A3D2):void { + + var a3DObject:A3D2Joint = new A3D2Joint( + exportBoundBox(source.boundBox, dest), + id, + source.name, + parents[source.parent is Skin ? source.parent.parent : source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.joints == null) dest.joints = new Vector.(); + dest.joints.push(a3DObject); + } + + private function exportObject3D(id:Long, source:Object3D, dest:A3D2):void { + var a3DObject:A3D2Object = new A3D2Object( + exportBoundBox(source.boundBox, dest), + id, + source.name, + parents[source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.objects == null) dest.objects = new Vector.(); + dest.objects.push(a3DObject); + } + + private function exportSpotLight(id:Long, source:SpotLight, dest:A3D2):void { + var a3DObject:A3D2SpotLight = new A3D2SpotLight( + source.attenuationBegin, + source.attenuationEnd, + exportBoundBox(source.boundBox, dest), + source.color, + source.falloff, + source.hotspot, + id, + source.intensity, + source.name, + parents[source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.spotLights == null) dest.spotLights = new Vector.(); + dest.spotLights.push(a3DObject); + } + + private function exportOmniLight(id:Long, source:OmniLight, dest:A3D2):void { + var a3DObject:A3D2OmniLight = new A3D2OmniLight( + source.attenuationBegin, + source.attenuationEnd, + exportBoundBox(source.boundBox, dest), + source.color, + id, + source.intensity, + source.name, + parents[source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.omniLights == null) dest.omniLights = new Vector.(); + dest.omniLights.push(a3DObject); + } + + private function exportDirLight(id:Long, source:DirectionalLight, dest:A3D2):void { + var a3DObject:A3D2DirectionalLight = new A3D2DirectionalLight( + exportBoundBox(source.boundBox, dest), + source.color, + id, + source.intensity, + source.name, + parents[source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.directionalLights == null) dest.directionalLights = new Vector.(); + dest.directionalLights.push(a3DObject); + + } + + private function exportAmbientLight(id:Long, source:AmbientLight, dest:A3D2):void { + var a3DObject:A3D2AmbientLight = new A3D2AmbientLight( + exportBoundBox(source.boundBox, dest), + source.color, + id, + source.intensity, + source.name, + parents[source.parent], + exportTransform(source.transform), + source.visible + ); + if (dest.ambientLights == null) dest.ambientLights = new Vector.(); + dest.ambientLights.push(a3DObject); + } + + private function exportMesh(id:Long, source:Mesh, dest:A3D2):void { + var geometryData:GeometryData = exportGeometry(source.geometry, dest); + var a3DMesh:A3D2Mesh = new A3D2Mesh( + exportBoundBox(source.boundBox, dest), + id, + geometryData.indexBufferID, + source.name, + parents[source.parent], + exportSurfaces(source._surfaces, dest), + exportTransform(source.transform), + geometryData.vertexBufferIDs, + source.visible + ); + if (dest.meshes == null) dest.meshes = new Vector.(); + dest.meshes.push(a3DMesh); + } + + private function exportSkin(id:Long, source:Skin, dest:A3D2):A3D2Skin { + var geometryData:GeometryData = exportGeometry(source.geometry, dest); + var a3DSkin:A3D2Skin = new A3D2Skin( + exportBoundBox(source.boundBox, dest), + id, + geometryData.indexBufferID, + exportJointsBindTransforms(source._renderedJoints), + exportJointsListFromSurfacesJoints(source.surfaceJoints), + source.name, + exportNumJoitns(source.surfaceJoints), + null, + exportSurfaces(source._surfaces, dest), + exportTransform(source.transform), + geometryData.vertexBufferIDs, + source.visible + ); + if (dest.skins == null) dest.skins = new Vector.(); + dest.skins.push(a3DSkin); + return a3DSkin; + } + + private function exportNumJoitns(surfaceJoints:Vector.>):Vector. { + var result:Vector. = new Vector.(); + for (var i:int = 0; i < surfaceJoints.length; i++) { + result.push(surfaceJoints[i].length); + } + return result; + } + + private function exportJointsBindTransforms(joints:Vector.):Vector. { + var result:Vector. = new Vector.(); + for each (var joint:Joint in joints) { + result.push(new A3D2JointBindTransform(exportTransform(joint.bindPoseTransform), idGenerator.getID(joint))); + } + return result; + } + + private function exportJointsListFromSurfacesJoints(surfaceJoints:Vector.>):Vector. { + var result:Vector. = new Vector.(); + for (var i:int = 0; i < surfaceJoints.length; i++) { + var joints:Vector. = surfaceJoints[i]; + for each (var joint:Joint in joints) { + result.push(idGenerator.getID(joint)); + } + } + return result; + } + + private function exportSurfaces(surfaces:Vector., dest:A3D2):Vector. { + var result:Vector. = new Vector.(); + for (var i:int = 0, count:int = surfaces.length; i < count; i++) { + var surface:Surface = surfaces[i]; + var resSurface:A3D2Surface = new A3D2Surface(surface.indexBegin, exportMaterial(surface.material, dest), surface.numTriangles); + result[i] = resSurface; + } + return result; + } + + private function exportMaterial(source:Material, dest:A3D2):int { + if (source == null) return -1; + var result:A3D2Material = materialsMap[source]; + if (result != null) return result.id; + if (source is ParserMaterial) { + var parserMaterial:ParserMaterial = source as ParserMaterial; + result = new A3D2Material( + exportMap(parserMaterial.textures["diffuse"], 0, dest), + exportMap(parserMaterial.textures["glossiness"], 0, dest), + materialID, + exportMap(parserMaterial.textures["emission"], 0, dest), + exportMap(parserMaterial.textures["bump"], 0, dest), + exportMap(parserMaterial.textures["transparent"], 0, dest), + -1, + exportMap(parserMaterial.textures["specular"], 0, dest) + ); + } else if (source is LightMapMaterial) { + var lightMapMaterial:LightMapMaterial = source as LightMapMaterial; + result = new A3D2Material( + exportMap(lightMapMaterial.diffuseMap, 0, dest), + -1, + materialID, + exportMap(lightMapMaterial.lightMap, lightMapMaterial.lightMapChannel, dest), + -1, + exportMap(lightMapMaterial.opacityMap, 0, dest), + -1, + -1); + } else if (source is StandardMaterial) { + var standardMaterial:StandardMaterial = source as StandardMaterial; + result = new A3D2Material( + exportMap(standardMaterial.diffuseMap, 0, dest), + exportMap(standardMaterial.glossinessMap, 0, dest), materialID, + -1, + exportMap(standardMaterial.normalMap, 0, dest), + exportMap(standardMaterial.opacityMap, 0, dest), + -1, + exportMap(standardMaterial.specularMap, 0, dest)); + } else if (source is TextureMaterial) { + var textureMaterial:TextureMaterial = source as TextureMaterial; + result = new A3D2Material(exportMap(textureMaterial.diffuseMap, 0, dest), -1, materialID, -1, -1, exportMap(textureMaterial.opacityMap, 0, dest), -1, -1); + } + materialsMap[source] = result; + if (dest.materials == null) dest.materials = new Vector.(); + dest.materials[materialID] = result; + return materialID++; + } + + private function exportMap(source:TextureResource, channel:int, dest:A3D2):int { + if (source == null) return -1; + var result:A3D2Map = mapsMap[source]; + if (result != null) return result.id; + if (source is ExternalTextureResource) { + var resource:ExternalTextureResource = source as ExternalTextureResource; + result = new A3D2Map(channel, mapID, exportImage(resource, dest)); + if (dest.maps == null) dest.maps = new Vector.(); + dest.maps[mapID] = result; + mapsMap[source] = result; + + return mapID++; + } + return -1; + } + + private function exportImage(source:ExternalTextureResource, dest:A3D2):int { + var image:Object = images[source]; + if (image != null) return int(image); + var result:A3D2Image = new A3D2Image(imageID, source.url); + if (dest.images == null) dest.images = new Vector.(); + dest.images[imageID] = result; + return imageID++; + } + + private function exportGeometry(geometry:Geometry, dest:A3D2):GeometryData { + var result:GeometryData = geometries[geometry]; + if (result != null) return result; + result = new GeometryData(); + result.vertexBufferIDs = new Vector.(); + var indicesData:ByteArray = new ByteArray(); + indicesData.endian = Endian.LITTLE_ENDIAN; + var indices:Vector. = geometry.indices; + for (var i:int = 0, count:int = indices.length; i < count; i++) { + indicesData.writeShort(indices[i]); + } + var indexBuffer:A3D2IndexBuffer = new A3D2IndexBuffer(indicesData, indexBufferID, indices.length); + result.indexBufferID = indexBufferID; + if (dest.indexBuffers == null) dest.indexBuffers = new Vector.(); + dest.indexBuffers[indexBufferID] = indexBuffer; + indexBufferID++; + for (i = 0,count = geometry._vertexStreams.length; i < count; i++) { + var stream:VertexStream = geometry._vertexStreams[i]; + var buffer:A3D2VertexBuffer = new A3D2VertexBuffer(exportAttributes(stream.attributes), stream.data, vertexBufferID, geometry.numVertices); + if (dest.vertexBuffers == null) dest.vertexBuffers = new Vector.(); + dest.vertexBuffers[vertexBufferID] = buffer; + result.vertexBufferIDs[i] = vertexBufferID++; + } + return result; + } + + private function exportAttributes(attributes:Array):Vector. { + var prev:int = -1; + var result:Vector. = new Vector.(); + for each (var attr:int in attributes) { + if (attr == prev) continue; + switch (attr) { + case VertexAttributes.POSITION: + result.push(A3D2VertexAttributes.POSITION); + break; + case VertexAttributes.NORMAL: + result.push(A3D2VertexAttributes.NORMAL); + break; + case VertexAttributes.TANGENT4: + result.push(A3D2VertexAttributes.TANGENT4); + break; + default: + if ((attr >= VertexAttributes.JOINTS[0]) && (attr <= VertexAttributes.JOINTS[3])) { + result.push(A3D2VertexAttributes.JOINT); + } else if ((attr >= VertexAttributes.TEXCOORDS[0]) && (attr <= VertexAttributes.TEXCOORDS[7])) { + result.push(A3D2VertexAttributes.TEXCOORD); + } + break; + } + prev = attr; + } + return result; + } + + private function exportTransform(source:Transform3D):A3D2Transform { + return new A3D2Transform(new A3DMatrix( + source.a, source.b, source.c, source.d, + source.e, source.f, source.g, source.h, + source.i, source.j, source.k, source.l + )); + } + + private function exportBoundBox(boundBox:BoundBox, dest:A3D2):int { + if (boundBox == null) return -1; + if (dest.boxes == null) dest.boxes = new Vector.(); + dest.boxes[boxID] = new A3D2Box(Vector.([boundBox.minX, boundBox.minY, boundBox.minZ, boundBox.maxX, boundBox.maxY, boundBox.maxZ]), boxID); + return boxID++; + } + } +} + +import alternativa.osgi.service.clientlog.IClientLog; +import alternativa.osgi.service.clientlog.IClientLogChannelListener; + +class GeometryData { + public var indexBufferID:int; + public var vertexBufferIDs:Vector.; + + public function GeometryData(indexBufferID:int = -1, vertexBufferIDs:Vector. = null) { + this.indexBufferID = indexBufferID; + this.vertexBufferIDs = vertexBufferIDs; + } +} + +class DummyClientLog implements IClientLog { + + public function logError(channelName:String, text:String, ... vars):void { + } + + public function log(channelName:String, text:String, ... rest):void { + } + + public function getChannelStrings(channelName:String):Vector. { + return null; + } + + public function addLogListener(listener:IClientLogChannelListener):void { + } + + public function removeLogListener(listener:IClientLogChannelListener):void { + } + + public function addLogChannelListener(channelName:String, listener:IClientLogChannelListener):void { + } + + public function removeLogChannelListener(channelName:String, listener:IClientLogChannelListener):void { + } + + public function getChannelNames():Vector. { + return null; + } +} diff --git a/src/alternativa/engine3d/loaders/IIDGenerator.as b/src/alternativa/engine3d/loaders/IIDGenerator.as new file mode 100644 index 0000000..7022fee --- /dev/null +++ b/src/alternativa/engine3d/loaders/IIDGenerator.as @@ -0,0 +1,23 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.core.Object3D; + import alternativa.types.Long; + + /** + * @private + */ + public interface IIDGenerator { + function getID(object:Object3D):Long; + + } +} diff --git a/src/alternativa/engine3d/loaders/IncrementalIDGenerator.as b/src/alternativa/engine3d/loaders/IncrementalIDGenerator.as new file mode 100644 index 0000000..48edae9 --- /dev/null +++ b/src/alternativa/engine3d/loaders/IncrementalIDGenerator.as @@ -0,0 +1,38 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.core.Object3D; + import alternativa.types.Long; + + import flash.utils.Dictionary; + + /** + * @private + */ + public class IncrementalIDGenerator implements IIDGenerator { + + private var lastID:uint = 0; + private var objects:Dictionary; + + public function IncrementalIDGenerator() { + objects = new Dictionary(true); + } + + public function getID(object:Object3D):Long { + var result:Long = objects[object]; + if (result == null) { + result = objects[object] = Long.fromInt(lastID); lastID++; + } + return result; + } + } +} diff --git a/src/alternativa/engine3d/loaders/Parser.as b/src/alternativa/engine3d/loaders/Parser.as new file mode 100644 index 0000000..197f2ac --- /dev/null +++ b/src/alternativa/engine3d/loaders/Parser.as @@ -0,0 +1,1067 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.animation.keys.Track; + import alternativa.engine3d.animation.keys.TransformKey; + import alternativa.engine3d.animation.keys.TransformTrack; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.core.VertexStream; + import alternativa.engine3d.lights.AmbientLight; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.materials.A3DUtils; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.LOD; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.objects.Sprite3D; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.types.Long; + + import commons.A3DMatrix; + import commons.Id; + + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + + import versions.version1.a3d.A3D; + import versions.version1.a3d.geometry.A3DGeometry; + import versions.version1.a3d.geometry.A3DIndexBuffer; + import versions.version1.a3d.geometry.A3DVertexBuffer; + import versions.version1.a3d.id.ParentId; + import versions.version1.a3d.materials.A3DImage; + import versions.version1.a3d.materials.A3DMap; + import versions.version1.a3d.materials.A3DMaterial; + import versions.version1.a3d.objects.A3DBox; + import versions.version1.a3d.objects.A3DObject; + import versions.version1.a3d.objects.A3DSurface; + import versions.version2.a3d.A3D2; + import versions.version2.a3d.A3D2Extra1; + import versions.version2.a3d.A3D2Extra2; + import versions.version2.a3d.animation.A3D2AnimationClip; + import versions.version2.a3d.animation.A3D2Keyframe; + import versions.version2.a3d.animation.A3D2Track; + import versions.version2.a3d.geometry.A3D2IndexBuffer; + import versions.version2.a3d.geometry.A3D2VertexAttributes; + import versions.version2.a3d.geometry.A3D2VertexBuffer; + import versions.version2.a3d.materials.A3D2CubeMap; + import versions.version2.a3d.materials.A3D2Image; + import versions.version2.a3d.materials.A3D2Map; + import versions.version2.a3d.materials.A3D2Material; + import versions.version2.a3d.objects.A3D2AmbientLight; + import versions.version2.a3d.objects.A3D2Box; + import versions.version2.a3d.objects.A3D2DirectionalLight; + import versions.version2.a3d.objects.A3D2Joint; + import versions.version2.a3d.objects.A3D2JointBindTransform; + import versions.version2.a3d.objects.A3D2LOD; + import versions.version2.a3d.objects.A3D2Layer; + import versions.version2.a3d.objects.A3D2Mesh; + import versions.version2.a3d.objects.A3D2Object; + import versions.version2.a3d.objects.A3D2OmniLight; + import versions.version2.a3d.objects.A3D2Skin; + import versions.version2.a3d.objects.A3D2SpotLight; + import versions.version2.a3d.objects.A3D2Sprite; + import versions.version2.a3d.objects.A3D2Surface; + import versions.version2.a3d.objects.A3D2Transform; + + use namespace alternativa3d; + + /** + * Base class for classes, that perform parsing of scenes of different formats. + */ + public class Parser { + + /** + * List of root objects. Root objects are objects, that have no parents. + * @see alternativa.engine3d.core.Object3D + */ + public var hierarchy:Vector.; + /** + * List of objects, that are got after parsing. + * @see alternativa.engine3d.core.Object3D + */ + public var objects:Vector.; + + /** + * Array of animations. + */ + public var animations:Vector.; + + /** + * List of all materials assigned to objects, that are got after parsing. + * @see alternativa.engine3d.loaders.ParserMaterial + */ + public var materials:Vector.; + + private var maps:Dictionary; + + private var cubemaps:Dictionary; + + /** + * @private + */ + alternativa3d var layersMap:Dictionary; + + alternativa3d var layers:Vector.; + + alternativa3d var compressedBuffers:Boolean = false; + + private var parsedMaterials:Dictionary; + private var parsedGeometries:Object; + private var unpackedBuffers:Dictionary; + + /** + * Returns object from array objects by name. + */ + public function getObjectByName(name:String):Object3D { + for each (var object:Object3D in objects) { + if (object.name == name) return object; + } + return null; + } + + /** + * Returns name of layer for specified object. + */ + public function getLayerByObject(object:Object3D):String { + return layersMap[object]; + } + + /** + * Erases all links to external objects. + */ + public function clean():void { + hierarchy = null; + objects = null; + materials = null; + animations = null; + layersMap = null; + objectsMap = null; + a3DBoxes = null; + parents = null; + layers = null; + } + + /** + * @private + */ + alternativa3d function init():void { + hierarchy = new Vector.(); + objects = new Vector.(); + materials = new Vector.(); + animations = new Vector.(); + layersMap = new Dictionary(true); + layers = new Vector.(); + } + + protected function complete(a3d:Object):void { + init(); + if (a3d is A3D) { + doParse2_0(convert1_2(A3D(a3d))); + } else if (a3d is A3D2) { + doParse2_0(A3D2(a3d)); + } else if (a3d is Vector.) { + var vec:Vector. = a3d as Vector.; + var len:int = vec.length; + for (var i:int = 0; i < len; i++) { + doParsePart(vec[i]); + } + } + completeHierarchy(); + } + + private function doParsePart(a3d:Object):void { + if (a3d is A3D) { + doParse2_0(convert1_2(A3D(a3d))); + } else if (a3d is A3D2) { + doParse2_0(A3D2(a3d)); + } else if (a3d is A3D2Extra1) { + doParseExtra1(A3D2Extra1(a3d)); + } else if (a3d is A3D2Extra2) { + doParseExtra2(A3D2Extra2(a3d)); + } + } + + private function doParseExtra1(a3d:A3D2Extra1):void { + var layersVec:Vector. = a3d.layers; + for each (var layer:A3D2Layer in layersVec) { + var layerName:String = (layer.name == null || layer.name.length == 0) ? "default" : layer.name; + layers.push(layerName); + for each (var id:Long in layer.objects) { + if (objectsMap[id] != null) { + layersMap[objectsMap[id]] = layerName; + } + } + } + } + + private function doParseExtra2(a3d:A3D2Extra2):void { + var lodsVec:Vector. = a3d.lods; + for each (var lod:A3D2LOD in lodsVec) { + var resObject:LOD = new LOD(); + resObject.visible = lod.visible; + resObject.name = lod.name; + parents[resObject] = lod.parentId; + objectsMap[lod.id] = resObject; + var length:uint = lod.objects.length; + for (var i:int = 0; i < length; i++) { + resObject.addLevel(objectsMap[lod.objects[i]], lod.distances[i]); + } + decomposeTransformation(lod.transform, resObject); + + } + } + + private var objectsMap:Dictionary; + + private var parents:Dictionary = new Dictionary(); + + private var a3DBoxes:Dictionary = new Dictionary(); + + private function doParse2_0(a3d:A3D2):void { + maps = new Dictionary(); + cubemaps = new Dictionary(); + parsedMaterials = new Dictionary(); + parsedGeometries = new Dictionary(); + unpackedBuffers = new Dictionary(); + objectsMap = new Dictionary(); + parents = new Dictionary(); + a3DBoxes = new Dictionary(); + + + var parsedTracks:Dictionary = new Dictionary(); + var a3DIndexBuffers:Dictionary = new Dictionary(); + var a3DVertexBuffers:Dictionary = new Dictionary(); + var a3DMaterials:Dictionary = new Dictionary(); + var a3DMaps:Dictionary = new Dictionary(); + var a3DImages:Dictionary = new Dictionary(); + var a3DCubeMaps:Dictionary = new Dictionary(); + var a3DObject:A3D2Object; + var a3DMesh:A3D2Mesh; + + var a3DIndexBuffer:A3D2IndexBuffer; + var a3DVertexBuffer:A3D2VertexBuffer; + var a3DMaterial:A3D2Material; + var a3DBox:A3D2Box; + var a3DMap:A3D2Map; + var a3DImage:A3D2Image; + var a3DAmbientLight:A3D2AmbientLight; + var a3DOmniLight:A3D2OmniLight; + var a3DSpotLight:A3D2SpotLight; + var a3DDirLight:A3D2DirectionalLight; + var a3DSkin:A3D2Skin; + var a3DJoint:A3D2Joint; + var a3DSprite:A3D2Sprite; + var a3DCubeMap:A3D2CubeMap; + + for each(a3DIndexBuffer in a3d.indexBuffers) { + a3DIndexBuffers[a3DIndexBuffer.id] = a3DIndexBuffer; + } + for each (var a3DTrack:A3D2Track in a3d.animationTracks) { + var resTrack:TransformTrack = new TransformTrack(a3DTrack.objectName); + for each (var a3DKeyFrame:A3D2Keyframe in a3DTrack.keyframes) { + var tFrame:TransformKey = new TransformKey(); + tFrame._time = a3DKeyFrame.time; + + var components:Vector. = getMatrix3D(a3DKeyFrame.transform).decompose(Orientation3D.QUATERNION); + + tFrame.x = components[0].x; + tFrame.y = components[0].y; + tFrame.z = components[0].z; + tFrame.rotation = components[1]; + tFrame.scaleX = components[2].x; + tFrame.scaleY = components[2].y; + tFrame.scaleZ = components[2].z; + resTrack.addKeyToList(tFrame); + } + parsedTracks[a3DTrack.id] = resTrack; + } + + var animationClip:AnimationClip; + + // Animation parsing + if (a3d.animationTracks != null && a3d.animationTracks.length > 0) { + if (a3d.animationClips == null || a3d.animationClips.length == 0) { + animationClip = new AnimationClip(); + for each (resTrack in parsedTracks) { + animationClip.addTrack(resTrack); + } + animations.push(animationClip); + } else { + for each (var a3DAnim:A3D2AnimationClip in a3d.animationClips) { + animationClip = new AnimationClip(a3DAnim.name); + animationClip.loop = a3DAnim.loop; + for each (var trackID:int in a3DAnim.tracks) { + var track:Track = parsedTracks[trackID]; + if (track != null) { + animationClip.addTrack(track); + } + } + animations.push(animationClip); + } + } + } + + + for each (a3DVertexBuffer in a3d.vertexBuffers) { + a3DVertexBuffers[a3DVertexBuffer.id] = a3DVertexBuffer; + } + + for each (a3DBox in a3d.boxes) { + a3DBoxes[a3DBox.id] = a3DBox; + } + + for each (a3DMaterial in a3d.materials) { + a3DMaterials[a3DMaterial.id] = a3DMaterial; + } + + for each (a3DMap in a3d.maps) { + a3DMaps[a3DMap.id] = a3DMap; + } + + for each (a3DCubeMap in a3d.cubeMaps) { + a3DCubeMaps[a3DCubeMap.id] = a3DCubeMap; + } + + + for each (a3DImage in a3d.images) { + a3DImages[a3DImage.id] = a3DImage; + } + var jointsMap:Dictionary = new Dictionary(); + + for each (a3DJoint in a3d.joints) { + var resJoint:Joint = new Joint(); + resJoint.visible = a3DJoint.visible; + resJoint.name = a3DJoint.name; + parents[resJoint] = a3DJoint.parentId; + jointsMap[a3DJoint.id] = resJoint; + decomposeTransformation(a3DJoint.transform, resJoint); + a3DBox = a3DBoxes[a3DJoint.boundBoxId]; + if (a3DBox != null) { + parseBoundBox(a3DBox.box, resJoint); + } + } + + for each (a3DObject in a3d.objects) { + var resObject:Object3D = new Object3D(); + resObject.visible = a3DObject.visible; + resObject.name = a3DObject.name; + parents[resObject] = a3DObject.parentId; + objectsMap[a3DObject.id] = resObject; + jointsMap[a3DObject.id] = resObject; + decomposeTransformation(a3DObject.transform, resObject); + + a3DBox = a3DBoxes[a3DObject.boundBoxId]; + if (a3DBox != null) { + parseBoundBox(a3DBox.box, resObject); + } + + } + + for each (a3DSprite in a3d.sprites) { + var resSprite:Sprite3D = new Sprite3D(a3DSprite.width, a3DSprite.height); + resSprite.material = parseMaterial(a3DMaterials[a3DSprite.materialId], a3DMaps, a3DCubeMaps, a3DImages); + resSprite.originX = a3DSprite.originX; + resSprite.originY = a3DSprite.originY; + resSprite.perspectiveScale = a3DSprite.perspectiveScale; + resSprite.alwaysOnTop = a3DSprite.alwaysOnTop; + resSprite.rotation = a3DSprite.rotation; + objectsMap[a3DSprite.id] = resSprite; + decomposeTransformation(a3DSprite.transform, resSprite); + } + + for each (a3DSkin in a3d.skins) { + var resSkin:Mesh = parseSkin(a3DSkin, jointsMap, parents, a3DIndexBuffers, a3DVertexBuffers, a3DMaterials, a3DMaps, a3DCubeMaps, a3DImages); + resSkin.visible = a3DSkin.visible; + resSkin.name = a3DSkin.name; + objectsMap[a3DSkin.id] = resSkin; + //The transformation should not affect skin (Due collada comatibility) + //decomposeTransformation(a3DSkin.transform, resSkin); + a3DBox = a3DBoxes[a3DSkin.boundBoxId]; + if (a3DBox != null) { + parseBoundBox(a3DBox.box, resSkin); + } + } + + for each (a3DAmbientLight in a3d.ambientLights) { + var resAmbientLight:AmbientLight = new AmbientLight(a3DAmbientLight.color); + resAmbientLight.intensity = a3DAmbientLight.intensity; + resAmbientLight.visible = a3DAmbientLight.visible; + resAmbientLight.name = a3DAmbientLight.name; + parents[resAmbientLight] = a3DAmbientLight.parentId; + objectsMap[a3DAmbientLight.id] = resAmbientLight; + decomposeTransformation(a3DAmbientLight.transform, resAmbientLight); + a3DBox = a3DBoxes[a3DAmbientLight.boundBoxId]; + if (a3DBox != null) { + parseBoundBox(a3DBox.box, resAmbientLight); + } + } + + for each (a3DOmniLight in a3d.omniLights) { + var resOmniLight:OmniLight = new OmniLight(a3DOmniLight.color, a3DOmniLight.attenuationBegin, a3DOmniLight.attenuationEnd); + resOmniLight.intensity = a3DOmniLight.intensity; + resOmniLight.visible = a3DOmniLight.visible; + resOmniLight.name = a3DOmniLight.name; + parents[resOmniLight] = a3DOmniLight.parentId; + objectsMap[a3DOmniLight.id] = resOmniLight; + decomposeTransformation(a3DOmniLight.transform, resOmniLight); + } + + for each (a3DSpotLight in a3d.spotLights) { + var resSpotLight:SpotLight = new SpotLight(a3DSpotLight.color, a3DSpotLight.attenuationBegin, a3DSpotLight.attenuationEnd, a3DSpotLight.hotspot, a3DSpotLight.falloff); + resSpotLight.intensity = a3DOmniLight.intensity; + resSpotLight.visible = a3DSpotLight.visible; + resSpotLight.name = a3DSpotLight.name; + parents[resSpotLight] = a3DSpotLight.parentId; + objectsMap[a3DSpotLight.id] = resSpotLight; + decomposeTransformation(a3DSpotLight.transform, resSpotLight); + } + + for each(a3DDirLight in a3d.directionalLights) { + var resDirLight:DirectionalLight = new DirectionalLight(a3DDirLight.color); + resDirLight.intensity = resDirLight.intensity; + resDirLight.visible = a3DDirLight.visible; + resDirLight.name = a3DDirLight.name; + parents[resDirLight] = a3DDirLight.parentId; + objectsMap[a3DDirLight.id] = resDirLight; + decomposeTransformation(a3DDirLight.transform, resDirLight); + } + + for each (a3DMesh in a3d.meshes) { + var resMesh:Mesh = parseMesh(a3DMesh, a3DIndexBuffers, a3DVertexBuffers, a3DMaterials, a3DMaps, a3DCubeMaps, a3DImages); + resMesh.visible = a3DMesh.visible; + resMesh.name = a3DMesh.name; + parents[resMesh] = a3DMesh.parentId; + objectsMap[a3DMesh.id] = resMesh; + decomposeTransformation(a3DMesh.transform, resMesh); + a3DBox = a3DBoxes[a3DMesh.boundBoxId]; + if (a3DBox != null) { + parseBoundBox(a3DBox.box, resMesh); + } + } + maps = null; + parsedMaterials = null; + parsedGeometries = null; + } + + private function completeHierarchy():void { + var parent:Long; + var p:Object3D; + var object:Object3D; + for each (object in objectsMap) { + objects.push(object); + if (object.parent != null) continue; + parent = parents[object]; + if (parent != null) { + p = objectsMap[parent]; + if (p != null) { + p.addChild(object); + } else { + hierarchy.push(object); + } + } else { + hierarchy.push(object); + } + } + } + + private function parseBoundBox(box:Vector., destination:Object3D):void { + destination.boundBox = new BoundBox(); + destination.boundBox.minX = box[0]; + destination.boundBox.minY = box[1]; + destination.boundBox.minZ = box[2]; + destination.boundBox.maxX = box[3]; + destination.boundBox.maxY = box[4]; + destination.boundBox.maxZ = box[5]; + } + + private final function unpackVertexBuffer(buffer:ByteArray):void { + var tempBuffer:ByteArray = new ByteArray(); + tempBuffer.endian = Endian.LITTLE_ENDIAN; + buffer.position = 0; + while (buffer.bytesAvailable > 0) { + var data:uint = buffer.readUnsignedShort(); + var vi:uint = data; + vi &= 0x7FFF; + vi ^= (vi + 0x1c000) ^ vi; + vi = vi << 13; + tempBuffer.writeUnsignedInt(data > 0x8000 ? vi | 0x80000000 : vi); + } + buffer.position = 0; + buffer.writeBytes(tempBuffer); + + } + + private function getMatrix3D(transform:A3D2Transform):Matrix3D { + if (transform == null) return null; + var matrix:A3DMatrix = transform.matrix; + return new Matrix3D(Vector.( + [matrix.a, matrix.e, matrix.i, 0, + matrix.b, matrix.f, matrix.j, 0, + matrix.c, matrix.g, matrix.k, 0, + matrix.d, matrix.h, matrix.l, 1 + ])); + } + + private function decomposeTransformation(transform:A3D2Transform, obj:Object3D):void { + if (transform == null) return; + var mat:Matrix3D = getMatrix3D(transform); + var vecs:Vector. = mat.decompose(); + obj.x = vecs[0].x; + obj.y = vecs[0].y; + obj.z = vecs[0].z; + obj.rotationX = vecs[1].x; + obj.rotationY = vecs[1].y; + obj.rotationZ = vecs[1].z; + obj.scaleX = vecs[2].x; + obj.scaleY = vecs[2].y; + obj.scaleZ = vecs[2].z; + } + + private function decomposeBindTransformation(transform:A3D2Transform, obj:Joint):void { + if (transform == null) return; + var matrix:A3DMatrix = transform.matrix; + var mat:Vector. = Vector.([ + matrix.a, matrix.b, matrix.c, matrix.d, + matrix.e, matrix.f, matrix.g, matrix.h, + matrix.i, matrix.j, matrix.k, matrix.l] + ); + + obj.setBindPoseMatrix(mat); + } + + private function parseMesh(a3DMesh:A3D2Mesh, indexBuffers:Dictionary, vertexBuffers:Dictionary, materials:Dictionary, a3DMaps:Dictionary, a3DCubeMaps:Dictionary, images:Dictionary):Mesh { + var res:Mesh = new Mesh(); + res.geometry = parseGeometry(a3DMesh.indexBufferId, a3DMesh.vertexBuffers, indexBuffers, vertexBuffers); + var surfaces:Vector. = a3DMesh.surfaces; + for (var i:int = 0; i < surfaces.length; i++) { + var s:A3D2Surface = surfaces[i]; + var m:ParserMaterial = parseMaterial(materials[s.materialId], a3DMaps, a3DCubeMaps, images); + res.addSurface(m, s.indexBegin, s.numTriangles); + } + return res; + } + + private function parseSkin(a3DSkin:A3D2Skin, jointsMap:Dictionary, parents:Dictionary, indexBuffers:Dictionary, vertexBuffers:Dictionary, materials:Dictionary, a3DMaps:Dictionary, a3DCubeMaps:Dictionary, images:Dictionary):Skin { + var geometry:Geometry = parseGeometry(a3DSkin.indexBufferId, a3DSkin.vertexBuffers, indexBuffers, vertexBuffers); + var res:Skin = new Skin(getNumInfluences(geometry)); + res.geometry = geometry; + var surfaces:Vector. = a3DSkin.surfaces; + for (var i:int = 0; i < surfaces.length; i++) { + var s:A3D2Surface = surfaces[i]; + var m:ParserMaterial = parseMaterial(materials[s.materialId], a3DMaps, a3DCubeMaps, images); + res.addSurface(m, s.indexBegin, s.numTriangles); + } + copyBones(res, a3DSkin, jointsMap, parents); + return res; + } + + private function copyBones(skin:Skin, a3DSkin:A3D2Skin, jointsMap:Dictionary, parents:Dictionary):void { + var rootBones:Vector. = new Vector.(); + var s2dMap:Dictionary = new Dictionary(); + var sourceJoints:Dictionary = new Dictionary(); + var jointIDs:Dictionary = new Dictionary(); + var joint:Joint; + var object:Object3D; + var indexOffset:uint = 0; + var dJoint:Joint; + for each (var numJoints:uint in a3DSkin.numJoints) { + for (var i:int = 0; i < numJoints; i++) { + var key:Long = a3DSkin.joints[int(indexOffset + i)]; + object = jointsMap[key]; + sourceJoints[key] = object; + jointIDs[object] = key; + } + indexOffset += numJoints; + } + + for (var idk:* in sourceJoints) { + object = sourceJoints[idk]; + if (object == null) { + throw new Error("Joint for skin " + a3DSkin.name + " not found"); + } + delete objectsMap[idk]; + s2dMap[object] = cloneJoint(object); + } + var count:int; + indexOffset = 0; + for (i = 0, count = a3DSkin.numJoints.length; i < count; i++) { + numJoints = a3DSkin.numJoints[i]; + skin.surfaceJoints[i] = new Vector.(); + for (var j:int = 0; j < numJoints; j++) { + skin.surfaceJoints[i].push(s2dMap[sourceJoints[a3DSkin.joints[int(indexOffset + j)]]]); + } + indexOffset += numJoints; + } + skin.calculateSurfacesProcedures(); + + for (i = 0; i < a3DSkin.jointBindTransforms.length; i++) { + var bindPose:A3D2JointBindTransform = a3DSkin.jointBindTransforms[i]; + //Joint is not affect to vertices, but affect on transformation of other joints (due to hierarchy). + if (sourceJoints[bindPose.id] == null) { + object = jointsMap[bindPose.id]; + sourceJoints[bindPose.id] = object; + s2dMap[object] = cloneJoint(object); + } + decomposeBindTransformation(bindPose.bindPoseTransform, Joint(s2dMap[sourceJoints[bindPose.id]])); + } + var skinParent:Long = null; + for each(object in sourceJoints) { + dJoint = s2dMap[object]; + var parent:Long = parents[object]; + if (isRootJointNode(object, parents, sourceJoints, jointsMap)) { + skinParent = parent; + rootBones.push(dJoint); + } else { + var pJointSource:Object3D = jointsMap[parent]; + var pJoint:Joint = s2dMap[pJointSource]; + if (pJoint == null) { + attachJoint(dJoint, object, parents, jointsMap, s2dMap); + } else { + pJoint.addChild(dJoint); + } + } + } + if (skinParent != null) { + parents[skin] = skinParent; + } + + skin._renderedJoints = new Vector.(); + for (i = 0; i < numJoints; i++) { + skin._renderedJoints.push(s2dMap[sourceJoints[a3DSkin.joints[i]]]); + } + + for each(joint in rootBones) { + skin.addChild(joint); + } + } + + private function attachJoint(joint:Joint, source:Object3D, parents:Dictionary, sourceJoints:Dictionary, s2dMap:Dictionary):void { + var parentID:Long = parents[source]; + var parentSource:Object3D = sourceJoints[parentID]; + var parentDestination:Joint = s2dMap[parentSource]; + if (parentDestination == null) { + s2dMap[parentSource] = parentDestination = cloneJoint(parentSource); + delete objectsMap[parentID]; + attachJoint(parentDestination, parentSource, parents, sourceJoints, s2dMap); + } + parentDestination.addChild(joint); + } + + private function isRootJointNode(joint:Object3D, parents:Dictionary, joints:Dictionary, jointsMap:Dictionary):Boolean { + var parent:Long = parents[joint]; + while (parent != null) { + var current:Object3D = jointsMap[parent]; + if (joints[parent] != null) { + return false; + } + parent = parents[current]; + } + + return true; + } + + private function cloneJoint(source:Object3D):Joint { + var result:Joint = new Joint(); + result.name = source.name; + result.visible = source.visible; + result.boundBox = source.boundBox ? source.boundBox.clone() : null; + result._x = source._x; + result._y = source._y; + result._z = source._z; + result._rotationX = source._rotationX; + result._rotationY = source._rotationY; + result._rotationZ = source._rotationZ; + result._scaleX = source._scaleX; + result._scaleY = source._scaleY; + result._scaleZ = source._scaleZ; + result.composeTransforms(); + return result; + } + + private function getNumInfluences(geometry:Geometry):uint { + var result:uint = 0; + for (var i:int = 0, count:int = VertexAttributes.JOINTS.length; i < count; i++) { + if (geometry.hasAttribute(VertexAttributes.JOINTS[i])) { + result += 2; + } + } + return result; + } + + private function parseGeometry(indexBufferID:int, vertexBuffersIDs:Vector., indexBuffers:Dictionary, vertexBuffers:Dictionary):Geometry { + var key:String = "i" + indexBufferID.toString(); + for each(var id:int in vertexBuffersIDs) { + key += "v" + id.toString(); + } + var geometry:Geometry = parsedGeometries[key]; + if (geometry != null) return geometry; + geometry = new Geometry(); + var a3dIB:A3D2IndexBuffer = indexBuffers[indexBufferID]; + + var indices:Vector. = A3DUtils.byteArrayToVectorUint(a3dIB.byteBuffer); + var uvoffset:int = 0; + geometry._indices = indices; + var buffers:Vector. = vertexBuffersIDs; + var vertexCount:uint; + for (var j:int = 0; j < buffers.length; j++) { + var buffer:A3D2VertexBuffer = vertexBuffers[buffers[j]]; + if (compressedBuffers) { + if (unpackedBuffers[buffer] == null) { + unpackVertexBuffer(buffer.byteBuffer); + unpackedBuffers[buffer] = true; + } + } + + vertexCount = buffer.vertexCount; + var byteArray:ByteArray = buffer.byteBuffer; + byteArray.endian = Endian.LITTLE_ENDIAN; + var offset:int = 0; + var attributes:Array = new Array(); + var jointsOffset:int = 0; + for (var k:int = 0; k < buffer.attributes.length; k++) { + var attr:int; + switch (buffer.attributes[k]) { + case A3D2VertexAttributes.POSITION: + attr = VertexAttributes.POSITION; + break; + case A3D2VertexAttributes.NORMAL: + attr = VertexAttributes.NORMAL; + break; + case A3D2VertexAttributes.TANGENT4: + attr = VertexAttributes.TANGENT4; + break; + case A3D2VertexAttributes.TEXCOORD: + attr = VertexAttributes.TEXCOORDS[uvoffset]; + uvoffset++; + break; + case A3D2VertexAttributes.JOINT: + attr = VertexAttributes.JOINTS[jointsOffset]; + jointsOffset++; + break; + } + var numFloats:int = VertexAttributes.getAttributeStride(attr); + numFloats = (numFloats < 1) ? 1 : numFloats; + for (var t:int = 0; t < numFloats; t++) { + attributes[offset] = attr; + offset++; + } + } + geometry.addVertexStream(attributes); + geometry._vertexStreams[0].data = byteArray; + } + geometry._numVertices = (buffers.length > 0) ? vertexCount : 0; + parsedGeometries[key] = geometry; + + return geometry; + } + + private function parseMap(source:A3D2Map, images:Dictionary):ExternalTextureResource { + if (source == null) return null; + var res:ExternalTextureResource = maps[source.imageId]; + if (res != null) return res; + res = maps[source.imageId] = new ExternalTextureResource(images[source.imageId].url); + return res; + } + + private function parseCubeMap(source:A3D2CubeMap, images:Dictionary):ExternalTextureResource { + return null; + } + + private function parseMaterial(source:A3D2Material, a3DMaps:Dictionary, a3DCubeMaps:Dictionary, images:Dictionary):ParserMaterial { + if (source == null) return null; + var res:ParserMaterial = parsedMaterials[source.id]; + if (res != null) return res; + + res = parsedMaterials[source.id] = new ParserMaterial(); + res.textures["diffuse"] = parseMap(a3DMaps[source.diffuseMapId], images); + res.textures["emission"] = parseMap(a3DMaps[source.lightMapId], images); + res.textures["bump"] = parseMap(a3DMaps[source.normalMapId], images); + res.textures["specular"] = parseMap(a3DMaps[source.specularMapId], images); + res.textures["glossiness"] = parseMap(a3DMaps[source.glossinessMapId], images); + res.textures["transparent"] = parseMap(a3DMaps[source.opacityMapId], images); + res.textures["reflection"] = parseCubeMap(a3DCubeMaps[source.reflectionCubeMapId], images); + materials.push(res); + return res; + } + + private static function convert1_2(source:A3D):A3D2 { + // source.boxes + var sourceBoxes:Vector. = source.boxes; + var destBoxes:Vector. = null; + if (sourceBoxes != null) { + destBoxes = new Vector.(); + for (var i:int = 0, count:int = sourceBoxes.length; i < count; i++) { + var sourceBox:A3DBox = sourceBoxes[i]; + var destBox:A3D2Box = new A3D2Box(sourceBox.box, sourceBox.id.id); + destBoxes[i] = destBox; + } + } + + // source.geometries + var sourceGeometries:Dictionary = new Dictionary(); + if (source.geometries != null) { + for each(var sourceGeometry:A3DGeometry in source.geometries) { + sourceGeometries[sourceGeometry.id.id] = sourceGeometry; + } + } + + // source.images + var sourceImages:Vector. = source.images; + var destImages:Vector. = null; + if (sourceImages != null) { + destImages = new Vector.(); + for (i = 0, count = sourceImages.length; i < count; i++) { + var sourceImage:A3DImage = sourceImages[i]; + var destImage:A3D2Image = new A3D2Image(sourceImage.id.id, sourceImage.url); + destImages[i] = destImage; + } + } + + // source.maps + var sourceMaps:Vector. = source.maps; + var destMaps:Vector. = null; + if (sourceMaps != null) { + destMaps = new Vector.(); + for (i = 0, count = sourceMaps.length; i < count; i++) { + var sourceMap:A3DMap = sourceMaps[i]; + var destMap:A3D2Map = new A3D2Map(sourceMap.channel, sourceMap.id.id, sourceMap.imageId.id); + destMaps[i] = destMap; + } + } + + // source.materials + var sourceMaterials:Vector. = source.materials; + var destMaterials:Vector. = null; + if (sourceMaterials != null) { + destMaterials = new Vector.(); + for (i = 0, count = sourceMaterials.length; i < count; i++) { + var sourceMaterial:A3DMaterial = sourceMaterials[i]; + var destMaterial:A3D2Material = + new A3D2Material( + idToInt(sourceMaterial.diffuseMapId), + idToInt(sourceMaterial.glossinessMapId), + idToInt(sourceMaterial.id), + idToInt(sourceMaterial.lightMapId), + idToInt(sourceMaterial.normalMapId), + idToInt(sourceMaterial.opacityMapId), + -1, + idToInt(sourceMaterial.specularMapId) + ); + destMaterials[i] = destMaterial; + } + } + + // source.objects + var sourceObjects:Vector. = source.objects; + var destObjects:Vector. = null; + var destMeshes:Vector. = null; + var destVertexBuffers:Vector. = null; + var destIndexBuffers:Vector. = null; + var lastIndexBufferIndex:uint = 0; + var lastVertexBufferIndex:uint = 0; + var objectsMap:Dictionary = new Dictionary(); + if (sourceObjects != null) { + destMeshes = new Vector.(); + destObjects = new Vector.(); + destVertexBuffers = new Vector.(); + destIndexBuffers = new Vector.(); + for (i = 0, count = sourceObjects.length; i < count; i++) { + var sourceObject:A3DObject = sourceObjects[i]; + if (sourceObject.surfaces != null && sourceObject.surfaces.length > 0) { + var destMesh:A3D2Mesh = null; + sourceGeometry = sourceGeometries[sourceObject.geometryId.id]; + var destIndexBufferId:int = -1; + var destVertexBuffersIds:Vector. = new Vector.(); + if (sourceGeometry != null) { + + var sourceIndexBuffer:A3DIndexBuffer = sourceGeometry.indexBuffer; + var sourceVertexBuffers:Vector. = sourceGeometry.vertexBuffers; + var destIndexBuffer:A3D2IndexBuffer = new A3D2IndexBuffer(sourceIndexBuffer.byteBuffer, lastIndexBufferIndex++, sourceIndexBuffer.indexCount); + destIndexBufferId = destIndexBuffer.id; + destIndexBuffers.push(destIndexBuffer); + for (var j:int = 0, inCount:int = sourceVertexBuffers.length; j < inCount; j++) { + var sourceVertexBuffer:A3DVertexBuffer = sourceVertexBuffers[j]; + var sourceAttributes:Vector. = sourceVertexBuffer.attributes; + var destAttributes:Vector. = new Vector.(); + for (var k:int = 0, kCount:int = sourceAttributes.length; k < kCount; k++) { + var attr:int = sourceAttributes[k]; + + switch (attr) { + case 0: + destAttributes[k] = A3D2VertexAttributes.POSITION; + break; + case 1: + destAttributes[k] = A3D2VertexAttributes.NORMAL; + break; + case 2: + destAttributes[k] = A3D2VertexAttributes.TANGENT4; + break; + case 3: + break; + case 4: + break; + case 5: + destAttributes[k] = A3D2VertexAttributes.TEXCOORD; + break; + } + } + var destVertexBuffer:A3D2VertexBuffer = + new A3D2VertexBuffer( + destAttributes, + sourceVertexBuffer.byteBuffer, + lastVertexBufferIndex++, + sourceVertexBuffer.vertexCount + ); + destVertexBuffers.push(destVertexBuffer); + destVertexBuffersIds.push(destVertexBuffer.id); + } + } + destMesh = new A3D2Mesh( + idToInt(sourceObject.boundBoxId), + idToLong(sourceObject.id), + destIndexBufferId, + sourceObject.name, + convertParent1_2(sourceObject.parentId), + convertSurfaces1_2(sourceObject.surfaces), + new A3D2Transform(sourceObject.transformation.matrix), + destVertexBuffersIds, + sourceObject.visible + ); + destMeshes.push(destMesh); + objectsMap[sourceObject.id.id] = destMesh; + } else { + var destObject:A3D2Object = new A3D2Object( + idToInt(sourceObject.boundBoxId), + idToLong(sourceObject.id), + sourceObject.name, + convertParent1_2(sourceObject.parentId), + new A3D2Transform(sourceObject.transformation.matrix), + sourceObject.visible + ); + destObjects.push(destObject); + objectsMap[sourceObject.id.id] = destObject; + } + } + } + + var result:A3D2 = new A3D2( + null, null, null, destBoxes, null, null, null, destImages, destIndexBuffers, null, + destMaps, destMaterials, + destMeshes != null && destMeshes.length > 0 ? destMeshes : null, + destObjects != null && destObjects.length > 0 ? destObjects : null, + null, null, null, null, destVertexBuffers + ); + return result; + } + + private static function idToInt(id:Id):int { + return id != null ? id.id : -1; + } + + private static function idToLong(id:Id):Long { + return id != null ? Long.fromInt(id.id) : Long.fromInt(-1); + } + + private static function convertParent1_2(parentId:ParentId):Long { + if (parentId == null) return null; + return parentId != null ? Long.fromInt(parentId.id) : null; + } + + private static function convertSurfaces1_2(source:Vector.):Vector. { + var dest:Vector. = new Vector.(); + for (var i:int = 0, count:int = source.length; i < count; i++) { + var sourceSurface:A3DSurface = source[i]; + var destSurface:A3D2Surface = new A3D2Surface( + sourceSurface.indexBegin, + idToInt(sourceSurface.materialId), + sourceSurface.numTriangles); + dest[i] = destSurface; + } + return dest; + } + + alternativa3d static function traceGeometry(geometry:Geometry):void { + var vertexStream:VertexStream = geometry._vertexStreams[0]; + var prev:int = -1; + + var attribtuesLength:int = vertexStream.attributes.length; + var stride:int = attribtuesLength*4; + var length:int = vertexStream.data.length/stride; + var data:ByteArray = vertexStream.data; + + for (var j:int = 0; j < length; j++) { + var traceString:String = "V" + j + " "; + var offset:int = -4; + for (var i:int = 0; i < attribtuesLength; i++) { + var attr:int = vertexStream.attributes[i]; + var x:Number, y:Number, z:Number; + if (attr == prev) continue; + offset = geometry.getAttributeOffset(attr)*4; + switch (attr) { + case VertexAttributes.POSITION: + data.position = j*stride + offset; + traceString += "P[" + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(2) + "] "; + break; + case 20: + data.position = j*stride + offset; + traceString += "A[" + data.readFloat().toString(2) + "]"; + break; + case VertexAttributes.NORMAL: + data.position = j*stride + offset; + x = data.readFloat(); + y = data.readFloat(); + z = data.readFloat(); + break; + case VertexAttributes.TANGENT4: + data.position = j*stride + offset; + x = data.readFloat(); + y = data.readFloat(); + z = data.readFloat(); + break; + case VertexAttributes.JOINTS[0]: + data.position = j*stride + offset; + traceString += "J0[" + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + "] "; + break; + case VertexAttributes.JOINTS[1]: + data.position = j*stride + offset; + traceString += "J1[" + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + "] "; + break; + case VertexAttributes.JOINTS[2]: + data.position = j*stride + offset; + traceString += "J1[" + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + "] "; + break; + case VertexAttributes.JOINTS[3]: + data.position = j*stride + offset; + traceString += "J1[" + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + ", " + data.readFloat().toFixed(0) + " = " + data.readFloat().toFixed(2) + "] "; + break; + + } + prev = attr; + } + trace(traceString); + + } + + } + } +} diff --git a/src/alternativa/engine3d/loaders/Parser3DS.as b/src/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..2badead --- /dev/null +++ b/src/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,1499 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.Geometry; + + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * Parser of .3ds files , that are presented as ByteArray. + */ + public class Parser3DS extends Parser { + + 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_LIGHT:int = 0x4600; + private static const CHUNK_CAMERA:int = 0x4700; + 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_FACESSMOOTHGROUPS:int = 0x4150; + private static const CHUNK_MAPPINGCOORDS:int = 0x4140; + private static const CHUNK_TRANSFORMATION:int = 0x4160; + private static const CHUNK_MATERIAL:int = 0xAFFF; + + private var data:ByteArray; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Object; + + /** + * Performs parsing. + * Result of parsing is placed in lists are follows objects, parents, materials. + * @param data ByteArray correspond to content of a 3ds file. + * @param texturesBaseURL Base path to texture files. After parsing diffuseMapURL and opacityMapURL properties gets string values, that consists of texturesBaseURL and file name. + * @param scale Amount to multiply vertex coordinates, objects coordinates and values of objects scaling. + * @param respectSmoothGroups Flag of accounting of smoothing groups. If flag set to true, then all vertices will duplicated according to smoothing groups, specified for the objects. + * + * @see alternativa.engine3d.loaders.ParserMaterial + * @see #objects + * @see #hierarchy + * @see #materials + */ + + public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1, respectSmoothGroups:Boolean = false):void { + if (data.bytesAvailable < 6) return; + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(data.position, data.bytesAvailable); + objects = new Vector.(); + hierarchy = new Vector.(); + materials = new Vector.(); + buildContent(texturesBaseURL, scale, respectSmoothGroups); + this.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) { + // Main + 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) { + // Version + case CHUNK_VERSION: + //version = data.readUnsignedInt(); + break; + // 3D-scene + case CHUNK_SCENE: + parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Animation + 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) { + // Material + case CHUNK_MATERIAL: + // Parse material + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Object + case CHUNK_OBJECT: + parseObject(chunkInfo); + break; + } + dataPosition = chunkInfo.nextChunkPosition; + bytesAvailable -= chunkInfo.size; + } + } + + private function parseObject(chunkInfo:ChunkInfo):void { + // Create list of objects, if it need. + if (objectDatas == null) { + objectDatas = new Object(); + } + // Create object data + var object:ObjectData = new ObjectData(); + // Get object name + object.name = getString(chunkInfo.dataPosition); + // Get object data to list + objectDatas[object.name] = object; + // Parse 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) { + // Mesh + case CHUNK_TRIMESH: + parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Light source + case CHUNK_LIGHT: + parseLightChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Camera + case CHUNK_CAMERA: + parseCameraChunk(object, chunkInfo.dataSize); + 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) { + // Vertices + case CHUNK_VERTICES: + parseVertices(object); + break; + // UV + case CHUNK_MAPPINGCOORDS: + parseUVs(object); + break; + // Transformation + case CHUNK_TRANSFORMATION: + parseMatrix(object); + break; + // Faces + 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.(3*num, true); + 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.(2*num, true); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.uvs[j++] = data.readFloat(); + object.uvs[j++] = 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.smoothGroups = new Vector.(num, true); + object.faces = new Vector.(3*num, true); + 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; // Skip the flag of edges rendering + } + 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) { + // Surfaces + case CHUNK_FACESMATERIAL: + parseSurface(object); + break; + // Smoothing groups. + case CHUNK_FACESSMOOTHGROUPS: + parseSmoothGroups(object); + break; + } + parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseSurface(object:ObjectData):void { + // Create list of surfaces, if it need. + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Name of surface and number of faces. + var sur:String = getString(data.position); + var num:int = data.readUnsignedShort(); + if (num > 0) { + // Create surface data + var surface:Vector. = new Vector.(num + 1); + // Put surface data to list + object.surfaces[sur] = surface; + // Get faces of surface + for (var i:int = 0; i < num; i++) { + surface[i] = data.readUnsignedShort(); + } + // Also stores number of the material (starts from 1) (additionally store the serial number of material (beginning from the one)) + surface[num] = (object.surfacesCount++); + } + } + + private function parseSmoothGroups(object:ObjectData):void { + var num:int = object.faces.length/3; + for (var i:int = 0; i < num; i++) { + object.smoothGroups [i] = data.readUnsignedInt(); + } + } + + private function parseAnimationChunk(dataPosition:int, bytesAvailable:int):void { + while (bytesAvailable >= 6) { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Object animation + case 0xB001: // ambient o_O + case 0xB002: + case 0xB003: + case 0xB004: // cam target + case 0xB005: + case 0xB006: // spot target + case 0xB007: + if (animationDatas == null) { + animationDatas = new Array(); + } + var animation:AnimationData = new AnimationData(); + animation.chunkId = chunkInfo.id; + animationDatas.push(animation); + parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Timeline + 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) { + // Identification of object and its link + case 0xB010: + // Name of the object + animation.objectName = getString(data.position); + if ((animation.chunkId == 0xB004) || (animation.chunkId == 0xB006)) animation.objectName += "_target"; + data.position += 4; + // Index of parent object in plain list of scene objects. + animation.parentIndex = data.readUnsignedShort(); + break; + // Name of dummy object + case 0xB011: + animation.instanceOf = animation.objectName; + animation.objectName = getString(data.position); + break; + // Pivot + case 0xB013: + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Offset of the object relative to its parent + case 0xB020: + data.position += 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Rotation of object relative to its parent (angle-axis) + case 0xB021: + data.position += 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Scale of object relative to its parent + 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) { + // Name of material + case 0xA000: + parseMaterialName(material); + break; + // Ambient color + case 0xA010: + data.position = chunkInfo.dataPosition + 6; + material.ambient = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Diffuse color + case 0xA020: + data.position = chunkInfo.dataPosition + 6; + material.diffuse = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + data.position = chunkInfo.dataPosition + 6; + material.specular = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Shininess percent + case 0xA040: + data.position = chunkInfo.dataPosition + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + break; + // Transparensy + case 0xA050: + data.position = chunkInfo.dataPosition + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + parseMaterialMapData("diffuse", material, chunkInfo); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + parseMaterialMapData("transparent", material, chunkInfo); + break; + // Bump map + case 0xA230: + parseMaterialMapData("bump", material, chunkInfo); + break; + // Specular map + case 0xA204: + parseMaterialMapData("specular", material, chunkInfo); + break; + // Shininess map + case 0xA33C: + parseMaterialMapData("glossiness", material, chunkInfo); + break; + // Self-illumination map + case 0xA33D: + parseMaterialMapData("emission", material, chunkInfo); + break; + // Reflection map + case 0xA220: + parseMaterialMapData("reflective", material, chunkInfo); + break; + } + parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMaterialMapData(channel:String, material:MaterialData, chunkInfo:ChunkInfo):void { + var map:MapData = new MapData; + map.channel = channel; + parseMapChunk(material.name, map, chunkInfo.dataPosition, chunkInfo.dataSize); + material.maps.push(map); + } + + private function parseMaterialName(material:MaterialData):void { + // Create list of materials, if it need + if (materialDatas == null) { + materialDatas = new Object(); + } + // Get name of material + material.name = getString(data.position); + // Put data of material in list + 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) { + // File name + case 0xA300: + map.filename = getString(chunkInfo.dataPosition).toLowerCase(); + break; + case 0xA351: + // Texture mapping options + break; + // Scale along U + case 0xA354: + map.scaleU = data.readFloat(); + break; + // Scale along V + case 0xA356: + map.scaleV = data.readFloat(); + break; + // Offset along U + case 0xA358: + map.offsetU = data.readFloat(); + break; + // Offset along V + case 0xA35A: + map.offsetV = data.readFloat(); + break; + // Rotation angle + 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; + } + + private function parseLightChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6 + 12) return; + var x:Number = data.readFloat(); + var y:Number = data.readFloat(); + var z:Number = data.readFloat(); + object.position = new Vector3D(x, y, z); + parseLightSubChunk(object, dataPosition + 12, bytesAvailable - 12); + } + + private function parseLightSubChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Float RGB + case 0x0010: + var r:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); + var g:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); + var b:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); + object.lightColor = r*65536 + g*256 + b; + break; + // Byte RGB + case 0x0011: + r = data.readUnsignedByte(); + g = data.readUnsignedByte(); + b = data.readUnsignedByte(); + object.lightColor = r*65536 + g*256 + b; + break; + // Spot light + case 0x4610: + var x:Number = data.readFloat(); + var y:Number = data.readFloat(); + var z:Number = data.readFloat(); + object.target = new Vector3D(x, y, z); + object.hotspot = data.readFloat(); + object.falloff = data.readFloat(); + break; + // Light is off + case 0x4620: + object.lightOff = true; + break; + // Attenuation is on + case 0x4625: + object.attenuationOn = true; + break; + // Inner range + case 0x4659: + object.innerRange = data.readFloat(); + break; + // Outer range + case 0x465A: + object.outerRange = data.readFloat(); + break; + // Multiplier + case 0x465B: + object.multiplier = data.readFloat(); + break; + default: + break; + } + parseLightSubChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseCameraChunk(object:ObjectData, bytesAvailable:int):void { + if (bytesAvailable < 32) return; + var x:Number = data.readFloat(); + var y:Number = data.readFloat(); + var z:Number = data.readFloat(); + object.position = new Vector3D(x, y, z); + x = data.readFloat(); + y = data.readFloat(); + z = data.readFloat(); + object.target = new Vector3D(x, y, z); + object.bank = data.readFloat(); + object.lens = data.readFloat(); + } + + private function buildContent(texturesBaseURL:String, scale:Number, respectSmoothGroups:Boolean):void { + // Calculation of matrices of texture materials + for (var materialName:String in materialDatas) { + var materialData:MaterialData = materialDatas[materialName]; + materialData.material = new ParserMaterial(); + materialData.material.name = materialName; + var mapData:MapData = materialData.diffuseMap; + if (mapData != null) { + if ((mapData.rotation != 0) || + (mapData.offsetU != 0) || + (mapData.offsetV != 0) || + (mapData.scaleU != 1) || + (mapData.scaleV != 1)) { + // transformation of texture is set + var materialMatrix:Matrix = new Matrix(); + var rot:Number = mapData.rotation*Math.PI/180; + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.rotate(-rot); + materialMatrix.translate(0.5, 0.5); + materialData.matrix = materialMatrix; + } + } + for each (mapData in materialData.maps) { + materialData.material.textures[mapData.channel] = new ExternalTextureResource(texturesBaseURL + mapData.filename); + } + materialData.material.colors["ambient"] = materialData.ambient; + materialData.material.colors["diffuse"] = materialData.diffuse; + materialData.material.colors["specular"] = materialData.specular; + materialData.material.transparency = 0.01*materialData.transparency; + materials.push(materialData.material); + } + var objectName:String; + var objectData:ObjectData; + var object:Object3D; + // Scene has hierarchically related objects and (or) specified data about objects transformations. + if (animationDatas != null) { + if (objectDatas != null) { + var i:int; + var length:int = animationDatas.length; + var animationData:AnimationData; + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + // Check for instances + if (objectData != null) { + for (var j:int = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.instanceOf) { + animationData2.instanceOf = null; + // Found match name for current part of animation, so make reference for it. + var newObjectData:ObjectData = new ObjectData(); + newObjectData.name = animationData2.objectName; + objectDatas[animationData2.objectName] = newObjectData; + + newObjectData.vertices = objectData.vertices; + newObjectData.uvs = objectData.uvs; + newObjectData.faces = objectData.faces; + newObjectData.surfaces = objectData.surfaces; + newObjectData.surfacesCount = objectData.surfacesCount; + newObjectData.smoothGroups = objectData.smoothGroups; + newObjectData.a = objectData.a; + newObjectData.b = objectData.b; + newObjectData.c = objectData.c; + newObjectData.d = objectData.d; + newObjectData.e = objectData.e; + newObjectData.f = objectData.f; + newObjectData.g = objectData.g; + newObjectData.h = objectData.h; + newObjectData.i = objectData.i; + newObjectData.j = objectData.j; + newObjectData.k = objectData.k; + newObjectData.l = objectData.l; + newObjectData.lightColor = objectData.lightColor; + newObjectData.lightOff = objectData.lightOff; + newObjectData.attenuationOn = objectData.attenuationOn; + newObjectData.hotspot = objectData.hotspot; + newObjectData.falloff = objectData.falloff; + newObjectData.innerRange = objectData.innerRange; + newObjectData.outerRange = objectData.outerRange; + newObjectData.multiplier = objectData.multiplier; + newObjectData.position = objectData.position; + newObjectData.target = objectData.target; + newObjectData.bank = objectData.bank; + newObjectData.lens = objectData.lens; + } + } + } + + if (objectData != null) { + object = buildObject3D(objectData, animationData, scale, respectSmoothGroups); + } else { + // Create empty Object3D + object = new Object3D(); + } + object.name = objectName; + animationData.object = object; + if (animationData.position != null) { + object.x = animationData.position.x*scale; + object.y = animationData.position.y*scale; + object.z = animationData.position.z*scale; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + } + // Add objects + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objects.push(animationData.object); + if (animationData.parentIndex == 0xFFFF) { + hierarchy.push(animationData.object); + } else { + AnimationData(animationDatas[animationData.parentIndex]).object.addChild(animationData.object); + } + } + } + // Scene has no hierarchically related objects and data about objects transformations is not specified. Only polygonal objects woll added to container. + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + object = buildObject3D(objectData, null, scale, respectSmoothGroups); + object.name = objectName; + objects.push(object); + hierarchy.push(object); + } + } + } + } + + private function buildObject3D(objectData:ObjectData, animationData:AnimationData, scale:Number, respectSmoothGroups:Boolean):Object3D { + var object:Object3D; + if (objectData.vertices != null) { + // Create polygonal object + object = new Mesh(); + buildMesh(object as Mesh, objectData, animationData, scale, respectSmoothGroups); + } else { + if (objectData.lightColor >= 0) { + // Light + var innerRange:Number = 0; + var outerRange:Number = 1e15; // must be Number.MAX_VALUE, but if you set radius to ~2^60 and more then SpotLight is not working. + if (objectData.attenuationOn && (objectData.outerRange < Number.MAX_VALUE)) { + innerRange = objectData.innerRange*scale; + outerRange = objectData.outerRange*scale; + } + if (objectData.target != null) { + var rad:Number = Math.PI/180; + object = new SpotLight(objectData.lightColor, innerRange, outerRange, objectData.hotspot*rad, objectData.falloff*rad); + } else { + object = new OmniLight(objectData.lightColor, innerRange, outerRange); + } + // Light intensity + Light3D(object).intensity = objectData.lightOff ? 0 : objectData.multiplier; + } else { + // Camera or something else + object = new Object3D; + } + if (objectData.position) { + object.x = objectData.position.x*scale; + object.y = objectData.position.y*scale; + object.z = objectData.position.z*scale; + if (objectData.target) { + // Turn object to target + var dx:Number = objectData.target.x*scale - object.x; + var dy:Number = objectData.target.y*scale - object.y; + var dz:Number = objectData.target.z*scale - object.z; + object.rotationX = (Math.atan2(dz, Math.sqrt(((dx*dx) + (dy*dy)))) - (Math.PI/2)); + object.rotationY = 0; + object.rotationZ = -(Math.atan2(dx, dy)); + // Pitch + var matrix:Matrix3D = object.matrix; + matrix.prependRotation(objectData.bank, Vector3D.Z_AXIS); + object.matrix = matrix; + } + } + } + return object; + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, scale:Number, respectSmoothGroups:Boolean):void { + // Quit early + if (objectData.faces == null) { + return; + } + + var vertices:Vector. = new Vector.(objectData.vertices.length/3); + var faces:Array = new Array(objectData.faces.length/3); // Vector. can't .sortOn() + + buildInitialGeometry(vertices, faces, objectData, animationData, scale); + + if (respectSmoothGroups) { + cloneVerticesToRespectSmoothGroups(vertices, faces); + } + + calculateVertexNormals(vertices, faces); + + if (materialDatas != null) { + assignMaterialsToFaces(faces, objectData); + + cloneAndTransformVerticesToRespectUVTransforms(vertices, faces); + } + + calculateVertexTangents(vertices, faces); + + // Default material for the faces without surfaces. + var defaultMaterialData:MaterialData = new MaterialData; + defaultMaterialData.numTriangles = 0; + defaultMaterialData.material = new ParserMaterial; + defaultMaterialData.material.colors["diffuse"] = 0x7F7F7F; + defaultMaterialData.material.name = "default"; + + var indices:Vector. = collectFacesIntoSurfaces(faces, defaultMaterialData); + + // Put all to mesh + var vec:Vector3D, vertex:Vertex; + var numVertices:int = vertices.length; + var byteArray:ByteArray = new ByteArray(); + byteArray.endian = Endian.LITTLE_ENDIAN; + for (var n:int = 0; n < numVertices; n++) { + vertex = vertices [n]; + byteArray.writeFloat(vertex.x); + byteArray.writeFloat(vertex.y); + byteArray.writeFloat(vertex.z); + byteArray.writeFloat(vertex.u); + byteArray.writeFloat(vertex.v); + + vec = vertex.normal; + byteArray.writeFloat(vec.x); + byteArray.writeFloat(vec.y); + byteArray.writeFloat(vec.z); + + vec = vertex.tangent; + byteArray.writeFloat(vec.x); + byteArray.writeFloat(vec.y); + byteArray.writeFloat(vec.z); + byteArray.writeFloat(vec.w); + } + mesh.geometry = new Geometry; + mesh.geometry._indices = indices; + mesh.geometry.addVertexStream([ + VertexAttributes.POSITION, + VertexAttributes.POSITION, + VertexAttributes.POSITION, + VertexAttributes.TEXCOORDS[0], + VertexAttributes.TEXCOORDS[0], + VertexAttributes.NORMAL, + VertexAttributes.NORMAL, + VertexAttributes.NORMAL, + VertexAttributes.TANGENT4, + VertexAttributes.TANGENT4, + VertexAttributes.TANGENT4, + VertexAttributes.TANGENT4 + ]); + mesh.geometry._vertexStreams[0].data = byteArray; + mesh.geometry._numVertices = numVertices; + if (objectData.surfaces != null) { + for (var key:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[key]; + mesh.addSurface(materialData.material, 3*materialData.indexBegin, materialData.numTriangles); + } + } + if (defaultMaterialData.numTriangles > 0) { + mesh.addSurface(defaultMaterialData.material, 3*defaultMaterialData.indexBegin, defaultMaterialData.numTriangles); + } + mesh.calculateBoundBox(); + } + + private function buildInitialGeometry(vertices:Vector., faces:Array, objectData:ObjectData, animationData:AnimationData, scale:Number):void { + var correct:Boolean = false; + if (animationData != null) { + var a:Number = objectData.a; + var b:Number = objectData.b; + var c:Number = objectData.c; + var d:Number = objectData.d; + var e:Number = objectData.e; + var f:Number = objectData.f; + var g:Number = objectData.g; + var h:Number = objectData.h; + var i:Number = objectData.i; + var j:Number = objectData.j; + var k:Number = objectData.k; + var l:Number = objectData.l; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + objectData.a = (-g*j + f*k)*det; + objectData.b = (c*j - b*k)*det; + objectData.c = (-c*f + b*g)*det; + objectData.d = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + objectData.e = (g*i - e*k)*det; + objectData.f = (-c*i + a*k)*det; + objectData.g = (c*e - a*g)*det; + objectData.h = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + objectData.i = (-f*i + e*j)*det; + objectData.j = (b*i - a*j)*det; + objectData.k = (-b*e + a*f)*det; + objectData.l = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + if (animationData.pivot != null) { + objectData.d -= animationData.pivot.x; + objectData.h -= animationData.pivot.y; + objectData.l -= animationData.pivot.z; + } + correct = true; + } + // Creation and correcting of vertices + var n:int, m:int, p:int, len:int = objectData.vertices.length; + var uv:Boolean = objectData.uvs != null && objectData.uvs.length > 0; + for (n = 0, m = 0, p = 0; n < len;) { + var vertex:Vertex = new Vertex; + if (correct) { + var x:Number = objectData.vertices[n++]; + var y:Number = objectData.vertices[n++]; + var z:Number = objectData.vertices[n++]; + vertex.x = objectData.a*x + objectData.b*y + objectData.c*z + objectData.d; + vertex.y = objectData.e*x + objectData.f*y + objectData.g*z + objectData.h; + vertex.z = objectData.i*x + objectData.j*y + objectData.k*z + objectData.l; + } else { + vertex.x = objectData.vertices[n++]; + vertex.y = objectData.vertices[n++]; + vertex.z = objectData.vertices[n++]; + } + vertex.x *= scale; + vertex.y *= scale; + vertex.z *= scale; + if (uv) { + vertex.u = objectData.uvs[m++]; + vertex.v = 1 - objectData.uvs[m++]; + } else { + // If you leave object without uv, then the calculation of tangents is breaks + x = vertex.x; + y = vertex.y; + var rxy:Number = 1e-5 + Math.sqrt(x*x + y*y); + vertex.u = Math.atan2(rxy, vertex.z); + vertex.v = Math.atan2(y, x); + } + vertices[p++] = vertex; + } + // Create faces + len = objectData.faces.length; + for (n = 0, p = 0; n < len;) { + var face:Face = new Face(); + face.a = objectData.faces[n++]; + face.b = objectData.faces[n++]; + face.c = objectData.faces[n++]; + face.smoothGroup = objectData.smoothGroups[p]; + faces[p++] = face; + } + } + + private function cloneVerticesToRespectSmoothGroups(vertices:Vector., faces:Array):void { + // Actions with smoothing groups: + // - if vertex is in faces with groups 1+2 and 3, then it is duplicated + // - if vertex is in faces with groups 1+2, 3 and 1+3, then it is not duplicated + + var n:int, m:int, p:int, q:int, len:int, numVertices:int = vertices.length, numFaces:int = faces.length; + // Calculate disjoint groups for vertices + var vertexGroups:Vector.> = new Vector.>(numVertices, true); + for (p = 0; p < numVertices; p++) { + vertexGroups [p] = new Vector.; + } + for (n = 0; n < numFaces; n++) { + var face:Face = Face(faces[n]); + for (m = 0; m < 3; m++) { + var groups:Vector. = vertexGroups [(m == 0) ? face.a : ((m == 1) ? face.b : face.c)]; + var group:uint = face.smoothGroup; + for (q = groups.length - 1; q >= 0; q--) { + if ((group & groups [q]) > 0) { + group |= groups [q]; + groups.splice(q, 1); + q = groups.length - 1; + } + } + groups.push(group); + } + } + // Clone vertices + var vertexClones:Vector.> = new Vector.>(numVertices, true); + for (p = 0; p < numVertices; p++) { + if ((len = vertexGroups [p].length) < 1) continue; + var clones:Vector. = new Vector.(len, true); + vertexClones [p] = clones; + clones [0] = p; + var vertex0:Vertex = vertices [p]; + for (m = 1; m < len; m++) { + var vertex1:Vertex = new Vertex; + vertex1.x = vertex0.x; + vertex1.y = vertex0.y; + vertex1.z = vertex0.z; + vertex1.u = vertex0.u; + vertex1.v = vertex0.v; + clones[m] = vertices.length; + vertices.push(vertex1); + } + } + numVertices = vertices.length; + + // Loop on faces + for (n = 0; n < numFaces; n++) { + face = Face(faces [n]); + group = face.smoothGroup; + + for (m = 0; m < 3; m++) { + p = (m == 0) ? face.a : ((m == 1) ? face.b : face.c); + groups = vertexGroups [p]; + len = groups.length; + clones = vertexClones [p]; + for (q = 0; q < len; q++) { + if (((group == 0) && (groups [q] == 0)) || + ((group & groups [q]) > 0)) { + var index:uint = clones [q]; + if (group == 0) { + // In case of there is no smoothing group, vertices of this face is unique + groups.splice(q, 1); + clones.splice(q, 1); + } + if (m == 0) face.a = index; else + if (m == 1) face.b = index; else + face.c = index; + q = len; + } + } + } + } + } + + private function cloneAndTransformVerticesToRespectUVTransforms(vertices:Vector., faces:Array):void { + // Actions with UV transformation + // if vertex in faces with different transform materials, then it is duplicated + var n:int, m:int, p:int, q:int, len:int, numVertices:int = vertices.length, numFaces:int = faces.length; + // Find transform materials for vertices + var vertexGroups:Vector.> = new Vector.>(numVertices, true); + for (p = 0; p < numVertices; p++) { + vertexGroups [p] = new Vector.; + } + for (n = 0; n < numFaces; n++) { + var face:Face = Face(faces [n]); + for (m = 0; m < 3; m++) { + var groups:Vector. = vertexGroups [(m == 0) ? face.a : ((m == 1) ? face.b : face.c)]; + var group:uint = face.uvTransformGroup; + if (groups.indexOf(group) < 0) groups.push(group); + } + } + // Clone vertices + var vertexClones:Vector.> = new Vector.>(numVertices, true); + for (p = 0; p < numVertices; p++) { + if ((len = vertexGroups [p].length) < 1) continue; + var clones:Vector. = new Vector.(len, true); + vertexClones [p] = clones; + clones [0] = p; + var vertex0:Vertex = vertices [p]; + for (m = 1; m < len; m++) { + var vertex1:Vertex = new Vertex; + vertex1.x = vertex0.x; + vertex1.y = vertex0.y; + vertex1.z = vertex0.z; + vertex1.u = vertex0.u; + vertex1.v = vertex0.v; + vertex1.normal = vertex0.normal; + clones [m] = vertices.length; + vertices.push(vertex1); + } + } + numVertices = vertices.length; + // Parse on faces, and apply the transformation + for (n = 0; n < numFaces; n++) { + face = Face(faces [n]); + group = face.uvTransformGroup; + + var materialData:MaterialData = materialDatas[face.surfaceName]; + + for (m = 0; m < 3; m++) { + p = (m == 0) ? face.a : ((m == 1) ? face.b : face.c); + groups = vertexGroups [p]; + len = groups.length; + clones = vertexClones [p]; + q = groups.indexOf(group); // must aways be in groups + var index:uint = clones [q]; + if (m == 0) face.a = index; else + if (m == 1) face.b = index; else + face.c = index; + + if (group > 0) { + vertex0 = vertices [index]; + if (vertex0.nonTransformed) { + vertex0.nonTransformed = false; + var u:Number = vertex0.u; + var v:Number = vertex0.v; + vertex0.u = materialData.matrix.a*u + materialData.matrix.b*v + materialData.matrix.tx; + vertex0.v = materialData.matrix.c*u + materialData.matrix.d*v + materialData.matrix.ty; + } + } + } + } + } + + private function calculateVertexNormals(vertices:Vector., faces:Array):void { + var n:int, m:int, numFaces:int = faces.length; + for (n = 0; n < numFaces; n++) { + var face:Face = Face(faces [n]); + + // Calculation of average normals of vertices + var vertex0:Vertex = vertices [face.a]; + var vertex1:Vertex = vertices [face.b]; + var vertex2:Vertex = vertices [face.c]; + + var deltaX1:Number = vertex1.x - vertex0.x; + var deltaY1:Number = vertex1.y - vertex0.y; + var deltaZ1:Number = vertex1.z - vertex0.z; + var deltaX2:Number = vertex2.x - vertex0.x; + var deltaY2:Number = vertex2.y - vertex0.y; + var deltaZ2:Number = vertex2.z - vertex0.z; + + face.deltaX1 = deltaX1; + face.deltaY1 = deltaY1; + face.deltaZ1 = deltaZ1; + face.deltaX2 = deltaX2; + face.deltaY2 = deltaY2; + face.deltaZ2 = deltaZ2; + + var normalX:Number = deltaZ2*deltaY1 - deltaY2*deltaZ1; + var normalY:Number = deltaX2*deltaZ1 - deltaZ2*deltaX1; + var normalZ:Number = deltaY2*deltaX1 - deltaX2*deltaY1; + + var normalLen:Number = 1e-5 + Math.sqrt(normalX*normalX + normalY*normalY + normalZ*normalZ); + normalX = normalX/normalLen; + normalY = normalY/normalLen; + normalZ = normalZ/normalLen; + + for (m = 0; m < 3; m++) { + var vertex:Vertex = (m == 0) ? vertex0 : ((m == 1) ? vertex1 : vertex2); + if (vertex.normal == null) { + vertex.normal = new Vector3D(normalX, normalY, normalZ); + } else { + var vec:Vector3D = vertex.normal; + vec.x += normalX; + vec.y += normalY; + vec.z += normalZ; + } + } + } + } + + private function calculateVertexTangents(vertices:Vector., faces:Array):void { + var n:int, m:int, numVertices:int = vertices.length, numFaces:int = faces.length; + for (n = 0; n < numFaces; n++) { + var face:Face = Face(faces [n]); + + // Calculation of average tangents of vertices + var vertex0:Vertex = vertices [face.a]; + var vertex1:Vertex = vertices [face.b]; + var vertex2:Vertex = vertices [face.c]; + + var deltaU1:Number = vertex1.u - vertex0.u; + var deltaV1:Number = vertex0.v - vertex1.v; + var deltaU2:Number = vertex2.u - vertex0.u; + var deltaV2:Number = vertex0.v - vertex2.v; + + // Inverse determinant is included to the formulas below as common multiplier. + // Its value is insignificantly, because vectors are normalized + //var invdet:Number = 1 / (deltaU1 * deltaV2 - deltaU2 * deltaV1); + //if (invdet > 1e9) invdet = 1e9; else if (invdet < -1e9) invdet = -1e9; + + var deltaX1:Number = face.deltaX1; + var deltaY1:Number = face.deltaY1; + var deltaZ1:Number = face.deltaZ1; + var deltaX2:Number = face.deltaX2; + var deltaY2:Number = face.deltaY2; + var deltaZ2:Number = face.deltaZ2; + + var stMatrix00:Number = (deltaV2)//*invdet; + var stMatrix01:Number = -(deltaV1)//*invdet; + var stMatrix10:Number = -(deltaU2)//*invdet; + var stMatrix11:Number = (deltaU1)//*invdet; + + var tangentX:Number = stMatrix00*deltaX1 + stMatrix01*deltaX2; + var tangentY:Number = stMatrix00*deltaY1 + stMatrix01*deltaY2; + var tangentZ:Number = stMatrix00*deltaZ1 + stMatrix01*deltaZ2; + + var biTangentX:Number = stMatrix10*deltaX1 + stMatrix11*deltaX2; + var biTangentY:Number = stMatrix10*deltaY1 + stMatrix11*deltaY2; + var biTangentZ:Number = stMatrix10*deltaZ1 + stMatrix11*deltaZ2; + + var tangentLen:Number = 1e-5 + Math.sqrt(tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ); + tangentX = tangentX/tangentLen; + tangentY = tangentY/tangentLen; + tangentZ = tangentZ/tangentLen; + + var biTangentLen:Number = 1e-5 + Math.sqrt(biTangentX*biTangentX + biTangentY*biTangentY + biTangentZ*biTangentZ); + biTangentX = biTangentX/biTangentLen; + biTangentY = biTangentY/biTangentLen; + biTangentZ = biTangentZ/biTangentLen; + + for (m = 0; m < 3; m++) { + var vertex:Vertex = (m == 0) ? vertex0 : ((m == 1) ? vertex1 : vertex2); + if (vertex.tangent == null) { + vertex.tangent = new Vector3D(tangentX, tangentY, tangentZ); + vertex.biTangent = new Vector3D(biTangentX, biTangentY, biTangentZ); + } else { + var vec:Vector3D; + vec = vertex.tangent; + vec.x += tangentX; + vec.y += tangentY; + vec.z += tangentZ; + vec = vertex.biTangent; + vec.x += biTangentX; + vec.y += biTangentY; + vec.z += biTangentZ; + } + } + } + + // orthonormalize TBN's + var normalX:Number, normalY:Number, normalZ:Number, dot:Number, vec2:Vector3D; + for (n = 0; n < numVertices; n++) { + vertex = vertices [n]; + if (vertex.normal == null) { + vertex.normal = Vector3D.X_AXIS; + vertex.tangent = Vector3D.Y_AXIS.clone(); + vertex.tangent.w = 1; + } else { + vec = vertex.normal; + vec.normalize(); + normalX = vec.x; + normalY = vec.y; + normalZ = vec.z; + + vec = vertex.tangent; + tangentX = vec.x; + tangentY = vec.y; + tangentZ = vec.z; + + dot = normalX*tangentX + normalY*tangentY + normalZ*tangentZ; + + // perform orthonormalization between normal and tangent: tangent -= normal*dot; + tangentX -= normalX*dot; + tangentY -= normalY*dot; + tangentZ -= normalZ*dot; + + tangentLen = tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ; + if (tangentLen > 0) { + tangentLen = Math.sqrt(tangentLen); + vec.x = tangentX/tangentLen; + vec.y = tangentY/tangentLen; + vec.z = tangentZ/tangentLen; + + // calculate direction of bi-normal + var crossX:Number = normalY*tangentZ - normalZ*tangentY; + var crossY:Number = normalZ*tangentX - normalX*tangentZ; + var crossZ:Number = normalX*tangentY - normalY*tangentX; + + vec2 = vertex.biTangent; + + dot = crossX*vec2.x + crossY*vec2.y + crossZ*vec2.z; + vec.w = (dot < 0) ? -1 : 1; + } else { + // tangent is degenerate, try to start from bi-normal + vec = vertex.biTangent; + biTangentX = vec.x; + biTangentY = vec.y; + biTangentZ = vec.z; + + dot = normalX*biTangentX + normalY*biTangentY + normalZ*biTangentZ; + + // perform orthonormalization between normal and bi-normal: biTangent -= normal*dot; + biTangentX -= normalX*dot; + biTangentY -= normalY*dot; + biTangentZ -= normalZ*dot; + + biTangentLen = biTangentX*biTangentX + biTangentY*biTangentY + biTangentZ*biTangentZ; + if (biTangentLen > 0) { + biTangentLen = Math.sqrt(biTangentLen); + vec.x = biTangentX/biTangentLen; + vec.y = biTangentY/biTangentLen; + vec.z = biTangentZ/biTangentLen; + } else { + // bi-normal is degenerate too, get any vector that is perpendicular to the normal + if (normalX != 0) { + vec.x = -normalY; + vec.y = normalX; + vec.z = 0; + } else { + vec.x = 0; + vec.y = -normalZ; + vec.z = normalY; + } + } + + // calculate tangent + biTangentX = vec.x; + biTangentY = vec.y; + biTangentZ = vec.z; + + vec = vertex.tangent; + vec.x = -(normalY*biTangentZ - normalZ*biTangentY); + vec.y = -(normalZ*biTangentX - normalX*biTangentZ); + vec.z = -(normalX*biTangentY - normalY*biTangentX); + + dot = biTangentX*vec.x + biTangentY*vec.y + biTangentZ*vec.z; + vec.w = (dot < 0) ? -1 : 1; + } + } + } + } + + private function assignMaterialsToFaces(faces:Array, objectData:ObjectData):void { + // Assign materials + if (objectData.surfaces != null) { + for (var key:String in objectData.surfaces) { + var surface:Vector. = objectData.surfaces[key]; + // Get serial number of material for sorting + var surfaceIndex:uint = surface.pop(); + var materialData:MaterialData = materialDatas[key]; + for (var n:int = 0; n < surface.length; n++) { + var face:Face = faces[surface[n]]; + face.surface = surfaceIndex; + face.surfaceName = key; + // If it need to correct UV-coordianates + face.uvTransformGroup = (materialData.matrix != null) ? surfaceIndex : 0; + } + } + } + } + + private function collectFacesIntoSurfaces(faces:Array, defaultMaterialData:MaterialData):Vector. { + // Sort faces on materials + faces.sortOn("surface"); + + // Create indices, calculate indexBegin and numTriangles + var numFaces:int = faces.length; + var indices:Vector. = new Vector.(numFaces*3, true); + + var lastMaterialData:MaterialData; + for (var n:int = 0; n < numFaces; n++) { + var face:Face = Face(faces [n]); + + var m:int = n*3; + indices [m] = face.a; + indices [m + 1] = face.b; + indices [m + 2] = face.c; + + var materialData:MaterialData = defaultMaterialData; + if (face.surfaceName != null) { + materialData = materialDatas[face.surfaceName]; + } + if (lastMaterialData != materialData) { + lastMaterialData = materialData; + materialData.indexBegin = n; + materialData.numTriangles = 1; + } else { + materialData.numTriangles++; + } + } + return indices; + } + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.engine3d.loaders.ParserMaterial; + +import flash.geom.Matrix; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var ambient:uint; + public var diffuse:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var matrix:Matrix; + public var material:ParserMaterial; + + public var maps:Vector. = new Vector.(); + + public function get diffuseMap():MapData { + for each (var map:MapData in maps) { + if (map.channel == "diffuse") { + return map; + } + } + return null; + } + + // parameters for Mesh.addSurface() + public var indexBegin:uint; + public var numTriangles:uint; +} + +class MapData { + public var channel:String; + 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; + // mesh + public var vertices:Vector.; + public var uvs:Vector.; + public var faces:Vector.; + public var surfaces:Object; + public var surfacesCount:uint; + public var smoothGroups:Vector.; + 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; + // light or camera + public var lightColor:int = -1; + public var lightOff:Boolean; + public var attenuationOn:Boolean; + public var hotspot:Number = 0; + public var falloff:Number = 0; + public var innerRange:Number = 0; + public var outerRange:Number = Number.MAX_VALUE; + public var multiplier:Number = 1; + public var position:Vector3D; + public var target:Vector3D; + public var bank:Number = 0; + public var lens:Number; +} + +class AnimationData { + public var chunkId:uint; + public var objectName:String; + public var object:Object3D; + public var parentIndex:int; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; + public var instanceOf:String; +} + +class ChunkInfo { + public var id:int; + public var size:int; + public var dataSize:int; + public var dataPosition:int; + public var nextChunkPosition:int; +} + +class Vertex { + public var x:Number; + public var y:Number; + public var z:Number; + public var u:Number; + public var v:Number; + public var nonTransformed:Boolean = true; + public var normal:Vector3D; + public var tangent:Vector3D; + public var biTangent:Vector3D; +} + +class Face { + public var a:uint; + public var b:uint; + public var c:uint; + public var surface:uint; + public var surfaceName:String; + public var smoothGroup:uint; + public var uvTransformGroup:uint; + public var deltaX1:Number; + public var deltaY1:Number; + public var deltaZ1:Number; + public var deltaX2:Number; + public var deltaY2:Number; + public var deltaZ2:Number; +} diff --git a/src/alternativa/engine3d/loaders/ParserA3D.as b/src/alternativa/engine3d/loaders/ParserA3D.as new file mode 100644 index 0000000..a1b94da --- /dev/null +++ b/src/alternativa/engine3d/loaders/ParserA3D.as @@ -0,0 +1,188 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.osgi.OSGi; + import alternativa.osgi.service.clientlog.IClientLog; + import alternativa.protocol.ICodec; + import alternativa.protocol.IProtocol; + import alternativa.protocol.OptionalMap; + import alternativa.protocol.ProtocolBuffer; + import alternativa.protocol.impl.OptionalMapCodecHelper; + import alternativa.protocol.impl.PacketHelper; + import alternativa.protocol.impl.Protocol; + import alternativa.protocol.info.TypeCodecInfo; + import alternativa.protocol.osgi.ProtocolActivator; + + import flash.utils.ByteArray; + + import platform.client.formats.a3d.osgi.Activator; + import platform.clients.fp10.libraries.alternativaprotocol.Activator; + + import versions.version1.a3d.A3D; + import versions.version2.a3d.A3D2; + import versions.version2.a3d.A3D2Extra1; + import versions.version2.a3d.A3D2Extra2; + + use namespace alternativa3d; + +/** + * A parser for loading models of A3D binary format. + * A3D format reference you can find here. + */ +public class ParserA3D extends Parser { + +// static public const logChannel:String = "ParserLog"; + + private var protocol:Protocol; + + private var wasInit:Boolean = false; + + /** + * Creates a new instance of ParserA3D. + * + */ + public function ParserA3D() { + init(); + } + + /** + * Parses model of a3d format, that is passed as byteArray to input parameter, then fills the arrays objects and hierarchy by the instances of three-dimensional objects. + * @param input ByteArray consists of A3D data. + */ + public function parse(input:ByteArray):void { + try { + input.position = 0; + var version:int = input.readByte(); + if (version == 0) { + // For the 1st version of format + parseVersion1(input); + } else { + // For the 2nd version of format and above, the first byte contains length of file and flag bits. + // Bit of packing. It always equal to 1, because version 2 and above is always packed. + parseVersionOver1(input); + } + } catch (e:Error) { + e.message = "Parsing failed: " + e.message; + throw e; + } + + } + + private function init():void { + if (wasInit) return; + var osgi:OSGi; + if (OSGi.getInstance() == null) { + osgi = new OSGi(); + OSGi.clientLog = new DummyClientLog(); + osgi.registerService(IClientLog, new DummyClientLog()); + new ProtocolActivator().start(osgi); + new platform.clients.fp10.libraries.alternativaprotocol.Activator().start(osgi); + } else { + osgi = OSGi.getInstance(); + } + new platform.client.formats.a3d.osgi.Activator().start(osgi); + protocol = Protocol(osgi.getService(IProtocol)); + wasInit = true; + } + + private function parseVersion1(input:ByteArray):void { + input.position = 4; + var nullMap:OptionalMap = OptionalMapCodecHelper.decodeNullMap(input); + nullMap.setReadPosition(0); + var data:ByteArray = new ByteArray(); + data.writeBytes(input, input.position); + data.position = 0; + var buffer:ProtocolBuffer = new ProtocolBuffer(data, data, nullMap); + var codec:ICodec = protocol.getCodec(new TypeCodecInfo(A3D, false)); + var _a3d:A3D = A3D(codec.decode(buffer)); + complete(_a3d); + } + + private function parseVersionOver1(input:ByteArray):void { + input.position = 0; + var data:ByteArray = new ByteArray(); + var buffer:ProtocolBuffer = new ProtocolBuffer(data, data, new OptionalMap()); + PacketHelper.unwrapPacket(input, buffer); + input.position = 0; + var versionMajor:int = buffer.reader.readUnsignedShort(); + var versionMinor:int = buffer.reader.readUnsignedShort(); + switch (versionMajor) { + case 2: + if (versionMinor >= 6) { + compressedBuffers = true; + } + var parts:Vector. = new Vector.(); + parts.push(parseVersion2_0(buffer)); + if (versionMinor >= 4) { + parts.push(parseVersion2_4(buffer)); + } + if (versionMinor >= 5) { + parts.push(parseVersion2_5(buffer)); + } + complete(parts); + break; + } + } + + private function parseVersion2_0(buffer:ProtocolBuffer):Object { + var codec:ICodec = protocol.getCodec(new TypeCodecInfo(A3D2, false)); + var a3d:A3D2 = A3D2(codec.decode(buffer)); + return a3d; + } + + private function parseVersion2_5(buffer:ProtocolBuffer):Object { + var codec:ICodec = protocol.getCodec(new TypeCodecInfo(A3D2Extra2, false)); + var a3d:A3D2Extra2 = A3D2Extra2(codec.decode(buffer)); + return a3d; + } + + private function parseVersion2_4(buffer:ProtocolBuffer):Object { + var codec:ICodec = protocol.getCodec(new TypeCodecInfo(A3D2Extra1, false)); + var a3d:A3D2Extra1 = A3D2Extra1(codec.decode(buffer)); + return a3d; + } + +} +} + +import alternativa.osgi.service.clientlog.IClientLog; +import alternativa.osgi.service.clientlog.IClientLogChannelListener; + +class DummyClientLog implements IClientLog { + + public function logError(channelName:String, text:String, ...vars):void { + } + + public function log(channelName:String, text:String, ...rest):void { + } + + public function getChannelStrings(channelName:String):Vector. { + return null; + } + + public function addLogListener(listener:IClientLogChannelListener):void { + } + + public function removeLogListener(listener:IClientLogChannelListener):void { + } + + public function addLogChannelListener(channelName:String, listener:IClientLogChannelListener):void { + } + + public function removeLogChannelListener(channelName:String, listener:IClientLogChannelListener):void { + } + + public function getChannelNames():Vector. { + return null; + } +} diff --git a/src/alternativa/engine3d/loaders/ParserCollada.as b/src/alternativa/engine3d/loaders/ParserCollada.as new file mode 100644 index 0000000..39a07be --- /dev/null +++ b/src/alternativa/engine3d/loaders/ParserCollada.as @@ -0,0 +1,391 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.animation.keys.Track; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + 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.resources.ExternalTextureResource; + + use namespace alternativa3d; + + /** + * A parser performs parsing of collada xml. + */ + public class ParserCollada extends Parser { + + /** + * List of the light sources + */ + public var lights:Vector.; + + /** + * Creates a new ParserCollada instance. + */ + public function ParserCollada() { + } + + /** + * Clears all links to external objects. + */ + override public function clean():void { + super.clean(); + lights = null; + } + + /** + * @private + * Initialization before the parsing + */ + override alternativa3d function init():void { + super.init(); + // cameras = new Vector.(); + lights = new Vector.(); + } + + /** + * Method parses xml of collada and fills arrays objects, hierarchy, materials, animations + * If you need to download textures, use class TexturesLoader. + *

Path to collada file should match with URI specification. E.g., file:///C:/test.dae or /C:/test.dae for the full paths and test.dae, ./test.dae in case of relative.

+ * + * @param data XML data type of collada. + * @param baseURL Path to textures relative to swf-file (Or file name only in case of trimPaths=true). + * @param trimPaths Use file names only, without paths. + * + * @see alternativa.engine3d.loaders.TexturesLoader + * @see #objects + * @see #hierarchy + * @see #materials + */ + public function parse(data:XML, baseURL:String = null, trimPaths:Boolean = false):void { + init(); + + var document:DaeDocument = new DaeDocument(data, 0); + if (document.scene != null) { + parseNodes(document.scene.nodes, null, false); + parseMaterials(document.materials, baseURL, trimPaths); + } + } + + /** + * Adds components of animated object to lists objects, parents, hierarchy, cameras, animations and to parent container. + */ + private function addObject(animatedObject:DaeObject, parent:Object3D, layer:String):Object3D { + var object:Object3D = Object3D(animatedObject.object); + this.objects.push(object); + if (parent == null) { + this.hierarchy.push(object); + } else { + parent.addChild(object); + } +// if (object is Camera3D) { +// this.cameras.push(object as Camera3D); +// } + if (object is Light3D) { + lights.push(Light3D(object)); + } + if (animatedObject.animation != null) { + this.animations.push(animatedObject.animation); + } + if (layer) { + layersMap[object] = layer; + } + return object; + } + + /** + * Adds objects to objects, parents, hierarchy, cameras and animations lists and to parent container. + * + * @return first object + */ + private function addObjects(animatedObjects:Vector., 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; + } + + /** + * Check if is there a skin among child objects. + */ + private function hasSkinsInChildren(node:DaeNode):Boolean { + var nodes:Vector. = 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; + } + if (hasSkinsInChildren(child)) { + return true; + } + } + return false; + } + + private function parseNodes(nodes:Vector., parent:Object3D, skinsOnly:Boolean = false):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + node.parse(); + + // Object to which child objects will be added. + var container:Object3D = null; + if (node.skins != null) { + // Main joint of skin + container = addObjects(node.skins, parent, node.layer); + } else { + if (!skinsOnly && !node.skinOrTopmostJoint) { + if (node.objects != null) { + container = addObjects(node.objects, parent, node.layer); + } else { + // Empty Object3D + container = new Object3D(); + container.name = node.cloneString(node.name); + addObject(node.applyAnimation(node.applyTransformations(container)), parent, node.layer); + container.calculateBoundBox(); + } + } else { + // Object or its parent is a skin or joint + // Create an object only if there are a child skins + if (hasSkinsInChildren(node)) { + container = new Object3D(); + container.name = node.cloneString(node.name); + addObject(node.applyAnimation(node.applyTransformations(container)), parent, node.layer); + parseNodes(node.nodes, container, skinsOnly || node.skinOrTopmostJoint); + container.calculateBoundBox(); + } + } + } + // Parse children + if (container != null) { + parseNodes(node.nodes, container, skinsOnly || node.skinOrTopmostJoint); + } + } + } + + private function trimPath(path:String):String { + var index:int = path.lastIndexOf("/"); + return (index < 0) ? path : path.substr(index + 1); + } + + private function parseMaterials(materials:Object, baseURL:String, trimPaths:Boolean):void { + var tmaterial:ParserMaterial; + for each (var material:DaeMaterial in materials) { + if (material.used) { + material.parse(); + this.materials.push(material.material); + } + } + var resource:ExternalTextureResource; + // Prepare paths + if (trimPaths) { + for each (tmaterial in this.materials) { + for each(resource in tmaterial.textures) { + if (resource != null && resource.url != null) { + resource.url = trimPath(fixURL(resource.url)); + } + } + } + } else { + for each (tmaterial in this.materials) { + for each(resource in tmaterial.textures) { + if (resource != null && resource.url != null) { + resource.url = fixURL(resource.url); + } + } + } + } + var base:String; + if (baseURL != null) { + baseURL = fixURL(baseURL); + var end:int = baseURL.lastIndexOf("/"); + base = (end < 0) ? "" : baseURL.substr(0, end); + for each (tmaterial in this.materials) { + for each(resource in tmaterial.textures) { + if (resource != null && resource.url != null) { + resource.url = resolveURL(resource.url, base); + } + } + } + } + } + + /** + * @private + * Prosesses URL with following actions. + * Replaces backslashes with slashes, adds three direct slashes after 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) : ""); + } + + /** + * @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 + * Converts relative paths to full paths + */ + private function resolveURL(url:String, base:String):String { + if (base == "") { + return url; + } + // http://labs.apache.org/webarch/uri/rfc/rfc3986.html + if (url.charAt(0) == "." && url.charAt(1) == "/") { + // File at the same folder + return base + url.substr(1); + } else if (url.charAt(0) == "/") { + // Full path + return url; + } else if (url.charAt(0) == "." && url.charAt(1) == ".") { + // Above on level + 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); + } + // Split base URL on parts + 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) { + // Scheme exists, no domain + 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 { + // No Scheme, no domain + 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)) { + // Contains the schema + return url; + } + return base + "/" + url; + } + + /** + * Returns animation from animations array by object, to which it refers. + */ + public function getAnimationByObject(object:Object):AnimationClip { + for each (var animation:AnimationClip in animations) { + var objects:Array = animation._objects; + if (objects.indexOf(object) >= 0) { + return animation; + } + } + return null; + } + + /** + * Parses and returns animation. + */ + public static function parseAnimation(data:XML):AnimationClip { + + var document:DaeDocument = new DaeDocument(data, 0); + var clip:AnimationClip = new AnimationClip(); + collectAnimation(clip, document.scene.nodes); + return (clip.numTracks > 0) ? clip : null; + } + + /** + * @private + */ + private static function collectAnimation(clip:AnimationClip, nodes:Vector.):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + var animation:AnimationClip = node.parseAnimation(); + if (animation != null) { + for (var t:int = 0, numTracks:int = animation.numTracks; t < numTracks; t++) { + var track:Track = animation.getTrackAt(t); + clip.addTrack(track); + } + } else { + clip.addTrack(node.createStaticTransformTrack()); + } + collectAnimation(clip, node.nodes); + } + } + } +} diff --git a/src/alternativa/engine3d/loaders/ParserMaterial.as b/src/alternativa/engine3d/loaders/ParserMaterial.as new file mode 100644 index 0000000..71e2c92 --- /dev/null +++ b/src/alternativa/engine3d/loaders/ParserMaterial.as @@ -0,0 +1,107 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.materials.*; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import avmplus.getQualifiedClassName; + + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + use namespace alternativa3d; + + /** + * A material which is assigned to each object that we got as a parsing result. This material should be treated as a debugging rather than a production one. + * It keeps links to all possible in Alternativa3D maps (such as light map or normal map) but can render only one of them as a diffuse like + * TextureMaterial. To make object that you get after parsing using all these maps you should create a new StandardMaterial + * and pass to them all textures. Then you can assign this material to the object. + * Since ParserMaterial sores only links to textures, you should worry about loading it. You can use TexturesLoader for. + * Can draws a Skin with no more than 41 Joints per surface. See Skin.divide() for more details. + * + * @see alternativa.engine3d.loaders.TexturesLoader + * @see alternativa.engine3d.materials + * @see alternativa.engine3d.objects.Skin#divide() + */ + public class ParserMaterial extends Material { + /** + * List of colors, that can be assigned to each channel instead of texture. Variants: ambient, emission, diffuse, specular, shininess, reflective, transparent, bump. + */ + public var colors:Object; + /** + * List of ExternalTextureResource, that you can load with a TexturesLoader. Keys of objects are names of channels. Variants: ambient, emission, diffuse, specular, shininess, reflective, transparent, bump. + * + * @see alternativa.engine3d.loaders.TexturesLoader + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public var textures:Object; + /** + * Transparency of material + */ + public var transparency:Number = 0; + /** + * Channel, that will be rendered. Possible options: ambient, emission, diffuse, specular, shininess, reflective, transparent, bump. + */ + public var renderChannel:String = "diffuse"; + + private var textureMaterial:TextureMaterial; + private var fillMaterial:FillMaterial; + + public function ParserMaterial() { + textures = new Object(); + colors = new Object(); + } + + /** + * @private + */ + override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void { + super.fillResources(resources, resourceType); + for each(var texture:TextureResource in textures) { + if (texture != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(texture)) as Class, resourceType)) { + resources[texture] = true; + } + + } + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + var colorO:Object = colors[renderChannel]; + var map:ExternalTextureResource; + if (colorO != null) { + if(fillMaterial == null) { + fillMaterial = new FillMaterial(int(colorO)); + } else { + fillMaterial.color = int(colorO); + } + fillMaterial.collectDraws(camera, surface, geometry, lights, lightsLength, objectRenderPriority); + } else if ((map = textures[renderChannel]) != null) { + if(textureMaterial == null) { + textureMaterial = new TextureMaterial(map); + } else { + textureMaterial.diffuseMap = map; + } + textureMaterial.collectDraws(camera, surface, geometry, lights, lightsLength, objectRenderPriority); + } + } + + } +} diff --git a/src/alternativa/engine3d/loaders/ResourceLoader.as b/src/alternativa/engine3d/loaders/ResourceLoader.as new file mode 100644 index 0000000..5168395 --- /dev/null +++ b/src/alternativa/engine3d/loaders/ResourceLoader.as @@ -0,0 +1,186 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.resources.BitmapTextureResource; + import alternativa.engine3d.resources.ExternalTextureResource; + + import flash.display.BitmapData; + import flash.display.Loader; + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.CubeTexture; + import flash.display3D.textures.Texture; + import flash.display3D.textures.TextureBase; + 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.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * @private + */ + public class ResourceLoader extends EventDispatcher { + + private var loadSequence:Vector. = new Vector.(); + private var context:Context3D; + private var currentIndex:int = 0; + public var generateMips:Boolean = true; + + private static const atfRegExp:RegExp = new RegExp(/\.atf/i); + + public function ResourceLoader(generateMips:Boolean = true) { + this.generateMips = generateMips; + } + + public function addResource(resource:ExternalTextureResource):void { + loadSequence.push(resource); + } + + public function addResources(resources:Vector.):void { + for each (var resource:ExternalTextureResource in resources) { + loadSequence.push(resource); + } + } + + public function load(context:Context3D):void { + this.context = context; + currentIndex = 0; + loadNext(); + } + + private function loadNext():void { + + if (currentIndex < loadSequence.length) { + var currentResource:ExternalTextureResource = loadSequence[currentIndex]; + if (currentResource.url.match(atfRegExp)) { + var atfLoader:URLLoader = new URLLoader(); + atfLoader.dataFormat = URLLoaderDataFormat.BINARY; + atfLoader.addEventListener(Event.COMPLETE, onATFComplete); + atfLoader.addEventListener(IOErrorEvent.IO_ERROR, onFailed); + atfLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFailed); + atfLoader.load(new URLRequest(currentResource.url)); + } else { + var bitmapLoader:Loader = new Loader(); + bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoaded); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.DISK_ERROR, onFailed); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onFailed); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, onFailed); + bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, onFailed); + bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFailed); + bitmapLoader.load(new URLRequest(currentResource.url)); + } + } else { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + private function getNearPowerOf2For(size:Number):Number { + if (size && (size - 1)) { + for (var i:int = 11; i > 0; i--) { + if (size >= (1 << i)) { + return 1 << i; + } + } + return 0; + } else { + return size; + } + } + + private function fitTextureToSizeLimits(textureData:BitmapData):BitmapData { + var fittedTextureData:BitmapData = textureData; + var width:Number, height:Number; + width = getNearPowerOf2For(fittedTextureData.width); + height = getNearPowerOf2For(fittedTextureData.height); + if (width != fittedTextureData.width || height != fittedTextureData.height) { + var newBitmap:BitmapData = new BitmapData(width, height, + fittedTextureData.transparent); + var matrix:Matrix = new Matrix(width/fittedTextureData.width, 0, 0, + height/fittedTextureData.height); + newBitmap.draw(fittedTextureData, matrix); + fittedTextureData = newBitmap; + } + return fittedTextureData; + } + + private function onBitmapLoaded(e:Event):void { + var resized:BitmapData = fitTextureToSizeLimits(e.target.content.bitmapData); + var texture:Texture = context.createTexture(resized.width, resized.height, Context3DTextureFormat.BGRA, false); + texture.uploadFromBitmapData(resized, 0); + if (generateMips) { + BitmapTextureResource.createMips(texture, resized); + } + + var currentResource:ExternalTextureResource = loadSequence[currentIndex]; + currentResource.data = texture; + dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS)); + currentIndex++; + loadNext(); + } + + private function onFailed(e:Event):void { + trace("Failed to load texture :", loadSequence[currentIndex].url); + //dispatchEvent(e); + currentIndex++; + loadNext(); + } + + private function onATFComplete(e:Event):void { + var value:ByteArray = e.target.data; + value.endian = Endian.LITTLE_ENDIAN; + value.position = 6; + var texture:TextureBase + var type:uint = value.readByte(); + var format:String; + switch (type & 0x7F) { + case 0: + format = Context3DTextureFormat.BGRA; + break; + case 1: + format = Context3DTextureFormat.BGRA; + break; + case 2: + format = Context3DTextureFormat.COMPRESSED; + break; + } + + if ((type & ~0x7F) == 0) { + texture = context.createTexture(1 << value.readByte(), 1 << value.readByte(), format, false); + texture.addEventListener("textureReady", onTextureUploaded); + Texture(texture).uploadCompressedTextureFromByteArray(value, 0, true); + } else { + texture = context.createCubeTexture(1 << value.readByte(), format, false); + texture.addEventListener("textureReady", onTextureUploaded); + CubeTexture(texture).uploadCompressedTextureFromByteArray(value, 0, true) + } + } + + private function onTextureUploaded(e:Event):void { + var currentResource:ExternalTextureResource = loadSequence[currentIndex]; + currentResource.data = TextureBase(e.target); + dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS)); + currentIndex++; + loadNext(); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/TexturesLoader.as b/src/alternativa/engine3d/loaders/TexturesLoader.as new file mode 100644 index 0000000..7b4b642 --- /dev/null +++ b/src/alternativa/engine3d/loaders/TexturesLoader.as @@ -0,0 +1,328 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.loaders.events.TexturesLoaderEvent; + import alternativa.engine3d.resources.BitmapTextureResource; + import alternativa.engine3d.resources.ExternalTextureResource; + + import flash.display.BitmapData; + import flash.display.Loader; + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.CubeTexture; + import flash.display3D.textures.Texture; + import flash.display3D.textures.TextureBase; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * Dispatches after complete loading of all textures. + * @eventType flash.events.TexturesLoaderEvent.COMPLETE + */ + [Event(name="complete",type="alternativa.engine3d.loaders.events.TexturesLoaderEvent")] + + /** + * An object that downloads textures by their reference and upload them into the Context3D + */ + public class TexturesLoader extends EventDispatcher { + + /** + * A Context3D to which resources wil be loaded. + */ + public var context:Context3D; + + private var textures:Object = new Object(); + private var bitmapDatas:Object = new Object(); + private var byteArrays:Object = new Object(); + + private var currentBitmapDatas:Vector.; + private var currentUrl:String; + + private var resources:Vector.; + private var counter:int = 0; + private var createTexture3D:Boolean; + private var needBitmapData:Boolean; + + private var loaderCompressed:URLLoader; + private var isATF:Boolean; + private var atfRegExp:RegExp = new RegExp(/\.atf/i); + + /** + * Creates a new TexturesLoader instance. + * @param context – A Context3D to which resources wil be loaded. + */ + public function TexturesLoader(context:Context3D) { + this.context = context; + } + + /** + * @private + */ + public function getTexture(url:String):TextureBase { + return textures[url]; + } + + private function loadCompressed(url:String):void { + loaderCompressed = new URLLoader(); + loaderCompressed.dataFormat = URLLoaderDataFormat.BINARY; + loaderCompressed.addEventListener(Event.COMPLETE, loadNext); + loaderCompressed.addEventListener(IOErrorEvent.IO_ERROR, loadNext); + loaderCompressed.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext); + loaderCompressed.load(new URLRequest(url)); + } + + /** + * Loads a resource. + * @param resource + * @param createTexture3D Create texture on uploading + * @param needBitmapData If true, keeps BitmapData after uploading textures into a context. + */ + public function loadResource(resource:ExternalTextureResource, createTexture3D:Boolean = true, needBitmapData:Boolean = true):void { + if (resources != null) { + throw new Error("Cannot start new load while loading"); + } + this.createTexture3D = createTexture3D; + this.needBitmapData = needBitmapData; + resources = Vector.([resource]); + currentBitmapDatas = new Vector.(1); + //currentTextures3D = new Vector.(1); + loadNext(); + } + + /** + * Loads list of textures + * @param resources List of ExternalTextureResource each of them has link to texture file which needs to be downloaded. + * @param createTexture3D Create texture on uploading. + * @param needBitmapData If true, keeps BitmapData after uploading textures into a context. + */ + public function loadResources(resources:Vector., createTexture3D:Boolean = true, needBitmapData:Boolean = true):void { + if (this.resources != null) { + throw new Error("Cannot start new load while loading"); + } + this.createTexture3D = createTexture3D; + this.needBitmapData = needBitmapData; + this.resources = resources; + currentBitmapDatas = new Vector.(resources.length); + loadNext(); + } + + /** + * Clears links to all data stored in this TexturesLoader instance. (List of downloaded textures) + */ + public function clean():void { + if (resources != null) { + throw new Error("Cannot clean while loading"); + } + textures = new Object(); + bitmapDatas = new Object(); + } + + /** + * Clears links to all data stored in this TexturesLoader instance and removes it from the context. + */ + public function cleanAndDispose():void { + if (resources != null) { + throw new Error("Cannot clean while loading"); + } + textures = new Object(); + for each (var b:BitmapData in bitmapDatas) { + b.dispose(); + } + bitmapDatas = new Object(); + } + + /** + * Removes texture resources from Context3D. + * @param urls List of links to resources, that should be removed. + */ + public function dispose(urls:Vector.):void { + for (var i:int = 0; i < urls.length; i++) { + var url:String = urls[i]; + var bmd:BitmapData = bitmapDatas[url] as BitmapData; + //if (bmd) { + delete bitmapDatas[url]; + bmd.dispose(); + //} + } + } + + private function loadNext(e:Event = null):void { +// trace("[NEXT]", e); + var bitmapData:BitmapData; + var byteArray:ByteArray; + var texture3D:TextureBase; + + if (e != null && !(e is ErrorEvent)) { + if (isATF) { + byteArray = e.target.data; + byteArrays[currentUrl] = byteArray; + try { + texture3D = addCompressedTexture(byteArray); + resources[counter - 1].data = texture3D; + } catch (err:Error) { + // throw new Error("loadNext:: " + err.message + " " + currentUrl); + trace("loadNext:: " + err.message + " " + currentUrl); + } + } else { + bitmapData = e.target.content.bitmapData; + bitmapDatas[currentUrl] = bitmapData; + currentBitmapDatas[counter - 1] = bitmapData; + if (createTexture3D) { + try { + texture3D = addTexture(bitmapData); + resources[counter - 1].data = texture3D; + } catch (err:Error) { + throw new Error("loadNext:: " + err.message + " " + currentUrl); + } + } + if (!needBitmapData) { + bitmapData.dispose(); + } + } + resources[counter - 1].data = texture3D; + } else if (e is ErrorEvent) { + trace("Missing: " + currentUrl); + } + + if (counter < resources.length) { + currentUrl = resources[counter++].url; + if (currentUrl != null) { + atfRegExp.lastIndex = currentUrl.lastIndexOf("."); + isATF = currentUrl.match(atfRegExp) != null; + } + + if (isATF) { + if (createTexture3D) { + texture3D = textures[currentUrl]; + if (texture3D == null) { + byteArray = byteArrays[currentUrl]; + if (byteArray) { + texture3D = addCompressedTexture(byteArray); + resources[counter - 1].data = texture3D; + //bitmapDatas[currentUrl] = bitmapData; + loadNext(); + } else { + loadCompressed(currentUrl); + } + } else { + resources[counter - 1].data = texture3D; + loadNext(); + } + } + } else { + if (needBitmapData) { + bitmapData = bitmapDatas[currentUrl]; + if (bitmapData) { + currentBitmapDatas[counter - 1] = bitmapData; + if (createTexture3D) { + texture3D = textures[currentUrl]; + if (texture3D == null) { + texture3D = addTexture(bitmapData); + } + resources[counter - 1].data = texture3D; + } + loadNext(); + } else { + load(currentUrl); + } + } else if (createTexture3D) { + texture3D = textures[currentUrl]; + if (texture3D == null) { + bitmapData = bitmapDatas[currentUrl]; + if (bitmapData) { + texture3D = addTexture(bitmapData); + resources[counter - 1].data = texture3D; + loadNext(); + } else { + load(currentUrl); + } + } else { + resources[counter - 1].data = texture3D; + loadNext(); + } + } + } + } else { + onTexturesLoad(); + } + } + + private function load(url:String):void { + 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(url)); + } + + private function onTexturesLoad():void { +// trace("[LOADED]"); + counter = 0; + var bmds:Vector. = currentBitmapDatas; + var reses:Vector. = resources; + currentBitmapDatas = null; + resources = null; + dispatchEvent(new TexturesLoaderEvent(TexturesLoaderEvent.COMPLETE, bmds, reses)); + } + + private function addTexture(value:BitmapData):Texture { + var texture:Texture = context.createTexture(value.width, value.height, Context3DTextureFormat.BGRA, false); + texture.uploadFromBitmapData(value, 0); + BitmapTextureResource.createMips(texture, value); + textures[currentUrl] = texture; + return texture; + } + + private function addCompressedTexture(value:ByteArray):TextureBase { + value.endian = Endian.LITTLE_ENDIAN; + value.position = 6; + var texture:TextureBase + var type:uint = value.readByte(); + var format:String; + switch (type & 0x7F) { + case 0: + format = Context3DTextureFormat.BGRA; + break; + case 1: + format = Context3DTextureFormat.BGRA; + break; + case 2: + format = Context3DTextureFormat.COMPRESSED; + break; + } + if ((type & ~0x7F) == 0) { + texture = context.createTexture(1 << value.readByte(), 1 << value.readByte(), format, false); + Texture(texture).uploadCompressedTextureFromByteArray(value, 0); + } else { + texture = context.createCubeTexture(1 << value.readByte(), format, false); + CubeTexture(texture).uploadCompressedTextureFromByteArray(value, 0) + } + textures[currentUrl] = texture; + return texture; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeArray.as b/src/alternativa/engine3d/loaders/collada/DaeArray.as new file mode 100644 index 0000000..d67c11c --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeArray.as @@ -0,0 +1,51 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeArray extends DaeElement { + + use namespace collada; + + /** + * Array of String values. + * Call parse() before using. + */ + 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeChannel.as b/src/alternativa/engine3d/loaders/collada/DaeChannel.as new file mode 100644 index 0000000..f99121f --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeChannel.as @@ -0,0 +1,213 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.keys.NumberKey; + import alternativa.engine3d.animation.keys.NumberTrack; + import alternativa.engine3d.animation.keys.Track; + + use namespace alternativa3d; + /** + * @private + */ + public class DaeChannel extends DaeElement { + + static public const PARAM_UNDEFINED:String = "undefined"; + static public const PARAM_TRANSLATE_X:String = "x"; + static public const PARAM_TRANSLATE_Y:String = "y"; + static public const PARAM_TRANSLATE_Z:String = "z"; + static public const PARAM_SCALE_X:String = "scaleX"; + static public const PARAM_SCALE_Y:String = "scaleY"; + static public const PARAM_SCALE_Z:String = "scaleZ"; + static public const PARAM_ROTATION_X:String = "rotationX"; + static public const PARAM_ROTATION_Y:String = "rotationY"; + static public const PARAM_ROTATION_Z:String = "rotationZ"; + static public const PARAM_TRANSLATE:String = "translate"; + static public const PARAM_SCALE:String = "scale"; + static public const PARAM_MATRIX:String = "matrix"; + + /** + * Animation track with keys. + * Call parse() before using. + */ + public var tracks:Vector.; + + /** + * Type of animated parameter. It can be one of DaeChannel.PARAM_*. values. + * * Call parse() before using. + */ + public var animatedParam:String = PARAM_UNDEFINED; + /** + * Key of animated object. + */ + public var animName:String; + + public function DaeChannel(data:XML, document:DaeDocument) { + super(data, document); + } + + /** + * Returns a node for which the animation is destined. + */ + public function get node():DaeNode { + var targetXML:XML = data.@target[0]; + if (targetXML != null) { + var targetParts:Array = targetXML.toString().split("/"); + // First part of item id + var node:DaeNode = document.findNodeByID(targetParts[0]); + if (node != null) { + // Last part is transformed item + 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; + + // Split the path on parts + var targetParts:Array = targetXML.toString().split("/"); + var sid:String = targetParts.pop(); + var sidParts:Array = sid.split("."); + var sidPartsCount:int = sidParts.length; + + //Define the type of property + var transformationXML:XML; + var node:DaeNode = this.node; + if (node == null) { + return; + } + animName = node.animName; + 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:: case with brackets (just in case) + 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:: look for the maximum value + 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) { + tracks = Vector.([sampler.parseTransformationTrack(animName)]); + return; + } + if (animatedParam == PARAM_TRANSLATE) { + tracks = sampler.parsePointsTracks(animName, "x", "y", "z"); + return; + } + if (animatedParam == PARAM_SCALE) { + tracks = sampler.parsePointsTracks(animName, "scaleX", "scaleY", "scaleZ"); + return; + } + if (animatedParam == PARAM_ROTATION_X || animatedParam == PARAM_ROTATION_Y || animatedParam == PARAM_ROTATION_Z) { + var track:NumberTrack = sampler.parseNumbersTrack(animName, animatedParam); + // Convert degrees to radians + var toRad:Number = Math.PI/180; + for (var key:NumberKey = track.keyList; key != null; key = key.next) { + key._value *= toRad; + } + tracks = Vector.([track]); + return; + } + if (animatedParam == PARAM_TRANSLATE_X || animatedParam == PARAM_TRANSLATE_Y || animatedParam == PARAM_TRANSLATE_Z || animatedParam == PARAM_SCALE_X || animatedParam == PARAM_SCALE_Y || animatedParam == PARAM_SCALE_Z) { + tracks = Vector.([sampler.parseNumbersTrack(animName, animatedParam)]); + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeController.as b/src/alternativa/engine3d/loaders/collada/DaeController.as new file mode 100644 index 0000000..fb8628c --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeController.as @@ -0,0 +1,542 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.*; + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.loaders.ParserMaterial; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + + use namespace collada; + use namespace alternativa3d; + + /** + * @private + */ + public class DaeController extends DaeElement { + + private var jointsBindMatrices:Vector. >; + private var vcounts:Array; + private var indices:Array; + private var jointsInput:DaeInput; + private var weightsInput:DaeInput; + private var inputsStride:int; + private var geometry:Geometry; + private var primitives:Vector.; + private var maxJointsPerVertex:int = 0; + private var bindShapeMatrix:Vector.; + + public function DaeController(data:XML, document:DaeDocument) { + super(data, document); + + // sources creates inside the DaeDocument. It should not be here. + } + + private function get daeGeometry():DaeGeometry { + var geom:DaeGeometry = document.findGeometry(data.skin.@source[0]); + if (geom == null) { + document.logger.logNotFoundError(data.@source[0]); + } + return geom; + } + + override protected function parseImplementation():Boolean { + var vertexWeightsXML:XML = this.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(); + var i:int, j:int; + for (i = 0; i < vcounts.length; i++) { + var count:int = vcounts[i]; + if (maxJointsPerVertex < count) maxJointsPerVertex = count; + } + + var geom:DaeGeometry = this.daeGeometry; + bindShapeMatrix = getBindShapeMatrix(); + if (geom != null) { + geom.parse(); + var vertices:Vector. = geom.geometryVertices; + var source:Geometry = geom.geometry; + var localMaxJointsPerVertex:int = (maxJointsPerVertex%2 != 0) ? maxJointsPerVertex + 1 : maxJointsPerVertex; + + // Create geometry + this.geometry = new Geometry(); + this.geometry._indices = source._indices.slice(); + var attributes:Array = source.getVertexStreamAttributes(0); + var numSourceAttributes:int = attributes.length; + + var index:int = numSourceAttributes; + for (i = 0; i < localMaxJointsPerVertex; i += 2) { + var attribute:int = VertexAttributes.JOINTS[int(i/2)]; + attributes[int(index++)] = attribute; + attributes[int(index++)] = attribute; + attributes[int(index++)] = attribute; + attributes[int(index++)] = attribute; + } + + var numMappings:int = attributes.length; + + var sourceData:ByteArray = source._vertexStreams[0].data; + var data:ByteArray = new ByteArray(); + data.endian = Endian.LITTLE_ENDIAN; + data.length = 4*numMappings*source._numVertices; + // Copy source data + sourceData.position = 0; + for (i = 0; i < source._numVertices; i++) { + data.position = 4*numMappings*i; + for (j = 0; j < numSourceAttributes; j++) { + data.writeFloat(sourceData.readFloat()); + } + } + + // Copy weights and joints + var byteArray:ByteArray = createVertexBuffer(vertices, localMaxJointsPerVertex); + byteArray.position = 0; + for (i = 0; i < source._numVertices; i++) { + data.position = 4*(numMappings*i + numSourceAttributes); + for (j = 0; j < localMaxJointsPerVertex; j++) { + data.writeFloat(byteArray.readFloat()); + data.writeFloat(byteArray.readFloat()); + } + } + + this.geometry.addVertexStream(attributes); + this.geometry._vertexStreams[0].data = data; + this.geometry._numVertices = source._numVertices; + transformVertices(this.geometry); + primitives = geom.primitives; + } + return true; + } + + private function transformVertices(geometry:Geometry):void { + var data:ByteArray = geometry._vertexStreams[0].data; + var numMappings:int = geometry._vertexStreams[0].attributes.length; + for (var i:int = 0; i < geometry._numVertices; i++) { + data.position = 4*numMappings*i; + var x:Number = data.readFloat(); + var y:Number = data.readFloat(); + var z:Number = data.readFloat(); + data.position -= 12; + data.writeFloat(x*bindShapeMatrix[0] + y*bindShapeMatrix[1] + z*bindShapeMatrix[2] + bindShapeMatrix[3]); + data.writeFloat(x*bindShapeMatrix[4] + y*bindShapeMatrix[5] + z*bindShapeMatrix[6] + bindShapeMatrix[7]); + data.writeFloat(x*bindShapeMatrix[8] + y*bindShapeMatrix[9] + z*bindShapeMatrix[10] + bindShapeMatrix[11]); + } + } + + private function createVertexBuffer(vertices:Vector., localMaxJointsPerVertex:int):ByteArray { + var jointsOffset:int = jointsInput.offset; + var weightsOffset:int = weightsInput.offset; + var weightsSource:DaeSource = weightsInput.prepareSource(1); + var weights:Vector. = weightsSource.numbers; + var weightsStride:int = weightsSource.stride; + var i:int, count:int; + var verticesDict:Dictionary = new Dictionary(); + var byteArray:ByteArray = new ByteArray(); + // Reserve required number of bytes + byteArray.length = vertices.length*localMaxJointsPerVertex*8; + byteArray.endian = Endian.LITTLE_ENDIAN; + + for (i = 0,count = vertices.length; i < count; i++) { + var vertex:DaeVertex = vertices[i]; + if (vertex == null) continue; + var vec:Vector. = verticesDict[vertex.vertexInIndex]; + if (vec == null) { + vec = verticesDict[vertex.vertexInIndex] = new Vector.(); + } + vec.push(vertex.vertexOutIndex); + } + + var vertexIndex:int = 0; + var vertexOutIndices:Vector.; + for (i = 0,count = vcounts.length; i < count; i++) { + var jointsPerVertex:int = vcounts[i]; + vertexOutIndices = verticesDict[i]; + for (var j:int = 0; j < vertexOutIndices.length; j++) { + byteArray.position = vertexOutIndices[j]*localMaxJointsPerVertex*8; + // Loop on joints + for (var k:int = 0; k < jointsPerVertex; k++) { + var index:int = inputsStride*(vertexIndex + k); + var jointIndex:int = indices[int(index + jointsOffset)]; + if (jointIndex >= 0) { + // Save joint index, multiplied with three + byteArray.writeFloat(jointIndex*3); + var weightIndex:int = indices[int(index + weightsOffset)]; + byteArray.writeFloat(weights[int(weightsStride*weightIndex)]); + } else { + byteArray.position += 8; + } + } + } + vertexIndex += jointsPerVertex; + } + byteArray.position = 0; + return byteArray; + } + + 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; + } + + /** + * Parses inverse matrices for joints and saves them to a vector. + */ + 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. >(count); + for (var i:int = 0; i < count; i++) { + var index:int = stride*i; + var matrix:Vector. = new Vector.(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]); + } + } + } + + /** + * Returns geometry with the joints and controller for the joints. + * Call parse() before using. + */ + public function parseSkin(materials:Object, topmostJoints:Vector., skeletons:Vector.):DaeObject { + var skinXML:XML = data.skin[0]; + if (skinXML != null) { + bindShapeMatrix = getBindShapeMatrix(); + var numJoints:int = jointsBindMatrices.length; + var skin:Skin = new Skin(maxJointsPerVertex); + skin.geometry = this.geometry; + var joints:Vector. = addJointsToSkin(skin, topmostJoints, findNodes(skeletons)); + setJointsBindMatrices(joints); + + skin.renderedJoints = collectRenderedJoints(joints, numJoints); + + if (primitives != null) { + for (var i:int = 0; i < primitives.length; i++) { + var p:DaePrimitive = primitives[i]; + var instanceMaterial:DaeInstanceMaterial = materials[p.materialSymbol]; + var material:ParserMaterial; + if (instanceMaterial != null) { + var daeMaterial:DaeMaterial = instanceMaterial.material; + if (daeMaterial != null) { + daeMaterial.parse(); + material = daeMaterial.material; + daeMaterial.used = true; + } + } + skin.addSurface(material, p.indexBegin, p.numTriangles); + } + } + skin.calculateBoundBox(); + return new DaeObject(skin, mergeJointsClips(skin, joints)); + } + return null; + } + + private function collectRenderedJoints(joints:Vector., numJoints:int):Vector. { + var result:Vector. = new Vector.(); + for (var i:int = 0; i < numJoints; i++) { + result[i] = Joint(joints[i].object); + } + return result; + } + + /** + * Unites animations of joints into the single animation, if required. + */ + private function mergeJointsClips(skin:Skin, joints:Vector.):AnimationClip { + if (!hasJointsAnimation(joints)) { + return null; + } + var result:AnimationClip = new AnimationClip(); + var resultObjects:Array = [skin]; + for (var i:int = 0, count:int = joints.length; i < count; i++) { + var animatedObject:DaeObject = joints[i]; + var clip:AnimationClip = animatedObject.animation; + if (clip != null) { + for (var t:int = 0; t < clip.numTracks; t++) { + result.addTrack(clip.getTrackAt(t)); + } + } else { + // Creates empty track for the joint. + result.addTrack(animatedObject.jointNode.createStaticTransformTrack()); + } + var object:Object3D = animatedObject.object; + object.name = animatedObject.jointNode.animName; + resultObjects.push(object); + } + result._objects = resultObjects; + return result; + } + + private function hasJointsAnimation(joints:Vector.):Boolean { + for (var i:int = 0, count:int = joints.length; i < count; i++) { + var object:DaeObject = joints[i]; + if (object.animation != null) { + return true; + } + } + return false; + } + + /** + * Set inverse matrices to joints. + */ + private function setJointsBindMatrices(animatedJoints:Vector.):void { + for (var i:int = 0, count:int = jointsBindMatrices.length; i < count; i++) { + var animatedJoint:DaeObject = animatedJoints[i]; + var bindMatrix:Vector. = jointsBindMatrices[i]; +// bindMatrix[3]; //*= document.unitScaleFactor; +// bindMatrix[7];// *= document.unitScaleFactor; +// bindMatrix[11];// *= document.unitScaleFactor; + Joint(animatedJoint.object).setBindPoseMatrix(bindMatrix); + } + } + + /** + * Creates a hierarchy of joints and adds them to skin. + * @return vector of joints with animation, which was added to skin. + * If you have added the auxiliary joint, then length of vector will differ from length of nodes vector. + */ + private function addJointsToSkin(skin:Skin, topmostJoints:Vector., nodes:Vector.):Vector. { + // Dictionary, in which key is a node and value is a position in nodes vector + 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. = new Vector.(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; + } + + /** + * Adds root joint to skin. + */ + private function addRootJointToSkin(skin:Skin, node:DaeNode, animatedJoints:Vector., nodes:Dictionary):DaeObject { + var joint:Joint = new Joint(); + joint.name = cloneString(node.name); + skin.addChild(joint); + var animatedJoint:DaeObject = node.applyAnimation(node.applyTransformations(joint)); + animatedJoint.jointNode = node; + if (node in nodes) { + animatedJoints[nodes[node]] = animatedJoint; + } else { + // Add at the end + animatedJoints.push(animatedJoint); + } + return animatedJoint; + } + + /** + * Creates a hierarchy of child joints and add them to parent joint. + * @param parent Parent joint. + * @param animatedJoints Vector of joints to which created joints will added. + * Auxiliary joints will be added to the end of the vector, if it's necessary. + * @param parentNode Node of parent joint + * @param nodes Dictionary. Key is a node of joint. And value is an index of joint in animatedJoints vector + * + */ + private function addJointChildren(parent:Joint, animatedJoints:Vector., parentNode:DaeNode, nodes:Dictionary):void { + var object:DaeObject; + var children:Vector. = 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 = cloneString(child.name); + object = child.applyAnimation(child.applyTransformations(joint)); + object.jointNode = child; + animatedJoints[nodes[child]] = object; + parent.addChild(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } else { + // If node is not a joint + if (hasJointInDescendants(child, nodes)) { + // If one of the children is a joint, there is need to create auxiliary joint instead of this node. + joint = new Joint(); + joint.name = cloneString(child.name); + object = child.applyAnimation(child.applyTransformations(joint)); + object.jointNode = child; + // Add new joint to the end + animatedJoints.push(object); + parent.addChild(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } + } + } + } + + private function hasJointInDescendants(parentNode:DaeNode, nodes:Dictionary):Boolean { + var children:Vector. = 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; + } + + /** + * Transforms all object vertices with the BindShapeMatrix from collada. + */ + private function getBindShapeMatrix():Vector. { + var matrixXML:XML = data.skin.bind_shape_matrix[0]; + var res:Vector. = new Vector.(16, true); + if (matrixXML != null) { + var matrix:Array = parseStringArray(matrixXML); + for (var i:int = 0; i < matrix.length; i++) { + res[i] = Number(matrix[i]); + } + } + return res; + } + + /** + * Returns true if joint hasn't parent joint. + * @param node Joint node + * @param nodes Dictionary. It items are the nodes keys. + * + */ + 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.):Vector. { + var nodes:Vector. = 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. = new Vector.(); + for (i = 0; i < count; i++) { + var node:DaeNode = nodes[i]; + if (isRootJointNode(node, nodesDictionary)) { + rootNodes.push(node); + } + } + return rootNodes; + } + return null; + } + + /** + * Find node by Sid on sceletons vector. + */ + private function findNode(nodeName:String, skeletons:Vector.):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; + } + + /** + * Returns the vector of joint nodes. + */ + private function findNodes(skeletons:Vector.):Vector. { + 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. = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = findNode(jointsSource.names[int(stride*i)], skeletons); + if (node == null) { + // Error: no node. + } + nodes[i] = node; + } + return nodes; + } + } else { + document.logger.logNotFoundError(jointsXML.@source[0]); + } + } + return null; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeDocument.as b/src/alternativa/engine3d/loaders/collada/DaeDocument.as new file mode 100644 index 0000000..b0d77c0 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeDocument.as @@ -0,0 +1,248 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + /** + * @private + */ + public class DaeDocument { + + use namespace collada; + + public var scene:DaeVisualScene; + + /** + * Collada file + */ + private var data:XML; + + // Dictionaries to store matchings id-> DaeElement + internal var sources:Object; + internal var arrays:Object; + internal var vertices:Object; + public var geometries:Object; + internal var nodes:Object; + internal var lights: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 var unitScaleFactor:Number = 1; + public function DaeDocument(document:XML, units:Number) { + this.data = document; + + var versionComponents:Array = data.@version[0].toString().split(/[.,]/); + versionMajor = parseInt(versionComponents[1], 10); + versionMinor = parseInt(versionComponents[2], 10); + + var colladaUnit:Number = parseFloat(data.asset[0].unit[0].@meter); + if (units > 0) { + unitScaleFactor = colladaUnit/units; + } else { + unitScaleFactor = 1; + } + + logger = new DaeLogger(); + + constructStructures(); + constructScenes(); + registerInstanceControllers(); + 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; + } + } + + // Search for the declarations of items and fill the dictionaries. + private function constructStructures():void { + var element:XML; + + sources = new Object(); + arrays = new Object(); + for each (element in data..source) { + // Collect all . Dictionary arrays is filled at constructors. + var source:DaeSource = new DaeSource(element, this); + if (source.id != null) { + sources[source.id] = source; + } + } + + lights = new Object(); + for each (element in data.library_lights.light) { + // Collect all . + var light:DaeLight = new DaeLight(element, this); + if (light.id != null) { + lights[light.id] = light; + } + } + images = new Object(); + for each (element in data.library_images.image) { + // Collect all . + 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) { + // Collect all . Dictionary images is filled at constructors. + 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) { + // Collect all . + 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) { + // Collect all . Dictionary vertices is filled at constructors. + 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) { + // Collect all . + 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) { + // Create only root nodes. Others are created recursively at constructors. + var node:DaeNode = new DaeNode(element, this); + if (node.id != null) { + nodes[node.id] = node; + } + } + } + + 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) { + // Create visual_scene. node's are created at constructors. + var vscene:DaeVisualScene = new DaeVisualScene(element, this); + if (vscene.id == vsceneID) { + this.scene = vscene; + } + } + if (vsceneID != null && scene == null) { + logger.logNotFoundError(vsceneURL); + } + } + + private function registerInstanceControllers():void { + if (scene != null) { + for (var i:int = 0, count:int = scene.nodes.length; i < count; i++) { + scene.nodes[i].registerInstanceControllers(); + } + } + } + + private function constructAnimations():void { + var element:XML; + samplers = new Object(); + for each (element in data.library_animations..sampler) { + // Collect all . + 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 findLight(url:XML):DaeLight { + return lights[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)]; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeEffect.as b/src/alternativa/engine3d/loaders/collada/DaeEffect.as new file mode 100644 index 0000000..c358cf6 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeEffect.as @@ -0,0 +1,215 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.loaders.ParserMaterial; + import alternativa.engine3d.resources.ExternalTextureResource; + + /** + * @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 ambient:DaeEffectParam; + private var transparent:DaeEffectParam; + private var transparency:DaeEffectParam; + private var bump:DaeEffectParam; + private var reflective:DaeEffectParam; + private var emission:DaeEffectParam; + private var specular:DaeEffectParam; + + public function DaeEffect(data:XML, document:DaeDocument) { + super(data, document); + + // image's are declared at + 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 ambientXML:XML = shader.ambient[0]; + if(ambientXML != null) { + ambient = new DaeEffectParam(ambientXML, this); + } + var reflectiveXML:XML = shader.reflective[0]; + if(reflectiveXML != null) { + reflective = new DaeEffectParam(reflectiveXML, 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; + } + } + + /** + * Returns material of the engine with given parameters. + * Call parse() before using. + */ + public function getMaterial(setparams:Object):ParserMaterial { + if (diffuse != null) { + var material:ParserMaterial = new ParserMaterial(); + if (diffuse) { + pushMap(material, diffuse, setparams); + } + if (specular != null) { + pushMap(material, specular, setparams); + } + + if (emission != null) { + pushMap(material, emission, setparams); + } + if (transparency != null) { + material.transparency = transparency.getFloat(setparams); + } + if (transparent != null) { + pushMap(material, transparent, setparams); + } + if (bump != null) { + pushMap(material, bump, setparams); + } + if (ambient) { + pushMap(material, ambient, setparams); + } + if (reflective) { + pushMap(material, reflective, setparams); + } + return material; + } + return null; + } + + private function pushMap(material:ParserMaterial, param:DaeEffectParam, setparams:Object):void { + var color:Array = param.getColor(setparams); + + if(color != null){ + material.colors[cloneString(param.data.localName())] = float4ToUint(color, true); + } + else { + var image:DaeImage = param.getImage(setparams); + if(image != null){ + material.textures[cloneString(param.data.localName())] = new ExternalTextureResource(cloneString(image.init_from)); + } + } + } + + /** + * Name of texture channel for main map of object. + * Call parse() before using. + */ + 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeEffectParam.as b/src/alternativa/engine3d/loaders/collada/DaeEffectParam.as new file mode 100644 index 0000000..fc17aeb --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeEffectParam.as @@ -0,0 +1,95 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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 { + // case of 3ds mas default export or so was used, it ignores spec and srores direct link to 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(); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeElement.as b/src/alternativa/engine3d/loaders/collada/DaeElement.as new file mode 100644 index 0000000..95b4961 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeElement.as @@ -0,0 +1,115 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + import flash.utils.ByteArray; + + /** + * @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; + + private static var _byteArray:ByteArray = new ByteArray(); + + public function DaeElement(data:XML, document:DaeDocument) { + this.document = document; + this.data = data; + } + + public function cloneString(str:String):String { + if(str == null) return null; + _byteArray.position = 0; + _byteArray.writeUTF(str); + _byteArray.position = 0; + var res:String = _byteArray.readUTF(); + return res; + } + + /** + * Performs pre-setting of object. + * @return false on error. + */ + 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; + } + + /** + * Overridden method parse(). + */ + protected function parseImplementation():Boolean { + return true; + } + + /** + * Returns array of String values. + */ + 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(); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeGeometry.as b/src/alternativa/engine3d/loaders/collada/DaeGeometry.as new file mode 100644 index 0000000..c923adb --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeGeometry.as @@ -0,0 +1,187 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.loaders.ParserMaterial; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Endian; + + /** + * @private + */ + public class DaeGeometry extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + internal var geometryVertices:Vector.; + internal var primitives:Vector.; + internal var geometry:Geometry; + + private var vertices:DaeVertices; + + public function DaeGeometry(data:XML, document:DaeDocument) { + super(data, document); + + /** + * Items sources, vertices are declared in the . + * You should create sources in DaeDocument, not here. + */ + 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) { + parsePrimitives(); + + vertices.parse(); + var numVertices:int = vertices.positions.numbers.length/vertices.positions.stride; + geometry = new Geometry(); + geometryVertices = new Vector.(numVertices); + var i:int; + var p:DaePrimitive; + var channels:uint = 0; + for (i = 0; i < primitives.length; i++) { + p = primitives[i]; + p.parse(); + if (p.verticesEquals(vertices)) { + numVertices = geometryVertices.length; + channels |= p.fillGeometry(geometry, geometryVertices); + } else { + // Error, Vertices of another geometry can not be used + } + } + + var attributes:Array = new Array(3); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + var index:int = 3; + if (channels & DaePrimitive.NORMALS) { + attributes[index++] = VertexAttributes.NORMAL; + attributes[index++] = VertexAttributes.NORMAL; + attributes[index++] = VertexAttributes.NORMAL; + } + if (channels & DaePrimitive.TANGENT4) { + attributes[index++] = VertexAttributes.TANGENT4; + attributes[index++] = VertexAttributes.TANGENT4; + attributes[index++] = VertexAttributes.TANGENT4; + attributes[index++] = VertexAttributes.TANGENT4; + } + for (i = 0; i < 8; i++) { + if (channels & DaePrimitive.TEXCOORDS[i]) { + attributes[index++] = VertexAttributes.TEXCOORDS[i]; + attributes[index++] = VertexAttributes.TEXCOORDS[i]; + } + } + + geometry.addVertexStream(attributes); + + numVertices = geometryVertices.length; + + var data:ByteArray = new ByteArray(); + data.endian = Endian.LITTLE_ENDIAN; + + var numMappings:int = attributes.length; + data.length = 4*numMappings*numVertices; + + for (i = 0; i < numVertices; i++) { + var vertex:DaeVertex = geometryVertices[i]; + if (vertex != null) { + data.position = 4*numMappings*i; + data.writeFloat(vertex.x); + data.writeFloat(vertex.y); + data.writeFloat(vertex.z); + if (vertex.normal != null) { + data.writeFloat(vertex.normal.x); + data.writeFloat(vertex.normal.y); + data.writeFloat(vertex.normal.z); + } + if (vertex.tangent != null) { + data.writeFloat(vertex.tangent.x); + data.writeFloat(vertex.tangent.y); + data.writeFloat(vertex.tangent.z); + data.writeFloat(vertex.tangent.w); + } + for (var j:int = 0; j < vertex.uvs.length; j++) { + data.writeFloat(vertex.uvs[j]); + } + } + } + geometry._vertexStreams[0].data = data; + geometry._numVertices = numVertices; + return true; + } + return false; + } + + private function parsePrimitives():void { + primitives = new Vector.(); + 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": + var p:DaePrimitive = new DaePrimitive(child, document); + primitives.push(p); + break; + } + } + } + + /** + * Creates geometry and returns it as mesh. + * Call parse() before using. + * @param materials Dictionary of materials + */ + public function parseMesh(materials:Object):Mesh { + if (data.mesh.length() > 0) { + var mesh:Mesh = new Mesh(); + mesh.geometry = geometry; + for (var i:int = 0; i < primitives.length; i++) { + var p:DaePrimitive = primitives[i]; + var instanceMaterial:DaeInstanceMaterial = materials[p.materialSymbol]; + var material:ParserMaterial; + if (instanceMaterial != null) { + var daeMaterial:DaeMaterial = instanceMaterial.material; + if (daeMaterial != null) { + daeMaterial.parse(); + material = daeMaterial.material; + daeMaterial.used = true; + } + } + mesh.addSurface(material, p.indexBegin, p.numTriangles); + } + mesh.calculateBoundBox(); + return mesh; + } + return null; + } + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeImage.as b/src/alternativa/engine3d/loaders/collada/DaeImage.as new file mode 100644 index 0000000..6c8640f --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeImage.as @@ -0,0 +1,37 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeInput.as b/src/alternativa/engine3d/loaders/collada/DaeInput.as new file mode 100644 index 0000000..69a0ad6 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeInput.as @@ -0,0 +1,63 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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); + } + + /** + * If DaeSource, located at the link source, is type of Number and + * number of components is not less than specified number, then this method will return it. + * + */ + 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 { + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + return null; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeInstanceController.as b/src/alternativa/engine3d/loaders/collada/DaeInstanceController.as new file mode 100644 index 0000000..6a87456 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeInstanceController.as @@ -0,0 +1,111 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import flash.utils.Dictionary; + + /** + * @private + */ + public class DaeInstanceController extends DaeElement { + + use namespace collada; + + public var node:DaeNode; + + /** + * List of top-level joints, which have common parent. (List of top-level joints, that have the common parent) + * Call parse() befire using. + */ + public var topmostJoints:Vector.; + + 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; + } + + /** + * Replaces each node in the list with its parent (the parent must be the same for all others node's or be a scene) + * @param nodes not empty array of nodes. + */ + private function replaceNodesByTopmost(nodes:Vector.):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; + } + } + } + // Replase node with its parent if it has the same parent with each other node or has no parent at all + 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. { + var list:XMLList = data.skeleton; + if (list.length() > 0) { + var skeletons:Vector. = new Vector.(); + 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as b/src/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as new file mode 100644 index 0000000..213e955 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as @@ -0,0 +1,49 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeLight.as b/src/alternativa/engine3d/loaders/collada/DaeLight.as new file mode 100644 index 0000000..b15ede4 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeLight.as @@ -0,0 +1,119 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.lights.AmbientLight; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + + /** + * @private + */ + public class DaeLight extends DaeElement { + + use namespace collada; + + public function DaeLight(data:XML, document:DaeDocument) { + super(data, document); + } + + private function float4ToUint(value:Array):uint { + var r:uint = (value[0] * 255); + var g:uint = (value[1] * 255); + var b:uint = (value[2] * 255); + return (r << 16) | (g << 8) | b | 0xFF000000; + } + + public function get revertDirection():Boolean { + var info:XML = data.technique_common.children()[0]; + return (info == null) ? false : (info.localName() == "directional" || info.localName() == "spot"); + } + + public function parseLight():Light3D { + var info:XML = data.technique_common.children()[0]; + var extra:XML = data.extra.technique.(@profile[0] == "OpenCOLLADA3dsMax").light[0]; + var light:Light3D = null; + if (info != null) { + var color:uint = float4ToUint(parseNumbersArray(info.color[0])); + var constantAttenuationXML:XML; + var linearAttenuationXML:XML; + var linearAttenuation:Number = 0; + var attenuationStart:Number = 0; + var attenuationEnd:Number = 1; + switch (info.localName()) { + case "ambient": + light = new AmbientLight(color); + break; + case "directional": + var dLight:DirectionalLight = new DirectionalLight(color); + light = dLight; + break; + case "point": + if (extra != null) { + attenuationStart = parseNumber(extra.attenuation_far_start[0]); + attenuationEnd = parseNumber(extra.attenuation_far_end[0]); + } else { + constantAttenuationXML = info.constant_attenuation[0]; + linearAttenuationXML = info.linear_attenuation[0]; + if (constantAttenuationXML != null) { + attenuationStart = -parseNumber(constantAttenuationXML); + } + if (linearAttenuationXML != null) { + linearAttenuation = parseNumber(linearAttenuationXML); + } + if (linearAttenuation > 0) { + attenuationEnd = 1/linearAttenuation + attenuationStart; + } else { + attenuationEnd = attenuationStart + 1; + } + } + var oLight:OmniLight = new OmniLight(color, attenuationStart, attenuationEnd); + light = oLight; + break; + case "spot": + var hotspot:Number = 0; + var fallof:Number = Math.PI/4; + const DEG2RAD:Number = Math.PI/180; + if (extra != null) { + attenuationStart = parseNumber(extra.attenuation_far_start[0]); + attenuationEnd = parseNumber(extra.attenuation_far_end[0]); + hotspot = DEG2RAD * parseNumber(extra.hotspot_beam[0]); + fallof = DEG2RAD * parseNumber(extra.falloff[0]); + } else { + constantAttenuationXML = info.constant_attenuation[0]; + linearAttenuationXML = info.linear_attenuation[0]; + if (constantAttenuationXML != null) { + attenuationStart = -parseNumber(constantAttenuationXML); + } + if (linearAttenuationXML != null) { + linearAttenuation = parseNumber(linearAttenuationXML); + } + if (linearAttenuation > 0) { + attenuationEnd = 1/linearAttenuation + attenuationStart; + } else { + attenuationEnd = attenuationStart + 1; + } + } + var sLight:SpotLight = new SpotLight(color, attenuationStart, attenuationEnd, hotspot, fallof); + light = sLight; + break; + } + } + if (extra != null) { + light.intensity = parseNumber(extra.multiplier[0]); + } + return light; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeLogger.as b/src/alternativa/engine3d/loaders/collada/DaeLogger.as new file mode 100644 index 0000000..e693fd5 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeLogger.as @@ -0,0 +1,62 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeLogger { + + public function DaeLogger() { + } + + private function logMessage(message:String, element:XML):void { + 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(" 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(" 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); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeMaterial.as b/src/alternativa/engine3d/loaders/collada/DaeMaterial.as new file mode 100644 index 0000000..f8f7b44 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeMaterial.as @@ -0,0 +1,72 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.loaders.ParserMaterial; + + /** + * @private + */ + public class DaeMaterial extends DaeElement { + + use namespace collada; + + /** + * Material of engine. + * Call parse() before using. + */ + public var material:ParserMaterial; + + /** + * Name of texture channel for color map of object. + * Call parse() before using. + */ + public var mainTexCoords:String; + + /** + * If truematerial is in use. + */ + 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 = cloneString(name); + } + return true; + } + return false; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeNode.as b/src/alternativa/engine3d/loaders/collada/DaeNode.as new file mode 100644 index 0000000..8ea9b79 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeNode.as @@ -0,0 +1,517 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.animation.keys.NumberTrack; + import alternativa.engine3d.animation.keys.Track; + import alternativa.engine3d.animation.keys.TransformTrack; + import alternativa.engine3d.core.Light3D; + 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; + + public var scene:DaeVisualScene; + public var parent:DaeNode; + + // Skin or top-level joint. + public var skinOrTopmostJoint:Boolean = false; + + /** + * Animation channels of this node. + */ + private var channels:Vector.; + + /** + * Vector of controllers, which have reference to this node. + */ + private var instanceControllers:Vector.; + + /** + * Array of nodes at this node. + */ + public var nodes:Vector.; + + /** + * Array of objects at this node. + * Call parse() before using. + * + */ + public var objects:Vector.; + + /** + * Vector of skins at this node. + * Call parse() before using. + * + */ + public var skins:Vector.; + + /** + * Name of object for animation + */ + public function get animName():String { + var n:String = this.name; + return (n == null) ? this.id : n; + } + + /** + * Create node from xml. Child nodes are created recursively. + */ + public function DaeNode(data:XML, document:DaeDocument, scene:DaeVisualScene = null, parent:DaeNode = null) { + super(data, document); + + this.scene = scene; + this.parent = parent; + + // Others node's declares inside + constructNodes(); + } + + private function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(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; + } + } + + internal function registerInstanceControllers():void { + var instanceControllerXMLs:XMLList = data.instance_controller; + var i:int; + var count:int = instanceControllerXMLs.length() + for (i = 0; i < count; i++) { + skinOrTopmostJoint = true; + var instanceControllerXML:XML = instanceControllerXMLs[i]; + var instanceController:DaeInstanceController = new DaeInstanceController(instanceControllerXML, document, this); + if (instanceController.parse()) { + var jointNodes:Vector. = instanceController.topmostJoints; + var numNodes:int = jointNodes.length; + if (numNodes > 0) { + var jointNode:DaeNode = jointNodes[0]; + jointNode.addInstanceController(instanceController); + for (var j:int = 0; j < numNodes; j++) { + jointNodes[j].skinOrTopmostJoint = true; + } + } + } + } + count = nodes.length; + for (i = 0; i < count; i++) { + nodes[i].registerInstanceControllers(); + } + } + + public function addChannel(channel:DaeChannel):void { + if (channels == null) { + channels = new Vector.(); + } + channels.push(channel); + } + + public function addInstanceController(controller:DaeInstanceController):void { + if (instanceControllers == null) { + instanceControllers = new Vector.(); + } + 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; + } + + /** + * Returns node by Sid. + */ + public function getNodeBySid(sid:String):DaeNode { + if (sid == this.sid) { + return this; + } + + var levelNodes:Vector. > = new Vector. >; + var levelNodes2:Vector. > = new Vector. >; + + levelNodes.push(nodes); + var len:int = levelNodes.length; + while (len > 0) { + for (var i:int = 0; i < len; i++) { + var children:Vector. = 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. > = levelNodes; + levelNodes = levelNodes2; + levelNodes2 = temp; + levelNodes2.length = 0; + + len = levelNodes.length; + } + return null; + } + + /** + * Parses and returns array of skins, associated with this node. + */ + public function parseSkins():Vector. { + if (instanceControllers == null) { + return null; + } + var skins:Vector. = new Vector.(); + 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); + // Name is got from node, that contains instance_controller. + skin.name = cloneString(instanceController.node.name); + // Not apply transformation and animation for skin. It specifies at root joints. + skins.push(skinAndAnimatedJoints); + } + } + return (skins.length > 0) ? skins : null; + } + + /** + * Parses and returns array of objects, associated with this node. + * Can be Mesh or Object3D, if type of object is unknown. + */ + public function parseObjects():Vector. { + var objects:Vector. = new Vector.(); + var children:XMLList = data.children(); + var i:int, count:int; + + for (i = 0, count = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case "instance_light": + var lightInstance:DaeLight = document.findLight(child.@url[0]); + if (lightInstance != null) { + var light:Light3D = lightInstance.parseLight(); + if (light != null) { + light.name = cloneString(name); + if (lightInstance.revertDirection) { + // Rotate 180 degrees along the x-axis, for correspondence to engine + var rotXMatrix:Matrix3D = new Matrix3D(); + rotXMatrix.appendRotation(180, Vector3D.X_AXIS); + // Not upload animations yet for these light sources + objects.push(new DaeObject(applyTransformations(light, rotXMatrix))); + } else { + objects.push(applyAnimation(applyTransformations(light))); + } + } + } else { + document.logger.logNotFoundError(child.@url[0]); + } + break; + 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 = cloneString(name); + objects.push(applyAnimation(applyTransformations(mesh))); + } + } else { + document.logger.logNotFoundError(child.@url[0]); + } + break; + case "instance_node": + document.logger.logInstanceNodeError(child); + break; + } + } + return (objects.length > 0) ? objects : null; + } + + /** + * Returns transformation of node as a matrix. + * @param initialMatrix To this matrix tranformation will appended. + */ + private 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--) { + //Transformations are append from the end to begin + var child:XML = children[i]; + var sid:XML = child.@sid[0]; + if (sid != null && sid.toString() == "post-rotationY") { + // Default 3dsmax exporter writes some trash which ignores + 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]*document.unitScaleFactor, + components[1]*document.unitScaleFactor, components[2]*document.unitScaleFactor); + break; + } + case "matrix" : { + components = parseNumbersArray(child); + matrix.append(new Matrix3D(Vector.([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]*document.unitScaleFactor ,components[7]*document.unitScaleFactor, components[11]*document.unitScaleFactor, components[15]]))); + break; + } + case "lookat" : { + break; + } + case "skew" : { + document.logger.logSkewError(child); + break; + } + } + } + return matrix; + } + + /** + * Apply transformation to object. + * @param prepend If is not null transformation will added to this matrix. + */ + public function applyTransformations(object:Object3D, prepend:Matrix3D = null, append:Matrix3D = null):Object3D { + var matrix:Matrix3D = getMatrix(prepend); + if (append != null) { + matrix.append(append); + } + var vs:Vector. = matrix.decompose(); + var t:Vector3D = vs[0]; + var r:Vector3D = vs[1]; + var s:Vector3D = vs[2]; + object.x = t.x; + object.y = t.y; + object.z = t.z; + object.rotationX = r.x; + object.rotationY = r.y; + object.rotationZ = r.z; + object.scaleX = s.x; + object.scaleY = s.y; + object.scaleZ = s.z; + return object; + } + + public function applyAnimation(object:Object3D):DaeObject { + var animation:AnimationClip = parseAnimation(object); + if (animation == null) { + return new DaeObject(object); + } + object.name = animName; + animation.attach(object, false); + return new DaeObject(object, animation); + } + + /** + * Returns animation of node. + */ + public function parseAnimation(object:Object3D = null):AnimationClip { + if (channels == null || !hasTransformationAnimation()) { + return null; + } + var channel:DaeChannel = getChannel(DaeChannel.PARAM_MATRIX); + if (channel != null) { + return createClip(channel.tracks); + } + var clip:AnimationClip = new AnimationClip(); + var components:Vector. = (object != null) ? null : getMatrix().decompose(); + + // Translation + channel = getChannel(DaeChannel.PARAM_TRANSLATE); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + channel = getChannel(DaeChannel.PARAM_TRANSLATE_X); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("x", (object == null) ? components[0].x : object.x)); + } + channel = getChannel(DaeChannel.PARAM_TRANSLATE_Y); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("y", (object == null) ? components[0].y : object.y)); + } + channel = getChannel(DaeChannel.PARAM_TRANSLATE_Z); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("z", (object == null) ? components[0].z : object.z)); + } + } + // Rotation + channel = getChannel(DaeChannel.PARAM_ROTATION_X); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("rotationX", (object == null) ? components[1].x : object.rotationX)); + } + channel = getChannel(DaeChannel.PARAM_ROTATION_Y); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("rotationY", (object == null) ? components[1].y : object.rotationY)); + } + channel = getChannel(DaeChannel.PARAM_ROTATION_Z); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("rotationZ", (object == null) ? components[1].z : object.rotationZ)); + } + // Scale + channel = getChannel(DaeChannel.PARAM_SCALE); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + channel = getChannel(DaeChannel.PARAM_SCALE_X); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("scaleX", (object == null) ? components[2].x : object.scaleX)); + } + channel = getChannel(DaeChannel.PARAM_SCALE_Y); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("scaleY", (object == null) ? components[2].y : object.scaleY)); + } + channel = getChannel(DaeChannel.PARAM_SCALE_Z); + if (channel != null) { + addTracksToClip(clip, channel.tracks); + } else { + clip.addTrack(createValueStaticTrack("scaleZ", (object == null) ? components[2].z : object.scaleZ)); + } + } + if (clip.numTracks > 0) { + return clip; + } + return null; + } + + private function createClip(tracks:Vector.):AnimationClip { + var clip:AnimationClip = new AnimationClip(); + for (var i:int = 0, count:int = tracks.length; i < count; i++) { + clip.addTrack(tracks[i]); + } + return clip; + } + + private function addTracksToClip(clip:AnimationClip, tracks:Vector.):void { + for (var i:int = 0, count:int = tracks.length; i < count; i++) { + clip.addTrack(tracks[i]); + } + } + + private function hasTransformationAnimation():Boolean { + for (var i:int = 0, count:int = channels.length; i < count; i++) { + var channel:DaeChannel = channels[i]; + channel.parse(); + var result:Boolean = channel.animatedParam == DaeChannel.PARAM_MATRIX; + result ||= channel.animatedParam == DaeChannel.PARAM_TRANSLATE; + result ||= channel.animatedParam == DaeChannel.PARAM_TRANSLATE_X; + result ||= channel.animatedParam == DaeChannel.PARAM_TRANSLATE_Y; + result ||= channel.animatedParam == DaeChannel.PARAM_TRANSLATE_Z; + result ||= channel.animatedParam == DaeChannel.PARAM_ROTATION_X; + result ||= channel.animatedParam == DaeChannel.PARAM_ROTATION_Y; + result ||= channel.animatedParam == DaeChannel.PARAM_ROTATION_Z; + result ||= channel.animatedParam == DaeChannel.PARAM_SCALE; + result ||= channel.animatedParam == DaeChannel.PARAM_SCALE_X; + result ||= channel.animatedParam == DaeChannel.PARAM_SCALE_Y; + result ||= channel.animatedParam == DaeChannel.PARAM_SCALE_Z; + if (result) { + return true; + } + } + return false; + } + + private function getChannel(param:String):DaeChannel { + for (var i:int = 0, count:int = channels.length; i < count; i++) { + var channel:DaeChannel = channels[i]; + channel.parse(); + if (channel.animatedParam == param) { + return channel; + } + } + return null; + } + + private function concatTracks(source:Vector., dest:Vector.):void { + for (var i:int = 0, count:int = source.length; i < count; i++) { + dest.push(source[i]); + } + } + + private function createValueStaticTrack(property:String, value:Number):Track { + var track:NumberTrack = new NumberTrack(animName, property); + track.addKey(0, value); + return track; + } + + public function createStaticTransformTrack():TransformTrack { + var track:TransformTrack = new TransformTrack(animName); + track.addKey(0, getMatrix()); + return track; + } + + public function get layer():String { + var layerXML:XML = data.@layer[0]; + return (layerXML == null) ? null : layerXML.toString(); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeObject.as b/src/alternativa/engine3d/loaders/collada/DaeObject.as new file mode 100644 index 0000000..5e0468a --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeObject.as @@ -0,0 +1,31 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.AnimationClip; + import alternativa.engine3d.core.Object3D; + + /** + * @private + */ + public class DaeObject { + + public var object:Object3D; + public var animation:AnimationClip; + public var jointNode:DaeNode; + + public function DaeObject(object:Object3D, animation:AnimationClip = null) { + this.object = object; + this.animation = animation; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeParam.as b/src/alternativa/engine3d/loaders/collada/DaeParam.as new file mode 100644 index 0000000..4a21524 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeParam.as @@ -0,0 +1,89 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +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; + } + + /** + * Returns Sid of a parameter type of surface. Only for sampler2D and Collada ver. 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaePrimitive.as b/src/alternativa/engine3d/loaders/collada/DaePrimitive.as new file mode 100644 index 0000000..277b255 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaePrimitive.as @@ -0,0 +1,331 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.resources.Geometry; + + use namespace collada; + + use namespace alternativa3d; + + /** + * @private + */ + public class DaePrimitive extends DaeElement { + + internal static const NORMALS:int = 1; + internal static const TANGENT4:int = 2; + internal static const TEXCOORDS:Vector. = Vector.([8, 16, 32, 64, 128, 256, 512, 1024]); + + internal var verticesInput:DaeInput; + internal var texCoordsInputs:Vector.; + internal var normalsInput:DaeInput; + internal var biNormalsInputs:Vector.; + internal var tangentsInputs:Vector.; + + internal var indices:Vector.; + internal var inputsStride:int; + + public var indexBegin:int; + public var numTriangles:int; + + public function DaePrimitive(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + parseInputs(); + parseIndices(); + return true; + } + + private function get type():String { + return data.localName() as String; + } + + private function parseInputs():void { + texCoordsInputs = new Vector.(); + tangentsInputs = new Vector.(); + biNormalsInputs = new Vector.(); + 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 parseIndices():void { + indices = new Vector.(); + var array:Array; + var vcount:Vector. = new Vector.(); + var i:int = 0; + var count:int = 0; + switch (data.localName()) { + case "polylist": + case "polygons": + var vcountXML:XMLList = data.vcount; + array = parseStringArray(vcountXML[0]); + for (i = 0,count = array.length; i < count; i++) { + vcount.push(parseInt(array[i])); + } + case "triangles": + var pList:XMLList = data.p; + for (i = 0,count = pList.length(); i < count; i++) { + array = parseStringArray(pList[i]); + for (var j:int = 0; j < array.length; j++) { + indices.push(parseInt(array[j], 10)); + } + if (vcount.length > 0) { + indices = triangulate(indices, vcount); + } + + } + break; + + } + } + + private function triangulate(input:Vector., vcount:Vector.):Vector. { + var res:Vector. = new Vector.(); + var indexIn:uint, indexOut:uint = 0; + var i:int, j:int, k:int, count:int; + for (i = 0,count = vcount.length; i < count; i++) { + var verticesCount:int = vcount[i]; + var attributesCount:int = verticesCount*inputsStride; + if (verticesCount == 3) { + for (j = 0; j < attributesCount; j++,indexIn++,indexOut++) { + res[indexOut] = input[indexIn]; + } + } else { + for (j = 1; j < verticesCount - 1; j++) { + // 0 - vertex + for (k = 0; k < inputsStride; k++,indexOut++) { + res[indexOut] = input[int(indexIn + k)]; + } + // 1 - vertex + for (k = 0; k < inputsStride; k++,indexOut++) { + res[indexOut] = input[int(indexIn + inputsStride*j + k)]; + } + // 2 - vertex + for (k = 0; k < inputsStride; k++,indexOut++) { + res[indexOut] = input[int(indexIn + inputsStride*(j + 1) + k)]; + } + } + indexIn += inputsStride*verticesCount; + } + } + return res; + } + + public function fillGeometry(geometry:Geometry, vertices:Vector.):uint { + if (verticesInput == null) { + // Error + return 0; + } + verticesInput.parse(); + + var numIndices:int = indices.length; + + var daeVertices:DaeVertices = document.findVertices(verticesInput.source); + if (daeVertices == null) { + document.logger.logNotFoundError(verticesInput.source); + return 0; + } + daeVertices.parse(); + + var positionSource:DaeSource = daeVertices.positions; + var vertexStride:int = 3; // XYZ + + var mainSource:DaeSource = positionSource; + var mainInput:DaeInput = verticesInput; + + var tangentSource:DaeSource; + var binormalSource:DaeSource; + + var channels:uint = 0; + var normalSource:DaeSource; + var inputOffsets:Vector. = new Vector.(); + inputOffsets.push(verticesInput.offset); + if (normalsInput != null) { + normalSource = normalsInput.prepareSource(3); + inputOffsets.push(normalsInput.offset); + vertexStride += 3; + channels |= NORMALS; + if (tangentsInputs.length > 0 && biNormalsInputs.length > 0) { + tangentSource = tangentsInputs[0].prepareSource(3); + inputOffsets.push(tangentsInputs[0].offset); + binormalSource = biNormalsInputs[0].prepareSource(3); + inputOffsets.push(biNormalsInputs[0].offset); + vertexStride += 4; + channels |= TANGENT4; + } + } + var textureSources:Vector. = new Vector.(); + var numTexCoordsInputs:int = texCoordsInputs.length; + if (numTexCoordsInputs > 8) { + // TODO: Warning + numTexCoordsInputs = 8; + } + for (var i:int = 0; i < numTexCoordsInputs; i++) { + var s:DaeSource = texCoordsInputs[i].prepareSource(2); + textureSources.push(s); + inputOffsets.push(texCoordsInputs[i].offset); + vertexStride += 2; + channels |= TEXCOORDS[i]; + } + + var verticesLength:int = vertices.length; + + // Make geometry data + var index:uint; + var vertex:DaeVertex; + + indexBegin = geometry._indices.length; + for (i = 0; i < numIndices; i += inputsStride) { + index = indices[int(i + mainInput.offset)]; + + vertex = vertices[index]; + if (vertex == null || !isEqual(vertex, indices, i, inputOffsets)) { + if (vertex != null) { + // Add to end + index = verticesLength++; + } + vertex = new DaeVertex(); + vertices[index] = vertex; + vertex.vertexInIndex = indices[int(i + verticesInput.offset)]; + vertex.addPosition(positionSource.numbers, vertex.vertexInIndex, positionSource.stride, document.unitScaleFactor); + + if (normalSource != null) { + vertex.addNormal(normalSource.numbers, indices[int(i + normalsInput.offset)], normalSource.stride); + + } + if (tangentSource != null) { + vertex.addTangentBiDirection(tangentSource.numbers, indices[int(i + tangentsInputs[0].offset)], tangentSource.stride, binormalSource.numbers, indices[int(i + biNormalsInputs[0].offset)], binormalSource.stride); + } + for (var j:int = 0; j < textureSources.length; j++) { + vertex.appendUV(textureSources[j].numbers, indices[int(i + texCoordsInputs[j].offset)], textureSources[j].stride); + } + } + vertex.vertexOutIndex = index; + geometry._indices.push(index); + } + numTriangles = (geometry._indices.length - indexBegin)/3; + return channels; + } + + private function isEqual(vertex:DaeVertex, indices:Vector., index:int, offsets:Vector.):Boolean { + var numOffsets:int = offsets.length; + for (var j:int = 0; j < numOffsets; j++) { + if (vertex.indices[j] != indices[int(index + offsets[j])]) { + return false; + } + } + return true; + } + + private function findInputBySet(inputs:Vector., setNum:int):DaeInput { + for (var i:int = 0, numInputs:int = inputs.length; i < numInputs; i++) { + var input:DaeInput = inputs[i]; + if (input.setNum == setNum) { + return input; + } + } + return null; + } + + /** + * Returns array of texture channels data. First element stores channel with mainSetNum. + */ + private function getTexCoordsDatas(mainSetNum:int):Vector. { + var mainInput:DaeInput = findInputBySet(texCoordsInputs, mainSetNum); + var i:int; + var numInputs:int = texCoordsInputs.length; + var datas:Vector. = new Vector.(); + for (i = 0; i < numInputs; i++) { + var texCoordsInput:DaeInput = texCoordsInputs[i]; + var texCoordsSource:DaeSource = texCoordsInput.prepareSource(2); + if (texCoordsSource != null) { + var data:VertexChannelData = new VertexChannelData(texCoordsSource.numbers, texCoordsSource.stride, texCoordsInput.offset, texCoordsInput.setNum); + if (texCoordsInput == mainInput) { + datas.unshift(data); + } else { + datas.push(data); + } + } + } + return (datas.length > 0) ? datas : null; + } + + /** + * Compare vertices of the privitive with given at otherVertices parameter vertices. + * Call parse() before using. + */ + 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 flash.geom.Point; + +class VertexChannelData { + public var values:Vector.; + public var stride:int; + public var offset:int; + public var index:int; + public var channel:Vector.; + public var inputSet:int; + + public function VertexChannelData(values:Vector., stride:int, offset:int, inputSet:int = 0) { + this.values = values; + this.stride = stride; + this.offset = offset; + this.inputSet = inputSet; + } + +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeSampler.as b/src/alternativa/engine3d/loaders/collada/DaeSampler.as new file mode 100644 index 0000000..e56fd38 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeSampler.as @@ -0,0 +1,118 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.keys.NumberTrack; + import alternativa.engine3d.animation.keys.Track; + import alternativa.engine3d.animation.keys.TransformTrack; + + import flash.geom.Matrix3D; + + use namespace collada; + + /** + * @private + */ + public class DaeSampler extends DaeElement { + + private var times:Vector.; + private var values:Vector.; + 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 parseNumbersTrack(objectName:String, property:String):NumberTrack { + if (times != null && values != null && timesStride > 0) { + var track:NumberTrack = new NumberTrack(objectName, property); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + track.addKey(times[int(timesStride*i)], values[int(valuesStride*i)]); + } + // TODO:: Exceptions with indices + return track; + } + return null; + } + + public function parseTransformationTrack(objectName:String):Track { + if (times != null && values != null && timesStride != 0) { + var track:TransformTrack = new TransformTrack(objectName); + 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.([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(times[i*timesStride], matrix); + } + return track; + } + return null; + } + + public function parsePointsTracks(objectName:String, xProperty:String, yProperty:String, zProperty:String):Vector. { + if (times != null && values != null && timesStride != 0) { + var xTrack:NumberTrack = new NumberTrack(objectName, xProperty); + xTrack.object = objectName; + var yTrack:NumberTrack = new NumberTrack(objectName, yProperty); + yTrack.object = objectName; + var zTrack:NumberTrack = new NumberTrack(objectName, zProperty); + zTrack.object = objectName; + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + var index:int = i*valuesStride; + var time:Number = times[i*timesStride]; + xTrack.addKey(time, values[index]); + yTrack.addKey(time, values[index + 1]); + zTrack.addKey(time, values[index + 2]); + } + return Vector.([xTrack, yTrack, zTrack]); + // TODO:: Exceptions with indices + } + return null; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeSource.as b/src/alternativa/engine3d/loaders/collada/DaeSource.as new file mode 100644 index 0000000..ee2e5ef --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeSource.as @@ -0,0 +1,164 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeSource extends DaeElement { + + use namespace collada; + + /** + * Types of arrays. + */ + private const FLOAT_ARRAY:String = "float_array"; + private const INT_ARRAY:String = "int_array"; + private const NAME_ARRAY:String = "Name_array"; + + /** + * Array of Number items. + * Call parse() before using. + */ + public var numbers:Vector.; + /** + * Array of int items. + * Call parse() before using. + */ + public var ints:Vector.; + /** + * Array of string items. + * Call parse() before using. + */ + + public var names:Vector.; + /** + * Size in bytes of one number or int element + * Call parse() before using. + */ + public var stride:int; + + public function DaeSource(data:XML, document:DaeDocument) { + super(data, document); + + // array declares within in . + 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.(int(arrStride*count)); + break; + case INT_ARRAY: + ints = new Vector.(int(arrStride*count)); + break; + case NAME_ARRAY: + names = new Vector.(int(arrStride*count)); + break; + } + var curr:int = 0; + for (var i:int = 0; i < arrStride; i++) { + // Only parameters which have name field should be read + 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeUnits.as b/src/alternativa/engine3d/loaders/collada/DaeUnits.as new file mode 100644 index 0000000..b5fee41 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeUnits.as @@ -0,0 +1,26 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + /** + * @private + */ + public class DaeUnits { + public static const METERS:Number = 1; + public static const DECIMETERS:Number = 0.1; + public static const CENTIMETERS:Number = 0.01; + public static const MILIMETERS:Number = 0.001; + public static const KILOMETERS:Number = 1000; + public static const INCHES:Number = 0.0254; + public static const FEET:Number = 0.3048; + public static const YARDS:Number = 0.9144; + public static const MILES:Number = 1609.347219; + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeVertex.as b/src/alternativa/engine3d/loaders/collada/DaeVertex.as new file mode 100644 index 0000000..5129b8b --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeVertex.as @@ -0,0 +1,76 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + import flash.geom.Vector3D; + + /** + * @private + */ + public class DaeVertex { + + public var vertexInIndex:int; + public var vertexOutIndex:int; + + public var indices:Vector. = new Vector.(); + + public var x:Number; + public var y:Number; + public var z:Number; + + public var uvs:Vector. = new Vector.(); + + public var normal:Vector3D; + public var tangent:Vector3D; + + public function addPosition(data:Vector., dataIndex:int, stride:int, unitScaleFactor:Number):void { + indices.push(dataIndex); + var offset:int = stride*dataIndex; + x = data[int(offset)]*unitScaleFactor; + y = data[int(offset + 1)]*unitScaleFactor; + z = data[int(offset + 2)]*unitScaleFactor; + } + + public function addNormal(data:Vector., dataIndex:int, stride:int):void { + indices.push(dataIndex); + var offset:int = stride*dataIndex; + normal = new Vector3D(); + normal.x = data[int(offset++)]; + normal.y = data[int(offset++)]; + normal.z = data[offset]; + } + + public function addTangentBiDirection(tangentData:Vector., tangentDataIndex:int, tangentStride:int, biNormalData:Vector., biNormalDataIndex:int, biNormalStride:int):void { + indices.push(tangentDataIndex); + indices.push(biNormalDataIndex); + var tangentOffset:int = tangentStride*tangentDataIndex; + var biNormalOffset:int = biNormalStride*biNormalDataIndex; + + var biNormalX:Number = biNormalData[int(biNormalOffset++)]; + var biNormalY:Number = biNormalData[int(biNormalOffset++)]; + var biNormalZ:Number = biNormalData[biNormalOffset]; + + tangent = new Vector3D(tangentData[int(tangentOffset++)], tangentData[int(tangentOffset++)], tangentData[tangentOffset]); + + 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; + } + + public function appendUV(data:Vector., dataIndex:int, stride:int):void { + indices.push(dataIndex); + uvs.push(data[int(dataIndex*stride)]); + uvs.push(1 - data[int(dataIndex*stride + 1)]); + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeVertexChannels.as b/src/alternativa/engine3d/loaders/collada/DaeVertexChannels.as new file mode 100644 index 0000000..8752f14 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeVertexChannels.as @@ -0,0 +1,29 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + + + /** + * @private + */ + public class DaeVertexChannels { + + public var uvChannels:Vector. = new Vector.(); + public var normalIndex:int; + public var tangentIndex:int; + public var biNormalIndex:int; + + public function DaeVertexChannels() { + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeVertices.as b/src/alternativa/engine3d/loaders/collada/DaeVertices.as new file mode 100644 index 0000000..d30bf37 --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeVertices.as @@ -0,0 +1,49 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * @private + */ + public class DaeVertices extends DaeElement { + + use namespace collada; + + /** + * Source of vertex coordinates data. Stores coordinates in numbers array. + *stride property of source is not less than three. + * Call parse() before using. + */ + public var positions:DaeSource; + //private var texCoords:Vector.; + + public function DaeVertices(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + // Get array of vertex coordinates. + 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; + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/DaeVisualScene.as b/src/alternativa/engine3d/loaders/collada/DaeVisualScene.as new file mode 100644 index 0000000..1b128be --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/DaeVisualScene.as @@ -0,0 +1,43 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeVisualScene extends DaeElement { + + use namespace collada; + + public var nodes:Vector.; + + public function DaeVisualScene(data:XML, document:DaeDocument) { + super(data, document); + + // nodes are declared in . + constructNodes(); + } + + public function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(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; + } + } + + } +} diff --git a/src/alternativa/engine3d/loaders/collada/collada.as b/src/alternativa/engine3d/loaders/collada/collada.as new file mode 100644 index 0000000..82f845d --- /dev/null +++ b/src/alternativa/engine3d/loaders/collada/collada.as @@ -0,0 +1,17 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public namespace collada = "http://www.collada.org/2005/11/COLLADASchema"; +} diff --git a/src/alternativa/engine3d/loaders/events/TexturesLoaderEvent.as b/src/alternativa/engine3d/loaders/events/TexturesLoaderEvent.as new file mode 100644 index 0000000..51b7f88 --- /dev/null +++ b/src/alternativa/engine3d/loaders/events/TexturesLoaderEvent.as @@ -0,0 +1,69 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.loaders.events { + + import alternativa.engine3d.resources.ExternalTextureResource; + + import flash.display.BitmapData; + import flash.events.Event; + + /** + * The Event is dispatched when TexturesLoader complete loading of textures. + * + * @see alternativa.engine3d.loaders.TexturesLoader + */ + public class TexturesLoaderEvent extends Event { + /** + * Value for type property . + */ + public static const COMPLETE:String = "complete"; + + private var bitmapDatas:Vector.; + private var textures:Vector.; + + public function TexturesLoaderEvent(type:String, bitmapDatas:Vector., textures:Vector.) { + this.bitmapDatas = bitmapDatas; + this.textures = textures; + super(type, false, false); + } + + /** + * Returns the list of loaded images. Method returns the list only when method TexturesLoader.loadResource() is called + * and TexturesLoader.loadResources() with needBitmapData is set to true. + * + * @see alternativa.engine3d.loaders.TexturesLoader#loadResource() + * @see alternativa.engine3d.loaders.TexturesLoader#loadResources() + */ + public function getBitmapDatas():Vector. { + return bitmapDatas; + } + + /** + * Returns the list of loaded textures. Method returns the list only when method TexturesLoader.loadResource() is called + * and TexturesLoader.loadResources() with needBitmapData is set to true. + * + * @see alternativa.engine3d.loaders.TexturesLoader#loadResource() + * @see alternativa.engine3d.loaders.TexturesLoader#loadResources() + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public function getTextures():Vector. { + return textures; + } + + /** + * Returns copy of object. + */ + override public function clone():Event { + return new TexturesLoaderEvent(type, bitmapDatas, textures); + } + + } +} diff --git a/src/alternativa/engine3d/materials/A3DUtils.as b/src/alternativa/engine3d/materials/A3DUtils.as new file mode 100644 index 0000000..258e1f1 --- /dev/null +++ b/src/alternativa/engine3d/materials/A3DUtils.as @@ -0,0 +1,343 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.compiler.CommandType; + import alternativa.engine3d.materials.compiler.VariableType; + + import avmplus.getQualifiedSuperclassName; + + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.IndexBuffer3D; + import flash.display3D.VertexBuffer3D; + import flash.display3D.textures.Texture; + import flash.geom.Point; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + import flash.utils.getDefinitionByName; + + /** + * @private + */ + public class A3DUtils { + + public static const NONE:int = 0; + public static const DXT1:int = 1; + public static const ETC1:int = 2; + public static const PVRTC:int = 3; + + private static const DXT1Data:ByteArray = getDXT1(); + private static const PVRTCData:ByteArray = getPVRTC(); + private static const ETC1Data:ByteArray = getETC1(); + + private static function getDXT1():ByteArray { + var DXT1Data:Vector. = Vector.([65,84,70,0,2,71,2,2,2,3,0,0,12,0,0,0,16,0,0,85,105,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,10,87,77,80,72,79,84,79,0,25,0,192,122,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,224,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,16,0,0,85,105,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,10,87,77,80,72,79,84,79,0,25,0,192,122,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,224,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,16,0,0,85,105,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,10,87,77,80,72,79,84,79,0,25,0,192,122,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,224,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,7,143,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + return getData(DXT1Data); + } + + private static function getETC1():ByteArray { + var ETC1Data:Vector. = Vector.([65,84,70,0,2,104,2,2,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,16,0,0,0,255,252,0,0,0,0,12,0,0,0,16,0,0,127,233,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,208,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,7,143,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,16,0,0,0,255,252,0,0,0,0,12,0,0,0,16,0,0,127,233,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,208,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,16,0,0,0,255,252,0,0,0,0,12,0,0,0,16,0,0,127,233,56,0,0,0,0,0,157,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,1,0,0,0,129,188,4,0,1,0,0,0,2,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,66,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,0,0,1,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,208,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,114,0,4,0]); + return getData(ETC1Data); + } + + private static function getPVRTC():ByteArray { + var PVRTCData:Vector. = Vector.([65,84,70,0,2,173,2,2,2,3,0,0,0,0,0,0,0,0,13,0,0,0,16,0,0,0,104,190,153,255,0,0,0,0,15,91,0,0,16,0,0,102,12,228,2,255,225,0,0,0,0,0,223,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,2,0,0,0,129,188,4,0,1,0,0,0,4,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,132,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,1,0,3,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,165,192,0,7,227,99,186,53,197,40,185,134,182,32,130,98,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,192,120,64,6,16,34,52,192,196,65,132,90,98,68,16,17,68,60,91,8,48,76,35,192,97,132,71,76,33,164,97,1,2,194,12,19,8,240,29,132,24,38,17,224,48,194,35,166,16,210,48,128,128,24,68,121,132,52,204,32,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,16,0,0,0,233,56,90,0,0,0,0,12,0,0,0,16,0,0,127,237,210,0,0,0,0,0,155,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,2,0,0,0,129,188,4,0,1,0,0,0,4,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,64,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,1,0,3,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,16,0,0,0,233,56,90,0,0,0,0,12,0,0,0,16,0,0,127,237,210,0,0,0,0,0,155,73,73,188,1,8,0,0,0,5,0,1,188,1,0,16,0,0,0,74,0,0,0,128,188,4,0,1,0,0,0,2,0,0,0,129,188,4,0,1,0,0,0,4,0,0,0,192,188,4,0,1,0,0,0,90,0,0,0,193,188,4,0,1,0,0,0,64,0,0,0,0,0,0,0,36,195,221,111,3,78,254,75,177,133,61,119,118,141,201,9,87,77,80,72,79,84,79,0,25,0,192,120,0,1,0,3,96,0,160,0,10,0,0,160,0,0,0,4,111,255,0,1,0,0,1,0,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,200,0,0,0,0,0,0,0,0,0,0]); + return getData(PVRTCData); + } + + private static function getData(source:Vector.):ByteArray { + var result:ByteArray = new ByteArray(); + for (var i:int = 0, length:int = source.length; i < length; i++) { + result.writeByte(source[i]); + } + return result; + } + + public static function getSizeFromATF(byteArray:ByteArray, size:Point):void { + byteArray.position = 7; + var w:int = byteArray.readByte(); + var h:int = byteArray.readByte(); + size.x = 1 << w; + size.y = 1 << h; + byteArray.position = 0; + } + + public static function getSupportedTextureFormat(context3D:Context3D):int { + var testTexture:Texture = context3D.createTexture(4, 4, Context3DTextureFormat.COMPRESSED, false); + var result:int = NONE; + try { + testTexture.uploadCompressedTextureFromByteArray(DXT1Data, 0); + result = DXT1; + } catch(e:Error) { + result = NONE; + } + if (result == NONE) { + try { + testTexture.uploadCompressedTextureFromByteArray(PVRTCData, 0); + result = PVRTC; + } catch(e:Error) { + result = NONE; + } + } + if (result == NONE) { + try { + testTexture.uploadCompressedTextureFromByteArray(ETC1Data, 0); + result = ETC1; + } catch(e:Error) { + result = NONE; + } + } + testTexture.dispose(); + return result; + } + + public static function vectorNumberToByteArray(vector:Vector.):ByteArray { + var result:ByteArray = new ByteArray(); + result.endian = Endian.LITTLE_ENDIAN; + for (var i:int = 0; i < vector.length; i++) { + result.writeFloat(vector[i]); + } + result.position = 0; + return result; + } + + public static function byteArrayToVectorUint(byteArray:ByteArray):Vector. { + var result:Vector. = new Vector.(); + var length:uint = 0; + byteArray.position = 0; + byteArray.endian = Endian.LITTLE_ENDIAN; + while (byteArray.bytesAvailable > 0) { + result[length++] = byteArray.readUnsignedShort(); + } + return result; + } + + public static function createVertexBufferFromByteArray(context:Context3D, byteArray:ByteArray, numVertices:uint, stride:uint = 3):VertexBuffer3D { + if (context == null) { + throw new ReferenceError("context is not set"); + } + var buffer:VertexBuffer3D = context.createVertexBuffer(numVertices, stride); + buffer.uploadFromByteArray(byteArray, 0, 0, numVertices); + return buffer; + } + + public static function createVertexBufferFromVector(context:Context3D, vector:Vector., numVertices:uint, stride:uint = 3):VertexBuffer3D { + if (context == null) { + throw new ReferenceError("context is not set"); + } + var buffer:VertexBuffer3D = context.createVertexBuffer(numVertices, stride); + + var byteArray:ByteArray = A3DUtils.vectorNumberToByteArray(vector); + buffer.uploadFromByteArray(byteArray, 0, 0, numVertices); + return buffer; + } + + public static function createTextureFromByteArray(context:Context3D, byteArray:ByteArray, width:Number, height:Number, format:String):Texture { + if (context == null) { + throw new ReferenceError("context is not set"); + } + var texture:Texture = context.createTexture(width, height, format, false); + texture.uploadCompressedTextureFromByteArray(byteArray, 0); + + return texture; + } + + public static function createIndexBufferFromByteArray(context:Context3D, byteArray:ByteArray, numIndices:uint):IndexBuffer3D { + if (context == null) { + throw new ReferenceError("context is not set"); + } + var buffer:IndexBuffer3D = context.createIndexBuffer(numIndices); + buffer.uploadFromByteArray(byteArray, 0, 0, numIndices); + + return buffer; + } + + public static function createIndexBufferFromVector(context:Context3D, vector:Vector., numIndices:int = -1):IndexBuffer3D { + if (context == null) { + throw new ReferenceError("context is not set"); + } + var count:uint = numIndices > 0 ? numIndices : vector.length; + var buffer:IndexBuffer3D = context.createIndexBuffer(count); + buffer.uploadFromVector(vector, 0, count); + + var byteArray:ByteArray = new ByteArray(); + byteArray.endian = Endian.LITTLE_ENDIAN; + for (var i:int = 0; i < count; i++) { + byteArray.writeInt(vector[i]); + } + byteArray.position = 0; + + buffer.uploadFromVector(vector, 0, count); + + return buffer; + } + + // Disassembler + private static var programType:Vector. = Vector.(["VERTEX", "FRAGMENT"]); + private static var samplerDimension:Vector. = Vector.(["2D", "cube", "3D"]); + private static var samplerWraping:Vector. = Vector.(["clamp", "repeat"]); + private static var samplerMipmap:Vector. = Vector.(["mipnone", "mipnearest", "miplinear"]); + private static var samplerFilter:Vector. = Vector.(["nearest", "linear"]); + private static var swizzleType:Vector. = Vector.(["x", "y", "z", "w"]); + private static var twoOperandsCommands:Dictionary; + private static const O_CODE:uint = "o".charCodeAt(0); + + public static function disassemble(byteCode:ByteArray):String { + if (!twoOperandsCommands) { + twoOperandsCommands = new Dictionary(); + twoOperandsCommands[0x1] = true; + twoOperandsCommands[0x2] = true; + twoOperandsCommands[0x3] = true; + twoOperandsCommands[0x4] = true; + twoOperandsCommands[0x6] = true; + twoOperandsCommands[0xb] = true; + twoOperandsCommands[0x11] = true; + twoOperandsCommands[0x12] = true; + twoOperandsCommands[0x13] = true; + twoOperandsCommands[0x17] = true; + twoOperandsCommands[0x18] = true; + twoOperandsCommands[0x19] = true; + twoOperandsCommands[0x28] = true; + twoOperandsCommands[0x29] = true; + twoOperandsCommands[0x2a] = true; + twoOperandsCommands[0x2c] = true; + twoOperandsCommands[0x2d] = true; + } + var res:String = ""; + byteCode.position = 0; + if (byteCode.bytesAvailable < 7) { + return "error in byteCode header"; + } + + res += "magic = " + byteCode.readUnsignedByte().toString(16); + res += "\nversion = " + byteCode.readInt().toString(10); + res += "\nshadertypeid = " + byteCode.readUnsignedByte().toString(16); + var pType:String = programType[byteCode.readByte()]; + res += "\nshadertype = " + pType; + res += "\nsource\n"; + pType = pType.substring(0, 1).toLowerCase(); + var lineNumber:uint = 1; + while (byteCode.bytesAvailable - 24 >= 0) { + res += (lineNumber++).toString() + ": " + getCommand(byteCode, pType) + "\n"; + } + if (byteCode.bytesAvailable > 0) { + res += "\nunexpected byteCode length. extra bytes:" + byteCode.bytesAvailable; + } + return res; + } + + private static function getCommand(byteCode:ByteArray, programType:String):String { + + var cmd:uint = byteCode.readUnsignedInt(); + var command:String = CommandType.COMMAND_NAMES[cmd]; + var result:String; + var destNumber:uint = byteCode.readUnsignedShort(); + var swizzle:uint = byteCode.readByte(); + var s:String = ""; + var destSwizzle:uint = 0; + if (swizzle < 15) { + s += "."; + s += ((swizzle & 0x1) > 0) ? "x" : ""; + s += ((swizzle & 0x2) > 0) ? "y" : ""; + s += ((swizzle & 0x4) > 0) ? "z" : ""; + s += ((swizzle & 0x8) > 0) ? "w" : ""; + destSwizzle = s.length; + } + + var destType:String = VariableType.TYPE_NAMES[byteCode.readUnsignedByte()].charAt(0); + if (destType.charCodeAt(0) == O_CODE) { + result = command + " " + attachProgramPrefix(destType, programType) + s + ", "; + } else { + result = command + " " + attachProgramPrefix(destType, programType) + destNumber.toString() + s + ", "; + } + + result += attachProgramPrefix(getSourceVariable(byteCode, destSwizzle), programType); + + if (twoOperandsCommands[cmd]) { + if (cmd == 0x28) { + result += ", " + attachProgramPrefix(getSamplerVariable(byteCode), programType); + } + else { + result += ", " + attachProgramPrefix(getSourceVariable(byteCode, destSwizzle), programType); + } + + } + else { + byteCode.readDouble(); + } + return result; + } + + private static function attachProgramPrefix(variable:String, programType:String):String { + var char:uint = variable.charCodeAt(0); + if (char == "o".charCodeAt(0)) + return variable + (programType == "f" ? "c" : "p"); + else if (char != "v".charCodeAt(0)) + return programType + variable; + return variable; + } + + private static function getSamplerVariable(byteCode:ByteArray):String { + var number:uint = byteCode.readUnsignedInt(); + byteCode.readByte(); + var dim:uint = byteCode.readByte() >> 4; + var wraping:uint = byteCode.readByte() >> 4; + var n:uint = byteCode.readByte(); + return "s" + number.toString() + " <" + samplerDimension[dim] + ", " + samplerWraping[wraping] + + ", " + samplerFilter[(n >> 4) & 0xf] + ", " + samplerMipmap[n & 0xf] + ">"; + } + + private static function getSourceVariable(byteCode:ByteArray, destSwizzle:uint):String { + var s1Number:uint = byteCode.readUnsignedShort(); + var offset:uint = byteCode.readUnsignedByte(); + var s:String = getSourceSwizzle(byteCode.readUnsignedByte(), destSwizzle); + + var s1Type:String = VariableType.TYPE_NAMES[byteCode.readUnsignedByte()].charAt(0); + var indexType:String = VariableType.TYPE_NAMES[byteCode.readUnsignedByte()].charAt(0); + var comp:String = swizzleType[byteCode.readUnsignedByte()]; + + if (byteCode.readUnsignedByte() > 0) { + return s1Type + "[" + indexType + s1Number.toString() + "." + comp + ((offset > 0) ? ("+" + offset.toString()) : "") + "]" + s; + } + return s1Type + s1Number.toString() + s; + } + + private static function getSourceSwizzle(swizzle:uint, destSwizzle:uint):String { + var s:String = ""; + if (swizzle != 0xe4) { + s += "."; + s += swizzleType[(swizzle & 0x3)]; + s += swizzleType[(swizzle >> 2) & 0x3]; + s += swizzleType[(swizzle >> 4) & 0x3]; + s += swizzleType[(swizzle >> 6) & 0x3]; + s = s.substring(0, destSwizzle > 0 ? destSwizzle : s.length); + } + return s; + } + + alternativa3d static function checkParent(child:Class, parent:Class):Boolean { + var current:Class = child; + if (parent == null) return true; + while (true) { + if (current == parent) return true; + var className:String = getQualifiedSuperclassName(current); + if (className != null) { + current = getDefinitionByName(className) as Class; + } else return false; + } + return false; + } + + } +} diff --git a/src/alternativa/engine3d/materials/EnvironmentMaterial.as b/src/alternativa/engine3d/materials/EnvironmentMaterial.as new file mode 100644 index 0000000..03fb4ae --- /dev/null +++ b/src/alternativa/engine3d/materials/EnvironmentMaterial.as @@ -0,0 +1,922 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.BitmapTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import avmplus.getQualifiedClassName; + + import flash.display.BitmapData; + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.VertexBuffer3D; + import flash.display3D.textures.CubeTexture; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + use namespace alternativa3d; + + /** + * The material which reflects the environment given with cube texture. + * + * @see alternativa.engine3d.resources.BitmapCubeTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public class EnvironmentMaterial extends TextureMaterial { + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Array; + + /** + * @private + */ + alternativa3d static var fogMode:int = FogMode.DISABLED; + /** + * @private + */ + alternativa3d static var fogNear:Number = 1000; + /** + * @private + */ + alternativa3d static var fogFar:Number = 5000; + + /** + * @private + */ + alternativa3d static var fogMaxDensity:Number = 1; + + /** + * @private + */ + alternativa3d static var fogColorR:Number = 0xC8/255; + /** + * @private + */ + alternativa3d static var fogColorG:Number = 0xA2/255; + /** + * @private + */ + alternativa3d static var fogColorB:Number = 0xC8/255; + + /** + * @private + */ + alternativa3d static var fogTexture:TextureResource; + + /** + * @private + */ + static alternativa3d const _passReflectionProcedure:Procedure = new Procedure([ + // i0 = position, i1 = normal + "#v1=vNormal", + "#v0=vPosition", + "mov v0, i0", + "mov v1, i1" + ], "passReflectionProcedure"); + + /** + * @private + */ + static alternativa3d const _applyReflectionProcedure:Procedure = getApplyReflectionProcedure(); + + private static function getApplyReflectionProcedure():Procedure { + var result:Procedure = new Procedure([ + "#v1=vNormal", + "#v0=vPosition", + "#s0=sCubeMap", + "#c0=cCamera", + "sub t0, v0, c0", + "dp3 t1.x, v1, t0", + "add t1.x, t1.x, t1.x", + "mul t1, v1, t1.x", + "sub t1, t0, t1", + "nrm t1.xyz, t1.xyz", + "m33 t1.xyz, t1.xyz, c1", + "nrm t1.xyz, t1.xyz", + "tex o0, t1, s0 " + ], "applyReflectionProcedure"); + result.assignVariableName(VariableType.CONSTANT, 1, "cLocalToGlobal", 3); + return result; + } + + /** + * @private + */ + static alternativa3d const _applyReflectionNormalMapProcedure:Procedure = getApplyReflectionNormalMapProcedure(); + + private static function getApplyReflectionNormalMapProcedure():Procedure { + var result:Procedure = new Procedure([ + "#s0=sCubeMap", + "#c0=cCamera", + "#v0=vPosition", + "sub t0, v0, c0", + "dp3 t1.x, i0.xyz, t0", + "add t1.x, t1.x, t1.x", + "mul t1, i0.xyz, t1.x", + "sub t1, t0, t1", + "nrm t1.xyz, t1.xyz", + "m33 t1.xyz, t1.xyz, c1", + "nrm t1.xyz, t1.xyz", + "tex o0, t1, s0 " + ], "applyReflectionNormalMapProcedure"); + result.assignVariableName(VariableType.CONSTANT, 1, "cLocalToGlobal", 3); + return result; + } + + /** + * @private + */ + static alternativa3d const _blendReflection:Procedure = new Procedure([ + "#c0=cAlpha", + "mul t1.xyz, i0.xyz, c0.y", + "mul t0.xyz, i1, c0.z", + "add t0.xyz, t1.xyz, t0", + "mov t0.w, i0.w", + "mov o0, t0" + ], "blendReflection"); + + /** + * @private + */ + static alternativa3d const _blendReflectionMap:Procedure = new Procedure([ + "#c0=cCamera", + "#c1=cAlpha", + "#s0=sReflection", + "#v0=vUV", + "tex t0, v0, s0 <2d,repeat,linear,miplinear>", + "mul t0, t0, c1.z", + "mul t1.xyz, i1, t0", + "sub t0, c0.www, t0", + "mul t2, i0, t0", + "add t0.xyz, t1, t2", + "mov t0.w, i0.w", + "mov o0, t0" + ], "blendReflectionMap"); + + // inputs : tangent, normal + private static const _passTBNRightProcedure:Procedure = getPassTBNProcedure(true); + private static const _passTBNLeftProcedure:Procedure = getPassTBNProcedure(false); + + private static function getPassTBNProcedure(right:Boolean):Procedure { + var crsInSpace:String = (right) ? "crs t1.xyz, i0, i1" : "crs t1.xyz, i1, i0"; + return new Procedure([ + "#v0=vTangent", + "#v1=vBinormal", + "#v2=vNormal", + // Calculates binormal + crsInSpace, + "mul t1.xyz, t1.xyz, i0.w", + // Transpose normal matrix + "mov v0.x, i0.x", + "mov v0.y, t1.x", + "mov v0.z, i1.x", + "mov v0.w, i1.w", + "mov v1.x, i0.y", + "mov v1.y, t1.y", + "mov v1.z, i1.y", + "mov v1.w, i1.w", + "mov v2.x, i0.z", + "mov v2.y, t1.z", + "mov v2.z, i1.z", + "mov v2.w, i1.w" + ], "passTBNProcedure"); + } + + // outputs : normal, viewVector + private static const _getNormalTangentProcedure:Procedure = new Procedure([ + "#v0=vTangent", + "#v1=vBinormal", + "#v2=vNormal", + "#v3=vUV", + "#c0=cCamera", + "#s0=sBump", + // Extract normal from the texture + "tex t0, v3, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "sub t0.xyz, t0.xyz, c0.www", + // Transform the normal with TBN + "nrm t1.xyz, v0.xyz", + "dp3 o0.x, t0.xyz, t1.xyz", + "nrm t1.xyz, v1.xyz", + "dp3 o0.y, t0.xyz, t1.xyz", + "nrm t1.xyz, v2.xyz", + "dp3 o0.z, t0.xyz, t1.xyz", + // Normalization after transform + "nrm o0.xyz, o0.xyz" + ], "getNormalTangentProcedure"); + // outputs : normal, viewVector + private static const _getNormalObjectProcedure:Procedure = new Procedure([ + "#v3=vUV", + "#c0=cCamera", + "#s0=sBump", + // Extract normal from the texture + "tex t0, v3, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "sub t0.xyz, t0.xyz, c0.www", + // Normalization + "nrm o0.xyz, t0.xyz" + ], "getNormalObjectProcedure"); + + // inputs : position + private static const passSimpleFogConstProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogSpace", + "dp4 t0.z, i0, c0", + "mov v0, t0.zzzz", + "sub v0.y, i0.w, t0.z" + ], "passSimpleFogConst"); + + // inputs : color + private static const outputWithSimpleFogProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogColor", + "#c1=cFogRange", + // Restrict fog factor with the range + "min t0.xy, v0.xy, c1.xy", + "max t0.xy, t0.xy, c1.zw", + "mul i0.xyz, i0.xyz, t0.y", + "mul t0.xyz, c0.xyz, t0.x", + "add i0.xyz, i0.xyz, t0.xyz", + "mov o0, i0" + ], "outputWithSimpleFog"); + + // inputs : position, projected + private static const postPassAdvancedFogConstProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogSpace", + "dp4 t0.z, i0, c0", + "mov v0, t0.zzzz", + "sub v0.y, i0.w, t0.z", + // Screen x coordinate + "mov v0.zw, i1.xwxw", + "mov o0, i1" + ], "postPassAdvancedFogConst"); + + // inputs : color + private static const outputWithAdvancedFogProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogConsts", + "#c1=cFogRange", + "#s0=sFogTexture", + // Restrict fog factor with the range + "min t0.xy, v0.xy, c1.xy", + "max t0.xy, t0.xy, c1.zw", + "mul i0.xyz, i0.xyz, t0.y", + // Calculate fog color + "mov t1.xyzw, c0.yyzw", + "div t0.z, v0.z, v0.w", + "mul t0.z, t0.z, c0.x", + "add t1.x, t1.x, t0.z", + "tex t1, t1, s0 <2d,repeat,linear,miplinear>", + "mul t0.xyz, t1.xyz, t0.x", + "add i0.xyz, i0.xyz, t0.xyz", + "mov o0, i0" + ], "outputWithAdvancedFog"); + + private static const _applyLightMapProcedure:Procedure = new Procedure([ + "#v0=vUV1", + "#s0=sLightMap", + "tex t0, v0, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "mul o0.xyz, i0.xyz, t0.xyz" + ], "applyLightMapProcedure"); + + private static const _passLightMapUVProcedure:Procedure = new Procedure([ + "#a0=aUV1", + "#v0=vUV1", + "mov v0, a0" + ], "passLightMapUVProcedure"); + + private var _normalMapSpace:int = NormalMapSpace.TANGENT_RIGHT_HANDED; + + /** + * Type of the normal map. Should be defined by constants of NormalMapSpace class. + * @default NormalMapSpace.TANGENT + * + * @see NormalMapSpace + */ + public function get normalMapSpace():int { + return _normalMapSpace; + } + + /** + * @private + */ + public function set normalMapSpace(value:int):void { + if (value != NormalMapSpace.TANGENT_RIGHT_HANDED && value != NormalMapSpace.TANGENT_LEFT_HANDED && value != NormalMapSpace.OBJECT) { + throw new ArgumentError("Value must be a constant from the NormalMapSpace class"); + } + + _normalMapSpace = value; + dirty(); + } + + /** + * Normal map. + */ + public function get normalMap():TextureResource { + return _normalMap; + } + + /** + * @private + */ + public function set normalMap(value:TextureResource):void { + _normalMap = value; + dirty(); + } + + /** + * Reflection texture. Should be BitmapCubeTextureResource or ExternalTextureResource with CubeTexture data. + */ + public function get environmentMap():TextureResource { + return _environmentMap; + } + + /** + * @private + */ + public function set environmentMap(value:TextureResource):void { + _environmentMap = value; + dirty(); + } + + /** + * Reflectivity map. + */ + public function get reflectionMap():TextureResource { + return _reflectionMap; + } + + /** + * @private + */ + public function set reflectionMap(value:TextureResource):void { + _reflectionMap = value; + dirty(); + } + + /** + * Light map. + */ + public function get lightMap():TextureResource { + return _lightMap; + } + + /** + * @private + */ + public function set lightMap(value:TextureResource):void { + _lightMap = value; + dirty(); + } + + /** + * Reflectivity. + */ + public var reflection:Number = 1; + + /** + * Number of the UV-channel for light map. + */ + public var lightMapChannel:uint = 1; + + /** + * @private + */ + alternativa3d var _normalMap:TextureResource; + + /** + * @private + */ + alternativa3d var _environmentMap:TextureResource; + + /** + * @private + */ + alternativa3d var _reflectionMap:TextureResource; + + /** + * @private + */ + alternativa3d var _lightMap:TextureResource; + + private var localToGlobalTransform:Transform3D = new Transform3D(); + + /*alternativa3d var lightMapOptions:SamplerOptions = new SamplerOptions(this); + + alternativa3d var normalMapOptions:SamplerOptions = new SamplerOptions(this); + + alternativa3d var environmentMapOptions:SamplerOptions = new SamplerOptions(this); + + alternativa3d var reflectionMapOptions:SamplerOptions = new SamplerOptions(this); + + alternativa3d var diffuseMapOptions:SamplerOptions = new SamplerOptions(this);*/ + + /** + * Creates a new EnvironmentMaterial instance. + * @param diffuseMap + * @param environmentMap + * @param normalMap + * @param reflectionMap + * @param lightMap + * @param opacityMap + * @param alpha + */ + public function EnvironmentMaterial(diffuseMap:TextureResource = null, environmentMap:TextureResource = null, normalMap:TextureResource = null, reflectionMap:TextureResource = null, lightMap:TextureResource = null, opacityMap:TextureResource = null, alpha:Number = 1) { + super(diffuseMap, opacityMap, alpha); + this._environmentMap = environmentMap; + this._normalMap = normalMap; + this._reflectionMap = reflectionMap; + this._lightMap = lightMap; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:EnvironmentMaterial = new EnvironmentMaterial(diffuseMap, _environmentMap, _normalMap, _reflectionMap, _lightMap, opacityMap, alpha); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Material):void { + super.clonePropertiesFrom(source); + var eMaterial:EnvironmentMaterial = EnvironmentMaterial(source); + reflection = eMaterial.reflection; + lightMapChannel = eMaterial.lightMapChannel; + _normalMapSpace = eMaterial._normalMapSpace; + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, resourceType:Class):void { + super.alternativa3d::fillResources(resources, resourceType); + if (_environmentMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(_environmentMap)) as Class, resourceType)) { + resources[_environmentMap] = true; + } + if (_normalMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(_normalMap)) as Class, resourceType)) { + resources[_normalMap] = true; + } + if (_reflectionMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(_reflectionMap)) as Class, resourceType)) { + resources[_reflectionMap] = true; + } + if (_lightMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(_lightMap)) as Class, resourceType)) { + resources[_lightMap] = true; + } + } + + private function setupProgram(targetObject:Object3D, opacityMap:TextureResource, alphaTest:int):EnvironmentMaterialShaderProgram { + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var positionVar:String = "aPosition"; + var normalVar:String = "aNormal"; + var tangentVar:String = "aTangent"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + vertexLinker.declareVariable(normalVar, VariableType.ATTRIBUTE); + if (targetObject.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(targetObject.transformProcedure, vertexLinker); + } + var procedure:Procedure; + if (targetObject.deltaTransformProcedure != null) { + vertexLinker.declareVariable("tTransformedNormal"); + procedure = targetObject.deltaTransformProcedure.newInstance(); + vertexLinker.addProcedure(procedure); + vertexLinker.setInputParams(procedure, normalVar); + vertexLinker.setOutputParams(procedure, "tTransformedNormal"); + normalVar = "tTransformedNormal"; + + if ((_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) && _normalMap != null) { + vertexLinker.declareVariable(tangentVar, VariableType.ATTRIBUTE); + vertexLinker.declareVariable("tTransformedTangent"); + procedure = targetObject.deltaTransformProcedure.newInstance(); + vertexLinker.addProcedure(procedure); + vertexLinker.setInputParams(procedure, tangentVar); + vertexLinker.setOutputParams(procedure, "tTransformedTangent"); + tangentVar = "tTransformedTangent"; + } + + } else { + if ((_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) && _normalMap != null) { + vertexLinker.declareVariable(tangentVar, VariableType.ATTRIBUTE); + } + } + if (_lightMap != null) { + vertexLinker.addProcedure(_passLightMapUVProcedure); + } + + vertexLinker.addProcedure(_passReflectionProcedure); + vertexLinker.setInputParams(_passReflectionProcedure, positionVar, normalVar); + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + vertexLinker.addProcedure(_passUVProcedure); + if (_normalMap != null) { + fragmentLinker.declareVariable("tNormal"); + if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) { + var nrmProcedure:Procedure = (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED) ? _passTBNRightProcedure : _passTBNLeftProcedure; + vertexLinker.addProcedure(nrmProcedure); + vertexLinker.setInputParams(nrmProcedure, tangentVar, normalVar); + fragmentLinker.addProcedure(_getNormalTangentProcedure); + fragmentLinker.setOutputParams(_getNormalTangentProcedure, "tNormal"); + } else { + fragmentLinker.addProcedure(_getNormalObjectProcedure); + fragmentLinker.setOutputParams(_getNormalObjectProcedure, "tNormal"); + } + } + + fragmentLinker.declareVariable("tColor"); + outputProcedure = opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure; + fragmentLinker.addProcedure(outputProcedure); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + + if (alphaTest > 0) { + outputProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure; + fragmentLinker.addProcedure(outputProcedure, "tColor"); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + } + + fragmentLinker.declareVariable("tReflection"); + if (_normalMap != null) { + fragmentLinker.addProcedure(_applyReflectionNormalMapProcedure); + fragmentLinker.setInputParams(_applyReflectionNormalMapProcedure, "tNormal"); + fragmentLinker.setOutputParams(_applyReflectionNormalMapProcedure, "tReflection"); + } else { + fragmentLinker.addProcedure(_applyReflectionProcedure); + fragmentLinker.setOutputParams(_applyReflectionProcedure, "tReflection"); + } + if (_lightMap != null) { + fragmentLinker.addProcedure(_applyLightMapProcedure); + fragmentLinker.setInputParams(_applyLightMapProcedure, "tColor"); + fragmentLinker.setOutputParams(_applyLightMapProcedure, "tColor"); + } + + var outputProcedure:Procedure; + if (_reflectionMap != null) { + fragmentLinker.addProcedure(_blendReflectionMap); + fragmentLinker.setInputParams(_blendReflectionMap, "tColor", "tReflection"); + outputProcedure = _blendReflectionMap; + } else { + fragmentLinker.addProcedure(_blendReflection); + fragmentLinker.setInputParams(_blendReflection, "tColor", "tReflection"); + outputProcedure = _blendReflection; + } + + if (fogMode == FogMode.SIMPLE || fogMode == FogMode.ADVANCED) { + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + } + if (fogMode == FogMode.SIMPLE) { + vertexLinker.addProcedure(passSimpleFogConstProcedure); + vertexLinker.setInputParams(passSimpleFogConstProcedure, positionVar); + fragmentLinker.addProcedure(outputWithSimpleFogProcedure); + fragmentLinker.setInputParams(outputWithSimpleFogProcedure, "tColor"); + } else if (fogMode == FogMode.ADVANCED) { + vertexLinker.declareVariable("tProjected"); + vertexLinker.setOutputParams(_projectProcedure, "tProjected"); + vertexLinker.addProcedure(postPassAdvancedFogConstProcedure); + vertexLinker.setInputParams(postPassAdvancedFogConstProcedure, positionVar, "tProjected"); + fragmentLinker.addProcedure(outputWithAdvancedFogProcedure); + fragmentLinker.setInputParams(outputWithAdvancedFogProcedure, "tColor"); + } + + fragmentLinker.varyings = vertexLinker.varyings; + return new EnvironmentMaterialShaderProgram(vertexLinker, fragmentLinker); + } + + /** + * @private + */ + alternativa3d function getProceduresCRC32(targetObject:Object3D, opacityMap:TextureResource, alphaTest:int):uint { + var crc:uint = 0xFFFFFFFF; + var procedureCRC:uint; + var crc32Table:Vector. = Procedure.crc32Table; + if (targetObject.transformProcedure != null) { + procedureCRC = targetObject.transformProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (targetObject.deltaTransformProcedure != null) { + procedureCRC = targetObject.deltaTransformProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + if ((_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) && _normalMap != null) { + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + + } + if (_lightMap != null) { + procedureCRC = _passLightMapUVProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (_normalMap != null) { + if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) { + procedureCRC = (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED) ? _passTBNRightProcedure.crc32 : _passTBNLeftProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + + procedureCRC = _getNormalTangentProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } else { + procedureCRC = _getNormalObjectProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + } + procedureCRC = opacityMap != null ? getDiffuseOpacityProcedure.crc32 : getDiffuseProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + if (alphaTest > 0) { + procedureCRC = alphaTest == 1 ? thresholdOpaqueAlphaProcedure.crc32 : thresholdTransparentAlphaProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (_normalMap != null) { + procedureCRC = _applyReflectionNormalMapProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } else { + procedureCRC = _applyReflectionProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (_lightMap != null) { + procedureCRC = _applyLightMapProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (_reflectionMap != null) { + procedureCRC = _blendReflectionMap.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } else { + procedureCRC = _blendReflection.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + if (fogMode == FogMode.SIMPLE) { + procedureCRC = passSimpleFogConstProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + procedureCRC = outputWithSimpleFogProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } else if (fogMode == FogMode.ADVANCED) { + procedureCRC = postPassAdvancedFogConstProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + procedureCRC = outputWithAdvancedFogProcedure.crc32; + crc = crc32Table[(crc ^ procedureCRC) & 0xFF] ^ (crc >> 8); + } + return crc ^ 0xFFFFFFFF; + } + + private function getDrawUnit(program:EnvironmentMaterialShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource):DrawUnit { + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL); + var tangentsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TANGENT4); + if (positionBuffer == null || uvBuffer == null || normalsBuffer == null) return null; + var i:int; + var object:Object3D = surface.object; + + if (program.sBump >= 0 && (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED)) { + if (tangentsBuffer == null) return null; + } + + // Draw call + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha); + // Set the textures + if (program.sLightMap >= 0) { + drawUnit.setTextureAt(program.sLightMap, _lightMap._texture); + drawUnit.setVertexBufferAt(program.aUV1, uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[lightMapChannel]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[lightMapChannel]]); + } + + if (program.sBump >= 0) { + drawUnit.setTextureAt(program.sBump, _normalMap._texture); + if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) { + drawUnit.setVertexBufferAt(program.aTangent, tangentsBuffer, geometry._attributesOffsets[VertexAttributes.TANGENT4], VertexAttributes.FORMATS[VertexAttributes.TANGENT4]); + } + } + + if (program.sReflection >= 0) { + drawUnit.setTextureAt(program.sReflection, _reflectionMap._texture); + } + + if (program.sOpacity >= 0) { + drawUnit.setTextureAt(program.sOpacity, opacityMap._texture); + } + + // Set the streams + drawUnit.setVertexBufferAt(program.aPosition, positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawUnit.setVertexBufferAt(program.aUV, uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + drawUnit.setVertexBufferAt(program.aNormal, normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]); + + // Set the constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.cProjMatrix, object.localToCameraTransform); + + drawUnit.setTextureAt(program.sTexture, diffuseMap._texture); + drawUnit.setTextureAt(program.sCubeMap, _environmentMap._texture); + var cameraToLocalTransform:Transform3D = object.cameraToLocalTransform; + drawUnit.setFragmentConstantsFromNumbers(program.cCamera, cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l); + drawUnit.setFragmentConstantsFromNumbers(program.cAlpha, 0, 1 - reflection, reflection, alpha); + + // Calculate local to global matrix + localToGlobalTransform.combine(camera.localToGlobalTransform, object.localToCameraTransform); + drawUnit.setFragmentConstantsFromTransform(program.cLocalToGlobal, localToGlobalTransform); + if (fogMode == FogMode.SIMPLE || fogMode == FogMode.ADVANCED) { + var lm:Transform3D = object.localToCameraTransform; + var dist:Number = fogFar - fogNear; + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("cFogSpace"), lm.i/dist, lm.j/dist, lm.k/dist, (lm.l - fogNear)/dist); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogRange"), fogMaxDensity, 1, 0, 1 - fogMaxDensity); + } + if (fogMode == FogMode.SIMPLE) { + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogColor"), fogColorR, fogColorG, fogColorB); + } + if (fogMode == FogMode.ADVANCED) { + if (fogTexture == null) { + var bmd:BitmapData = new BitmapData(32, 1, false, 0xFF0000); + for (i = 0; i < 32; i++) { + bmd.setPixel(i, 0, ((i/32)*255) << 16); + } + fogTexture = new BitmapTextureResource(bmd); + fogTexture.upload(camera.context3D); + } + var cLocal:Transform3D = camera.localToGlobalTransform; + var halfW:Number = camera.view.width/2; + var leftX:Number = -halfW*cLocal.a + camera.focalLength*cLocal.c; + var leftY:Number = -halfW*cLocal.e + camera.focalLength*cLocal.g; + var rightX:Number = halfW*cLocal.a + camera.focalLength*cLocal.c; + var rightY:Number = halfW*cLocal.e + camera.focalLength*cLocal.g; + // UV + var angle:Number = (Math.atan2(leftY, leftX) - Math.PI/2); + if (angle < 0) angle += Math.PI*2; + var dx:Number = rightX - leftX; + var dy:Number = rightY - leftY; + var lens:Number = Math.sqrt(dx*dx + dy*dy); + leftX /= lens; + leftY /= lens; + rightX /= lens; + rightY /= lens; + var uScale:Number = Math.acos(leftX*rightX + leftY*rightY)/Math.PI/2; + var uRight:Number = angle/Math.PI/2; + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogConsts"), 0.5*uScale, 0.5 - uRight, 0); + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sFogTexture"), fogTexture._texture); + } + return drawUnit; + } + + private function getProgram(targetObject:Object3D, camera:Camera3D, opacityMap:TextureResource, alphaTest:int):EnvironmentMaterialShaderProgram { + // Renew program cache for this context + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Array(); + caches[cachedContext3D] = programsCache; + } + } + + var key:uint; + var program:EnvironmentMaterialShaderProgram; + key = getProceduresCRC32(targetObject, opacityMap, alphaTest); + program = programsCache[key]; + if (program == null) { + program = programsCache[key] = setupProgram(targetObject, opacityMap, alphaTest); + + program.upload(camera.context3D); + } + return program; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + if (diffuseMap == null || diffuseMap._texture == null) return; + if (_environmentMap == null || _environmentMap._texture == null || !(_environmentMap._texture is CubeTexture)) return; + if (opacityMap != null && opacityMap._texture == null) return; + if (_normalMap != null && _normalMap._texture == null) return; + if (_reflectionMap != null && _reflectionMap._texture == null) return; + if (_lightMap != null && _lightMap._texture == null) return; + var object:Object3D = surface.object; + + // Program + var program:EnvironmentMaterialShaderProgram; + var drawUnit:DrawUnit; + // Opaque pass + if (opaquePass && alphaThreshold <= alpha) { + if (alphaThreshold > 0) { + // Alpha test + // use opacityMap if it is presented + program = getProgram(object, camera, opacityMap, 1); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // do not use opacityMap at all + program = getProgram(object, camera, null, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, null); + } + if (drawUnit == null) return; + // Use z-buffer within DrawCall, draws without blending + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + // Transparent pass + if (transparentPass && alphaThreshold > 0 && alpha > 0) { + // use opacityMap if it is presented + if (alphaThreshold <= alpha && !opaquePass) { + // Alpha threshold + program = getProgram(object, camera, opacityMap, 2); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // There is no Alpha threshold or check z-buffer by previous pass + program = getProgram(object, camera, opacityMap, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } + if (drawUnit == null) return; + // Do not use z-buffer, draws with blending + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } + } + +// + /** + * @private + */ + alternativa3d function dirty():void { + for each (var program:EnvironmentMaterialShaderProgram in programsCache) { + program.dirty = true; + } + } + + } +} + +import alternativa.engine3d.alternativa3d; +import alternativa.engine3d.materials.ShaderProgram; +import alternativa.engine3d.materials.compiler.Linker; + +use namespace alternativa3d; + +class EnvironmentMaterialShaderProgram extends ShaderProgram { + + public var aTangent:int = -1; + public var aNormal:int = -1; + public var aPosition:int = -1; + public var aUV:int = -1; + public var aUV1:int = -1; + + public var cCamera:int = -1; + public var cLocalToGlobal:int = -1; + public var cAlpha:int = -1; + public var cProjMatrix:int = -1; + + public var sBump:int = -1; + public var sTexture:int = -1; + public var sOpacity:int = -1; + public var sCubeMap:int = -1; + public var sReflection:int = -1; + public var sLightMap:int = -1; + public var dirty:Boolean = false; + + public function EnvironmentMaterialShaderProgram(vertexShader:Linker, fragmentShader:Linker) { + super(vertexShader, fragmentShader); + fragmentShader.varyings = vertexShader.varyings; + vertexShader.link(); + fragmentShader.link(); + aPosition = vertexShader.findVariable("aPosition"); + aNormal = vertexShader.findVariable("aNormal"); + aUV = vertexShader.findVariable("aUV"); + sBump = fragmentShader.findVariable("sBump"); + aTangent = vertexShader.findVariable("aTangent"); + sReflection = fragmentShader.findVariable("sReflection"); + sLightMap = fragmentShader.findVariable("sLightMap"); + aUV1 = vertexShader.findVariable("aUV1"); + cProjMatrix = vertexShader.findVariable("cProjMatrix"); + sTexture = fragmentShader.findVariable("sDiffuse"); + sCubeMap = fragmentShader.findVariable("sCubeMap"); + cCamera = fragmentShader.findVariable("cCamera"); + cLocalToGlobal = fragmentShader.findVariable("cLocalToGlobal"); + cAlpha = fragmentShader.findVariable("cAlpha"); + sOpacity = fragmentShader.findVariable("sOpacity"); + } + +} diff --git a/src/alternativa/engine3d/materials/FillMaterial.as b/src/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..06dfefc --- /dev/null +++ b/src/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,153 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * The materiall fills surface with solid color in light-independent manner. Can draw a Skin with no more than 41 Joints per surface. See Skin.divide() for more details. + * + * @see alternativa.engine3d.objects.Skin#divide() + */ + public class FillMaterial extends Material { + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Dictionary; + + private static var outColorProcedure:Procedure = new Procedure(["#c0=cColor", "mov o0, c0"], "outColorProcedure"); + + /** + * Transparency + */ + public var alpha:Number = 1; + + private var red:Number; + private var green:Number; + private var blue:Number; + + /** + * Color. + */ + public function get color():uint { + return (red*0xFF << 16) + (green*0xFF << 8) + blue*0xFF; + } + + /** + * @private + */ + public function set color(value:uint):void { + red = ((value >> 16) & 0xFF)/0xFF; + green = ((value >> 8) & 0xFF)/0xFF; + blue = (value & 0xff)/0xFF; + } + + /** + * Creates a new FillMaterial instance. + * @param color Color . + * @param alpha Transparency. + */ + public function FillMaterial(color:uint = 0x7F7F7F, alpha:Number = 1) { + this.color = color; + this.alpha = alpha; + } + + private function setupProgram(object:Object3D):ShaderProgram { + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var positionVar:String = "aPosition"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + if (object.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker); + } + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + fragmentLinker.addProcedure(outColorProcedure); + fragmentLinker.varyings = vertexLinker.varyings; + return new ShaderProgram(vertexLinker, fragmentLinker); + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + var object:Object3D = surface.object; + // Strams + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + // Check validity + if (positionBuffer == null) return; + // Program + + // Renew program cache for this context + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Dictionary(); + caches[cachedContext3D] = programsCache; + } + } + + var program:ShaderProgram = programsCache[object.transformProcedure]; + if (program == null) { + program = setupProgram(object); + program.upload(camera.context3D); + programsCache[object.transformProcedure] = program; + } + // Drawcall + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + // Streams + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + // Constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cColor"), red, green, blue, alpha); + // Send to render + if (alpha < 1) { + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } else { + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:FillMaterial = new FillMaterial(color, alpha); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/materials/FogMode.as b/src/alternativa/engine3d/materials/FogMode.as new file mode 100644 index 0000000..476e498 --- /dev/null +++ b/src/alternativa/engine3d/materials/FogMode.as @@ -0,0 +1,24 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + /** + * @private + */ + public class FogMode { + + public static const DISABLED:int = 0; + public static const SIMPLE:int = 1; + public static const ADVANCED:int = 2; + + } + +} diff --git a/src/alternativa/engine3d/materials/LightMapMaterial.as b/src/alternativa/engine3d/materials/LightMapMaterial.as new file mode 100644 index 0000000..4a906ee --- /dev/null +++ b/src/alternativa/engine3d/materials/LightMapMaterial.as @@ -0,0 +1,256 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import avmplus.getQualifiedClassName; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + use namespace alternativa3d; + + /** + * Texture material which supports light map. Can draw a Skin with no more than 41 Joints per surface. See Skin.divide() for more details. + * To be drawn with this material, geometry should have UV coordinates. Different UV-channels can be used for diffuse texture and light map. + * + * @see alternativa.engine3d.objects.Skin#divide() + * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS + */ + public class LightMapMaterial extends TextureMaterial { + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Dictionary; + + // inputs: color + private static const _applyLightMapProcedure:Procedure = new Procedure([ + "#v0=vUV1", + "#s0=sLightMap", + "tex t0, v0, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "mul i0.xyz, i0.xyz, t0.xyz", + "mov o0, i0" + ], "applyLightMapProcedure"); + + private static const _passLightMapUVProcedure:Procedure = new Procedure([ + "#a0=aUV1", + "#v0=vUV1", + "mov v0, a0" + ], "passLightMapUVProcedure"); + + /** + * Light map. + */ + public var lightMap:TextureResource; + /** + * Number of the UV-channel for light map. + */ + public var lightMapChannel:uint = 0; + + /** + * Creates a new LightMapMaterial instance. + * @param diffuseMap Diffuse texture. + * @param lightMap Light map. + * @param lightMapChannel Number of the UV-channel for light map. + */ + public function LightMapMaterial(diffuseMap:TextureResource = null, lightMap:TextureResource = null, lightMapChannel:uint = 0, opacityMap:TextureResource = null) { + super(diffuseMap, opacityMap); + this.lightMap = lightMap; + this.lightMapChannel = lightMapChannel; + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:LightMapMaterial = new LightMapMaterial(diffuseMap, lightMap, lightMapChannel, opacityMap); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @private + */ + override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void { + super.fillResources(resources, resourceType); + + if (lightMap != null && + A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(lightMap)) as Class, resourceType)) { + resources[lightMap] = true; + } + } + + /** + * @param object + * @param programs + * @param camera + * @param opacityMap + * @param alphaTest 0 - disabled, 1 - opaque, 2 - contours + * @return + */ + private function getProgram(object:Object3D, programs:Vector., camera:Camera3D, opacityMap:TextureResource, alphaTest:int):ShaderProgram { + var key:int = (opacityMap != null ? 3 : 0) + alphaTest; + var program:ShaderProgram = programs[key]; + if (program == null) { + // Make program + // Vertex shader + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + + var positionVar:String = "aPosition"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + if (object.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker); + } + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + vertexLinker.addProcedure(_passUVProcedure); + vertexLinker.addProcedure(_passLightMapUVProcedure); + + // Pixel shader + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + fragmentLinker.declareVariable("tColor"); + var outProcedure:Procedure = (opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure); + fragmentLinker.addProcedure(outProcedure); + fragmentLinker.setOutputParams(outProcedure, "tColor"); + + if (alphaTest > 0) { + outProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure; + fragmentLinker.addProcedure(outProcedure, "tColor"); + fragmentLinker.setOutputParams(outProcedure, "tColor"); + } + + fragmentLinker.addProcedure(_applyLightMapProcedure, "tColor"); + + fragmentLinker.varyings = vertexLinker.varyings; + + program = new ShaderProgram(vertexLinker, fragmentLinker); + + program.upload(camera.context3D); + programs[key] = program; + } + return program; + } + + private function getDrawUnit(program:ShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource):DrawUnit { + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var lightMapUVBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[lightMapChannel]); + + var object:Object3D = surface.object; + + // Drawcall + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + // Streams + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV1"), lightMapUVBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[lightMapChannel]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[lightMapChannel]]); + // Constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha); + // Textures + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sDiffuse"), diffuseMap._texture); + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sLightMap"), lightMap._texture); + if (opacityMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sOpacity"), opacityMap._texture); + } + + return drawUnit; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + if (diffuseMap == null || lightMap == null || diffuseMap._texture == null || lightMap._texture == null) return; + if (opacityMap != null && opacityMap._texture == null) return; + + var object:Object3D = surface.object; + + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var lightMapUVBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[lightMapChannel]); + + if (positionBuffer == null || uvBuffer == null || lightMapUVBuffer == null) return; + + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Dictionary(); + caches[cachedContext3D] = programsCache; + } + } + + var optionsPrograms:Vector. = programsCache[object.transformProcedure]; + if(optionsPrograms == null) { + optionsPrograms = new Vector.(6, true); + programsCache[object.transformProcedure] = optionsPrograms; + } + + var program:ShaderProgram; + var drawUnit:DrawUnit; + // Opaque pass + if (opaquePass && alphaThreshold <= alpha) { + if (alphaThreshold > 0) { + // Alpha test + // use opacityMap if it is presented + program = getProgram(object, optionsPrograms, camera, opacityMap, 1); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // do not use opacityMap at all + program = getProgram(object, optionsPrograms, camera, null, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, null); + } + // Use z-buffer within DrawCall, draws without blending + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + // Transparent pass + if (transparentPass && alphaThreshold > 0 && alpha > 0) { + // use opacityMap if it is presented + if (alphaThreshold <= alpha && !opaquePass) { + // Alpha threshold + program = getProgram(object, optionsPrograms, camera, opacityMap, 2); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // There is no Alpha threshold or check z-buffer by previous pass + program = getProgram(object, optionsPrograms, camera, opacityMap, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } + // Do not use z-buffer, draws with blending + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } + } + + } +} diff --git a/src/alternativa/engine3d/materials/Material.as b/src/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..6fe8eae --- /dev/null +++ b/src/alternativa/engine3d/materials/Material.as @@ -0,0 +1,116 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Resource; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Base class for all materials. Material defines, in which way surface will be visualized. + */ + public class Material { + + /** + * @private + */ + alternativa3d function get canDrawInShadowMap():Boolean {return true} + + /** + * Name of the material + */ + public var name:String; + + /** + * @private + */ + alternativa3d static const _projectProcedure:Procedure = getPojectProcedure(); + + private static function getPojectProcedure():Procedure { + var res:Procedure = new Procedure(["m44 o0, i0, c0"], "projectProcedure"); + res.assignVariableName(VariableType.CONSTANT, 0, "cProjMatrix", 4); + return res; + } + + public function Material() { + } + + /** + * @private + */ + alternativa3d function appendPositionTransformProcedure(transformProcedure:Procedure, vertexShader:Linker):String { + vertexShader.declareVariable("tTransformedPosition"); + vertexShader.addProcedure(transformProcedure); + vertexShader.setInputParams(transformProcedure, "aPosition"); + vertexShader.setOutputParams(transformProcedure, "tTransformedPosition"); + return "tTransformedPosition"; + } + + /** + * Gather resources used by material for uploading into context3D. + * + * @param resourceType Gather the resources given type only. + * @return Vector consists of resources. + * @see flash.display.Stage3D + */ + public function getResources(resourceType:Class = null):Vector. { + var res:Vector. = new Vector.(); + var dict:Dictionary = new Dictionary(); + var count:int = 0; + fillResources(dict, resourceType); + for (var key:* in dict) { + res[count++] = key as Resource; + } + return res; + } + + /** + * @private + */ + alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void { + } + + /** + * @private + */ + alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + } + + /** + * Duplicates an instance of a Material. + * @return A new Material object that is identical to the original. + */ + public function clone():Material { + var res:Material = new Material(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * Duplicates basic properties. Invoked byclone(). + * @param source Source of properties to be copied from. + */ + protected function clonePropertiesFrom(source:Material):void { + name = source.name; + } + + } +} diff --git a/src/alternativa/engine3d/materials/NormalMapSpace.as b/src/alternativa/engine3d/materials/NormalMapSpace.as new file mode 100644 index 0000000..f0dd850 --- /dev/null +++ b/src/alternativa/engine3d/materials/NormalMapSpace.as @@ -0,0 +1,34 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + /** + * NormalMapSpace offers constant values that can be used for the normalMapSpace property of materials which use normal map. + * + * @see StandardMaterial#normalMapSpace + */ + public class NormalMapSpace { + + /** + * Normal map defined in surface space, y-axis oriented on top. + */ + public static const TANGENT_RIGHT_HANDED:int = 0; + /** + * Normal map defined in surface space, y-axis oriented on bottom. + */ + public static const TANGENT_LEFT_HANDED:int = 1; + /** + * Normal map defined in object space. + */ + public static const OBJECT:int = 2; + + } +} diff --git a/src/alternativa/engine3d/materials/ShaderProgram.as b/src/alternativa/engine3d/materials/ShaderProgram.as new file mode 100644 index 0000000..9645fb5 --- /dev/null +++ b/src/alternativa/engine3d/materials/ShaderProgram.as @@ -0,0 +1,59 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.compiler.Linker; + + import flash.display3D.Context3D; + import flash.display3D.Program3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class ShaderProgram { + + public var program:Program3D; + + public var vertexShader:Linker; + public var fragmentShader:Linker; + + public function ShaderProgram(vertexShader:Linker, fragmentShader:Linker) { + this.vertexShader = vertexShader; + this.fragmentShader = fragmentShader; + } + + public function upload(context3D:Context3D):void { + if (program != null) program.dispose(); + if (vertexShader != null && fragmentShader != null) { + vertexShader.link(); + fragmentShader.link(); + program = context3D.createProgram(); + try { + program.upload(vertexShader.data, fragmentShader.data); + } catch (e:Error) { + throw (e); + } + } else { + program = null; + } + } + + public function dispose():void { + if (program != null) { + program.dispose(); + program = null; + } + } + + } +} diff --git a/src/alternativa/engine3d/materials/StandardMaterial.as b/src/alternativa/engine3d/materials/StandardMaterial.as new file mode 100644 index 0000000..7530de6 --- /dev/null +++ b/src/alternativa/engine3d/materials/StandardMaterial.as @@ -0,0 +1,939 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.BitmapTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import avmplus.getQualifiedClassName; + + import flash.display.BitmapData; + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DVertexBufferFormat; + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + use namespace alternativa3d; + + /** + * Material with diffuse, normal, opacity, specular maps and glossiness value. The material is able to draw skin + * with the number of bones in surface no more than 41. To reduce the number of bones in surface can break + * the skin for more surface with fewer bones. Use the method Skin.divide (). To be drawn with this material, + * geometry should have UV coordinates vertex normals and tangent and binormal values​​. + * + * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS + * @see alternativa.engine3d.core.VertexAttributes#NORMAL + * @see alternativa.engine3d.core.VertexAttributes#TANGENT4 + * @see alternativa.engine3d.objects.Skin#divide() + */ + public class StandardMaterial extends TextureMaterial { + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Dictionary; + + /** + * @private + */ + alternativa3d static const DISABLED:int = 0; + /** + * @private + */ + alternativa3d static const SIMPLE:int = 1; + /** + * @private + */ + alternativa3d static const ADVANCED:int = 2; + + /** + * @private + */ + alternativa3d static var fogMode:int = DISABLED; + /** + * @private + */ + alternativa3d static var fogNear:Number = 1000; + /** + * @private + */ + alternativa3d static var fogFar:Number = 5000; + + /** + * @private + */ + alternativa3d static var fogMaxDensity:Number = 1; + + /** + * @private + */ + alternativa3d static var fogColorR:Number = 0xC8/255; + /** + * @private + */ + alternativa3d static var fogColorG:Number = 0xA2/255; + /** + * @private + */ + alternativa3d static var fogColorB:Number = 0xC8/255; + + /** + * @private + */ + alternativa3d static var fogTexture:TextureResource; + //light procedure caching. The key is light3d instance. + private static const _lightFragmentProcedures:Dictionary = new Dictionary(); + + // inputs : position + private static const _passVaryingsProcedure:Procedure = new Procedure([ + "#v0=vPosition", + "#v1=vViewVector", + "#c0=cCameraPosition", + // Pass the position + "mov v0, i0", + // Vector to Camera + "sub t0, c0, i0", + "mov v1.xyz, t0.xyz", + "mov v1.w, c0.w" + ]); + + // inputs : tangent, normal + private static const _passTBNRightProcedure:Procedure = getPassTBNProcedure(true); + private static const _passTBNLeftProcedure:Procedure = getPassTBNProcedure(false); + private static function getPassTBNProcedure(right:Boolean):Procedure { + var crsInSpace:String = (right) ? "crs t1.xyz, i0, i1" : "crs t1.xyz, i1, i0"; + return new Procedure([ + "#v0=vTangent", + "#v1=vBinormal", + "#v2=vNormal", + // Calculate binormal + crsInSpace, + "mul t1.xyz, t1.xyz, i0.w", + // Transpose normal matrix + "mov v0.x, i0.x", + "mov v0.y, t1.x", + "mov v0.z, i1.x", + "mov v0.w, i1.w", + "mov v1.x, i0.y", + "mov v1.y, t1.y", + "mov v1.z, i1.y", + "mov v1.w, i1.w", + "mov v2.x, i0.z", + "mov v2.y, t1.z", + "mov v2.z, i1.z", + "mov v2.w, i1.w" + ], "passTBNProcedure"); + } + + // outputs : light, highlight + private static const _ambientLightProcedure:Procedure = new Procedure([ + "#c0=cSurface", + "mov o0, i0", + "mov o1, c0.xxxx" + ], "ambientLightProcedure"); + + // Set o.w to glossiness + private static const _setGlossinessFromConstantProcedure:Procedure = new Procedure([ + "#c0=cSurface", + "mov o0.w, c0.y" + ], "setGlossinessFromConstantProcedure"); + // Set o.w to glossiness from texture + private static const _setGlossinessFromTextureProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#c0=cSurface", + "#s0=sGlossiness", + "tex t0, v0, s0 <2d, repeat, linear, miplinear>", + "mul o0.w, t0.x, c0.y" + ], "setGlossinessFromTextureProcedure"); + + // outputs : normal, viewVector + private static const _getNormalAndViewTangentProcedure:Procedure = new Procedure([ + "#v0=vTangent", + "#v1=vBinormal", + "#v2=vNormal", + "#v3=vUV", + "#v4=vViewVector", + "#c0=cAmbientColor", + "#s0=sBump", + // Extract normal from the texture + "tex t0, v3, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "sub t0.xyz, t0.xyz, c0.www", + // Transform the normal with TBN + "nrm t1.xyz, v0.xyz", + "dp3 o0.x, t0.xyz, t1.xyz", + "nrm t1.xyz, v1.xyz", + "dp3 o0.y, t0.xyz, t1.xyz", + "nrm t1.xyz, v2.xyz", + "dp3 o0.z, t0.xyz, t1.xyz", + // Normalization + "nrm o0.xyz, o0.xyz", + // Returns normalized vector of view + "nrm o1.xyz, v4" + ], "getNormalAndViewTangentProcedure"); + // outputs : normal, viewVector + private static const _getNormalAndViewObjectProcedure:Procedure = new Procedure([ + "#v3=vUV", + "#v4=vViewVector", + "#c0=cAmbientColor", + "#s0=sBump", + // Extract normal from the texture + "tex t0, v3, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "sub t0.xyz, t0.xyz, c0.www", + // Normalization + "nrm o0.xyz, t0.xyz", + // Returns normalized vector of view + "nrm o1.xyz, v4" + ], "getNormalAndViewObjectProcedure"); + + // Apply specular map color to a flare + private static const _applySpecularProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#s0=sSpecular", + "tex t0, v0, s0 <2d, repeat,linear,miplinear>", + "mul o0.xyz, o0.xyz, t0.xyz" + ], "applySpecularProcedure"); + + //Apply light and flare to diffuse + // inputs : "diffuse", "tTotalLight", "tTotalHighLight" + private static const _mulLightingProcedure:Procedure = new Procedure([ + "#c0=cSurface", // c0.z - specularPower + "mul i0.xyz, i0.xyz, i1.xyz", + "mul t1.xyz, i2.xyz, c0.z", + "add i0.xyz, i0.xyz, t1.xyz", + "mov o0, i0" + ], "mulLightingProcedure"); + + // inputs : position + private static const passSimpleFogConstProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogSpace", + "dp4 t0.z, i0, c0", + "mov v0, t0.zzzz", + "sub v0.y, i0.w, t0.z" + ], "passSimpleFogConst"); + + // inputs : color + private static const outputWithSimpleFogProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogColor", + "#c1=cFogRange", + // Restrict fog factor with the range + "min t0.xy, v0.xy, c1.xy", + "max t0.xy, t0.xy, c1.zw", + "mul i0.xyz, i0.xyz, t0.y", + "mul t0.xyz, c0.xyz, t0.x", + "add i0.xyz, i0.xyz, t0.xyz", + "mov o0, i0" + ], "outputWithSimpleFog"); + + // inputs : position, projected + private static const postPassAdvancedFogConstProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogSpace", + "dp4 t0.z, i0, c0", + "mov v0, t0.zzzz", + "sub v0.y, i0.w, t0.z", + // Screen x coordinate + "mov v0.zw, i1.xwxw", + "mov o0, i1" + ], "postPassAdvancedFogConst"); + + // inputs : color + private static const outputWithAdvancedFogProcedure:Procedure = new Procedure([ + "#v0=vZDistance", + "#c0=cFogConsts", + "#c1=cFogRange", + "#s0=sFogTexture", + // Restrict fog factor with the range + "min t0.xy, v0.xy, c1.xy", + "max t0.xy, t0.xy, c1.zw", + "mul i0.xyz, i0.xyz, t0.y", + // Calculate fog color + "mov t1.xyzw, c0.yyzw", + "div t0.z, v0.z, v0.w", + "mul t0.z, t0.z, c0.x", + "add t1.x, t1.x, t0.z", + "tex t1, t1, s0 <2d, repeat, linear, miplinear>", + "mul t0.xyz, t1.xyz, t0.x", + "add i0.xyz, i0.xyz, t0.xyz", + "mov o0, i0" + ], "outputWithAdvancedFog"); + + // Add lightmap value with light + private static const _addLightMapProcedure:Procedure = new Procedure([ + "#v0=vUV1", + "#s0=sLightMap", + "tex t0, v0, s0 <2d,repeat,linear,miplinear>", + "add t0, t0, t0", + "add o0.xyz, i0.xyz, t0.xyz" + ], "applyLightMapProcedure"); + + private static const _passLightMapUVProcedure:Procedure = new Procedure([ + "#a0=aUV1", + "#v0=vUV1", + "mov v0, a0" + ], "passLightMapUVProcedure"); + + /** + * Normal map. + */ + public var normalMap:TextureResource; + + private var _normalMapSpace:int = NormalMapSpace.TANGENT_RIGHT_HANDED; + /** + * Type of the normal map. Should be defined by constants of NormalMapSpace class. + * + * @default NormalMapSpace.TANGENT + * + * @see NormalMapSpace + */ + public function get normalMapSpace():int { + return _normalMapSpace; + } + + /** + * @private + */ + public function set normalMapSpace(value:int):void { + if (value != NormalMapSpace.TANGENT_RIGHT_HANDED && value != NormalMapSpace.TANGENT_LEFT_HANDED && value != NormalMapSpace.OBJECT) { + throw new ArgumentError("Value must be a constant from the NormalMapSpace class"); + } + _normalMapSpace = value; + } + + /** + * Specular map. + */ + public var specularMap:TextureResource; + /** + * Glossiness map. + */ + public var glossinessMap:TextureResource; + + /** + * Light map. + */ + public var lightMap:TextureResource; + + /** + * Number of the UV-channel for light map. + */ + public var lightMapChannel:uint = 0; + /** + * Glossiness. Multiplies with glossinessMap value. + */ + public var glossiness:Number = 100; + + /** + * Brightness of a flare. Multiplies with specularMap value. + */ + public var specularPower:Number = 1; + + /** + * Creates a new StandardMaterial instance. + * @param diffuseMap Diffuse map. + * @param normalMap Normal map. + * @param specularMap Specular map. + * @param glossinessMap Glossiness map. + * @param opacityMap Opacity map. + */ + public function StandardMaterial(diffuseMap:TextureResource = null, normalMap:TextureResource = null, specularMap:TextureResource = null, glossinessMap:TextureResource = null, opacityMap:TextureResource = null) { + super(diffuseMap, opacityMap); + this.normalMap = normalMap; + this.specularMap = specularMap; + this.glossinessMap = glossinessMap; + } + + /** + * @private + */ + override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void { + super.fillResources(resources, resourceType); + if (normalMap != null && + A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(normalMap)) as Class, resourceType)) { + resources[normalMap] = true; + } + + if (lightMap != null && + A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(lightMap)) as Class, resourceType)) { + resources[lightMap] = true; + } + + if (glossinessMap != null && + A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(glossinessMap)) as Class, resourceType)) { + resources[glossinessMap] = true; + } + + if (specularMap != null && + A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(specularMap)) as Class, resourceType)) { + resources[specularMap] = true; + } + } + + /** + * @private + */ + alternativa3d function getPassUVProcedure():Procedure { + return _passUVProcedure; + } + + /** + * @private + */ + alternativa3d function setPassUVProcedureConstants(destination:DrawUnit, vertexLinker:Linker):void { + } + + // inputs: tNormal", "tViewVector", "shadow", "cAmbientColor" + // outputs : light, hightlight + private function formDirectionalProcedure(procedure:Procedure, light:Light3D, useShadow:Boolean):void { + var source:Array = [ + "#c0=c" + light.lightID + "Direction", + "#c1=c" + light.lightID + "Color", + // Calculate half-way vector + "add t0.xyz, i1.xyz, c0.xyz", + "mov t0.w, c0.w", + "nrm t0.xyz,t0.xyz", + // Calculate a flare + "dp3 t0.w, t0.xyz, i0.xyz", + "pow t0.w, t0.w, o1.w", + // Calculate light + "dp3 t0.x, i0.xyz, c0.xyz", + "sat t0.x, t0.x", + ]; + if (useShadow) { + source.push("mul t0.x, t0.x, i2.x"); + source.push("mul t0.xyz, c1.xyz, t0.xxx"); + source.push("add o0.xyz, t0.xyz, i3.xyz"); + source.push("mul t0.w, i2.x, t0.w"); + source.push("mul o1.xyz, c1.xyz, t0.www"); + } else { + // Apply calculated values + source.push("mul t0.xyz, c1.xyz, t0.xxxx"); + source.push("add o0, o0, t0.xyz"); + source.push("mul t0.xyz, c1.xyz, t0.w"); + source.push("add o1.xyz, o1.xyz, t0.xyz"); + } + procedure.compileFromArray(source); + } + + /** + * @param object + * @param materialKey + * @param opacityMap + * @param alphaTest 0:disabled 1:alpha-test 2:contours + * @param lights + * @param directionalLight + * @param lightsLength + */ + private function getProgram(object:Object3D, programs:Dictionary, camera:Camera3D, materialKey:String, opacityMap:TextureResource, alphaTest:int, lights:Vector., lightsLength:int, shadowedLight:Light3D):ShaderProgram { + var key:String = materialKey + (opacityMap != null ? "O" : "o") + alphaTest.toString(); + var program:ShaderProgram = programs[key]; + if (program == null) { + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var i:int; + + fragmentLinker.declareVariable("tTotalLight"); + fragmentLinker.declareVariable("tTotalHighLight"); + fragmentLinker.declareVariable("tNormal"); + fragmentLinker.declareVariable("cAmbientColor", VariableType.CONSTANT); + fragmentLinker.addProcedure(_ambientLightProcedure); + fragmentLinker.setInputParams(_ambientLightProcedure, "cAmbientColor"); + fragmentLinker.setOutputParams(_ambientLightProcedure, "tTotalLight", "tTotalHighLight"); + var positionVar:String = "aPosition"; + var normalVar:String = "aNormal"; + var tangentVar:String = "aTangent"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + vertexLinker.declareVariable(tangentVar, VariableType.ATTRIBUTE); + vertexLinker.declareVariable(normalVar, VariableType.ATTRIBUTE); + if (object.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker); + } + + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + + vertexLinker.addProcedure(getPassUVProcedure()); + + if (glossinessMap != null) { + fragmentLinker.addProcedure(_setGlossinessFromTextureProcedure); + fragmentLinker.setOutputParams(_setGlossinessFromTextureProcedure, "tTotalHighLight"); + } else { + fragmentLinker.addProcedure(_setGlossinessFromConstantProcedure); + fragmentLinker.setOutputParams(_setGlossinessFromConstantProcedure, "tTotalHighLight"); + } + if (lightsLength > 0) { + var procedure:Procedure; + if (object.deltaTransformProcedure != null) { + vertexLinker.declareVariable("tTransformedNormal"); + procedure = object.deltaTransformProcedure.newInstance(); + vertexLinker.addProcedure(procedure); + vertexLinker.setInputParams(procedure, normalVar); + vertexLinker.setOutputParams(procedure, "tTransformedNormal"); + normalVar = "tTransformedNormal"; + + vertexLinker.declareVariable("tTransformedTangent"); + procedure = object.deltaTransformProcedure.newInstance(); + vertexLinker.addProcedure(procedure); + vertexLinker.setInputParams(procedure, tangentVar); + vertexLinker.setOutputParams(procedure, "tTransformedTangent"); + tangentVar = "tTransformedTangent"; + } + vertexLinker.addProcedure(_passVaryingsProcedure); + vertexLinker.setInputParams(_passVaryingsProcedure, positionVar); + fragmentLinker.declareVariable("tViewVector"); + + if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) { + var nrmProcedure:Procedure = (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED) ? _passTBNRightProcedure : _passTBNLeftProcedure; + vertexLinker.addProcedure(nrmProcedure); + vertexLinker.setInputParams(nrmProcedure, tangentVar, normalVar); + fragmentLinker.addProcedure(_getNormalAndViewTangentProcedure); + fragmentLinker.setOutputParams(_getNormalAndViewTangentProcedure, "tNormal", "tViewVector"); + } else { + fragmentLinker.addProcedure(_getNormalAndViewObjectProcedure); + fragmentLinker.setOutputParams(_getNormalAndViewObjectProcedure, "tNormal", "tViewVector"); + } + if (shadowedLight != null && shadowedLight is DirectionalLight) { + vertexLinker.addProcedure(shadowedLight.shadow.vertexShadowProcedure, positionVar); + var shadowProc:Procedure = shadowedLight.shadow.fragmentShadowProcedure; + fragmentLinker.addProcedure(shadowProc); + fragmentLinker.setOutputParams(shadowProc, "tTotalLight"); + + var dirMulShadowProcedure:Procedure = _lightFragmentProcedures[shadowedLight.shadow]; + if (dirMulShadowProcedure == null) { + dirMulShadowProcedure = new Procedure(); + formDirectionalProcedure(dirMulShadowProcedure, shadowedLight, true); + } + fragmentLinker.addProcedure(dirMulShadowProcedure); + fragmentLinker.setInputParams(dirMulShadowProcedure, "tNormal", "tViewVector", "tTotalLight", "cAmbientColor"); + fragmentLinker.setOutputParams(dirMulShadowProcedure, "tTotalLight", "tTotalHighLight"); + } + + for (i = 0; i < lightsLength; i++) { + var light:Light3D = lights[i]; + if (light == shadowedLight) continue; + var lightFragmentProcedure:Procedure = _lightFragmentProcedures[light]; + if (lightFragmentProcedure == null) { + lightFragmentProcedure = new Procedure(); + lightFragmentProcedure.name = "light" + i.toString(); + if (light is DirectionalLight) { + formDirectionalProcedure(lightFragmentProcedure, light, false); + lightFragmentProcedure.name += "Directional"; + } else if (light is OmniLight) { + lightFragmentProcedure.compileFromArray([ + "#c0=c" + light.lightID + "Position", + "#c1=c" + light.lightID + "Color", + "#c2=c" + light.lightID + "Radius", + "#v0=vPosition", + // Calculate vector from the point to light + "sub t0, c0, v0", // L = lightPos - PointPos + "dp3 t0.w, t0.xyz, t0.xyz", // lenSqr + "nrm t0.xyz, t0.xyz", // L = normalize(L) + // Calculate half-way vector + "add t1.xyz, i1.xyz, t0.xyz", + "mov t1.w, c0.w", + "nrm t1.xyz, t1.xyz", + // Calculate a flare + "dp3 t1.w, t1.xyz, i0.xyz", + "pow t1.w, t1.w, o1.w", + // Calculate distance to the light source + "sqt t1.x, t0.w", // len = sqt(lensqr) + // Calculate light + "dp3 t0.w, t0.xyz, i0.xyz", // dot = dot(normal, L) + // Calculate decay + "sub t0.x, t1.x, c2.z", // len = len - atenuationBegin + "div t0.y, t0.x, c2.y", // att = len/radius + "sub t0.x, c2.x, t0.y", // att = 1 - len/radius + "sat t0.xw, t0.xw", // t = max(t, 0) + // Multiply light color with the decay value + "mul t0.xyz, c1.xyz, t0.xxx", // t = color*t + "mul t1.xyz, t0.xyz, t1.w", + "add o1.xyz, o1.xyz, t1.xyz", + "mul t0.xyz, t0.xyz, t0.www", + "add o0.xyz, o0.xyz, t0.xyz" + ]); + lightFragmentProcedure.name += "Omni"; + } else if (light is SpotLight) { + lightFragmentProcedure.compileFromArray([ + "#c0=c" + light.lightID + "Position", + "#c1=c" + light.lightID + "Color", + "#c2=c" + light.lightID + "Radius", + "#c3=c" + light.lightID + "Axis", + "#v0=vPosition", + // Calculate vector from the point to light + "sub t0, c0, v0",// L = pos - lightPos + "dp3 t0.w, t0, t0",// lenSqr + "nrm t0.xyz,t0.xyz",// L = normalize(L) + // Calculate half-way vector + "add t2.xyz, i1.xyz, t0.xyz", + "nrm t2.xyz, t2.xyz", + //Calculate a flare + "dp3 t2.x, t2.xyz, i0.xyz", + "pow t2.x, t2.x, o1.w", + "dp3 t1.x, t0.xyz, c3.xyz", //axisDirDot + "dp3 t0.x, t0, i0.xyz",// dot = dot(normal, L) + "sqt t0.w, t0.w",// len = sqt(lensqr) + "sub t0.w, t0.w, c2.y",// len = len - atenuationBegin + "div t0.y, t0.w, c2.x",// att = len/radius + "sub t0.w, c0.w, t0.y",// att = 1 - len/radius + "sub t0.y, t1.x, c2.w", + "div t0.y, t0.y, c2.z", + "sat t0.xyw,t0.xyw",// t = sat(t) + "mul t1.xyz,c1.xyz,t0.yyy",// t = color*t + "mul t1.xyz,t1.xyz,t0.www",// + "mul t2.xyz, t2.x, t1.xyz", + "add o1.xyz, o1.xyz, t2.xyz", + "mul t1.xyz, t1.xyz, t0.xxx", + + "add o0.xyz, o0.xyz, t1.xyz" + ]); + lightFragmentProcedure.name += "Spot"; + } + } + fragmentLinker.addProcedure(lightFragmentProcedure); + fragmentLinker.setInputParams(lightFragmentProcedure, "tNormal", "tViewVector"); + fragmentLinker.setOutputParams(lightFragmentProcedure, "tTotalLight", "tTotalHighLight"); + } + } + + var outputProcedure:Procedure; + if (specularMap != null) { + fragmentLinker.addProcedure(_applySpecularProcedure); + fragmentLinker.setOutputParams(_applySpecularProcedure, "tTotalHighLight"); + outputProcedure = _applySpecularProcedure; + } + if (lightMap != null) { + vertexLinker.addProcedure(_passLightMapUVProcedure); + fragmentLinker.addProcedure(_addLightMapProcedure); + fragmentLinker.setInputParams(_addLightMapProcedure, "tTotalLight"); + fragmentLinker.setOutputParams(_addLightMapProcedure, "tTotalLight"); + } + + fragmentLinker.declareVariable("tColor"); + outputProcedure = opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure; + fragmentLinker.addProcedure(outputProcedure); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + + if (alphaTest > 0) { + outputProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure; + fragmentLinker.addProcedure(outputProcedure, "tColor"); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + } + + fragmentLinker.addProcedure(_mulLightingProcedure, "tColor", "tTotalLight", "tTotalHighLight"); + + + if (fogMode == SIMPLE || fogMode == ADVANCED) { + fragmentLinker.setOutputParams(_mulLightingProcedure, "tColor"); + } + if (fogMode == SIMPLE) { + vertexLinker.addProcedure(passSimpleFogConstProcedure); + vertexLinker.setInputParams(passSimpleFogConstProcedure, positionVar); + fragmentLinker.addProcedure(outputWithSimpleFogProcedure); + fragmentLinker.setInputParams(outputWithSimpleFogProcedure, "tColor"); + outputProcedure = outputWithSimpleFogProcedure; + } else if (fogMode == ADVANCED) { + vertexLinker.declareVariable("tProjected"); + vertexLinker.setOutputParams(_projectProcedure, "tProjected"); + vertexLinker.addProcedure(postPassAdvancedFogConstProcedure); + vertexLinker.setInputParams(postPassAdvancedFogConstProcedure, positionVar, "tProjected"); + fragmentLinker.addProcedure(outputWithAdvancedFogProcedure); + fragmentLinker.setInputParams(outputWithAdvancedFogProcedure, "tColor"); + outputProcedure = outputWithAdvancedFogProcedure; + } + + fragmentLinker.varyings = vertexLinker.varyings; + program = new ShaderProgram(vertexLinker, fragmentLinker); + + + program.upload(camera.context3D); + programs[key] = program; + } + return program; + } + + private function getDrawUnit(program:ShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource, lights:Vector., lightsLength:int, shadowedLight:Light3D):DrawUnit { + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL); + var tangentsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TANGENT4); + + var object:Object3D = surface.object; + + // Draw call + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + // Streams + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + + // Constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform); + // Set options for a surface. X should be 0. + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cSurface"), 0, glossiness, specularPower, 1); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha); + + if (lightsLength > 0) { + if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) { + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aNormal"), normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aTangent"), tangentsBuffer, geometry._attributesOffsets[VertexAttributes.TANGENT4], VertexAttributes.FORMATS[VertexAttributes.TANGENT4]); + } + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sBump"), normalMap._texture); + + var camTransform:Transform3D = object.cameraToLocalTransform; + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("cCameraPosition"), camTransform.d, camTransform.h, camTransform.l); + + var transform:Transform3D; + var rScale:Number; + for (var i:int = 0; i < lightsLength; i++) { + var light:Light3D = lights[i]; + if (light is DirectionalLight) { + transform = light.lightToObjectTransform; + var len:Number = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k); + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Direction"), -transform.c/len, -transform.g/len, -transform.k/len, 1); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Color"), light.red, light.green, light.blue); + } else if (light is OmniLight) { + var omni:OmniLight = light as OmniLight; + transform = light.lightToObjectTransform; + rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i); + rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j); + rScale += Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k); + rScale /= 3; + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Position"), transform.d, transform.h, transform.l); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Radius"), 1, omni.attenuationEnd*rScale - omni.attenuationBegin*rScale, omni.attenuationBegin*rScale); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Color"), light.red, light.green, light.blue); + } else if (light is SpotLight) { + var spot:SpotLight = light as SpotLight; + transform = light.lightToObjectTransform; + rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i); + rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j); + rScale += len = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k); + rScale /= 3; + var falloff:Number = Math.cos(spot.falloff*0.5); + var hotspot:Number = Math.cos(spot.hotspot*0.5); + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Position"), transform.d, transform.h, transform.l); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Axis"), -transform.c/len, -transform.g/len, -transform.k/len); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Radius"), spot.attenuationEnd*rScale - spot.attenuationBegin*rScale, spot.attenuationBegin*rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + light.lightID + "Color"), light.red, light.green, light.blue); + } + } + } + + // Textures + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sDiffuse"), diffuseMap._texture); + if (opacityMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sOpacity"), opacityMap._texture); + } + if (glossinessMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sGlossiness"), glossinessMap._texture); + } + if (specularMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sSpecular"), specularMap._texture); + } + + if (lightMap != null) { + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV1"), + geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[lightMapChannel]), + geometry._attributesOffsets[VertexAttributes.TEXCOORDS[lightMapChannel]], + Context3DVertexBufferFormat.FLOAT_2); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cAmbientColor"), 0,0,0, 1); + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sLightMap"), lightMap._texture); + } else { + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex("cAmbientColor"), camera.ambient, 1); + } + setPassUVProcedureConstants(drawUnit, program.vertexShader); + + if (shadowedLight != null && shadowedLight is DirectionalLight) { + shadowedLight.shadow.setup(drawUnit, program.vertexShader, program.fragmentShader, surface); + } + + if (fogMode == SIMPLE || fogMode == ADVANCED) { + var lm:Transform3D = object.localToCameraTransform; + var dist:Number = fogFar - fogNear; + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("cFogSpace"), lm.i/dist, lm.j/dist, lm.k/dist, (lm.l - fogNear)/dist); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogRange"), fogMaxDensity, 1, 0, 1 - fogMaxDensity); + } + if (fogMode == SIMPLE) { + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogColor"), fogColorR, fogColorG, fogColorB); + } + if (fogMode == ADVANCED) { + if (fogTexture == null) { + var bmd:BitmapData = new BitmapData(32, 1, false, 0xFF0000); + for (i = 0; i < 32; i++) { + bmd.setPixel(i, 0, ((i/32)*255) << 16); + } + fogTexture = new BitmapTextureResource(bmd); + fogTexture.upload(camera.context3D); + } + var cLocal:Transform3D = camera.localToGlobalTransform; + var halfW:Number = camera.view.width/2; + var leftX:Number = -halfW*cLocal.a + camera.focalLength*cLocal.c; + var leftY:Number = -halfW*cLocal.e + camera.focalLength*cLocal.g; + var rightX:Number = halfW*cLocal.a + camera.focalLength*cLocal.c; + var rightY:Number = halfW*cLocal.e + camera.focalLength*cLocal.g; + // Finding UV + var angle:Number = (Math.atan2(leftY, leftX) - Math.PI/2); + if (angle < 0) angle += Math.PI*2; + var dx:Number = rightX - leftX; + var dy:Number = rightY - leftY; + var lens:Number = Math.sqrt(dx*dx + dy*dy); + leftX /= lens; + leftY /= lens; + rightX /= lens; + rightY /= lens; + var uScale:Number = Math.acos(leftX*rightX + leftY*rightY)/Math.PI/2; + var uRight:Number = angle/Math.PI/2; + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogConsts"), 0.5*uScale, 0.5 - uRight, 0); + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sFogTexture"), fogTexture._texture); + } + return drawUnit; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + if (diffuseMap == null || normalMap == null || diffuseMap._texture == null || normalMap._texture == null) return; + // Check if textures uploaded in to the context. + if (opacityMap != null && opacityMap._texture == null || glossinessMap != null && glossinessMap._texture == null || specularMap != null && specularMap._texture == null || lightMap != null && lightMap._texture == null) return; + + var object:Object3D = surface.object; + + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL); + var tangentsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TANGENT4); + + if (positionBuffer == null || uvBuffer == null) return; + + if (lightsLength > 0 && (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED)) { + if (normalsBuffer == null || tangentsBuffer == null) return; + } + + // Make shared part of the key. + var materialKey:String = (fogMode.toString()) + + ((lightMap != null) ? "L" : "l") + + (_normalMapSpace.toString()) + + ((glossinessMap != null) ? "G" : "g") + + ((specularMap != null) ? "S" : "s"); + var shadowedLight:Light3D; + for (var i:int = 0; i < lightsLength; i++) { + var light:Light3D = lights[i]; + if (light.shadow != null && shadowedLight == null) { + shadowedLight = light; + materialKey += light.shadow.type; + } + materialKey += light.lightID; + } + + // Refresh programs for this context. + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Dictionary(); + caches[cachedContext3D] = programsCache; + } + } + + var optionsPrograms:Dictionary = programsCache[object.transformProcedure]; + if (optionsPrograms == null) { + optionsPrograms = new Dictionary(false); + programsCache[object.transformProcedure] = optionsPrograms; + } + + var program:ShaderProgram; + var drawUnit:DrawUnit; + // Opaque pass + if (opaquePass && alphaThreshold <= alpha) { + if (alphaThreshold > 0) { + // Alpha test + // use opacityMap if it is presented + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, lights, lightsLength, shadowedLight); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength, shadowedLight); + } else { + // do not use opacityMap at all + program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, lights, lightsLength, shadowedLight); + drawUnit = getDrawUnit(program, camera, surface, geometry, null, lights, lightsLength, shadowedLight); + } + // Use z-buffer within DrawCall, draws without blending + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + // Transparent pass + if (transparentPass && alphaThreshold > 0 && alpha > 0) { + // use opacityMap if it is presented + if (alphaThreshold <= alpha && !opaquePass) { + // Alpha threshold + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 2, lights, lightsLength, shadowedLight); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength, shadowedLight); + } else { + // There is no Alpha threshold or check z-buffer by previous pass + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, lights, lightsLength, shadowedLight); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength, shadowedLight); + } + // Do not use z-buffer, draws with blending + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:StandardMaterial = new StandardMaterial(diffuseMap, normalMap, specularMap, glossinessMap, opacityMap); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Material):void { + super.clonePropertiesFrom(source); + var sMaterial:StandardMaterial = StandardMaterial(source); + glossiness = sMaterial.glossiness; + specularPower = sMaterial.specularPower; + _normalMapSpace = sMaterial._normalMapSpace; + lightMap = sMaterial.lightMap; + lightMapChannel = sMaterial.lightMapChannel; + } + + } +} diff --git a/src/alternativa/engine3d/materials/TextureMaterial.as b/src/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..20008ee --- /dev/null +++ b/src/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,333 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import avmplus.getQualifiedClassName; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + use namespace alternativa3d; + + /** + * The materiall fills surface with bitmap image in light-independent manner. Can draw a Skin with no more than 41 Joints per surface. See Skin.divide() for more details. + * + * To be drawn with this material, geometry shoud have UV coordinates. + * @see alternativa.engine3d.objects.Skin#divide() + * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS + */ + public class TextureMaterial extends Material { + + /** + * @private + */ + alternativa3d override function get canDrawInShadowMap():Boolean { + return opaquePass && alphaThreshold == 0; + } + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Dictionary; + + /** + * @private + * Procedure for diffuse map with alpha channel + */ + static alternativa3d const getDiffuseProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#s0=sDiffuse", + "#c0=cThresholdAlpha", + "tex t0, v0, s0 <2d, linear,repeat, miplinear>", + "mul t0.w, t0.w, c0.w", + "mov o0, t0" + ], "getDiffuseProcedure"); + + /** + * @private + * Procedure for diffuse with opacity map. + */ + static alternativa3d const getDiffuseOpacityProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#s0=sDiffuse", + "#s1=sOpacity", + "#c0=cThresholdAlpha", + "tex t0, v0, s0 <2d, linear,repeat, miplinear>", + "tex t1, v0, s1 <2d, linear,repeat, miplinear>", + "mul t0.w, t1.x, c0.w", + "mov o0, t0" + ], "getDiffuseOpacityProcedure"); + + /** + * @private + * Alpha-test check procedure. + */ + static alternativa3d const thresholdOpaqueAlphaProcedure:Procedure = new Procedure([ + "#c0=cThresholdAlpha", + "sub t0.w, i0.w, c0.x", + "kil t0.w", + "mov o0, i0" + ], "thresholdOpaqueAlphaProcedure"); + + /** + * @private + * Alpha-test check procedure. + */ + static alternativa3d const thresholdTransparentAlphaProcedure:Procedure = new Procedure([ + "#c0=cThresholdAlpha", + "slt t0.w, i0.w, c0.x", + "mul i0.w, t0.w, i0.w", + "mov o0, i0" + ], "thresholdTransparentAlphaProcedure"); + + /** + * @private + * Pass UV to the fragment shader procedure + */ + static alternativa3d const _passUVProcedure:Procedure = new Procedure(["#v0=vUV", "#a0=aUV", "mov v0, a0"], "passUVProcedure"); + + /** + * Diffuse map. + */ + public var diffuseMap:TextureResource; + + /** + * Opacity map. + */ + public var opacityMap:TextureResource; + + /** + * If true, perform transparent pass. Parts of surface, cumulative alpha value of which is below than alphaThreshold draw within transparent pass. + * @see #alphaThreshold + */ + public var transparentPass:Boolean = true; + + /** + * If true, perform opaque pass. Parts of surface, cumulative alpha value of which is greater or equal than alphaThreshold draw within opaque pass. + * @see #alphaThreshold + */ + public var opaquePass:Boolean = true; + + /** + * alphaThreshold defines starts from which value of alpha a fragment of surface will get into transparent pass. + * @see #transparentPass + * @see #opaquePass + */ + public var alphaThreshold:Number = 0; + + /** + * Transparency. + */ + public var alpha:Number = 1; + + /** + * Creates a new TextureMaterial instance. + * + * @param diffuseMap Diffuse map. + * @param alpha Transparency. + */ + public function TextureMaterial(diffuseMap:TextureResource = null, opacityMap:TextureResource = null, alpha:Number = 1) { + this.diffuseMap = diffuseMap; + this.opacityMap = opacityMap; + this.alpha = alpha; + } + + /** + * @private + */ + override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void { + super.fillResources(resources, resourceType); + if (diffuseMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(diffuseMap)) as Class, resourceType)) { + resources[diffuseMap] = true; + } + if (opacityMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(opacityMap)) as Class, resourceType)) { + resources[opacityMap] = true; + } + } + + /** + * @param object + * @param programs + * @param camera + * @param opacityMap + * @param alphaTest 0 - disabled, 1 - opaque, 2 - contours + * @return + */ + private function getProgram(object:Object3D, programs:Vector., camera:Camera3D, opacityMap:TextureResource, alphaTest:int):ShaderProgram { + var key:int = (opacityMap != null ? 3 : 0) + alphaTest; + var program:ShaderProgram = programs[key]; + if (program == null) { + // Make program + // Vertex shader + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + + var positionVar:String = "aPosition"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + if (object.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker); + } + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + vertexLinker.addProcedure(_passUVProcedure); + + // Pixel shader + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var outProcedure:Procedure = (opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure); + fragmentLinker.addProcedure(outProcedure); + if (alphaTest > 0) { + fragmentLinker.declareVariable("tColor"); + fragmentLinker.setOutputParams(outProcedure, "tColor"); + if (alphaTest == 1) { + fragmentLinker.addProcedure(thresholdOpaqueAlphaProcedure, "tColor"); + } else { + fragmentLinker.addProcedure(thresholdTransparentAlphaProcedure, "tColor"); + } + } + fragmentLinker.varyings = vertexLinker.varyings; + + program = new ShaderProgram(vertexLinker, fragmentLinker); + + program.upload(camera.context3D); + programs[key] = program; + } + return program; + } + + private function getDrawUnit(program:ShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource):DrawUnit { + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + + var object:Object3D = surface.object; + + // Draw call + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + // Streams + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + //Constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha); + // Textures + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sDiffuse"), diffuseMap._texture); + if (opacityMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sOpacity"), opacityMap._texture); + } + return drawUnit; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + var object:Object3D = surface.object; + + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + + // Check validity + if (positionBuffer == null || uvBuffer == null || diffuseMap == null || diffuseMap._texture == null || opacityMap != null && opacityMap._texture == null) return; + + // Refresh program cache for this context + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Dictionary(); + caches[cachedContext3D] = programsCache; + } + } + var optionsPrograms:Vector. = programsCache[object.transformProcedure]; + if(optionsPrograms == null) { + optionsPrograms = new Vector.(6, true); + programsCache[object.transformProcedure] = optionsPrograms; + } + + var program:ShaderProgram; + var drawUnit:DrawUnit; + // Opaque pass + if (opaquePass && alphaThreshold <= alpha) { + if (alphaThreshold > 0) { + // Alpha test + // use opacityMap if it is presented + program = getProgram(object, optionsPrograms, camera, opacityMap, 1); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // do not use opacityMap at all + program = getProgram(object, optionsPrograms, camera, null, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, null); + } + // Use z-buffer within DrawCall, draws without blending + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + // Transparent pass + if (transparentPass && alphaThreshold > 0 && alpha > 0) { + // use opacityMap if it is presented + if (alphaThreshold <= alpha && !opaquePass) { + // Alpha threshold + program = getProgram(object, optionsPrograms, camera, opacityMap, 2); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } else { + // There is no Alpha threshold or check z-buffer by previous pass + program = getProgram(object, optionsPrograms, camera, opacityMap, 0); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap); + } + // Do not use z-buffer, draws with blending + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:TextureMaterial = new TextureMaterial(diffuseMap, opacityMap, alpha); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Material):void { + super.clonePropertiesFrom(source); + var tex:TextureMaterial = source as TextureMaterial; + diffuseMap = tex.diffuseMap; + opacityMap = tex.opacityMap; + opaquePass = tex.opaquePass; + transparentPass = tex.transparentPass; + alphaThreshold = tex.alphaThreshold; + alpha = tex.alpha; + } + + } +} diff --git a/src/alternativa/engine3d/materials/VertexLightTextureMaterial.as b/src/alternativa/engine3d/materials/VertexLightTextureMaterial.as new file mode 100644 index 0000000..bf29db4 --- /dev/null +++ b/src/alternativa/engine3d/materials/VertexLightTextureMaterial.as @@ -0,0 +1,357 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Texture material with dynamic vertex lightning. The material is able to draw skin + * with the number of bones in surface no more than 41. To reduce the number of bones in surface can break + * the skin for more surface with fewer bones. Use the method Skin.divide(). To be drawn with this material, geometry should have UV coordinates and vertex normals​​. + * + * @see alternativa.engine3d.objects.Skin#divide() + * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS + * @see alternativa.engine3d.core.VertexAttributes#NORMAL + */ + public class VertexLightTextureMaterial extends TextureMaterial { + + private static var caches:Dictionary = new Dictionary(true); + private var cachedContext3D:Context3D; + private var programsCache:Dictionary; + + private static const _passLightingProcedure:Procedure = new Procedure(["#v0=vLightColor","mov v0, i0"], "passLightingProcedure"); + private static const _ambientLightProcedure:Procedure = new Procedure(["mov o0, i0"], "ambientLightProcedure"); + private static const _mulLightingProcedure:Procedure = new Procedure(["#v0=vLightColor","mul o0, i0, v0"], "mulLightingProcedure"); + private static const _directionalLightCode:Array = [ + "dp3 t0.x,i0,c0", + "sat t0.x,t0.x", + "mul t0, c1, t0.xxxx", + "add o0, o0, t0" + ]; + + private static const _omniLightCode:Array = [ + "sub t0, c0, i1", // L = pos - lightPos + "dp3 t0.w, t0, t0", // lenSqr + "nrm t0.xyz,t0.xyz", // L = normalize(L) + "dp3 t0.x,t0,i0", // dot = dot(normal, L) + "sqt t0.w,t0.w", // len = sqt(lensqr) + "sub t0.w, t0.w, c2.z", // len = len - atenuationBegin + "div t0.y, t0.w, c2.y", // att = len/radius + "sub t0.w, c2.x, t0.y", // att = 1 - len/radius + "sat t0.xw,t0.xw", // t = sat(t) + "mul t0.xyz,c1.xyz,t0.xxx", // t = color*t + "mul t0.xyz, t0.xyz, t0.www", + "add o0.xyz, o0.xyz, t0.xyz" + ]; + + private static const _spotLightCode:Array = [ + "sub t0, c0, i1", // L = pos - lightPos + "dp3 t0.w, t0, t0", // lenSqr + + "nrm t0.xyz,t0.xyz", // L = normalize(L) + + "dp3 t1.x, t0.xyz, c3.xyz", //axisDirDot + "dp3 t0.x,t0,i0", // dot = dot(normal, L) + + "sqt t0.w,t0.w", // len = sqt(lensqr) + "sub t0.w, t0.w, c2.y", // len = len - atenuationBegin + "div t0.y, t0.w, c2.x", // att = len/radius + "sub t0.w, c0.w, t0.y", // att = 1 - len/radius + "sub t0.y, t1.x, c2.w", + "div t0.y, t0.y, c2.z", + "sat t0.xyw,t0.xyw", // t = sat(t) + "mul t1.xyz,c1.xyz,t0.xxx", // t = color*t + "mul t1.xyz,t1.xyz,t0.yyy", // + "mul t1.xyz, t1.xyz, t0.www", + "add o0.xyz, o0.xyz, t1.xyz" + ]; + + private static const _lightsProcedures:Dictionary = new Dictionary(true); + + /** + * Creates a new VertexLightTextureMaterial instance. + * + * @param diffuse Diffuse map. + * @param alpha Transparency. + */ + public function VertexLightTextureMaterial(diffuse:TextureResource = null, opacityMap:TextureResource = null, alpha:Number = 1) { + super(diffuse, opacityMap, alpha); + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:VertexLightTextureMaterial = new VertexLightTextureMaterial(diffuseMap, opacityMap, alpha); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @param object + * @param materialKey + * @param opacityMap + * @param alphaTest - 0:disabled 1:alpha-test 2:contours + * @param lights + * @param directionalLight + * @param lightsLength + */ + private function getProgram(object:Object3D, programs:Dictionary, camera:Camera3D, materialKey:String, opacityMap:TextureResource, alphaTest:int, lights:Vector., lightsLength:int):ShaderProgram { + var key:String = materialKey + (opacityMap != null ? "O" : "o") + alphaTest.toString(); + var program:ShaderProgram = programs[key]; + if (program == null) { + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + vertexLinker.declareVariable("tTotalLight"); + vertexLinker.declareVariable("aNormal", VariableType.ATTRIBUTE); + vertexLinker.declareVariable("cAmbientColor", VariableType.CONSTANT); + vertexLinker.addProcedure(_passUVProcedure); + var positionVar:String = "aPosition"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + if (object.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker); + } + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + vertexLinker.addProcedure(_ambientLightProcedure); + vertexLinker.setInputParams(_ambientLightProcedure, "cAmbientColor"); + vertexLinker.setOutputParams(_ambientLightProcedure, "tTotalLight"); + if (lightsLength > 0) { + var normalVar:String = "aNormal"; + if (object.deltaTransformProcedure != null) { + vertexLinker.declareVariable("tTransformedNormal"); + vertexLinker.addProcedure(object.deltaTransformProcedure); + vertexLinker.setInputParams(object.deltaTransformProcedure, "aNormal"); + vertexLinker.setOutputParams(object.deltaTransformProcedure, "tTransformedNormal"); + normalVar = "tTransformedNormal"; + } + for (var i:uint = 0; i < lightsLength; i++) { + var light:Light3D = lights[i]; + var lightProcedure:Procedure = _lightsProcedures[light]; + if (lightProcedure == null) { + lightProcedure = new Procedure(); + if (light is DirectionalLight) { + lightProcedure.compileFromArray(_directionalLightCode); + lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Direction"); + lightProcedure.name = "Directional" + i.toString(); + } else if (light is OmniLight) { + lightProcedure.compileFromArray(_omniLightCode); + lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Position"); + lightProcedure.assignVariableName(VariableType.CONSTANT, 2, "c" + light.name + "Radius"); + lightProcedure.name = "Omni" + i.toString(); + } else if (light is SpotLight) { + lightProcedure.compileFromArray(_spotLightCode); + lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Position"); + lightProcedure.assignVariableName(VariableType.CONSTANT, 2, "c" + light.name + "Radius"); + lightProcedure.assignVariableName(VariableType.CONSTANT, 3, "c" + light.name + "Axis"); + lightProcedure.name = "Spot" + i.toString(); + } + lightProcedure.assignVariableName(VariableType.CONSTANT, 1, "c" + light.name + "Color"); + _lightsProcedures[light] = lightProcedure; + } + vertexLinker.addProcedure(lightProcedure); + vertexLinker.setInputParams(lightProcedure, normalVar, positionVar); + vertexLinker.setOutputParams(lightProcedure, "tTotalLight"); + } + } + vertexLinker.addProcedure(_passLightingProcedure); + vertexLinker.setInputParams(_passLightingProcedure, "tTotalLight"); + + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + fragmentLinker.declareVariable("tColor"); + var outputProcedure:Procedure = opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure; + fragmentLinker.addProcedure(outputProcedure); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + + if (alphaTest > 0) { + outputProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure; + fragmentLinker.addProcedure(outputProcedure, "tColor"); + fragmentLinker.setOutputParams(outputProcedure, "tColor"); + } + fragmentLinker.addProcedure(_mulLightingProcedure, "tColor"); + + fragmentLinker.varyings = vertexLinker.varyings; + program = new ShaderProgram(vertexLinker, fragmentLinker); + + program.upload(camera.context3D); + programs[key] = program; + } + return program; + } + + private function getDrawUnit(program:ShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource, lights:Vector., lightsLength:int):DrawUnit { + // Buffers + var object:Object3D = surface.object; + + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL); + + // Draw call + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + // Streams + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + + // Constants + object.setTransformConstants(drawUnit, surface, program.vertexShader, camera); + drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform); + drawUnit.setVertexConstantsFromVector(program.vertexShader.getVariableIndex("cAmbientColor"), camera.ambient, 1); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha); + + if (lightsLength > 0) { + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aNormal"), normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]); + + var i:int; + var light:Light3D; + + var transform:Transform3D; + var rScale:Number; + for (i = 0; i < lightsLength; i++) { + light = lights[i]; + transform = light.lightToObjectTransform; + var len:Number = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k); + if (light is DirectionalLight) { + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Direction"), -transform.c/len, -transform.g/len, -transform.k/len); + } else if (light is OmniLight) { + var omni:OmniLight = light as OmniLight; + rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i); + rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j); + rScale += len; + rScale /= 3; + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Position"), transform.d, transform.h, transform.l); + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Radius"), 1, omni.attenuationEnd*rScale - omni.attenuationBegin*rScale, omni.attenuationBegin*rScale); + } else if (light is SpotLight) { + var spot:SpotLight = light as SpotLight; + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Position"), transform.d, transform.h, transform.l); + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Axis"), -transform.c/len, -transform.g/len, -transform.k/len); + rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i); + rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j); + rScale += len; + rScale /= 3; + + var falloff:Number = Math.cos(spot.falloff*0.5); + var hotspot:Number = Math.cos(spot.hotspot*0.5); + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Radius"), spot.attenuationEnd*rScale - spot.attenuationBegin*rScale, spot.attenuationBegin*rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff); + } + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Color"), light.red, light.green, light.blue); + } + } + + // Textures + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sDiffuse"), diffuseMap._texture); + if (opacityMap != null) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sOpacity"), opacityMap._texture); + } + return drawUnit; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + if (diffuseMap == null || diffuseMap._texture == null || opacityMap != null && opacityMap._texture == null) return; + + var object:Object3D = surface.object; + + // Buffers + var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION); + var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL); + + if (positionBuffer == null || uvBuffer == null || normalsBuffer == null) return; + + // Program + var light:Light3D; + var materialKey:String = ""; + for (var i:int = 0; i < lightsLength; i++) { + light = lights[i]; + materialKey += light.lightID; + } + + // Refresh programs for this context. + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + programsCache = caches[cachedContext3D]; + if (programsCache == null) { + programsCache = new Dictionary(); + caches[cachedContext3D] = programsCache; + } + } + + var optionsPrograms:Dictionary = programsCache[object.transformProcedure]; + if (optionsPrograms == null) { + optionsPrograms = new Dictionary(false); + programsCache[object.transformProcedure] = optionsPrograms; + } + + var program:ShaderProgram; + var drawUnit:DrawUnit; + // Opaque passOpaque pass + if (opaquePass && alphaThreshold <= alpha) { + if (alphaThreshold > 0) { + // Alpha test + // use opacityMap if it is presented + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, lights, lightsLength); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength); + } else { + // do not use opacityMap at all + program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, lights, lightsLength); + drawUnit = getDrawUnit(program, camera, surface, geometry, null, lights, lightsLength); + } + // Use z-buffer within DrawCall, draws without blending + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE); + } + // Transparent pass + if (transparentPass && alphaThreshold > 0 && alpha > 0) { + // use opacityMap if it is presented + if (alphaThreshold <= alpha && !opaquePass) { + // Alpha threshold + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 2, lights, lightsLength); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength); + } else { + // There is no Alpha threshold or check z-buffer by previous pass + program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, lights, lightsLength); + drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength); + } + // Do not use z-buffer, draws with blending + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT); + } + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/CommandType.as b/src/alternativa/engine3d/materials/compiler/CommandType.as new file mode 100644 index 0000000..4016aa1 --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/CommandType.as @@ -0,0 +1,59 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + /** + * @private + */ + public class CommandType { + + public static const MOV:uint = 0x00; + public static const ADD:uint = 0x01; + public static const SUB:uint = 0x02; + public static const MUL:uint = 0x03; + public static const DIV:uint = 0x04; + public static const RCP:uint = 0x05; + public static const MIN:uint = 0x06; + public static const MAX:uint = 0x07; + public static const FRC:uint = 0x08; + public static const SQT:uint = 0x09; + public static const RSQ:uint = 0x0a; + public static const POW:uint = 0x0b; + public static const LOG:uint = 0x0c; + public static const EXP:uint = 0x0d; + public static const NRM:uint = 0x0e; + public static const SIN:uint = 0x0f; + public static const COS:uint = 0x10; + public static const CRS:uint = 0x11; + public static const DP3:uint = 0x12; + public static const DP4:uint = 0x13; + public static const ABS:uint = 0x14; + public static const NEG:uint = 0x15; + public static const SAT:uint = 0x16; + public static const M33:uint = 0x17; + public static const M44:uint = 0x18; + public static const M34:uint = 0x19; + public static const KIL:uint = 0x27; + public static const TEX:uint = 0x28; + public static const SGE:uint = 0x29; + public static const SLT:uint = 0x2a; + public static const SEQ:uint = 0x2c; + public static const SNE:uint = 0x2d; + public static const DEF:uint = 0x80; + public static const CAL:uint = 0x81; + public static const COMMAND_NAMES:Vector. = Vector.( + ["mov", "add", "sub", "mul", "div", "rcp", "min", "max", "frc", "sqt", "rsq", "pow", "log", "exp", + "nrm", "sin", "cos", "crs", "dp3", "dp4", "abs", "neg", "sat", "m33", "m44", "m34","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26", "kil", "tex", "sge", "slt", "2b", "seq", "sne"] + ); + public function CommandType() { + } + } +} \ No newline at end of file diff --git a/src/alternativa/engine3d/materials/compiler/DestinationVariable.as b/src/alternativa/engine3d/materials/compiler/DestinationVariable.as new file mode 100644 index 0000000..a9f9736 --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/DestinationVariable.as @@ -0,0 +1,71 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import flash.utils.ByteArray; + + /** + * @private + */ + public class DestinationVariable extends Variable { + + public function DestinationVariable(source:String) { + var strType:String = source.match(/[tovi]/)[0]; + index = parseInt(source.match(/\d+/)[0], 10); + var swizzle:Array = source.match(/\.[xyzw]{1,4}/); + var regmask:uint; + var maskmatch:String = swizzle ? swizzle[0] : null; + if (maskmatch != null) { + regmask = 0; + var cv:int; + var maskLength:uint = maskmatch.length; + // If first char is point, then skip + for (var i:int = 1; i < maskLength; i++) { + cv = maskmatch.charCodeAt(i) - X_CHAR_CODE; + if (cv == -1) cv = 3; + regmask |= 1 << cv; + } + } else { + regmask = 0xf; // id swizzle or mask + } + lowerCode = (regmask << 16) | index; + + switch(strType){ + case "t": + lowerCode |= 0x2000000; + type = 2; + break; + case "o": + lowerCode |= 0x3000000; + type = 3; + break; + case "v": + lowerCode |= 0x4000000; + type = 4; + break; + case "i": + lowerCode |= 0x6000000; + type = 6; + break; + default : + throw new ArgumentError("Wrong destination register type, must be \"t\" or \"o\" or \"v\", var = " + source); + break; + } + } + + override public function writeToByteArray(byteCode:ByteArray, newIndex:int, newType:int, offset:int = 0):void { + byteCode.position = position + offset; + + byteCode.writeUnsignedInt((lowerCode & ~(0xf00ffff)) | newIndex | (newType << 24)); + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/Linker.as b/src/alternativa/engine3d/materials/compiler/Linker.as new file mode 100644 index 0000000..097c54a --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/Linker.as @@ -0,0 +1,423 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3DProgramType; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * @private + * Dynamic shader linker + */ + public class Linker { + + /** + * Data after linking. + */ + public var data:ByteArray = null; + + /** + * Number of used slots. + */ + public var slotsCount:int = 0; + /** + * Number of lines of the shader code. + */ + public var commandsCount:int = 0; + + /** + * Linker type. Can be vertex of fragment. + */ + public var type:String; + + private var procedures:Vector. = new Vector.(); + + /** + * @private + * Variables after linking. + */ + alternativa3d var _linkedVariables:Object; + + // Dictionary of temporary variables at this linker. Key is a name of variable, value is a variable. + private var _localVariables:Object = new Object(); + + // Key - procedure, value - array of strings. + private var _inputParams:Dictionary = new Dictionary(); + // Key - procedure, value - array of strings. + private var _outputParams:Dictionary = new Dictionary(); + + // Counters of variables by types + private var _locals:Vector. = new Vector.(6, true); + + private var samplers:Object = new Object(); + + private var _varyings:Object = new Object(); + + /** + * Creates a new Linker instance. + * + * @param programType Type of shader. + */ + public function Linker(programType:String) { + type = programType; + } + + /** + * Clears a content. + */ + public function clear():void { + data = null; + _locals[0] = _locals[1] = _locals[2] = _locals[3] = _locals[4] = _locals[5] = 0; + procedures.length = 0; + _varyings = new Object(); + samplers = new Object(); + + commandsCount = 0; + slotsCount = 0; + _linkedVariables = null; + + _inputParams = new Dictionary(); + _outputParams = new Dictionary(); + } + + /** + * Adds a new shader procedure. + * + * @param procedure Procedure to add. + * + * @see Procedure + */ + public function addProcedure(procedure:Procedure, ...args):void { + for each(var v:Variable in procedure.variablesUsages[VariableType.VARYING]) { + if (v == null) continue; + var nv:Variable = _varyings[v.name] = new Variable(); + nv.name = v.name; + nv.type = v.type; + nv.index = -1; + } + procedures.push(procedure); + _inputParams[procedure] = args; + data = null; + } + + /** + * Declaration of variable of given type. + * + * @param name Name of variable + * @param type Type of variable. Should be one of the VariableType constants. The default value is Temporary variable. + * + * @see VariableType + */ + public function declareVariable(name:String, type:uint = 2):void { + var v:Variable = new Variable(); + v.index = -1; + v.type = type; + v.name = name; + _localVariables[name] = v; + if (v.type == VariableType.VARYING) { + _varyings[v.name] = v; + } + data = null; + } + + public function declareSampler(output:String, uv:String, sampler:String, options:String):void { + if (_localVariables[uv] == null) { + throw new ArgumentError("Undefined variable " + uv); + } + + if (_localVariables[sampler] == null) { + throw new ArgumentError("Undefined variable " + sampler); + } + + if (_localVariables[output] == null) { + declareVariable(output, 2); + } + data = null; + } + + /** + * Setting of input parameters of procedure. + * + * @param procedure A procedure to which parameters will be set. + * @param args Names of variables, separated by the comma, that are passed into the procedure. + * Variables must be previously declared, using the method declareVariable(). + * + * @see #declareVariable() + */ + public function setInputParams(procedure:Procedure, ...args):void { + _inputParams[procedure] = args; + data = null; + } + + /** + * Setting of output parameters of procedure. + * + * @param procedure A procedure to which parameters will be set. + * @param args Names of variables, separated by the comma, that are passed into the procedure. + * Variables must be previously declared, using the method declareVariable(). + * + * @see #declareVariable() + */ + public function setOutputParams(procedure:Procedure, ...args):void { + _outputParams[procedure] = args; + data = null; + } + + /** + * Returns of index of variable after the linking. + * + * @param name Name of variable. + * @return Its index for sending to Context3D + */ + public function getVariableIndex(name:String):int { + if (_linkedVariables == null) throw new Error("Not linked"); + var variable:Variable = _linkedVariables[name]; + if (variable == null) { + throw new Error('Variable "' + name + '" not found'); + } + return variable.index; + } + + /** + * Returns index of variable or -1 there is no variable with such name. + */ + public function findVariable(name:String):int { + if (_linkedVariables == null) throw new Error("Has not linked"); + var variable:Variable = _linkedVariables[name]; + if (variable == null) { + return -1; + } + return variable.index; + } + + /** + * Returns the existence of this variable in linked code. + * @param name Name of variable + */ + public function containsVariable(name:String):Boolean { + if (_linkedVariables == null) throw new Error("Not linked"); + return _linkedVariables[name] != null; + } + + /** + * Linking of procedures to one shader. + */ + public function link():void { + if (data != null) return; + + var v:Variable; + var variables:Object = _linkedVariables = new Object(); + var p:Procedure; + var i:int, j:int; + var nv:Variable; + for each (v in _localVariables) { + nv = variables[v.name] = new Variable(); + nv.index = -1; + nv.type = v.type; + nv.name = v.name; + nv.size = v.size; + } + data = new ByteArray(); + data.endian = Endian.LITTLE_ENDIAN; + data.writeByte(0xa0); + data.writeUnsignedInt(0x1); // AGAL version, big endian, bit pattern will be 0x01000000 + data.writeByte(0xa1); // tag program id + data.writeByte((type == Context3DProgramType.FRAGMENT) ? 1 : 0); // vertex or fragment + + commandsCount = 0; + slotsCount = 0; + + _locals[0] = 0; + _locals[1] = 0; + _locals[2] = 0; + _locals[3] = 0; + _locals[4] = 0; + _locals[5] = 0; + // First iteration - collecting of variables. + for each (p in procedures) { + var iLength:int = p.variablesUsages.length; + _locals[1] += p.reservedConstants; + for (i = 0; i < iLength; i++) { + var vector:Vector. = p.variablesUsages[i]; + var jLength:int = vector.length; + for (j = 0; j < jLength; j++) { + v = vector[j]; + if (v == null || v.name == null) continue; + if (v.name == null && i != 2 && i != 6 && i != 3) { + throw new Error("Linkage error: Noname variable. Procedure = " + p.name + ", type = " + i.toString() + ", index = " + j.toString()); + } + nv = variables[v.name] = new Variable(); + nv.index = -1; + nv.type = v.type; + nv.name = v.name; + nv.size = v.size; + } + } + } + + for each (p in procedures) { + // Changing of inputs + var offset:int = data.length; + data.position = data.length; + data.writeBytes(p.byteCode, 0, p.byteCode.length); + var input:Array = _inputParams[p]; + var output:Array = _outputParams[p]; + var param:String; + var numParams:int; + if (input != null) { + numParams = input.length; + for (j = 0; j < numParams; j++) { + param = input[j]; + v = variables[param]; + if (v == null) { + throw new Error("Input parameter not set. paramName = " + param); + } + if (p.variablesUsages[6].length > j) { + var inParam:Variable = p.variablesUsages[6][j]; + if (inParam == null) { + throw new Error("Input parameter set, but not exist in code. paramName = " + param + ", register = i" + j.toString()); + } + if (v.index < 0) { + v.index = _locals[v.type]; + _locals[v.type] += v.size; + } + while (inParam != null) { + inParam.writeToByteArray(data, v.index, v.type, offset); + inParam = inParam.next; + } + } + + } + } + if (output != null) { + // Output parameters + numParams = output.length; + for (j = 0; j < numParams; j++) { + param = output[j]; + v = variables[param]; + if (v == null) { + if (j == 0 && (i == procedures.length - 1)) { + // Output variable + continue; + } + throw new Error("Output parameter have not declared. paramName = " + param); + } + if (v.index < 0) { + if (v.type != 2) { + throw new Error("Wrong output type:" + VariableType.TYPE_NAMES[v.type]); + } + v.index = _locals[v.type]; + _locals[v.type] += v.size; + } + var outParam:Variable = p.variablesUsages[3][j]; + if (outParam == null) { + throw new Error("Output parameter set, but not exist in code. paramName = " + param + ", register = i" + j.toString()); + } + while (outParam != null) { + outParam.writeToByteArray(data, v.index, v.type, offset); + outParam = outParam.next; + } + } + } + var vars:Vector. = p.variablesUsages[2]; + for (j = 0; j < vars.length; j++) { + v = vars[j]; + if (v == null) continue; + while (v != null) { + v.writeToByteArray(data, v.index + _locals[2], VariableType.TEMPORARY, offset); + v = v.next; + } + } + + resolveVariablesUsages(data, variables, p.variablesUsages[0], VariableType.ATTRIBUTE, offset); + resolveVariablesUsages(data, variables, p.variablesUsages[1], VariableType.CONSTANT, offset); + resolveVariablesUsages(data, _varyings, p.variablesUsages[4], VariableType.VARYING, offset); + resolveVariablesUsages(data, variables, p.variablesUsages[5], VariableType.SAMPLER, offset); + + commandsCount += p.commandsCount; + slotsCount += p.slotsCount; + } + } + private function resolveVariablesUsages(code:ByteArray, variables:Object, variableUsages:Vector., type:uint, offset:int):void { + for (var j:int = 0; j < variableUsages.length; j++) { + var vUsage:Variable = variableUsages[j]; + if (vUsage == null) continue; + if (vUsage.isRelative) continue; + var variable:Variable = variables[vUsage.name]; + if (variable.index < 0) { + variable.index = _locals[type]; + _locals[type] += variable.size; + } + while (vUsage != null) { + vUsage.writeToByteArray(code, variable.index, variable.type, offset); + vUsage = vUsage.next; + } + } + } + + /** + * Returns description of procedures: name, size, input and output parameters. + * @return + */ + public function describeLinkageInfo():String { + var str:String; + var result:String = "LINKER:\n"; + var totalCodes:uint = 0; + var totalCommands:uint = 0; + for (var i:int = 0; i < procedures.length; i++) { + var p:Procedure = procedures[i]; + if (p.name != null) { + result += p.name + "("; + } else { + result += "#" + i.toString() + "("; + } + var args:* = _inputParams[p]; + if (args != null) { + for each (str in args) { + result += str + ","; + } + result = result.substr(0, result.length - 1); + } + result += ")"; + args = _outputParams[p]; + if (args != null) { + result += "->("; + for each (str in args) { + result += str + ","; + } + result = result.substr(0, result.length - 1); + result += ")"; + } + result += " [IS:" + p.slotsCount.toString() + ", CMDS:" + p.commandsCount.toString() + "]\n"; + totalCodes += p.slotsCount; + totalCommands += p.commandsCount; + } + result += "[IS:" + totalCodes.toString() + ", CMDS:" + totalCommands.toString() + "]\n"; + return result; + } + + public function get varyings():Object { + return _varyings; + } + + public function set varyings(value:Object):void { + _varyings = value; + data = null; + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/Procedure.as b/src/alternativa/engine3d/materials/compiler/Procedure.as new file mode 100644 index 0000000..063727e --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/Procedure.as @@ -0,0 +1,487 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3DProgramType; + + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * @private + * Shader procedure + */ + public class Procedure { + + // Name of procedure + public var name:String; + + alternativa3d static const crc32Table:Vector. = createCRC32Table(); + + private static function createCRC32Table():Vector. { + var crc_table:Vector. = new Vector.(256); + var crc:uint, i:int, j:int; + for (i = 0; i < 256; i++) { + crc = i; + for (j = 0; j < 8; j++) + crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320 : crc >> 1; + + crc_table[i] = crc; + } + return crc_table; + } + + alternativa3d var crc32:uint = 0; + + /** + * Code of procedure. + */ + public var byteCode:ByteArray = new ByteArray(); + public var variablesUsages:Vector.> = new Vector.>(); + + /** + * Number of instruction slots in a procedure. + */ + public var slotsCount:int = 0; + + /** + * Number of strings in a procedure. + */ + public var commandsCount:int = 0; + + alternativa3d var reservedConstants:uint = 0; + + /** + * Creates a new Procedure instance. + * + * @param array Array of AGAL strings + */ + public function Procedure(array:Array = null, name:String = null) { + byteCode.endian = Endian.LITTLE_ENDIAN; + this.name = name; + if (array != null) { + compileFromArray(array); + } + } + + public function getByteCode(type:String):ByteArray { + var result:ByteArray = new ByteArray(); + result.endian = Endian.LITTLE_ENDIAN; + result.writeByte(0xa0); + result.writeUnsignedInt(0x1); // AGAL version, big endian, bit pattern will be 0x01000000 + result.writeByte(0xa1); // tag program id + result.writeByte((type == Context3DProgramType.FRAGMENT) ? 1 : 0); // vertex or fragment + result.writeBytes(byteCode); + return result; + } + + private function addVariableUsage(v:Variable):void { + var vars:Vector. = variablesUsages[v.type]; + var index:int = v.index; + if (index >= vars.length) { + vars.length = index + 1; + } else { + v.next = vars[index]; + } + vars[index] = v; + } + + /** + * Sets name and size of variable + * + * @param type Type of variable. One of VariableType constants. + * @param index Index of variable at shader code. + * @param name Assigned variable name. + * @param size Size of variable in vectors. + * + * @see VariableType + */ + public function assignVariableName(type:uint, index:uint, name:String, size:uint = 1):void { + var v:Variable = variablesUsages[type][index]; + while (v != null) { + v.size = size; + v.name = name; + v = v.next; + } + } + + /** + * Compiles shader from the string. + */ + public function compileFromString(source:String):void { + var commands:Array = source.split("\n"); + compileFromArray(commands); + } + + /** + * Compiles shader from the array of strings. + */ + public function compileFromArray(source:Array):void { + for (var i:int = 0; i < 7; i++) { + variablesUsages[i] = new Vector.(); + } + byteCode.length = 0; + commandsCount = 0; + slotsCount = 0; + + var declarationStrings:Vector. = new Vector.(); + var count:int = source.length; + for (i = 0; i < count; i++) { + var cmd:String = source[i]; + var declaration:Array = cmd.match(/# *[acvs]\d{1,3} *= *[a-zA-Z0-9_]*/i); + if (declaration != null && declaration.length > 0) { + declarationStrings.push(declaration[0]); + } else { + writeCommand(cmd); + } + } + for (i = 0,count = declarationStrings.length; i < count; i++) { + var decArray:Array = declarationStrings[i].split("="); + var regType:String = decArray[0].match(/[acvs]/i); + var varIndex:int = int(decArray[0].match(/\d{1,3}/i)); + var varName:String = decArray[1].match(/[a-zA-Z0-9]*/i); + switch (regType.toLowerCase()) { + case "a": + assignVariableName(VariableType.ATTRIBUTE, varIndex, varName); + break; + case "c": + assignVariableName(VariableType.CONSTANT, varIndex, varName); + break; + case "v": + assignVariableName(VariableType.VARYING, varIndex, varName); + break; + case "s": + assignVariableName(VariableType.SAMPLER, varIndex, varName); + break; + } + } + crc32 = createCRC32(byteCode); + } + + public function assignConstantsArray(registersCount:uint = 1):void { + reservedConstants = registersCount; + } + + private function writeCommand(source:String):void { + var commentIndex:int = source.indexOf("//"); + if (commentIndex >= 0) { + source = source.substr(0, commentIndex); + } + // mov vt0, v0 + // mov vt0, v0, vc1 + // mov vt0.xy, a0.xy, vc1.xy + // mov vt0.xy, a0.xy, vc1.xy + // mov vt0.xy, v0[va1.x + 2], vc[va0.x + 2] + // mov op, v0[va1.x + 2], vc[va0.x + 2] + // tex t0, v0, s0 <2d, linear> + + // Errors: + //1) Merged commands + //2) Syntax errors + //-- incorrect number of operands + //-- unknown commands + //-- unknown registers + //-- unknown constructions + //3) Using of unwritable registers + //-- in vertex shader (va0, c0, s0); + //-- in fragment shader (v0, va0, c0, s0); + //4) Using of unreadable registers + //-- in vertex shader (v0, s0); + //-- in fragment shader (va0); + //5) Deny write into the input registers + //6) Mismatch the size of types of registers + //7) Relative addressing in the fragment shader is not possible + //-- You can not use it for recording + //-- Offset is out of range [0..255] + //8) Flow errors + //-- unused variable + //-- using of uninitialized variable + //-- using of partially uninitialized variable + //-- function is not return value + //9) Restrictions + //-- too many commands + //-- too many constants + //-- too many textures + //-- too many temporary variables + //-- too many interpolated values + // You can not use kil in fragment shader + + var operands:Array = source.match(/[A-Za-z]+(((\[.+\])|(\d+))(\.[xyzw]{1,4})?(\ *\<.*>)?)?/g); + + // It is possible not use the input parameter. It is optimization of the linker + // Determine the size of constant + + if (operands.length < 2) { + return; + } + var opCode:String = operands[0]; + var destination:Variable; + var source1:SourceVariable; + var source2:Variable; + if (opCode == "kil") { + source1 = new SourceVariable(operands[1]); + } else { + destination = new DestinationVariable(operands[1]); + source1 = new SourceVariable(operands[2]); + addVariableUsage(destination); + } + addVariableUsage(source1); + + var type:uint; + switch (opCode) { + case "mov": + type = CommandType.MOV; + slotsCount++; + break; + case "add": + type = CommandType.ADD; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "sub": + type = CommandType.SUB; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "mul": + type = CommandType.MUL; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "div": + type = CommandType.DIV; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "rcp": + type = CommandType.RCP; + slotsCount++; + break; + case "min": + type = CommandType.MIN; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "max": + type = CommandType.MAX; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "frc": + type = CommandType.FRC; + slotsCount++; + break; + case "sqt": + type = CommandType.SQT; + slotsCount++; + break; + case "rsq": + type = CommandType.RSQ; + slotsCount++; + break; + case "pow": + type = CommandType.POW; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount += 3; + break; + case "log": + type = CommandType.LOG; + slotsCount++; + break; + case "exp": + type = CommandType.EXP; + slotsCount++; + break; + case "nrm": + type = CommandType.NRM; + slotsCount += 3; + break; + case "sin": + type = CommandType.SIN; + slotsCount += 8; + break; + case "cos": + type = CommandType.COS; + slotsCount += 8; + break; + case "crs": + type = CommandType.CRS; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount += 2; + break; + case "dp3": + type = CommandType.DP3; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "dp4": + type = CommandType.DP4; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "abs": + type = CommandType.ABS; + slotsCount++; + break; + case "neg": + type = CommandType.NEG; + slotsCount++; + break; + case "sat": + type = CommandType.SAT; + slotsCount++; + break; + case "m33": + type = CommandType.M33; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount += 3; + break; + case "m44": + type = CommandType.M44; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount += 4; + break; + case "m34": + type = CommandType.M34; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount += 3; + break; + case "kil": + type = CommandType.KIL; + slotsCount++; + break; + case "tex": + type = CommandType.TEX; + source2 = new SamplerVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "sge": + type = CommandType.SGE; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "slt": + type = CommandType.SLT; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "seq": + type = CommandType.SEQ; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + case "sne": + type = CommandType.SNE; + source2 = new SourceVariable(operands[3]); + addVariableUsage(source2); + slotsCount++; + break; + default: + break; + } + // Fill of byteCode of command + byteCode.writeUnsignedInt(type); + if (destination != null) { + destination.position = byteCode.position; + byteCode.writeUnsignedInt(destination.lowerCode); + } else { + byteCode.writeUnsignedInt(0); + } + source1.position = byteCode.position; + if (source1.relative != null) { + addVariableUsage(source1.relative); + source1.relative.position = byteCode.position; + } + byteCode.writeUnsignedInt(source1.lowerCode); + byteCode.writeUnsignedInt(source1.upperCode); + if (source2 != null) { + var s2v:SourceVariable = source2 as SourceVariable; + source2.position = byteCode.position; + if (s2v != null && s2v.relative != null) { + addVariableUsage(s2v.relative); + s2v.relative.position = s2v.position; + } + byteCode.writeUnsignedInt(source2.lowerCode); + byteCode.writeUnsignedInt(source2.upperCode); + } else { + byteCode.position = (byteCode.length += 8); + } + commandsCount++; + } + + /** + * Creates and returns an instance of procedure from array of strings. + */ + public static function compileFromArray(source:Array, name:String = null):Procedure { + var proc:Procedure = new Procedure(source, name); + return proc; + } + + /** + * Creates and returns an instance of procedure from string. + */ + public static function compileFromString(source:String, name:String = null):Procedure { + var proc:Procedure = new Procedure(null, name); + proc.compileFromString(source); + return proc; + } + + /** + * Create an instance of procedure. + */ + public function newInstance():Procedure { + var res:Procedure = new Procedure(); + res.byteCode = this.byteCode; + res.variablesUsages = this.variablesUsages; + res.slotsCount = this.slotsCount; + res.reservedConstants = this.reservedConstants; + res.commandsCount = this.commandsCount; + res.name = name; + return res; + } + + + + + alternativa3d static function createCRC32(byteCode:ByteArray):uint { + byteCode.position = 0; + var len:uint = byteCode.length; + var crc:uint = 0xFFFFFFFF; + while (len--) { + var byte:int = byteCode.readByte(); + crc = crc32Table[(crc ^ byte) & 0xFF] ^ (crc >> 8); + } + return crc ^ 0xFFFFFFFF; + } + } + +} diff --git a/src/alternativa/engine3d/materials/compiler/RelativeVariable.as b/src/alternativa/engine3d/materials/compiler/RelativeVariable.as new file mode 100644 index 0000000..78cb232 --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/RelativeVariable.as @@ -0,0 +1,66 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler +{ + import flash.utils.ByteArray; + + /** + * @private + */ + public class RelativeVariable extends Variable { + + public function RelativeVariable(source:String) { + var relname:Array = source.match( /[A-Za-z]/g ); + index = parseInt(source.match(/\d+/g)[0], 10); + switch(relname[0]){ + case "a": + type = VariableType.ATTRIBUTE; + break; + case "c": + type = VariableType.CONSTANT; + break; + case "t": + type = VariableType.TEMPORARY; + break; + case "i": + type = VariableType.INPUT; + break; + } + var selmatch:Array = source.match(/(\.[xyzw]{1,1})/); + if (selmatch.length == 0) { + throw new Error("error: bad index register select"); + } + var relsel:int = selmatch[0].charCodeAt(1) - X_CHAR_CODE; + if (relsel == -1) relsel = 3; + var relofs:Array = source.match(/\+\d{1,3}/g); + var reloffset:int = 0; + if (relofs.length > 0) { + reloffset = parseInt(relofs[0], 10); + } + if (reloffset < 0 || reloffset > 255) { + throw new Error("Error: index offset " + reloffset + " out of bounds. [0..255]"); + } + + lowerCode = reloffset << 16 | index; + upperCode |= type << 8; + upperCode |= relsel << 16; + upperCode |= 1 << 31; + } + + override public function writeToByteArray(byteCode:ByteArray, newIndex:int, newType:int, offset:int = 0):void { + byteCode.position = position + offset; + byteCode.writeShort(newIndex); + byteCode.position = position + offset + 5; + byteCode.writeByte(newType); + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/SamplerVariable.as b/src/alternativa/engine3d/materials/compiler/SamplerVariable.as new file mode 100644 index 0000000..2ea3dca --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/SamplerVariable.as @@ -0,0 +1,99 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import flash.utils.ByteArray; + + /** + * @private + */ + public class SamplerVariable extends Variable { + + public function SamplerVariable(source:String) { + var strType:String = String(source.match(/[si]/g)[0]); + switch(strType){ + case "s": + upperCode = VariableType.SAMPLER; + break; + case "i": + upperCode = VariableType.INPUT; + break; + } + index = parseInt(source.match(/\d+/g)[0], 10); + lowerCode = index; + var optsi:int = source.search(/<.*>/g); + var opts:Array; + if (optsi != -1) { + opts = source.substring(optsi).match(/(\w+)/g); + } + type = upperCode; + //upperInt = 5; // type 5 + var optsLength:uint = opts.length; + for (var i:int = 0; i < optsLength; i++) { + var op:String = opts[i]; + switch(op){ + case "2d": + upperCode &= ~(0xf000); + break; + case "3d": + upperCode &= ~(0xf000); + upperCode |= 0x2000; + break; + case "cube": + upperCode &= ~(0xf000); + upperCode |= 0x1000; + break; + case "mipnearest": + upperCode &= ~(0xf000000); + upperCode |= 0x1000000; + break; + case "miplinear": + upperCode &= ~(0xf000000); + upperCode |= 0x2000000; + break; + case "mipnone": + case "nomip": + upperCode &= ~(0xf000000); + break; + case "nearest": + upperCode &= ~(0xf0000000); + break; + case "linear": + upperCode &= ~(0xf0000000); + upperCode |= 0x10000000; + break; + case "centroid": + upperCode |= 0x100000000; + break; + case "single": + upperCode |= 0x200000000; + break; + case "depth": + upperCode |= 0x400000000; + break; + case "repeat": + case "wrap": + upperCode &= ~(0xf00000); + upperCode |= 0x100000; + break; + case "clamp": + upperCode &= ~(0xf00000); + break; + } + } + } + + override public function writeToByteArray(byteCode:ByteArray, newIndex:int, newType:int, offset:int = 0):void { + super.writeToByteArray(byteCode, newIndex, newType, offset); + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/SourceVariable.as b/src/alternativa/engine3d/materials/compiler/SourceVariable.as new file mode 100644 index 0000000..d144ded --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/SourceVariable.as @@ -0,0 +1,104 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import flash.utils.ByteArray; + + /** + * @private + */ + public class SourceVariable extends Variable { + + public var relative:RelativeVariable; + + override public function get size():uint { + if(relative){ + return 0; + } + return super.size; + } + + public function SourceVariable(source:String) { + var strType:String = String(source.match(/[catsoiv]/g)[0]); + + var regmask:uint; + + var relreg:Array = source.match( /\[.*\]/g ); + var isRel:Boolean = relreg.length > 0; + if(isRel){ + source = source.replace(relreg[0], "0"); + } else { + index = parseInt(source.match(/\d+/g)[0], 10); + } + + var swizzle:Array = source.match(/\.[xyzw]{1,4}/); + + var maskmatch:String = swizzle ? swizzle[0] : null; + if (maskmatch) { + regmask = 0; + var cv:int; + var maskLength:uint = maskmatch.length; + for (var i:int = 1; i < maskLength; i++) { + cv = maskmatch.charCodeAt(i) - X_CHAR_CODE; + if (cv == -1) cv = 3; + regmask |= cv << ( ( i - 1 ) << 1 ); + } + for ( ; i <= 4; i++ ) + regmask |= cv << ( ( i - 1 ) << 1 ); // repeat last + } else { + regmask = 0xe4; // id swizzle or mask + } + lowerCode = (regmask << 24) | index; + + switch(strType){ + case "a": + type = VariableType.ATTRIBUTE; + break; + case "c": + type = VariableType.CONSTANT; + break; + case "t": + type = VariableType.TEMPORARY; + break; + case "o": + type = VariableType.OUTPUT; + break; + case "v": + type = VariableType.VARYING; + break; + case "i": + type = VariableType.INPUT; + break; + default : + throw new ArgumentError('Wrong source register type, must be "a" or "c" or "t" or "o" or "v" or "i", var = ' + source); + break; + } + upperCode = type; + if (isRel) { + relative = new RelativeVariable(relreg[0]); + lowerCode |= relative.lowerCode; + upperCode |= relative.upperCode; + isRelative = true; + } + } + + override public function writeToByteArray(byteCode:ByteArray, newIndex:int, newType:int, offset:int = 0):void { + if (relative == null) { + super.writeToByteArray(byteCode, newIndex, newType, offset); + } else { + byteCode.position = position + 2; + } + byteCode.position = position + offset + 4; + byteCode.writeByte(newType); + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/Variable.as b/src/alternativa/engine3d/materials/compiler/Variable.as new file mode 100644 index 0000000..eb6c3a6 --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/Variable.as @@ -0,0 +1,71 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + import flash.utils.ByteArray; + + /** + * @private + */ + public class Variable { + + protected static const X_CHAR_CODE:Number = "x".charCodeAt(0); + + public var name:String; + // Index of register. + public var index:int; + // Type of VariableType register. + public var type:uint; + // Location of calling for variable in byte code. + public var position:uint = 0; + // Next calling for variable with the same index. + public var next:Variable; + + public var lowerCode:uint; + public var upperCode:uint; + public var isRelative:Boolean; + private var _size:uint = 1; + + private static var collector:Variable; + + public static function create():Variable { + if(collector == null){ + collector = new Variable(); + } + var output:Variable = collector; + collector = collector.next; + output.next = null; + return output; + } + + public function dispose():void { + next = collector; + collector = this; + } + + public function Variable() { + } + + public function get size():uint { + return _size; + } + + public function set size(value:uint):void { + _size = value; + } + + public function writeToByteArray(byteCode:ByteArray, newIndex:int, newType:int, offset:int = 0):void { + byteCode.position = position + offset; + byteCode.writeShort(newIndex); + } + + } +} diff --git a/src/alternativa/engine3d/materials/compiler/VariableType.as b/src/alternativa/engine3d/materials/compiler/VariableType.as new file mode 100644 index 0000000..43e25c8 --- /dev/null +++ b/src/alternativa/engine3d/materials/compiler/VariableType.as @@ -0,0 +1,55 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.materials.compiler { + + /** + * @private + * Types of shader variables + */ + public class VariableType { + + /** + * Input attribute of vertex. + */ + public static const ATTRIBUTE:uint = 0; + /** + * Constant. + */ + public static const CONSTANT:uint = 1; + /** + * Temporary variable. + */ + public static const TEMPORARY:uint = 2; + /** + * Output variable. + */ + public static const OUTPUT:uint = 3; + /** + * Interpolated variable. + */ + public static const VARYING:uint = 4; + /** + * Texture. + */ + public static const SAMPLER:uint = 5; + /** + * Input variable. + */ + public static const INPUT:uint = 6; + + public static const TYPE_NAMES:Vector. = Vector.( + ["attribute", "constant", "temporary", "output", "varying", "sampler", "input"] + ); + public function VariableType() { + } + + } +} diff --git a/src/alternativa/engine3d/objects/AnimSprite.as b/src/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..e2f3b8b --- /dev/null +++ b/src/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,144 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.Material; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Animated sprite. Instances of Material use as frames for animation. Playing the animation can be done through changing frame property. + * I. e. + * animSprite.frame++;
+ * camera.render(context3D)
+ */ + public class AnimSprite extends Sprite3D { + private var _materials:Vector.; + private var _frame:int = 0; + private var _loop:Boolean = false; + + /** + * Creates a new AnimSprite instance. + * @param width Width. + * @param height Height. + * @param materials List of materials. + * @param loop If true, Loops animation. + * @param frame Current frame. + * @see alternativa.engine3d.materials.Material + */ + public function AnimSprite(width:Number, height:Number, materials:Vector. = null, loop:Boolean = false, frame:int = 0) { + super(width, height); + _materials = materials; + _loop = loop; + this.frame = frame; + } + + /** + * List of materials. + */ + public function get materials():Vector. { + return _materials; + } + + /** + * @private + */ + public function set materials(value:Vector.):void { + _materials = value; + if (value != null) { + frame = _frame; + } else { + material = null; + } + } + + /** + * In case of true, when frame takes value greater than length of materials list, it switches to begin. + * Otherwise the value of frame property will equal materials.length-1 after setting greater value. + * @see #frame + * @see #materials + */ + public function get loop():Boolean { + return _loop; + } + + /** + * @private + */ + public function set loop(value:Boolean):void { + _loop = value; + frame = _frame; + } + + /** + * Current frame of animation. While rendering, the material to draw AnimSprite will taken from materials list according to value of this property. + * @see #loop + * @see #materials + */ + public function get frame():int { + return _frame; + } + + /** + * @private + */ + public function set frame(value:int):void { + _frame = value; + if (_materials != null) { + var materialsLength:int = _materials.length; + var index:int = _frame; + if (_frame < 0) { + var mod:int = _frame%materialsLength; + index = (_loop && mod != 0) ? (mod + materialsLength) : 0; + } else if (_frame > materialsLength - 1) { + index = _loop ? (_frame%materialsLength) : (materialsLength - 1); + } + material = _materials[index]; + } + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (materials != null) { + for each (var material:Material in materials) { + if (material != null) material.fillResources(resources, resourceType); + } + } + super.fillResources(resources, hierarchy, resourceType); + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:AnimSprite = new AnimSprite(width, height); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:AnimSprite = source as AnimSprite; + _materials = src._materials; + _loop = src._loop; + _frame = src._frame; + } + } +} diff --git a/src/alternativa/engine3d/objects/AxisAlignedSprite.as b/src/alternativa/engine3d/objects/AxisAlignedSprite.as new file mode 100644 index 0000000..765b6bb --- /dev/null +++ b/src/alternativa/engine3d/objects/AxisAlignedSprite.as @@ -0,0 +1,273 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * AxisAlignedSprite is a flat image which keeps vertical orientation but able to revolve on its own z-axis. Z-rotation defines by relative position of a sprite and a camera. + * AxisAlignedSprite can look to the camera as well as keep the same direction with it. In the first case normal of the sprite point to the camera, + * and normal is parallel with z-axis of the camera in second case. Set alignToView to true for the second case and false otherwise. + * Please note, if z-axis of the AxisAlignedSprite point to the camera, you will not able see it. + */ + public class AxisAlignedSprite extends Object3D { + + static private const geometries:Dictionary = new Dictionary(); + + static private var transformProcedureStatic:Procedure = new Procedure([ + // Pivot + "sub t0.x, i0.x, c0.x", + "add t0.z, i0.z, c0.y", + // Width and height + "mul t0.x, t0.x, c0.z", + "mul o0.z, t0.z, c0.w", + // Rotation + "mov t1.z, c1.x", + "sin t1.x, t1.z", // sin + "cos t1.y, t1.z", // cos + "mul o0.x, t0.x, t1.y", // x*cos + "mul o0.y, t0.x, t1.x", // x*sin + "mov o0.w, i0.w", + // Declaration + "#c0=size", // originX, originY, width, height + "#c1=rotation", // angle, 0, 0, 1 + ]); + + static private var deltaTransformProcedureStatic:Procedure = new Procedure([ + // Rotation + "mov t1.z, c1.x", + "sin t1.x, t1.z", // sin + "neg t1.x, t1.x", + "cos t1.y, t1.z", // cos + "mul o0.x, i0.y, t1.x", // y*sin + "mul o0.y, i0.y, t1.y", // y*cos + "mov o0.z, i0.z", + "mov o0.w, i0.w", + // Declaration + "#c0=size", // originX, originY, width, height + "#c1=rotation", // angle, 0, 0, 1 + ]); + + /** + * Horizontal coordinate in the AxisAlignedSprite plane which defines what part of the plane will placed in x = 0 of the AxisAlignedSprite object. The dimension considered with UV-coordinates. + * Thus, if originX = 0, image will drawn from 0 to the right, if originX = -1 – to the left. + * And image will drawn in the center of the AxisAlignedSprite, if originX = 0.5. + */ + public var originX:Number = 0.5; + + /** + * Vertical coordinate in the AxisAlignedSprite plane which defines what part of the plane will placed in y = 0 of the AxisAlignedSprite object. The dimension considered with UV-coordinates. + * Thus, if originY = 0, image will drawn from 0 to the bottom, if originY = -1 – to the top. + * And image will drawn in the center of the AxisAlignedSprite, if originY = 0.5. + */ + public var originY:Number = 0.5; + + /** + * Width + */ + public var width:Number; + + /** + * Height + */ + public var height:Number; + + /** + * If true, the normal of the AxisAlignedSprite will be parallel to z-axis of the camera, otherwise the normal will point to the camera. + */ + public var alignToView:Boolean = true; + + /** + * @private + */ + alternativa3d var surface:Surface; + + /** + * Creates a new AxisAlignedSprite instance. + * @param width Width + * @param height Height + * @param material The material. + * @see alternativa.engine3d.materials.Material + */ + public function AxisAlignedSprite(width:Number, height:Number, material:Material = null) { + this.width = width; + this.height = height; + surface = new Surface(); + surface.object = this; + this.material = material; + surface.indexBegin = 0; + surface.numTriangles = 2; + // Transform position to the local space + transformProcedure = transformProcedureStatic; + // Transform vector to the local space + deltaTransformProcedure = deltaTransformProcedureStatic; + } + + /** + * Material of a sprite. + * @see alternativa.engine3d.materials.Material + */ + public function get material():Material { + return surface.material; + } + + /** + * @private + */ + public function set material(value:Material):void { + surface.material = value; + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (surface.material != null) surface.material.fillResources(resources, resourceType); + super.fillResources(resources, hierarchy, resourceType); + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + var geometry:Geometry = getGeometry(camera.context3D); + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength); + // Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + + /** + * @private + */ + override alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + // Set constants + drawUnit.setVertexConstantsFromNumbers(0, originX, originY, width, height); + if (alignToView || camera.orthographic) { + drawUnit.setVertexConstantsFromNumbers(1, Math.PI - Math.atan2(-cameraToLocalTransform.c, -cameraToLocalTransform.g), 0, 0, 1); + } else { + drawUnit.setVertexConstantsFromNumbers(1, Math.PI - Math.atan2(cameraToLocalTransform.d, cameraToLocalTransform.h), 0, 0, 1); + } + } + + /** + * @private + */ + alternativa3d function getGeometry(context:Context3D):Geometry { + var geometry:Geometry = geometries[context]; + if (geometry == null) { + geometry = new Geometry(4); + + var attributes:Array = new Array(); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.NORMAL; + attributes[4] = VertexAttributes.NORMAL; + attributes[5] = VertexAttributes.NORMAL; + attributes[6] = VertexAttributes.TEXCOORDS[0]; + attributes[7] = VertexAttributes.TEXCOORDS[0]; + attributes[8] = VertexAttributes.TEXCOORDS[1]; + attributes[9] = VertexAttributes.TEXCOORDS[1]; + attributes[10] = VertexAttributes.TEXCOORDS[2]; + attributes[11] = VertexAttributes.TEXCOORDS[2]; + attributes[12] = VertexAttributes.TEXCOORDS[3]; + attributes[13] = VertexAttributes.TEXCOORDS[3]; + attributes[14] = VertexAttributes.TEXCOORDS[4]; + attributes[15] = VertexAttributes.TEXCOORDS[4]; + attributes[16] = VertexAttributes.TEXCOORDS[5]; + attributes[17] = VertexAttributes.TEXCOORDS[5]; + attributes[18] = VertexAttributes.TEXCOORDS[6]; + attributes[19] = VertexAttributes.TEXCOORDS[6]; + attributes[20] = VertexAttributes.TEXCOORDS[7]; + attributes[21] = VertexAttributes.TEXCOORDS[7]; + attributes[22] = VertexAttributes.TANGENT4; + attributes[23] = VertexAttributes.TANGENT4; + attributes[24] = VertexAttributes.TANGENT4; + attributes[25] = VertexAttributes.TANGENT4; + geometry.addVertexStream(attributes); + + geometry.setAttributeValues(VertexAttributes.POSITION, Vector.([0, 0, 0, 0, 0, -1, 1, 0, -1, 1, 0, 0])); + geometry.setAttributeValues(VertexAttributes.NORMAL, Vector.([0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[1], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[2], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[3], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[4], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[5], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[6], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[7], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + + geometry.indices = Vector.([0, 1, 3, 2, 3, 1]); + + geometry.upload(context); + geometries[context] = geometry; + } + return geometry; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:AxisAlignedSprite = new AxisAlignedSprite(width, height); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:AxisAlignedSprite = source as AxisAlignedSprite; + width = src.width; + height = src.height; + material = src.material; + originX = src.originX; + originY = src.originY; + alignToView = src.alignToView; + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + if (transform != null) { + // TODO: + } + var radius:Number = ((originX >= 0.5) ? originX : (1 - originX))*width; + var top:Number = originY*height; + var bottom:Number = (originY - 1)*height; + if (-radius < boundBox.minX) boundBox.minX = -radius; + if (radius > boundBox.maxX) boundBox.maxX = radius; + if (-radius < boundBox.minY) boundBox.minY = -radius; + if (radius > boundBox.maxY) boundBox.maxY = radius; + if (bottom < boundBox.minZ) boundBox.minZ = bottom; + if (top > boundBox.maxZ) boundBox.maxZ = top; + } + } +} diff --git a/src/alternativa/engine3d/objects/Decal.as b/src/alternativa/engine3d/objects/Decal.as new file mode 100644 index 0000000..03ae83b --- /dev/null +++ b/src/alternativa/engine3d/objects/Decal.as @@ -0,0 +1,110 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + + use namespace alternativa3d; + + /** + * A Mesh which has z-fighting engine. Most popular case of use is for dynamic addition of different tracks over existing surfaces. + * The Plane instance can be used as the geometry source. + * + * var plane = new Plane(200, 200); + * var decal = new Decal(); + * decal.geometry = plane.geometry; + * for (var i:int = 0; i < plane.numSurfaces; i++){ + * decal.addSurface(null, plane.getSurface(i).indexBegin, plane.getSurface(i).numTriangles);} + * decal.geometry.upload(stage3D.context3D); + * + */ + public class Decal extends Mesh { + + static private var transformProcedureStatic:Procedure = new Procedure([ + // Z in a camera + "dp4 t0.z, i0, c0", + // delta calculates with z*z/(zNear*(1<, lightsLength:int):void { + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, Renderer.DECALS); + // Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + } + + /** + * @private + */ + override alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("cCam"), cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l, camera.nearClipping*(1 << zBufferPrecision)); + drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("cTrm"), localToCameraTransform.i, localToCameraTransform.j, localToCameraTransform.k, localToCameraTransform.l); + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Decal = new Decal(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + } + + } +} diff --git a/src/alternativa/engine3d/objects/Joint.as b/src/alternativa/engine3d/objects/Joint.as new file mode 100644 index 0000000..a7cd7ef --- /dev/null +++ b/src/alternativa/engine3d/objects/Joint.as @@ -0,0 +1,126 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * A joint uses with Skin as handler for set of vertices. + * @see alternativa.engine3d.objects.Skin + */ + public class Joint extends Object3D { + + /** + * @private + * A joint transform matrix. + */ + alternativa3d var jointTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d var bindPoseTransform:Transform3D = new Transform3D(); + + /** + * @private + */ + alternativa3d function setBindPoseMatrix(matrix:Vector.):void { + bindPoseTransform.initFromVector(matrix); + } + + public function get bindingMatrix():Matrix3D { + return new Matrix3D(Vector.([ + bindPoseTransform.a, bindPoseTransform.e, bindPoseTransform.i, 0, + bindPoseTransform.b, bindPoseTransform.f, bindPoseTransform.j, 0, + bindPoseTransform.c, bindPoseTransform.g, bindPoseTransform.k, 0, + bindPoseTransform.d, bindPoseTransform.h, bindPoseTransform.l, 1 + ])); + } + + public function set bindingMatrix(value:Matrix3D):void { + var data:Vector. = value.rawData; + bindPoseTransform.a = data[0]; + bindPoseTransform.b = data[4]; + bindPoseTransform.c = data[8]; + bindPoseTransform.d = data[12]; + bindPoseTransform.e = data[1]; + bindPoseTransform.f = data[5]; + bindPoseTransform.g = data[9]; + bindPoseTransform.h = data[13]; + bindPoseTransform.i = data[2]; + bindPoseTransform.j = data[6]; + bindPoseTransform.k = data[10]; + bindPoseTransform.l = data[14]; + } + + /** + * @private + */ + alternativa3d function calculateBindingMatrices():void { + for (var child:Object3D = childrenList; child != null; child = child.next) { + var joint:Joint = child as Joint; + if (joint != null) { + if (joint.transformChanged) { + joint.composeTransforms(); + } + joint.bindPoseTransform.combine(bindPoseTransform, joint.inverseTransform); + joint.calculateBindingMatrices(); + } + } + } + + + /** + * @private + */ + alternativa3d function calculateTransform():void { + if (bindPoseTransform != null) { + jointTransform.combine(localToGlobalTransform, bindPoseTransform); + } + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Joint = new Joint(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var sourceJoint:Joint = source as Joint; + bindPoseTransform.a = sourceJoint.bindPoseTransform.a; + bindPoseTransform.b = sourceJoint.bindPoseTransform.b; + bindPoseTransform.c = sourceJoint.bindPoseTransform.c; + bindPoseTransform.d = sourceJoint.bindPoseTransform.d; + bindPoseTransform.e = sourceJoint.bindPoseTransform.e; + bindPoseTransform.f = sourceJoint.bindPoseTransform.f; + bindPoseTransform.g = sourceJoint.bindPoseTransform.g; + bindPoseTransform.h = sourceJoint.bindPoseTransform.h; + bindPoseTransform.i = sourceJoint.bindPoseTransform.i; + bindPoseTransform.j = sourceJoint.bindPoseTransform.j; + bindPoseTransform.k = sourceJoint.bindPoseTransform.k; + bindPoseTransform.l = sourceJoint.bindPoseTransform.l; + } + + } +} diff --git a/src/alternativa/engine3d/objects/LOD.as b/src/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..4d0fa08 --- /dev/null +++ b/src/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,346 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.RayIntersectionData; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.events.Event3D; + + import flash.geom.Vector3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Levels of detail is an Object3D which can have several representation of different detail level. + * The current level will be chosen for the rendering according to distance to the camera. + */ + public class LOD extends Object3D { + + /** + * @private + */ + alternativa3d var levelList:Object3D; + + /** + * Adds a children as a new level of detail. In case of given object is a children of other Object3D already, it will removed from the previous place. + * @param level Object3D which will added. + * @param distance If the LOD closer to the camera than distance value, this level will be preferred to the distant one. + * @return Object, given as lod parameter. + */ + public function addLevel(level:Object3D, distance:Number):Object3D { + // Checking for the errors. + if (level == null) throw new TypeError("Parameter level must be non-null."); + if (level == this) throw new ArgumentError("An object cannot be added as a child of itself."); + for (var container:Object3D = _parent; container != null; container = container._parent) { + if (container == level) throw new ArgumentError("An object cannot be added as a child to one of it's children (or children's children, etc.)."); + } + // Add. + if (level._parent != this) { + // Remove from previous parent. + if (level._parent != null) level._parent.removeChild(level); + // Add + addToLevelList(level, distance); + level._parent = this; + // Dispatch of event. + if (level.willTrigger(Event3D.ADDED)) level.dispatchEvent(new Event3D(Event3D.ADDED, true)); + } else { + if (removeFromList(level) == null) removeFromLevelList(level); + // Add. + addToLevelList(level, distance); + } + return level; + } + + /** + * Removes level of detail. + * + * @param level Object3d which was used as level of detail that will be removed. + * @return The Object3d instance that you pass in the level parameter. + * @see #addLevel() + */ + public function removeLevel(level:Object3D):Object3D { + // Checking for the errors. + if (level == null) throw new TypeError("Parameter level must be non-null."); + if (level._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + level = removeFromLevelList(level); + if (level == null) throw new ArgumentError("Cannot remove level."); + // Dispatch of event. + if (level.willTrigger(Event3D.REMOVED)) level.dispatchEvent(new Event3D(Event3D.REMOVED, true)); + level._parent = null; + return level; + } + + /** + * Returns distance was set up for the given level. If the LOD closer to the camera than distance, this level will be preferred to the distant one. + * @param level Object3d which was used as level of detail. + * @return Distance was set up for the given level. + */ + public function getLevelDistance(level:Object3D):Number { + // Checking for the errors + if (level == null) throw new TypeError("Parameter level must be non-null."); + if (level._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + for (var current:Object3D = levelList; current != null; current = current.next) { + if (level == current) return level.distance; + } + throw new ArgumentError("Cannot get level distance."); + } + + /** + * Sets distance to the given level. If the LOD closer to the camera than distance value, this level will be preffered to the distant one. + * @param level Object3d which was used as level of detail. + * @param distance Distance value. + */ + public function setLevelDistance(level:Object3D, distance:Number):void { + // Checking for the errors. + if (level == null) throw new TypeError("Parameter level must be non-null."); + if (level._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + level = removeFromLevelList(level); + if (level == null) throw new ArgumentError("Cannot set level distance."); + addToLevelList(level, distance); + } + + /** + * Returns Object3D which was used as level of detail at given distance. + * @param distance Distance. + * @return Object3D which was used as level of detail at given distance. + */ + public function getLevelByDistance(distance:Number):Object3D { + for (var current:Object3D = levelList; current != null; current = current.next) { + if (distance <= current.distance) return current; + } + return null; + } + + /** + * + * Returns Object3D which was used as level of detail with given name. + * @param name Name of the object. + * @return Object3D with given name. + */ + public function getLevelByName(name:String):Object3D { + // Checking for the errors. + if (name == null) throw new TypeError("Parameter name must be non-null."); + // Search for object + for (var current:Object3D = levelList; current != null; current = current.next) { + if (current.name == name) return current; + } + return null; + } + + /** + * Returns all Object3Ds which was used as levels of detail in this LOD, in Vector.<Object3D>. + * @return Vector.<Object3D> consists of Object3Ds which was used as levels of detail. + */ + public function getLevels():Vector. { + var res:Vector. = new Vector.(); + var num:int = 0; + for (var current:Object3D = levelList; current != null; current = current.next) { + res[num] = current; + num++; + } + return res; + } + + /** + * Number of levels of detail. + */ + public function get numLevels():int { + var num:int = 0; + for (var current:Object3D = levelList; current != null; current = current.next) num++; + return num; + } + + /** + * @private + */ + override alternativa3d function get useLights():Boolean { + return true; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + var distance:Number = Math.sqrt(localToCameraTransform.d*localToCameraTransform.d + localToCameraTransform.h*localToCameraTransform.h + localToCameraTransform.l*localToCameraTransform.l); + for (var level:Object3D = levelList; level != null; level = level.next) { + if (distance <= level.distance) { + collectChildDraws(level, this, camera, lights, lightsLength); + break; + } + } + } + + /** + * @private + */ + alternativa3d function collectChildDraws(child:Object3D, parent:Object3D, camera:Camera3D, lights:Vector., lightsLength:int):void { + // Composing direct and reverse matrices + if (child.transformChanged) child.composeTransforms(); + // Calculation of transfer matrix from camera to local space. + child.cameraToLocalTransform.combine(child.inverseTransform, parent.cameraToLocalTransform); + // Calculation of transfer matrix from local space to camera. + child.localToCameraTransform.combine(parent.localToCameraTransform, child.transform); + // Pass + child.culling = parent.culling; + child.listening = parent.listening; + // If object needs on light sources. + if (lightsLength > 0 && child.useLights) { + // Calculation of transfer matrices from sources to object. + for (var i:int = 0; i < lightsLength; i++) { + var light:Light3D = lights[i]; + light.lightToObjectTransform.combine(child.cameraToLocalTransform, light.localToCameraTransform); + } + child.collectDraws(camera, lights, lightsLength); + } else { + child.collectDraws(camera, null, 0); + } + // Hierarchical call + for (var c:Object3D = child.childrenList; c != null; c = c.next) { + collectChildDraws(c, child, camera, lights, lightsLength); + } + } + + /** + * @private + */ + override alternativa3d function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (hierarchy) { + for (var current:Object3D = levelList; current != null; current = current.next) { + current.fillResources(resources, hierarchy, resourceType); + } + } + super.fillResources(resources, hierarchy, resourceType); + } + + /** + * @inheritDoc + */ + override public function intersectRay(origin:Vector3D, direction:Vector3D):RayIntersectionData { + var childrenData:RayIntersectionData = super.intersectRay(origin, direction); + var contentData:RayIntersectionData; + if (levelList != null && (boundBox == null || boundBox.intersectRay(origin, direction))) { + if (levelList.transformChanged) levelList.composeTransforms(); + var childOrigin:Vector3D = new Vector3D(); + var childDirection:Vector3D = new Vector3D(); + childOrigin.x = levelList.inverseTransform.a*origin.x + levelList.inverseTransform.b*origin.y + levelList.inverseTransform.c*origin.z + levelList.inverseTransform.d; + childOrigin.y = levelList.inverseTransform.e*origin.x + levelList.inverseTransform.f*origin.y + levelList.inverseTransform.g*origin.z + levelList.inverseTransform.h; + childOrigin.z = levelList.inverseTransform.i*origin.x + levelList.inverseTransform.j*origin.y + levelList.inverseTransform.k*origin.z + levelList.inverseTransform.l; + childDirection.x = levelList.inverseTransform.a*direction.x + levelList.inverseTransform.b*direction.y + levelList.inverseTransform.c*direction.z; + childDirection.y = levelList.inverseTransform.e*direction.x + levelList.inverseTransform.f*direction.y + levelList.inverseTransform.g*direction.z; + childDirection.z = levelList.inverseTransform.i*direction.x + levelList.inverseTransform.j*direction.y + levelList.inverseTransform.k*direction.z; + contentData = levelList.intersectRay(childOrigin, childDirection); + } + if (childrenData != null) { + if (contentData != null) { + return childrenData.time < contentData.time ? childrenData : contentData; + } else { + return childrenData; + } + } else { + return contentData; + } + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + for (var current:Object3D = levelList; current != null; current = current.next) { + if (current.transformChanged) current.composeTransforms(); + if (transform != null) { + current.localToCameraTransform.combine(transform, current.transform); + } else { + current.localToCameraTransform.copy(current.transform); + } + current.updateBoundBox(boundBox, current.localToCameraTransform); + updateBoundBoxChildren(current, boundBox); + } + } + + private function updateBoundBoxChildren(parent:Object3D, boundBox:BoundBox):void { + for (var current:Object3D = parent.childrenList; current != null; current = current.next) { + if (current.transformChanged) current.composeTransforms(); + current.localToCameraTransform.combine(parent.localToCameraTransform, current.transform); + current.updateBoundBox(boundBox, current.localToCameraTransform); + updateBoundBoxChildren(current, boundBox); + } + } + + private function addToLevelList(level:Object3D, distance:Number):void { + level.distance = distance; + var prev:Object3D = null; + for (var current:Object3D = levelList; current != null; current = current.next) { + if (distance < current.distance) { + level.next = current; + break; + } + prev = current; + } + if (prev != null) { + prev.next = level; + } else { + levelList = level; + } + } + + private function removeFromLevelList(level:Object3D):Object3D { + var prev:Object3D; + for (var current:Object3D = levelList; current != null; current = current.next) { + if (current == level) { + if (prev != null) { + prev.next = current.next; + } else { + levelList = current.next; + } + current.next = null; + return level; + } + prev = current; + } + return null; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:LOD = new LOD(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:LOD = source as LOD; + for (var current:Object3D = src.levelList, last:Object3D; current != null; current = current.next) { + var newLevel:Object3D = current.clone(); + if (levelList != null) { + last.next = newLevel; + } else { + levelList = newLevel; + } + last = newLevel; + newLevel._parent = this; + newLevel.distance = current.distance; + } + } + + } +} diff --git a/src/alternativa/engine3d/objects/Mesh.as b/src/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..d0ed2a3 --- /dev/null +++ b/src/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,195 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.collisions.EllipsoidCollider; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.RayIntersectionData; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.resources.Geometry; + + import flash.geom.Vector3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * A polygonal object defined by set of vertices and surfaces built on this vertices. Surface is a set of triangles which have same material. + * To get access to vertices data you should use geometry property. + */ + public class Mesh extends Object3D { + + /** + * Through geometry property you can get access to vertices. + * @see alternativa.engine3d.resources.Geometry + */ + public var geometry:Geometry; + + /** + * @private + */ + alternativa3d var _surfaces:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var _surfacesLength:int = 0; + + /** + * @inheritDoc + */ + override public function intersectRay(origin:Vector3D, direction:Vector3D):RayIntersectionData { + var childrenData:RayIntersectionData = super.intersectRay(origin, direction); + var contentData:RayIntersectionData; + if (geometry != null && (boundBox == null || boundBox.intersectRay(origin, direction))) { + var minTime:Number = 1e22; + for each (var surface:Surface in _surfaces) { + var data:RayIntersectionData = geometry.intersectRay(origin, direction, surface.indexBegin, surface.numTriangles); + if (data != null && data.time < minTime) { + contentData = data; + contentData.object = this; + contentData.surface = surface; + minTime = data.time; + } + } + } + if (childrenData != null) { + if (contentData != null) { + return childrenData.time < contentData.time ? childrenData : contentData; + } else { + return childrenData; + } + } else { + return contentData; + } + } + + /** + * Adds Surface to Mesh object. + * @param material Material of the surface. + * @param indexBegin Position of the firs index of surface in the geometry. + * @param numTriangles Number of triangles. + */ + public function addSurface(material:Material, indexBegin:uint, numTriangles:uint):Surface { + var res:Surface = new Surface(); + res.object = this; + res.material = material; + res.indexBegin = indexBegin; + res.numTriangles = numTriangles; + _surfaces[_surfacesLength++] = res; + return res; + } + + /** + * Returns surface by index. + * + * @param index Index. + * @return Surface with given index. + */ + public function getSurface(index:int):Surface { + return _surfaces[index]; + } + + /** + * Number of surfaces. + */ + public function get numSurfaces():int { + return _surfacesLength; + } + + /** + * Assign given material to all surfaces. + * + * @param material Material. + * @see alternativa.engine3d.objects.Surface + * @see alternativa.engine3d.materials + */ + public function setMaterialToAllSurfaces(material:Material):void { + for (var i:int = 0; i < _surfaces.length; i++) { + _surfaces[i].material = material; + } + } + + /** + * @private + */ + override alternativa3d function get useLights():Boolean { + return true; + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + if (geometry != null) geometry.updateBoundBox(boundBox, transform); + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (geometry != null && (resourceType == null || geometry is resourceType)) resources[geometry] = true; + for (var i:int = 0; i < _surfacesLength; i++) { + var s:Surface = _surfaces[i]; + if (s.material != null) s.material.fillResources(resources, resourceType); + } + super.fillResources(resources, hierarchy, resourceType); + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength); + // Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + } + + /** + * @private + */ + override alternativa3d function collectGeometry(collider:EllipsoidCollider, excludedObjects:Dictionary):void { + collider.geometries.push(geometry); + collider.transforms.push(localToGlobalTransform); + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Mesh = new Mesh(); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var mesh:Mesh = source as Mesh; + geometry = mesh.geometry; + _surfacesLength = 0; + _surfaces.length = 0; + for each (var s:Surface in mesh._surfaces) { + addSurface(s.material, s.indexBegin, s.numTriangles); + } + } + + } +} diff --git a/src/alternativa/engine3d/objects/MeshSet.as b/src/alternativa/engine3d/objects/MeshSet.as new file mode 100644 index 0000000..3197d6a --- /dev/null +++ b/src/alternativa/engine3d/objects/MeshSet.as @@ -0,0 +1,263 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexStream; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3DVertexBufferFormat; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class MeshSet extends Mesh { + private var root:Object3D; + + private static const ATTRIBUTE:uint = 20; + + private var surfaceMeshes:Vector.> = new Vector.>(); + + public static const MESHES_PER_SURFACE:uint = 30; + private var surfaceTransformProcedures:Vector. = new Vector.(); + private var surfaceDeltaTransformProcedures:Vector. = new Vector.(); + + private static var _transformProcedures:Dictionary = new Dictionary(); + private static var _deltaTransformProcedures:Dictionary = new Dictionary(); + + public function MeshSet(root:Object3D) { + this.root = root; + calculateGeometry(); + } + + alternativa3d override function calculateVisibility(camera:Camera3D):void { + super.alternativa3d::calculateVisibility(camera); + if (root.transformChanged) root.composeTransforms(); + root.localToGlobalTransform.copy(root.transform); + calculateMeshesTransforms(root); + } + + alternativa3d override function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint"), geometry.getVertexBuffer(ATTRIBUTE), geometry._attributesOffsets[ATTRIBUTE], Context3DVertexBufferFormat.FLOAT_1); + var index:uint = _surfaces.indexOf(surface); + var meshes:Vector. = surfaceMeshes[index]; + for (var i:int = 0, count:int = meshes.length; i < count; i++) { + var mesh:Mesh = meshes[i]; + drawUnit.setVertexConstantsFromTransform(i*3, mesh.localToGlobalTransform); + } + } + + private function calculateMeshesTransforms(root:Object3D):void { + for (var child:Object3D = root.childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + // Put skin transfer matrix to localToGlobalTransform + child.localToGlobalTransform.combine(root.localToGlobalTransform, child.transform); + calculateMeshesTransforms(child); + } + } + + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + if (geometry == null) return; + // Calculation of joints matrices. + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + transformProcedure = surfaceTransformProcedures[i]; + deltaTransformProcedure = surfaceDeltaTransformProcedures[i]; + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength); + // Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + // Debug + if (camera.debug) { + var debug:int = camera.checkInDebug(this); + if ((debug & Debug.BOUNDS) && boundBox != null) Debug.drawBoundBox(camera, boundBox, localToCameraTransform); + } + } + + private function calculateGeometry():void { + geometry = new Geometry(0); + addSurface(null, 0, 0); + var numAttributes:int = 32; + var attributesDict:Vector. = new Vector.(numAttributes, true); + var attributesLengths:Vector. = new Vector.(numAttributes, true); + var numMeshes:Number = collectAttributes(root, attributesDict, attributesLengths); + + var attributes:Array = new Array(); + var i:int; + + for (i = 0; i < numAttributes; i++) { + if (attributesDict[i] > 0) { + attributesLengths[i] = attributesLengths[i]/attributesDict[i]; + } + } + for (i = 0; i < numAttributes; i++) { + if (Number(attributesDict[i])/numMeshes == 1) { + for (var j:int = 0; j < attributesLengths[i]; j++) { + attributes.push(i); + } + + } + } + attributes.push(ATTRIBUTE); + geometry.addVertexStream(attributes); + if (root is Mesh) appendMesh(root as Mesh); + collectMeshes(root); + var surfaceIndex:uint = _surfaces.length - 1; + var meshes:Vector. = surfaceMeshes[surfaceIndex]; + surfaceTransformProcedures[surfaceIndex] = calculateTransformProcedure(meshes.length); + surfaceDeltaTransformProcedures[surfaceIndex] = calculateDeltaTransformProcedure(meshes.length); + } + + private function collectAttributes(root:Object3D, attributesDict:Vector., attributesLengths:Vector.):int { + var geom:Geometry; + var numMeshes:int = 0; + if (root is Mesh) { + geom = Mesh(root).geometry; + + for each (var stream:VertexStream in geom._vertexStreams) { + var prev:int = -1; + var attributes:Array = stream.attributes; + for each (var attr:int in attributes) { + attributesLengths[attr]++; + if (attr == prev) continue; + attributesDict[attr]++; + prev = attr; + } + } + numMeshes++; + } + + for (var child:Object3D = root.childrenList; child != null; child = child.next) { + numMeshes += collectAttributes(child, attributesDict, attributesLengths); + } + return numMeshes; + } + + override public function addSurface(material:Material, indexBegin:uint, numTriangles:uint):Surface { + surfaceMeshes.push(new Vector.()); + return super.addSurface(material, indexBegin, numTriangles); + } + + private function collectMeshes(root:Object3D):void { + for (var child:Object3D = root.childrenList; child != null; child = child.next) { + if (child is Mesh) { + appendMesh(child as Mesh); + } + collectMeshes(child); + } + } + + private function appendGeometry(geom:Geometry, index:int):void { + var stream:VertexStream; + var i:int, j:int; + var length:uint = geom._vertexStreams.length; + var numVertices:int = geom._numVertices; + for (i = 0; i < length; i++) { + stream = geom._vertexStreams[i]; + var attributes:Array = geometry._vertexStreams[i].attributes; + var attribtuesLength:int = attributes.length; + var destStream:VertexStream = geometry._vertexStreams[i]; + var newOffset:int = destStream.data.length; + destStream.data.position = newOffset; + + stream.data.position = 0; + var stride:int = stream.attributes.length*4; + var destStride:int = destStream.attributes.length*4; + for (j = 0; j < numVertices; j++) { + var prev:int = -1; + for (var k:int = 0; k < attribtuesLength; k++) { + var attr:int = attributes[k]; + if (attr == ATTRIBUTE) { + destStream.data.writeFloat(index*3); + continue; + } + if (attr != prev) { + stream.data.position = geom._attributesOffsets[attr]*4 + stride*j; + destStream.data.position = newOffset + geometry._attributesOffsets[attr]*4 + destStride*j; + } + destStream.data.writeFloat(stream.data.readFloat()); + prev = attr; + } + } + + } + geometry._numVertices += geom._numVertices; + + } + + private function compareAttribtues(destStream:VertexStream, sourceStream:VertexStream):Boolean { + if ((destStream.attributes.length - 1) != sourceStream.attributes.length) return false; + var len:int = sourceStream.attributes.length; + for (var i:int = 0; i < len; i++) { + if (destStream.attributes[i] != sourceStream.attributes[i]) return false; + } + return true; + } + + private function appendMesh(mesh:Mesh):void { + var surfaceIndex:uint = _surfaces.length - 1; + var destSurface:Surface = _surfaces[surfaceIndex]; + var meshes:Vector. = surfaceMeshes[surfaceIndex]; + if (meshes.length >= MESHES_PER_SURFACE) { + surfaceTransformProcedures[surfaceIndex] = calculateTransformProcedure(meshes.length); + surfaceDeltaTransformProcedures[surfaceIndex] = calculateDeltaTransformProcedure(meshes.length); + addSurface(null, geometry._indices.length, 0); + surfaceIndex++; + destSurface = _surfaces[surfaceIndex]; + meshes = surfaceMeshes[surfaceIndex]; + } + meshes.push(mesh); + var geom:Geometry = mesh.geometry; + var vertexOffset:uint; + var i:int, j:int; + vertexOffset = geometry._numVertices; + appendGeometry(geom, meshes.length - 1); + trace(surfaceIndex); + // Copy indexes + for (i = 0; i < mesh._surfacesLength; i++) { + var surface:Surface = mesh._surfaces[i]; + var indexEnd:uint = surface.numTriangles*3 + surface.indexBegin; + destSurface.numTriangles += surface.numTriangles; + for (j = surface.indexBegin; j < indexEnd; j++) { + geometry._indices.push(geom._indices[j] + vertexOffset); + } + } + } + + private function calculateTransformProcedure(numMeshes:int):Procedure { + var res:Procedure = _transformProcedures[numMeshes]; + if (res != null) return res; + res = _transformProcedures[numMeshes] = new Procedure(null, "MeshSetTransformProcedure"); + res.compileFromArray(["#a0=joint", "m34 o0.xyz, i0, c[a0.x]", "mov o0.w, i0.w"]); + res.assignConstantsArray(numMeshes*3); + return res; + } + + private function calculateDeltaTransformProcedure(numMeshes:int):Procedure { + var res:Procedure = _deltaTransformProcedures[numMeshes]; + if (res != null) return res; + res = _deltaTransformProcedures[numMeshes] = new Procedure(null, "MeshSetDeltaTransformProcedure"); + res.compileFromArray(["#a0=joint", "m33 o0.xyz, i0, c[a0.x]", "mov o0.w, i0.w"]); + return res; + } + } +} diff --git a/src/alternativa/engine3d/objects/Skin.as b/src/alternativa/engine3d/objects/Skin.as new file mode 100644 index 0000000..b5b31b0 --- /dev/null +++ b/src/alternativa/engine3d/objects/Skin.as @@ -0,0 +1,733 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.core.VertexStream; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * Skin is a Mesh which can have a skeleton besides surfaces. The skeleton is a hierarchy of bones, represented by Joint class. + * Each bone can be linked with set of vertices, thus, position of the bone will affect to position of linked vertices. Set of positions of all skeleton bones + * defines a pose which also defines pose of skin's surfaces. Character animation implements through making sequence of such poses. + * If number of bones which affect one surface more than fixed number, skin would not be drawn. + * "Fixed number" defines by the material of given surface, look documentation of the material class for it. + * To avoid this problem use Skin.divide() method. + * + * If you creates a skeleton within a code, make sure that each bone added to: + * 1) skin hierarchy with addChild() method, + * 2) renderedJoints property which represented by Vector.<Joint>. + * + * Link of a vertex and a bone stores in vertex data of VertexAttributes.JOINTS type. + * Vertex buffer point to a joint with following form: index of the joint within renderedJoints multiplied with 3. + * It is done so in order to avoid this multiplication within vertex shader for each frame. + * + * @see alternativa.engine3d.core.VertexAttributes#JOINTS + * @see alternativa.engine3d.objects.Joint + * @see #divide() + */ + public class Skin extends Mesh { + + /** + * @private + */ + alternativa3d var _renderedJoints:Vector.; + + /** + * @private + */ + alternativa3d var surfaceJoints:Vector.>; + + /** + * @private + */ + alternativa3d var surfaceTransformProcedures:Vector.; + + /** + * @private + */ + alternativa3d var surfaceDeltaTransformProcedures:Vector.; + + /** + * @private + */ + alternativa3d var maxInfluences:int = 0; + + // key = maxInfluences | numJoints << 16 + private static var _transformProcedures:Dictionary = new Dictionary(); + // Cashing of procedures on number of influence + private static var _deltaTransformProcedures:Vector. = new Vector.(9); + + /** + * Creates a new Skin instance. + * @param maxInfluences Max number of bones that can affect one vertex. + */ + public function Skin(maxInfluences:int) { + this.maxInfluences = maxInfluences; + + surfaceJoints = new Vector.>(); + surfaceTransformProcedures = new Vector.(); + surfaceDeltaTransformProcedures = new Vector.(); + } + + public function calculateBindingMatrices():void { + for (var child:Object3D = childrenList; child != null; child = child.next) { + var joint:Joint = child as Joint; + if (joint != null) { + if (joint.transformChanged) { + joint.composeTransforms(); + } + joint.bindPoseTransform.copy(joint.inverseTransform); + joint.calculateBindingMatrices(); + } + } + } + + /** + * @inheritDoc + */ + override public function addSurface(material:Material, indexBegin:uint, numTriangles:uint):Surface { + surfaceJoints[_surfacesLength] = _renderedJoints; + surfaceTransformProcedures[_surfacesLength] = transformProcedure; + surfaceDeltaTransformProcedures[_surfacesLength] = deltaTransformProcedure; + return super.addSurface(material, indexBegin, numTriangles); + } + + private function divideSurface(limit:uint, iterations:uint, surface:Surface, jointsOffsets:Vector., jointBufferVertexSize:uint, inVertices:ByteArray, outVertices:ByteArray, outIndices:Vector., outSurfaces:Vector., outJointsMaps:Vector.):uint { + var indexBegin:uint = surface.indexBegin; + var indexCount:uint = surface.numTriangles*3; + var i:int, j:int, count:int, jointsLength:int, index:uint; + var indices:Vector. = geometry._indices; + var groups:Dictionary = new Dictionary(); + var group:Dictionary; + + var key:*, key2:*; + var jointIndex:uint; + var weight:Number; + for (i = indexBegin,count = indexBegin + indexCount; i < count; i += 3) { + group = groups[i] = new Dictionary(); + var jointsGroupLength:uint = 0; + for (var n:int = 0; n < 3; n++) { + index = indices[int(i + n)]; + for (j = 0,jointsLength = jointsOffsets.length; j < jointsLength; j++) { + inVertices.position = jointBufferVertexSize*index + jointsOffsets[j]; + jointIndex = uint(inVertices.readFloat()); + weight = inVertices.readFloat(); + if (weight > 0) { + group[jointIndex] = true; + } + } + } + for (key in group) { + jointsGroupLength++; + } + if (jointsGroupLength > limit) { + throw new Error("Unable to divide Skin."); + } + } + var localNumJoints:uint; + + var facesGroups:Dictionary = optimizeGroups(groups, limit, iterations); + var newIndex:uint = 0; + var newIndexBegin:uint; + for (key in facesGroups) { + var faces:Dictionary = facesGroups[key]; + localNumJoints = 0; + group = groups[key]; + for (key2 in group) { + if (group[key2] is Boolean) { + group[key2] = 3*localNumJoints++; + } + } + var locatedIndices:Dictionary = new Dictionary(); + for (key2 in faces) { + for (i = 0; i < 3; i++) { + index = indices[int(key2 + i)]; + if (locatedIndices[index] != null) { + outIndices.push(locatedIndices[index]); + continue; + } + locatedIndices[index] = newIndex; + outIndices.push(newIndex++); + outVertices.writeBytes(inVertices, index*jointBufferVertexSize, jointBufferVertexSize); + outVertices.position -= jointBufferVertexSize; + var origin:uint = outVertices.position; + var sumWeight:Number = 0; + // reindexation of bones + for (j = 0; j < jointsLength; j++) { + outVertices.position = origin + jointsOffsets[j]; + jointIndex = uint(outVertices.readFloat()); + weight = outVertices.readFloat(); + outVertices.position -= 8; + if (weight > 0) { + outVertices.writeFloat(group[jointIndex]); + outVertices.writeFloat(weight); + sumWeight += weight; + } + } + // normalization of weights + if (sumWeight != 1) { + for (j = 0; j < jointsLength; j++) { + outVertices.position = origin + jointsOffsets[j] + 4; + weight = outVertices.readFloat(); + if (weight > 0) { + outVertices.position -= 4; + outVertices.writeFloat(weight/sumWeight); + } + } + } + outVertices.position = origin + jointBufferVertexSize; + } + } + var resSurface:Surface = new Surface(); + resSurface.object = this; + resSurface.material = surface.material; + resSurface.indexBegin = newIndexBegin; + resSurface.numTriangles = (outIndices.length - newIndexBegin)/3; + outSurfaces.push(resSurface); + outJointsMaps.push(group); + newIndexBegin = outIndices.length; + } + return newIndex; + } + + /** + * Union of groups. + * @groups Set of groups for merging. + * @limit Max number of joints per group. + * @iterations Number of algorythm iteration (more iterations - better result). + */ + private function optimizeGroups(groups:Dictionary, limit:uint, iterations:uint = 1):Dictionary { + var key:*; + var inKey:*; + var facesGroups:Dictionary = new Dictionary(); + for (var i:int = 1; i < iterations + 1; i++) { + var minLike:Number = 1 - i/iterations; + for (key in groups) { + var group1:Dictionary = groups[key]; + for (inKey in groups) { + if (key == inKey) continue; + var group2:Dictionary = groups[inKey]; + var like:Number = calculateLikeFactor(group1, group2, limit); + if (like >= minLike) { + delete groups[inKey]; + for (var copyKey:* in group2) { + group1[copyKey] = true; + } + var indices:Dictionary = facesGroups[key]; + if (indices == null) { + indices = facesGroups[key] = new Dictionary(); + indices[key] = true; + } + + var indices2:Dictionary = facesGroups[inKey]; + if (indices2 != null) { + delete facesGroups[inKey]; + for (copyKey in indices2) { + indices[copyKey] = true; + } + } else { + indices[inKey] = true; + } + } + } + } + } + return facesGroups; + } + + // Calculates "level of similarity" of two groups + private function calculateLikeFactor(group1:Dictionary, group2:Dictionary, limit:uint):Number { + var key:*; + var unionCount:uint; + var intersectCount:uint; + var group1Count:uint; + var group2Count:uint; + for (key in group1) { + unionCount++; + if (group2[key] != null) { + intersectCount++; + } + group1Count++; + } + for (key in group2) { + if (group1[key] == null) { + unionCount++ + } + group2Count++; + } + if (unionCount > limit) return -1; + return intersectCount/unionCount; + } + + /** + * Subdivides skin surfaces. It can be useful in case of impossibility to render a skin due to too big number of bones affected to one surface. (In this case appropriate exception will generated). + * @param limit No more than limit of bones can have its own surface. I.e. if skin instance has 6 joints and limit = 3, + * it will divided into 2 surface and if limit = 6 - into 6 surfaces. + * @param iterations Number of iterations. Increase accuracy and execution time. + */ + public function divide(limit:uint, iterations:uint = 1):void { + if (_renderedJoints == null || maxInfluences <= 0) return; + // Checking: are all joints at one vertex-buffer? + var jointsBuffer:int = geometry.findVertexStreamByAttribute(VertexAttributes.JOINTS[0]); + var jointsOffsets:Vector. = new Vector.(); + var jointOffset:int = 0; + if (jointsBuffer >= 0) { + jointOffset = geometry.getAttributeOffset(VertexAttributes.JOINTS[0])*4; + jointsOffsets.push(jointOffset); + jointsOffsets.push(jointOffset + 8); + } else { + throw new Error("Cannot divide skin, joints[0] must be binded"); + } + var jbTest:int = geometry.findVertexStreamByAttribute(VertexAttributes.JOINTS[1]); + if (jbTest >= 0) { + jointOffset = geometry.getAttributeOffset(VertexAttributes.JOINTS[1])*4; + jointsOffsets.push(jointOffset); + jointsOffsets.push(jointOffset + 8); + if (jointsBuffer != jbTest) { + throw new Error("Cannot divide skin, all joinst must be in the same buffer"); + } + } + + jbTest = geometry.findVertexStreamByAttribute(VertexAttributes.JOINTS[2]); + + if (jbTest >= 0) { + jointOffset = geometry.getAttributeOffset(VertexAttributes.JOINTS[2])*4; + jointsOffsets.push(jointOffset); + jointsOffsets.push(jointOffset + 8); + if (jointsBuffer != jbTest) { + throw new Error("Cannot divide skin, all joinst must be in the same buffer"); + } + } + + jbTest = geometry.findVertexStreamByAttribute(VertexAttributes.JOINTS[3]); + + if (jbTest >= 0) { + jointOffset = geometry.getAttributeOffset(VertexAttributes.JOINTS[3])*4; + jointsOffsets.push(jointOffset); + jointsOffsets.push(jointOffset + 8); + if (jointsBuffer != jbTest) { + throw new Error("Cannot divide skin, all joinst must be in the same buffer"); + } + } + var outSurfaces:Vector. = new Vector.(); + var totalVertices:ByteArray = new ByteArray(); + totalVertices.endian = Endian.LITTLE_ENDIAN; + var totalIndices:Vector. = new Vector.(); + var totalIndicesLength:uint = 0; + var lastMaxIndex:uint = 0; + var key:*; + var lastSurfaceIndex:uint = 0; + var lastIndicesCount:uint = 0; + surfaceJoints.length = 0; + var jointsBufferNumMappings:int = geometry._vertexStreams[jointsBuffer].attributes.length; + var jointsBufferData:ByteArray = geometry._vertexStreams[jointsBuffer].data; + for (var i:int = 0; i < _surfacesLength; i++) { + var outIndices:Vector. = new Vector.(); + var outVertices:ByteArray = new ByteArray(); + var outJointsMaps:Vector. = new Vector.(); + outVertices.endian = Endian.LITTLE_ENDIAN; + var maxIndex:uint = divideSurface(limit, iterations, _surfaces[i], jointsOffsets, + jointsBufferNumMappings*4, jointsBufferData, outVertices, outIndices, outSurfaces, outJointsMaps); + for (var j:int = 0, count:int = outIndices.length; j < count; j++) { + totalIndices[totalIndicesLength++] = lastMaxIndex + outIndices[j]; + } + + for (j = 0,count = outJointsMaps.length; j < count; j++) { + var maxJoints:uint = 0; + var vec:Vector. = surfaceJoints[j + lastSurfaceIndex] = new Vector.(); + var joints:Dictionary = outJointsMaps[j]; + for (key in joints) { + var index:uint = uint(joints[key]/3); + if (vec.length < index) vec.length = index + 1; + vec[index] = _renderedJoints[uint(key/3)]; + maxJoints++; + } + } + for (j = lastSurfaceIndex; j < outSurfaces.length; j++) { + outSurfaces[j].indexBegin += lastIndicesCount; + + } + lastSurfaceIndex += outJointsMaps.length; + lastIndicesCount += outIndices.length; + totalVertices.writeBytes(outVertices, 0, outVertices.length); + lastMaxIndex += maxIndex; + } + _surfaces = outSurfaces; + _surfacesLength = outSurfaces.length; + surfaceTransformProcedures.length = _surfacesLength; + surfaceDeltaTransformProcedures.length = _surfacesLength; + calculateSurfacesProcedures(); + var newGeometry:Geometry = new Geometry(); + newGeometry._indices = totalIndices; + + for (i = 0; i < geometry._vertexStreams.length; i++) { + var attributes:Array = geometry._vertexStreams[i].attributes; + newGeometry.addVertexStream(attributes); + if (i == jointsBuffer) { + newGeometry._vertexStreams[i].data = totalVertices; + } else { + var data:ByteArray = new ByteArray(); + data.endian = Endian.LITTLE_ENDIAN; + data.writeBytes(geometry._vertexStreams[i].data); + newGeometry._vertexStreams[i].data = data; + } + } + newGeometry._numVertices = totalVertices.length/(newGeometry._vertexStreams[0].attributes.length << 2); + geometry = newGeometry; + } + + /** + * @private + */ + alternativa3d function calculateJointsTransforms(root:Object3D):void { + for (var child:Object3D = root.childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + // Write transformToSkin matrix to localToGlobalTransform property + child.localToGlobalTransform.combine(root.localToGlobalTransform, child.transform); + if (child is Joint) { + Joint(child).calculateTransform(); + } + calculateJointsTransforms(child); + } + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + // Write transformToSkin matrix to localToGlobalTransform property + child.localToGlobalTransform.copy(child.transform); + if (child is Joint) { + Joint(child).calculateTransform(); + } + calculateJointsTransforms(child); + } + var vertexSurface:Dictionary = new Dictionary(); + var indices:Vector. = geometry._indices; + // Fill the map vertex-surface + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + for (var j:int = surface.indexBegin, count:int = surface.indexBegin + surface.numTriangles*3; j < count; j++) { + vertexSurface[indices[j]] = i; + } + } + var joints:Vector.; + var positions:VertexStream = geometry._attributesStreams[VertexAttributes.POSITION]; + var positionOffset:int = geometry._attributesOffsets[VertexAttributes.POSITION]*4; + var jointsStreams:Vector. = new Vector.(); + var jointsOffsets:Vector. = new Vector.(); + for (i = 0; i < 4; i++) { + if (geometry.hasAttribute(VertexAttributes.JOINTS[i])) { + jointsStreams.push(geometry._attributesStreams[VertexAttributes.JOINTS[i]]); + jointsOffsets.push(geometry._attributesOffsets[VertexAttributes.JOINTS[i]]*4); + } + } + var jointsStreamsLength:uint = jointsStreams.length; + for (i = 0; i < geometry._numVertices; i++) { + joints = surfaceJoints[vertexSurface[i]]; + var buffer:ByteArray = positions.data; + buffer.position = positionOffset + i*positions.attributes.length*4; + + var x:Number = buffer.readFloat(); + var y:Number = buffer.readFloat(); + var z:Number = buffer.readFloat(); + var ox:Number = 0; + var oy:Number = 0; + var oz:Number = 0; + var tx:Number, ty:Number, tz:Number; + for (j = 0; j < jointsStreamsLength; j++) { + buffer = jointsStreams[j].data; + buffer.position = jointsOffsets[j] + i*jointsStreams[j].attributes.length*4; + var jointIndex1:int = buffer.readFloat(); + var jointWeight1:Number = buffer.readFloat(); + var jointIndex2:int = buffer.readFloat(); + var jointWeight2:Number = buffer.readFloat(); + var joint:Joint; + var trm:Transform3D; + if (jointWeight1 > 0) { + joint = joints[int(jointIndex1/3)]; + trm = joint.jointTransform; + tx = x*trm.a + y*trm.b + z*trm.c + trm.d; + ty = x*trm.e + y*trm.f + z*trm.g + trm.h; + tz = x*trm.i + y*trm.j + z*trm.k + trm.l; + ox += tx*jointWeight1; + oy += ty*jointWeight1; + oz += tz*jointWeight1; + } + if (jointWeight2 > 0) { + joint = joints[int(jointIndex2/3)]; + trm = joint.jointTransform; + tx = x*trm.a + y*trm.b + z*trm.c + trm.d; + ty = x*trm.e + y*trm.f + z*trm.g + trm.h; + tz = x*trm.i + y*trm.j + z*trm.k + trm.l; + ox += tx*jointWeight2; + oy += ty*jointWeight2; + oz += tz*jointWeight2; + } + } + + if (transform != null) { + tx = ox*transform.a + oy*transform.b + oz*transform.c + transform.d; + ty = ox*transform.e + oy*transform.f + oz*transform.g + transform.h; + tz = ox*transform.i + oy*transform.j + oz*transform.k + transform.l; + ox = tx; oy = ty; oz = tz; + } + + if (ox < boundBox.minX) { + boundBox.minX = ox; + } + + if (oy < boundBox.minY) { + boundBox.minY = oy; + } + + if (oz < boundBox.minZ) { + boundBox.minZ = oz; + } + + if (ox > boundBox.maxX) { + boundBox.maxX = ox; + } + + if (oy > boundBox.maxY) { + boundBox.maxY = oy; + } + + if (oz > boundBox.maxZ) { + boundBox.maxZ = oz; + } + } + } + + /** + * @private + */ + public function get renderedJoints():Vector. { + return _renderedJoints; + } + + /** + * @private + */ + public function set renderedJoints(value:Vector.):void { + //If skin is not divided, change number of bonesfor each surface + for (var i:int = 0; i < _surfacesLength; i++) { + if (surfaceJoints[i] == _renderedJoints) { + surfaceJoints[i] = value; + } + } + _renderedJoints = value; + + calculateSurfacesProcedures(); + } + + /** + * @private + * Recalculate procedures of surface transformation with respect to number of bones and their influences. + */ + alternativa3d function calculateSurfacesProcedures():void { + var numJoints:int = _renderedJoints != null ? _renderedJoints.length : 0; + transformProcedure = calculateTransformProcedure(maxInfluences, numJoints); + deltaTransformProcedure = calculateDeltaTransformProcedure(maxInfluences); + for (var i:int = 0; i < _surfacesLength; i++) { + numJoints = surfaceJoints[i] != null ? surfaceJoints[i].length : 0; + surfaceTransformProcedures[i] = calculateTransformProcedure(maxInfluences, numJoints); + surfaceDeltaTransformProcedures[i] = calculateDeltaTransformProcedure(maxInfluences); + } + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + if (geometry == null) return; + // Calculate joints matrices + for (var child:Object3D = childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + // Write transformToSkin matrix to localToGlobalTransform property + child.localToGlobalTransform.copy(child.transform); + if (child is Joint) { + Joint(child).calculateTransform(); + } + calculateJointsTransforms(child); + } + + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + transformProcedure = surfaceTransformProcedures[i]; + deltaTransformProcedure = surfaceDeltaTransformProcedures[i]; + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength); + + /*var destination:DrawUnit = surface.getDrawUnit(camera, geometry, lights, lightsLength); + if (destination == null) continue; + camera.renderer.addDrawUnit(destination); + setTransformConstants(destination, surface, destination.program.vertexShader, camera);*/ + // Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + } + + /** + * @private + */ + override alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + var i:int, count:int; + for (i = 0; i < maxInfluences; i += 2) { + var attribute:int = VertexAttributes.JOINTS[i >> 1]; + drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint" + i.toString()), geometry.getVertexBuffer(attribute), geometry._attributesOffsets[attribute], VertexAttributes.FORMATS[attribute]); + } + var surfaceIndex:int = _surfaces.indexOf(surface); + var joints:Vector. = surfaceJoints[surfaceIndex]; + for (i = 0,count = joints.length; i < count; i++) { + var joint:Joint = joints[i]; + drawUnit.setVertexConstantsFromTransform(i*3, joint.jointTransform); + } + } + + private function calculateTransformProcedure(maxInfluences:int, numJoints:int):Procedure { + var res:Procedure = _transformProcedures[maxInfluences | (numJoints << 16)]; + if (res != null) return res; + res = _transformProcedures[maxInfluences | (numJoints << 16)] = new Procedure(null, "SkinTransformProcedure"); + var array:Array = []; + var j:int = 0; + for (var i:int = 0; i < maxInfluences; i ++) { + var joint:int = int(i/2); + if (i%2 == 0) { + if (i == 0) { + array[j++] = "m34 t0.xyz, i0, c[a" + joint + ".x]"; + array[j++] = "mul o0, t0.xyz, a" + joint + ".y"; + } else { + array[j++] = "m34 t0.xyz, i0, c[a" + joint + ".x]"; + array[j++] = "mul t0.xyz, t0.xyz, a" + joint + ".y"; + array[j++] = "add o0, o0, t0.xyz"; + } + } else { + array[j++] = "m34 t0.xyz, i0, c[a" + joint + ".z]"; + array[j++] = "mul t0.xyz, t0.xyz, a" + joint + ".w"; + array[j++] = "add o0, o0, t0.xyz"; + } + } + array[j++] = "mov o0.w, i0.w"; + res.compileFromArray(array); + res.assignConstantsArray(numJoints*3); + for (i = 0; i < maxInfluences; i += 2) { + res.assignVariableName(VariableType.ATTRIBUTE, int(i/2), "joint" + i); + } + return res; + } + + private function calculateDeltaTransformProcedure(maxInfluences:int):Procedure { + var res:Procedure = _deltaTransformProcedures[maxInfluences]; + if (res != null) return res; + res = new Procedure(null, "SkinDeltaTransformProcedure"); + _deltaTransformProcedures[maxInfluences] = res; + var array:Array = []; + var j:int = 0; + for (var i:int = 0; i < maxInfluences; i ++) { + var joint:int = int(i/2); + if (i%2 == 0) { + if (i == 0) { + array[j++] = "m33 t0.xyz, i0, c[a" + joint + ".x]"; + array[j++] = "mul o0, t0.xyz, a" + joint + ".y"; + } else { + array[j++] = "m33 t0.xyz, i0, c[a" + joint + ".x]"; + array[j++] = "mul t0.xyz, t0.xyz, a" + joint + ".y"; + array[j++] = "add o0, o0, t0.xyz"; + } + } else { + array[j++] = "m33 t0.xyz, i0, c[a" + joint + ".z]"; + array[j++] = "mul t0.xyz, t0.xyz, a" + joint + ".w"; + array[j++] = "add o0, o0, t0.xyz"; + } + } + array[j++] = "mov o0.w, i0.w"; + array[j++] = "nrm o0.xyz, o0.xyz"; + res.compileFromArray(array); + for (i = 0; i < maxInfluences; i += 2) { + res.assignVariableName(VariableType.ATTRIBUTE, int(i/2), "joint" + i); + } + return res; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Skin = new Skin(maxInfluences); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var skin:Skin = Skin(source); + this.maxInfluences = skin.maxInfluences; + if (skin._renderedJoints != null) { + // Clone renderedJoints + this._renderedJoints = cloneJointsVector(skin._renderedJoints, skin); + } + this.transformProcedure = skin.transformProcedure; + this.deltaTransformProcedure = skin.deltaTransformProcedure; + for (var i:int = 0; i < _surfacesLength; i++) { + surfaceJoints[i] = cloneJointsVector(skin.surfaceJoints[i], skin); + surfaceTransformProcedures[i] = skin.surfaceTransformProcedures[i]; + surfaceDeltaTransformProcedures[i] = skin.surfaceDeltaTransformProcedures[i]; + } + } + + private function cloneJointsVector(joints:Vector., skin:Skin):Vector. { + var count:int = joints.length; + var result:Vector. = new Vector.(); + for (var i:int = 0; i < count; i++) { + var joint:Joint = joints[i]; + result[i] = Joint(findClonedJoint(joint, skin, this)); + } + return result; + } + + private function findClonedJoint(joint:Joint, parentSource:Object3D, parentDest:Object3D):Object3D { + for (var srcChild:Object3D = parentSource.childrenList, dstChild:Object3D = parentDest.childrenList; srcChild != null; srcChild = srcChild.next, dstChild = dstChild.next) { + if (srcChild == joint) { + return dstChild; + } + if (srcChild.childrenList != null) { + var j:Object3D = findClonedJoint(joint, srcChild, dstChild); + if (j != null) return j; + } + } + return null; + } + + } +} diff --git a/src/alternativa/engine3d/objects/SkyBox.as b/src/alternativa/engine3d/objects/SkyBox.as new file mode 100644 index 0000000..6188c73 --- /dev/null +++ b/src/alternativa/engine3d/objects/SkyBox.as @@ -0,0 +1,328 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.resources.Geometry; + + use namespace alternativa3d; + + /** + * A polygonal box with faces turned inside. It did not cut on farClipping distance and it is difference with Box. + * + * @see alternativa.engine3d.core.Camera3D#farClipping + */ + public class SkyBox extends Mesh { + + /** + * Left side. + */ + static public const LEFT:String = "left"; + + /** + * Right side. + */ + static public const RIGHT:String = "right"; + + /** + * Back side. + */ + static public const BACK:String = "back"; + + /** + * Front side. + */ + static public const FRONT:String = "front"; + + /** + * Bottom side. + */ + static public const BOTTOM:String = "bottom"; + + /** + * Top side.. + */ + static public const TOP:String = "top"; + + static private var transformProcedureStatic:Procedure = new Procedure([ + // Offset + "sub t0.xyz, i0.xyz, c0.xyz", + // Scale + "mul t0.x, t0.x, c0.w", + "mul t0.y, t0.y, c0.w", + "mul t0.z, t0.z, c0.w", + // Back offset + "add o0.xyz, t0.xyz, c0.xyz", + "mov o0.w, i0.w", + // Declaration + "#c0=cTrans", // Camera position and scale + ]); + + private var leftSurface:Surface; + private var rightSurface:Surface; + private var backSurface:Surface; + private var frontSurface:Surface; + private var bottomSurface:Surface; + private var topSurface:Surface; + + private var size:Number; + + /** + * Creates a new SkyBox instance. + * @param size Length of each edge. + * @param left Material of the left side. + * @param right Material of the right side. + * @param back Material of the back side. + * @param front Material of the front side. + * @param bottom Material of the bottom side. + * @param top Material of the top side. + * @param uvPadding Texture padding in UV space. + * @see alternativa.engine3d.materials.Material + */ + public function SkyBox(size:Number, left:Material = null, right:Material = null, back:Material = null, front:Material = null, bottom:Material = null, top:Material = null, uvPadding:Number = 0) { + + size *= 0.5; + + this.size = size; + + geometry = new Geometry(24); + + var attributes:Array = new Array(); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[6] = VertexAttributes.TEXCOORDS[0]; + attributes[7] = VertexAttributes.TEXCOORDS[0]; + geometry.addVertexStream(attributes); + + geometry.setAttributeValues(VertexAttributes.POSITION, Vector.([ + -size, -size, size, + -size, -size, -size, + -size, size, -size, + -size, size, size, + + size, size, size, + size, size, -size, + size, -size, -size, + size, -size, size, + + size, -size, size, + size, -size, -size, + -size, -size, -size, + -size, -size, size, + + -size, size, size, + -size, size, -size, + size, size, -size, + size, size, size, + + -size, size, -size, + -size, -size, -size, + size, -size, -size, + size, size, -size, + + -size, -size, size, + -size, size, size, + size, size, size, + size, -size, size + ])); + + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], Vector.([ + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding, + + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding, + + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding, + + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding, + + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding, + + uvPadding, uvPadding, + uvPadding, 1 - uvPadding, + 1 - uvPadding, 1 - uvPadding, + 1 - uvPadding, uvPadding + ])); + + geometry.indices = Vector.([ + 0, 1, 3, 2, 3, 1, + 4, 5, 7, 6, 7, 5, + 8, 9, 11, 10, 11, 9, + 12, 13, 15, 14, 15, 13, + 16, 17, 19, 18, 19, 17, + 20, 21, 23, 22, 23, 21 + ]); + + leftSurface = addSurface(left, 0, 2); + rightSurface = addSurface(right, 6, 2); + backSurface = addSurface(back, 12, 2); + frontSurface = addSurface(front, 18, 2); + bottomSurface = addSurface(bottom, 24, 2); + topSurface = addSurface(top, 30, 2); + + transformProcedure = transformProcedureStatic; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + for (var i:int = 0; i < _surfacesLength; i++) { + var surface:Surface = _surfaces[i]; + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, Renderer.SKY); + //Mouse events + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + } + + /** + * @private + */ + override alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + var max:Number = 0; + var dx:Number; + var dy:Number; + var dz:Number; + var len:Number; + dx = -size - cameraToLocalTransform.d; + dy = -size - cameraToLocalTransform.h; + dz = -size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = size - cameraToLocalTransform.d; + dy = -size - cameraToLocalTransform.h; + dz = -size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = size - cameraToLocalTransform.d; + dy = size - cameraToLocalTransform.h; + dz = -size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = -size - cameraToLocalTransform.d; + dy = size - cameraToLocalTransform.h; + dz = -size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = -size - cameraToLocalTransform.d; + dy = -size - cameraToLocalTransform.h; + dz = size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = size - cameraToLocalTransform.d; + dy = -size - cameraToLocalTransform.h; + dz = size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = size - cameraToLocalTransform.d; + dy = size - cameraToLocalTransform.h; + dz = size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + dx = -size - cameraToLocalTransform.d; + dy = size - cameraToLocalTransform.h; + dz = size - cameraToLocalTransform.l; + len = dx*dx + dy*dy + dz*dz; + if (len > max) max = len; + drawUnit.setVertexConstantsFromNumbers(0, cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l, camera.farClipping/Math.sqrt(max)); + } + + /** + * Returns a Surface by given alias. You can use SkyBox class constants as value of side parameter. They are following: SkyBox.LEFT, SkyBox.RIGHT, SkyBox.BACK, SkyBox.FRONT, SkyBox.BOTTOM, SkyBox.TOP. + * @param side Surface alias. + * @return Surface by given alias. + */ + public function getSide(side:String):Surface { + switch (side) { + case LEFT: + return leftSurface; + break; + case RIGHT: + return rightSurface; + break; + case BACK: + return backSurface; + break; + case FRONT: + return frontSurface; + break; + case BOTTOM: + return bottomSurface; + break; + case TOP: + return topSurface; + break; + } + return null; + } + + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:SkyBox = new SkyBox(0); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + // Clone marks + var src:SkyBox = source as SkyBox; + for (var i:int = 0; i < src._surfacesLength; i++) { + var surface:Surface = src._surfaces[i]; + var newSurface:Surface = _surfaces[i]; + if (surface == src.leftSurface) { + leftSurface = newSurface; + } else if (surface == src.rightSurface) { + rightSurface = newSurface; + } else if (surface == src.backSurface) { + backSurface = newSurface; + } else if (surface == src.frontSurface) { + frontSurface = newSurface; + } else if (surface == src.bottomSurface) { + bottomSurface = newSurface; + } else if (surface == src.topSurface) { + topSurface = newSurface; + } + } + } + + } +} diff --git a/src/alternativa/engine3d/objects/Sprite3D.as b/src/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..cfd0288 --- /dev/null +++ b/src/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,333 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Sprite3D is a flat Object3D always turned in to the camera. + */ + public class Sprite3D extends Object3D { + static private const geometries:Dictionary = new Dictionary(); + + static private var transformProcedureStatic:Procedure = new Procedure([ + // Pivot + "sub t0.z, i0.x, c3.x", + "sub t0.w, i0.y, c3.y", + // Width and height + "mul t0.z, t0.z, c3.z", + "mul t0.w, t0.w, c3.w", + // Rotation + "mov t1.z, c4.w", + "sin t1.x, t1.z", // sin + "cos t1.y, t1.z", // cos + "mul t1.z, t0.z, t1.y", // x*cos + "mul t1.w, t0.w, t1.x", // y*sin + "sub t0.x, t1.z, t1.w", // X + "mul t1.z, t0.z, t1.x", // x*sin + "mul t1.w, t0.w, t1.y", // y*cos + "add t0.y, t1.z, t1.w", // Y + // Offset + "add t0.x, t0.x, c4.x", + "add t0.y, t0.y, c4.y", + "add t0.z, i0.z, c4.z", + "mov t0.w, i0.w", + // Transform to local coordinates + "dp4 o0.x, t0, c0", + "dp4 o0.y, t0, c1", + "dp4 o0.z, t0, c2", + "mov o0.w, t0.w", + // Declaration + "#c0=trans1", + "#c1=trans2", + "#c2=trans3", + "#c3=size", // originX, originY, width, height + "#c4=coords", // x, y, z, rotation + ]); + + static private var deltaTransformProcedureStatic:Procedure = new Procedure([ + // Rotation + "mov t1.z, c4.w", + "sin t1.x, t1.z", // sin + "cos t1.y, t1.z", // cos + "mul t1.z, i0.x, t1.y", // x*cos + "mul t1.w, i0.y, t1.x", // y*sin + "sub t0.x, t1.z, t1.w", // X + "mul t1.z, i0.x, t1.x", // x*sin + "mul t1.w, i0.y, t1.y", // y*cos + "add t0.y, t1.z, t1.w", // Y + "mov t0.z, i0.z", + "mov t0.w, i0.w", + // Transform to local coordinates + "dp3 o0.x, t0, c0", + "dp3 o0.y, t0, c1", + "dp3 o0.z, t0, c2", + // Declaration + "#c0=trans1", + "#c1=trans2", + "#c2=trans3", + "#c3=size", // originX, originY, width, height + "#c4=coords" // x, y, z, rotation + ]); + + /** + * Horizontal coordinate in the Sprite3D plane which defines what part of the plane will placed in x = 0 of the Sprite3D object. The dimension considered with UV-coordinates. + * Thus, if originX = 0, image will drawn from 0 to the right, if originX = -1 – to the left. + * And image will drawn in the center of the Sprite3D, if originX = 0.5. + */ + public var originX:Number = 0.5; + + /** + * Vertical coordinate in the Sprite3D plane which defines what part of the plane will placed in y = 0 of the Sprite3D object. The dimension considered with UV-coordinates. + * Thus, if originY = 0, image will drawn from 0 to the bottom, if originY = -1 – to the top. + * And image will drawn in the center of the Sprite3D, if originY = 0.5. + */ + public var originY:Number = 0.5; + /** + * Rotation in the screen plane, defines in radians. + */ + public var rotation:Number = 0; + + /** + * Width. + */ + public var width:Number; + /** + * Height. + */ + public var height:Number; + + /** + * If true, screen size of a Sprite3D will have perspective correction according to distance to a camera. Otherwise Sprite3D will have fixed size with no dependence on point of view. + */ + public var perspectiveScale:Boolean = true; + + /** + * If true, Sprite3D will drawn over all the rest objects which uses z-buffer sorting. + */ + public var alwaysOnTop:Boolean = false; + + /** + * @private + */ + alternativa3d var surface:Surface; + + /** + * Creates a new Sprite3D instance. + * @param width Width. + * @param height Height + * @param material Material. + * @see alternativa.engine3d.materials.Material + */ + public function Sprite3D(width:Number, height:Number, material:Material = null) { + this.width = width; + this.height = height; + surface = new Surface(); + surface.object = this; + this.material = material; + surface.indexBegin = 0; + surface.numTriangles = 2; + // Transform to the local space + transformProcedure = transformProcedureStatic; + // Transformation of the vector to the local space. + deltaTransformProcedure = deltaTransformProcedureStatic; + } + + /** + * Material of the Sprite3D. + * @see alternativa.engine3d.materials.Material + */ + public function get material():Material { + return surface.material; + } + + /** + * @private + */ + public function set material(value:Material):void { + surface.material = value; + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + if (surface.material != null) surface.material.fillResources(resources, resourceType); + super.fillResources(resources, hierarchy, resourceType); + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + var geometry:Geometry = getGeometry(camera.context3D); + if (surface.material != null) surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, alwaysOnTop ? Renderer.NEXT_LAYER : -1); + // Mouse events. + if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure); + } + + /** + * @private + */ + override alternativa3d function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void { + // Average size + var scale:Number = Math.sqrt(localToCameraTransform.a*localToCameraTransform.a + localToCameraTransform.e*localToCameraTransform.e + localToCameraTransform.i*localToCameraTransform.i); + scale += Math.sqrt(localToCameraTransform.b*localToCameraTransform.b + localToCameraTransform.f*localToCameraTransform.f + localToCameraTransform.j*localToCameraTransform.j); + scale += Math.sqrt(localToCameraTransform.c*localToCameraTransform.c + localToCameraTransform.g*localToCameraTransform.g + localToCameraTransform.k*localToCameraTransform.k); + scale /= 3; + // Distance dependence + if (!perspectiveScale && !camera.orthographic) scale *= localToCameraTransform.l/camera.focalLength; + // Set the constants + drawUnit.setVertexConstantsFromTransform(0, cameraToLocalTransform); + drawUnit.setVertexConstantsFromNumbers(3, originX, originY, width*scale, height*scale); + drawUnit.setVertexConstantsFromNumbers(4, localToCameraTransform.d, localToCameraTransform.h, localToCameraTransform.l, rotation); + } + + /** + * @private + */ + alternativa3d function getGeometry(context:Context3D):Geometry { + var geometry:Geometry = geometries[context]; + if (geometry == null) { + geometry = new Geometry(4); + + var attributes:Array = new Array(); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.NORMAL; + attributes[4] = VertexAttributes.NORMAL; + attributes[5] = VertexAttributes.NORMAL; + attributes[6] = VertexAttributes.TEXCOORDS[0]; + attributes[7] = VertexAttributes.TEXCOORDS[0]; + attributes[8] = VertexAttributes.TEXCOORDS[1]; + attributes[9] = VertexAttributes.TEXCOORDS[1]; + attributes[10] = VertexAttributes.TEXCOORDS[2]; + attributes[11] = VertexAttributes.TEXCOORDS[2]; + attributes[12] = VertexAttributes.TEXCOORDS[3]; + attributes[13] = VertexAttributes.TEXCOORDS[3]; + attributes[14] = VertexAttributes.TEXCOORDS[4]; + attributes[15] = VertexAttributes.TEXCOORDS[4]; + attributes[16] = VertexAttributes.TEXCOORDS[5]; + attributes[17] = VertexAttributes.TEXCOORDS[5]; + attributes[18] = VertexAttributes.TEXCOORDS[6]; + attributes[19] = VertexAttributes.TEXCOORDS[6]; + attributes[20] = VertexAttributes.TEXCOORDS[7]; + attributes[21] = VertexAttributes.TEXCOORDS[7]; + attributes[22] = VertexAttributes.TANGENT4; + attributes[23] = VertexAttributes.TANGENT4; + attributes[24] = VertexAttributes.TANGENT4; + attributes[25] = VertexAttributes.TANGENT4; + geometry.addVertexStream(attributes); + + geometry.setAttributeValues(VertexAttributes.POSITION, Vector.([0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0])); + geometry.setAttributeValues(VertexAttributes.NORMAL, Vector.([0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[1], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[2], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[3], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[4], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[5], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[6], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[7], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + + geometry.indices = Vector.([0, 1, 3, 2, 3, 1]); + + geometry.upload(context); + geometries[context] = geometry; + } + return geometry; + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Sprite3D = new Sprite3D(width, height); + res.clonePropertiesFrom(this); + return res; + } + + /** + * @inheritDoc + */ + override protected function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + var src:Sprite3D = source as Sprite3D; + width = src.width; + height = src.height; + // autoSize = src.autoSize; + material = src.material; + originX = src.originX; + originY = src.originY; + rotation = src.rotation; + perspectiveScale = src.perspectiveScale; + alwaysOnTop = src.alwaysOnTop; + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + var ww:Number = width; + var hh:Number = height; + // Calculate local radius. + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*ww; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*hh; + var radius:Number = Math.sqrt(w*w + h*h); + var cx:Number = 0; + var cy:Number = 0; + var cz:Number = 0; + if (transform != null) { + // Find average size + var ax:Number = transform.a; + var ay:Number = transform.e; + var az:Number = transform.i; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = transform.b; + ay = transform.f; + az = transform.j; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = transform.c; + ay = transform.g; + az = transform.k; + size += Math.sqrt(ax*ax + ay*ay + az*az); + radius *= size/3; + cx = transform.d; + cy = transform.h; + cz = transform.l; + } + if (cx - radius < boundBox.minX) boundBox.minX = cx - radius; + if (cx + radius > boundBox.maxX) boundBox.maxX = cx + radius; + if (cy - radius < boundBox.minY) boundBox.minY = cy - radius; + if (cy + radius > boundBox.maxY) boundBox.maxY = cy + radius; + if (cz - radius < boundBox.minZ) boundBox.minZ = cz - radius; + if (cz + radius > boundBox.maxZ) boundBox.maxZ = cz + radius; + } + } +} diff --git a/src/alternativa/engine3d/objects/Surface.as b/src/alternativa/engine3d/objects/Surface.as new file mode 100644 index 0000000..7d3aef5 --- /dev/null +++ b/src/alternativa/engine3d/objects/Surface.as @@ -0,0 +1,60 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.Material; + + use namespace alternativa3d; + + /** + * Surface is a set of triangles within Mesh object or instance of kindred class like Skin. + * Surface is a entity associated with one material, so different surfaces within one mesh can have different materials. + */ + public class Surface { + + /** + * Material. + */ + public var material:Material; + + /** + * Index of the vertex with which surface starts within index buffer of object's geometry. + * @see alternativa.engine3d.resources.Geometry#indices + */ + public var indexBegin:int = 0; + + /** + * Number of triangles which form this surface. + */ + public var numTriangles:int = 0; + + /** + * @private + */ + alternativa3d var object:Object3D; + + /** + * Returns a copy of this surface. + * @return A copy of this surface. + */ + public function clone():Surface { + var res:Surface = new Surface(); + res.object = object; + res.material = material; + res.indexBegin = indexBegin; + res.numTriangles = numTriangles; + return res; + } + + } +} diff --git a/src/alternativa/engine3d/objects/WireFrame.as b/src/alternativa/engine3d/objects/WireFrame.as new file mode 100644 index 0000000..f108d81 --- /dev/null +++ b/src/alternativa/engine3d/objects/WireFrame.as @@ -0,0 +1,409 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.A3DUtils; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.WireGeometry; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.geom.Vector3D; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Wireframe is an Object3D which consists of solid lines. Line draws with z-buffer but has no perspective correction, so it has fixed thickness. + * Wireframe can be built on Mesh geometry as well as on sequence of points. + */ + public class WireFrame extends Object3D { + + private static const cachedPrograms:Dictionary = new Dictionary(true); + /** + * @private + */ + alternativa3d var shaderProgram:ShaderProgram; + private var cachedContext3D:Context3D; + + private static function initProgram():ShaderProgram { + var vertexShader:Linker = new Linker(Context3DProgramType.VERTEX); + var transform:Procedure = new Procedure(); + transform.compileFromArray([ + "mov t0, a0", // it is because a0.w holds offset direction + "mov t0.w, c0.y", // replace w with 1 + "m34 t0.xyz, t0, c2", // Transform p0 to the camera coordinates + "m34 t1.xyz, a1, c2", // Transform p1 to the camera coordinates + "sub t2, t1.xyz, t0.xyz", // L = p1 - p0 + // if point places behind the camera, it need to be cut to point lies in the nearClipping plane + "slt t5.x, t0.z, c1.z", // behind = (Q0.z < Camera.near) ? 1 : 0 + "sub t5.y, c0.y, t5.x", // !behind = 1 - behind + //find intersection point of section and nearClipping plane + "add t4.x, t0.z, c0.z", // p0.z + Camera.nearCliping + "sub t4.y, t0.z, t1.z", // p0.z - p1.z + "add t4.y, t4.y, c0.w", // Add some small value for cases of Q0.z = Q1.z + "div t4.z, t4.x, t4.y", // t = ( p0.z - near ) / ( p0.z - p1.z ) + "mul t4.xyz, t4.zzz, t2.xyz", // t(L) + "add t3.xyz, t0.xyz, t4.xyz", // pClipped = p0 + t(L) + // Clip p0 + "mul t0, t0, t5.y", // !behind * p0 + "mul t3.xyz, t3.xyz, t5.x", // behind * pClipped + "add t0, t0, t3.xyz", // newp0 = p0 + pClipped + // Calculate vector of thickness direction + "sub t2, t1.xyz, t0.xyz", // L = p1 - p0 + "crs t3.xyz, t2, t0", // S = L x D + "nrm t3.xyz, t3.xyz", // normalize( S ) + "mul t3.xyz, t3.xyz, a0.w", // Direction correction + "mul t3.xyz, t3.xyz, c1.w", // S *= weight + // Scale vector depends on distance to the camera + "mul t4.x, t0.z, c1.x", // distance *= vpsod + "mul t3.xyz, t3.xyz, t4.xxx", // S.xyz *= pixelScaleFactor + "add t0.xyz, t0.xyz, t3.xyz", // p0 + S + "m44 o0, t0, c5" // projection + ]); + transform.assignVariableName(VariableType.ATTRIBUTE, 0, "pos1"); + transform.assignVariableName(VariableType.ATTRIBUTE, 1, "pos2"); + transform.assignVariableName(VariableType.CONSTANT, 0, "ZERO"); + transform.assignVariableName(VariableType.CONSTANT, 1, "consts"); + transform.assignVariableName(VariableType.CONSTANT, 2, "worldView", 3); + transform.assignVariableName(VariableType.CONSTANT, 5, "proj", 4); + vertexShader.addProcedure(transform); + vertexShader.link(); + + var fragmentShader:Linker = new Linker(Context3DProgramType.FRAGMENT); + var fp:Procedure = new Procedure(); + fp.compileFromArray(["mov o0, c0"]); + fp.assignVariableName(VariableType.CONSTANT, 0, "color"); + fragmentShader.addProcedure(fp); + fragmentShader.link(); + + return new ShaderProgram(vertexShader, fragmentShader); + } + + /** + * Thickness. + */ + public var thickness:Number = 1; + + /** + * @private + */ + alternativa3d var _colorVec:Vector. = new Vector.(4, true); + /** + * @private + */ + alternativa3d var geometry:WireGeometry; + + /** + * The constructor did not make any geometry, so if you need class instance - use static methods createLinesList(), + * createLineStrip() and createEdges() which return one. + * + * @see #createLinesList() + * @see #createLineStrip() + * @see #createEdges() + */ + public function WireFrame(color:uint = 0, alpha:Number = 1, thickness:Number = 0.5) { + this.color = color; + this.alpha = alpha; + this.thickness = thickness; + geometry = new WireGeometry(); + } + + /** + * Transparency. + */ + public function get alpha():Number { + return _colorVec[3]; + } + + /** + * @private + */ + public function set alpha(value:Number):void { + _colorVec[3] = value; + } + + /** + * Color + */ + public function get color():uint { + return (_colorVec[0]*255 << 16) | (_colorVec[1]*255 << 8) | (_colorVec[2]*255); + } + + /** + * @private + */ + public function set color(value:uint):void { + _colorVec[0] = ((value >> 16) & 0xff)/255; + _colorVec[1] = ((value >> 8) & 0xff)/255; + _colorVec[2] = (value & 0xff)/255; + + } + + /** + * @private + */ + override alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + if (geometry != null) { + geometry.updateBoundBox(boundBox, transform); + } + } + + /** + * @private + */ + alternativa3d override function collectDraws(camera:Camera3D, lights:Vector., lightsLength:int):void { + if (camera.context3D != cachedContext3D) { + cachedContext3D = camera.context3D; + shaderProgram = cachedPrograms[cachedContext3D]; + if (shaderProgram == null) { + shaderProgram = initProgram(); + shaderProgram.upload(cachedContext3D); + cachedPrograms[cachedContext3D] = shaderProgram; + } + } + geometry.getDrawUnits(camera, _colorVec, thickness, this, shaderProgram); + } + + /** + * @private + */ + alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void { + super.fillResources(resources, hierarchy, resourceType); + if (A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(geometry)) as Class, resourceType)) { + resources[geometry] = true; + } + } + + /** + * Creates and returns a new WireFrame instance consists of segments for each couple of points in the given array. + * + * @param points Set of point couples. One point of couple defines start of line segment and another one - end of the line. + * @param color Color of the line. + * @param alpha Transparency. + * @param thickness Thickness. + * @return A new WireFrame instance. + */ + public static function createLinesList(points:Vector., color:uint = 0, alpha:Number = 1, thickness:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var p0:Vector3D; + var p1:Vector3D; + var geometry:WireGeometry = result.geometry; + for (var i:uint = 0, count:uint = points.length - 1; i < count; i += 2) { + p0 = points[i]; + p1 = points[i + 1]; + geometry.addLine(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); + } + result.calculateBoundBox(); + return result; + } + + /** + * Creates and returns a new WireFrame instance of solid line built on point sequence. + * + * @param points Point sequence. + * @param color Color of the line. + * @param alpha Transparency. + * @param thickness Thickness. + * @return A new WireFrame instance. + */ + public static function createLineStrip(points:Vector., color:uint = 0, alpha:Number = 1, thickness:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var p0:Vector3D; + var p1:Vector3D; + var geometry:WireGeometry = result.geometry; + for (var i:uint = 0, count:uint = points.length - 1; i < count; i++) { + // TODO : don't get vector value twice + p0 = points[i]; + p1 = points[i + 1]; + geometry.addLine(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); + } + result.calculateBoundBox(); + return result; + } + + /** + * Creates and returns a new WireFrame instance built on edges of given Mesh. + * + * @param mesh Source of geometry. + * @param color Color of the line. + * @param alpha Transparency. + * @param thickness Thickness. + * @return A new WireFrame instance. + */ + public static function createEdges(mesh:Mesh, color:uint = 0, alpha:Number = 1, thickness:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var geometry:Geometry = mesh.geometry; + var resultGeometry:WireGeometry = result.geometry; + var edges:Dictionary = new Dictionary(); + var indices:Vector. = geometry.indices; + var vertices:Vector. = geometry.getAttributeValues(VertexAttributes.POSITION); + // Loop over all the faces of mesh, create lines like 0-1-2-0 + for (var i:int = 0, count:int = indices.length; i < count; i += 3) { + var index:uint = indices[i]*3; + var v1x:Number = vertices[index]; + index++; + var v1y:Number = vertices[index]; + index++; + var v1z:Number = vertices[index]; + index = indices[int(i + 1)]*3; + var v2x:Number = vertices[index]; + index++; + var v2y:Number = vertices[index]; + index++; + var v2z:Number = vertices[index]; + index = indices[int(i + 2)]*3; + var v3x:Number = vertices[index]; + index++; + var v3y:Number = vertices[index]; + index++; + var v3z:Number = vertices[index]; + if (checkEdge(edges, v1x, v1y, v1z, v2x, v2y, v2z)) { + resultGeometry.addLine(v1x, v1y, v1z, v2x, v2y, v2z); + } + if (checkEdge(edges, v2x, v2y, v2z, v3x, v3y, v3z)) { + resultGeometry.addLine(v2x, v2y, v2z, v3x, v3y, v3z); + } + if (checkEdge(edges, v1x, v1y, v1z, v3x, v3y, v3z)) { + resultGeometry.addLine(v1x, v1y, v1z, v3x, v3y, v3z); + } + } + result.calculateBoundBox(); + result._x = mesh._x; + result._y = mesh._y; + result._z = mesh._z; + result._rotationX = mesh._rotationX; + result._rotationY = mesh._rotationY; + result._rotationZ = mesh._rotationZ; + result._scaleX = mesh._scaleX; + result._scaleY = mesh._scaleY; + result._scaleZ = mesh._scaleZ; + return result; + } + + alternativa3d static function createNormals(mesh:Mesh, color:uint = 0, alpha:Number = 1, thickness:Number = 1, length:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var geometry:Geometry = mesh.geometry; + var resultGeometry:WireGeometry = result.geometry; + var vertices:Vector. = geometry.getAttributeValues(VertexAttributes.POSITION); + var normals:Vector. = geometry.getAttributeValues(VertexAttributes.NORMAL); + var numVertices:uint = geometry._numVertices; + for (var i:int = 0; i < numVertices; i++) { + var index:uint = i*3; + resultGeometry.addLine( + vertices[index], vertices[int(index + 1)], vertices[int(index + 2)], + vertices[index] + normals[index]*length, vertices[int(index + 1)] + normals[int(index + 1)]*length, vertices[int(index + 2)] + normals[int(index + 2)]*length); + } + result.calculateBoundBox(); + result._x = mesh._x; + result._y = mesh._y; + result._z = mesh._z; + result._rotationX = mesh._rotationX; + result._rotationY = mesh._rotationY; + result._rotationZ = mesh._rotationZ; + result._scaleX = mesh._scaleX; + result._scaleY = mesh._scaleY; + result._scaleZ = mesh._scaleZ; + return result; + } + + /** + * @private + */ + alternativa3d static function createTangents(mesh:Mesh, color:uint = 0, alpha:Number = 1, thickness:Number = 1, length:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var geometry:Geometry = mesh.geometry; + var resultGeometry:WireGeometry = result.geometry; + var vertices:Vector. = geometry.getAttributeValues(VertexAttributes.POSITION); + var tangents:Vector. = geometry.getAttributeValues(VertexAttributes.TANGENT4); + var numVertices:uint = geometry._numVertices; + for (var i:int = 0; i < numVertices; i++) { + var index:uint = i*3; + resultGeometry.addLine( + vertices[index], vertices[int(index + 1)], vertices[int(index + 2)], + vertices[index] + tangents[int(i*4)]*length, vertices[int(index + 1)] + tangents[int(i*4 + 1)]*length, vertices[int(index + 2)] + tangents[int(i*4 + 2)]*length); + } + result.calculateBoundBox(); + result._x = mesh._x; + result._y = mesh._y; + result._z = mesh._z; + result._rotationX = mesh._rotationX; + result._rotationY = mesh._rotationY; + result._rotationZ = mesh._rotationZ; + result._scaleX = mesh._scaleX; + result._scaleY = mesh._scaleY; + result._scaleZ = mesh._scaleZ; + return result; + } + + /** + * @private + */ + alternativa3d static function createBinormals(mesh:Mesh, color:uint = 0, alpha:Number = 1, thickness:Number = 1, length:Number = 1):WireFrame { + var result:WireFrame = new WireFrame(color, alpha, thickness); + var geometry:Geometry = mesh.geometry; + var resultGeometry:WireGeometry = result.geometry; + var vertices:Vector. = geometry.getAttributeValues(VertexAttributes.POSITION); + var tangents:Vector. = geometry.getAttributeValues(VertexAttributes.TANGENT4); + var normals:Vector. = geometry.getAttributeValues(VertexAttributes.NORMAL); + var numVertices:uint = geometry._numVertices; + for (var i:int = 0; i < numVertices; i++) { + var index:uint = i*3; + var normal:Vector3D = new Vector3D(normals[index], normals[int(index + 1)], normals[int(index + 2)]); + var tangent:Vector3D = new Vector3D(tangents[int(i*4)], tangents[int(i*4 + 1)], tangents[int(i*4 + 2)]); + var binormal:Vector3D = normal.crossProduct(tangent); + + binormal.scaleBy(tangents[int(i*4 + 3)]); + binormal.normalize(); + resultGeometry.addLine( + vertices[index], vertices[int(index + 1)], vertices[int(index + 2)], + vertices[index] + binormal.z*length, vertices[int(index + 1)] + binormal.z*length, vertices[int(index + 2)] + binormal.z*length); + } + result.calculateBoundBox(); + result._x = mesh._x; + result._y = mesh._y; + result._z = mesh._z; + result._rotationX = mesh._rotationX; + result._rotationY = mesh._rotationY; + result._rotationZ = mesh._rotationZ; + result._scaleX = mesh._scaleX; + result._scaleY = mesh._scaleY; + result._scaleZ = mesh._scaleZ; + return result; + } + + private static function checkEdge(edges:Dictionary, v1x:Number, v1y:Number, v1z:Number, v2x:Number, v2y:Number, v2z:Number):Boolean { + var str:String; + if (v1x*v1x + v1y*v1y + v1z*v1z < v2x*v2x + v2y*v2y + v2z*v2z) { + str = v1x.toString() + v1y.toString() + v1z.toString() + v2x.toString() + v2y.toString() + v2z.toString(); + } else { + str = v2x.toString() + v2y.toString() + v2z.toString() + v1x.toString() + v1y.toString() + v1z.toString(); + } + if (edges[str]) return false; + edges[str] = true; + return true; + } + + } +} diff --git a/src/alternativa/engine3d/primitives/Box.as b/src/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..a0e7119 --- /dev/null +++ b/src/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,283 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * A cuboid primitive. + */ + public class Box extends Mesh { + /** + * Creates a new Box instance. + * @param width Width. Can not be less than 0. + * @param length Length. Can not be less than 0. + * @param height Height. Can not be less than 0. + * @param widthSegments Number of subdivisions along x-axis. + * @param lengthSegments Number of subdivisions along y-axis. + * @param heightSegments Number of subdivisions along z-axis. + * @param reverse If true, face normals will turned inside, so the box will be visible from inside only. Otherwise, the normals will turned outside. + * @param material Material. + */ + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, material:Material = null) { + if (widthSegments <= 0 || lengthSegments <= 0 || heightSegments <= 0) return; + var indices:Vector. = new Vector.(); + 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 halfWidth:Number = width*0.5; + var halfLength:Number = length*0.5; + var halfHeight: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:ByteArray = new ByteArray(); + vertices.endian = Endian.LITTLE_ENDIAN; + var offset:uint = 0; + var offsetFromPos:Number = 28; + // Bottom face + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices.writeFloat(x*ws - halfWidth); + vertices.writeFloat(y*ls - halfLength); + vertices.writeFloat(-halfHeight); + vertices.writeFloat((widthSegments - x)*wd); + vertices.writeFloat((lengthSegments - y)*ld); + vertices.length = vertices.position += offsetFromPos; + } + } + offset = vertices.position; + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + createFace(indices, vertices, (x + 1)*lp + y + 1, (x + 1)*lp + y, x*lp + y, x*lp + y + 1, 0, 0, -1, halfHeight, 0,-1,0,0, reverse); + } + } + } + vertices.position = offset; + var o:uint = wp*lp; + // Top face + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices.writeFloat(x*ws - halfWidth); + vertices.writeFloat(y*ls - halfLength); + vertices.writeFloat(halfHeight); + vertices.writeFloat(x*wd); + vertices.writeFloat((lengthSegments - y)*ld); + vertices.length = vertices.position += offsetFromPos; + } + } + offset = vertices.position; + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + createFace(indices, vertices, o + x*lp + y, o + (x + 1)*lp + y, o + (x + 1)*lp + y + 1, o + x*lp + y + 1, 0, 0, 1, halfHeight,0,-1,0,0, reverse); + } + } + } + vertices.position = offset; + o += wp*lp; + // Back face + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices.writeFloat(x*ws - halfWidth); + vertices.writeFloat(-halfLength); + vertices.writeFloat(z*hs - halfHeight); + vertices.writeFloat(x*wd); + vertices.writeFloat((heightSegments - z)*hd); + vertices.length = vertices.position += offsetFromPos; + } + } + + offset = vertices.position; + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + createFace(indices, vertices, o + x*hp + z, o + (x + 1)*hp + z, o + (x + 1)*hp + z + 1, o + x*hp + z + 1, 0, -1, 0, halfLength,0,0,-1,0, reverse); + } + } + } + vertices.position = offset; + o += wp*hp; + // Front face + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices.writeFloat(x*ws - halfWidth); + vertices.writeFloat(halfLength); + vertices.writeFloat(z*hs - halfHeight); + vertices.writeFloat((widthSegments - x)*wd); + vertices.writeFloat((heightSegments - z)*hd); + vertices.length = vertices.position += offsetFromPos; + } + } + offset = vertices.position; + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + createFace(indices, vertices, o + x*hp + z, o + x*hp + z + 1, o + (x + 1)*hp + z + 1, o + (x + 1)*hp + z, 0, 1, 0, halfLength,0,0,-1,0, reverse); + } + } + } + vertices.position = offset; + o += wp*hp; + // Left face + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices.writeFloat(-halfWidth); + vertices.writeFloat(y*ls - halfLength); + vertices.writeFloat(z*hs - halfHeight); + vertices.writeFloat((lengthSegments - y)*ld); + vertices.writeFloat((heightSegments - z)*hd); + vertices.length = vertices.position += offsetFromPos; + } + } + offset = vertices.position; + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + createFace(indices, vertices, o + y*hp + z, o + y*hp + z + 1, o + (y + 1)*hp + z + 1, o + (y + 1)*hp + z, -1, 0, 0, halfWidth, 0,0,-1,0,reverse); + } + } + } + vertices.position = offset; + o += lp*hp; + // Right face + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices.writeFloat(halfWidth); + vertices.writeFloat(y*ls - halfLength); + vertices.writeFloat(z*hs - halfHeight); + vertices.writeFloat(y*ld); + vertices.writeFloat((heightSegments - z)*hd); + vertices.length = vertices.position += offsetFromPos; + } + } + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + createFace(indices, vertices, o + y*hp + z, o + (y + 1)*hp + z, o + (y + 1)*hp + z + 1, o + y*hp + z + 1, 1, 0, 0, halfWidth,0,0,-1,0, reverse); + } + } + } + + // Set bounds + geometry = new Geometry(); + geometry._indices = indices; + var attributes:Array = new Array; + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.TEXCOORDS[0]; + attributes[4] = VertexAttributes.TEXCOORDS[0]; + attributes[5] = VertexAttributes.NORMAL; + attributes[6] = VertexAttributes.NORMAL; + attributes[7] = VertexAttributes.NORMAL; + attributes[8] = VertexAttributes.TANGENT4; + attributes[9] = VertexAttributes.TANGENT4; + attributes[10] = VertexAttributes.TANGENT4; + attributes[11] = VertexAttributes.TANGENT4; + + geometry.addVertexStream(attributes); + geometry._vertexStreams[0].data = vertices; + geometry._numVertices = vertices.length/48; + addSurface(material, 0, indices.length/3); + + boundBox = new BoundBox(); + boundBox.minX = -halfWidth; + boundBox.minY = -halfLength; + boundBox.minZ = -halfHeight; + boundBox.maxX = halfWidth; + boundBox.maxY = halfLength; + boundBox.maxZ = halfHeight; + } + + private function createFace(indices:Vector., vertices:ByteArray, a:int, b:int, c:int, d:int, nx:Number, ny:Number, nz:Number, no:Number, tx:Number, ty:Number, tz:Number, tw:Number, reverse:Boolean):void { + var v:int; + if (reverse) { + nx = -nx; + ny = -ny; + nz = -nz; + no = -no; + v = a; + a = d; + d = v; + v = b; + b = c; + c = v; + } + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(a); + indices.push(c); + indices.push(d); + vertices.position = a*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = b*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = c*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = d*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Box = new Box(0, 0, 0, 0, 0, 0); + res.clonePropertiesFrom(this); + return res; + } + } +} diff --git a/src/alternativa/engine3d/primitives/GeoSphere.as b/src/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..0a2a361 --- /dev/null +++ b/src/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,407 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.resources.Geometry; + + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * A spherical primitive consists of set of equal triangles. + */ + public class GeoSphere extends Mesh { + + /** + * Creates a new GeoSphere instance. + * + * @param radius Radius of a sphere. Can't be less than 0. + * @param segments Level of subdivision. + * @param reverse If true, face normals will turned inside, so the sphere will be visible from inside only. Otherwise, the normals will turned outside. + * @param material Material. If you use TextureMaterial, it is need to set repeat property to true. + */ + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false, material:Material = null) { + if (segments == 0) return; + radius = (radius < 0) ? 0 : radius; + var indices:Vector. = new Vector.(); + var sections:uint = 20; + var deg180:Number = Math.PI; + var deg360:Number = Math.PI*2; + var vertices:Vector. = new Vector.(); + var uvs:Vector. = new Vector.(); + var i:uint; + var f:uint; + var theta:Number; + var sin:Number; + var cos:Number; + // distance along z-axis to the top and bottom pole hats + var subz:Number = 4.472136E-001*radius; + //radius along subz distance. + var subrad:Number = 2*subz; + vertices.push(new Vector3D(0, 0, radius, -1)); + uvs.length += 2; + // Make vertices of the top pole hats + for (i = 0; i < 5; i++) { + theta = deg360*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices.push(new Vector3D(subrad*cos, subrad*sin, subz, -1)); + uvs.length += 2; + } + // Make vertices of the bottom pole hats + for (i = 0; i < 5; i++) { + theta = deg180*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices.push(new Vector3D(subrad*cos, subrad*sin, -subz, -1)); + uvs.length += 2; + } + vertices.push(new Vector3D(0, 0, -radius, -1)); + uvs.length += 2; + for (i = 1; i < 6; i++) { + interpolate(0, i, segments, vertices, uvs); + + } + for (i = 1; i < 6; i++) { + interpolate(i, i%5 + 1, segments, vertices, uvs); + } + for (i = 1; i < 6; i++) { + interpolate(i, i + 5, segments, vertices, uvs); + } + for (i = 1; i < 6; i++) { + interpolate(i, (i + 3)%5 + 6, segments, vertices, uvs); + } + for (i = 1; i < 6; i++) { + interpolate(i + 5, i%5 + 6, segments, vertices, uvs); + } + for (i = 6; i < 11; i++) { + interpolate(11, i, segments, vertices, uvs); + } + 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, uvs); + } + } + 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, uvs); + } + } + 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, uvs); + } + } + 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, uvs); + } + } + // Make faces + 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:Vector3D = vertices[aIndex]; + var b:Vector3D = vertices[bIndex]; + var c:Vector3D = 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; + // Pole + 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; + } + // Duplication + if (a.w > 0 && uvs[aIndex*2] != au) { + a = createVertex(a.x, a.y, a.z); + aIndex = vertices.push(a) - 1; + + } + uvs[aIndex*2] = au; + uvs[aIndex*2 + 1] = av; + a.w = 1; + if (b.w > 0 && uvs[bIndex*2] != bu) { + b = createVertex(b.x, b.y, b.z); + bIndex = vertices.push(b) - 1; + } + uvs[bIndex*2] = bu; + uvs[bIndex*2 + 1] = bv; + b.w = 1; + if (c.w > 0 && uvs[cIndex*2] != cu) { + c = createVertex(c.x, c.y, c.z); + cIndex = vertices.push(c) - 1; + } + uvs[cIndex*2] = cu; + uvs[cIndex*2 + 1] = cv; + c.w = 1; + if (reverse) { + indices.push(aIndex, cIndex, bIndex); + } else { + indices.push(aIndex, bIndex, cIndex); + } + 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; + } + // Duplication + if (a.w > 0 && uvs[aIndex*2] != au) { + a = createVertex(a.x, a.y, a.z); + aIndex = vertices.push(a) - 1; + } + uvs[aIndex*2] = au; + uvs[aIndex*2 + 1] = av; + a.w = 1; + if (b.w > 0 && uvs[bIndex*2] != bu) { + b = createVertex(b.x, b.y, b.z); + bIndex = vertices.push(b) - 1; + } + uvs[bIndex*2] = bu; + uvs[bIndex*2 + 1] = bv; + b.w = 1; + if (c.w > 0 && uvs[cIndex*2] != cu) { + c = createVertex(c.x, c.y, c.z); + cIndex = vertices.push(c) - 1; + } + uvs[cIndex*2] = cu; + uvs[cIndex*2 + 1] = cv; + c.w = 1; + if (reverse) { + indices.push(aIndex, bIndex, cIndex); + } else { + indices.push(aIndex, cIndex, bIndex); + } + } + } + } + } + + var byteArray:ByteArray = new ByteArray(); + byteArray.endian = Endian.LITTLE_ENDIAN; + for (i = 0; i < vertices.length; i++) { + var v:Vector3D = vertices[i]; + byteArray.writeFloat(v.x); + byteArray.writeFloat(v.y); + byteArray.writeFloat(v.z); + byteArray.writeFloat(uvs[i*2]); + byteArray.writeFloat(uvs[i*2 + 1]); + byteArray.writeFloat(v.x / radius); + byteArray.writeFloat(v.y / radius); + byteArray.writeFloat(v.z / radius); + + var longitude:Number = deg360 * uvs[i*2]; + byteArray.writeFloat( +Math.sin (longitude)); + byteArray.writeFloat( -Math.cos (longitude)); + byteArray.writeFloat(0.0); + byteArray.writeFloat(-1.0); + } + + geometry = new Geometry(); + geometry._indices = indices; + var attributes:Array = new Array(); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.TEXCOORDS[0]; + attributes[4] = VertexAttributes.TEXCOORDS[0]; + attributes[5] = VertexAttributes.NORMAL; + attributes[6] = VertexAttributes.NORMAL; + attributes[7] = VertexAttributes.NORMAL; + attributes[8] = VertexAttributes.TANGENT4; + attributes[9] = VertexAttributes.TANGENT4; + attributes[10] = VertexAttributes.TANGENT4; + attributes[11] = VertexAttributes.TANGENT4; + + geometry.addVertexStream(attributes); + geometry._vertexStreams[0].data = byteArray; + geometry._numVertices = byteArray.length/48; + +// this.geometry.calculateFacesNormals(); + addSurface(material, 0, indices.length/3); + calculateBoundBox(); + } + + private function createVertex(x:Number, y:Number, z:Number):Vector3D { + var vertex:Vector3D = new Vector3D(); + vertex.x = x; + vertex.y = y; + vertex.z = z; + vertex.w = -1; + return vertex; + } + + private function interpolate(v1:uint, v2:uint, num:uint, vertices:Vector., uvs:Vector.):void { + if (num < 2) { + return; + } + var a:Vector3D = vertices[v1]; + var b:Vector3D = 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(new Vector3D((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin, -1)); + uvs.length += 2; + } + } + + 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 res:GeoSphere = new GeoSphere(1, 0); + res.clonePropertiesFrom(this); + return res; + } + } + +} diff --git a/src/alternativa/engine3d/primitives/Plane.as b/src/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..6830ae7 --- /dev/null +++ b/src/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,202 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.resources.Geometry; + + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + /** + * A plane primitive. + */ + public class Plane extends Mesh { + + /** + * Creates a new Plane instance. + * @param width Width. Can not be less than 0. + * @param length Length. Can not be less than 0. + * @param widthSegments Number of subdivisions along x-axis. + * @param lengthSegments Number of subdivisions along y-axis. + * @param twoSided If true, plane has surface for both sides: tob and bottom and only one otherwise. + * @param reverse If twoSided=false, reverse parameter determines for which side surface will be created. + * @param bottom Material of the bottom surface. + * @param top Material of the top surface. + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, bottom:Material = null, top:Material = null) { + if (widthSegments <= 0 || lengthSegments <= 0) return; + var indices:Vector. = new Vector.(); + var x:int; + var y:int; + var wEdges:int = widthSegments + 1; + var lEdges:int = lengthSegments + 1; + var halfWidth:Number = width*0.5; + var halfLength:Number = length*0.5; + var segmentUSize:Number = 1/widthSegments; + var segmentVSize:Number = 1/lengthSegments; + var segmentWidth:Number = width/widthSegments; + var segmentLength:Number = length/lengthSegments; + + var vertices:ByteArray = new ByteArray(); + vertices.endian = Endian.LITTLE_ENDIAN; + var offsetAdditionalData:Number = 28; + // Top face. + for (x = 0; x < wEdges; x++) { + for (y = 0; y < lEdges; y++) { + vertices.writeFloat(x*segmentWidth - halfWidth); + vertices.writeFloat(y*segmentLength - halfLength); + vertices.writeFloat(0); + vertices.writeFloat(x*segmentUSize); + vertices.writeFloat((lengthSegments - y)*segmentVSize); + vertices.length = vertices.position += offsetAdditionalData; + } + } + var lastPosition:uint = vertices.position; + for (x = 0; x < wEdges; x++) { + for (y = 0; y < lEdges; y++) { + if (x < widthSegments && y < lengthSegments) { + createFace(indices, vertices, x*lEdges + y, (x + 1)*lEdges + y, (x + 1)*lEdges + y + 1, x*lEdges + y + 1, 0, 0, 1, 1, 0, 0, -1, reverse); + } + } + } + + if (twoSided) { + vertices.position = lastPosition; + // Bottom face. + for (x = 0; x < wEdges; x++) { + for (y = 0; y < lEdges; y++) { + vertices.writeFloat(x*segmentWidth - halfWidth); + vertices.writeFloat(y*segmentLength - halfLength); + vertices.writeFloat(0); + vertices.writeFloat((widthSegments - x)*segmentUSize); + vertices.writeFloat((lengthSegments - y)*segmentVSize); + vertices.length = vertices.position += offsetAdditionalData; + } + } + var baseIndex:uint = wEdges*lEdges; + for (x = 0; x < wEdges; x++) { + for (y = 0; y < lEdges; y++) { + if (x < widthSegments && y < lengthSegments) { + createFace(indices, vertices, baseIndex + (x + 1)*lEdges + y + 1, baseIndex + (x + 1)*lEdges + y, baseIndex + x*lEdges + y, baseIndex + x*lEdges + y + 1, 0, 0, -1, -1, 0, 0, -1, reverse); + } + } + } + } + + // Set bounds + geometry = new Geometry(); + geometry._indices = indices; + var attributes:Array = new Array; + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.TEXCOORDS[0]; + attributes[4] = VertexAttributes.TEXCOORDS[0]; + attributes[5] = VertexAttributes.NORMAL; + attributes[6] = VertexAttributes.NORMAL; + attributes[7] = VertexAttributes.NORMAL; + attributes[8] = VertexAttributes.TANGENT4; + attributes[9] = VertexAttributes.TANGENT4; + attributes[10] = VertexAttributes.TANGENT4; + attributes[11] = VertexAttributes.TANGENT4; + + geometry.addVertexStream(attributes); + geometry._vertexStreams[0].data = vertices; + geometry._numVertices = vertices.length/48; + if (!twoSided) { + addSurface(top, 0, indices.length/3); + } else { + addSurface(top, 0, indices.length/6); + addSurface(bottom, indices.length/2 , indices.length/6); + } + + boundBox = new BoundBox(); + boundBox.minX = -halfWidth; + boundBox.minY = -halfLength; + boundBox.minZ = 0; + boundBox.maxX = halfWidth; + boundBox.maxY = halfLength; + boundBox.maxZ = 0; + } + + private function createFace(indices:Vector., vertices:ByteArray, a:int, b:int, c:int, d:int, nx:Number, ny:Number, nz:Number, tx:Number, ty:Number, tz:Number, tw:Number, reverse:Boolean):void { + var temp:int; + if (reverse) { + nx = -nx; + ny = -ny; + nz = -nz; + tw = -tw; + temp = a; + a = d; + d = temp; + temp = b; + b = c; + c = temp; + } + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(a); + indices.push(c); + indices.push(d); + vertices.position = a*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = b*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = c*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + vertices.position = d*48 + 20; + vertices.writeFloat(nx); + vertices.writeFloat(ny); + vertices.writeFloat(nz); + vertices.writeFloat(tx); + vertices.writeFloat(ty); + vertices.writeFloat(tz); + vertices.writeFloat(tw); + } + + /** + * @inheritDoc + */ + override public function clone():Object3D { + var res:Plane = new Plane(0, 0, 0, 0); + res.clonePropertiesFrom(this); + return res; + } + + } +} diff --git a/src/alternativa/engine3d/resources/ATFTextureResource.as b/src/alternativa/engine3d/resources/ATFTextureResource.as new file mode 100644 index 0000000..3130478 --- /dev/null +++ b/src/alternativa/engine3d/resources/ATFTextureResource.as @@ -0,0 +1,111 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.CubeTexture; + import flash.display3D.textures.Texture; + import flash.events.Event; + import flash.utils.ByteArray; + + use namespace alternativa3d; + + /** + * Allows to upload in textures of ATF format to GPU. + * + * @see alternativa.engine3d.resources.BitmapTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + * @see alternativa.engine3d.resources.TextureResource + */ + public class ATFTextureResource extends TextureResource { + /** + * ByteArray, that contains texture of ATF format. + */ + public var data:ByteArray; + + private var uploadCallback:Function = null; + + /** + * Create an instance of CompressedTextureResource. + * @param data ByteArray, that contains ATF texture. + */ + public function ATFTextureResource(data:ByteArray) { + this.data = data; + } + + /** + * @inheritDoc + */ + override public function upload(context3D:Context3D):void { + uploadInternal(context3D); + } + + public function uploadAsync(context3D:Context3D, callback:Function):void { + uploadInternal(context3D, true, callback); + } + + private function uploadInternal(context3D:Context3D, async:Boolean = false, callback:Function = null):void { + if (_texture != null) _texture.dispose(); + + if (data != null) { + data.position = 6; + var type:uint = data.readByte(); + var format:String; + switch (type & 0x7F) { + case 0: + format = Context3DTextureFormat.BGRA; + break; + case 1: + format = Context3DTextureFormat.BGRA; + break; + case 2: + format = Context3DTextureFormat.COMPRESSED; + break; + } + + if ((type & ~0x7F) == 0) { + _texture = context3D.createTexture(1 << data.readByte(), 1 << data.readByte(), format, false); + if (async) { + uploadCallback = callback; + _texture.addEventListener("textureReady", onTextureReady); + Texture(_texture).uploadCompressedTextureFromByteArray(data, 0, true); + } else { + Texture(_texture).uploadCompressedTextureFromByteArray(data, 0, false); + } + + } else { + _texture = context3D.createCubeTexture(1 << data.readByte(), format, false); + if (async) { + uploadCallback = callback; + _texture.addEventListener("textureReady", onTextureReady); + CubeTexture(_texture).uploadCompressedTextureFromByteArray(data, 0, true); + } else { + CubeTexture(_texture).uploadCompressedTextureFromByteArray(data, 0, false); + } + } + } else { + _texture = null; + throw new Error("Cannot upload without data"); + } + } + + private function onTextureReady(e:Event):void { + if (uploadCallback != null) { + uploadCallback(this); + uploadCallback = null; + } + } + + } +} diff --git a/src/alternativa/engine3d/resources/BitmapCubeTextureResource.as b/src/alternativa/engine3d/resources/BitmapCubeTextureResource.as new file mode 100644 index 0000000..336410a --- /dev/null +++ b/src/alternativa/engine3d/resources/BitmapCubeTextureResource.as @@ -0,0 +1,255 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.CubeTexture; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Resource of cube texture. + * + * Allows user to upload cube texture, that consists of six BitmapData images to GPU. + * Size of texture must be power of two (e.g., 256х256, 128*512, 256* 32). + * @see alternativa.engine3d.resources.TextureResource + * @see alternativa.engine3d.resources.ATFTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public class BitmapCubeTextureResource extends TextureResource { + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + + static private var temporaryBitmapData:BitmapData; + static private const rect:Rectangle = new Rectangle(); + static private const point:Point = new Point(); + static private const matrix:Matrix = new Matrix(0.5, 0, 0, 0.5); + + /** + * BitmapData, that will be used as left face. + */ + public var left:BitmapData; + /** + * BitmapData, that will be used as right face. + */ + public var right:BitmapData; + /** + * BitmapData, that will be used as top face. + */ + public var top:BitmapData; + /** + * BitmapData, that will be used as bottom face. + */ + public var bottom:BitmapData; + /** + * BitmapData, that will be used as front face. + */ + public var front:BitmapData; + /** + * BitmapData, that will be used as back face. + */ + public var back:BitmapData; + /** + * Property, that define the choice of type of coordinate system: left-side or right-side. + * If axis Y is directed to up, and axis X - to front, then if you use right-side coordinate + * system, axis Z is directed to right. But if you use right-side coordinate system, then + * axis Z is directed to left. + */ + public var leftHanded:Boolean; + + /** + * Creates a new instance of BitmapCubeTextureResource. + * @param left BitmapData, that will be used as left face. + * @param right BitmapData, that will be used as right face. + * @param bottom BitmapData, that will be used as bottom face. + * @param top BitmapData, that will be used as top face. + * @param back BitmapData, that will be used as back face. + * @param front BitmapData, that will be used as front face. + * @param leftHanded Property, that define the choice of type of coordinate system: left-side or right-side. + */ + public function BitmapCubeTextureResource(left:BitmapData, right:BitmapData, back:BitmapData, front:BitmapData, bottom:BitmapData, top:BitmapData, leftHanded:Boolean = false) { + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + this.back = back; + this.front = front; + this.leftHanded = leftHanded; + } + + /** + * @inheritDoc + */ + override public function upload(context3D:Context3D):void { + if (_texture != null) _texture.dispose(); + _texture = context3D.createCubeTexture(left.width, Context3DTextureFormat.BGRA, false); + var cubeTexture:CubeTexture = CubeTexture(_texture); + filter.preserveAlpha = !left.transparent; + var bmp:BitmapData = (temporaryBitmapData != null) ? temporaryBitmapData : new BitmapData(left.width, left.height, left.transparent); + + var level:int = 0; + + + + var current:BitmapData; + + if (leftHanded) { + current = left; + } else { + current = new BitmapData(left.width, left.height, left.transparent); + current.draw(left, new Matrix(0, -1, -1, 0, left.width, left.height)); + } + cubeTexture.uploadFromBitmapData(current, 1, level++); + + 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); + cubeTexture.uploadFromBitmapData(current, 1, level++); + } + + level = 0; + if (leftHanded) { + current = right; + } else { + current = new BitmapData(right.width, right.height, right.transparent); + current.draw(right, new Matrix(0, 1, 1, 0)); + } + + cubeTexture.uploadFromBitmapData(current, 0, level++); + 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); + cubeTexture.uploadFromBitmapData(current, 0, level++); + } + + level = 0; + + + if (leftHanded) { + current = back; + } else { + current = new BitmapData(back.width, back.height, back.transparent); + current.draw(back, new Matrix(-1, 0, 0, 1, back.width, 0)); + } + + cubeTexture.uploadFromBitmapData(current, 3, level++); + + 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); + cubeTexture.uploadFromBitmapData(current, 3, level++); + } + + level = 0; + if (leftHanded) { + current = front; + } else { + current = new BitmapData(front.width, front.height, front.transparent); + current.draw(front, new Matrix(1, 0, 0, -1, 0, front.height)); + } + cubeTexture.uploadFromBitmapData(current, 2, level++); + + 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); + cubeTexture.uploadFromBitmapData(current, 2, level++); + } + + level = 0; + if (leftHanded) { + current = bottom; + } else { + current = new BitmapData(bottom.width, bottom.height, bottom.transparent); + current.draw(bottom, new Matrix(-1, 0, 0, 1, bottom.width, 0)); + } + cubeTexture.uploadFromBitmapData(current, 5, level++); + + 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); + cubeTexture.uploadFromBitmapData(current, 5, level++); + } + + level = 0; + if (leftHanded) { + current = top; + } else { + current = new BitmapData(top.width, top.height, top.transparent); + current.draw(top, new Matrix(1, 0, 0, -1, 0, top.height)); + } + cubeTexture.uploadFromBitmapData(current, 4, level++); + + 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); + cubeTexture.uploadFromBitmapData(current, 4, level++); + } + if (temporaryBitmapData == null) bmp.dispose(); + } + + } +} diff --git a/src/alternativa/engine3d/resources/BitmapTextureResource.as b/src/alternativa/engine3d/resources/BitmapTextureResource.as new file mode 100644 index 0000000..31470fb --- /dev/null +++ b/src/alternativa/engine3d/resources/BitmapTextureResource.as @@ -0,0 +1,133 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.Texture; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Texture resource, that allows user to upload textures from BitmapData to GPU. + * Size of texture must be power of two (e.g., 256х256, 128*512, 256* 32). + * @see alternativa.engine3d.resources.TextureResource + * @see alternativa.engine3d.resources.ATFTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public class BitmapTextureResource extends TextureResource { + + static private const rect:Rectangle = new Rectangle(); + 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 point:Point = new Point(); + /** + * BitmapData + */ + public var data:BitmapData; + + /** + * Uploads textures from BitmapData to GPU. + */ + public function BitmapTextureResource(data:BitmapData) { + this.data = data; + } + + /** + * @inheritDoc + */ + override public function upload(context3D:Context3D):void { + if (_texture != null) _texture.dispose(); + if (data != null) { + _texture = context3D.createTexture(data.width, data.height, Context3DTextureFormat.BGRA, false); + filter.preserveAlpha = !data.transparent; + Texture(_texture).uploadFromBitmapData(data, 0); + var level:int = 1; + var bmp:BitmapData = new BitmapData(data.width, data.height, data.transparent); + var current:BitmapData = data; + rect.width = data.width; + rect.height = data.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 != data) current.dispose(); + current = new BitmapData(rect.width, rect.height, data.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + Texture(_texture).uploadFromBitmapData(current, level++); + } + if (current != data) current.dispose(); + bmp.dispose(); + } else { + _texture = null; + throw new Error("Cannot upload without data"); + } + } + + /** + * @private + */ + alternativa3d function createMips(texture:Texture, bitmapData:BitmapData):void { + rect.width = bitmapData.width; + rect.height = bitmapData.height; + var level:int = 1; + var bmp:BitmapData = new BitmapData(rect.width, rect.height, bitmapData.transparent); + var current:BitmapData = bitmapData; + 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 != bitmapData) current.dispose(); + current = new BitmapData(rect.width, rect.height, bitmapData.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + texture.uploadFromBitmapData(current, level++); + } + if (current != bitmapData) current.dispose(); + bmp.dispose(); + } + + /** + * @private + */ + static alternativa3d function createMips(texture:Texture, bitmapData:BitmapData):void { + rect.width = bitmapData.width; + rect.height = bitmapData.height; + var level:int = 1; + var bmp:BitmapData = new BitmapData(rect.width, rect.height, bitmapData.transparent); + var current:BitmapData = bitmapData; + 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 != bitmapData) current.dispose(); + current = new BitmapData(rect.width, rect.height, bitmapData.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + texture.uploadFromBitmapData(current, level++); + } + if (current != bitmapData) current.dispose(); + bmp.dispose(); + } + + } +} diff --git a/src/alternativa/engine3d/resources/ExternalTextureResource.as b/src/alternativa/engine3d/resources/ExternalTextureResource.as new file mode 100644 index 0000000..0636f92 --- /dev/null +++ b/src/alternativa/engine3d/resources/ExternalTextureResource.as @@ -0,0 +1,61 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + + import flash.display3D.Context3D; + import flash.display3D.textures.TextureBase; + + use namespace alternativa3d; + + /** + * a TextureResource, that uses data from external source (file). Link for this source is specified on instance creation. + * Used with TexturesLoader, which gets ExternalTextureResource for uploading needed files. + * Size of texture must be power of two (e.g., 256х256, 128*512, 256* 32). + * @see alternativa.engine3d.loaders.TexturesLoader#loadResource() + * @see alternativa.engine3d.loaders.TexturesLoader#loadResources() + */ + public class ExternalTextureResource extends TextureResource { + /** + * URL-path to texture. + */ + public var url:String; + + /** + * @param url Adress of texture. + */ + public function ExternalTextureResource(url:String) { + this.url = url; + } + + /** + * @inheritDoc + */ + override public function upload(context3D:Context3D):void { + } + + /** + * Resource data, that are get from external resource. + */ + public function get data():TextureBase { + return _texture; + } + + /** + * @private + */ + public function set data(value:TextureBase):void { + _texture = value; + } + + } +} diff --git a/src/alternativa/engine3d/resources/Geometry.as b/src/alternativa/engine3d/resources/Geometry.as new file mode 100644 index 0000000..45fc80f --- /dev/null +++ b/src/alternativa/engine3d/resources/Geometry.as @@ -0,0 +1,1016 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.RayIntersectionData; + import alternativa.engine3d.core.Resource; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.core.VertexStream; + + import flash.display3D.Context3D; + import flash.display3D.IndexBuffer3D; + import flash.display3D.VertexBuffer3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + /** + * Resource, that stores data about geometry of object. All data are stored for each vertex. + * So, you can set any set of parameters. And this set will be defined for each vertex of geometry. + * It will be useful to divide parameters by some vertexBuffers in order to update these data at + * memory of GPU, independently of each other (vertexBuffer can be updated at once only). + * For this, you can store groups of parameters in different streams. Based on them vertexBuffers will be formed on uploading to GPU. + * When new stream is formed, are specified the parameters, that will be stored in it. + * @example This code creates stream on properties: x,y,z,u,v and forms a triangle by three vertices. + * + * var attributes:Array = new Array(); + * attributes[0] = VertexAttributes.POSITION; + * attributes[1] = VertexAttributes.POSITION; + * attributes[2] = VertexAttributes.POSITION; + * attributes[3] = VertexAttributes.TEXCOORDS[0]; + * attributes[4] = VertexAttributes.TEXCOORDS[0]; + * var geometry = new Geometry(); + * geometry.addVertexStream(attributes); + * geometry.numVertices = 3; + * geometry.setAttributeValues(VertexAttributes.POSITION, new [x1,y1,z1,x2,y2,z2,x3,y3,z3]); + * geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], new [u1,v1,u2,v2,u3,v3]); + * geometry.indices = Vector.([0,1,2]); + * + * To get access to data, you can use method getAttributeValues by parameter name, e.g.: + * geometry.getAttributeValues(VertexAttributes.POSITION) + * returns vector from coordinates: [x1,y1,z1,x2,y2,z2,x3,y3,z3]. + */ + public class Geometry extends Resource { + + /** + * @private + */ + alternativa3d var _vertexStreams:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var _indexBuffer:IndexBuffer3D; + + /** + * @private + */ + alternativa3d var _numVertices:int; + + /** + * @private + */ + alternativa3d var _indices:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var _attributesStreams:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var _attributesOffsets:Vector. = new Vector.(); + + private var _attributesStrides:Vector. = new Vector.(); + + /** + * Creates a new instance. + * @param numVertices Number of vertices. + */ + public function Geometry(numVertices:int = 0) { + this._numVertices = numVertices; + } + + /** + * Number of triangles, that are contained in geometry. + */ + public function get numTriangles():int { + return _indices.length/3; + } + + /** + * Indexes of vertices for specifying of triangles of surface. + * Example of specifying of surface, that consists of two triangles: Vector.([vertex_id_1,vertex_id_2,vertex_id_3,vertex_id_4,vertex_id_5,vertex_id_6]);. + */ + public function get indices():Vector. { + return _indices.slice(); + } + + /** + * @private + */ + public function set indices(value:Vector.):void { + if (value == null) { + _indices.length = 0; + } else { + _indices = value.slice() + } + } + + /** + * Number of vertices of geometry. + */ + public function get numVertices():int { + return _numVertices; + } + + /** + * @private + */ + public function set numVertices(value:int):void { + if (_numVertices != value) { + // Change buffers. + for each (var vBuffer:VertexStream in _vertexStreams) { + var numMappings:int = vBuffer.attributes.length; + vBuffer.data.length = 4*numMappings*value; + } + _numVertices = value; + } + } + + /** + * Calculation of vertex normals. + */ + public function calculateNormals():void { + if (!hasAttribute(VertexAttributes.POSITION)) throw new Error("Vertices positions is required to calculate normals"); + var normals:Array = new Array(); + var positionsStream:VertexStream = _attributesStreams[VertexAttributes.POSITION]; + var positionsData:ByteArray = positionsStream.data; + var positionsOffset:int = _attributesOffsets[VertexAttributes.POSITION]*4; + var stride:int = positionsStream.attributes.length*4; + var numIndices:int = _indices.length; + var normal:Vector3D; + var i:int; + // Normals calculations + for (i = 0; i < numIndices; i += 3) { + var vertIndexA:int = _indices[i]; + var vertIndexB:int = _indices[i + 1]; + var vertIndexC:int = _indices[i + 2]; + // v1 + positionsData.position = vertIndexA*stride + positionsOffset; + var ax:Number = positionsData.readFloat(); + var ay:Number = positionsData.readFloat(); + var az:Number = positionsData.readFloat(); + + // v2 + positionsData.position = vertIndexB*stride + positionsOffset; + var bx:Number = positionsData.readFloat(); + var by:Number = positionsData.readFloat(); + var bz:Number = positionsData.readFloat(); + + // v3 + positionsData.position = vertIndexC*stride + positionsOffset; + var cx:Number = positionsData.readFloat(); + var cy:Number = positionsData.readFloat(); + var cz:Number = positionsData.readFloat(); + + // v2-v1 + var abx:Number = bx - ax; + var aby:Number = by - ay; + var abz:Number = bz - az; + + // v3-v1 + var acx:Number = cx - ax; + var acy:Number = cy - ay; + var acz:Number = cz - az; + + var normalX:Number = acz*aby - acy*abz; + var normalY:Number = acx*abz - acz*abx; + var normalZ:Number = acy*abx - acx*aby; + + var normalLen:Number = Math.sqrt(normalX*normalX + normalY*normalY + normalZ*normalZ); + + if (normalLen > 0) { + normalX /= normalLen; + normalY /= normalLen; + normalZ /= normalLen; + } else { + trace("degenerated triangle", i/3); + } + + // v1 normal + normal = normals[vertIndexA]; + + if (normal == null) { + normals[vertIndexA] = new Vector3D(normalX, normalY, normalZ); + } else { + normal.x += normalX; + normal.y += normalY; + normal.z += normalZ; + } + + // v2 normal + normal = normals[vertIndexB]; + + if (normal == null) { + normals[vertIndexB] = new Vector3D(normalX, normalY, normalZ); + } else { + normal.x += normalX; + normal.y += normalY; + normal.z += normalZ; + } + + // v3 normal + normal = normals[vertIndexC]; + + if (normal == null) { + normals[vertIndexC] = new Vector3D(normalX, normalY, normalZ); + } else { + normal.x += normalX; + normal.y += normalY; + normal.z += normalZ; + } + } + + if (hasAttribute(VertexAttributes.NORMAL)) { + + var normalsOffset:int = _attributesOffsets[VertexAttributes.NORMAL]*4; + var normalsStream:VertexStream = _attributesStreams[VertexAttributes.NORMAL]; + var normalsBuffer:ByteArray = normalsStream.data; + var normalsBufferStride:uint = normalsStream.attributes.length*4; + for (i = 0; i < _numVertices; i++) { + normal = normals[i]; + normal.normalize(); + normalsBuffer.position = i*normalsBufferStride + normalsOffset; + normalsBuffer.writeFloat(normal.x); + normalsBuffer.writeFloat(normal.y); + normalsBuffer.writeFloat(normal.z); + } + } else { + // Write normals to ByteArray + var resultByteArray:ByteArray = new ByteArray(); + resultByteArray.endian = Endian.LITTLE_ENDIAN; + for (i = 0; i < _numVertices; i++) { + normal = normals[i]; + normal.normalize(); + resultByteArray.writeBytes(positionsData, i*stride, stride); + resultByteArray.writeFloat(normal.x); + resultByteArray.writeFloat(normal.y); + resultByteArray.writeFloat(normal.z); + } + positionsStream.attributes.push(VertexAttributes.NORMAL); + positionsStream.attributes.push(VertexAttributes.NORMAL); + positionsStream.attributes.push(VertexAttributes.NORMAL); + + positionsStream.data = resultByteArray; + positionsData.clear(); + + _attributesOffsets[VertexAttributes.NORMAL] = stride/4; + _attributesStreams[VertexAttributes.NORMAL] = positionsStream; + _attributesStrides[VertexAttributes.NORMAL] = 3; + } + + } + + /** + * Calculation of tangents and bi-normals. Normals of geometry must be calculated. + */ + public function calculateTangents(uvChannel:int):void { + if (!hasAttribute(VertexAttributes.POSITION)) throw new Error("Vertices positions is required to calculate normals"); + if (!hasAttribute(VertexAttributes.NORMAL)) throw new Error("Vertices normals is required to calculate tangents, call calculateNormals first"); + if (!hasAttribute(VertexAttributes.TEXCOORDS[uvChannel])) throw new Error("Specified uv channel does not exist in geometry"); + + var tangents:Array = new Array(); + + var positionsStream:VertexStream = _attributesStreams[VertexAttributes.POSITION]; + var positionsData:ByteArray = positionsStream.data; + var positionsOffset:int = _attributesOffsets[VertexAttributes.POSITION]*4; + var positionsStride:int = positionsStream.attributes.length*4; + + var normalsStream:VertexStream = _attributesStreams[VertexAttributes.NORMAL]; + var normalsData:ByteArray = normalsStream.data; + var normalsOffset:int = _attributesOffsets[VertexAttributes.NORMAL]*4; + var normalsStride:int = normalsStream.attributes.length*4; + + var uvsStream:VertexStream = _attributesStreams[VertexAttributes.TEXCOORDS[uvChannel]]; + var uvsData:ByteArray = uvsStream.data; + var uvsOffset:int = _attributesOffsets[VertexAttributes.TEXCOORDS[uvChannel]]*4; + var uvsStride:int = uvsStream.attributes.length*4; + + var numIndices:int = _indices.length; + var normal:Vector3D; + var tangent:Vector3D; + var i:int; + + for (i = 0; i < numIndices; i += 3) { + var vertIndexA:int = _indices[i]; + var vertIndexB:int = _indices[i + 1]; + var vertIndexC:int = _indices[i + 2]; + + // a.xyz + positionsData.position = vertIndexA*positionsStride + positionsOffset; + var ax:Number = positionsData.readFloat(); + var ay:Number = positionsData.readFloat(); + var az:Number = positionsData.readFloat(); + + // b.xyz + positionsData.position = vertIndexB*positionsStride + positionsOffset; + var bx:Number = positionsData.readFloat(); + var by:Number = positionsData.readFloat(); + var bz:Number = positionsData.readFloat(); + + // c.xyz + positionsData.position = vertIndexC*positionsStride + positionsOffset; + var cx:Number = positionsData.readFloat(); + var cy:Number = positionsData.readFloat(); + var cz:Number = positionsData.readFloat(); + + // a.uv + uvsData.position = vertIndexA*uvsStride + uvsOffset; + var au:Number = uvsData.readFloat(); + var av:Number = uvsData.readFloat(); + + // b.uv + uvsData.position = vertIndexB*uvsStride + uvsOffset; + var bu:Number = uvsData.readFloat(); + var bv:Number = uvsData.readFloat(); + + // c.uv + uvsData.position = vertIndexC*uvsStride + uvsOffset; + var cu:Number = uvsData.readFloat(); + var cv:Number = uvsData.readFloat(); + + // a.nrm + normalsData.position = vertIndexA*normalsStride + normalsOffset; + var anx:Number = normalsData.readFloat(); + var any:Number = normalsData.readFloat(); + var anz:Number = normalsData.readFloat(); + + // b.nrm + normalsData.position = vertIndexB*normalsStride + normalsOffset; + var bnx:Number = normalsData.readFloat(); + var bny:Number = normalsData.readFloat(); + var bnz:Number = normalsData.readFloat(); + + // c.nrm + normalsData.position = vertIndexC*normalsStride + normalsOffset; + var cnx:Number = normalsData.readFloat(); + var cny:Number = normalsData.readFloat(); + var cnz:Number = normalsData.readFloat(); + + // v2-v1 + var abx:Number = bx - ax; + var aby:Number = by - ay; + var abz:Number = bz - az; + + // v3-v1 + var acx:Number = cx - ax; + var acy:Number = cy - ay; + var acz:Number = cz - az; + + var abu:Number = bu - au; + var abv:Number = bv - av; + + var acu:Number = cu - au; + var acv:Number = cv - av; + + var r:Number = 1/(abu*acv - acu*abv); + + var tangentX:Number = r*(acv*abx - acx*abv); + var tangentY:Number = r*(acv*aby - abv*acy); + var tangentZ:Number = r*(acv*abz - abv*acz); + + tangent = tangents[vertIndexA]; + + if (tangent == null) { + tangents[vertIndexA] = new Vector3D( + tangentX - anx*(anx*tangentX + any*tangentY + anz*tangentZ), + tangentY - any*(anx*tangentX + any*tangentY + anz*tangentZ), + tangentZ - anz*(anx*tangentX + any*tangentY + anz*tangentZ)); + + } else { + tangent.x += tangentX - anx*(anx*tangentX + any*tangentY + anz*tangentZ); + tangent.y += tangentY - any*(anx*tangentX + any*tangentY + anz*tangentZ); + tangent.z += tangentZ - anz*(anx*tangentX + any*tangentY + anz*tangentZ); + } + + tangent = tangents[vertIndexB]; + + if (tangent == null) { + tangents[vertIndexB] = new Vector3D( + tangentX - bnx*(bnx*tangentX + bny*tangentY + bnz*tangentZ), + tangentY - bny*(bnx*tangentX + bny*tangentY + bnz*tangentZ), + tangentZ - bnz*(bnx*tangentX + bny*tangentY + bnz*tangentZ)); + + } else { + tangent.x += tangentX - bnx*(bnx*tangentX + bny*tangentY + bnz*tangentZ); + tangent.y += tangentY - bny*(bnx*tangentX + bny*tangentY + bnz*tangentZ); + tangent.z += tangentZ - bnz*(bnx*tangentX + bny*tangentY + bnz*tangentZ); + } + + tangent = tangents[vertIndexC]; + + if (tangent == null) { + tangents[vertIndexC] = new Vector3D( + tangentX - cnx*(cnx*tangentX + cny*tangentY + cnz*tangentZ), + tangentY - cny*(cnx*tangentX + cny*tangentY + cnz*tangentZ), + tangentZ - cnz*(cnx*tangentX + cny*tangentY + cnz*tangentZ)); + + } else { + tangent.x += tangentX - cnx*(cnx*tangentX + cny*tangentY + cnz*tangentZ); + tangent.y += tangentY - cny*(cnx*tangentX + cny*tangentY + cnz*tangentZ); + tangent.z += tangentZ - cnz*(cnx*tangentX + cny*tangentY + cnz*tangentZ); + } + + } + + if (hasAttribute(VertexAttributes.TANGENT4)) { + + var tangentsOffset:int = _attributesOffsets[VertexAttributes.TANGENT4]*4; + var tangentsStream:VertexStream = _attributesStreams[VertexAttributes.TANGENT4]; + var tangentsBuffer:ByteArray = tangentsStream.data; + var tangentsBufferStride:uint = tangentsStream.attributes.length*4; + for (i = 0; i < _numVertices; i++) { + tangent = tangents[i]; + tangent.normalize(); + tangentsBuffer.position = i*tangentsBufferStride + tangentsOffset; + tangentsBuffer.writeFloat(tangent.x); + tangentsBuffer.writeFloat(tangent.y); + tangentsBuffer.writeFloat(tangent.z); + tangentsBuffer.writeFloat(-1); + } + } else { + // Write normals to ByteArray + var resultByteArray:ByteArray = new ByteArray(); + resultByteArray.endian = Endian.LITTLE_ENDIAN; + for (i = 0; i < _numVertices; i++) { + tangent = tangents[i]; + tangent.normalize(); + resultByteArray.writeBytes(positionsData, i*positionsStride, positionsStride); + resultByteArray.writeFloat(tangent.x); + resultByteArray.writeFloat(tangent.y); + resultByteArray.writeFloat(tangent.z); + resultByteArray.writeFloat(-1); + } + positionsStream.attributes.push(VertexAttributes.TANGENT4); + positionsStream.attributes.push(VertexAttributes.TANGENT4); + positionsStream.attributes.push(VertexAttributes.TANGENT4); + positionsStream.attributes.push(VertexAttributes.TANGENT4); + + positionsStream.data = resultByteArray; + positionsData.clear(); + + _attributesOffsets[VertexAttributes.TANGENT4] = positionsStride/4; + _attributesStreams[VertexAttributes.TANGENT4] = positionsStream; + _attributesStrides[VertexAttributes.TANGENT4] = 4; + } + + } + + /** + * Adds a stream for set of parameters, that can be updated independently of the other sets of parameters. + * @param attributes List of parameters. Types of parameters are get from VertexAttributes. + * @return Index of stream, that has been created. + */ + public function addVertexStream(attributes:Array):int { + var numMappings:int = attributes.length; + if (numMappings < 1) { + throw new Error("Must be at least one attribute ​​to create the buffer."); + } + var vBuffer:VertexStream = new VertexStream(); + var newBufferIndex:int = _vertexStreams.length; + var attribute:uint = attributes[0]; + var stride:int = 1; + for (var i:int = 1; i <= numMappings; i++) { + var next:uint = (i < numMappings) ? attributes[i] : 0; + if (next != attribute) { + // Last item will enter here forcibly. + if (attribute != 0) { + if (attribute < _attributesStreams.length && _attributesStreams[attribute] != null) { + throw new Error("Attribute " + attribute + " already used in this geometry."); + } + var numStandartFloats:int = VertexAttributes.getAttributeStride(attribute); + if (numStandartFloats != 0 && numStandartFloats != stride) { + throw new Error("Standard attributes must be predefined size."); + } + if (_attributesStreams.length < attribute) { + _attributesStreams.length = attribute + 1; + _attributesOffsets.length = attribute + 1; + _attributesStrides.length = attribute + 1; + } + var startIndex:int = i - stride; + _attributesStreams[attribute] = vBuffer; + _attributesOffsets[attribute] = startIndex; + _attributesStrides[attribute] = stride; + } + stride = 1; + } else { + stride++; + } + attribute = next; + } + vBuffer.attributes = attributes.slice(); + // vBuffer.data = new Vector.(numMappings*_numVertices); + vBuffer.data = new ByteArray(); + vBuffer.data.endian = Endian.LITTLE_ENDIAN; + vBuffer.data.length = 4*numMappings*_numVertices; + _vertexStreams[newBufferIndex] = vBuffer; + return newBufferIndex; + } + + /** + * Number of vertex-streams. + */ + public function get numVertexStreams():int { + return _vertexStreams.length; + } + + /** + * returns mapping of stream by index. + * @param index index of stream. + * @return mapping. + */ + public function getVertexStreamAttributes(index:int):Array { + return _vertexStreams[index].attributes.slice(); + } + + /** + * Check the existence of attribute in all streams. + * @param attribute Attribute, that is checked. + * @return + */ + public function hasAttribute(attribute:uint):Boolean { + return attribute < _attributesStreams.length && _attributesStreams[attribute] != null; + } + + /** + * Returns index of stream, that contains needed attribute. + * + * @param attribute + * + * @return -1 if attribute is not found. + */ + public function findVertexStreamByAttribute(attribute:uint):int { + var vBuffer:VertexStream = (attribute < _attributesStreams.length) ? _attributesStreams[attribute] : null; + if (vBuffer != null) { + for (var i:int = 0; i < _vertexStreams.length; i++) { + if (_vertexStreams[i] == vBuffer) { + return i; + } + } + } + return -1; + } + + /** + * Offset of attribute at stream, with which this attribute is stored. You can find index of stream using findVertexStreamByAttribute. + * + * @param attribute Type of attribute. List of types of attributes placed at VertexAttributes. + * @return Offset. + * + * @see #findVertexStreamByAttribute + * @see VertexAttributes + */ + public function getAttributeOffset(attribute:uint):int { + var vBuffer:VertexStream = (attribute < _attributesStreams.length) ? _attributesStreams[attribute] : null; + if (vBuffer == null) { + throw new Error("Attribute not found."); + } + return _attributesOffsets[attribute]; + } + + /** + * Sets value for attribute. + * If buffer has not been initialized, then it initialized with zeros automatically. + * + * @param attribute + * @param values + */ + public function setAttributeValues(attribute:uint, values:Vector.):void { + var vBuffer:VertexStream = (attribute < _attributesStreams.length) ? _attributesStreams[attribute] : null; + if (vBuffer == null) { + throw new Error("Attribute not found."); + } + var stride:int = _attributesStrides[attribute]; + if (values == null || values.length != stride*_numVertices) { + throw new Error("Values count must be the same."); + } + var numMappings:int = vBuffer.attributes.length; + var data:ByteArray = vBuffer.data; + var offset:int = _attributesOffsets[attribute]; + // Copy values + for (var i:int = 0; i < _numVertices; i++) { + var srcIndex:int = stride*i; + data.position = 4*(numMappings*i + offset); + for (var j:int = 0; j < stride; j++) { + data.writeFloat(values[int(srcIndex + j)]); + } + } + } + + public function getAttributeValues(attribute:uint):Vector. { + var vBuffer:VertexStream = (attribute < _attributesStreams.length) ? _attributesStreams[attribute] : null; + if (vBuffer == null) { + throw new Error("Attribute not found."); + } + var data:ByteArray = vBuffer.data; + var stride:int = _attributesStrides[attribute]; + var result:Vector. = new Vector.(stride*_numVertices); + var numMappings:int = vBuffer.attributes.length; + var offset:int = _attributesOffsets[attribute]; + // Copy values + for (var i:int = 0; i < _numVertices; i++) { + data.position = 4*(numMappings*i + offset); + var dstIndex:int = stride*i; + for (var j:int = 0; j < stride; j++) { + result[int(dstIndex + j)] = data.readFloat(); + } + } + return result; + } + + /** + * Check for existence of resource in video memory. + */ + override public function get isUploaded():Boolean { + return _indexBuffer != null; + } + + /** + * @inheritDoc + */ + override public function upload(context3D:Context3D):void { + var vBuffer:VertexStream; + var i:int; + var numBuffers:int = _vertexStreams.length; + if (_indexBuffer != null) { + // Clear old resources + _indexBuffer.dispose(); + _indexBuffer = null; + for (i = 0; i < numBuffers; i++) { + vBuffer = _vertexStreams[i]; + vBuffer.buffer.dispose(); + vBuffer.buffer = null; + } + } + if (_indices.length <= 0 || _numVertices <= 0) { + return; + } + + for (i = 0; i < numBuffers; i++) { + vBuffer = _vertexStreams[i]; + var numMappings:int = vBuffer.attributes.length; + var data:ByteArray = vBuffer.data; + if (data == null) { + throw new Error("Cannot upload without vertex buffer data."); + } + vBuffer.buffer = context3D.createVertexBuffer(_numVertices, numMappings); + vBuffer.buffer.uploadFromByteArray(data, 0, 0, _numVertices); + } + var numIndices:int = _indices.length; + _indexBuffer = context3D.createIndexBuffer(numIndices); + _indexBuffer.uploadFromVector(_indices, 0, numIndices); + } + + /** + * @inheritDoc + */ + override public function dispose():void { + if (_indexBuffer != null) { + _indexBuffer.dispose(); + _indexBuffer = null; + var numBuffers:int = _vertexStreams.length; + for (var i:int = 0; i < numBuffers; i++) { + var vBuffer:VertexStream = _vertexStreams[i]; + vBuffer.buffer.dispose(); + vBuffer.buffer = null; + } + } + } + + /** + * Updates values of index-buffer in video memory. + * @param data List of values. + * @param startOffset Offset. + * @param count Count of updated values. + */ + public function updateIndexBufferInContextFromVector(data:Vector., startOffset:int, count:int):void { + if (_indexBuffer == null) { + throw new Error("Geometry must be uploaded."); + } + _indexBuffer.uploadFromVector(data, startOffset, count); + } + + /** + * Updates values of index-buffer in video memory. + * @param data Data + * @param startOffset Offset + * @param count Number of updated values. + */ + public function updateIndexBufferInContextFromByteArray(data:ByteArray, byteArrayOffset:int, startOffset:int, count:int):void { + if (_indexBuffer == null) { + throw new Error("Geometry must be uploaded."); + } + _indexBuffer.uploadFromByteArray(data, byteArrayOffset, startOffset, count); + } + + /** + * Updates values of vertex-buffer in video memory. + * @param data List of values. + * @param startVertex Offset. + * @param numVertices Number of updated values. + */ + public function updateVertexBufferInContextFromVector(index:int, data:Vector., startVertex:int, numVertices:int):void { + if (_indexBuffer == null) { + throw new Error("Geometry must be uploaded."); + } + _vertexStreams[index].buffer.uploadFromVector(data, startVertex, numVertices); + } + + /** + * Updates values of vertex-buffer in video memory. + * @param data Data + * @param startVertex Offset. + * @param numVertices Number of updated values. + */ + public function updateVertexBufferInContextFromByteArray(index:int, data:ByteArray, byteArrayOffset:int, startVertex:int, numVertices:int):void { + if (_indexBuffer == null) { + throw new Error("Geometry must be uploaded."); + } + _vertexStreams[index].buffer.uploadFromByteArray(data, byteArrayOffset, startVertex, numVertices); + } + + alternativa3d function intersectRay(origin:Vector3D, direction:Vector3D, indexBegin:uint, numTriangles:uint):RayIntersectionData { + 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 nax:Number; + var nay:Number; + var naz:Number; + var nau:Number; + var nav:Number; + + var nbx:Number; + var nby:Number; + var nbz:Number; + var nbu:Number; + var nbv:Number; + + var ncx:Number; + var ncy:Number; + var ncz:Number; + var ncu:Number; + var ncv:Number; + + var nrmX:Number; + var nrmY:Number; + var nrmZ:Number; + + var point:Vector3D; + var minTime:Number = 1e+22; + var posAttribute:int = VertexAttributes.POSITION; + var uvAttribute:int = VertexAttributes.TEXCOORDS[0]; + var positionStream:VertexStream; + if (VertexAttributes.POSITION >= _attributesStreams.length || (positionStream = _attributesStreams[posAttribute]) == null) { + throw new Error("Raycast require POSITION attribute"); + } + var positionBuffer:ByteArray = positionStream.data; + // Offset of position attribute. + const positionOffset:uint = _attributesOffsets[posAttribute]*4; + // Length of vertex on bytes. + var stride:uint = positionStream.attributes.length*4; + + var uvStream:VertexStream; + var hasUV:Boolean = uvAttribute < _attributesStreams.length && (uvStream = _attributesStreams[uvAttribute]) != null; + var uvBuffer:ByteArray; + var uvOffset:uint; + var uvStride:uint; + if (hasUV) { + uvBuffer = uvStream.data; + uvOffset = _attributesOffsets[uvAttribute]*4; + uvStride = uvStream.attributes.length*4; + } + + if (numTriangles*3 > indices.length) { + throw new ArgumentError("index is out of bounds"); + } + for (var i:int = indexBegin, count:int = indexBegin + numTriangles*3; i < count; i += 3) { + var indexA:uint = indices[i]; + var indexB:uint = indices[int(i + 1)]; + var indexC:uint = indices[int(i + 2)]; + positionBuffer.position = indexA*stride + positionOffset; + var ax:Number = positionBuffer.readFloat(); + var ay:Number = positionBuffer.readFloat(); + var az:Number = positionBuffer.readFloat(); + var au:Number; + var av:Number; + positionBuffer.position = indexB*stride + positionOffset; + var bx:Number = positionBuffer.readFloat(); + var by:Number = positionBuffer.readFloat(); + var bz:Number = positionBuffer.readFloat(); + var bu:Number; + var bv:Number; + + positionBuffer.position = indexC*stride + positionOffset; + var cx:Number = positionBuffer.readFloat(); + var cy:Number = positionBuffer.readFloat(); + var cz:Number = positionBuffer.readFloat(); + var cu:Number; + var cv:Number; + + if (hasUV) { + uvBuffer.position = indexA*uvStride + uvOffset; + au = uvBuffer.readFloat(); + av = uvBuffer.readFloat(); + + uvBuffer.position = indexB*uvStride + uvOffset; + bu = uvBuffer.readFloat(); + bv = uvBuffer.readFloat(); + + uvBuffer.position = indexC*uvStride + uvOffset; + cu = uvBuffer.readFloat(); + cv = uvBuffer.readFloat(); + } + + var abx:Number = bx - ax; + var aby:Number = by - ay; + var abz:Number = bz - az; + var acx:Number = cx - ax; + var acy:Number = cy - ay; + var acz:Number = cz - az; + 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 - (ax*normalX + ay*normalY + az*normalZ); + if (offset > 0) { + var time:Number = -offset/dot; + if (point == null || time < minTime) { + var rx:Number = ox + dx*time; + var ry:Number = oy + dy*time; + var rz:Number = oz + dz*time; + abx = bx - ax; + aby = by - ay; + abz = bz - az; + acx = rx - ax; + acy = ry - ay; + acz = rz - az; + if ((acz*aby - acy*abz)*normalX + (acx*abz - acz*abx)*normalY + (acy*abx - acx*aby)*normalZ >= 0) { + abx = cx - bx; + aby = cy - by; + abz = cz - bz; + acx = rx - bx; + acy = ry - by; + acz = rz - bz; + if ((acz*aby - acy*abz)*normalX + (acx*abz - acz*abx)*normalY + (acy*abx - acx*aby)*normalZ >= 0) { + abx = ax - cx; + aby = ay - cy; + abz = az - cz; + acx = rx - cx; + acy = ry - cy; + acz = rz - cz; + if ((acz*aby - acy*abz)*normalX + (acx*abz - acz*abx)*normalY + (acy*abx - acx*aby)*normalZ >= 0) { + if (time < minTime) { + minTime = time; + if (point == null) point = new Vector3D(); + point.x = rx; + point.y = ry; + point.z = rz; + nax = ax; + nay = ay; + naz = az; + nau = au; + nav = av; + nrmX = normalX; + nbx = bx; + nby = by; + nbz = bz; + nbu = bu; + nbv = bv; + nrmY = normalY; + ncx = cx; + ncy = cy; + ncz = cz; + ncu = cu; + ncv = cv; + nrmZ = normalZ; + } + } + } + } + } + } + } + } + if (point != null) { + var res:RayIntersectionData = new RayIntersectionData(); + res.point = point; + res.time = minTime; + if (hasUV) { + // Calculation of UV. + abx = nbx - nax; + aby = nby - nay; + abz = nbz - naz; + var abu:Number = nbu - nau; + var abv:Number = nbv - nav; + + acx = ncx - nax; + acy = ncy - nay; + acz = ncz - naz; + var acu:Number = ncu - nau; + var acv:Number = ncv - nav; + + // Calculation of uv-transformation matrix. + var det:Number = -nrmX*acy*abz + acx*nrmY*abz + nrmX*aby*acz - abx*nrmY*acz - acx*aby*nrmZ + abx*acy*nrmZ; + var ima:Number = (-nrmY*acz + acy*nrmZ)/det; + var imb:Number = (nrmX*acz - acx*nrmZ)/det; + var imc:Number = (-nrmX*acy + acx*nrmY)/det; + var imd:Number = (nax*nrmY*acz - nrmX*nay*acz - nax*acy*nrmZ + acx*nay*nrmZ + nrmX*acy*naz - acx*nrmY*naz)/det; + var ime:Number = (nrmY*abz - aby*nrmZ)/det; + var imf:Number = (-nrmX*abz + abx*nrmZ)/det; + var img:Number = (nrmX*aby - abx*nrmY)/det; + var imh:Number = (nrmX*nay*abz - nax*nrmY*abz + nax*aby*nrmZ - abx*nay*nrmZ - nrmX*aby*naz + abx*nrmY*naz)/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 + nau; + 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 + nav; + // UV + res.uv = new Point(ma*point.x + mb*point.y + mc*point.z + md, me*point.x + mf*point.y + mg*point.z + mh); + } + + return res; + } else { + return null; + } + } + + /** + * @private + */ + alternativa3d function getVertexBuffer(attribute:int):VertexBuffer3D { + if (attribute < _attributesStreams.length) { + var stream:VertexStream = _attributesStreams[attribute]; + return stream != null ? stream.buffer : null; + } else { + return null; + } + } + + /** + * @private + */ + alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + var vBuffer:VertexStream = (VertexAttributes.POSITION < _attributesStreams.length) ? _attributesStreams[VertexAttributes.POSITION] : null; + if (vBuffer == null) { + throw new Error("Cannot calculate BoundBox without data."); + } + var offset:int = _attributesOffsets[VertexAttributes.POSITION]; + var numMappings:int = vBuffer.attributes.length; + var data:ByteArray = vBuffer.data; + + for (var i:int = 0; i < _numVertices; i++) { + data.position = 4*(numMappings*i + offset); + var vx:Number = data.readFloat(); + var vy:Number = data.readFloat(); + var vz:Number = data.readFloat(); + var x:Number, y:Number, z:Number; + if (transform != null) { + x = transform.a*vx + transform.b*vy + transform.c*vz + transform.d; + y = transform.e*vx + transform.f*vy + transform.g*vz + transform.h; + z = transform.i*vx + transform.j*vy + transform.k*vz + transform.l; + } else { + x = vx; + y = vy; + z = vz; + } + if (x < boundBox.minX) boundBox.minX = x; + if (x > boundBox.maxX) boundBox.maxX = x; + if (y < boundBox.minY) boundBox.minY = y; + if (y > boundBox.maxY) boundBox.maxY = y; + if (z < boundBox.minZ) boundBox.minZ = z; + if (z > boundBox.maxZ) boundBox.maxZ = z; + } + } + + } +} diff --git a/src/alternativa/engine3d/resources/TextureResource.as b/src/alternativa/engine3d/resources/TextureResource.as new file mode 100644 index 0000000..b02dfd9 --- /dev/null +++ b/src/alternativa/engine3d/resources/TextureResource.as @@ -0,0 +1,56 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Resource; + + import flash.display3D.textures.TextureBase; + + use namespace alternativa3d; + + /** + * Base resource for texture resources, that can be uploaded into the video memory. + * BitmapTextureResource and ATFTextureResource allows user + * to upload textures into the video memory from BitmapData and ATF format accordingly. + * ExternalTextureResource should be used with TexturesLoader, + * that uploads textures from files and automatically puts them into the video memory. + * Size of texture must be power of two (e.g., 256х256, 128*512, 256* 32). + * @see alternativa.engine3d.resources.BitmapTextureResource + * @see alternativa.engine3d.resources.ATFTextureResource + * @see alternativa.engine3d.resources.ExternalTextureResource + */ + public class TextureResource extends Resource { + + /** + * @private + */ + alternativa3d var _texture:TextureBase; + + /** + * @inheritDoc + */ + override public function get isUploaded():Boolean { + return _texture != null; + } + + /** + * @inheritDoc + */ + override public function dispose():void { + if (_texture != null) { + _texture.dispose(); + _texture = null; + } + } + + } +} diff --git a/src/alternativa/engine3d/resources/WireGeometry.as b/src/alternativa/engine3d/resources/WireGeometry.as new file mode 100644 index 0000000..516fad3 --- /dev/null +++ b/src/alternativa/engine3d/resources/WireGeometry.as @@ -0,0 +1,189 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.resources { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Resource; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.materials.ShaderProgram; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DVertexBufferFormat; + import flash.display3D.IndexBuffer3D; + import flash.display3D.VertexBuffer3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class WireGeometry extends Resource { + + private const MAX_VERTICES_COUNT:uint = 65500; + private const VERTEX_STRIDE:uint = 7; + + alternativa3d var vertexBuffers:Vector.; + alternativa3d var indexBuffers:Vector.; + private var nTriangles:Vector.; + private var vertices:Vector.>; + private var indices:Vector.>; + + // Current set of pairs vertex-buffer + index-buffer, that has a place for writing. + private var currentSetIndex:int = 0; + private var currentSetVertexOffset:uint = 0; + + public function WireGeometry() { + vertexBuffers = new Vector.(1); + indexBuffers = new Vector.(1); + clear(); + } + + override public function upload(context3D:Context3D):void { + for (var i:int = 0; i <= currentSetIndex; i++) { + if (vertexBuffers[i] != null) { + vertexBuffers[i].dispose(); + } + if (indexBuffers[i] != null) { + indexBuffers[i].dispose(); + } + if (nTriangles[i] > 0) { + var verts:Vector. = vertices[i]; + var inds:Vector. = indices[i]; + var vBuffer:VertexBuffer3D = vertexBuffers[i] = context3D.createVertexBuffer(verts.length/VERTEX_STRIDE, VERTEX_STRIDE); + vBuffer.uploadFromVector(verts, 0, verts.length/VERTEX_STRIDE); + var iBuffer:IndexBuffer3D = indexBuffers[i] = context3D.createIndexBuffer(inds.length); + iBuffer.uploadFromVector(inds, 0, inds.length); + } + } + } + + override public function dispose():void { + for (var i:int = 0; i <= currentSetIndex; i++) { + if (vertexBuffers[i] != null) { + vertexBuffers[i].dispose(); + vertexBuffers[i] = null; + } + if (indexBuffers[i] != null) { + indexBuffers[i].dispose(); + indexBuffers[i] = null; + } + } + } + + override public function get isUploaded():Boolean { + for (var i:int = 0; i <= currentSetIndex; i++) { + if (vertexBuffers[i] == null) { + return false; + } + if (indexBuffers[i] == null) { + return false; + } + } + return true; + } + + public function clear():void { + dispose(); + vertices = new Vector.>(); + indices = new Vector.>(); + vertices[0] = new Vector.(); + indices[0] = new Vector.(); + nTriangles = new Vector.(1); + currentSetVertexOffset = 0; + } + + alternativa3d function updateBoundBox(boundBox:BoundBox, transform:Transform3D = null):void { + for (var i:int = 0, count:int = vertices.length; i < count; i++) { + for (var j:int = 0, vcount:int = vertices[i].length; j < vcount; j += VERTEX_STRIDE) { + var verts:Vector. = vertices[i]; + var vx:Number = verts[j]; + var vy:Number = verts[int(j + 1)]; + var vz:Number = verts[int(j + 2)]; + var x:Number, y:Number, z:Number; + if (transform != null) { + x = transform.a*vx + transform.b*vy + transform.c*vz + transform.d; + y = transform.e*vx + transform.f*vy + transform.g*vz + transform.h; + z = transform.i*vx + transform.j*vy + transform.k*vz + transform.l; + } else { + x = vx; + y = vy; + z = vz; + } + if (x < boundBox.minX) boundBox.minX = x; + if (x > boundBox.maxX) boundBox.maxX = x; + if (y < boundBox.minY) boundBox.minY = y; + if (y > boundBox.maxY) boundBox.maxY = y; + if (z < boundBox.minZ) boundBox.minZ = z; + if (z > boundBox.maxZ) boundBox.maxZ = z; + } + } + } + + alternativa3d function getDrawUnits(camera:Camera3D, color:Vector., thickness:Number, object:Object3D, shader:ShaderProgram):void { + for (var i:int = 0; i <= currentSetIndex; i++) { + var iBuffer:IndexBuffer3D = indexBuffers[i]; + var vBuffer:VertexBuffer3D = vertexBuffers[i]; + if (iBuffer != null && vBuffer != null) { + var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, shader.program, iBuffer, 0, nTriangles[i], shader); + drawUnit.setVertexBufferAt(0, vBuffer, 0, Context3DVertexBufferFormat.FLOAT_4); + drawUnit.setVertexBufferAt(1, vBuffer, 4, Context3DVertexBufferFormat.FLOAT_3); + drawUnit.setVertexConstantsFromNumbers(0, 0, 1, -1, 0.000001); + drawUnit.setVertexConstantsFromNumbers(1, -1/camera.focalLength, 0, camera.nearClipping, thickness); + drawUnit.setVertexConstantsFromTransform(2, object.localToCameraTransform); + drawUnit.setProjectionConstants(camera, 5); + drawUnit.setFragmentConstantsFromNumbers(0, color[0], color[1], color[2], color[3]); + if (color[3] < 1) { + drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA; + drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + camera.renderer.addDrawUnit(drawUnit, Renderer.TRANSPARENT_SORT); + } else { + camera.renderer.addDrawUnit(drawUnit, Renderer.OPAQUE); + } + } + } + } + + alternativa3d function addLine(v1x:Number, v1y:Number, v1z:Number, v2x:Number, v2y:Number, v2z:Number):void { + var currentVertices:Vector. = vertices[currentSetIndex]; + var currentIndices:Vector. = indices[currentSetIndex]; + var verticesCount:uint = currentVertices.length/VERTEX_STRIDE; + + if (verticesCount > (MAX_VERTICES_COUNT - 4)) { + // Limit of vertices has been reached + currentSetVertexOffset = 0; + currentSetIndex++; + nTriangles[currentSetIndex] = 0; + currentVertices = vertices[currentSetIndex] = new Vector.(); + currentIndices = indices[currentSetIndex] = new Vector.(); + vertexBuffers.length = currentSetIndex + 1; + indexBuffers.length = currentSetIndex + 1; + } else { + nTriangles[currentSetIndex] += 2; + } + currentVertices.push( + v1x, v1y, v1z, 0.5, v2x, v2y, v2z, + v2x, v2y, v2z, -0.5, v1x, v1y, v1z, + v1x, v1y, v1z, -0.5, v2x, v2y, v2z, + v2x, v2y, v2z, 0.5, v1x, v1y, v1z + ); + currentIndices.push(currentSetVertexOffset, currentSetVertexOffset + 1, currentSetVertexOffset + 2, + currentSetVertexOffset + 3, currentSetVertexOffset + 2, currentSetVertexOffset + 1); + currentSetVertexOffset += 4; + } + + } +} diff --git a/src/alternativa/engine3d/shadows/DirectionalLightShadow.as b/src/alternativa/engine3d/shadows/DirectionalLightShadow.as new file mode 100644 index 0000000..f4765f1 --- /dev/null +++ b/src/alternativa/engine3d/shadows/DirectionalLightShadow.as @@ -0,0 +1,879 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Renderer; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.Geometry; + import alternativa.engine3d.resources.TextureResource; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.VertexBuffer3D; + import flash.display3D.textures.Texture; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Class of shadow, that is created by one source of light(DirectionalLight). Shadow is rendered in fixed volume. + * For binding of shadow to light source you need: + * 1) to set DirectionalLightShadow as a value of property shadow of light source; + * 2) to add Object3D to corresponding list, using the method addCaster(). + * + * @see #addCaster() + * @see alternativa.engine3d.lights.DirectionalLight#shadow + * @see #farBoundPosition + */ + public class DirectionalLightShadow extends Shadow { + + private var renderer:Renderer = new Renderer(); + + /** + * Debug mode. + */ + public var debug:Boolean = false; + + /** + * Degree of correcting offset of shadow map space. It need for getting rid of self-shadowing artifacts. + */ + public var biasMultiplier:Number = 0.99; + + // TODO: implement property parent + + /** + * Coordinate X of center of shadow rendering area. Relative to the center are specified such properties as: + * width, height, nearBoundPosition, farBoundPosition. + * @see #width + * @see #height + * @see #nearBoundPosition + * @see #farBoundPosition + */ + public var centerX:Number = 0; + + /** + * Coordinate Y of center of shadow rendering area. Relative to the center are specified such properties as: + * width, height, nearBoundPosition, farBoundPosition. + * @see #width + * @see #height + * @see #nearBoundPosition + * @see #farBoundPosition + */ + public var centerY:Number = 0; + + /** + * Coordinate Z of center of shadow rendering area. Relative to the center are specified such properties as: + * width, height, nearBoundPosition, farBoundPosition. + * @see #width + * @see #height + * @see #nearBoundPosition + * @see #farBoundPosition + */ + public var centerZ:Number = 0; + + /** + * Width of shadow area (basics of bounbox). + * @see #centerX + * @see #centerY + * @see #centerZ + */ + public var width:Number; + + /** + * Length of shadow area (basics of bounbox). + * @see #centerX + * @see #centerY + * @see #centerZ + */ + public var height:Number; + + /** + * Near clipping bound of calculation of shadow area. + * Shadow map essentially similar to z-buffer: distance from light source to shadow + * casting place is coded by pixel color. So, properties nearBoundPosition + * and farBoundPosition in some ways are analogues of Camera3D.farClipping + * and Camera3D.nearclipping. The greater the range between nearBoundPosition + * and farBoundPosition , the rougher the coordinates of the pixel shader + * will be determined. Shadow area, that is not included into this range would not be drawn. + * Value is measured from center of shadow, that is set by properties: centerX, + * centerY, centerZ. + * @see #centerX + * @see #centerY + * @see #centerZ + */ + public var nearBoundPosition:Number = 0; + + /** + * Far clipping bound of calculation of shadow area. + * Shadow map essentially similar to z-buffer: distance from light source to shadow + * casting place is coded by pixel color. So, properties nearBoundPosition + * and farBoundPosition in some ways are analogues of Camera3D.farClipping + * and Camera3D.nearclipping. The greater the range between nearBoundPosition + * and farBoundPosition , the rougher the coordinates of the pixel shader + * will be determined. Shadow area, that is not included into this range would not be drawn. + * Value is measured from center of shadow, that is set by properties centerX, + * centerY, centerZ. + * @see #centerX + * @see #centerY + * @see #centerZ + */ + public var farBoundPosition:Number = 0; + + // TODO: implement property rotation + + private var _casters:Vector. = new Vector.(); + private var actualCasters:Vector. = new Vector.(); + + private var programs:Dictionary = new Dictionary(); + private var cachedContext:Context3D; + private var shadowMap:Texture; + private var _mapSize:int; + + // TODO: to understand the correctness of offset setting in shadowmap units. (It is possible that it is incorrect after the clipping on zone on edges). + private var _pcfOffset:Number; + + /** + * Enable/disable automatic calculation of shadow zone parameters on specified bound-box at shadowBoundBox property. + */ + public var calculateParametersByVolume:Boolean = false; + public var volume:BoundBox = null; + + // TODO: implement special shader for display of shadowmap in debug (black-and-white). + private var debugTexture:ExternalTextureResource = new ExternalTextureResource("debug"); + private var debugMaterial:TextureMaterial; + private var emptyLightVector:Vector. = new Vector.(); + + private var debugPlane:Mesh; + + // Matrix of projection from light source to context. + private var cameraToShadowMapContextProjection:Transform3D = new Transform3D(); + // Matrix of projection from light source to shadowmap texture. + private var cameraToShadowMapUVProjection:Transform3D = new Transform3D(); + // Auxiliary matrix for transfer of object to shadowmap. + private var objectToShadowMapTransform:Transform3D = new Transform3D(); + // Auxiliary matrix for transfer from global space to local space of light source. + private var globalToLightTransform:Transform3D = new Transform3D(); + + private var tempBounds:BoundBox = new BoundBox(); + private var rect:Rectangle = new Rectangle(); + private var tmpPoints:Vector. = Vector.([ + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D() + ]); + private var localTmpPoints:Vector. = Vector.([ + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D() + ]); + + /** + * Create an instance of DirectionalLightShadow. + * @param width Width of area, that will dispay shadow. + * @param height Lenght of area, that will display shadow. + * @param nearBoundPosition Near bound of cut of shadow calculation area. + * @param farBoundPosition Far bound of cut of shadow calculation area. + * @param mapSize Size of shadow map. Must be a power of two. + * @param pcfOffset Mitigation of shadow bounds. + */ + public function DirectionalLightShadow(width:Number, height:Number, nearBoundPosition:Number, farBoundPosition:Number, mapSize:int = 512, pcfOffset:Number = 0) { + this.width = width; + this.height = height; + this.nearBoundPosition = nearBoundPosition; + this.farBoundPosition = farBoundPosition; + + if (mapSize < 2) { + throw new ArgumentError("Map size cannot be less than 2."); + } else if (mapSize > 2048) { + throw new ArgumentError("Map size exceeds maximum value 2048."); + } + if ((Math.log(mapSize)/Math.LN2 % 1) != 0) { + throw new ArgumentError("Map size must be power of two."); + } + this._mapSize = mapSize; + + this._pcfOffset = pcfOffset; + this.type = _pcfOffset > 0 ? "S" : "s"; + + vertexShadowProcedure = getVShader(); + fragmentShadowProcedure = _pcfOffset > 0 ? getFShaderPCF() : getFShader(); + + debugMaterial = new TextureMaterial(debugTexture); + debugMaterial.alphaThreshold = 1.1; + debugMaterial.opaquePass = false; + debugMaterial.alpha = 0.7; + } + + private function createDebugPlane(material:Material, context:Context3D):Mesh { + var mesh:Mesh = new Mesh(); + var geometry:Geometry = new Geometry(4); + mesh.geometry = geometry; + + var attributes:Array = new Array(); + attributes[0] = VertexAttributes.POSITION; + attributes[1] = VertexAttributes.POSITION; + attributes[2] = VertexAttributes.POSITION; + attributes[3] = VertexAttributes.TEXCOORDS[0]; + attributes[4] = VertexAttributes.TEXCOORDS[0]; + geometry.addVertexStream(attributes); + + geometry.setAttributeValues(VertexAttributes.POSITION, Vector.([-0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, -0.5, 0])); + geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], Vector.([0, 0, 0, 1, 1, 1, 1, 0])); + + geometry.indices = Vector.([0, 1, 3, 2, 3, 1, 0, 3, 1, 2, 1, 3]); + + mesh.addSurface(material, 0, 4); + geometry.upload(context); + + return mesh; + } + + /** + * @private + */ + override alternativa3d function process(camera:Camera3D):void { + var i:int; + var object:Object3D; + // Clipping of casters, that have shadows which are invisible. + var numActualCasters:int = 0; + for (i = 0; i < _casters.length; i++) { + object = _casters[i]; + + var visible:Boolean = object.visible; + var parent:Object3D = object._parent; + while (visible && parent != null) { + visible = parent.visible; + parent = parent._parent; + } + if (visible) { + actualCasters[numActualCasters++] = object; + } + } + + if (camera.context3D != cachedContext) { + // Processing of changing of context. + programs = new Dictionary(); + shadowMap = null; + debugPlane = null; + cachedContext = camera.context3D; + } + + var frustumMinX:Number; + var frustumMaxX:Number; + var frustumMinY:Number; + var frustumMaxY:Number; + var frustumMinZ:Number; + var frustumMaxZ:Number; + + globalToLightTransform.combine(_light.cameraToLocalTransform, camera.globalToLocalTransform); + + // 2 - calculate boundaries of shadow frustum. + if (calculateParametersByVolume) { + updateParametersByVolume(); + } + var cx:Number = centerX*globalToLightTransform.a + centerY*globalToLightTransform.b + centerZ*globalToLightTransform.c + globalToLightTransform.d; + var cy:Number = centerX*globalToLightTransform.e + centerY*globalToLightTransform.f + centerZ*globalToLightTransform.g + globalToLightTransform.h; + var cz:Number = centerX*globalToLightTransform.i + centerY*globalToLightTransform.j + centerZ*globalToLightTransform.k + globalToLightTransform.l; + + // Size of pixel in light source. + var wPSize:Number = width/_mapSize; + var hPSize:Number = height/_mapSize; + // Round coordinates of center to integer pixels. + cx = Math.round(cx/wPSize)*wPSize; + cy = Math.round(cy/hPSize)*hPSize; + // TODO: implement rounding among the z-axis too + + frustumMinX = cx - width*0.5; + frustumMaxX = cx + width*0.5; + frustumMinY = cy - height*0.5; + frustumMaxY = cy + height*0.5; + + frustumMinZ = cz + nearBoundPosition; + frustumMaxZ = cz + farBoundPosition; + + // Calculation of projection matrices to shadowmap in context. + var correction:Number = (_mapSize - 2)/_mapSize; + // Calculate projection matrix. + cameraToShadowMapContextProjection.a = 2/(frustumMaxX - frustumMinX)*correction; + cameraToShadowMapContextProjection.b = 0; + cameraToShadowMapContextProjection.c = 0; + cameraToShadowMapContextProjection.e = 0; + cameraToShadowMapContextProjection.f = -2/(frustumMaxY - frustumMinY)*correction; + cameraToShadowMapContextProjection.g = 0; + cameraToShadowMapContextProjection.h = 0; + cameraToShadowMapContextProjection.i = 0; + cameraToShadowMapContextProjection.j = 0; + cameraToShadowMapContextProjection.k = 1 / (frustumMaxZ - frustumMinZ); + cameraToShadowMapContextProjection.d = (-0.5 * (frustumMaxX + frustumMinX) * cameraToShadowMapContextProjection.a); + cameraToShadowMapContextProjection.h = (-0.5 * (frustumMaxY + frustumMinY) * cameraToShadowMapContextProjection.f); + cameraToShadowMapContextProjection.l = -frustumMinZ / (frustumMaxZ - frustumMinZ); + + cameraToShadowMapUVProjection.copy(cameraToShadowMapContextProjection); + cameraToShadowMapUVProjection.a = 1 / ((frustumMaxX - frustumMinX)) * correction; + cameraToShadowMapUVProjection.f = 1 / ((frustumMaxY - frustumMinY)) * correction; + cameraToShadowMapUVProjection.d = 0.5 - (0.5 * (frustumMaxX + frustumMinX) * cameraToShadowMapUVProjection.a); + cameraToShadowMapUVProjection.h = 0.5 - (0.5 * (frustumMaxY + frustumMinY) * cameraToShadowMapUVProjection.f); + + cameraToShadowMapContextProjection.prepend(_light.cameraToLocalTransform); + cameraToShadowMapUVProjection.prepend(_light.cameraToLocalTransform); + + // Calculation of transfer matrix to space of shadowmap texture. + for (i = 0; i < numActualCasters; i++) { + object = actualCasters[i]; + // 4- Collect drawcalls for caster and its child objects. + collectDraws(camera.context3D, object); + } + + // Rendering of drawacalls to atlas. + if (shadowMap == null) { + shadowMap = camera.context3D.createTexture(_mapSize, _mapSize, Context3DTextureFormat.BGRA, true); + debugTexture._texture = shadowMap; + } + camera.context3D.setRenderToTexture(shadowMap, true); + camera.context3D.clear(1, 0, 0, 0.3); + + renderer.camera = camera; + + rect.x = 1; + rect.y = 1; + rect.width = _mapSize - 2; + rect.height = _mapSize - 2; + camera.context3D.setScissorRectangle(rect); + + renderer.render(camera.context3D); + + camera.context3D.setScissorRectangle(null); + + camera.context3D.setRenderToBackBuffer(); + + if (debug) { + if (numActualCasters > 0) { + if (debugPlane == null) { + debugPlane = createDebugPlane(debugMaterial, camera.context3D); + } + // Form transformation matrix for debugPlane + debugPlane.transform.compose((frustumMinX + frustumMaxX) / 2, (frustumMinY + frustumMaxY) / 2, frustumMinZ, 0, 0, 0, (frustumMaxX - frustumMinX), (frustumMaxY - frustumMinY), 1); + debugPlane.localToCameraTransform.combine(_light.localToCameraTransform, debugPlane.transform); + + // Draw + var debugSurface:Surface = debugPlane._surfaces[0]; + debugSurface.material.collectDraws(camera, debugSurface, debugPlane.geometry, emptyLightVector, 0, -1); + + // Form transformation matrix for debugPlane + debugPlane.transform.compose((frustumMinX + frustumMaxX) / 2, (frustumMinY + frustumMaxY) / 2, frustumMaxZ, 0, 0, 0, (frustumMaxX - frustumMinX), (frustumMaxY - frustumMinY), 1); + debugPlane.localToCameraTransform.combine(_light.localToCameraTransform, debugPlane.transform); + debugSurface.material.collectDraws(camera, debugSurface, debugPlane.geometry, emptyLightVector, 0, -1); + } + + tempBounds.minX = frustumMinX; + tempBounds.maxX = frustumMaxX; + tempBounds.minY = frustumMinY; + tempBounds.maxY = frustumMaxY; + tempBounds.minZ = frustumMinZ; + tempBounds.maxZ = frustumMaxZ; + Debug.drawBoundBox(camera, tempBounds, _light.localToCameraTransform, 0xe1cd27); + } + } + + private function updateParametersByVolume():void { +// globalToLightTransform.combine(_light.cameraToLocalTransform, camera.globalToLocalTransform); + + if (volume != null) { + // converts boundbox to point. + tmpPoints[0].x = tmpPoints[2].x = tmpPoints[3].x = volume.minX; + tmpPoints[1].x = volume.maxX; + tmpPoints[2].y = volume.minY; + tmpPoints[0].y = tmpPoints[1].y = tmpPoints[3].y = volume.maxY; + tmpPoints[0].z = tmpPoints[1].z = tmpPoints[2].z = volume.minZ; + tmpPoints[3].z = volume.maxZ; + + var i:int; + var tmpPoint:Vector3D; + var x:Number; + var y:Number; + var z:Number; + var localX:Number; + var localY:Number; + var localZ:Number; + + // converts points to local space. + tmpPoint = tmpPoints[0]; + x = tmpPoint.x; + y = tmpPoint.y; + z = tmpPoint.z; + localX = x*globalToLightTransform.a + y*globalToLightTransform.b + z*globalToLightTransform.c + globalToLightTransform.d; + localY = x*globalToLightTransform.e + y*globalToLightTransform.f + z*globalToLightTransform.g + globalToLightTransform.h; + localZ = x*globalToLightTransform.i + y*globalToLightTransform.j + z*globalToLightTransform.k + globalToLightTransform.l; + tempBounds.minX = localX; + tempBounds.maxX = localX; + tempBounds.minY = localY; + tempBounds.maxY = localY; + tempBounds.minZ = localZ; + tempBounds.maxZ = localZ; + tmpPoint = localTmpPoints[0]; + tmpPoint.x = localX; + tmpPoint.y = localY; + tmpPoint.z = localZ; + + for (i = 1; i<4; i++){ + tmpPoint = tmpPoints[i]; + x = tmpPoint.x; + y = tmpPoint.y; + z = tmpPoint.z; + localX = x*globalToLightTransform.a + y*globalToLightTransform.b + z*globalToLightTransform.c + globalToLightTransform.d; + localY = x*globalToLightTransform.e + y*globalToLightTransform.f + z*globalToLightTransform.g + globalToLightTransform.h; + localZ = x*globalToLightTransform.i + y*globalToLightTransform.j + z*globalToLightTransform.k + globalToLightTransform.l; + + // Find maximums and minimums and put them to local boundbox. + if (tempBounds.minX > localX)tempBounds.minX = localX; + if (tempBounds.maxX < localX)tempBounds.maxX = localX; + if (tempBounds.minY > localY)tempBounds.minY = localY; + if (tempBounds.maxY < localY)tempBounds.maxY = localY; + if (tempBounds.minZ > localZ)tempBounds.minZ = localZ; + if (tempBounds.maxZ < localZ)tempBounds.maxZ = localZ; + tmpPoint = localTmpPoints[i]; + tmpPoint.x = localX; + tmpPoint.y = localY; + tmpPoint.z = localZ; + } + + // Find last four points and maximums/minimums of them. + var localTmpPoint0:Vector3D = localTmpPoints[0]; + var localTmpPoint1:Vector3D = localTmpPoints[1]; + var localTmpPoint2:Vector3D = localTmpPoints[2]; + var localTmpPoint3:Vector3D = localTmpPoints[3]; + //7 + localX = localTmpPoint2.x + localTmpPoint3.x - localTmpPoint0.x; + localY = localTmpPoint2.y + localTmpPoint3.y - localTmpPoint0.y; + localZ = localTmpPoint2.z + localTmpPoint3.z - localTmpPoint0.z; + if (tempBounds.minX > localX)tempBounds.minX = localX; + if (tempBounds.maxX < localX)tempBounds.maxX = localX; + if (tempBounds.minY > localY)tempBounds.minY = localY; + if (tempBounds.maxY < localY)tempBounds.maxY = localY; + if (tempBounds.minZ > localZ)tempBounds.minZ = localZ; + if (tempBounds.maxZ < localZ)tempBounds.maxZ = localZ; + + //5 + localX = localTmpPoint3.x + localTmpPoint1.x - localTmpPoint0.x; + localY = localTmpPoint3.y + localTmpPoint1.y - localTmpPoint0.y; + localZ = localTmpPoint3.z + localTmpPoint1.z - localTmpPoint0.z; + if (tempBounds.minX > localX)tempBounds.minX = localX; + if (tempBounds.maxX < localX)tempBounds.maxX = localX; + if (tempBounds.minY > localY)tempBounds.minY = localY; + if (tempBounds.maxY < localY)tempBounds.maxY = localY; + if (tempBounds.minZ > localZ)tempBounds.minZ = localZ; + if (tempBounds.maxZ < localZ)tempBounds.maxZ = localZ; + + //6 + localX = localTmpPoint2.x + localTmpPoint1.x - localTmpPoint0.x; + localY = localTmpPoint2.y + localTmpPoint1.y - localTmpPoint0.y; + localZ = localTmpPoint2.z + localTmpPoint1.z - localTmpPoint0.z; + if (tempBounds.minX > localX)tempBounds.minX = localX; + if (tempBounds.maxX < localX)tempBounds.maxX = localX; + if (tempBounds.minY > localY)tempBounds.minY = localY; + if (tempBounds.maxY < localY)tempBounds.maxY = localY; + if (tempBounds.minZ > localZ)tempBounds.minZ = localZ; + if (tempBounds.maxZ < localZ)tempBounds.maxZ = localZ; + + //4 + localX = localX + localTmpPoint3.x - localTmpPoint0.x; + localY = localY + localTmpPoint3.y - localTmpPoint0.y; + localZ = localZ + localTmpPoint3.z - localTmpPoint0.z; + if (tempBounds.minX > localX)tempBounds.minX = localX; + if (tempBounds.maxX < localX)tempBounds.maxX = localX; + if (tempBounds.minY > localY)tempBounds.minY = localY; + if (tempBounds.maxY < localY)tempBounds.maxY = localY; + if (tempBounds.minZ > localZ)tempBounds.minZ = localZ; + if (tempBounds.maxZ < localZ)tempBounds.maxZ = localZ; + + //Calculate parameters, depending on the boundbox. + width = tempBounds.maxX - tempBounds.minX; + height = tempBounds.maxY - tempBounds.minY; + nearBoundPosition = (tempBounds.minZ - tempBounds.maxZ)/2; + farBoundPosition = -nearBoundPosition; + + centerX = (volume.minX + volume.maxX)/2; + centerY = (volume.minY + volume.maxY)/2; + centerZ = (volume.minZ + volume.maxZ)/2; + } + } + + private function getProgram(transformProcedure:Procedure, programListByTransformProcedure:Vector., context:Context3D, alphaTest:Boolean, useDiffuseAlpha:Boolean):ShaderProgram { + var key:int = (alphaTest ? (useDiffuseAlpha ? 1 : 2) : 0); + var program:ShaderProgram = programListByTransformProcedure[key]; + + if (program == null) { + var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + + var positionVar:String = "aPosition"; + vLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + + if (alphaTest) { + vLinker.addProcedure(passUVProcedure); + } + + if (transformProcedure != null) { + var newPosVar:String = "tTransformedPosition"; + vLinker.declareVariable(newPosVar); + vLinker.addProcedure(transformProcedure, positionVar); + vLinker.setOutputParams(transformProcedure, newPosVar); + positionVar = newPosVar; + } + + + var proc:Procedure = Procedure.compileFromArray([ + "#c3=cScale", + "#v0=vDistance", + "m34 t0.xyz, i0, c0", + "mov t0.w, c3.w", + "mul v0, t0, c3.x", + "mov o0, t0" + ]); + proc.assignVariableName(VariableType.CONSTANT, 0, "cTransform", 3); + vLinker.addProcedure(proc, positionVar); + + if (alphaTest) { + if (useDiffuseAlpha) { + fLinker.addProcedure(diffuseAlphaTestProcedure); + } else { + fLinker.addProcedure(opacityAlphaTestProcedure); + } + } + fLinker.addProcedure(Procedure.compileFromArray([ + "#v0=vDistance", + "#c0=cConstants", + "mov t0.xy, v0.zz", + "frc t0.y, v0.z", + "sub t0.x, v0.z, t0.y", + "mul t0.x, t0.x, c0.x", + "mov t0.z, c0.z", + "mov t0.w, c0.w", + "mov o0, t0" + ])); + program = new ShaderProgram(vLinker, fLinker); + fLinker.varyings = vLinker.varyings; + programListByTransformProcedure[key] = program; + program.upload(context); + } + return program; + } + + /** + * @private + * Procedure for passing of UV coordinates to fragment shader. + */ + static private const passUVProcedure:Procedure = new Procedure(["#v0=vUV", "#a0=aUV", "mov v0, a0"], "passUVProcedure"); + + // diffuse alpha test + private static const diffuseAlphaTestProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#s0=sTexture", + "#c0=cThresholdAlpha", + "tex t0, v0, s0 <2d, linear,repeat, miplinear>", + "mul t0.w, t0.w, c0.w", + "sub t0.w, t0.w, c0.x", + "kil t0.w", + ], "diffuseAlphaTestProcedure"); + + // opacity alpha test + private static const opacityAlphaTestProcedure:Procedure = new Procedure([ + "#v0=vUV", + "#s0=sTexture", + "#c0=cThresholdAlpha", + "tex t0, v0, s0 <2d, linear,repeat, miplinear>", + "mul t0.w, t0.x, c0.w", + "sub t0.w, t0.w, c0.x", + "kil t0.w"], "opacityAlphaTestProcedure"); + + // collectDraws for rendering to shadowmap. + private function collectDraws(context:Context3D, object:Object3D):void { + // alphaThreshold:Number, diffuse:TextureResource, opacity:TextureResource, materialAlpha:Number + var child:Object3D; + + var mesh:Mesh = object as Mesh; + if (mesh != null && mesh.geometry != null) { + var program:ShaderProgram; + var programListByTransformProcedure:Vector.; + var skin:Skin = mesh as Skin; + + if (skin != null) { + // Calculation of matrices of joints. + for (child = skin.childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + // Write в localToGlobalTransform matrix of transfering to skin coordinates + child.localToGlobalTransform.copy(child.transform); + if (child is Joint) { + Joint(child).calculateTransform(); + } + skin.calculateJointsTransforms(child); + } + } + + // 1- calculation of transfer matrix from object to light source. + objectToShadowMapTransform.combine(cameraToShadowMapContextProjection, object.localToCameraTransform); + + for (var i:int = 0; i < mesh._surfacesLength; i++) { + // Form drawcall. + var surface:Surface = mesh._surfaces[i]; + if (surface.material == null) continue; + + var material:Material = surface.material; + var geometry:Geometry = mesh.geometry; + var alphaTest:Boolean; + var useDiffuseAlpha:Boolean; + var alphaThreshold:Number; + var materialAlpha:Number; + var diffuse:TextureResource; + var opacity:TextureResource; + var uvBuffer:VertexBuffer3D; + + if (material is TextureMaterial) { + alphaThreshold = TextureMaterial(material).alphaThreshold; + materialAlpha = TextureMaterial(material).alpha; + diffuse = TextureMaterial(material).diffuseMap; + opacity = TextureMaterial(material).opacityMap; + alphaTest = alphaThreshold > 0; + useDiffuseAlpha = TextureMaterial(material).opacityMap == null; + uvBuffer = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]); + if (uvBuffer == null) continue; + } else { + alphaTest = false; + useDiffuseAlpha = false; + } + + var positionBuffer:VertexBuffer3D = mesh.geometry.getVertexBuffer(VertexAttributes.POSITION); + if (positionBuffer == null) continue; + + if (skin != null) { + object.transformProcedure = skin.surfaceTransformProcedures[i]; + } + programListByTransformProcedure = programs[object.transformProcedure]; + if (programListByTransformProcedure == null) { + programListByTransformProcedure = new Vector.(3, true); + programs[object.transformProcedure] = programListByTransformProcedure; + } + program = getProgram(object.transformProcedure, programListByTransformProcedure, context, alphaTest, useDiffuseAlpha); + + var drawUnit:DrawUnit = renderer.createDrawUnit(object, program.program, mesh.geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program); + + // Setting of buffers. + object.setTransformConstants(drawUnit, surface, program.vertexShader, null); + + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, mesh.geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + + if (alphaTest) { + drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, materialAlpha); + if (useDiffuseAlpha) { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sTexture"), diffuse._texture); + } else { + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sTexture"), opacity._texture); + } + } + + // Setting of constants. + drawUnit.setVertexConstantsFromTransform(program.vertexShader.getVariableIndex("cTransform"), objectToShadowMapTransform); + drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("cScale"), 255, 0, 0, 1); + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cConstants"), 1 / 255, 0, 0, 1); + + renderer.addDrawUnit(drawUnit, Renderer.OPAQUE); + } + } + for (child = object.childrenList; child != null; child = child.next) { + if (child.visible) collectDraws(context, child); + } + } + + //------------- ShadowMap Shader ---------- + + private static function getVShader():Procedure { + var shader:Procedure = Procedure.compileFromArray([ + "#v0=vSample", + "m34 v0.xyz, i0, c0", + "mov v0.w, i0.w" + ], "DirectionalShadowMapVertex"); + shader.assignVariableName(VariableType.CONSTANT, 0, "cUVProjection", 3); + return shader; + } + + private static function getFShader():Procedure { + var shaderArr:Array = [ + "#v0=vSample", + "#c0=cConstants", + "#c1=cDist", // 0, -max*10000, 10000 + "#s0=sShadowMap" + ]; + var line:int = 4; + shaderArr[line++] = "mov t0.zw, v0.zz"; + // Distance. + shaderArr[line++] = "tex t0.xy, v0, s0 <2d,clamp,near,nomip>"; + shaderArr[line++] = "dp3 t0.x, t0.xyz, c0.xyz"; + + // Clipping by distance. + shaderArr[line++] = "sub t0.y, c1.x, t0.z"; // maxDist - z + shaderArr[line++] = "mul t0.y, t0.y, c1.y"; // mul 10000 + shaderArr[line++] = "sat t0.xy, t0.xy"; + shaderArr[line++] = "mul t0.x, t0.x, t0.y"; + shaderArr[line++] = "sub o0, c1.z, t0.x"; + + return Procedure.compileFromArray(shaderArr, "DirectionalShadowMapFragment"); + } + + private static const pcfOffsetRegisters:Array = [ + "xx", "xy", "xz", "xw", + "yx", "yy", "yz", "yw", + "zx", "zy", "zz", "zw", + "wx", "wy", "wz", "ww" + ]; + private static const componentByIndex:Array = [ + "x", "y", "z", "w" + ]; + + private static function getFShaderPCF():Procedure { + var shaderArr:Array = [ + "#v0=vSample", + "#c0=cConstants", + "#c1=cPCFOffsets", + "#c2=cDist", + "#s0=sShadowMap" + ]; + var line:int = 5; + shaderArr[line++] = "mov t0.zw, v0.zz"; // put distance to t1.z + for (var i:int = 0; i < 16; i++) { + var column:int = i & 3; + + // Calculation of offset + shaderArr[line++] = "add t0.xy, v0.xy, c1." + pcfOffsetRegisters[i]; + // Distance. + shaderArr[line++] = "tex t0.xy, t0, s0 <2d,clamp,near,nomip>"; + shaderArr[line++] = "dp3 t1." + componentByIndex[column] + ", t0.xyz, c0.xyz"; // restore distance and calculate difference + if (column == 3) { + // Last item in string. + shaderArr[line++] = "sat t1, t1"; + shaderArr[line++] = "dp4 t2." + componentByIndex[int(i >> 2)] + ", t1, c0.w"; + } + } + shaderArr[line++] = "dp4 t0.x, t2, v0.w"; + + // Clipping by distance. + shaderArr[line++] = "sub t0.y, c2.x, t0.z"; // maxDist - z + shaderArr[line++] = "mul t0.y, t0.y, c2.y"; // mul 10000 + shaderArr[line++] = "sat t0.y, t0.y"; + shaderArr[line++] = "mul t0.x, t0.x, t0.y"; + shaderArr[line++] = "sub o0, c2.z, t0.x"; + + return Procedure.compileFromArray(shaderArr, "DirectionalShadowMapFragment"); + } + + /** + * @private + */ + alternativa3d override function setup(drawUnit:DrawUnit, vertexLinker:Linker, fragmentLinker:Linker, surface:Surface):void { + // Set transfer matrix to shadowmap. + objectToShadowMapTransform.combine(cameraToShadowMapUVProjection, surface.object.localToCameraTransform); + + drawUnit.setVertexConstantsFromTransform(vertexLinker.getVariableIndex("cUVProjection"), objectToShadowMapTransform); + // Set shadowmap. + drawUnit.setTextureAt(fragmentLinker.getVariableIndex("sShadowMap"), shadowMap); + // TODO: set multiplier more correct. It is possible that 65536 (resolution of the buffer depth). + // Set coefficients. + drawUnit.setFragmentConstantsFromNumbers(fragmentLinker.getVariableIndex("cConstants"), -255*10000, -10000, biasMultiplier*255*10000, 1/16); + if (_pcfOffset > 0) { + var offset1:Number = _pcfOffset/_mapSize; + var offset2:Number = offset1/3; + + drawUnit.setFragmentConstantsFromNumbers(fragmentLinker.getVariableIndex("cPCFOffsets"), -offset1, -offset2, offset2, offset1); + } + drawUnit.setFragmentConstantsFromNumbers(fragmentLinker.getVariableIndex("cDist"), 0.9999, 10000, 1); + } + + /** + * Adds given object to list of objects, that cast shadow. + * @param object Added object. + */ + public function addCaster(object:Object3D):void { + if (_casters.indexOf(object) < 0) { + _casters.push(object); + } + } + + /** + * Clears the list of objects, that cast shadow. + */ + public function clearCasters():void { + _casters.length = 0; + } + + /** + * Set resolution of shadow map. This property can get value of power of 2 (up to 2048). + */ + public function get mapSize():int { + return _mapSize; + } + + /** + * @private + */ + public function set mapSize(value:int):void { + if (value != _mapSize) { + this._mapSize = value; + if (value < 2) { + throw new ArgumentError("Map size cannot be less than 2."); + } else if (value > 2048) { + throw new ArgumentError("Map size exceeds maximum value 2048."); + } + if ((Math.log(value)/Math.LN2 % 1) != 0) { + throw new ArgumentError("Map size must be power of two."); + } + if (shadowMap != null) { + shadowMap.dispose(); + } + shadowMap = null; + } + } + + /** + * Offset of Percentage Closer Filtering. This way of filtering is used for mitigation of shadow bounds. + */ + public function get pcfOffset():Number { + return _pcfOffset; + } + + /** + * @private + */ + public function set pcfOffset(value:Number):void { + _pcfOffset = value; + type = _pcfOffset > 0 ? "S" : "s"; + fragmentShadowProcedure = _pcfOffset > 0 ? getFShaderPCF() : getFShader(); + } + + } +} diff --git a/src/alternativa/engine3d/shadows/DirectionalShadowRenderer.as b/src/alternativa/engine3d/shadows/DirectionalShadowRenderer.as new file mode 100644 index 0000000..ffdf234 --- /dev/null +++ b/src/alternativa/engine3d/shadows/DirectionalShadowRenderer.as @@ -0,0 +1,540 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.primitives.Box; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.TextureResource; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.Program3D; + import flash.display3D.textures.Texture; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class DirectionalShadowRenderer extends ShadowRenderer { + + public var offset:Vector3D = new Vector3D(); + + public var caster:Object3D; + + private var context:Context3D; + + private var shadowMap:Texture; + private var _worldSize:Number; + + private var light:DirectionalLight; + alternativa3d var globalToShadowMap:Matrix3D = new Matrix3D(); + + private var debugObject:Mesh; + public var debugMaterial:TextureMaterial = new TextureMaterial(); + private var debugTexture:TextureResource = new ExternalTextureResource("null"); +// private var debugTexture:TextureResource = new BitmapTextureResource(new BitmapData(4, 4, false, 0xFF0000)); + + private static const constants:Vector. = Vector.([ +// 255, 255*0.98, 100, 1 + 255, 255*0.96, 100, 1 + ]); + + private var pcfOffset:Number = 0; + private var pcfOffsets:Vector.; + + public function DirectionalShadowRenderer(context:Context3D, size:int, worldSize:Number, pcfSize:Number = 0) { + this.context = context; + this._worldSize = worldSize; + this.pcfOffset = pcfSize/worldSize/255; +// this.pcfOffset = pcfSize; + if (pcfOffset > 0) { + pcfOffsets = Vector.([ + -pcfOffset, -pcfOffset, 0, 1/4, + -pcfOffset, pcfOffset, 0, 1, + pcfOffset, -pcfOffset, 0, 1, + pcfOffset, pcfOffset, 0, 1 + ]); + } + this.shadowMap = context.createTexture(size, size, Context3DTextureFormat.BGRA, true); + debugTexture._texture = this.shadowMap; + debugMaterial.diffuseMap = debugTexture; + debugMaterial.alpha = 0.9; + // TODO: fix + debugMaterial.transparentPass = true; + debugMaterial.opaquePass = false; + debugMaterial.alphaThreshold = 1.1; + +// debugTexture.upload(context); + + debugObject = new Box(worldSize, worldSize, 1, 1, 1, 1, false, debugMaterial); + debugObject.geometry.upload(context); + } + + public function get worldSize():Number { + return _worldSize; + } + + public function set worldSize(value:Number):void { + _worldSize = value; + var newDebug:Mesh = new Box(_worldSize, _worldSize, 1, 1, 1, 1, false, debugMaterial); + newDebug.geometry.upload(context); + if (debugObject._parent != null) { + debugObject._parent.addChild(newDebug); + debugObject._parent.removeChild(debugObject); + } + debugObject = newDebug; + } + + private var _debug:Boolean = false; + public function setLight(value:DirectionalLight):void { + light = value; + if (_debug) { + light.addChild(debugObject); + } + } + + override public function get debug():Boolean { + return _debug; + } + + override public function set debug(value:Boolean):void { + _debug = value; + if (_debug) { + if (light != null) { + light.addChild(debugObject); + } + } else { + if (debugObject._parent != null) { + debugObject._parent.removeChild(debugObject); + } + } + } + + private static var matrix:Matrix3D = new Matrix3D(); + override alternativa3d function cullReciever(boundBox:BoundBox, object:Object3D):Boolean { + copyMatrixFromTransform(matrix, object.localToGlobalTransform); + matrix.append(this.globalToShadowMap); + return cullObjectImplementation(boundBox, matrix); + } + + private var lightProjectionMatrix:Matrix3D = new Matrix3D(); + private var uvMatrix:Matrix3D = new Matrix3D(); + private var center:Vector3D = new Vector3D(); + override public function update():void { + active = true; + var root:Object3D; + // Расчитываем матрицу объекта +// if (caster.transformChanged) { + caster.localToCameraTransform.compose(caster._x, caster._y, caster._z, caster._rotationX, caster._rotationY, caster._rotationZ, caster._scaleX, caster._scaleY, caster._scaleZ); +// } else { +// caster.localToCameraTransform.copy(caster.transform); +// } + root = caster; + while (root._parent != null) { + root = root._parent; +// if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); +// } + caster.localToCameraTransform.append(root.localToGlobalTransform); + } + + // Расчитываем матрицу лайта + light.localToGlobalTransform.compose(light._x, light._y, light._z, light._rotationX, light._rotationY, light._rotationZ, light._scaleX, light._scaleY, light._scaleZ); + root = light; + while (root._parent != null) { + root = root._parent; +// if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); +// } + light.localToGlobalTransform.append(root.localToGlobalTransform); + } + light.globalToLocalTransform.copy(light.localToGlobalTransform); + light.globalToLocalTransform.invert(); + + // Получаем матрицу перевода из объекта в лайт + caster.localToCameraTransform.append(light.globalToLocalTransform); + + // Расчет матрицы проецирования + var t:Transform3D = caster.localToCameraTransform; + center.x = t.a*offset.x + t.b*offset.y + t.c*offset.z + t.d; + center.y = t.e*offset.x + t.f*offset.y + t.g*offset.z + t.h; + center.z = t.i*offset.x + t.j*offset.y + t.k*offset.z + t.l; +// var center:Vector3D = new Vector3D(caster.localToCameraTransform.d, caster.localToCameraTransform.h, caster.localToCameraTransform.l); + + calculateShadowMapProjection(lightProjectionMatrix, uvMatrix, center, _worldSize, _worldSize, _worldSize); + copyMatrixFromTransform(globalToShadowMap, light.globalToLocalTransform); + globalToShadowMap.append(uvMatrix); + + debugObject.x = center.x; + debugObject.y = center.y; + debugObject.z = center.z - _worldSize/2; +// trace("center", center); + + debugMaterial.diffuseMap = null; + + // Рисуем в шедоумапу + context.setRenderToTexture(shadowMap, true, 0, 0); +// context.clear(1); + context.clear(1, 1, 1, 1); + cleanContext(context); + drawObjectToShadowMap(context, caster, light, lightProjectionMatrix); + context.setRenderToBackBuffer(); + cleanContext(context); + debugMaterial.diffuseMap = debugTexture; + } + + private static var transformToMatrixRawData:Vector. = new Vector.(16); + alternativa3d static function copyMatrixFromTransform(matrix:Matrix3D, transform:Transform3D):void { + transformToMatrixRawData[0] = transform.a; + transformToMatrixRawData[1] = transform.e; + transformToMatrixRawData[2] = transform.i; + transformToMatrixRawData[3] = 0; + transformToMatrixRawData[4] = transform.b; + transformToMatrixRawData[5] = transform.f; + transformToMatrixRawData[6] = transform.j; + transformToMatrixRawData[7] = 0; + transformToMatrixRawData[8] = transform.c; + transformToMatrixRawData[9] = transform.g; + transformToMatrixRawData[10] = transform.k; + transformToMatrixRawData[11] = 0; + transformToMatrixRawData[12] = transform.d; + transformToMatrixRawData[13] = transform.h; + transformToMatrixRawData[14] = transform.l; + transformToMatrixRawData[15] = 1; +// matrix.copyRawDataFrom(transformToMatrixRawData); + matrix.rawData = transformToMatrixRawData; + } + + alternativa3d static function drawObjectToShadowMap(context:Context3D, object:Object3D, light:DirectionalLight, projection:Matrix3D):void { + if (object is Mesh) { + drawMeshToShadowMap(context, Mesh(object), projection); + } + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.visible && child.useShadow) { + if (child.transformChanged) child.composeTransforms(); + child.localToCameraTransform.combine(object.localToCameraTransform, child.transform); + drawObjectToShadowMap(context, child, light, projection); + } + } + } + + private static var drawProjection:Matrix3D = new Matrix3D(); + private static var directionalShadowMapProgram:Program3D; + private static function drawMeshToShadowMap(context:Context3D, mesh:Mesh, projection:Matrix3D):void { + if (mesh.geometry == null || mesh.geometry.numTriangles == 0 || !mesh.geometry.isUploaded) { + return; + } + + copyMatrixFromTransform(drawProjection, mesh.localToCameraTransform); + drawProjection.append(projection); + if (directionalShadowMapProgram == null) directionalShadowMapProgram = initMeshToShadowMapProgram(context); + context.setProgram(directionalShadowMapProgram); + + context.setVertexBufferAt(0, mesh.geometry.getVertexBuffer(VertexAttributes.POSITION), mesh.geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + + context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, drawProjection, true); + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, Vector.([255, 0, 0, 1])); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([1/255, 0, 0, 1])); + + context.setCulling(Context3DTriangleFace.BACK); + + for (var i:int = 0; i < mesh._surfacesLength; i++) { + var surface:Surface = mesh._surfaces[i]; + if (surface.material == null || !surface.material.canDrawInShadowMap) continue; + context.drawTriangles(mesh.geometry._indexBuffer, surface.indexBegin, surface.numTriangles); + } + context.setVertexBufferAt(0, null); + } + + private static function initMeshToShadowMapProgram(context3d:Context3D):Program3D { + var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var proc:Procedure = Procedure.compileFromArray([ + "#a0=a0", + "#c4=c4", + "#v0=v0", + "m44 t0, a0, c0", + "mul v0, t0, c4.x", + "mov o0, t0" + ]); + proc.assignVariableName(VariableType.CONSTANT, 0, "c0", 4); + vLinker.addProcedure(proc); + + fLinker.addProcedure(Procedure.compileFromArray([ + "#v0=v0", + "#c0=c0", + "mov t0.xy, v0.zz", + "frc t0.y, v0.z", + "sub t0.x, v0.z, t0.y", + "mul t0.x, t0.x, c0.x", + "mov t0.z, c0.z", + "mov t0.w, c0.w", + "mov o0, t0" + ])); + var program:Program3D = context3d.createProgram(); +// trace("VERTEX"); +// trace(A3DUtils.disassemble(vLinker.getByteCode())); +// trace("FRAGMENT"); +// trace(A3DUtils.disassemble(fLinker.getByteCode())); + fLinker.varyings = vLinker.varyings; + vLinker.link(); + fLinker.link(); + program.upload(vLinker.data, fLinker.data); + return program; + } + + // должен быть заполнен нулями + private var rawData:Vector. = new Vector.(16); + private function calculateShadowMapProjection(matrix:Matrix3D, uvMatrix:Matrix3D, offset:Vector3D, width:Number, height:Number, length:Number):void { + var halfW:Number = width/2; + var halfH:Number = height/2; + var halfL:Number = length/2; + var frustumMinX:Number = offset.x - halfW; + var frustumMaxX:Number = offset.x + halfW; + var frustumMinY:Number = offset.y - halfH; + var frustumMaxY:Number = offset.y + halfH; + var frustumMinZ:Number = offset.z - halfL; + var frustumMaxZ:Number = offset.z + halfL; + + // Считаем матрицу проецирования + 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; + matrix.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.rawData = rawData; + } + +/* + private static const fullVShader:Procedure = initFullVShader(); + private static function initFullVShader():Procedure { + var shader:Procedure = Procedure.compileFromArray([ + "m44 o0, a0, c0", + // Координата вершины в локальном пространстве + "m44 v0, a0, c4", + ]); + shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPosition"); + shader.assignVariableName(VariableType.CONSTANT, 0, "cPROJ", 4); + shader.assignVariableName(VariableType.CONSTANT, 4, "cTOSHADOW", 4); + shader.assignVariableName(VariableType.VARYING, 0, "vSHADOWSAMPLE"); + return shader; + } +*/ + private static function initVShader(index:int):Procedure { + var shader:Procedure = Procedure.compileFromArray([ + "m44 v0, a0, c0" + ]); + shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPosition"); + shader.assignVariableName(VariableType.CONSTANT, 0, index + "cTOSHADOW", 4); + shader.assignVariableName(VariableType.VARYING, 0, index + "vSHADOWSAMPLE"); + return shader; + } + private static function initFShader(mult:Boolean, usePCF:Boolean, index:int, grayScale:Boolean = false):Procedure { + var i:int; + var line:int = 0; + var shaderArr:Array = []; + var numPass:uint = (usePCF) ? 4 : 1; + for (i = 0; i < numPass; i++) { + // Расстояние + shaderArr[line++] = "mov t0.w, v0.z"; + shaderArr[line++] = "mul t0.w, t0.w, c4.y"; // bias [0.99] * 255 + + if (usePCF) { + // Добавляем смещение + shaderArr[line++] = "mul t1, c" + (i + 6).toString() + ", t0.w"; + shaderArr[line++] = "add t1, v0, t1"; + shaderArr[line++] = "tex t1, t1, s0 <2d,clamp,near,nomip>"; + } else { + shaderArr[line++] = "tex t1, v0, s0 <2d,clamp,near,nomip>"; + } + + // Восстанавливаем расстояние + shaderArr[line++] = "mul t1.w, t1.x, c4.x"; // * 255 + shaderArr[line++] = "add t1.w, t1.w, t1.y"; + + // Перекрытие тенью + shaderArr[line++] = "sub t2.z, t1.w, t0.w"; + shaderArr[line++] = "mul t2.z, t2.z, c4.z"; // smooth [10000] + shaderArr[line++] = "sat t2.z, t2.z"; + + // Добавляем маску и прозрачность, затем sat + if (grayScale) { + shaderArr[line++] = "add t2, t2.zzzz, t1.zzzz"; // маска тени + } else { + shaderArr[line++] = "add t2.z, t2.z, t1.z"; // маска тени + shaderArr[line++] = "add t2, t2.zzzz, c5"; // цвет тени + } + shaderArr[line++] = "sat t2, t2"; + + if (usePCF) { + if (i == 0) { + shaderArr[line++] = "mov t3, t2"; + } else { + shaderArr[line++] = "add t3, t3, t2"; + } + } + } + if (usePCF) { + shaderArr[line++] = "mul t2, t3, c6.w"; + } + if (grayScale) { + shaderArr[line++] = "mov o0.w, t2.x"; + } else { + if (mult) { + shaderArr[line++] = "mul t0.xyz, i0.xyz, t2.xyz"; + shaderArr[line++] = "mov t0.w, i0.w"; + shaderArr[line++] = "mov o0, t0"; + } else { + shaderArr[line++] = "mov o0, t2"; + } + } + var shader:Procedure = Procedure.compileFromArray(shaderArr, "DirectionalShadowMap"); + shader.assignVariableName(VariableType.VARYING, 0, index + "vSHADOWSAMPLE"); + shader.assignVariableName(VariableType.CONSTANT, 4, index + "cConstants", 1); + if (!grayScale) shader.assignVariableName(VariableType.CONSTANT, 5, index + "cShadowColor", 1); + if (usePCF) { + for (i = 0; i < numPass; i++) { + shader.assignVariableName(VariableType.CONSTANT, i + 6, "cDPCF" + i.toString(), 1); + } + } + shader.assignVariableName(VariableType.SAMPLER, 0, index + "sSHADOWMAP"); + return shader; + } + + override public function getVShader(index:int = 0):Procedure { + return initVShader(index); + } + override public function getFShader(index:int = 0):Procedure { + return initFShader(false, (pcfOffset > 0), index); + } +// override public function getMultFShader():Procedure { +// return initFShader(true, (pcfOffset > 0), 0); +// } +// override public function getMultVShader():Procedure { +// return initVShader(0); +// } + + override public function getFIntensityShader():Procedure { + return initFShader(false, (pcfOffset > 0), 0, true); + } + + private static const objectToShadowMap:Matrix3D = new Matrix3D(); + private static const localToGlobal:Transform3D = new Transform3D(); + private static const vector:Vector. = new Vector.(16, false); + override public function applyShader(drawUnit:DrawUnit, program:ShaderProgram, object:Object3D, camera:Camera3D, index:int = 0):void { + // Считаем матрицу перевода в лайт из объекта + localToGlobal.combine(camera.localToGlobalTransform, object.localToCameraTransform); + copyMatrixFromTransform(objectToShadowMap, localToGlobal); + objectToShadowMap.append(globalToShadowMap); + objectToShadowMap.copyRawDataTo(vector, 0, true); +// objectToShadowMap.transpose(); + +// drawUnit.setVertexConstantsFromVector(program.vertexShader.getVariableIndex(index + "cTOSHADOW"), objectToShadowMap.rawData, 4) + drawUnit.setVertexConstantsFromVector(program.vertexShader.getVariableIndex(index + "cTOSHADOW"), vector, 4) + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex(index + "cConstants"), constants, 1); + if (program.fragmentShader.containsVariable(index + "cShadowColor")) { +// drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex(index + "cShadowColor"), camera.ambient, 1); + // В дальнейшем яркость тени увеличтся в два раза + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex(index + "cShadowColor"), camera.ambient[0]/2, camera.ambient[1]/2, camera.ambient[2]/2, 1); + } + + if (pcfOffset > 0) { +// destination.addFragmentConstantSet(program.fragmentShader.getVariableIndex(index + "cPCF0"), pcfOffsets, pcfOffsets.length/4); + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex("cDPCF0"), pcfOffsets, pcfOffsets.length/4); + } + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex(index + "sSHADOWMAP"), shadowMap); + } + +// override public function getTextureIndex(fLinker:Linker):int { +// return fLinker.getVariableIndex("sSHADOWMAP"); +// } + +// private static var program:ShaderProgram; +// private static var programPCF:ShaderProgram; +// private static function initMeshProgram(context:Context3D, usePCF:Boolean):ShaderProgram { +// var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); +// vLinker.addProcedure(fullVShader); +// +// var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); +// if (usePCF) { +// fLinker.addProcedure(pcfFShader); +// } else { +// fLinker.addProcedure(fShader); +// } +// +// vLinker.setOppositeLinker(fLinker); +// fLinker.setOppositeLinker(vLinker); +// +//// trace("[VERTEX]"); +//// trace(AgalUtils.disassemble(vLinker.getByteCode())); +//// trace("[FRAGMENT]"); +//// trace(AgalUtils.disassemble(fLinker.getByteCode())); +// +// var result:ShaderProgram; +// if (usePCF) { +// programPCF = new ShaderProgram(vLinker, fLinker); +// result = programPCF; +// } else { +// program = new ShaderProgram(vLinker, fLinker); +// result = program; +// } +// return result; +// } + +// override public function drawShadow(mesh:Mesh, camera:Camera3D, texture:Texture):void { +// var context3d:Context3D = camera.view._context3d; +// +// var linkedProgram:ShaderProgram; +// if (pcfOffset > 0) { +// linkedProgram = (programPCF == null) ? initMeshProgram(context3d, true) : programPCF; +// } else { +// linkedProgram = (program == null) ? initMeshProgram(context3d, false) : program; +// } +// var vLinker:Linker = linkedProgram.vLinker; +// var fLinker:Linker = linkedProgram.fLinker; +// context3d.setProgram(linkedProgram.program); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), mesh.geometry.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); +// context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vLinker.getVariableIndex("cPROJ"), mesh.projectionMatrix, true); +// applyShader(context3d, linkedProgram, mesh, camera); +// context3d.setVertexBufferAt(1, null); +// +// context3d.setCulling(Context3DTriangleFace.FRONT); +// context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), null); +// context.setTextureAt(getTextureIndex(fLinker), texture); +// } + + } +} diff --git a/src/alternativa/engine3d/shadows/OmniShadowRenderer.as b/src/alternativa/engine3d/shadows/OmniShadowRenderer.as new file mode 100644 index 0000000..270ac8f --- /dev/null +++ b/src/alternativa/engine3d/shadows/OmniShadowRenderer.as @@ -0,0 +1,798 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.core.View; + import alternativa.engine3d.lights.OmniLight; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.primitives.GeoSphere; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.Program3D; + import flash.display3D.textures.CubeTexture; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class OmniShadowRenderer extends ShadowRenderer { + +// [Embed("geosphere.A3D", mimeType="application/octet-stream")] private static const ModelClass:Class; + private static const debugGeometry:Geometry = createDebugGeometry(); + private static function createDebugGeometry():Geometry { +// var parser:ParserA3D = new ParserA3D(); +// parser.parse(new ModelClass()); +// var mesh:Mesh = Mesh(parser.getObjectByName("sphere")); +// return mesh.geometry; + var geo:GeoSphere = new GeoSphere(0.5, 4); + return geo.geometry; + } + + private var caster:Object3D; + private var casterBounds:BoundBox = new BoundBox(); + + private var pcfOffset:Number = 0; + + private var context:Context3D; + + public var omnies:Vector.; + + private var shadowMap:CubeTexture; + private var shadowMapSize:int; + private var cameras:Vector. = new Vector.(); + private var clearBits:uint = 0xFF; + + private var currentOmni:OmniLight = new OmniLight(0, 0, 0); + + private static const constants:Vector. = Vector.([ + 255, 0.97, 10000, 1/255 + ]); + private static const offset:Number = 0.005; + private static const pcfOffsets:Vector. = Vector.([ + -offset, -offset, -offset, 1/8, + -offset, -offset, offset, 1, + -offset, offset, -offset, 1, + -offset, offset, offset, 1, + offset, -offset, -offset, 1, + offset, -offset, offset, 1, + offset, offset, -offset, 1, + offset, offset, offset, 1, + ]); + + public function OmniShadowRenderer(context:Context3D, size:int, pcfSize:Number = 0) { + this.context = context; + this.pcfOffset = pcfSize; + shadowMapSize = size; + shadowMap = context.createCubeTexture(size, Context3DTextureFormat.BGRA, true); + debugGeometry.upload(context); + + for (var i:int = 0; i < 6; i++) { + var cam:Camera3D = new Camera3D(1, 100); + cam.fov = 1.910633237; + cam.view = new View(size, size); + cameras[i] = cam; + } + // Left + cameras[1].rotationY = -Math.PI/2; + cameras[1].scaleY = -1; + // Right + cameras[0].rotationY = Math.PI/2; + cameras[0].scaleY = -1; + // Back + cameras[3].rotationX = -Math.PI/2; + cameras[3].rotationZ = Math.PI; + cameras[3].scaleX = -1; + // Front + cameras[2].rotationX = -Math.PI/2; + cameras[2].scaleY = -1; + // Bottom + cameras[5].rotationX = Math.PI; + cameras[5].scaleX = -1; + // Top + cameras[4].rotationX = 0; + cameras[4].scaleY = -1; + } + + alternativa3d override function cullReciever(boundBox:BoundBox, object:Object3D):Boolean { +// tempBounds.reset(); +// object.localToCameraTransform.copy(object.localToGlobalTransform); +// StaticShadowRenderer.calculateBoundBox(tempBounds, object, false); + var bounds:BoundBox = object.boundBox; + object.globalToLocalTransform.copy(object.localToGlobalTransform); + object.globalToLocalTransform.invert(); + var inverseMatrix:Transform3D = object.globalToLocalTransform; + +// trace(object.scaleX, object.scaleY, object.scaleZ); + var ox:Number = inverseMatrix.a*currentOmni._x + inverseMatrix.b*currentOmni._y + inverseMatrix.c*currentOmni._z + inverseMatrix.d; + var oy:Number = inverseMatrix.e*currentOmni._x + inverseMatrix.f*currentOmni._y + inverseMatrix.g*currentOmni._z + inverseMatrix.h; + var oz:Number = inverseMatrix.i*currentOmni._x + inverseMatrix.j*currentOmni._y + inverseMatrix.k*currentOmni._z + inverseMatrix.l; + var radius:Number = currentOmni.attenuationEnd; + if (ox + radius > bounds.minX && ox - radius < bounds.maxX && oy + radius > bounds.minY && oy - radius < bounds.maxY && oz + radius > bounds.minZ && oz - radius < bounds.maxZ) { + return true; + } + return false; + } + + alternativa3d override function get needMultiplyBlend():Boolean { + return true; + } + + public var debugObject:Mesh = new Mesh(); + // TODO: repair +// private var debugMaterial:OmniShadowRendererDebugMaterial = new OmniShadowRendererDebugMaterial(); + private var debugMaterial:Object; + + public function setCaster(object:Object3D):void { + caster = object; + object.localToCameraTransform.identity(); + StaticShadowRenderer.calculateBoundBox(casterBounds, object); + + debugObject.geometry = debugGeometry; +// debugObject.addSurface(debugMaterial, 0, debugGeometry.numTriangles); + debugObject.addSurface(new FillMaterial(0xFFFFFF), 0, debugGeometry.numTriangles); + debugObject.scaleX = 400; + debugObject.scaleY = 400; + debugObject.scaleZ = 400; + } + + private var culledOmnies:Vector. = new Vector.(); + + private var influences:Vector. = new Vector.(); + private static const inverseMatrix:Transform3D = new Transform3D(); + +// private static const omniLocalCoords:Vector. = new Vector.(3); + + override public function update():void { + // Расчет матрицы объекта + caster.localToGlobalTransform.compose(caster._x, caster._y, caster._z, caster._rotationX, caster._rotationY, caster._rotationZ, caster._scaleX, caster._scaleY, caster._scaleZ); + var root:Object3D = caster; + while (root._parent != null) { + root = root._parent; + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); + caster.localToGlobalTransform.append(root.localToGlobalTransform); + } + // Расчет матрицы перевода в объект + caster.globalToLocalTransform.copy(caster.localToGlobalTransform); + caster.globalToLocalTransform.invert(); + +/** +// // Вычисление множителя масштаба +// caster.inverseCameraMatrix.transformVectors(sIn, sOut); +// var dx:Number = sOut[0] - sOut[3]; +// var dy:Number = sOut[1] - sOut[4]; +// var dz:Number = sOut[2] - sOut[5]; +// var scale:Number = Math.sqrt(dx*dx + dy*dy + dz*dz);*/ + +// var selectedOmni:OmniLight; +// var selectedOmniInfluence:Number = -1; + var influenceSum:Number = 0; + + var omni:OmniLight; + + culledOmnies.length = 0; + influences.length = 0; + // Куллинг источников света и нахождение основного + for each (omni in omnies) { + // Вычисление глобальной позиции омника + inverseMatrix.identity(); + var parent:Object3D = omni._parent; + if (parent != null) { + parent.localToGlobalTransform.compose(parent._x, parent._y, parent._z, parent._rotationX, parent._rotationY, parent._rotationZ, parent._scaleX, parent._scaleY, parent._scaleZ); + root = parent; + while (root._parent != null) { + if (root == caster || parent == caster) { + throw new Error("Caster can not be parent of light"); + } + root = root._parent; + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); + parent.localToGlobalTransform.append(root.localToGlobalTransform); + } + inverseMatrix.append(parent.localToGlobalTransform); + } + inverseMatrix.append(caster.globalToLocalTransform); + + var ox:Number = inverseMatrix.a*omni._x + inverseMatrix.b*omni._y + inverseMatrix.c*omni._z + inverseMatrix.d; + var oy:Number = inverseMatrix.e*omni._x + inverseMatrix.f*omni._y + inverseMatrix.g*omni._z + inverseMatrix.h; + var oz:Number = inverseMatrix.i*omni._x + inverseMatrix.j*omni._y + inverseMatrix.k*omni._z + inverseMatrix.l; + + // Использовать описывающий баунд-бокс объекта + // Куллинг + if (ox + omni.attenuationEnd > casterBounds.minX && ox - omni.attenuationEnd < casterBounds.maxX && oy + omni.attenuationEnd > casterBounds.minY && oy - omni.attenuationEnd < casterBounds.maxY && oz + omni.attenuationEnd > casterBounds.minZ && oz - omni.attenuationEnd < casterBounds.maxZ) { + // В зоне действия источника + // Считаем степень влияния + var d:Number = Math.sqrt(ox*ox + oy*oy + oz*oz)/omni.attenuationEnd - 0.1; + var influence:Number; + if (d > 1) { + influence = 0; + } else { + influence = omni.intensity*calcBrightness(omni.color) * (1 - d); + } +// if (influence > selectedOmniInfluence) { +// selectedOmni = omni; +// selectedOmniInfluence = influence; +// } + influenceSum += influence; + influences.push(influence); + culledOmnies.push(omni); + } + } + debugMaterial.texture = null; + + var i:int; + var surface:uint; + var drawed:int = 0; +/** if (selectedOmni == null || influenceSum <= 0) {*/ + if (culledOmnies.length == 0 || influenceSum <= 0) { + // Ни один источник не влияет + for (i = 0; i < 6; i++) { + surface = 1 << i; + if (clearBits & surface) { + context.setRenderToTexture(shadowMap, true, 0, i); +// context.clear(1); + context.clear(1, 1, 1, 1); +// trace("clear", i); + clearBits &= ~surface; + } + } +// trace("INVISIBLE"); + } else { + currentOmni._x = 0; + currentOmni._y = 0; + currentOmni._z = 0; + currentOmni.attenuationEnd = 0; + for (i = 0; i < culledOmnies.length; i++) { + var weight:Number = influences[i]/influenceSum; + omni = culledOmnies[i]; + // Считаем матрицу перевода в глобальное пространство из омника + omni.localToGlobalTransform.identity(); + omni.localToGlobalTransform.d = omni.x; + omni.localToGlobalTransform.h = omni.y; + omni.localToGlobalTransform.l = omni.z; + root = omni; + while (root._parent != null) { + root = root._parent; + if (root.transformChanged) root.composeTransforms(); + omni.localToGlobalTransform.append(root.transform); + } + currentOmni._x += omni.localToGlobalTransform.d*weight; + currentOmni._y += omni.localToGlobalTransform.h*weight; + currentOmni._z += omni.localToGlobalTransform.l*weight; + currentOmni.attenuationEnd += omni.attenuationEnd*weight; + } + currentOmni.localToGlobalTransform.identity(); + currentOmni.localToGlobalTransform.d = currentOmni._x; + currentOmni.localToGlobalTransform.h = currentOmni._y; + currentOmni.localToGlobalTransform.l = currentOmni._z; + +// constants[3] = 0.5*1/255; + constants[3] = 1.0/255; +/** // Расчитываем яркость тени +// var weight:Number = (selectedOmniInfluence > 0) ? 1 - (influenceSum - selectedOmniInfluence)/influenceSum : 0; +// trace(weight, influenceSum, selectedOmniInfluence); +// trace(weight); +// var weight:Number = 1; +// if (weight > 0) { +// constants[3] = (1 + (1 - weight)*5)/255; +// } else { +// constants[3] = 1/255; +// } +// // Считаем матрицу перевода в глобальное пространство из омника +// selectedOmni.cameraMatrix.identity(); +// selectedOmni.cameraMatrix.appendTranslation(selectedOmni.x, selectedOmni.y, selectedOmni.z); +//// selectedOmni.composeMatrix(); +// root = selectedOmni; +// while (root._parent != null) { +// root = root._parent; +// root.composeMatrix(); +// selectedOmni.cameraMatrix.append(root.cameraMatrix); +// } +//// // Матрица родителя уже посчитана +//// if (omni._parent != null) { +//// omni.cameraMatrix.append(omni._parent.cameraMatrix); +//// } +// selectedOmni.globalCoords[0] = 0; +// selectedOmni.globalCoords[1] = 0; +// selectedOmni.globalCoords[2] = 0; +// selectedOmni.cameraMatrix.transformVectors(selectedOmni.globalCoords, selectedOmni.globalCoords); */ + // Записываем параметры омника в константы + + debugObject.x = currentOmni._x; + debugObject.y = currentOmni._y; + debugObject.z = currentOmni._z; + + cleanContext(context); + for (i = 0; i < 6; i++) { + surface = 1 << i; + context.setRenderToTexture(shadowMap, true, 0, i); +// trace("SIDE:", i); + if (renderToOmniShadowMap(currentOmni, cameras[i])) { + drawed++; + clearBits |= surface; + } else { + if (clearBits & surface) { +// trace("clear", i); + context.clear(1, 1, 1, 1); +// context.clear(1, 1, 1, 1); + clearBits &= ~surface; + } + } + } +// trace("NUMSIDES:", drawed); + debugMaterial.texture = shadowMap; + } + context.setRenderToBackBuffer(); + cleanContext(context); + active = drawed > 0; + } + + private function calcBrightness(color:uint):Number { + var r:uint = color & 0xFF; + var g:uint = (color >> 8) & 0xFF; + var b:uint = (color >> 16) & 0xFF; + var result:uint = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); + return result/255; + } + +// private var axises:Vector. = Vector.([ +// 1, 0, 0, +// 0, 1, 0, +// ]); +// private var globalAxises:Vector. = new Vector.(6); + + public function renderToOmniShadowMap(omni:OmniLight, camera:Camera3D):Boolean { + camera.nearClipping = 1; + camera.farClipping = omni.attenuationEnd; + // Расчёт параметров проецирования + camera.calculateProjection(camera.view._width, camera.view._height); + + if (camera.transformChanged) camera.composeTransforms(); + // Считаем омник родительским объектом камеры + camera.localToGlobalTransform.combine(omni.localToGlobalTransform, camera.transform); + camera.globalToLocalTransform.copy(camera.localToGlobalTransform); + camera.globalToLocalTransform.invert(); + + caster.localToCameraTransform.compose(caster._x, caster._y, caster._z, caster._rotationX, caster._rotationY, caster._rotationZ, caster._scaleX, caster._scaleY, caster._scaleZ); + var root:Object3D = caster; + while (root._parent != null) { + root = root._parent; + if (root.transformChanged) root.composeTransforms(); + caster.localToCameraTransform.append(root.transform); + } + caster.localToCameraTransform.append(camera.globalToLocalTransform); + +/** if (pcfOffset > 0.1) { +// axises[0] = pcfOffset; +// // Считаем преобразования PCF +// camera.globalMatrix.transformVectors(axises, globalAxises); +// pcfOffsets[0] = -pcfOffset*globalAxises[0]; +// pcfOffsets[1] = -pcfOffset*globalAxises[1]; +// pcfOffsets[2] = -pcfOffset*globalAxises[2]; +// pcfOffsets[4] = pcfOffset*globalAxises[0]; +// pcfOffsets[5] = pcfOffset*globalAxises[1]; +// pcfOffsets[6] = pcfOffset*globalAxises[2]; +// pcfOffsets[8] = -pcfOffset*globalAxises[3]; +// pcfOffsets[9] = -pcfOffset*globalAxises[4]; +// pcfOffsets[10] = -pcfOffset*globalAxises[5]; +// pcfOffsets[12] = pcfOffset*globalAxises[3]; +// pcfOffsets[13] = pcfOffset*globalAxises[4]; +// pcfOffsets[14] = pcfOffset*globalAxises[5]; + } */ + + // Отрисовка в шедоумапу + if (cullingInCamera(caster, casterBounds)) { + context.clear(1, 1, 0, 1); + drawObjectToShadowMap(context, caster, camera); + return true; + } + return false; + } + + private static const points:Vector. = Vector.([ + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D() + ]); + private static const boundVertices:Vector. = new Vector.(24); + alternativa3d function cullingInCamera(object:Object3D, objectBounds:BoundBox):Boolean { + var i:int; + var infront:Boolean; + var behind:Boolean; + // Заполнение + var point:Vector3D; + var bb:BoundBox = objectBounds; + point = points[0]; + point.x = bb.minX; + point.y = bb.minY; + point.z = bb.minZ; + point = points[1]; + point.x = bb.minX; + point.y = bb.minY; + point.z = bb.maxZ; + point = points[2]; + point.x = bb.minX; + point.y = bb.maxY; + point.z = bb.minZ; + point = points[3]; + point.x = bb.minX; + point.y = bb.maxY; + point.z = bb.maxZ; + point = points[4]; + point.x = bb.maxX; + point.y = bb.minY; + point.z = bb.minZ; + point = points[5]; + point.x = bb.maxX; + point.y = bb.minY; + point.z = bb.maxZ; + point = points[6]; + point.x = bb.maxX; + point.y = bb.maxY; + point.z = bb.minZ; + point = points[7]; + point.x = bb.maxX; + point.y = bb.maxY; + point.z = bb.maxZ; + // Коррекция под 90 градусов + var transform:Transform3D = object.localToCameraTransform; + for (i = 0; i < 8; i++) { + point = points[i]; + var x:Number = transform.a*point.x + transform.b*point.y + transform.c*point.z + transform.d; + var y:Number = transform.e*point.x + transform.f*point.y + transform.g*point.z + transform.h; + var z:Number = transform.i*point.x + transform.j*point.y + transform.k*point.z + transform.l; + var index:int = 3*i; + boundVertices[int(index++)] = x; + boundVertices[int(index++)] = y; + boundVertices[index] = z; + } + + // Куллинг + 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) { +// trace("L", infront); + if (!infront) return false; + } + 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) { +// trace("R", infront); + if (!infront) return false; + } + 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) { +// trace("U", infront); + if (!infront) return false; + } + 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) { +// trace("D", infront); + if (!infront) return false; + } + return true; + } + + alternativa3d static function drawObjectToShadowMap(context:Context3D, object:Object3D, camera:Camera3D):void { + if (object is Mesh) { + drawMeshToShadowMap(context, Mesh(object), camera); + } + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.visible) { + if (child.transformChanged) child.composeTransforms(); + child.localToCameraTransform.combine(object.localToCameraTransform, child.transform); + drawObjectToShadowMap(context, child, camera); + } + } + } + + private static function copyRawFromTransform(raw:Vector., transform:Transform3D):void { + raw[0] = transform.a; + raw[1] = transform.b; + raw[2] = transform.c; + raw[3] = transform.d; + raw[4] = transform.e; + raw[5] = transform.f; + raw[6] = transform.g; + raw[7] = transform.h; + raw[8] = transform.i; + raw[9] = transform.j; + raw[10] = transform.k; + raw[11] = transform.l; + raw[12] = 0; + raw[13] = 0; + raw[14] = 0; + raw[15] = 1; + } + + private static var shadowMapProgram:Program3D; + private static var projectionVector:Vector. = new Vector.(16); + private static function drawMeshToShadowMap(context:Context3D, mesh:Mesh, camera:Camera3D):void { + if (mesh.geometry == null || mesh.geometry.numTriangles == 0 || !mesh.geometry.isUploaded) { + return; + } + + // TODO : update to new logic + if (shadowMapProgram == null) shadowMapProgram = initMeshToShadowMapProgram(context); + context.setProgram(shadowMapProgram); + + context.setVertexBufferAt(0, mesh.geometry.getVertexBuffer(VertexAttributes.POSITION), mesh.geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + + // TODO: uncomment +// camera.composeProjectionMatrix(projectionVector, 0, mesh.localToCameraTransform); + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, projectionVector, 4); + + copyRawFromTransform(projectionVector, mesh.localToCameraTransform); + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, projectionVector, 4); + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8, Vector.([Math.sqrt(255)/camera.farClipping, 0, 0, 1])); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([1/255, 0, 0, 1])); + + context.setCulling(Context3DTriangleFace.BACK); + for (var i:int = 0; i < mesh._surfacesLength; i++) { + var surface:Surface = mesh._surfaces[i]; + if (surface.material == null) continue; + context.drawTriangles(mesh.geometry._indexBuffer, surface.indexBegin, surface.numTriangles); + } + context.setVertexBufferAt(0, null); + } + + private static function initMeshToShadowMapProgram(context3d:Context3D):Program3D { + var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var proc:Procedure = Procedure.compileFromArray([ + "#a0=a0", + "#c8=c8", + "#v0=v0", + "m44 o0, a0, c0", + "m44 t0, a0, c4", + "mul t0, t0, c8.x", + "mov v0, t0" + ]); + proc.assignVariableName(VariableType.CONSTANT, 0, "c0", 4); + proc.assignVariableName(VariableType.CONSTANT, 4, "c4", 4); + vLinker.addProcedure(proc); + + fLinker.addProcedure(Procedure.compileFromArray([ + "#v0=v0", + "#c0=c0", + "mov t0.zw, c0.z \n", + "dp3 t1.w, v0.xyz, v0.xyz \n", + "frc t0.y, t1.w", + "sub t0.x, t1.w, t0.y", + "mul t0.x, t0.x, c0.x", + "mov o0, to" + ])); + var program:Program3D = context3d.createProgram(); +// trace("VERTEX"); +// trace(A3DUtils.disassemble(vLinker.getByteCode())); +// trace("FRAGMENT"); +// trace(A3DUtils.disassemble(fLinker.getByteCode())); + fLinker.varyings = vLinker.varyings; + vLinker.link(); + fLinker.link(); + program.upload(vLinker.data, fLinker.data); + return program; + } + + private static function initVShader():Procedure { + var shader:Procedure = Procedure.compileFromArray([ + // Координата вершины в глобальном пространстве + "m44 v0, a0, c0" + ]); + shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPosition"); + shader.assignVariableName(VariableType.CONSTANT, 0, "cGLOBALMATRIX", 4); + shader.assignVariableName(VariableType.VARYING, 0, "vPOSITION"); + return shader; + } + + private static function initFShader(mult:Boolean, usePCF:Boolean):Procedure { + var line:int = 0; + var shaderArr:Array = []; + var numPass:uint = (usePCF) ? 8 : 1; + for (var i:int = 0; i < numPass; i++) { + // Вектор от источника света к точке + shaderArr[line++] = "sub t0.xyz, v0.xyz, c4.xyz"; + + // Квадрат расстояния + shaderArr[line++] = "dp3 t0.w, t0.xyz, t0.xyz"; + shaderArr[line++] = "mul t0.w, t0.w, c4.w"; // * (255 / radius^2) + shaderArr[line++] = "mul t0.w, t0.w, c5.y"; // bias [0.95] + + // Квадрат расстояния из карты теней + shaderArr[line++] = "nrm t0.xyz, t0.xyz"; + + if (usePCF) { + shaderArr[line++] = "add t0.xyz, t0.xyz, c" + (i + 6).toString(); + } + + shaderArr[line++] = "tex t1, t0, s0 "; + shaderArr[line++] = "mov t3, t1"; + shaderArr[line++] = "mul t1.w, t1.x, c5.x"; // 255 + shaderArr[line++] = "add t1.w, t1.w, t1.y"; + + // Перекрытие тенью + shaderArr[line++] = "sub t2.z, t1.w, t0.w"; + shaderArr[line++] = "mul t2.z, t2.z, c5.z"; // smooth [10000] + shaderArr[line++] = "sat t2.z, t2.z"; + +// // Затухание тени по расстоянию + shaderArr[line++] = "mul t1.x, t0.w, c5.w"; // div 255 + shaderArr[line++] = "add t2.z, t2.z, t1.x"; + if (i == 0) { + shaderArr[line++] = "sat t2.x, t2.z"; + } else { + shaderArr[line++] = "sat t2.z, t2.z"; + shaderArr[line++] = "add t2.x, t2.x, t2.z"; + } + } + if (usePCF) { + shaderArr[line++] = "mul t2.x, t2.x, c6.w"; + } + if (mult) { + shaderArr.push("mul t0.xyz, i0.xyz, t2.x"); +// shaderArr.push("mul t0.xyz, t1.w, c5.w"); + shaderArr.push("mov t0.w, i0.w"); + shaderArr.push("mov o0, t0"); + } else { + shaderArr.push("mov o0, t2.xxxx"); + } + var shader:Procedure = Procedure.compileFromArray(shaderArr, "OmniShadowMap"); + shader.assignVariableName(VariableType.VARYING, 0, "vPOSITION"); + shader.assignVariableName(VariableType.CONSTANT, 4, "cOmni", 1); + shader.assignVariableName(VariableType.CONSTANT, 5, "cConstants", 1); + if (usePCF) { + shader.assignVariableName(VariableType.CONSTANT, 6, "cPCF0", 1); + shader.assignVariableName(VariableType.CONSTANT, 7, "cPCF1", 1); + shader.assignVariableName(VariableType.CONSTANT, 8, "cPCF2", 1); + shader.assignVariableName(VariableType.CONSTANT, 9, "cPCF3", 1); + shader.assignVariableName(VariableType.CONSTANT, 10, "cPCF4", 1); + shader.assignVariableName(VariableType.CONSTANT, 11, "cPCF5", 1); + shader.assignVariableName(VariableType.CONSTANT, 12, "cPCF6", 1); + shader.assignVariableName(VariableType.CONSTANT, 13, "cPCF7", 1); + } + shader.assignVariableName(VariableType.SAMPLER, 0, "sCUBE"); + return shader; + } + + override public function getVShader(index:int = 0):Procedure { + return initVShader(); + } + + override public function getFShader(index:int = 0):Procedure { + return initFShader(false, pcfOffset > 0); + } + + private static const globalMatrix:Transform3D = new Transform3D(); + override public function applyShader(drawUnit:DrawUnit, program:ShaderProgram, object:Object3D, camera:Camera3D, index:int = 0):void { + var fLinker:Linker = program.fragmentShader; + + globalMatrix.combine(camera.localToGlobalTransform, object.localToCameraTransform); + + var mIndex:int = program.vertexShader.getVariableIndex("cGLOBALMATRIX"); + drawUnit.setVertexConstantsFromNumbers(mIndex, globalMatrix.a, globalMatrix.b, globalMatrix.c, globalMatrix.d); + drawUnit.setVertexConstantsFromNumbers(mIndex+1, globalMatrix.e, globalMatrix.f, globalMatrix.g, globalMatrix.h); + drawUnit.setVertexConstantsFromNumbers(mIndex+2, globalMatrix.i, globalMatrix.j, globalMatrix.k, globalMatrix.l); + drawUnit.setVertexConstantsFromNumbers(mIndex+3, 0, 0, 0, 1); + +// destination.addFragmentConstantSet(fLinker.getVariableIndex("cOmni"), omniPos, 1); + drawUnit.setFragmentConstantsFromNumbers(fLinker.getVariableIndex("cOmni"), currentOmni._x, currentOmni._y, currentOmni._z, 255/currentOmni.attenuationEnd/currentOmni.attenuationEnd); + drawUnit.setFragmentConstantsFromVector(fLinker.getVariableIndex("cConstants"), constants, 1); + if (pcfOffset > 0) { + drawUnit.setVertexConstantsFromVector(fLinker.getVariableIndex("cPCF0"), pcfOffsets, 8); + } + drawUnit.setTextureAt(fLinker.getVariableIndex("sCUBE"), shadowMap); + } + +// private static var program:LinkedProgram; +// private static var programPCF:LinkedProgram; +// private static function initMeshProgram(context:Context3D, usePCF:Boolean):LinkedProgram { +// var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); +// vLinker.addShader(vShader); +// +// var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); +// if (usePCF) { +// fLinker.addShader(pcfFShader); +// } else { +// fLinker.addShader(fShader); +// } +// +// vLinker.setOppositeLinker(fLinker); +// fLinker.setOppositeLinker(vLinker); +// +// trace("[VERTEX]"); +// trace(AgalUtils.disassemble(vLinker.getByteCode())); +// trace("[FRAGMENT]"); +// trace(AgalUtils.disassemble(fLinker.getByteCode())); +// +// var result:LinkedProgram; +// if (usePCF) { +// programPCF = new LinkedProgram(); +// result = programPCF; +// } else { +// program = new LinkedProgram(); +// result = program; +// } +// result.vLinker = vLinker; +// result.fLinker = fLinker; +// result.program = context.createProgram(); +// result.program.upload(vLinker.getByteCode(), fLinker.getByteCode()); +// +// return result; +// } +// +// override public function drawShadow(mesh:Mesh, camera:Camera3D, texture:Texture):void { +// var context3d:Context3D = camera.view._context3d; +// +// var linkedProgram:LinkedProgram; +// if (pcfOffset > 0) { +// linkedProgram = (programPCF == null) ? initMeshProgram(context3d, true) : programPCF; +// } else { +// linkedProgram = (program == null) ? initMeshProgram(context3d, false) : program; +// } +// var vLinker:Linker = linkedProgram.vLinker; +// var fLinker:Linker = linkedProgram.fLinker; +// context3d.setProgram(linkedProgram.program); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), mesh.geometry.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); +// context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vLinker.getVariableIndex("cPROJ"), mesh.projectionMatrix, true); +// applyShader(context3d, linkedProgram, mesh, camera); +// context3d.setVertexBufferAt(1, null); +// +// context3d.setCulling(Context3DTriangleFace.FRONT); +// context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), null); +// context.setTextureAt(getTextureIndex(fLinker), texture); +// } + + } +} diff --git a/src/alternativa/engine3d/shadows/OmniShadowRendererDebugMaterial.as b/src/alternativa/engine3d/shadows/OmniShadowRendererDebugMaterial.as new file mode 100644 index 0000000..6dfd11a --- /dev/null +++ b/src/alternativa/engine3d/shadows/OmniShadowRendererDebugMaterial.as @@ -0,0 +1,162 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.*; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.resources.Geometry; + + import flash.display3D.Context3DProgramType; + import flash.display3D.textures.CubeTexture; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class OmniShadowRendererDebugMaterial extends Material { + + alternativa3d override function get canDrawInShadowMap():Boolean { + return false; + } + + static alternativa3d const _samplerSetProcedure:Procedure = new Procedure( + [ + "#v0=vUV", + "#s0=sTexture", + "#c0=cAlpha", + "tex t0, v0, s0 ", + "mov t0.w, c0.w", + "mov o0, t0" + ]); + + static alternativa3d const _samplerSetProcedureDiffuseAlpha:Procedure = new Procedure( + [ + "#v0=vUV", + "#s0=sTexture", + "#c0=cAlpha", + "tex t0, v0, s0 ", + "mul t0.w, t0.w, c0.w", + "mov o0, t0" + ]); + + static alternativa3d const _passUVProcedure:Procedure = new Procedure(["#v0=vUV", "#a0=aNORMAL", "mov v0, a0"]); + private static var _programs:Dictionary = new Dictionary(); + /** + * Текстура + */ + public var texture:CubeTexture; + /** + * Прозрачность + */ + public var alpha:Number = 1; + /** + * Использование alpha канала текстуры + */ + public var useDiffuseAlphaChannel:Boolean = false; + + /** + * Создает экземпляр материала + * @param texture текстура + * @param alpha прозрачность + */ + public function OmniShadowRendererDebugMaterial(texture:CubeTexture = null, alpha:Number = 1) { + this.texture = texture; + this.alpha = alpha; + } + + private function setupProgram(targetObject:Object3D):Vector. { + var optionsPrograms:Vector. = new Vector.(); + + var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var positionVar:String = "aPosition"; + vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE); + if (targetObject.transformProcedure != null) { + positionVar = appendPositionTransformProcedure(targetObject.transformProcedure, vertexLinker); + } + vertexLinker.addProcedure(_projectProcedure); + vertexLinker.setInputParams(_projectProcedure, positionVar); + vertexLinker.addProcedure(_passUVProcedure); + vertexLinker.link(); + + var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + fragmentLinker.addProcedure(_samplerSetProcedure); + fragmentLinker.varyings = vertexLinker.varyings; + optionsPrograms[optionsPrograms.length] = new ShaderProgram(vertexLinker, fragmentLinker); + + var fragmentLinkerDiffuseAlpha:Linker = new Linker(Context3DProgramType.FRAGMENT); + fragmentLinkerDiffuseAlpha.addProcedure(_samplerSetProcedureDiffuseAlpha); + fragmentLinkerDiffuseAlpha.varyings = vertexLinker.varyings; + optionsPrograms[optionsPrograms.length] = new ShaderProgram(vertexLinker, fragmentLinkerDiffuseAlpha); + +// trace(A3DUtils.disassemble(fragmentLinker.getByteCode())); + + _programs[targetObject.transformProcedure] = optionsPrograms; + return optionsPrograms; + } + + /** + * @private + */ + override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void { + // TODO: repair + /* + destination.isValid = destination.isValid && texture != null; + + var optionsPrograms:Vector. = _programs[transformHolder.transformProcedure]; + if(!optionsPrograms) optionsPrograms = setupProgram(transformHolder); + var program:ShaderProgram; + if(!useDiffuseAlphaChannel){ + program = optionsPrograms[0]; + }else { + program = optionsPrograms[1]; + } + + if (!destination.isValid) { + return; + } + + if (alpha < 1 || useDiffuseAlphaChannel) { + destination.blendModeSource = Context3DBlendFactor.SOURCE_ALPHA; + destination.blendModeDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + destination.renderPriority = Renderer.TRANSPARENT_SORT; + } else { + destination.blendModeSource = Context3DBlendFactor.ONE; + destination.blendModeDestination = Context3DBlendFactor.ZERO; + destination.renderPriority = Renderer.OPAQUE; + } + + destination.program = program; + geometry.setAttribute(destination, VertexAttributes.POSITION, program.vertexShader.getVariableIndex("aPosition")); + geometry.setAttribute(destination, VertexAttributes.NORMAL, program.vertexShader.getVariableIndex("aNORMAL")); + camera.composeProjectionMatrix(destination.constantSetVertexVectorValues, program.vertexShader.getVariableIndex("cProjMatrix") << 2, transformHolder.localToCameraTransform); + destination.constantSetVertexRegistersCount = destination.constantSetVertexVectorValues.length >> 2; + destination.addTextureSet(program.fragmentShader.getVariableIndex("sTexture"), texture); + destination.addFragmentConstantVector(program.fragmentShader.getVariableIndex("cAlpha"), 0, 0, 0, alpha); + destination.cullingMode = cullingMode; + */ + } + + /** + * @inheritDoc + */ + override public function clone():Material { + var res:OmniShadowRendererDebugMaterial = new OmniShadowRendererDebugMaterial(texture, alpha); + res.clonePropertiesFrom(this); + return res; + } + + override protected function clonePropertiesFrom(source:Material):void { + super.clonePropertiesFrom(source); + var t:OmniShadowRendererDebugMaterial = OmniShadowRendererDebugMaterial(source); + texture = t.texture; + alpha = t.alpha; + } + } +} diff --git a/src/alternativa/engine3d/shadows/Shadow.as b/src/alternativa/engine3d/shadows/Shadow.as new file mode 100644 index 0000000..080e510 --- /dev/null +++ b/src/alternativa/engine3d/shadows/Shadow.as @@ -0,0 +1,64 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Light3D; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.objects.Surface; + + use namespace alternativa3d; + + /** + * Base class for shadows. + */ + public class Shadow { + + /** + * @private + * Key for processing in materials. + */ + alternativa3d var type:String = "s"; + + /** + * @private + */ + alternativa3d var _light:Light3D; + + /** + * @private + * inputs: position + */ + alternativa3d var vertexShadowProcedure:Procedure; + + /** + * @private + * outputs: shadow intensity + */ + alternativa3d var fragmentShadowProcedure:Procedure; + + /** + * @private + */ + alternativa3d function process(camera:Camera3D):void { + } + + /** + * @private + */ + alternativa3d function setup(drawUnit:DrawUnit, vertexLinker:Linker, fragmentLinker:Linker, surface:Surface):void { + } + + } +} diff --git a/src/alternativa/engine3d/shadows/ShadowRenderer.as b/src/alternativa/engine3d/shadows/ShadowRenderer.as new file mode 100644 index 0000000..5c7b712 --- /dev/null +++ b/src/alternativa/engine3d/shadows/ShadowRenderer.as @@ -0,0 +1,184 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.compiler.Procedure; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DCompareMode; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class ShadowRenderer { + + alternativa3d var shaderKey:String; + private static var counter:int = 0; + + public var active:Boolean = false; + + public function ShadowRenderer() { + counter++; + shaderKey = "M" + counter.toString(); + } + + alternativa3d function get needMultiplyBlend():Boolean { + return false; + } + + public function update():void {} + + // Нет входных, выходных параметров + public function getVShader(index:int = 0):Procedure {return null} + public function getFShader(index:int = 0):Procedure {return null} + + public function getFIntensityShader():Procedure { throw new Error("Not implemented") }; + +// public function getMultVShader():Procedure {return null}; +// // i0 - input color +// // o0 - shadowed result +// public function getMultFShader():Procedure {return null}; + + public function applyShader(destination:DrawUnit, program:ShaderProgram, object:Object3D, camera:Camera3D, index:int = 0):void {} +// public function drawShadow(mesh:Mesh, camera:Camera3D, texture:Texture):void {} + +// public function getTextureIndex(fLinker:Linker):int {return 0}; + + public function get debug():Boolean { return false } + public function set debug(value:Boolean):void { } + + alternativa3d function cullReciever(boundBox:BoundBox, object:Object3D):Boolean { + return false; + } + + protected function cleanContext(context:Context3D):void { + context.setTextureAt(0, null); + context.setTextureAt(1, null); + context.setTextureAt(2, null); + context.setTextureAt(3, null); + context.setTextureAt(4, null); + context.setTextureAt(5, null); + context.setTextureAt(6, null); + context.setTextureAt(7, null); + context.setVertexBufferAt(1, null); + context.setVertexBufferAt(2, null); + context.setVertexBufferAt(3, null); + context.setVertexBufferAt(4, null); + context.setVertexBufferAt(5, null); + context.setVertexBufferAt(6, null); + context.setVertexBufferAt(7, null); + context.setDepthTest(true, Context3DCompareMode.LESS); + context.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO); + } + + static private const boundVertices:Vector. = new Vector.(24); + alternativa3d function cullObjectImplementation(bounds:BoundBox, matrix:Matrix3D):Boolean { + var i:int; + var infront:Boolean; + var behind:Boolean; + // Заполнение + boundVertices[0] = bounds.minX; + boundVertices[1] = bounds.minY; + boundVertices[2] = bounds.minZ; + boundVertices[3] = bounds.maxX; + boundVertices[4] = bounds.minY; + boundVertices[5] = bounds.minZ; + boundVertices[6] = bounds.minX; + boundVertices[7] = bounds.maxY; + boundVertices[8] = bounds.minZ; + boundVertices[9] = bounds.maxX; + boundVertices[10] = bounds.maxY; + boundVertices[11] = bounds.minZ; + boundVertices[12] = bounds.minX; + boundVertices[13] = bounds.minY; + boundVertices[14] = bounds.maxZ; + boundVertices[15] = bounds.maxX; + boundVertices[16] = bounds.minY; + boundVertices[17] = bounds.maxZ; + boundVertices[18] = bounds.minX; + boundVertices[19] = bounds.maxY; + boundVertices[20] = bounds.maxZ; + boundVertices[21] = bounds.maxX; + boundVertices[22] = bounds.maxY; + boundVertices[23] = bounds.maxZ; + + // Трансформация в камеру + matrix.transformVectors(boundVertices, boundVertices); + // Куллинг + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return false; + } + // left + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return false; + } + // right + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (boundVertices[i] < 1) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return false; + } + // up + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return false; + } + // down + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (boundVertices[i] < 1) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return false; + } + return true; + } + + } +} diff --git a/src/alternativa/engine3d/shadows/ShadowsSystem.as b/src/alternativa/engine3d/shadows/ShadowsSystem.as new file mode 100644 index 0000000..0481d04 --- /dev/null +++ b/src/alternativa/engine3d/shadows/ShadowsSystem.as @@ -0,0 +1,96 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class ShadowsSystem { + + private static const MAX_SHADOWMAPS:int = 3; +// private static const MAX_SHADOWMAPS:int = 4; + + public var renderers:Vector. = new Vector.(); + + private var containers:Dictionary = new Dictionary(); + + public function ShadowsSystem() { + } + + private var numShadowed:int; + + private var numActiveRenderers:int; + private var activeRenderers:Vector. = new Vector.(); + + private var maxShadows:int; + + public function update(root:Object3D):void { + if (renderers.length == 0) return; + numActiveRenderers = 0; + var num:int = renderers.length; + for (var i:int = 0; i < num; i++) { + var renderer:ShadowRenderer = renderers[i]; + renderer.update(); + if (renderer.active) { + activeRenderers[numActiveRenderers] = renderer; + numActiveRenderers++; + } + } + // Пробегаемся иерархически по объектам и проверяем наложение на них тени + if (root.transformChanged) root.composeTransforms(); + root.localToGlobalTransform.copy(root.transform); + numShadowed = 0; + maxShadows = 0; + recursive(root); +// trace("SHADOWED:", numShadowed, ":", maxShadows); + } + + private function recursive(object:Object3D):void { + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + var value:Vector. = null; + var numRenderers:int = 0; + if (child.visible) { + if (child.transformChanged) child.composeTransforms(); + child.localToGlobalTransform.combine(object.localToGlobalTransform, child.transform); + for (var i:int = 0; i < numActiveRenderers; i++) { + var renderer:ShadowRenderer = activeRenderers[i]; + if (child.useShadow) { + if (child.boundBox == null || renderer.cullReciever(child.boundBox, child)) { + numShadowed++; + if (value == null) { + value = containers[child]; + if (value == null) { + value = new Vector.(); + containers[child] = value; + } else { + value.length = 0; + } + } + value[numRenderers] = renderer; + numRenderers++; + } + } + } + recursive(child); + } + setRenderers(child, value, numRenderers); + } + } + + private function setRenderers(object:Object3D, renderers:Vector., numShadowRenderers:int):void { + if (numShadowRenderers > maxShadows) maxShadows = numShadowRenderers; + if (numShadowRenderers > MAX_SHADOWMAPS) { + numShadowRenderers = MAX_SHADOWMAPS; + renderers.length = MAX_SHADOWMAPS; + } + object.shadowRenderers = renderers; + object.numShadowRenderers = numShadowRenderers; + } + + } +} diff --git a/src/alternativa/engine3d/shadows/SpotShadowRenderer.as b/src/alternativa/engine3d/shadows/SpotShadowRenderer.as new file mode 100644 index 0000000..965e5a1 --- /dev/null +++ b/src/alternativa/engine3d/shadows/SpotShadowRenderer.as @@ -0,0 +1,656 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.core.VertexAttributes; + import alternativa.engine3d.lights.SpotLight; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.compiler.Linker; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Surface; + import alternativa.engine3d.primitives.Box; + import alternativa.engine3d.resources.TextureResource; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.Program3D; + import flash.display3D.textures.Texture; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class SpotShadowRenderer extends ShadowRenderer { + + public var caster:Object3D; + + private var context:Context3D; + + private var shadowMap:Texture; + + private var light:SpotLight; + + private var debugObject:Mesh; + public var debugMaterial:TextureMaterial = new TextureMaterial(); + private var debugTexture:TextureResource = new TextureResource(); + + private var globalToShadowMap:Matrix3D = new Matrix3D(); + + private static const constants:Vector. = Vector.([ + 255, 255*0.96, 100, 1 + ]); + + private var pcfSize:Number = 0; + private var pcfOffset:Number = 0; + private var pcfOffsets:Vector.; + + public function SpotShadowRenderer(context:Context3D, size:int, pcfSize:Number = 0) { + this.context = context; + this.shadowMap = context.createTexture(size, size, Context3DTextureFormat.BGRA, true); + this.pcfSize = pcfSize; + debugTexture._texture = this.shadowMap; +// debugMaterial.diffuseMap = debugTexture; + debugMaterial.alpha = 0.9; + debugMaterial.opaquePass = false; + debugMaterial.transparentPass = true; + debugMaterial.alphaThreshold = 1.1; + } + + public function setLight(value:SpotLight):void { + light = value; + var width:Number = 2*Math.sin(light.falloff*0.5)*light.attenuationEnd; + this.pcfOffset = pcfSize/width/255; + if (pcfOffset > 0) { + pcfOffsets = Vector.([ + -pcfOffset, -pcfOffset, 0, 1/4, + -pcfOffset, pcfOffset, 0, 1, + pcfOffset, -pcfOffset, 0, 1, + pcfOffset, pcfOffset, 0, 1 + ]); + } + debugObject = new Box(width, width, 1, 1, 1, 1, false, debugMaterial); + debugObject.rotationX = Math.PI; + debugObject.geometry.upload(context); + if (_debug) { + light.addChild(debugObject); + } + } + + private var _debug:Boolean = false; + override public function get debug():Boolean { + return _debug; + } + + override public function set debug(value:Boolean):void { + _debug = value; + if (_debug) { + if (light != null) { + light.addChild(debugObject); + } + } else { + if (debugObject != null && debugObject._parent != null) { + debugObject._parent.removeChild(debugObject); + } + } + } + + private static var matrix:Matrix3D = new Matrix3D(); + override alternativa3d function cullReciever(boundBox:BoundBox, object:Object3D):Boolean { + copyMatrixFromTransform(matrix, object.localToGlobalTransform); + matrix.append(this.globalToShadowMap); + return cullObjectImpl(boundBox, matrix); + } + private function cullCaster(boundBox:BoundBox, objectToGlobal:Transform3D):Boolean { + copyMatrixFromTransform(matrix, objectToGlobal); + matrix.append(this.globalToShadowMap); + return cullObjectImpl(boundBox, matrix, light.attenuationEnd); + } + + private var projection:ProjectionTransform3D = new ProjectionTransform3D(); + private var uvProjection:Matrix3D = new Matrix3D(); + override public function update():void { + // Считаем матрицу перевода в лайт + var root:Object3D; + // Расчитываем матрицу объекта для перевода в глобал + // if (caster.transformChanged) { + caster.localToGlobalTransform.compose(caster._x, caster._y, caster._z, caster._rotationX, caster._rotationY, caster._rotationZ, caster._scaleX, caster._scaleY, caster._scaleZ); + // } else { + // caster.localToCameraTransform.copy(caster.transform); + // } + root = caster; + while (root._parent != null) { + root = root._parent; + // if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); + // } + caster.localToGlobalTransform.append(root.localToGlobalTransform); + } + + // Расчитываем матрицу лайта + light.localToGlobalTransform.compose(light._x, light._y, light._z, light._rotationX, light._rotationY, light._rotationZ, light._scaleX, light._scaleY, light._scaleZ); + root = light; + while (root._parent != null) { + root = root._parent; + // if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); + // } + light.localToGlobalTransform.append(root.localToGlobalTransform); + } + light.globalToLocalTransform.copy(light.localToGlobalTransform); + light.globalToLocalTransform.invert(); + + // Получаем матрицу перевода из объекта в лайт + caster.localToCameraTransform.combine(light.globalToLocalTransform, caster.localToGlobalTransform); +// caster.localToCameraTransform.append(light.globalToLocalTransform); + + // Считаем матрицу проецирования + calculateProjection(projection, uvProjection, light.falloff, 1, light.attenuationEnd); +// globalToShadowMap.copy(light.globalToLocalTransform); + copyMatrixFromTransform(globalToShadowMap, light.globalToLocalTransform); + globalToShadowMap.append(uvProjection); + + debugMaterial.diffuseMap = null; + +// trace("TEST:", testCasterCulling(caster)); + if (!testCasterCulling(caster)) { + active = false; + return; + } + active = true; + + // Рисуем в шедоумапу + context.setRenderToTexture(shadowMap, true, 0, 0); + // context.clear(1); + context.clear(1, 1, 1, 1); + cleanContext(context); + drawObjectToShadowMap(context, caster, projection); + context.setRenderToBackBuffer(); + cleanContext(context); + debugMaterial.diffuseMap = debugTexture; + } + + private function testCasterCulling(object:Object3D):Boolean { + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.visible) { + if (child.transformChanged) child.composeTransforms(); + child.localToGlobalTransform.combine(object.localToGlobalTransform, child.transform); + if (child.boundBox == null || cullCaster(child.boundBox, child.localToGlobalTransform)) { + return true; + } + if (testCasterCulling(child)) { + return true; + } + } + } + return false; + } + + static private const boundVertices:Vector. = new Vector.(24); + alternativa3d function cullObjectImpl(bounds:BoundBox, matrix:Matrix3D, far:Number = 0):Boolean { + var i:int; + var infront:Boolean; + var behind:Boolean; + // Заполнение + boundVertices[0] = bounds.minX; + boundVertices[1] = bounds.minY; + boundVertices[2] = bounds.minZ; + boundVertices[3] = bounds.maxX; + boundVertices[4] = bounds.minY; + boundVertices[5] = bounds.minZ; + boundVertices[6] = bounds.minX; + boundVertices[7] = bounds.maxY; + boundVertices[8] = bounds.minZ; + boundVertices[9] = bounds.maxX; + boundVertices[10] = bounds.maxY; + boundVertices[11] = bounds.minZ; + boundVertices[12] = bounds.minX; + boundVertices[13] = bounds.minY; + boundVertices[14] = bounds.maxZ; + boundVertices[15] = bounds.maxX; + boundVertices[16] = bounds.minY; + boundVertices[17] = bounds.maxZ; + boundVertices[18] = bounds.minX; + boundVertices[19] = bounds.maxY; + boundVertices[20] = bounds.maxZ; + boundVertices[21] = bounds.maxX; + boundVertices[22] = bounds.maxY; + boundVertices[23] = bounds.maxZ; + + // Трансформация в камеру + matrix.transformVectors(boundVertices, boundVertices); + // Куллинг + // left + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { +// trace("POS", boundVertices[i], boundVertices[int(i + 2)]); + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { +// trace("L", infront); + if (!infront) return false; + } + // right + 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) { +// trace("R", infront); + if (!infront) return false; + } + // up + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { +// if (-boundVertices[i] < boundVertices[int(i + 1)]) { + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { +// trace("U", infront); + if (!infront) return false; + } + // down + 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) { +// trace("D", infront); + if (!infront) return false; + } + if (far > 0) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundVertices[i] < far) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + // trace("N", infront); + if (!infront) return false; + } + } + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundVertices[i] > 0) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + // trace("N", infront); + if (!infront) return false; + } + return true; + } + + // должен быть заполнен нулями + private var rawData:Vector. = new Vector.(16); + private var m:Matrix3D = new Matrix3D(); + private function calculateProjection(projection:ProjectionTransform3D, uvProjection:Matrix3D, fov:Number, nearClipping:Number, farClipping:Number):void { + var viewSize:Number = 1; + var focalLength:Number = viewSize/Math.tan(fov*0.5); + projection.m0 = focalLength/viewSize; + projection.m5 = -focalLength/viewSize; + projection.m10 = farClipping/(farClipping - nearClipping); + projection.m14 = -nearClipping*projection.m10; + + for (var i:int = 0; i < 16; i++) { + rawData[i] = 0; + } + + // TODO: предумножить матрицы + + rawData[0] = projection.m0; + rawData[5] = -projection.m5; + rawData[10]= projection.m10; + rawData[11]= 1; + rawData[14]= projection.m14; + uvProjection.rawData = rawData; + +// 0.5f, 0.0f, 0.0f, 0.0f, +// 0.0f, 0.5f, 0.0f, 0.0f, +// 0.0f, 0.0f, 0.5f, 0.0f, +// 0.5f, 0.5f, 0.5f, 1.0f + + rawData[0] = 0.5; + rawData[12] = 0.5; + rawData[5] = 0.5; + rawData[13] = 0.5; + rawData[10] = 0.5; + rawData[14] = 0.5; + rawData[11] = 0; + rawData[15] = 1; + m.rawData = rawData; + uvProjection.append(m); + } + + private var transformToMatrixRawData:Vector. = new Vector.(16); + private function copyMatrixFromTransform(matrix:Matrix3D, transform:Transform3D):void { + transformToMatrixRawData[0] = transform.a; + transformToMatrixRawData[1] = transform.e; + transformToMatrixRawData[2] = transform.i; + transformToMatrixRawData[3] = 0; + transformToMatrixRawData[4] = transform.b; + transformToMatrixRawData[5] = transform.f; + transformToMatrixRawData[6] = transform.j; + transformToMatrixRawData[7] = 0; + transformToMatrixRawData[8] = transform.c; + transformToMatrixRawData[9] = transform.g; + transformToMatrixRawData[10] = transform.k; + transformToMatrixRawData[11] = 0; + transformToMatrixRawData[12] = transform.d; + transformToMatrixRawData[13] = transform.h; + transformToMatrixRawData[14] = transform.l; + transformToMatrixRawData[15] = 1; + matrix.rawData = transformToMatrixRawData; + } + + alternativa3d static function drawObjectToShadowMap(context:Context3D, object:Object3D, projection:ProjectionTransform3D):void { + if (object is Mesh) { + drawMeshToShadowMap(context, Mesh(object), projection); + } + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.visible) { + if (child.transformChanged) child.composeTransforms(); + child.localToCameraTransform.combine(object.localToCameraTransform, child.transform); + drawObjectToShadowMap(context, child, projection); + } + } + } + + private static var shadowMapProgram:Program3D; + private static var projectionVector:Vector. = new Vector.(16); + private static function drawMeshToShadowMap(context:Context3D, mesh:Mesh, projection:ProjectionTransform3D):void { + if (mesh.geometry == null || mesh.geometry.numTriangles == 0 || !mesh.geometry.isUploaded) { + return; + } + + if (shadowMapProgram == null) shadowMapProgram = initMeshToShadowMapProgram(context); + context.setProgram(shadowMapProgram); + + context.setVertexBufferAt(0, mesh.geometry.getVertexBuffer(VertexAttributes.POSITION), mesh.geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]); + + var transform:Transform3D = mesh.localToCameraTransform; + projectionVector[0] = transform.a*projection.m0; + projectionVector[1] = transform.b*projection.m0; + projectionVector[2] = transform.c*projection.m0; + projectionVector[3] = transform.d*projection.m0; + projectionVector[4] = transform.e*projection.m5; + projectionVector[5] = transform.f*projection.m5; + projectionVector[6] = transform.g*projection.m5; + projectionVector[7] = transform.h*projection.m5; + projectionVector[8] = transform.i*projection.m10; + projectionVector[9] = transform.j*projection.m10; + projectionVector[10] = transform.k*projection.m10; + projectionVector[11] = transform.l*projection.m10 + projection.m14; + projectionVector[12] = transform.i; + projectionVector[13] = transform.j; + projectionVector[14] = transform.k; + projectionVector[15] = transform.l; + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 0, projectionVector, 4); + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, Vector.([255, 0, 0, 1])); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.([1/255, 0, 0, 1])); + + context.setCulling(Context3DTriangleFace.BACK); + for (var i:int = 0; i < mesh._surfacesLength; i++) { + var surface:Surface = mesh._surfaces[i]; + if (surface.material == null) continue; + context.drawTriangles(mesh.geometry._indexBuffer, surface.indexBegin, surface.numTriangles); + } + context.setVertexBufferAt(0, null); + } + + private static function initMeshToShadowMapProgram(context3d:Context3D):Program3D { + var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); + var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); + var proc:Procedure = Procedure.compileFromArray([ + "#a0=a0", + "#c4=c4", + "#v0=v0", + "m44 t0, a0, c0", + "mul v0, t0, c4.x", + "mov o0, t0" + ]); + proc.assignVariableName(VariableType.CONSTANT, 0, "c0", 4); + vLinker.addProcedure(proc); + + fLinker.addProcedure(Procedure.compileFromArray([ + "#v0=v0", + "#c0=c0", + "mov t0.xy, v0.zz", + "frc t0.y, v0.z", + "sub t0.x, v0.z, t0.y", + "mul t0.x, t0.x, c0.x", + "mov t0.z, c0.z", + "mov t0.w, c0.w", + "mov o0, t0" + ])); + var program:Program3D = context3d.createProgram(); +// trace("VERTEX"); +// trace(A3DUtils.disassemble(vLinker.getByteCode())); +// trace("FRAGMENT"); +// trace(A3DUtils.disassemble(fLinker.getByteCode())); + fLinker.varyings = vLinker.varyings; + vLinker.link(); + fLinker.link(); + program.upload(vLinker.data, fLinker.data); + return program; + } + + // Rendering with shadow + private static function initVShader(index:int):Procedure { + var shader:Procedure = Procedure.compileFromArray([ + "m44 v0, a0, c0", + "mov v1, a0" + ]); + shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPosition"); + shader.assignVariableName(VariableType.CONSTANT, 0, index + "cTOSHADOW", 4); + shader.assignVariableName(VariableType.VARYING, 0, index + "vSHADOWSAMPLE"); + shader.assignVariableName(VariableType.VARYING, 1, "vPosition"); + return shader; + } + + private static function initFShader(mult:Boolean, usePCF:Boolean, index:int):Procedure { + var i:int; + var line:int = 0; + var shaderArr:Array = []; + var numPass:uint = (usePCF) ? 4 : 1; + for (i = 0; i < numPass; i++) { + // Расстояние + shaderArr[line++] = "mov t0.w, v0.z"; + shaderArr[line++] = "div t2, v0, v0.w"; + shaderArr[line++] = "mul t0.w, t0.w, c4.y"; // bias [0.99] * 255 + + if (usePCF) { + // Добавляем смещение + shaderArr[line++] = "mul t1, c" + (i + 9).toString() + ", t0.w"; +// shaderArr[line++] = "add t1, v0, t1"; + shaderArr[line++] = "add t1, t2, t1"; + shaderArr[line++] = "tex t1, t1, s0 <2d,clamp,near,nomip>"; + } else { +// shaderArr[line++] = "tex t1, v0, s0 <2d,clamp,near,nomip>"; + shaderArr[line++] = "tex t1, t2, s0 <2d,clamp,near,nomip>"; + } + + // Восстанавливаем расстояние + shaderArr[line++] = "mul t1.w, t1.x, c4.x"; // * 255 + shaderArr[line++] = "add t1.w, t1.w, t1.y"; + + // Перекрытие тенью + shaderArr[line++] = "sub t2.z, t1.w, t0.w"; + shaderArr[line++] = "mul t2.z, t2.z, c4.z"; // smooth [10000] + shaderArr[line++] = "sat t2.z, t2.z"; + + // Добавляем маску и прозрачность, затем sat + shaderArr[line++] = "add t2.z, t2.z, t1.z"; // маска тени + shaderArr[line++] = "add t2, t2.zzzz, c5"; // цвет тени + + // Плавный уход в прозрачность ------------- + shaderArr[line++] = "#c6=c" + index + "Spot"; + shaderArr[line++] = "#c7=c" + index + "Direction"; + shaderArr[line++] = "#c8=c" + index + "Geometry"; + shaderArr[line++] = "#v1=vPosition"; + // Считаем вектор из точки к свету + + // Вектор из точки к свету + shaderArr[line++] = "sub t0, c6, v1"; +// shaderArr[line++] = "sub t0, v1, c6"; + // Квадрат расстояния до точки + shaderArr[line++] = "dp3 t0.w, t0, t0"; + // Расстояние до точки + shaderArr[line++] = "sqt t0.w, t0.w"; + + // Нормализованное направление к источнику + shaderArr[line++] = "nrm t0.xyz, t0.xyz"; + // cos(Угол) между направлением к точке и направлением спота + shaderArr[line++] = "dp3 t0.y, t0.xyz, c7.xyz"; +// // cos(угол) - cos(falloff*0.5) + shaderArr[line++] = "sub t0.y, t0.y, c8.w"; +// // Делим на (cos(hotspot*0.5) - cos(falloff*0.5)) + shaderArr[line++] = "div t0.y, t0.y, c8.z"; + + // Минус atenuationBegin + shaderArr[line++] = "sub t0.w, t0.w, c8.y"; // len = len - atenuationBegin + // Делим на (atenuationEnd - atenuationBegin) + shaderArr[line++] = "div t0.w, t0.w, c8.x"; // att = len/radius + // 1 - соотношение между расстоянием до точки и максимальным расстоянием + shaderArr[line++] = "sub t0.w, c6.w, t0.w"; // att = 1 - len/radius + + shaderArr[line++] = "mul t0.w, t0.y, t0.w"; +// shaderArr[line++] = "mov t0.w, t0.y"; + shaderArr[line++] = "sub t0.w, c7.w, t0.w"; +// shaderArr[line++] = "mov t2, t0.wwww"; + shaderArr[line++] = "sat t0.w, t0.w"; + shaderArr[line++] = "add t2, t2, t0.wwww"; + // ----------------------------------------------------- + + shaderArr[line++] = "sat t2, t2"; + + if (usePCF) { + if (i == 0) { + shaderArr[line++] = "mov t3, t2"; + } else { + shaderArr[line++] = "add t3, t3, t2"; + } + } + } + if (usePCF) { + shaderArr[line++] = "mul t2, t3, c9.w"; + } + if (mult) { + shaderArr[line++] = "mul t0.xyz, i0.xyz, t2.xyz"; + shaderArr[line++] = "mov t0.w, i0.w"; + shaderArr[line++] = "mov o0, t0"; + } else { + shaderArr[line++] = "mov o0, t2"; +// shaderArr[line++] = "mov o0, t1"; + } + var shader:Procedure = Procedure.compileFromArray(shaderArr); + shader.assignVariableName(VariableType.VARYING, 0, index + "vSHADOWSAMPLE"); + shader.assignVariableName(VariableType.CONSTANT, 4, index + "cConstants", 1); + shader.assignVariableName(VariableType.CONSTANT, 5, index + "cShadowColor", 1); + if (usePCF) { + for (i = 0; i < numPass; i++) { + shader.assignVariableName(VariableType.CONSTANT, i + 9, "cSPCF" + i.toString(), 1); + } + } + shader.assignVariableName(VariableType.SAMPLER, 0, index + "sSHADOWMAP"); + return shader; + } + + override public function getFShader(index:int = 0):Procedure { + return initFShader(false, (pcfOffset > 0), index); + } + override public function getVShader(index:int = 0):Procedure { + return initVShader(index); + } + + private static const objectToShadowMap:Matrix3D = new Matrix3D(); + private static const localToGlobal:Transform3D = new Transform3D(); + override public function applyShader(drawUnit:DrawUnit, program:ShaderProgram, object:Object3D, camera:Camera3D, index:int = 0):void { + // Считаем матрицу перевода в лайт из объекта + localToGlobal.combine(camera.localToGlobalTransform, object.localToCameraTransform); + copyMatrixFromTransform(objectToShadowMap, localToGlobal); + objectToShadowMap.append(globalToShadowMap); + +// var casterPos:Vector3D = new Vector3D(caster.localToGlobalTransform.d, caster.localToGlobalTransform.h, caster.localToGlobalTransform.l); +// var p:Vector3D = objectToShadowMap.transformVector(casterPos); +// p.scaleBy(1/p.w); +// trace("caster pos:", p); + + objectToShadowMap.transpose(); + + drawUnit.setVertexConstantsFromVector(program.vertexShader.getVariableIndex(index + "cTOSHADOW"), objectToShadowMap.rawData, 4); + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex(index + "cConstants"), constants, constants.length/4); + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex(index + "cShadowColor"), camera.ambient, 1); + if (pcfOffset > 0) { + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex("cSPCF0"), pcfOffsets, pcfOffsets.length/4); + } + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex(index + "sSHADOWMAP"), shadowMap); + +// localToGlobal.combine(light.cameraToLocalTransform, object.localToCameraTransform); + localToGlobal.combine(object.cameraToLocalTransform, light.localToCameraTransform); +// localToGlobal.invert(); + + // Настройки затухания + var transform:Transform3D = localToGlobal; + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + index + "Spot"), transform.d, transform.h, transform.l, 1); + var rScale:Number = Math.sqrt(transform.a * transform.a + transform.e * transform.e + transform.i * transform.i); + rScale += Math.sqrt(transform.b * transform.b + transform.f * transform.f + transform.j * transform.j); + var dLen:Number = Math.sqrt(transform.c * transform.c + transform.g * transform.g + transform.k * transform.k); + rScale += dLen; + rScale /= 3; + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + index + "Direction"), -transform.c / dLen, -transform.g / dLen, -transform.k / dLen, 0.5); + + var falloff:Number = Math.cos(light.falloff * 0.5); + var hotspot:Number = Math.cos(light.hotspot * 0.5); + + drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("c" + index + "Geometry"), light.attenuationEnd * rScale - light.attenuationBegin * rScale, light.attenuationBegin * rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff); + } + + } +} + +class ProjectionTransform3D { + public var m0:Number; + public var m5:Number; + public var m10:Number; + public var m14:Number; +} diff --git a/src/alternativa/engine3d/shadows/StaticShadowRenderer.as b/src/alternativa/engine3d/shadows/StaticShadowRenderer.as new file mode 100644 index 0000000..18449ac --- /dev/null +++ b/src/alternativa/engine3d/shadows/StaticShadowRenderer.as @@ -0,0 +1,569 @@ +package alternativa.engine3d.shadows { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.DrawUnit; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + import alternativa.engine3d.lights.DirectionalLight; + import alternativa.engine3d.materials.ShaderProgram; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.materials.compiler.Procedure; + import alternativa.engine3d.materials.compiler.VariableType; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.primitives.Box; + import alternativa.engine3d.resources.ExternalTextureResource; + import alternativa.engine3d.resources.TextureResource; + + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.Texture; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * @private + */ + public class StaticShadowRenderer extends ShadowRenderer { + + public var context:Context3D; + + private const alpha:Number = 0.7; + + private var bounds:BoundBox = new BoundBox(); + private var partSize:Number; + private var partsShadowMaps:Vector.> = new Vector.>(); + private var partsUVMatrices:Vector.> = new Vector.>(); + + private var light:DirectionalLight; + private var globalToLight:Transform3D = new Transform3D(); + + private var _debug:Boolean = false; + private var debugContainer:Object3D; + + private var _recievers:Dictionary = new Dictionary(); + public function addReciever(object:Object3D):void { + _recievers[object] = true; + } + public function removeReciever(object:Object3D):void { + delete _recievers[object]; + } + + private static const constants:Vector. = Vector.([ + // 255, 255*0.99, 100, 1/255 +// 255, 255*0.96, 100, 1 + 255, 255, 1000, 1 + ]); + + private var pcfOffset:Number = 0; + private static var pcfOffsets:Vector.; + + public function dispose():void { + for each (var textures:Vector. in partsShadowMaps) { + for each (var texture:Texture in textures) { + texture.dispose(); + } + } + partsShadowMaps.length = 0; + partsUVMatrices.length = 0; + } + + public function StaticShadowRenderer(context:Context3D, partSize:int, pcfSize:Number = 0) { + this.context = context; + this.partSize = partSize; + this.pcfOffset = pcfSize; + constants[3] = 1 - alpha; + } + + override alternativa3d function cullReciever(boundBox:BoundBox, object:Object3D):Boolean { + return _recievers[object]; + } + + private var lightProjectionMatrix:Matrix3D = new Matrix3D(); + public function calculateShadows(object:Object3D, light:DirectionalLight, widthPartsCount:int = 1, heightPartsCount:int = 1, overlap:Number = 0):void { + this.light = light; + + var root:Object3D; + // Расчитываем матрицу объекта +// if (object.transformChanged) { + object.localToCameraTransform.compose(object._x, object._y, object._z, object._rotationX, object._rotationY, object._rotationZ, object._scaleX, object._scaleY, object._scaleZ); +// } else { +// object.localToCameraTransform.copy(caster.transform); +// } + root = object; + while (root._parent != null) { + root = root._parent; +// if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); +// } + object.localToCameraTransform.append(root.localToGlobalTransform); + } + + // Расчитываем матрицу лайта + light.localToGlobalTransform.compose(light._x, light._y, light._z, light._rotationX, light._rotationY, light._rotationZ, light._scaleX, light._scaleY, light._scaleZ); + root = light; + while (root._parent != null) { + root = root._parent; +// if (root.transformChanged) { + root.localToGlobalTransform.compose(root._x, root._y, root._z, root._rotationX, root._rotationY, root._rotationZ, root._scaleX, root._scaleY, root._scaleZ); +// } + light.localToGlobalTransform.append(root.localToGlobalTransform); + } + light.globalToLocalTransform.copy(light.localToGlobalTransform); + light.globalToLocalTransform.invert(); + + globalToLight.copy(light.globalToLocalTransform); + + // Получаем матрицу перевода из объекта в лайт + object.localToCameraTransform.append(light.globalToLocalTransform); + + bounds.reset(); + calculateBoundBox(bounds, object); + + var frustumMinX:Number = bounds.minX; + var frustumMaxX:Number = bounds.maxX; + var frustumMinY:Number = bounds.minY; + var frustumMaxY:Number = bounds.maxY; + var frustumMinZ:Number = bounds.minZ; + var frustumMaxZ:Number = bounds.maxZ; + + // Считаем шаг + var halfOverlap:Number = overlap*0.5; + var partWorldWidth:Number = (frustumMaxX - frustumMinX)/widthPartsCount; + var partWorldHeight:Number = (frustumMaxY - frustumMinY)/heightPartsCount; + + debugContainer = new Object3D(); + if (_debug) { + light.addChild(debugContainer); + } + + // Создаем шэдоумапы и рендерим + for (var xIndex:int = 0; xIndex < widthPartsCount; xIndex++) { + var maps:Vector. = new Vector.(); + var matrices:Vector. = new Vector.(); + for (var yIndex:int = 0; yIndex < heightPartsCount; yIndex++) { + var leftX:Number = frustumMinX + xIndex*partWorldWidth; + var leftY:Number = frustumMinY + yIndex*partWorldHeight; + + var width:Number; + var height:Number; + if (xIndex == 0) { + width = partWorldWidth + halfOverlap; + } else if (xIndex == (widthPartsCount - 1)) { + leftX -= halfOverlap; + width = partWorldWidth + halfOverlap; + } else { + leftX -= halfOverlap; + width = partWorldWidth + overlap; + } + if (yIndex == 0) { + height = partWorldHeight + halfOverlap; + } else if (yIndex == (heightPartsCount - 1)) { + leftY -= halfOverlap; + height = partWorldHeight + halfOverlap; + } else { + leftY -= halfOverlap; + height = partWorldHeight + overlap; + } + + var uvMatrix:Matrix3D = new Matrix3D(); + calculateShadowMapProjection(lightProjectionMatrix, uvMatrix, leftX, leftY, frustumMinZ, leftX + width, leftY + height, frustumMaxZ); + + var shadowMap:Texture = context.createTexture(partSize, partSize, Context3DTextureFormat.BGRA, true); + // Рисуем в шедоумапу + context.setRenderToTexture(shadowMap, true, 0, 0); + context.clear(1, 1, 1, 0.5); + cleanContext(context); + DirectionalShadowRenderer.drawObjectToShadowMap(context, object, light, lightProjectionMatrix); + cleanContext(context); + + maps.push(shadowMap); + matrices.push(uvMatrix); + + var texture:TextureResource = new ExternalTextureResource(null); + texture._texture = shadowMap; + var material:TextureMaterial = new TextureMaterial(texture); + material.opaquePass = false; + material.transparentPass = true; + material.alphaThreshold = 1.1; + var debugObject:Mesh = new Box(width, height, 1, 1, 1, 1, false, material); +// var debugObject:Mesh = new Box(width, height, 1, 1, 1, 1, false, new FillMaterial()); + debugObject.geometry.upload(context); + debugObject.x = leftX + width/2; + debugObject.y = leftY + height/2; + debugObject.z = frustumMinZ; + debugContainer.addChild(debugObject); + } + partsShadowMaps.push(maps); + partsUVMatrices.push(matrices); + } + context.setRenderToBackBuffer(); + if (pcfOffset > 0) { + var offset:Number = pcfOffset/partWorldWidth; +// pcfOffsets = Vector.([ +// -offset, -offset, 0, 1/4, +// -offset, offset, 0, 1, +// offset, -offset, 0, 1, +// offset, offset, 0, 1, +// ]); + pcfOffsets = Vector.([ + -offset, -offset, 0, 1/4, + -offset, offset, 0, 1, + offset, -offset, 0, 1, + offset, offset, 0, 1 + ]); + } + } + + private static const points:Vector. = Vector.([ + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D(), + new Vector3D(), new Vector3D(), new Vector3D(), new Vector3D() + ]); + alternativa3d static function calculateBoundBox(boundBox:BoundBox, object:Object3D, hierarchy:Boolean = true):void { + // Считаем баунды объекта в лайте + var point:Vector3D; + if (object.boundBox != null) { + var bb:BoundBox = object.boundBox; + point = points[0]; + point.x = bb.minX; + point.y = bb.minY; + point.z = bb.minZ; + point = points[1]; + point.x = bb.minX; + point.y = bb.minY; + point.z = bb.maxZ; + point = points[2]; + point.x = bb.minX; + point.y = bb.maxY; + point.z = bb.minZ; + point = points[3]; + point.x = bb.minX; + point.y = bb.maxY; + point.z = bb.maxZ; + point = points[4]; + point.x = bb.maxX; + point.y = bb.minY; + point.z = bb.minZ; + point = points[5]; + point.x = bb.maxX; + point.y = bb.minY; + point.z = bb.maxZ; + point = points[6]; + point.x = bb.maxX; + point.y = bb.maxY; + point.z = bb.minZ; + point = points[7]; + point.x = bb.maxX; + point.y = bb.maxY; + point.z = bb.maxZ; + var transform:Transform3D = object.localToCameraTransform; + for (var i:int = 0; i < 8; i++) { + point = points[i]; + var x:Number = transform.a*point.x + transform.b*point.y + transform.c*point.z + transform.d; + var y:Number = transform.e*point.x + transform.f*point.y + transform.g*point.z + transform.h; + var z:Number = transform.i*point.x + transform.j*point.y + transform.k*point.z + transform.l; + if (x < boundBox.minX) { + boundBox.minX = x; + } + if (x > boundBox.maxX) { + boundBox.maxX = x; + } + if (y < boundBox.minY) { + boundBox.minY = y; + } + if (y > boundBox.maxY) { + boundBox.maxY = y; + } + if (z < boundBox.minZ) { + boundBox.minZ = z; + } + if (z > boundBox.maxZ) { + boundBox.maxZ = z; + } + } + } + if (hierarchy) { + // Пробегаемся по дочерним объектам + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.visible) { + if (child.transformChanged) { + child.composeTransforms(); + } + child.localToCameraTransform.combine(object.localToCameraTransform, child.transform); + calculateBoundBox(boundBox, child); + } + } + } + } + + private var rawData:Vector. = new Vector.(16); + private function calculateShadowMapProjection(matrix:Matrix3D, uvMatrix:Matrix3D, frustumMinX:Number, frustumMinY:Number, frustumMinZ:Number, frustumMaxX:Number, frustumMaxY:Number, frustumMaxZ:Number):void { + // Считаем матрицу проецирования + 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; + matrix.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.rawData = rawData; + } + + override public function get debug():Boolean { + return _debug; + } + + override public function set debug(value:Boolean):void { + _debug = value; + if (debugContainer != null) { + if (value) { + if (light != null) { + light.addChild(debugContainer); + } + } else { + if (debugContainer._parent != null) { + debugContainer.removeFromParent(); + } + } + } + } + +// private static const vShader:Procedure = initVShader(index); + private static function initVShader(index:int):Procedure { + var shader:Procedure = Procedure.compileFromArray([ +// "m44 o0, a0, c0", +// Координата вершины в локальном пространстве + "m44 v0, a0, c4" + ]); + shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPosition"); + shader.assignVariableName(VariableType.CONSTANT, 0, "cPROJ", 4); + shader.assignVariableName(VariableType.CONSTANT, 4, "cTOSHADOW", 4); + shader.assignVariableName(VariableType.VARYING, 0, "vSHADOWSAMPLE"); + return shader; + } +// private static const multVShader:Procedure = initMultVShader(); +// private static function initMultVShader():Procedure { +// var shader:Procedure = Procedure.compileFromArray([ +// "m44 v0, a0, c0", +// ]); +// shader.assignVariableName(VariableType.ATTRIBUTE, 0, "aPOSITION"); +// shader.assignVariableName(VariableType.CONSTANT, 0, "cTOSHADOW", 4); +// shader.assignVariableName(VariableType.VARYING, 0, "vSHADOWSAMPLE"); +// return shader; +// } +// private static const fShader:Procedure = initFShader(false, false, index); +// private static const pcfFShader:Procedure = initFShader(false, true, index); + // i0 - input color + // o0 - shadowed result +// private static const multFShader:Procedure = initFShader(true, false, index); +// private static const pcfMultFShader:Procedure = initFShader(true, true, index); + private static function initFShader(mult:Boolean, usePCF:Boolean, index:int, grayScale:Boolean = false):Procedure { + var i:int; + var line:int = 0; + var shaderArr:Array = []; + var numPass:uint = (usePCF) ? 4 : 1; + for (i = 0; i < numPass; i++) { + // Расстояние + shaderArr[line++] = "mov t0.w, v0.z"; + shaderArr[line++] = "mul t0.w, t0.w, c4.y"; // bias [0.99] * 255 + + if (usePCF) { + // Добавляем смещение +// shaderArr[line++] = "mul t1, c" + (i + 5).toString() + ", t0.w"; + shaderArr[line++] = "add t1, v0, c" + (i + 5).toString() + ""; + // shaderArr[line++] = "add t1, v0, c" + (i + 5).toString(); + shaderArr[line++] = "tex t1, t1, s0 <2d,clamp,near,nomip>"; + } else { + shaderArr[line++] = "tex t1, v0, s0 <2d,clamp,near,nomip>"; + } + + // Восстанавливаем расстояние + shaderArr[line++] = "mul t1.w, t1.x, c4.x"; // * 255 + shaderArr[line++] = "add t1.w, t1.w, t1.y"; + + // Перекрытие тенью + shaderArr[line++] = "sub t2.z, t1.w, t0.w"; + shaderArr[line++] = "mul t2.z, t2.z, c4.z"; // smooth [10000] + shaderArr[line++] = "sat t2.z, t2.z"; + + // Добавляем маску и прозрачность, затем sat +// shaderArr[line++] = "add t2.z, t2.z, t1.z"; // маска тени +// shaderArr[line++] = "add t2.z, t2.z, c4.w"; // вес тени + shaderArr[line++] = "sat t2.z, t2.z"; + + if (usePCF) { + if (i == 0) { + shaderArr[line++] = "mov t2.x, t2.z"; + } else { + shaderArr[line++] = "add t2.x, t2.x, t2.z"; + } + } + } + if (usePCF) { + shaderArr[line++] = "mul t2.z, t2.x, c5.w"; + } + if (grayScale) { + shaderArr.push("mov o0.w, t2.z"); + } else { + if (mult) { + shaderArr.push("mul t0.xyz, i0.xyz, t2.z"); + shaderArr.push("mov t0.w, i0.w"); + shaderArr.push("mov o0, t0"); + } else { + shaderArr.push("mov t0, t2.z"); + shaderArr.push("mov o0, t0"); + } + } + var shader:Procedure = Procedure.compileFromArray(shaderArr, "StaticShadowMap"); + shader.assignVariableName(VariableType.VARYING, 0, "vSHADOWSAMPLE"); + shader.assignVariableName(VariableType.CONSTANT, 4, "cConstants", 1); + if (usePCF) { + for (i = 0; i < numPass; i++) { + shader.assignVariableName(VariableType.CONSTANT, i + 5, "cPCF" + i.toString(), 1); + } + } + shader.assignVariableName(VariableType.SAMPLER, 0, "sSHADOWMAP"); + return shader; + } + + override public function getVShader(index:int = 0):Procedure { + return initVShader(index); + } + override public function getFShader(index:int = 0):Procedure { + return initFShader(false, (pcfOffset > 0), index); + } + +// override public function getMultFShader():Procedure { +// return (pcfOffset > 0) ? pcfMultFShader : multFShader; +// } +// override public function getMultVShader():Procedure { +// return multVShader; +// } + + override public function getFIntensityShader():Procedure { + return initFShader(false, (pcfOffset > 0), 0, true); + } + + private static const objectToShadowMap:Transform3D = new Transform3D(); + private static const objectToUVMap:Matrix3D = new Matrix3D(); + override public function applyShader(drawUnit:DrawUnit, program:ShaderProgram, object:Object3D, camera:Camera3D, index:int = 0):void { + // Считаем матрицу перевода в лайт из объекта + + objectToShadowMap.combine(camera.localToGlobalTransform, object.localToCameraTransform); + objectToShadowMap.append(globalToLight); + +// objectToShadowMap.identity(); +// objectToShadowMap.append(object.cameraMatrix); +// objectToShadowMap.append(camera.globalMatrix); +// objectToShadowMap.append(globalToLight); + + // Получаем индекс шедоумапы +// var coords:Vector3D = objectToShadowMap.position; + var coords:Vector3D = new Vector3D(objectToShadowMap.d, objectToShadowMap.h, objectToShadowMap.l); + var xIndex:int = (coords.x - bounds.minX)/(bounds.maxX - bounds.minX)*partsShadowMaps.length; + + xIndex = (xIndex < 0) ? 0 : ((xIndex >= partsShadowMaps.length) ? partsShadowMaps.length - 1 : xIndex); + var maps:Vector. = partsShadowMaps[xIndex]; + var matrices:Vector. = partsUVMatrices[xIndex]; + + var yIndex:int = (coords.y - bounds.minY)/(bounds.maxY - bounds.minY)*maps.length; + yIndex = (yIndex < 0) ? 0 : ((yIndex >= maps.length) ? maps.length - 1 : yIndex); + +// trace(xIndex, yIndex); + + var shadowMap:Texture = maps[yIndex]; + var uvMatrix:Matrix3D = matrices[yIndex]; + + DirectionalShadowRenderer.copyMatrixFromTransform(objectToUVMap, objectToShadowMap); + objectToUVMap.append(uvMatrix); + objectToUVMap.transpose(); +// objectToShadowMap.append(uvMatrix); + + drawUnit.setVertexConstantsFromVector(program.vertexShader.getVariableIndex("cTOSHADOW"), objectToUVMap.rawData, 4); + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex("cConstants"), constants, 1); + if (pcfOffset > 0) { + drawUnit.setFragmentConstantsFromVector(program.fragmentShader.getVariableIndex("cPCF0"), pcfOffsets, pcfOffsets.length >> 2) + } + drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sSHADOWMAP"), shadowMap); + } + +// private static var program:LinkedProgram; +// private static var programPCF:LinkedProgram; +// private static function initMeshProgram(context:Context3D, usePCF:Boolean):LinkedProgram { +// var vLinker:Linker = new Linker(Context3DProgramType.VERTEX); +// vLinker.addShader(vShader); +// +// var fLinker:Linker = new Linker(Context3DProgramType.FRAGMENT); +// if (usePCF) { +// fLinker.addShader(pcfFShader); +// } else { +// fLinker.addShader(fShader); +// } +// +// vLinker.setOppositeLinker(fLinker); +// fLinker.setOppositeLinker(vLinker); +// +//// trace("[VERTEX]"); +//// trace(AgalUtils.disassemble(vLinker.getByteCode())); +//// trace("[FRAGMENT]"); +//// trace(AgalUtils.disassemble(fLinker.getByteCode())); +// +// var result:LinkedProgram; +// if (usePCF) { +// programPCF = new LinkedProgram(); +// result = programPCF; +// } else { +// program = new LinkedProgram(); +// result = program; +// } +// result.vLinker = vLinker; +// result.fLinker = fLinker; +// result.program = context.createProgram(); +// result.program.upload(vLinker.getByteCode(), fLinker.getByteCode()); +// +// return result; +// } +// +// override public function drawShadow(mesh:Mesh, camera:Camera3D, texture:Texture):void { +// var context3d:Context3D = camera.view._context3d; +// +// var linkedProgram:LinkedProgram; +// if (pcfOffset > 0) { +// linkedProgram = (programPCF == null) ? initMeshProgram(context3d, true) : programPCF; +// } else { +// linkedProgram = (program == null) ? initMeshProgram(context3d, false) : program; +// } +// var vLinker:Linker = linkedProgram.vLinker; +// var fLinker:Linker = linkedProgram.fLinker; +// context3d.setProgram(linkedProgram.program); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), mesh.geometry.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); +// context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vLinker.getVariableIndex("cPROJ"), mesh.projectionMatrix, true); +// applyShader(context3d, linkedProgram, mesh, camera); +// context3d.setVertexBufferAt(1, null); +// +// context3d.setCulling(Context3DTriangleFace.FRONT); +// context3d.drawTriangles(mesh.geometry.indexBuffer, 0, mesh.geometry.numTriangles); +// +// context3d.setVertexBufferAt(vLinker.getVariableIndex("aPOSITION"), null); +// context.setTextureAt(getTextureIndex(fLinker), texture); +// } + + } +} diff --git a/src/alternativa/engine3d/utils/Object3DUtils.as b/src/alternativa/engine3d/utils/Object3DUtils.as new file mode 100644 index 0000000..ba55984 --- /dev/null +++ b/src/alternativa/engine3d/utils/Object3DUtils.as @@ -0,0 +1,92 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.engine3d.utils { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Transform3D; + + use namespace alternativa3d; + + /** + * @private + */ + public class Object3DUtils { + + private static const toRootTransform:Transform3D = new Transform3D(); + private static const fromRootTransform:Transform3D = new Transform3D(); + + /** + * @private + * Performs calculation of bound box of objects hierarchy branch. + */ + public static function calculateHierarchyBoundBox(object:Object3D, boundBoxSpace:Object3D = null, result:BoundBox = null):BoundBox { + if (result == null) result = new BoundBox(); + + if (boundBoxSpace != null && object != boundBoxSpace) { + // Calculate transfer matrix from object to provided space. + var objectRoot:Object3D; + var toSpaceTransform:Transform3D = null; + + if (object.transformChanged) object.composeTransforms(); + toRootTransform.copy(object.transform); + var root:Object3D = object; + while (root._parent != null) { + root = root._parent; + if (root.transformChanged) root.composeTransforms(); + toRootTransform.append(root.transform); + if (root == boundBoxSpace) { + // Matrix has been composed. + toSpaceTransform = toRootTransform; + } + } + objectRoot = root; + if (toSpaceTransform == null) { + // Transfer matrix from root to needed space. + if (boundBoxSpace.transformChanged) boundBoxSpace.composeTransforms(); + fromRootTransform.copy(boundBoxSpace.inverseTransform); + root = boundBoxSpace; + while (root._parent != null) { + root = root._parent; + if (root.transformChanged) root.composeTransforms(); + fromRootTransform.prepend(root.inverseTransform); + } + if (objectRoot == root) { + toRootTransform.append(fromRootTransform); + toSpaceTransform = toRootTransform; + } else { + throw new ArgumentError("Object and boundBoxSpace must be located in the same hierarchy."); + } + } + updateBoundBoxHierarchically(object, result, toSpaceTransform); + } else { + updateBoundBoxHierarchically(object, result); + } + return result; + } + + /** + * @private + * Calculates hierarchical bound. + */ + alternativa3d static function updateBoundBoxHierarchically(object:Object3D, boundBox:BoundBox, transform:Transform3D = null):void { + object.updateBoundBox(boundBox, transform); + for (var child:Object3D = object.childrenList; child != null; child = child.next) { + if (child.transformChanged) child.composeTransforms(); + child.localToCameraTransform.copy(child.transform); + if (transform != null) child.localToCameraTransform.append(transform); + updateBoundBoxHierarchically(child, boundBox, child.localToCameraTransform); + } + } + + } +} diff --git a/src/alternativa/osgi/service/clientlog/IClientLog.as b/src/alternativa/osgi/service/clientlog/IClientLog.as new file mode 100644 index 0000000..d6691df --- /dev/null +++ b/src/alternativa/osgi/service/clientlog/IClientLog.as @@ -0,0 +1,73 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.osgi.service.clientlog { + + /** + * @private + */ + public interface IClientLog extends IClientLogBase { + + /** + * Adds record in specified log, duplicating its into the "error" channel. + * + * @param channelName Name of channel. + * @param text Text, that can contain expressions like %i, i=1..n. These expressions will be changed by values, that is passed as subsequent parameters. + * @param vars Values of variables in text. + */ + function logError(channelName:String, text:String, ...vars):void; + + /** + * Returns list of strings at specified channel. If channel is not exists, null is returned. + * + * @param channelName Name of channel + * @return List of string in specified channel. + */ + function getChannelStrings(channelName:String):Vector.; + + /** + * Add listener for all channel of log. + * + * @param listener Listener. + */ + function addLogListener(listener:IClientLogChannelListener):void; + + /** + * Removes listener from all channels of log. + * + * @param listener Listener. + */ + function removeLogListener(listener:IClientLogChannelListener):void; + + /** + * Add listener of channel. + * + * @param channelName Name of channel, for which the listener is added. + * @param listener Listener. + */ + function addLogChannelListener(channelName:String, listener:IClientLogChannelListener):void; + + /** + * Removes listener of channel. + * + * @param channelName Name of channel, for which the listener is removed. + * @param listener Listener. + */ + function removeLogChannelListener(channelName:String, listener:IClientLogChannelListener):void; + + /** + * Returns list of existing channels. + * + * @return List of existing channels. + */ + function getChannelNames():Vector.; + + } +} diff --git a/src/alternativa/osgi/service/clientlog/IClientLogChannelListener.as b/src/alternativa/osgi/service/clientlog/IClientLogChannelListener.as new file mode 100644 index 0000000..a257fe9 --- /dev/null +++ b/src/alternativa/osgi/service/clientlog/IClientLogChannelListener.as @@ -0,0 +1,21 @@ +/** + * Exhibit A - Source Code Form License Notice + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + * You may add additional accurate notices of copyright ownership. + * + * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ + * */ + +package alternativa.osgi.service.clientlog { + + /** + * @private + */ + public interface IClientLogChannelListener { + + function onLogEntryAdded(channelName:String, logText:String):void; + + } +}