In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

[Alternativa3D 7.6] OrbitCameraController

イージング(遅延減速)付きのカメラコントローラーGeoCameraController クラスをfork して作成しました。

地球の周りを回る衛星のようにオブジェクトの周りをグルグル回したい時に最適なカメラコントローラーです。
色んな機能を搭載しているので幅広く使えると思います。

例1:レンダリングの必要可否が分かるので、処理負荷を下げることができる。
例2 : 緯度・経度でのカメラの位置制御が可能。
/**
 * Copyright clockmaker ( http://wonderfl.net/user/clockmaker )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/tSHD
 */

// forked from narutohyper's Alternativa3D 7.6 Template
package
{
    import alternativ7.engine3d.core.Sorting;
    import alternativ7.engine3d.loaders.MaterialLoader;
    import alternativ7.engine3d.loaders.Parser3DS;
    import alternativ7.engine3d.materials.TextureMaterial;
    import alternativ7.engine3d.objects.Mesh;
    import alternativ7.engine3d.objects.Sprite3D;
    
    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import flash.system.*;

    /**
     * 
     * イージング(遅延減速)付きのカメラコントローラー OrbitCameraController クラスを fork して作成しました。
     *
     * 地球の周りを回る衛星のようにオブジェクトの周りをグルグル回したい時に最適なカメラコントローラーです。
     * 色んな機能を搭載しているので幅広く使えると思います。
     * 例1:レンダリングの必要可否が分かるので、処理負荷を下げることができる。
     * 例2 : 緯度・経度でのカメラの位置制御が可能。
     *
     * @author narutohyper
     * @author clockmaker
     */
    [SWF(frameRate="60", width="465", height="465")]
    public class Main extends Sprite
    {
        private var scene:AlternativaTemplate;

        public function Main():void
        {
            //AlternativaTemplate作成
            scene = new AlternativaTemplate(this);
            scene.cameraController.object = null;
            addChild(scene.camera.diagram);
            opaqueBackground = 0x0;

            // オブジェクト配置
            createObject();

            // カメラコントローラーの作成
            var cameraController:OrbitCameraController = new OrbitCameraController(scene.camera, stage, stage, stage);
            cameraController.bindBasicKey();

            // レンダリング開始
            addEventListener(Event.ENTER_FRAME, function():void{
                cameraController.update();

                if (cameraController.needsRendering)
                    scene.singleRender();
            });
        }

        private function createObject():void
        {
            //星パーティクル
            var bmd:BitmapData = new BitmapData(10, 10, false, 0xFFFFFFFF);
            var material:TextureMaterial = new TextureMaterial(bmd);
            
            for (var i:int = 0; i < 500; i++)
            {
                var s:Sprite3D = new Sprite3D(10, 10, material);
                s.x = 10000 * (Math.random() - 0.5);
                s.y = 10000 * (Math.random() - 0.5);
                s.z = 10000 * (Math.random() - 0.5);
                scene.container.addChild(s);
            }

            // オブジェクト
            Security.loadPolicyFile("http://clockmaker.jp/crossdomain.xml");
            var loader:URLLoader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, function():void{
                // パース
                var parser:Parser3DS = new Parser3DS();
                parser.parse(loader.data, "http://clockmaker.jp/labs/090807_alternativa3d/", 30);

                for (var j:int = 0; j < parser.objects.length; j++)
                {
                    var mesh:Mesh = parser.objects[j] as Mesh;
                    mesh.sorting = Sorting.NONE;
                    scene.container.addChild(mesh);
                }

                // マテリアル読み込み
                var materialLoader:MaterialLoader = new MaterialLoader();
                materialLoader.addEventListener(Event.COMPLETE, function():void{
                    scene.singleRender();
                });
                materialLoader.load(parser.textureMaterials);
            });

            loader.load(new URLRequest("http://clockmaker.jp/labs/090807_alternativa3d/test.3ds"));
        }
    }
}

import alternativ7.engine3d.containers.BSPContainer;
import alternativ7.engine3d.containers.ConflictContainer;
import alternativ7.engine3d.containers.DistanceSortContainer;
import alternativ7.engine3d.containers.KDContainer;
import alternativ7.engine3d.containers.LODContainer;
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.Object3D;
import alternativ7.engine3d.core.Object3DContainer;
import alternativ7.engine3d.core.View;

import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Vector3D;
import flash.ui.Keyboard;
import flash.ui.Mouse;
import flash.ui.MouseCursor;

/**
 * GeoCameraController は 3D オブジェクトの周りに配置することのできるコントローラークラスです。
 * 緯度・経度で配置することができます。
 *
 * @author narutohyper
 * @author clockmaker
 *
 * @see http://wonderfl.net/c/fwPU
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 */
internal class OrbitCameraController extends SimpleObjectController
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /** 中心と方向へ移動するアクションを示す定数です。 */
    public static const ACTION_FORWARD:String = "actionForward";

    /** 中心と反対方向へ移動するアクションを示す定数です。 */
    public static const ACTION_BACKWARD:String = "actionBackward";

    /** イージングの終了判断に用いるパラメーターです。0〜0.2で設定し、0に近いほどイージングが残されます。 */
    private static const ROUND_VALUE:Number = 0.1;

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    /**
     * 新しい GeoCameraController インスタンスを作成します。
     * @param targetObject    コントローラーで制御したいオブジェクトです。
     * @param mouseDownEventSource    マウスダウンイベントとひもづけるオブジェクトです。
     * @param mouseUpEventSource    マウスアップイベントとひもづけるオブジェクトです。推奨は stage です。
     * @param keyEventSource    キーダウン/キーマップイベントとひもづけるオブジェクトです。推奨は stage です。
     * @param useKeyControl    キーコントロールを使用するか指定します。
     * @param useMouseWheelControl    マウスホイールコントロールを使用するか指定します。
     */
    public function OrbitCameraController(
        targetObject:Object3D,
        mouseDownEventSource:InteractiveObject,
        mouseUpEventSource:InteractiveObject,
        keyEventSource:InteractiveObject,
        useKeyControl:Boolean = true,
        useMouseWheelControl:Boolean = true
        )
    {
        _target = targetObject;

        super(mouseDownEventSource, targetObject, 0, 3, mouseSensitivity);
        super.mouseSensitivity = 0;
        super.unbindAll();
        super.accelerate(true);

        this._mouseDownEventSource = mouseDownEventSource;
        this._mouseUpEventSource = mouseUpEventSource;
        this._keyEventSource = keyEventSource;

        _mouseDownEventSource.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);

        // マウスホイール操作
        if (useMouseWheelControl)
        {
            _mouseDownEventSource.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
        }

        // キーボード操作
        if (useKeyControl)
        {
            _keyEventSource.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            _keyEventSource.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
        }
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    //--------------------------------------
    // easingSeparator 
    //--------------------------------------

    private var _easingSeparator:Number = 10;

    /**
     * イージング時の現在の位置から最後の位置までの分割係数
     * フレームレートと相談して使用
     * 1〜
     * 正の整数のみ。0 を指定しても 1 になります。
     * 1 でイージング無し。数値が高いほど、遅延しぬるぬるします
     */
    public function set easingSeparator(value:uint):void
    {
        if (value)
        {
            _easingSeparator = value;
        }
        else
        {
            _easingSeparator = 1;
        }
    }
    /** Camera から、中心までの最大距離です。デフォルト値は 2000 です。 */
    public var maxDistance:Number = 2000;

    /** Camera から、中心までの最小距離です。デフォルト値は 200 です。 */
    public var minDistance:Number = 200;

    //--------------------------------------
    // mouseSensitivityX 
    //--------------------------------------

    private var _mouseSensitivityX:Number = -1;

    /**
     * マウスの X 方向の感度
     */
    public function set mouseSensitivityX(value:Number):void
    {
        _mouseSensitivityX = value;
    }

    //--------------------------------------
    // mouseSensitivityY 
    //--------------------------------------

    private var _mouseSensitivityY:Number = 1;

    /**
     * マウスの Y 方向の感度
     */
    public function set mouseSensitivityY(value:Number):void
    {
        _mouseSensitivityY = value;
    }

    public var multiplyValue:Number = 10;

    //--------------------------------------
    // needsRendering 
    //--------------------------------------

    private var _needsRendering:Boolean;

    /**
     * レンダリングが必要かどうかを取得します。
     * この値は update() メソッドが呼び出されたタイミングで更新されます。
     *
     * @see GeoCameraController.update
     */
    public function get needsRendering():Boolean
    {
        return _needsRendering;
    }

    //--------------------------------------
    // pitchSpeed 
    //--------------------------------------

    private var _pitchSpeed:Number = 5;

    /**
     * 上下スピード
     * 初期値は5(px)
     */
    public function set pitchSpeed(value:Number):void
    {
        _pitchSpeed = value
    }

    public var useHandCursor:Boolean = true;

    //--------------------------------------
    // yawSpeed 
    //--------------------------------------

    private var _yawSpeed:Number = 5;

    /**
     * 回転スピード
     * 初期値は5(度)
     */
    public function set yawSpeed(value:Number):void
    {
        _yawSpeed = value * Math.PI / 180;
    }

    /**
     * Zoomスピード
     * 初期値は5(px)
     */
    public function set zoomSpeed(value:Number):void
    {
        _distanceSpeed = value;
    }

    private var _angleLatitude:Number = 0;
    private var _angleLongitude:Number = 0;
    private var _distanceSpeed:Number = 5;
    private var _keyEventSource:InteractiveObject;
    private var _lastLatitude:Number = 0;
    private var _lastLength:Number = 700;
    private var _lastLongitude:Number = _angleLongitude;
    private var _lastLookAtX:Number = _lookAtX;
    private var _lastLookAtY:Number = _lookAtY;
    private var _lastLookAtZ:Number = _lookAtZ;
    private var _length:Number = 700;
    private var _lookAtX:Number = 0;
    private var _lookAtY:Number = 0;
    private var _lookAtZ:Number = 0;
    private var _mouseDownEventSource:InteractiveObject;
    private var _mouseMove:Boolean;
    private var _mouseUpEventSource:InteractiveObject;
    private var _mouseX:Number;
    private var _mouseY:Number;
    private var _oldLatitude:Number;
    private var _oldLongitude:Number;
    private var _pitchDown:Boolean;
    private var _pitchUp:Boolean;
    private var _taregetDistanceValue:Number = 0;
    private var _taregetPitchValue:Number = 0;
    private var _taregetYawValue:Number = 0;
    private var _target:Object3D
    private var _yawLeft:Boolean;
    private var _yawRight:Boolean;
    private var _zoomIn:Boolean;
    private var _zoomOut:Boolean;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * 自動的に適切なキーを割り当てます。
     */
    public function bindBasicKey():void
    {
        bindKey(Keyboard.LEFT, SimpleObjectController.ACTION_YAW_LEFT);
        bindKey(Keyboard.RIGHT, SimpleObjectController.ACTION_YAW_RIGHT);
        bindKey(Keyboard.DOWN, SimpleObjectController.ACTION_PITCH_DOWN);
        bindKey(Keyboard.UP, SimpleObjectController.ACTION_PITCH_UP);
        bindKey(Keyboard.PAGE_UP, OrbitCameraController.ACTION_BACKWARD);
        bindKey(Keyboard.PAGE_DOWN, OrbitCameraController.ACTION_FORWARD);
    }

    /** 上方向に移動します。 */
    public function pitchUp():void
    {
        _taregetPitchValue = _pitchSpeed * multiplyValue;
    }

    /** 下方向に移動します。 */
    public function pitchDown():void
    {
        _taregetPitchValue = _pitchSpeed * -multiplyValue;
    }

    /** 左方向に移動します。 */
    public function yawLeft():void
    {
        _taregetYawValue = _yawSpeed * multiplyValue;
    }

    /** 右方向に移動します。 */
    public function yawRight():void
    {
        _taregetYawValue = _yawSpeed * -multiplyValue;
    }

    /** 中心へ向かって近づきます。 */
    public function moveForeward():void
    {
        _taregetDistanceValue -= _distanceSpeed * multiplyValue;
    }

    /** 中心から遠くに離れます。 */
    public function moveBackward():void
    {
        _taregetDistanceValue += _distanceSpeed * multiplyValue;
    }

    /**
     * @inheritDoc
     */
    override public function bindKey(keyCode:uint, action:String):void
    {
        switch (action)
        {
            case ACTION_FORWARD:
                keyBindings[keyCode] = toggleForward;
                break
            case ACTION_BACKWARD:
                keyBindings[keyCode] = toggleBackward;
                break
            case ACTION_YAW_LEFT:
                keyBindings[keyCode] = toggleYawLeft;
                break
            case ACTION_YAW_RIGHT:
                keyBindings[keyCode] = toggleYawRight;
                break
            case ACTION_PITCH_DOWN:
                keyBindings[keyCode] = togglePitchDown;
                break
            case ACTION_PITCH_UP:
                keyBindings[keyCode] = togglePitchUp;
                break
        }
        //super.bindKey(keyCode, action)
    }

    /**
     * 下方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function togglePitchDown(value:Boolean):void
    {
        _pitchDown = value
    }

    /**
     * 上方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function togglePitchUp(value:Boolean):void
    {
        _pitchUp = value
    }

    /**
     * 左方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function toggleYawLeft(value:Boolean):void
    {
        _yawLeft = value;
    }

    /**
     * 右方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function toggleYawRight(value:Boolean):void
    {
        _yawRight = value;
    }

    /**
     * 中心方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function toggleForward(value:Boolean):void
    {
        _zoomIn = value;
    }

    /**
     * 中心と反対方向に移動し続けるかを設定します。
     * @param value    true の場合は移動が有効になります。
     */
    public function toggleBackward(value:Boolean):void
    {
        _zoomOut = value;
    }

    /**
     * @inheritDoc
     */
    override public function update():void
    {
        const RADIAN:Number = Math.PI / 180;
        var oldAngleLatitude:Number = _angleLatitude;
        var oldAngleLongitude:Number = _angleLongitude;
        var oldLengh:Number = _lastLength;

        //CameraZoom制御
        if (_zoomIn)
        {
            _lastLength -= _distanceSpeed;
        }
        else if (_zoomOut)
        {
            _lastLength += _distanceSpeed;
        }

        // ズーム制御
        if (_taregetDistanceValue != 0)
        {
            _lastLength += _taregetDistanceValue;
            _taregetDistanceValue = 0;
        }

        if (_lastLength < minDistance)
        {
            _lastLength = minDistance;
        }
        else if (_lastLength > maxDistance)
        {
            _lastLength = maxDistance;
        }
        if (_lastLength - _length)
        {
            _length += (_lastLength - _length) / _easingSeparator;
        }

        if (Math.abs(_lastLength - _length) < ROUND_VALUE)
        {
            _length = _lastLength;
        }

        //Camera回転制御
        if (_mouseMove)
        {
            _lastLongitude = _oldLongitude + (_mouseDownEventSource.mouseX - _mouseX) * _mouseSensitivityX
        }
        else if (_yawLeft)
        {
            _lastLongitude += _yawSpeed;
        }
        else if (_yawRight)
        {
            _lastLongitude -= _yawSpeed;
        }

        if (_taregetYawValue)
        {
            _lastLongitude += _taregetYawValue;
            _taregetYawValue = 0;
        }

        if (_lastLongitude - _angleLongitude)
        {
            _angleLongitude += (_lastLongitude - _angleLongitude) / _easingSeparator;
        }

        if (Math.abs(_lastLatitude - _angleLatitude) < ROUND_VALUE)
        {
            _angleLatitude = _lastLatitude;
        }

        //CameraZ位置制御
        if (_mouseMove)
        {
            _lastLatitude = _oldLatitude + (_mouseDownEventSource.mouseY - _mouseY) * _mouseSensitivityY;
        }
        else if (_pitchDown)
        {
            _lastLatitude -= _pitchSpeed;
        }
        else if (_pitchUp)
        {
            _lastLatitude += _pitchSpeed;
        }

        if (_taregetPitchValue)
        {
            _lastLatitude += _taregetPitchValue;
            _taregetPitchValue = 0;
        }

        _lastLatitude = Math.max(-89.9, Math.min(_lastLatitude, 89.9));

        if (_lastLatitude - _angleLatitude)
        {
            _angleLatitude += (_lastLatitude - _angleLatitude) / _easingSeparator;
        }
        if (Math.abs(_lastLongitude - _angleLongitude) < ROUND_VALUE)
        {
            _angleLongitude = _lastLongitude;
        }

        var vec3d:Vector3D = this.translateGeoCoords(_angleLatitude, _angleLongitude, _length);
        _target.x = vec3d.x;
        _target.y = vec3d.y;
        _target.z = vec3d.z;

        //lookAt制御
        if (_lastLookAtX - _lookAtX)
        {
            _lookAtX += (_lastLookAtX - _lookAtX) / _easingSeparator;
        }

        if (_lastLookAtY - _lookAtY)
        {
            _lookAtY += (_lastLookAtY - _lookAtY) / _easingSeparator;
        }

        if (_lastLookAtZ - _lookAtZ)
        {
            _lookAtZ += (_lastLookAtZ - _lookAtZ) / _easingSeparator;
        }

        //super.update()
        updateObjectTransform()
        lookAtXYZ(_lookAtX, _lookAtY, _lookAtZ);

        _needsRendering = oldAngleLatitude != _angleLatitude
            || oldAngleLongitude != _angleLongitude
            || oldLengh != _length;
    }

    /** @inheritDoc */
    override public function startMouseLook():void
    {
        // 封印
    }

    /** @inheritDoc */
    override public function stopMouseLook():void
    {
        // 封印
    }

    /**
     * Cameraの向く方向を指定します。
     * lookAtやlookAtXYZとの併用は不可。
     * @param x
     * @param y
     * @param z
     * @param immediate    trueで、イージングしないで変更
     */
    public function lookAtPosition(x:Number, y:Number, z:Number, immediate:Boolean = false):void
    {
        if (immediate)
        {
            _lookAtX = x
            _lookAtY = y
            _lookAtZ = z
        }
        _lastLookAtX = x
        _lastLookAtY = y
        _lastLookAtZ = z
    }

    /**
     * 経度を設定します。
     * @param value    0で、正面から中央方向(lookAtPosition)を見る
     * @param immediate    trueで、イージングしないで変更
     */
    public function setLongitude(value:Number, immediate:Boolean = false):void
    {
        if (immediate)
        {
            _angleLongitude = value;
        }
        _lastLongitude = value;
    }

    /**
     * 緯度を設定します。
     * @param value    0で、正面から中央方向(lookAtPosition)を見る
     * @param immediate    trueで、イージングしないで変更
     */
    public function setLatitude(value:Number, immediate:Boolean = false):void
    {
        if (immediate)
        {
            _angleLatitude = value;
        }
        _lastLatitude = value;
    }

    /**
     * Cameraから、targetObjectまでの距離を設定します。
     * @param value    Cameraから、targetObjectまでの距離
     * @param immediate trueで、イージングしないで変更
     */
    public function setDistance(value:Number, immediate:Boolean = false):void
    {
        if (immediate)
        {
            _length = value;
        }
        _lastLength = value;
    }

    /**
     * オブジェクトを使用不可にしてメモリを解放します。
     */
    public function dispose():void
    {
        // イベントの解放
        if (_mouseDownEventSource)
            _mouseDownEventSource.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);

        // マウスホイール操作
        if (_mouseDownEventSource)
            _mouseDownEventSource.removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);

        // キーボード操作
        if (_keyEventSource)
        {
            _keyEventSource.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            _keyEventSource.removeEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
        }

        // プロパティーの解放
        _easingSeparator = 0;
        maxDistance = 0;
        minDistance = 0;
        _mouseSensitivityX = 0;
        _mouseSensitivityY = 0;
        multiplyValue = 0;
        _needsRendering = false;
        _pitchSpeed = 0;
        useHandCursor = false;
        _yawSpeed = 0;
        _angleLatitude = 0;
        _angleLongitude = 0;
        _distanceSpeed = 0;
        _keyEventSource = null;
        _lastLatitude = 0;
        _lastLength = 0;
        _lastLongitude = 0;
        _lastLookAtX = 0;
        _lastLookAtY = 0;
        _lastLookAtZ = 0;
        _length = 0;
        _lookAtX = 0;
        _lookAtY = 0;
        _lookAtZ = 0;
        _mouseDownEventSource = null;
        _mouseMove = false;
        _mouseUpEventSource = null;
        _mouseX = 0;
        _mouseY = 0;
        _oldLatitude = 0;
        _oldLongitude = 0;
        _pitchDown = false;
        _pitchUp = false;
        _taregetDistanceValue = 0;
        _taregetPitchValue = 0;
        _taregetYawValue = 0;
        _target = null;
        _yawLeft = false;
        _yawRight = false;
        _zoomIn = false;
        _zoomOut = false;
    }

    protected function mouseDownHandler(event:Event):void
    {
        _oldLongitude = _lastLongitude;
        _oldLatitude = _lastLatitude;
        _mouseX = _mouseDownEventSource.mouseX;
        _mouseY = _mouseDownEventSource.mouseY;
        _mouseMove = true;

        if (useHandCursor)
            Mouse.cursor = MouseCursor.HAND;

        _mouseUpEventSource.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    }

    protected function mouseUpHandler(event:Event):void
    {
        if (useHandCursor)
            Mouse.cursor = MouseCursor.AUTO;

        _mouseMove = false;
        _mouseUpEventSource.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    }

    private function keyDownHandler(event:KeyboardEvent):void
    {
        for (var key:String in keyBindings)
        {
            if (String(event.keyCode) == key)
            {
                keyBindings[key](true)
            }
        }
    }

    private function keyUpHandler(event:KeyboardEvent = null):void
    {
        for (var key:String in keyBindings)
        {
            keyBindings[key](false)
        }
    }

    private function mouseWheelHandler(event:MouseEvent):void
    {
        _lastLength -= event.delta * 20;
        if (_lastLength < minDistance)
        {
            _lastLength = minDistance
        }
        else if (_lastLength > maxDistance)
        {
            _lastLength = maxDistance
        }
    }

    /**
     * 経度と緯度から位置を算出します。
     * @param latitude    緯度
     * @param longitude    経度
     * @param radius    半径
     * @return 位置情報
     */
    private function translateGeoCoords(latitude:Number, longitude:Number, radius:Number):Vector3D
    {
        const latitudeDegreeOffset:Number = 90;
        const longitudeDegreeOffset:Number = -90;

        latitude = Math.PI * latitude / 180;
        longitude = Math.PI * longitude / 180;

        latitude -= (latitudeDegreeOffset * (Math.PI / 180));
        longitude -= (longitudeDegreeOffset * (Math.PI / 180));

        var x:Number = radius * Math.sin(latitude) * Math.cos(longitude);
        var y:Number = radius * Math.cos(latitude);
        var z:Number = radius * Math.sin(latitude) * Math.sin(longitude);

        return new Vector3D(x, z, y);
    }
}

class AlternativaTemplate extends Sprite
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /**
     * 子オブジェクトを最適な方法でソートするコンテナ
     * (ConflictContainer)
     */
    public static const CONFLICT:String = 'conflict';
    /**
     * 子オブジェクトをBSP(バイナリ空間分割法)によってソートするコンテナ
     * (BSPContainer)
     */
    public static const BSP:String = 'bsp';

    /**
     * 子オブジェクトをカメラからのZ値でソートするコンテナ
     * (DistanceSortContainer)
     */
    public static const ZSORT:String = 'zsort';
    /**
     * KDツリー(http://ja.wikipedia.org/wiki/Kd%E6%9C%A8)によってソートするコンテナ
     * (KDContainer)
     */
    public static const KD:String = 'kd';
    /**
     * detalizationと子オブジェクトの距離でソートするコンテナ(詳細は調査中)
     * (LODContainer)
     */
    public static const LOD:String = 'lod';

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    /**
     * 新しい Alternativa3DTemplate インスタンスを作成します。
     * @param        mc
     * @param        containerType
     * @param        viewWidth
     * @param        viewHeight
     * @param        scaleToStage
     */
    public function AlternativaTemplate(mc:DisplayObjectContainer, containerType:String = CONFLICT, viewWidth:int = 640, viewHeight:int = 480, scaleToStage:Boolean = true)
    {
        _mc = mc
        _mc.addChild(this)

        _containerType = containerType
        _viewWidth = viewWidth;
        _viewHeight = viewHeight;
        _scaleToStage = scaleToStage;

        if (stage)
            init();
        else
            addEventListener(Event.ADDED_TO_STAGE, init);
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    /**
     * カメラインスタンスです。
     */
    public var camera:Camera3D;

    /**
     * カメラコントローラーです。
     */
    public var cameraController:SimpleObjectController

    /**
     * 3dオブジェクト格納するコンテナインスタンス。
     */
    public var container:Object3DContainer;

    //--------------------------------------
    // onPostRender 
    //--------------------------------------

    /**
     * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
     * レンダリング後に実行したい処理を記述します。
     */
    private var _onPostRender:Function = function():void{};

    public function get onPostRender():Function
    {
        return _onPostRender;
    }

    public function set onPostRender(value:Function):void
    {
        _onPostRender = value;
    }


    //--------------------------------------
    // onPreRender 
    //--------------------------------------

    /**
     * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
     * レンダリング前に実行したい処理をオーバーライドして記述します。
     */
    private var _onPreRender:Function = function():void{};

    public function get onPreRender():Function
    {
        return _onPreRender;
    }

    public function set onPreRender(value:Function):void
    {
        _onPreRender = value;
    }

    /**
     * ビューインスタンスです。
     */
    public var view:View
    private var _containerType:String

    private var _mc:DisplayObjectContainer
    private var _scaleToStage:Boolean;
    private var _viewHeight:int;
    private var _viewWidth:int;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * レンダリングを開始します。
     */
    public function startRendering():void
    {
        addEventListener(Event.ENTER_FRAME, onRenderTick);
    }

    /**
     * レンダリングを停止します。
     */
    public function stopRendering():void
    {
        removeEventListener(Event.ENTER_FRAME, onRenderTick);
    }

    /**
     * シングルレンダリング(レンダリングを一回だけ)を実行します。
     */
    public function singleRender():void
    {
        onRenderTick();
    }

    /**
     * 初期化されたときに実行されるイベントです。
     * 初期化時に実行したい処理をオーバーライドして記述します。
     */
    protected function atInit():void
    {
    }

    /**
     * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
     * レンダリング前に実行したい処理をオーバーライドして記述します。
     */
    protected function atPreRender():void
    {
    }

    /**
     * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
     * レンダリング後に実行したい処理をオーバーライドして記述します。
     */
    protected function atPostRender():void
    {
    }

    /**
     * @private
     */
    private function init(e:Event = null):void
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // entry point
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
        stage.quality = StageQuality.HIGH;

        //Root objectの作成
        if (_containerType == CONFLICT)
        {
            container = new ConflictContainer();
        }
        else if (_containerType == BSP)
        {
            container = new BSPContainer();
        }
        else if (_containerType == ZSORT)
        {
            container = new DistanceSortContainer();
        }
        else if (_containerType == KD)
        {
            container = new KDContainer();
        }
        else if (_containerType == LOD)
        {
            container = new LODContainer();
        }
        //Viewの作成
        view = new View(stage.stageWidth, stage.stageHeight);
        _mc.addChild(view);

        //cameraの作成
        camera = new Camera3D();
        camera.view = view;
        camera.x = 0;
        camera.y = -500;
        camera.z = 0;
        container.addChild(camera);

        // Camera controller
        cameraController = new SimpleObjectController(stage, camera, 10);
        cameraController.mouseSensitivity = 0
        cameraController.unbindAll()
        cameraController.lookAtXYZ(0, 0, 0)

        onResize();
        stage.addEventListener(Event.RESIZE, onResize);

        atInit();
    }

    /**
     * @private
     */
    private function onResize(e:Event = null):void
    {
        if (_scaleToStage)
        {
            view.width = stage.stageWidth;
            view.height = stage.stageHeight;
        }
        else
        {
            view.width = _viewWidth;
            view.height = _viewHeight;
        }
    }

    /**
     * @private
     */
    private function onRenderTick(e:Event = null):void
    {
        atPreRender();
        _onPreRender()
        cameraController.update();
        camera.render();
        atPostRender();
        _onPostRender();
    }
}