/*
   SoundManager 2: Javascript Sound for the Web
   ----------------------------------------------
   http://schillmania.com/projects/soundmanager2/

   Copyright (c) 2007, Scott Schiller. All rights reserved.
   Code licensed under the BSD License:
   http://www.schillmania.com/projects/soundmanager2/license.txt

   Flash 9 / ActionScript 3 version
*/

package {

  import flash.external.*;
  import flash.events.*;
  import flash.display.Sprite;
  import flash.display.StageDisplayState;
  import flash.display.StageScaleMode;
  import flash.display.StageAlign;
  import flash.geom.Rectangle;
  import flash.media.Sound;
  import flash.media.SoundChannel;
  import flash.media.SoundLoaderContext;
  import flash.media.SoundTransform;
  import flash.media.SoundMixer;
  import flash.media.Video;
  import flash.net.URLRequest;
  import flash.utils.ByteArray;
  import flash.net.NetConnection;
  import flash.net.NetStream;

  public class SoundManager2_SMSound_AS3 extends Sound {

    public var sm: SoundManager2_AS3 = null;
    // externalInterface references (for Javascript callbacks)
    public var baseJSController: String = "soundManager";
    public var baseJSObject: String = baseJSController + ".sounds";
    public var soundChannel: SoundChannel = new SoundChannel();
    public var urlRequest: URLRequest;
    public var soundLoaderContext: SoundLoaderContext;
    public var waveformData: ByteArray = new ByteArray();
    public var waveformDataArray: Array = [];
    public var eqData: ByteArray = new ByteArray();
    public var eqDataArray: Array = [];
    public var usePeakData: Boolean = false;
    public var useWaveformData: Boolean = false;
    public var useEQData: Boolean = false;
    public var sID: String;
    public var sURL: String;
    public var justBeforeFinishOffset: int;
    public var didJustBeforeFinish: Boolean;
    public var didFinish: Boolean;
    public var loaded: Boolean;
    public var connected: Boolean;
    public var failed: Boolean;
    public var paused: Boolean;
    public var duration: Number;
    public var totalBytes: Number;
    public var handledDataError: Boolean = false;
    public var ignoreDataError: Boolean = false;

    public var lastValues: Object = {
      bytes: 0,
      position: 0,
      volume: 100,
      pan: 0,
      nLoops: 1,
      leftPeak: 0,
      rightPeak: 0,
      waveformDataArray: null,
      eqDataArray: null,
      isBuffering: null
    };
    public var didLoad: Boolean = false;
    public var sound: Sound = new Sound();

    public var cc: Object;
    public var nc: NetConnection;
    public var ns: NetStream;
    public var st: SoundTransform;
    public var useNetstream: Boolean;
    public var useVideo: Boolean = false;
    public var bufferTime: Number = -1;
    public var lastNetStatus: String = null;
    public var serverUrl: String = null;

    public var oVideo: Video = null;
    public var videoWidth: Number = 0;
    public var videoHeight: Number = 0;

    public function SoundManager2_SMSound_AS3(oSoundManager: SoundManager2_AS3, sIDArg: String = null, sURLArg: String = null, usePeakData: Boolean = false, useWaveformData: Boolean = false, useEQData: Boolean = false, useNetstreamArg: Boolean = false, useVideo: Boolean = false, netStreamBufferTime: Number = -1, serverUrl: String = null, duration: Number = 0, totalBytes: Number = 0) {
      this.sm = oSoundManager;
      this.sID = sIDArg;
      this.sURL = sURLArg;
      this.usePeakData = usePeakData;
      this.useWaveformData = useWaveformData;
      this.useEQData = useEQData;
      this.urlRequest = new URLRequest(sURLArg);
      this.justBeforeFinishOffset = 0;
      this.didJustBeforeFinish = false;
      this.didFinish = false; // non-MP3 formats only
      this.loaded = false;
      this.connected = false;
      this.failed = false;
      this.soundChannel = null;
      this.lastNetStatus = null;
      this.useNetstream = useNetstreamArg;
      this.serverUrl = serverUrl;
      this.duration = duration;
      this.totalBytes = totalBytes;
      this.useVideo = useVideo;
      this.bufferTime = netStreamBufferTime;
      writeDebug('in SoundManager2_SMSound_AS3, got duration '+duration+' and totalBytes '+totalBytes);

      if (this.useNetstream) {
        this.cc = new Object();
        this.nc = new NetConnection();

        // Handle FMS bandwidth check callback.
        // @see onBWDone
        // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
        // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
        this.nc.client = this;

        // TODO: security/IO error handling
        // this.nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
        // this.nc.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
        nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);

        writeDebug('Got server URL: '+ this.serverUrl);
        if (this.serverUrl != null) {
          writeDebug('NetConnection: connecting to server ' + this.serverUrl + '...');
        }
        this.nc.connect(serverUrl);
      } else {
        this.connected = true;
      }
    }

    private function netStatusHandler(event:NetStatusEvent):void {
      switch (event.info.code) {
        case "NetConnection.Connect.Success":

          writeDebug('NetConnection: connected');
          try {
            this.ns = new NetStream(this.nc);
            this.ns.checkPolicyFile = true;
            // bufferTime reference: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#bufferTime
            if (this.bufferTime != -1) {
              this.ns.bufferTime = this.bufferTime; // set to 0.1 or higher. 0 is reported to cause playback issues with static files.
            }
            this.st = new SoundTransform();
            this.cc.onMetaData = this.metaDataHandler;
            this.ns.client = this.cc;
            this.ns.receiveAudio(true);
            if (this.useVideo) {
              this.oVideo = new Video();
              this.ns.receiveVideo(true);
              this.sm.stage.addEventListener(Event.RESIZE, this.resizeHandler);
              this.oVideo.smoothing = true; // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Video.html#smoothing
              this.oVideo.visible = false; // hide until metadata received
              this.sm.addChild(this.oVideo);
              this.oVideo.attachNetStream(this.ns);
              writeDebug('setting video w/h to stage: ' + this.sm.stage.stageWidth + 'x' + this.sm.stage.stageHeight);
              this.oVideo.width = this.sm.stage.stageWidth;
              this.oVideo.height = this.sm.stage.stageHeight;
            }
            this.connected = true;
            ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 1);
          } catch(e: Error) {
            this.failed = true;
            writeDebug('netStream error: ' + e.toString());
          }
          break;

        case "NetStream.Play.StreamNotFound":
          this.failed = true;
          writeDebug("NetConnection: Stream not found!");
          break;

        case "NetConnection.Connect.Closed":
          this.failed = true;
          writeDebug("NetConnection: Connection closed!");
          break;

        default:
          this.failed = true;
          writeDebug("NetConnection: got unhandled code '" + event.info.code + "'!");
          ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 0);
          break;
      }
    }

    public function resizeHandler(e: Event) : void {
      // scale video to stage dimensions
      // probably less performant than using native flash scaling, but that doesn't quite seem to work. I'm probably missing something simple.
      this.oVideo.width = this.sm.stage.stageWidth;
      this.oVideo.height = this.sm.stage.stageHeight;
    }

    public function writeDebug(s: String, bTimestamp: Boolean = false) : Boolean {
      return this.sm.writeDebug(s, bTimestamp); // defined in main SM object
    }

    public function doNetStatus(e: NetStatusEvent) : void {
      writeDebug('netStatusEvent: ' + e.info.code);
    }

    public function metaDataHandler(infoObject: Object) : void {
      /*
    var data:String = new String();
    for (var prop:* in infoObject) {
    data += prop+': '+infoObject[prop]+' ';
    }
    ExternalInterface.call('soundManager._writeDebug','Metadata: '+data);
    */
      if (this.oVideo) {
        // set dimensions accordingly
        if (!infoObject.width && !infoObject.height) {
          writeDebug('No width/height specified');
          infoObject.width = 0;
          infoObject.height = 0;
        }
        writeDebug('video dimensions: ' + infoObject.width + 'x' + infoObject.height + ' (w/h)');
        this.videoWidth = infoObject.width;
        this.videoHeight = infoObject.height;
        // implement a subset of metadata to pass over EI bridge
        // some formats have extra stuff, eg. "aacaot", "avcprofile"
        // http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00000267.html
        var oMeta: Object = new Object();
        var item: Object = null;
        for (item in infoObject) {
          // exclude seekpoints for now, presumed not useful and overly large.
          if (item != 'seekpoints') {
            oMeta[item] = infoObject[item];
          }
        }
        ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onmetadata", oMeta);
        writeDebug('showing video for ' + this.sID);
        this.oVideo.visible = true; // show ze video!
      }
      if (!this.loaded) {
        ExternalInterface.call(baseJSObject + "['" + this.sID + "']._whileloading", this.bytesLoaded, (this.bytesTotal || this.totalBytes), (infoObject.duration || this.duration));
      }
      this.duration = infoObject.duration * 1000;
      // null this out for the duration of this object's existence.
      // it may be called multiple times.
      this.cc.onMetaData = function (infoObject: Object) : void {}

    }

    public function getWaveformData() : void {
      // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
      SoundMixer.computeSpectrum(this.waveformData, false, 0); // sample wave data at 44.1 KHz
      this.waveformDataArray = [];
      for (var i: int = 0, j: int = this.waveformData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
        this.waveformDataArray.push(int(this.waveformData.readFloat() * 1000) / 1000);
      }
    }

    public function getEQData() : void {
      // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
      SoundMixer.computeSpectrum(this.eqData, true, 0); // sample EQ data at 44.1 KHz
      this.eqDataArray = [];
      for (var i: int = 0, j: int = this.eqData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
        this.eqDataArray.push(int(this.eqData.readFloat() * 1000) / 1000);
      }
    }

    public function start(nMsecOffset: int, nLoops: int) : void {
      writeDebug("Called start nMsecOffset "+ nMsecOffset+ ' nLoops '+nLoops);
      this.sm.currentObject = this; // reference for video, full-screen
      if (this.useNetstream) {
        writeDebug('start: seeking to ' + nMsecOffset);
        this.cc.onMetaData = this.metaDataHandler;
        this.ns.seek(nMsecOffset);
        if (this.paused) {
          this.ns.resume(); // get the sound going again
          if (!this.didLoad) this.didLoad = true;
        } else if (!this.didLoad) {
          this.ns.play(this.sURL);
          this.didLoad = true;
        }
        // this.ns.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      } else {
        this.soundChannel = this.play(nMsecOffset, nLoops);
        this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      }
    }

    private function _onfinish() : void {
      this.removeEventListener(Event.SOUND_COMPLETE, _onfinish);
    }

    public function loadSound(sURL: String, bStream: Boolean) : void {
      if (this.useNetstream) {
        if (this.didLoad != true) {
          ExternalInterface.call('loadSound(): loading ' + this.sURL);
          this.ns.play(this.sURL);
          this.didLoad = true;
        }
        // this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      } else {
        try {
          this.didLoad = true;
          this.urlRequest = new URLRequest(sURL);
          this.soundLoaderContext = new SoundLoaderContext(1000, true); // check for policy (crossdomain.xml) file on remote domains - http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundLoaderContext.html
          this.load(this.urlRequest, this.soundLoaderContext);
        } catch(e: Error) {
          writeDebug('error during loadSound(): ' + e.toString());
        }
      }
    }

    public function setVolume(nVolume: Number) : void {
      this.lastValues.volume = nVolume / 100;
      this.applyTransform();
    }

    public function setPan(nPan: Number) : void {
      this.lastValues.pan = nPan / 100;
      this.applyTransform();
    }

    public function applyTransform() : void {
      var st: SoundTransform = new SoundTransform(this.lastValues.volume, this.lastValues.pan);
      if (this.useNetstream) {
        this.ns.soundTransform = st;
      } else if (this.soundChannel) {
        this.soundChannel.soundTransform = st; // new SoundTransform(this.lastValues.volume, this.lastValues.pan);
      }
    }

    // Handle FMS bandwidth check callback.
    // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
    // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
    public function onBWDone():void{
      writeDebug('onBWDone: called and ignored');
    }

    // NetStream client callback.  Invoked when the song is complete
    public function onPlayStatus(info:Object):void {
      writeDebug('onPlayStatus called with '+info);
      switch(info.code) {
        case "NetStream.Play.Complete":
          writeDebug('Song has finished!');
          break;
      }
    }
  }
}

/*
   SoundManager 2: Javascript Sound for the Web
   ----------------------------------------------
   http://schillmania.com/projects/soundmanager2/

   Copyright (c) 2007, Scott Schiller. All rights reserved.
   Code licensed under the BSD License:
   http://www.schillmania.com/projects/soundmanager2/license.txt

   Flash 9 / ActionScript 3 version
*/

package {

  import flash.external.*;
  import flash.events.*;
  import flash.display.Sprite;
  import flash.display.StageDisplayState;
  import flash.display.StageScaleMode;
  import flash.display.StageAlign;
  import flash.geom.Rectangle;
  import flash.media.Sound;
  import flash.media.SoundChannel;
  import flash.media.SoundLoaderContext;
  import flash.media.SoundTransform;
  import flash.media.SoundMixer;
  import flash.media.Video;
  import flash.net.URLRequest;
  import flash.utils.ByteArray;
  import flash.net.NetConnection;
  import flash.net.NetStream;

  public class SoundManager2_SMSound_AS3 extends Sound {

    public var sm: SoundManager2_AS3 = null;
    // externalInterface references (for Javascript callbacks)
    public var baseJSController: String = "soundManager";
    public var baseJSObject: String = baseJSController + ".sounds";
    public var soundChannel: SoundChannel = new SoundChannel();
    public var urlRequest: URLRequest;
    public var soundLoaderContext: SoundLoaderContext;
    public var waveformData: ByteArray = new ByteArray();
    public var waveformDataArray: Array = [];
    public var eqData: ByteArray = new ByteArray();
    public var eqDataArray: Array = [];
    public var usePeakData: Boolean = false;
    public var useWaveformData: Boolean = false;
    public var useEQData: Boolean = false;
    public var sID: String;
    public var sURL: String;
    public var justBeforeFinishOffset: int;
    public var didJustBeforeFinish: Boolean;
    public var didFinish: Boolean;
    public var loaded: Boolean;
    public var connected: Boolean;
    public var failed: Boolean;
    public var paused: Boolean;
    public var duration: Number;
    public var totalBytes: Number;
    public var handledDataError: Boolean = false;
    public var ignoreDataError: Boolean = false;

    public var lastValues: Object = {
      bytes: 0,
      position: 0,
      volume: 100,
      pan: 0,
      nLoops: 1,
      leftPeak: 0,
      rightPeak: 0,
      waveformDataArray: null,
      eqDataArray: null,
      isBuffering: null
    };
    public var didLoad: Boolean = false;
    public var sound: Sound = new Sound();

    public var cc: Object;
    public var nc: NetConnection;
    public var ns: NetStream;
    public var st: SoundTransform;
    public var useNetstream: Boolean;
    public var useVideo: Boolean = false;
    public var bufferTime: Number = -1;
    public var lastNetStatus: String = null;
    public var serverUrl: String = null;

    public var oVideo: Video = null;
    public var videoWidth: Number = 0;
    public var videoHeight: Number = 0;

    public function SoundManager2_SMSound_AS3(oSoundManager: SoundManager2_AS3, sIDArg: String = null, sURLArg: String = null, usePeakData: Boolean = false, useWaveformData: Boolean = false, useEQData: Boolean = false, useNetstreamArg: Boolean = false, useVideo: Boolean = false, netStreamBufferTime: Number = -1, serverUrl: String = null, duration: Number = 0, totalBytes: Number = 0) {
      this.sm = oSoundManager;
      this.sID = sIDArg;
      this.sURL = sURLArg;
      this.usePeakData = usePeakData;
      this.useWaveformData = useWaveformData;
      this.useEQData = useEQData;
      this.urlRequest = new URLRequest(sURLArg);
      this.justBeforeFinishOffset = 0;
      this.didJustBeforeFinish = false;
      this.didFinish = false; // non-MP3 formats only
      this.loaded = false;
      this.connected = false;
      this.failed = false;
      this.soundChannel = null;
      this.lastNetStatus = null;
      this.useNetstream = useNetstreamArg;
      this.serverUrl = serverUrl;
      this.duration = duration;
      this.totalBytes = totalBytes;
      this.useVideo = useVideo;
      this.bufferTime = netStreamBufferTime;
      writeDebug('in SoundManager2_SMSound_AS3, got duration '+duration+' and totalBytes '+totalBytes);

      if (this.useNetstream) {
        this.cc = new Object();
        this.nc = new NetConnection();

        // Handle FMS bandwidth check callback.
        // @see onBWDone
        // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
        // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
        this.nc.client = this;

        // TODO: security/IO error handling
        // this.nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
        // this.nc.addEventListener(IOErrorEvent.IO_ERROR, doIOError);
        nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);

        writeDebug('Got server URL: '+ this.serverUrl);
        if (this.serverUrl != null) {
          writeDebug('NetConnection: connecting to server ' + this.serverUrl + '...');
        }
        this.nc.connect(serverUrl);
      } else {
        this.connected = true;
      }
    }

    private function netStatusHandler(event:NetStatusEvent):void {
      switch (event.info.code) {
        case "NetConnection.Connect.Success":

          writeDebug('NetConnection: connected');
          try {
            this.ns = new NetStream(this.nc);
            this.ns.checkPolicyFile = true;
            // bufferTime reference: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#bufferTime
            if (this.bufferTime != -1) {
              this.ns.bufferTime = this.bufferTime; // set to 0.1 or higher. 0 is reported to cause playback issues with static files.
            }
            this.st = new SoundTransform();
            this.cc.onMetaData = this.metaDataHandler;
            this.ns.client = this.cc;
            this.ns.receiveAudio(true);
            if (this.useVideo) {
              this.oVideo = new Video();
              this.ns.receiveVideo(true);
              this.sm.stage.addEventListener(Event.RESIZE, this.resizeHandler);
              this.oVideo.smoothing = true; // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Video.html#smoothing
              this.oVideo.visible = false; // hide until metadata received
              this.sm.addChild(this.oVideo);
              this.oVideo.attachNetStream(this.ns);
              writeDebug('setting video w/h to stage: ' + this.sm.stage.stageWidth + 'x' + this.sm.stage.stageHeight);
              this.oVideo.width = this.sm.stage.stageWidth;
              this.oVideo.height = this.sm.stage.stageHeight;
            }
            this.connected = true;
            ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 1);
          } catch(e: Error) {
            this.failed = true;
            writeDebug('netStream error: ' + e.toString());
          }
          break;

        case "NetStream.Play.StreamNotFound":
          this.failed = true;
          writeDebug("NetConnection: Stream not found!");
          break;

        case "NetConnection.Connect.Closed":
          this.failed = true;
          writeDebug("NetConnection: Connection closed!");
          break;

        default:
          this.failed = true;
          writeDebug("NetConnection: got unhandled code '" + event.info.code + "'!");
          ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 0);
          break;
      }
    }

    public function resizeHandler(e: Event) : void {
      // scale video to stage dimensions
      // probably less performant than using native flash scaling, but that doesn't quite seem to work. I'm probably missing something simple.
      this.oVideo.width = this.sm.stage.stageWidth;
      this.oVideo.height = this.sm.stage.stageHeight;
    }

    public function writeDebug(s: String, bTimestamp: Boolean = false) : Boolean {
      return this.sm.writeDebug(s, bTimestamp); // defined in main SM object
    }

    public function doNetStatus(e: NetStatusEvent) : void {
      writeDebug('netStatusEvent: ' + e.info.code);
    }

    public function metaDataHandler(infoObject: Object) : void {
      /*
    var data:String = new String();
    for (var prop:* in infoObject) {
    data += prop+': '+infoObject[prop]+' ';
    }
    ExternalInterface.call('soundManager._writeDebug','Metadata: '+data);
    */
      if (this.oVideo) {
        // set dimensions accordingly
        if (!infoObject.width && !infoObject.height) {
          writeDebug('No width/height specified');
          infoObject.width = 0;
          infoObject.height = 0;
        }
        writeDebug('video dimensions: ' + infoObject.width + 'x' + infoObject.height + ' (w/h)');
        this.videoWidth = infoObject.width;
        this.videoHeight = infoObject.height;
        // implement a subset of metadata to pass over EI bridge
        // some formats have extra stuff, eg. "aacaot", "avcprofile"
        // http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00000267.html
        var oMeta: Object = new Object();
        var item: Object = null;
        for (item in infoObject) {
          // exclude seekpoints for now, presumed not useful and overly large.
          if (item != 'seekpoints') {
            oMeta[item] = infoObject[item];
          }
        }
        ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onmetadata", oMeta);
        writeDebug('showing video for ' + this.sID);
        this.oVideo.visible = true; // show ze video!
      }
      if (!this.loaded) {
        ExternalInterface.call(baseJSObject + "['" + this.sID + "']._whileloading", this.bytesLoaded, (this.bytesTotal || this.totalBytes), (infoObject.duration || this.duration));
      }
      this.duration = infoObject.duration * 1000;
      // null this out for the duration of this object's existence.
      // it may be called multiple times.
      this.cc.onMetaData = function (infoObject: Object) : void {}

    }

    public function getWaveformData() : void {
      // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
      SoundMixer.computeSpectrum(this.waveformData, false, 0); // sample wave data at 44.1 KHz
      this.waveformDataArray = [];
      for (var i: int = 0, j: int = this.waveformData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
        this.waveformDataArray.push(int(this.waveformData.readFloat() * 1000) / 1000);
      }
    }

    public function getEQData() : void {
      // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
      SoundMixer.computeSpectrum(this.eqData, true, 0); // sample EQ data at 44.1 KHz
      this.eqDataArray = [];
      for (var i: int = 0, j: int = this.eqData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
        this.eqDataArray.push(int(this.eqData.readFloat() * 1000) / 1000);
      }
    }

    public function start(nMsecOffset: int, nLoops: int) : void {
      writeDebug("Called start nMsecOffset "+ nMsecOffset+ ' nLoops '+nLoops);
      this.sm.currentObject = this; // reference for video, full-screen
      if (this.useNetstream) {
        writeDebug('start: seeking to ' + nMsecOffset);
        this.cc.onMetaData = this.metaDataHandler;
        this.ns.seek(nMsecOffset);
        if (this.paused) {
          this.ns.resume(); // get the sound going again
          if (!this.didLoad) this.didLoad = true;
          this.paused = false;
        } else if (!this.didLoad) {
          this.ns.play(this.sURL);
          this.didLoad = true;
          this.paused = false;
        }
        // this.ns.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      } else {
        this.soundChannel = this.play(nMsecOffset, nLoops);
        this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      }
    }

    private function _onfinish() : void {
      this.removeEventListener(Event.SOUND_COMPLETE, _onfinish);
    }

    public function loadSound(sURL: String, bStream: Boolean) : void {
      if (this.useNetstream) {
        if (this.didLoad != true) {
          ExternalInterface.call('loadSound(): loading ' + this.sURL);
          this.ns.play(this.sURL);
          this.didLoad = true;
        }
        // this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
        this.applyTransform();
      } else {
        try {
          this.didLoad = true;
          this.urlRequest = new URLRequest(sURL);
          this.soundLoaderContext = new SoundLoaderContext(1000, true); // check for policy (crossdomain.xml) file on remote domains - http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundLoaderContext.html
          this.load(this.urlRequest, this.soundLoaderContext);
        } catch(e: Error) {
          writeDebug('error during loadSound(): ' + e.toString());
        }
      }
    }

    public function setVolume(nVolume: Number) : void {
      this.lastValues.volume = nVolume / 100;
      this.applyTransform();
    }

    public function setPan(nPan: Number) : void {
      this.lastValues.pan = nPan / 100;
      this.applyTransform();
    }

    public function applyTransform() : void {
      var st: SoundTransform = new SoundTransform(this.lastValues.volume, this.lastValues.pan);
      if (this.useNetstream) {
        this.ns.soundTransform = st;
      } else if (this.soundChannel) {
        this.soundChannel.soundTransform = st; // new SoundTransform(this.lastValues.volume, this.lastValues.pan);
      }
    }

    // Handle FMS bandwidth check callback.
    // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
    // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
    public function onBWDone():void{
      writeDebug('onBWDone: called and ignored');
    }

    // NetStream client callback.  Invoked when the song is complete
    public function onPlayStatus(info:Object):void {
      writeDebug('onPlayStatus called with '+info);
      switch(info.code) {
        case "NetStream.Play.Complete":
          writeDebug('Song has finished!');
          break;
      }
    }
  }
}

Fix pause issue. When track is played and then paused, paused state was not reset.


package com.dotfold.dotvimstat.view.summary
{
	import asx.string.empty;

	import com.dotfold.dotvimstat.net.service.VimeoBasicService;
	import com.dotfold.dotvimstat.view.IView;

	import modena.core.Element;
	import modena.core.ElementContent;
	import modena.ui.Label;

	/**
	 * VideosSummary view shows the count of videos retrieved from the Vimeo API.
	 *
	 * @author jamesmcnamee
	 *
	 */
	public class VideosSummary extends Element implements IView
	{
		public static const ELEMENT_NAME:String = "videosSummary";

		private var _label:Label;

		/**
		 * Constructor.
		 */
		public function VideosSummary(styleClass:String = null)
		{
			super(styleClass);
		}

		/**
		 * @inheritDoc
		 */
		override protected function createElementContent():ElementContent
		{
			var content:ElementContent = super.createElementContent();

			_label = new Label("summaryCount");
			content.addChild(_label);

			return content;
		}

		/**
		 * Update the label with count of videos.
		 */
		public function set videosCount(value:int):void
		{
			_label.text = value.toString();
			invalidateRender();
		}

		override public function validateRender():void
		{
			super.validateRender();

			_label.x = (width - _label.width) / 2;
			_label.y = (height - _label.height) / 2;
		}

	}
}

package com.dotfold.dotvimstat.view.summary
{
	import asx.string.empty;

	import com.dotfold.dotvimstat.net.service.VimeoBasicService;
	import com.dotfold.dotvimstat.view.IView;
	import com.greensock.TweenLite;

	import modena.core.Element;
	import modena.core.ElementContent;
	import modena.ui.Label;

	/**
	 * VideosSummary view shows the count of videos retrieved from the Vimeo API.
	 *
	 * @author jamesmcnamee
	 *
	 */
	public class VideosSummary extends Element implements IView
	{
		public static const ELEMENT_NAME:String = "videosSummary";

		private var _label:Label;

		/**
		 * Constructor.
		 */
		public function VideosSummary(styleClass:String = null)
		{
			super(styleClass);
		}

		/**
		 * @inheritDoc
		 */
		override protected function createElementContent():ElementContent
		{
			var content:ElementContent = super.createElementContent();

			_label = new Label("summaryCount");
			content.addChild(_label);
			_label.alpha = 0;

			return content;
		}

		/**
		 * Update the label with count of videos.
		 */
		public function set videosCount(value:int):void
		{
			_label.text = value.toString();

			TweenLite.to(_label, 0.4, { alpha: 1, ease: 'easeInQuad' });
			invalidateRender();
		}

		override public function validateRender():void
		{
			super.validateRender();

			_label.x = (width - _label.width) / 2;
			_label.y = (height - _label.height) / 2;
		}

	}
}

add animation to VideosSummary

[feat] add animation to VideosSummary


package aerys.minko.render.shader.compiler
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import avmplus.getQualifiedClassName;

	import flash.geom.Point;
	import flash.geom.Vector3D;

	/**
	 * The Serializer class provides a set of static methods to serialize
	 * CPU values into Vector.<Number> than can be used as shader constants.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Serializer
	{
		public function serializeKnownLength(data	: Object,
											 target	: Vector.<Number>,
											 offset	: uint,
											 size	: uint) : void
		{
			if (data is Number)
			{
				var intData : Number = Number(data);

				if (size == 1)
				{
					target[offset] = intData;
				}
				else if (size == 2)
				{
					target[offset] = ((intData & 0xFFFF0000) >>> 16) / Number(0xffff);
					target[int(offset + 2)] = (intData & 0x0000FFFF) / Number(0xffff);
				}
				else if (size == 3)
				{
					target[offset] = ((intData & 0xFF0000) >>> 16) / 255.;
					target[int(offset + 1)] = ((intData & 0x00FF00) >>> 8) / 255.;
					target[int(offset + 2)] = (intData & 0x0000FF) / 255.;
				}
				else if (size == 4)
				{
					target[offset] = ((intData & 0xFF000000) >>> 24) / 255.;
					target[int(offset + 1)] = ((intData & 0x00FF0000) >>> 16) / 255.;
					target[int(offset + 2)] = ((intData & 0x0000FF00) >>> 8) / 255.;
					target[int(offset + 3)] = ((intData & 0x000000FF)) / 255.;
				}
			}
			else if (data is Vector4)
			{
				var vectorData : Vector3D = Vector4(data).minko_math::_vector;

				target[offset] = vectorData.x;
				size >= 2 && (target[int(offset + 1)] = vectorData.y);
				size >= 3 && (target[int(offset + 2)] = vectorData.z);
				size >= 4 && (target[int(offset + 3)] = vectorData.w);
			}
			else if (data is Matrix4x4)
			{
				Matrix4x4(data).getRawData(target, offset, true);
			}
			else if (data is Vector.<Number>)
			{
				var vectorNumberData		: Vector.<Number>	= Vector.<Number>(data);
				var vectorNumberDatalength	: uint				= vectorNumberData.length;

				for (var k : int = 0; k < size && k < vectorNumberDatalength; ++k)
					target[offset + k] = vectorNumberData[k];
			}
			else if (data is Vector.<Vector4>)
			{
				var vectorVectorData		: Vector.<Vector4>	= Vector.<Vector4>(data);
				var vectorVectorDataLength	: uint				= vectorVectorData.length;

				for (var j : uint = 0; j < vectorVectorDataLength; ++j)
				{
					vectorData = vectorVectorData[j].minko_math::_vector;

					target[offset + 4 * j] = vectorData.x;
					target[int(offset + 4 * j + 1)] = vectorData.y;
					target[int(offset + 4 * j + 2)] = vectorData.z;
					target[int(offset + 4 * j + 3)] = vectorData.w;
				}
			}
			else if (data is Vector.<Matrix4x4>)
			{
				var matrixVectorData		: Vector.<Matrix4x4>	= Vector.<Matrix4x4>(data);
				var matrixVectorDataLength	: uint					= matrixVectorData.length;

				for (var i : uint = 0; i < matrixVectorDataLength; ++i)
					matrixVectorData[i].getRawData(target, offset + i * 16, true);
			}
			else if (data == null)
			{
				throw new Error('You cannot serialize a null value.');
			}
			else
			{
				throw new Error('Unsupported type:' + getQualifiedClassName(data));
			}
		}

		public function serializeUnknownLength(data		: Object,
											   target	: Vector.<Number>,
											   offset	: uint) : void
		{
			if (data is Number)
			{
				target[offset] = Number(data);
			}
			else if (data is Point)
			{
				var pointData : Point = Point(data);

				target[offset]			= pointData.x;
				target[int(offset + 1)]	= pointData.y;
			}
			else if (data is Vector4)
			{
				var vectorData : Vector3D = Vector4(data).minko_math::_vector;

				target[offset]			= vectorData.x;
				target[int(offset + 1)]	= vectorData.y;
				target[int(offset + 2)]	= vectorData.z;
				target[int(offset + 3)]	= vectorData.w;
			}
			else if (data is Matrix4x4)
			{
				Matrix4x4(data).getRawData(target, offset, true);
			}
			else if (data is Array || data is Vector.<Number> || data is Vector.<Vector4> || data is Vector.<Matrix4x4>)
			{
				for each (var datum : Object in data)
				{
					serializeUnknownLength(datum, target, offset);
					offset = target.length;
				}
			}
			else if (data == null)
			{
				throw new Error('You cannot serialize a null value.');
			}
			else
			{
				throw new Error('Unsupported type:' + getQualifiedClassName(data));
			}
		}
	}
}

package aerys.minko.render.shader.compiler
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.geom.Point;
	import flash.geom.Vector3D;
	import flash.utils.getQualifiedClassName;

	/**
	 * The Serializer class provides a set of static methods to serialize
	 * CPU values into Vector.<Number> than can be used as shader constants.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Serializer
	{
		public function serializeKnownLength(data	: Object,
											 target	: Vector.<Number>,
											 offset	: uint,
											 size	: uint) : void
		{
			if (data is Number)
			{
				var intData : Number = Number(data);

				if (size == 1)
				{
					target[offset] = intData;
				}
				else if (size == 2)
				{
					target[offset] = ((intData & 0xFFFF0000) >>> 16) / Number(0xffff);
					target[int(offset + 2)] = (intData & 0x0000FFFF) / Number(0xffff);
				}
				else if (size == 3)
				{
					target[offset] = ((intData & 0xFF0000) >>> 16) / 255.;
					target[int(offset + 1)] = ((intData & 0x00FF00) >>> 8) / 255.;
					target[int(offset + 2)] = (intData & 0x0000FF) / 255.;
				}
				else if (size == 4)
				{
					target[offset] = ((intData & 0xFF000000) >>> 24) / 255.;
					target[int(offset + 1)] = ((intData & 0x00FF0000) >>> 16) / 255.;
					target[int(offset + 2)] = ((intData & 0x0000FF00) >>> 8) / 255.;
					target[int(offset + 3)] = ((intData & 0x000000FF)) / 255.;
				}
			}
			else if (data is Vector4)
			{
				var vectorData : Vector3D = Vector4(data).minko_math::_vector;

				target[offset] = vectorData.x;
				size >= 2 && (target[int(offset + 1)] = vectorData.y);
				size >= 3 && (target[int(offset + 2)] = vectorData.z);
				size >= 4 && (target[int(offset + 3)] = vectorData.w);
			}
			else if (data is Matrix4x4)
			{
				Matrix4x4(data).getRawData(target, offset, true);
			}
			else if (data is Vector.<Number>)
			{
				var vectorNumberData		: Vector.<Number>	= Vector.<Number>(data);
				var vectorNumberDatalength	: uint				= vectorNumberData.length;

				for (var k : int = 0; k < size && k < vectorNumberDatalength; ++k)
					target[offset + k] = vectorNumberData[k];
			}
			else if (data is Vector.<Vector4>)
			{
				var vectorVectorData		: Vector.<Vector4>	= Vector.<Vector4>(data);
				var vectorVectorDataLength	: uint				= vectorVectorData.length;

				for (var j : uint = 0; j < vectorVectorDataLength; ++j)
				{
					vectorData = vectorVectorData[j].minko_math::_vector;

					target[offset + 4 * j] = vectorData.x;
					target[int(offset + 4 * j + 1)] = vectorData.y;
					target[int(offset + 4 * j + 2)] = vectorData.z;
					target[int(offset + 4 * j + 3)] = vectorData.w;
				}
			}
			else if (data is Vector.<Matrix4x4>)
			{
				var matrixVectorData		: Vector.<Matrix4x4>	= Vector.<Matrix4x4>(data);
				var matrixVectorDataLength	: uint					= matrixVectorData.length;

				for (var i : uint = 0; i < matrixVectorDataLength; ++i)
					matrixVectorData[i].getRawData(target, offset + i * 16, true);
			}
			else if (data == null)
			{
				throw new Error('You cannot serialize a null value.');
			}
			else
			{
				throw new Error('Unsupported type:' + getQualifiedClassName(data);
			}
		}

		public function serializeUnknownLength(data		: Object,
											   target	: Vector.<Number>,
											   offset	: uint) : void
		{
			if (data is Number)
			{
				target[offset] = Number(data);
			}
			else if (data is Point)
			{
				var pointData : Point = Point(data);

				target[offset]			= pointData.x;
				target[int(offset + 1)]	= pointData.y;
			}
			else if (data is Vector4)
			{
				var vectorData : Vector3D = Vector4(data).minko_math::_vector;

				target[offset]			= vectorData.x;
				target[int(offset + 1)]	= vectorData.y;
				target[int(offset + 2)]	= vectorData.z;
				target[int(offset + 3)]	= vectorData.w;
			}
			else if (data is Matrix4x4)
			{
				Matrix4x4(data).getRawData(target, offset, true);
			}
			else if (data is Array || data is Vector.<Number> || data is Vector.<Vector4> || data is Vector.<Matrix4x4>)
			{
				for each (var datum : Object in data)
				{
					serializeUnknownLength(datum, target, offset);
					offset = target.length;
				}
			}
			else if (data == null)
			{
				throw new Error('You cannot serialize a null value.');
			}
			else
			{
				throw new Error('Unsupported type:' + getQualifiedClassName(data));
			}
		}
	}
}

fix wrong import for getQualifiedClassName()


//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.filesystem.File;
import flash.utils.ByteArray;

import deng.fzip.FZip;
import deng.fzip.FZipFile;

import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.VisibleFuture;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import com.threerings.util.F;
import com.threerings.util.Log;

public class FlaLoader
{
    public function load (name :String, file :File) :Future {
        log.info("Loading fla", "path", file.nativePath, "name", name);

        const future :VisibleFuture = new VisibleFuture();
        _library = new XflLibrary(name);
        _loader.terminated.add(function (..._) :void {
            _library.finishLoading();
            future.succeed(_library);
        });

        var loadSWF :Future = _library.loadSWF(file.nativePath + ".swf");
        loadSWF.succeeded.add(function () :void {
            // Since listLibrary shuts down the executor, wait for the swf to load first
            listLibrary(file);
        });
        loadSWF.failed.add(F.adapt(_loader.shutdown));

        return future;
    }

    protected function listLibrary (file :File) :void {
        const loadZip :Future = Files.load(file, _loader);
        loadZip.succeeded.add(function (data :ByteArray) :void {
            const zip :FZip = new FZip();
            zip.loadBytes(data);

            const domFile :FZipFile = zip.getFileByName("DOMDocument.xml");
            const symbolPaths :Vector.<String> = _library.parseDocumentFile(
                domFile.content, domFile.filename);
            for each (var path :String in symbolPaths) {
                var symbolFile :FZipFile = zip.getFileByName(path);
                _library.parseLibraryFile(symbolFile.content, path);
            }
            _loader.shutdown();
        });
        loadZip.failed.add(function (error :Object) :void {
            _library.addTopLevelError(ParseError.CRIT, "Unable to read " + file.nativePath, error);
            _loader.shutdown();
        });
    }

    protected const _loader :Executor = new Executor();

    protected var _library :XflLibrary;

    private static const log :Log = Log.getLog(FlaLoader);
}
}

//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.filesystem.File;
import flash.utils.ByteArray;

import deng.fzip.FZip;
import deng.fzip.FZipFile;

import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.VisibleFuture;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import com.threerings.util.F;
import com.threerings.util.Log;

public class FlaLoader
{
    public function load (name :String, file :File) :Future {
        log.info("Loading fla", "path", file.nativePath, "name", name);

        const future :VisibleFuture = new VisibleFuture();
        _library = new XflLibrary(name);
        _loader.terminated.add(function (..._) :void {
            _library.finishLoading();
            future.succeed(_library);
        });

        var loadSWF :Future = _library.loadSWF(Files.replaceExtension(file, "swf"));
        loadSWF.succeeded.add(function () :void {
            // Since listLibrary shuts down the executor, wait for the swf to load first
            listLibrary(file);
        });
        loadSWF.failed.add(F.adapt(_loader.shutdown));

        return future;
    }

    protected function listLibrary (file :File) :void {
        const loadZip :Future = Files.load(file, _loader);
        loadZip.succeeded.add(function (data :ByteArray) :void {
            const zip :FZip = new FZip();
            zip.loadBytes(data);

            const domFile :FZipFile = zip.getFileByName("DOMDocument.xml");
            const symbolPaths :Vector.<String> = _library.parseDocumentFile(
                domFile.content, domFile.filename);
            for each (var path :String in symbolPaths) {
                var symbolFile :FZipFile = zip.getFileByName(path);
                _library.parseLibraryFile(symbolFile.content, path);
            }
            _loader.shutdown();
        });
        loadZip.failed.add(function (error :Object) :void {
            _library.addTopLevelError(ParseError.CRIT, "Unable to read " + file.nativePath, error);
            _loader.shutdown();
        });
    }

    protected const _loader :Executor = new Executor();

    protected var _library :XflLibrary;

    private static const log :Log = Log.getLog(FlaLoader);
}
}

load "MyFla.swf", not "MyFla.fla.swf"

FlaLoader: load "MyFla.swf", not "MyFla.fla.swf"


Scene@ testScene;
Camera@ camera;
Node@ cameraNode;
Node@ vehicleHullNode;

float yaw = 0.0;
float pitch = 0.0;
int drawDebug = 0;

void Start()
{
    if (!engine.headless)
    {
        InitConsole();
        InitUI();
    }
    else
        OpenConsoleWindow();

    InitScene();
    InitVehicle();

    SubscribeToEvent("Update", "HandleUpdate");
    SubscribeToEvent("PostUpdate", "HandlePostUpdate");
    SubscribeToEvent("KeyDown", "HandleKeyDown");
    SubscribeToEvent("MouseMove", "HandleMouseMove");
    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
}

void InitConsole()
{
    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");

    engine.CreateDebugHud();
    debugHud.defaultStyle = uiStyle;
    debugHud.mode = DEBUGHUD_SHOW_ALL;

    engine.CreateConsole();
    console.defaultStyle = uiStyle;
}

void InitUI()
{
    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");

    Cursor@ newCursor = Cursor("Cursor");
    newCursor.SetStyleAuto(uiStyle);
    newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
    ui.cursor = newCursor;
    ui.cursor.visible = false;
}

void InitScene()
{
    testScene = Scene("TestScene");

    // Enable access to this script file & scene from the console
    script.defaultScene = testScene;
    script.defaultScriptFile = scriptFile;

    // Create the camera outside the scene so it is unaffected by scene load/save
    cameraNode = Node();
    camera = cameraNode.CreateComponent("Camera");
    camera.farClip = 1000;
    camera.nearClip = 0.5;
    cameraNode.position = Vector3(0, 20, 0);

    if (!engine.headless)
    {
        renderer.viewports[0] = Viewport(testScene, camera);
        audio.listener = cameraNode.CreateComponent("SoundListener");
    }

    PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
    testScene.CreateComponent("Octree");
    testScene.CreateComponent("DebugRenderer");

    Node@ zoneNode = testScene.CreateChild("Zone");
    Zone@ zone = zoneNode.CreateComponent("Zone");
    zone.ambientColor = Color(0.15, 0.15, 0.15);
    zone.fogColor = Color(0.5, 0.5, 0.7);
    zone.fogStart = 500.0;
    zone.fogEnd = 1000.0;
    zone.boundingBox = BoundingBox(-2000, 2000);

    {
        Node@ lightNode = testScene.CreateChild("GlobalLight");
        lightNode.direction = Vector3(0.5, -0.5, 0.5);

        Light@ light = lightNode.CreateComponent("Light");
        light.lightType = LIGHT_DIRECTIONAL;
        light.castShadows = true;
        light.shadowBias = BiasParameters(0.0001, 0.5);
        light.shadowCascade = CascadeParameters(15.0, 50.0, 200.0, 0.0, 0.8);
        light.specularIntensity = 0.5;
    }

    Terrain@ terrain;

    {
        Node@ terrainNode = testScene.CreateChild("Terrain");
        terrainNode.position = Vector3(0, 0, 0);
        terrain = terrainNode.CreateComponent("Terrain");
        terrain.patchSize = 64;
        terrain.spacing = Vector3(2, 0.1, 2);
        terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
        terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
        terrain.occluder = true;

        RigidBody@ body = terrainNode.CreateComponent("RigidBody");
        body.collisionLayer = 2;
        CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
        shape.SetTerrain();
        shape.margin = 0.01;
    }

    for (uint i = 0; i < 1000; ++i)
    {
        Node@ objectNode = testScene.CreateChild("Mushroom");
        Vector3 position(Random() * 2000 - 1000, 0, Random() * 2000 - 1000);
        position.y = terrain.GetHeight(position) - 0.1;

        objectNode.position = position;
        objectNode.rotation = Quaternion(Vector3(0, 1, 0), terrain.GetNormal(position));
        objectNode.SetScale(3);

        StaticModel@ object = objectNode.CreateComponent("StaticModel");
        object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
        object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
        object.castShadows = true;

        RigidBody@ body = objectNode.CreateComponent("RigidBody");
        body.collisionLayer = 2;
        CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
        shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
    }
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    float timeStep = eventData["TimeStep"].GetFloat();

    // Nothing to do for now
}

void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
{
    float timeStep = eventData["TimeStep"].GetFloat();

    // Physics update has completed. Position camera behind vehicle
    Quaternion dir(vehicleHullNode.rotation.yaw, Vector3(0, 1, 0));;
    dir = dir * Quaternion(yaw, Vector3(0, 1, 0));
    dir = dir * Quaternion(pitch, Vector3(1, 0, 0));

    Vector3 cameraTargetPos = vehicleHullNode.position - dir * Vector3(0, 0, 10);
    Vector3 cameraStartPos = vehicleHullNode.position;

    // Raycast camera against static objects (physics collision mask 2)
    // and move it closer to the vehicle if something in between
    Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
    float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
    PhysicsRaycastResult result = scene.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
    if (result.body !is null)
        cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);

    cameraNode.position = cameraTargetPos;
    cameraNode.rotation = dir;
}

void HandleKeyDown(StringHash eventType, VariantMap& eventData)
{
    int key = eventData["Key"].GetInt();

    if (key == KEY_ESC)
    {
        if (ui.focusElement is null)
            engine.Exit();
        else
            console.visible = false;
    }

    if (key == KEY_F1)
        console.Toggle();

    if (ui.focusElement is null)
    {
        if (key == '1')
        {
            int quality = renderer.textureQuality;
            ++quality;
            if (quality > 2)
                quality = 0;
            renderer.textureQuality = quality;
        }

        if (key == '2')
        {
            int quality = renderer.materialQuality;
            ++quality;
            if (quality > 2)
                quality = 0;
            renderer.materialQuality = quality;
        }

        if (key == '3')
            renderer.specularLighting = !renderer.specularLighting;

        if (key == '4')
            renderer.drawShadows = !renderer.drawShadows;

        if (key == '5')
        {
            int size = renderer.shadowMapSize;
            size *= 2;
            if (size > 2048)
                size = 512;
            renderer.shadowMapSize = size;
        }

        if (key == '6')
            renderer.shadowQuality = renderer.shadowQuality + 1;

        if (key == '7')
        {
            bool occlusion = renderer.maxOccluderTriangles > 0;
            occlusion = !occlusion;
            renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
        }

        if (key == '8')
            renderer.dynamicInstancing = !renderer.dynamicInstancing;

        if (key == ' ')
        {
            drawDebug++;
            if (drawDebug > 2)
                drawDebug = 0;
        }

        if (key == 'T')
            debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
    }
}

void HandleMouseMove(StringHash eventType, VariantMap& eventData)
{
    int mousedx = eventData["DX"].GetInt();
    int mousedy = eventData["DY"].GetInt();
    yaw += mousedx / 10.0;
    pitch += mousedy / 10.0;
    if (pitch < 0.0)
        pitch = 0.0;
    if (pitch > 60.0)
        pitch = 60.0;
}

void HandlePostRenderUpdate()
{
    if (engine.headless)
        return;

    // Draw rendering debug geometry without depth test to see the effect of occlusion
    if (drawDebug == 1)
        renderer.DrawDebugGeometry(false);
    if (drawDebug == 2)
        testScene.physicsWorld.DrawDebugGeometry(true);
}

void InitVehicle()
{
    vehicleHullNode = testScene.CreateChild("VehicleHull");
    StaticModel@ hullObject = vehicleHullNode.CreateComponent("StaticModel");
    RigidBody@ hullBody = vehicleHullNode.CreateComponent("RigidBody");
    CollisionShape@ hullShape = vehicleHullNode.CreateComponent("CollisionShape");

    vehicleHullNode.position = Vector3(0, 5, 0);
    vehicleHullNode.scale = Vector3(1.5, 1, 3);
    hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
    hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
    hullObject.castShadows = true;
    hullShape.SetBox(Vector3(1, 1, 1));
    hullBody.mass = 3;
    hullBody.linearDamping = 0.2; // Some air resistance
    hullBody.collisionLayer = 1;

    Node@ fl = InitVehicleWheel("FrontLeft", vehicleHullNode, Vector3(-0.6, -0.4, 0.3));
    Node@ fr = InitVehicleWheel("FrontRight", vehicleHullNode, Vector3(0.6, -0.4, 0.3));
    Node@ rr = InitVehicleWheel("RearLeft", vehicleHullNode, Vector3(-0.6, -0.4, -0.3));
    Node@ rl = InitVehicleWheel("RearRight", vehicleHullNode, Vector3(0.6, -0.4, -0.3));

    Vehicle@ vehicle = cast<Vehicle>(vehicleHullNode.CreateScriptObject(scriptFile, "Vehicle"));
    vehicle.SetWheels(fl, fr, rl, rr);
}

Node@ InitVehicleWheel(String name, Node@ vehicleHullNode, Vector3 offset)
{
    Node@ wheelNode = testScene.CreateChild(name);
    wheelNode.position = vehicleHullNode.LocalToWorld(offset);
    wheelNode.rotation = vehicleHullNode.worldRotation * (offset.x >= 0.0 ? Quaternion(0, 0, -90) : Quaternion(0, 0, 90));
    wheelNode.scale = Vector3(0.75, 0.5, 0.75);

    StaticModel@ wheelObject = wheelNode.CreateComponent("StaticModel");
    RigidBody@ wheelBody = wheelNode.CreateComponent("RigidBody");
    CollisionShape@ wheelShape = wheelNode.CreateComponent("CollisionShape");
    Constraint@ wheelConstraint = wheelNode.CreateComponent("Constraint");

    wheelObject.model = cache.GetResource("Model", "Models/Cylinder.mdl");
    wheelObject.material = cache.GetResource("Material", "Materials/Stone.xml");
    wheelObject.castShadows = true;
    wheelShape.SetCylinder(1, 1);
    wheelBody.friction = 1;
    wheelBody.mass = 1;
    wheelBody.linearDamping = 0.2; // Some air resistance
    wheelBody.angularDamping = 0.75; // Current version of Bullet used by Urho doesn't have rolling friction, so mimic that with
                                    // some angular damping on the wheels
    wheelBody.collisionLayer = 1;
    wheelConstraint.constraintType = CONSTRAINT_HINGE;
    wheelConstraint.otherBody = vehicleHullNode.GetComponent("RigidBody");
    wheelConstraint.worldPosition = wheelNode.worldPosition; // Set constraint's both ends at wheel's location
    wheelConstraint.axis = Vector3(0, 1, 0); // Wheel rotates around its local Y-axis
    wheelConstraint.otherAxis = offset.x >= 0.0 ? Vector3(1, 0, 0) : Vector3(-1, 0, 0); // Wheel's hull axis points either left or right
    wheelConstraint.lowLimit = Vector2(-180, 0); // Let the wheel rotate freely around the axis
    wheelConstraint.highLimit = Vector2(180, 0);
    wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull

    return wheelNode;
}

class Vehicle : ScriptObject
{
    Node@ frontLeft;
    Node@ frontRight;
    Node@ rearLeft;
    Node@ rearRight;
    Constraint@ frontLeftAxis;
    Constraint@ frontRightAxis;
    RigidBody@ hullBody;
    RigidBody@ frontLeftBody;
    RigidBody@ frontRightBody;
    RigidBody@ rearLeftBody;
    RigidBody@ rearRightBody;

    float steering = 0.0;
    float enginePower = 8.0;
    float downForce = 10.0;
    float maxWheelAngle = 22.5;

    void SetWheels(Node@ fl, Node@ fr, Node@ rl, Node@ rr)
    {
        frontLeft = fl;
        frontRight = fr;
        rearLeft = rl;
        rearRight = rr;

        hullBody = node.GetComponent("RigidBody");
        frontLeftAxis = frontLeft.GetComponent("Constraint");
        frontRightAxis = frontRight.GetComponent("Constraint");
        frontLeftBody = frontLeft.GetComponent("RigidBody");
        frontRightBody = frontRight.GetComponent("RigidBody");
        rearLeftBody = rearLeft.GetComponent("RigidBody");
        rearRightBody = rearRight.GetComponent("RigidBody");
    }

    void FixedUpdate(float timeStep)
    {
        float newSteering = 0.0;
        float accelerator = 0.0;

        if (ui.focusElement is null)
        {
            if (input.keyDown['A'])
                newSteering = -1.0f;
            if (input.keyDown['D'])
                newSteering = 1.0f;
            if (input.keyDown['W'])
                accelerator = 1.0f;
            if (input.keyDown['S'])
                accelerator = -0.5f;
        }

        // When steering, wake up the wheel rigidbodies so that their orientation is updated
        if (newSteering != 0.0)
        {
            frontLeftBody.Activate();
            frontRightBody.Activate();
            steering = steering * 0.95 + newSteering * 0.05;
        }
        else
            steering = steering * 0.8 + newSteering * 0.2;

        Quaternion steeringRot(0, steering * maxWheelAngle, 0);

        frontLeftAxis.otherAxis = steeringRot * Vector3(-1, 0, 0);
        frontRightAxis.otherAxis = steeringRot * Vector3(1, 0, 0);

        if (accelerator != 0.0)
        {
            // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
            Vector3 torqueVec = Vector3(enginePower * accelerator, 0, 0);

            frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
            frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
            rearLeftBody.ApplyTorque(node.rotation * torqueVec);
            rearRightBody.ApplyTorque(node.rotation * torqueVec);
        }

        // Apply downforce proportional to velocity
        Vector3 localVelocity = node.worldRotation.Inverse() * hullBody.linearVelocity;
        hullBody.ApplyForce(Vector3(0, -1, 0) * Abs(localVelocity.z) * downForce);
    }
}

Scene@ testScene;
Camera@ camera;
Node@ cameraNode;
Node@ vehicleHullNode;

float yaw = 0.0;
float pitch = 0.0;
int drawDebug = 0;

void Start()
{
    if (!engine.headless)
    {
        InitConsole();
        InitUI();
    }
    else
        OpenConsoleWindow();

    InitScene();
    InitVehicle();

    SubscribeToEvent("Update", "HandleUpdate");
    SubscribeToEvent("PostUpdate", "HandlePostUpdate");
    SubscribeToEvent("KeyDown", "HandleKeyDown");
    SubscribeToEvent("MouseMove", "HandleMouseMove");
    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
}

void InitConsole()
{
    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");

    engine.CreateDebugHud();
    debugHud.defaultStyle = uiStyle;
    debugHud.mode = DEBUGHUD_SHOW_ALL;

    engine.CreateConsole();
    console.defaultStyle = uiStyle;
}

void InitUI()
{
    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");

    Cursor@ newCursor = Cursor("Cursor");
    newCursor.SetStyleAuto(uiStyle);
    newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
    ui.cursor = newCursor;
    ui.cursor.visible = false;
}

void InitScene()
{
    testScene = Scene("TestScene");

    // Enable access to this script file & scene from the console
    script.defaultScene = testScene;
    script.defaultScriptFile = scriptFile;

    // Create the camera outside the scene so it is unaffected by scene load/save
    cameraNode = Node();
    camera = cameraNode.CreateComponent("Camera");
    camera.farClip = 1000;
    camera.nearClip = 0.5;
    cameraNode.position = Vector3(0, 20, 0);

    if (!engine.headless)
    {
        renderer.viewports[0] = Viewport(testScene, camera);
        audio.listener = cameraNode.CreateComponent("SoundListener");
    }

    PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
    testScene.CreateComponent("Octree");
    testScene.CreateComponent("DebugRenderer");

    Node@ zoneNode = testScene.CreateChild("Zone");
    Zone@ zone = zoneNode.CreateComponent("Zone");
    zone.ambientColor = Color(0.15, 0.15, 0.15);
    zone.fogColor = Color(0.5, 0.5, 0.7);
    zone.fogStart = 500.0;
    zone.fogEnd = 1000.0;
    zone.boundingBox = BoundingBox(-2000, 2000);

    {
        Node@ lightNode = testScene.CreateChild("GlobalLight");
        lightNode.direction = Vector3(0.5, -0.5, 0.5);

        Light@ light = lightNode.CreateComponent("Light");
        light.lightType = LIGHT_DIRECTIONAL;
        light.castShadows = true;
        light.shadowBias = BiasParameters(0.0001, 0.5);
        light.shadowCascade = CascadeParameters(15.0, 50.0, 200.0, 0.0, 0.8);
        light.specularIntensity = 0.5;
    }

    Terrain@ terrain;

    {
        Node@ terrainNode = testScene.CreateChild("Terrain");
        terrainNode.position = Vector3(0, 0, 0);
        terrain = terrainNode.CreateComponent("Terrain");
        terrain.patchSize = 64;
        terrain.spacing = Vector3(2, 0.1, 2);
        terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
        terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
        terrain.occluder = true;

        RigidBody@ body = terrainNode.CreateComponent("RigidBody");
        body.collisionLayer = 2;
        CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
        shape.SetTerrain();
        shape.margin = 0.01;
    }

    for (uint i = 0; i < 1000; ++i)
    {
        Node@ objectNode = testScene.CreateChild("Mushroom");
        Vector3 position(Random() * 2000 - 1000, 0, Random() * 2000 - 1000);
        position.y = terrain.GetHeight(position) - 0.1;

        objectNode.position = position;
        objectNode.rotation = Quaternion(Vector3(0, 1, 0), terrain.GetNormal(position));
        objectNode.SetScale(3);

        StaticModel@ object = objectNode.CreateComponent("StaticModel");
        object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
        object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
        object.castShadows = true;

        RigidBody@ body = objectNode.CreateComponent("RigidBody");
        body.collisionLayer = 2;
        CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
        shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
    }
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    float timeStep = eventData["TimeStep"].GetFloat();

    // Nothing to do for now
}

void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
{
    float timeStep = eventData["TimeStep"].GetFloat();

    // Physics update has completed. Position camera behind vehicle
    Quaternion dir(vehicleHullNode.rotation.yaw, Vector3(0, 1, 0));;
    dir = dir * Quaternion(yaw, Vector3(0, 1, 0));
    dir = dir * Quaternion(pitch, Vector3(1, 0, 0));

    Vector3 cameraTargetPos = vehicleHullNode.position - dir * Vector3(0, 0, 10);
    Vector3 cameraStartPos = vehicleHullNode.position;

    // Raycast camera against static objects (physics collision mask 2)
    // and move it closer to the vehicle if something in between
    Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
    float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
    PhysicsRaycastResult result = scene.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
    if (result.body !is null)
        cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);

    cameraNode.position = cameraTargetPos;
    cameraNode.rotation = dir;
}

void HandleKeyDown(StringHash eventType, VariantMap& eventData)
{
    int key = eventData["Key"].GetInt();

    if (key == KEY_ESC)
    {
        if (ui.focusElement is null)
            engine.Exit();
        else
            console.visible = false;
    }

    if (key == KEY_F1)
        console.Toggle();

    if (ui.focusElement is null)
    {
        if (key == '1')
        {
            int quality = renderer.textureQuality;
            ++quality;
            if (quality > 2)
                quality = 0;
            renderer.textureQuality = quality;
        }

        if (key == '2')
        {
            int quality = renderer.materialQuality;
            ++quality;
            if (quality > 2)
                quality = 0;
            renderer.materialQuality = quality;
        }

        if (key == '3')
            renderer.specularLighting = !renderer.specularLighting;

        if (key == '4')
            renderer.drawShadows = !renderer.drawShadows;

        if (key == '5')
        {
            int size = renderer.shadowMapSize;
            size *= 2;
            if (size > 2048)
                size = 512;
            renderer.shadowMapSize = size;
        }

        if (key == '6')
            renderer.shadowQuality = renderer.shadowQuality + 1;

        if (key == '7')
        {
            bool occlusion = renderer.maxOccluderTriangles > 0;
            occlusion = !occlusion;
            renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
        }

        if (key == '8')
            renderer.dynamicInstancing = !renderer.dynamicInstancing;

        if (key == ' ')
        {
            drawDebug++;
            if (drawDebug > 2)
                drawDebug = 0;
        }

        if (key == 'T')
            debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
    }
}

void HandleMouseMove(StringHash eventType, VariantMap& eventData)
{
    int mousedx = eventData["DX"].GetInt();
    int mousedy = eventData["DY"].GetInt();
    yaw += mousedx / 10.0;
    pitch += mousedy / 10.0;
    if (pitch < 0.0)
        pitch = 0.0;
    if (pitch > 60.0)
        pitch = 60.0;
}

void HandlePostRenderUpdate()
{
    if (engine.headless)
        return;

    // Draw rendering debug geometry without depth test to see the effect of occlusion
    if (drawDebug == 1)
        renderer.DrawDebugGeometry(false);
    if (drawDebug == 2)
        testScene.physicsWorld.DrawDebugGeometry(true);
}

void InitVehicle()
{
    vehicleHullNode = testScene.CreateChild("VehicleHull");
    StaticModel@ hullObject = vehicleHullNode.CreateComponent("StaticModel");
    RigidBody@ hullBody = vehicleHullNode.CreateComponent("RigidBody");
    CollisionShape@ hullShape = vehicleHullNode.CreateComponent("CollisionShape");

    vehicleHullNode.position = Vector3(0, 5, 0);
    vehicleHullNode.scale = Vector3(1.5, 1, 3);
    hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
    hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
    hullObject.castShadows = true;
    hullShape.SetBox(Vector3(1, 1, 1));
    hullBody.mass = 3;
    hullBody.linearDamping = 0.2; // Some air resistance
    hullBody.collisionLayer = 1;

    Node@ fl = InitVehicleWheel("FrontLeft", vehicleHullNode, Vector3(-0.6, -0.4, 0.3));
    Node@ fr = InitVehicleWheel("FrontRight", vehicleHullNode, Vector3(0.6, -0.4, 0.3));
    Node@ rr = InitVehicleWheel("RearLeft", vehicleHullNode, Vector3(-0.6, -0.4, -0.3));
    Node@ rl = InitVehicleWheel("RearRight", vehicleHullNode, Vector3(0.6, -0.4, -0.3));

    Vehicle@ vehicle = cast<Vehicle>(vehicleHullNode.CreateScriptObject(scriptFile, "Vehicle"));
    vehicle.SetWheels(fl, fr, rl, rr);
}

Node@ InitVehicleWheel(String name, Node@ vehicleHullNode, Vector3 offset)
{
    Node@ wheelNode = testScene.CreateChild(name);
    wheelNode.position = vehicleHullNode.LocalToWorld(offset);
    wheelNode.rotation = vehicleHullNode.worldRotation * (offset.x >= 0.0 ? Quaternion(0, 0, -90) : Quaternion(0, 0, 90));
    wheelNode.scale = Vector3(0.75, 0.5, 0.75);

    StaticModel@ wheelObject = wheelNode.CreateComponent("StaticModel");
    RigidBody@ wheelBody = wheelNode.CreateComponent("RigidBody");
    CollisionShape@ wheelShape = wheelNode.CreateComponent("CollisionShape");
    Constraint@ wheelConstraint = wheelNode.CreateComponent("Constraint");

    wheelObject.model = cache.GetResource("Model", "Models/Cylinder.mdl");
    wheelObject.material = cache.GetResource("Material", "Materials/Stone.xml");
    wheelObject.castShadows = true;
    wheelShape.SetCylinder(1, 1);
    wheelBody.friction = 1;
    wheelBody.mass = 1;
    wheelBody.linearDamping = 0.2; // Some air resistance
    wheelBody.angularDamping = 0.75; // Current version of Bullet used by Urho doesn't have rolling friction, so mimic that with
                                    // some angular damping on the wheels
    wheelBody.collisionLayer = 1;
    wheelConstraint.constraintType = CONSTRAINT_HINGE;
    wheelConstraint.otherBody = vehicleHullNode.GetComponent("RigidBody");
    wheelConstraint.worldPosition = wheelNode.worldPosition; // Set constraint's both ends at wheel's location
    wheelConstraint.axis = Vector3(0, 1, 0); // Wheel rotates around its local Y-axis
    wheelConstraint.otherAxis = offset.x >= 0.0 ? Vector3(1, 0, 0) : Vector3(-1, 0, 0); // Wheel's hull axis points either left or right
    wheelConstraint.lowLimit = Vector2(-180, 0); // Let the wheel rotate freely around the axis
    wheelConstraint.highLimit = Vector2(180, 0);
    wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull

    return wheelNode;
}

class Vehicle : ScriptObject
{
    Node@ frontLeft;
    Node@ frontRight;
    Node@ rearLeft;
    Node@ rearRight;
    Constraint@ frontLeftAxis;
    Constraint@ frontRightAxis;
    RigidBody@ hullBody;
    RigidBody@ frontLeftBody;
    RigidBody@ frontRightBody;
    RigidBody@ rearLeftBody;
    RigidBody@ rearRightBody;

    float steering = 0.0;
    float enginePower = 8.0;
    float downForce = 10.0;
    float maxWheelAngle = 22.5;

    void SetWheels(Node@ fl, Node@ fr, Node@ rl, Node@ rr)
    {
        frontLeft = fl;
        frontRight = fr;
        rearLeft = rl;
        rearRight = rr;

        hullBody = node.GetComponent("RigidBody");
        frontLeftAxis = frontLeft.GetComponent("Constraint");
        frontRightAxis = frontRight.GetComponent("Constraint");
        frontLeftBody = frontLeft.GetComponent("RigidBody");
        frontRightBody = frontRight.GetComponent("RigidBody");
        rearLeftBody = rearLeft.GetComponent("RigidBody");
        rearRightBody = rearRight.GetComponent("RigidBody");
    }

    void FixedUpdate(float timeStep)
    {
        float newSteering = 0.0;
        float accelerator = 0.0;

        if (ui.focusElement is null)
        {
            if (input.keyDown['A'])
                newSteering = -1.0f;
            if (input.keyDown['D'])
                newSteering = 1.0f;
            if (input.keyDown['W'])
                accelerator = 1.0f;
            if (input.keyDown['S'])
                accelerator = -0.5f;
        }

        // When steering, wake up the wheel rigidbodies so that their orientation is updated
        if (newSteering != 0.0)
        {
            frontLeftBody.Activate();
            frontRightBody.Activate();
            steering = steering * 0.95 + newSteering * 0.05;
        }
        else
            steering = steering * 0.8 + newSteering * 0.2;

        Quaternion steeringRot(0, steering * maxWheelAngle, 0);

        frontLeftAxis.otherAxis = steeringRot * Vector3(-1, 0, 0);
        frontRightAxis.otherAxis = steeringRot * Vector3(1, 0, 0);

        if (accelerator != 0.0)
        {
            // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
            Vector3 torqueVec = Vector3(enginePower * accelerator, 0, 0);

            frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
            frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
            rearLeftBody.ApplyTorque(node.rotation * torqueVec);
            rearRightBody.ApplyTorque(node.rotation * torqueVec);
        }

        // Apply downforce proportional to velocity
        Vector3 localVelocity = node.worldRotation.Inverse() * hullBody.linearVelocity;
        hullBody.ApplyForce(node.worldRotation * Vector3(0, -1, 0) * Abs(localVelocity.z) * downForce);
    }
}

Align downforce to vehicle hull orientation.


package aerys.minko.scene.controller.camera
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.CameraDataProvider;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	public final class CameraController extends AbstractController
	{
		use namespace minko_scene;

		private var _camera		: AbstractCamera	= null;
		private var _ortho 		: Boolean 			= false;

		public function CameraController(ortho : Boolean = false)
		{
			super(AbstractCamera);

			_ortho = ortho;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(controller	: CameraController,
											target		: AbstractCamera) : void
		{
			if (_camera != null)
				throw new Error('The CameraController can target only one Camera object.');

			_camera = target;
			_camera.addedToScene.add(addedToSceneHandler);
			_camera.removedFromScene.add(removedFromSceneHandler);
			_camera.worldToLocal.changed.add(worldToLocalChangedHandler);
		}

		private function targetRemovedHandler(controller	: CameraController,
											  target		: AbstractCamera) : void
		{
			_camera.addedToScene.remove(addedToSceneHandler);
			_camera.removedFromScene.remove(removedFromSceneHandler);
			_camera.worldToLocal.changed.remove(worldToLocalChangedHandler);
			_camera = null;
		}

		private function addedToSceneHandler(camera : AbstractCamera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			resetSceneCamera(scene);

			if (camera.enabled)
				sceneBindings.addProvider(camera.cameraData);

			camera.activated.add(cameraActivatedHandler);
			camera.deactivated.add(cameraDeactivatedHandler);

			camera.cameraData.changed.add(cameraPropertyChangedHandler);

			sceneBindings.addCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.addCallback('viewportHeight', viewportSizeChanged);

			updateProjection();
		}

		private function removedFromSceneHandler(camera : AbstractCamera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			resetSceneCamera(scene);

			if (camera.enabled)
				sceneBindings.removeProvider(camera.cameraData);

			camera.activated.remove(cameraActivatedHandler);
			camera.deactivated.remove(cameraDeactivatedHandler);

			camera.cameraData.changed.remove(cameraPropertyChangedHandler);

			sceneBindings.removeCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.removeCallback('viewportHeight', viewportSizeChanged);
		}

		private function worldToLocalChangedHandler(worldToLocal : Matrix4x4) : void
		{
			var cameraData		: CameraDataProvider	= _camera.cameraData;
			var worldToScreen	: Matrix4x4				= cameraData.worldToScreen;
			var screenToWorld	: Matrix4x4				= cameraData.screenToWorld;
			var viewToWorld		: Matrix4x4				= cameraData.viewToWorld;
			var cameraPosition	: Vector4				= cameraData.position;
			var cameraDirection	: Vector4				= cameraData.direction;

			worldToScreen.lock();
			screenToWorld.lock();
			cameraPosition.lock();
			cameraDirection.lock();

			worldToScreen.copyFrom(_camera.worldToLocal).append(cameraData.projection);
			screenToWorld.copyFrom(cameraData.screenToView).append(_camera.localToWorld);
			viewToWorld.transformVector(Vector4.ZERO, cameraPosition);
			viewToWorld.deltaTransformVector(Vector4.Z_AXIS, cameraDirection).normalize();

			cameraData.frustum.updateFromMatrix(worldToScreen);

			worldToScreen.unlock();
			screenToWorld.unlock();
			cameraPosition.unlock();
			cameraDirection.unlock();
		}

		private function viewportSizeChanged(bindings 	: DataBindings,
											 key 		: String,
											 oldValue 	: Object,
											 newValue 	: Object) : void
		{
			updateProjection();
		}

		private function cameraPropertyChangedHandler(provider : IDataProvider,
                                                      property : String) : void
		{
			if (property == 'zFar' || property == 'zNear' || property == 'fieldOfView'
                || property == 'zoom')
				updateProjection();
		}

		private function updateProjection() : void
		{
			var sceneBindings	: DataBindings	= Scene(_camera.root).bindings;

			if (sceneBindings.propertyExists('viewportWidth')
				&& sceneBindings.propertyExists('viewportHeight'))
			{
				var cameraData		: CameraDataProvider	= _camera.cameraData;
				var viewportWidth	: Number				= sceneBindings.getProperty('viewportWidth');
				var viewportHeight	: Number				= sceneBindings.getProperty('viewportHeight');
				var ratio			: Number				= viewportWidth / viewportHeight;

				var projection		: Matrix4x4				= cameraData.projection;
				var screenToView	: Matrix4x4				= cameraData.screenToView;
				var screenToWorld	: Matrix4x4				= cameraData.screenToWorld;
				var worldToScreen	: Matrix4x4				= cameraData.worldToScreen;

				projection.lock();
				screenToView.lock();
				screenToWorld.lock();
				worldToScreen.lock();

				if (_ortho)
					projection.ortho(
						viewportWidth / cameraData.zoom,
						viewportHeight / cameraData.zoom,
						cameraData.zNear,
						cameraData.zFar
					);
				else
					projection.perspectiveFoV(
						cameraData.fieldOfView,
						ratio,
						cameraData.zNear,
						cameraData.zFar
					);

				screenToView.copyFrom(projection).invert();
				screenToWorld.copyFrom(screenToView).append(_camera.localToWorld);
				worldToScreen.copyFrom(_camera.worldToLocal).append(projection);

				cameraData.frustum.updateFromMatrix(worldToScreen);

				projection.unlock();
				screenToView.unlock();
				screenToWorld.unlock();
				worldToScreen.unlock();
			}
		}

		private function cameraActivatedHandler(camera : AbstractCamera) : void
		{
			var scene : Scene = camera.root as Scene;

			scene.bindings.addProvider(camera.cameraData);
			resetSceneCamera(scene);
		}

		private function cameraDeactivatedHandler(camera : AbstractCamera) : void
		{
			var scene 	: Scene	= camera.root as Scene;

			scene.bindings.removeProvider(camera.cameraData);
			resetSceneCamera(scene);
		}

		private function resetSceneCamera(scene : Scene) : void
		{
			var cameras 	: Vector.<ISceneNode> 	= scene.getDescendantsByType(AbstractCamera);
			var numCameras 	: uint 					= cameras.length;
			var cameraId 	: uint 					= 0;
			var camera 		: AbstractCamera 		= null;

			if (_camera.enabled)
			{
				scene._camera = _camera;
				for (cameraId; cameraId < numCameras; ++cameraId)
				{
					camera = cameras[cameraId] as AbstractCamera;
					camera.enabled = camera == _camera;
				}
			}
			else
			{
				scene._camera = null;
				for (cameraId; cameraId < numCameras; ++cameraId)
				{
					camera = cameras[cameraId] as AbstractCamera;
					if (camera.enabled)
						scene._camera = camera;
				}
			}
		}
	}
}

package aerys.minko.scene.controller.camera
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.CameraDataProvider;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	public final class CameraController extends AbstractController
	{
		use namespace minko_scene;

		private var _camera		: AbstractCamera	= null;
		private var _ortho 		: Boolean 			= false;

		public function CameraController(ortho : Boolean = false)
		{
			super(AbstractCamera);

			_ortho = ortho;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(controller	: CameraController,
											target		: AbstractCamera) : void
		{
			if (_camera != null)
				throw new Error('The CameraController can target only one Camera object.');

			_camera = target;
			_camera.addedToScene.add(addedToSceneHandler);
			_camera.removedFromScene.add(removedFromSceneHandler);
			_camera.worldToLocal.changed.add(worldToLocalChangedHandler);
		}

		private function targetRemovedHandler(controller	: CameraController,
											  target		: AbstractCamera) : void
		{
			_camera.addedToScene.remove(addedToSceneHandler);
			_camera.removedFromScene.remove(removedFromSceneHandler);
			_camera.worldToLocal.changed.remove(worldToLocalChangedHandler);
			_camera = null;
		}

		private function addedToSceneHandler(camera : AbstractCamera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			resetSceneCamera(scene);

            if (camera.enabled)
                sceneBindings.addProvider(camera.cameraData);

			camera.activated.add(cameraActivatedHandler);
			camera.deactivated.add(cameraDeactivatedHandler);

			camera.cameraData.changed.add(cameraPropertyChangedHandler);

			sceneBindings.addCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.addCallback('viewportHeight', viewportSizeChanged);

			updateProjection();
		}

		private function removedFromSceneHandler(camera : AbstractCamera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			resetSceneCamera(scene);

			if (camera.enabled)
				sceneBindings.removeProvider(camera.cameraData);

			camera.activated.remove(cameraActivatedHandler);
			camera.deactivated.remove(cameraDeactivatedHandler);

			camera.cameraData.changed.remove(cameraPropertyChangedHandler);

			sceneBindings.removeCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.removeCallback('viewportHeight', viewportSizeChanged);
		}

		private function worldToLocalChangedHandler(worldToLocal : Matrix4x4) : void
		{
			var cameraData		: CameraDataProvider	= _camera.cameraData;
			var worldToScreen	: Matrix4x4				= cameraData.worldToScreen;
			var screenToWorld	: Matrix4x4				= cameraData.screenToWorld;
			var viewToWorld		: Matrix4x4				= cameraData.viewToWorld;
			var cameraPosition	: Vector4				= cameraData.position;
			var cameraDirection	: Vector4				= cameraData.direction;

			worldToScreen.lock();
			screenToWorld.lock();
			cameraPosition.lock();
			cameraDirection.lock();

			worldToScreen.copyFrom(_camera.worldToLocal).append(cameraData.projection);
			screenToWorld.copyFrom(cameraData.screenToView).append(_camera.localToWorld);
			viewToWorld.transformVector(Vector4.ZERO, cameraPosition);
			viewToWorld.deltaTransformVector(Vector4.Z_AXIS, cameraDirection).normalize();

			cameraData.frustum.updateFromMatrix(worldToScreen);

			worldToScreen.unlock();
			screenToWorld.unlock();
			cameraPosition.unlock();
			cameraDirection.unlock();
		}

		private function viewportSizeChanged(bindings 	: DataBindings,
											 key 		: String,
											 oldValue 	: Object,
											 newValue 	: Object) : void
		{
			updateProjection();
		}

		private function cameraPropertyChangedHandler(provider : IDataProvider,
                                                      property : String) : void
		{
			if (property == 'zFar' || property == 'zNear' || property == 'fieldOfView'
                || property == 'zoom')
				updateProjection();
		}

		private function updateProjection() : void
		{
			var sceneBindings	: DataBindings	= Scene(_camera.root).bindings;

			if (sceneBindings.propertyExists('viewportWidth')
				&& sceneBindings.propertyExists('viewportHeight'))
			{
				var cameraData		: CameraDataProvider	= _camera.cameraData;
				var viewportWidth	: Number				= sceneBindings.getProperty('viewportWidth');
				var viewportHeight	: Number				= sceneBindings.getProperty('viewportHeight');
				var ratio			: Number				= viewportWidth / viewportHeight;

				var projection		: Matrix4x4				= cameraData.projection;
				var screenToView	: Matrix4x4				= cameraData.screenToView;
				var screenToWorld	: Matrix4x4				= cameraData.screenToWorld;
				var worldToScreen	: Matrix4x4				= cameraData.worldToScreen;

				projection.lock();
				screenToView.lock();
				screenToWorld.lock();
				worldToScreen.lock();

				if (_ortho)
					projection.ortho(
						viewportWidth / cameraData.zoom,
						viewportHeight / cameraData.zoom,
						cameraData.zNear,
						cameraData.zFar
					);
				else
					projection.perspectiveFoV(
						cameraData.fieldOfView,
						ratio,
						cameraData.zNear,
						cameraData.zFar
					);

				screenToView.copyFrom(projection).invert();
				screenToWorld.copyFrom(screenToView).append(_camera.localToWorld);
				worldToScreen.copyFrom(_camera.worldToLocal).append(projection);

				cameraData.frustum.updateFromMatrix(worldToScreen);

				projection.unlock();
				screenToView.unlock();
				screenToWorld.unlock();
				worldToScreen.unlock();
			}
		}

		private function cameraActivatedHandler(camera : AbstractCamera) : void
		{
			var scene : Scene = camera.root as Scene;

			resetSceneCamera(scene);
			scene.bindings.addProvider(camera.cameraData);
		}

		private function cameraDeactivatedHandler(camera : AbstractCamera) : void
		{
			var scene 	: Scene	= camera.root as Scene;

			resetSceneCamera(scene);
			scene.bindings.removeProvider(camera.cameraData);
		}

		private function resetSceneCamera(scene : Scene) : void
		{
			var cameras 	: Vector.<ISceneNode> 	= scene.getDescendantsByType(AbstractCamera);
			var numCameras 	: uint 					= cameras.length;
			var cameraId 	: uint 					= 0;
			var camera 		: AbstractCamera 		= null;

			if (_camera.enabled)
			{
				scene._camera = _camera;
				for (cameraId; cameraId < numCameras; ++cameraId)
				{
					camera = cameras[cameraId] as AbstractCamera;
					camera.enabled = camera == _camera;
				}
			}
			else
			{
				scene._camera = null;
				for (cameraId; cameraId < numCameras; ++cameraId)
				{
					camera = cameras[cameraId] as AbstractCamera;
					if (camera.enabled)
						scene._camera = camera;
				}
			}
		}
	}
}

fix multiple cameras switch by patching CameraController to make sure the 'old' camera is deactivated and it's data provider out of the scene bindings before adding the data provider of the 'new' one


package com.jonas.net
{
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLRequestHeader;
	import flash.utils.ByteArray;

	/**
	 *
	 * Based on RFC 1867 + real case observation
	 *
	 * RFC 1867
	 * https://tools.ietf.org/html/rfc1867
	 *
	 */
	public class Multipart
	{
		private var _url:String;
		private var _fields:Array = [];
		private var _files:Array = [];
		private var _data:ByteArray = new ByteArray();
		private var _encoding:String = 'ascii'; //http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/charset-codes.html

		public function Multipart(url:String = null) {
			_url = url;
		}

		public function addFields(fields:Object):void {
			for (var s:String in fields) {
				addField(s, fields[s]);
			}
		}

		public function addField(name:String, value:String):void {
			_fields.push({name:name, value:value});
		}

		public function addFile(name:String, byteArray:ByteArray, mimeType:String, fileName:String):void {
			_files.push({name:name, byteArray:byteArray, mimeType:mimeType, fileName:fileName});
		}

		public function clear():void {
			_data = new ByteArray();
			_fields = [];
			_files = [];
		}

		public function get request():URLRequest
		{
			var boundary: String = (Math.round(Math.random()*100000000)).toString();

			var n:uint;
			var i:uint;

			// Add fields
			n = _fields.length;
			for(i=0; i<n; i++){
				_writeString('--' + boundary + '\r\n'
					+'Content-Disposition: form-data; name="'+_fields[i].name+'"\r\n\r\n'
					+_fields[i].value+'\r\n');
			}

			// Add files
			n = _files.length;
			for(i=0; i<n; i++){
				_writeString('--'+boundary + '\r\n'
					+'Content-Disposition: form-data; name="'+_files[i].name+'"; filename="'+_files[i].fileName+'"\r\n'
					+'Content-Type: '+_files[i].mimeType+'\r\n\r\n');

				_writeBytes(_files[i].byteArray);
				_writeString('\r\n');
			}

			// Close
			_writeString('--' + boundary + '--\r\n');

			var r: URLRequest = new URLRequest(_url);
			r.data = _data;
			r.method = URLRequestMethod.POST;
			r.requestHeaders.push( new URLRequestHeader('Content-type', 'multipart/form-data; boundary=' + boundary) );

			return r;

		}
		public function get url():String
		{
			return _url;
		}
		public function set url(value:String):void
		{
			_url = value;
		}

		public function get encoding():String
		{
			return _encoding;
		}

		public function set encoding(value:String):void
		{
			_encoding = value;
		}

		private function _writeString(value:String):void {
			var b:ByteArray = new ByteArray();
			b.writeMultiByte(value, _encoding);
			_data.writeBytes(b, 0, b.length);
		}

		private function _writeBytes(value:ByteArray):void {
			//_writeString("....FILE....");
			value.position = 0;
			_data.writeBytes(value, 0, value.length);
		}
	}
}

package com.jonas.net
{
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLRequestHeader;
	import flash.utils.ByteArray;

	/**
	 *
	 * Based on RFC 1867 + real case observation
	 *
	 * RFC 1867
	 * https://tools.ietf.org/html/rfc1867
	 *
	 */
	public class Multipart
	{
		private var _url:String;
		private var _fields:Array = [];
		private var _files:Array = [];
		private var _data:ByteArray = new ByteArray();
		private var _encoding:String = 'utf-8'; //http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/charset-codes.html

		public function Multipart(url:String = null) {
			_url = url;
		}

		public function addFields(fields:Object):void {
			for (var s:String in fields) {
				addField(s, fields[s]);
			}
		}

		public function addField(name:String, value:String):void {
			_fields.push({name:name, value:value});
		}

		public function addFile(name:String, byteArray:ByteArray, mimeType:String, fileName:String):void {
			_files.push({name:name, byteArray:byteArray, mimeType:mimeType, fileName:fileName});
		}

		public function clear():void {
			_data = new ByteArray();
			_fields = [];
			_files = [];
		}

		public function get request():URLRequest
		{
			var boundary: String = (Math.round(Math.random()*100000000)).toString();

			var n:uint;
			var i:uint;

			// Add fields
			n = _fields.length;
			for(i=0; i<n; i++){
				_writeString('--' + boundary + '\r\n'
					+ 'Content-Disposition: form-data; name="' + _fields[i].name+'"\r\n\r\n');
				_writeString(_fields[i].value, _encoding);
				_writeString('\r\n');
			}

			// Add files
			n = _files.length;
			for(i=0; i<n; i++){
				_writeString('--'+boundary + '\r\n'
					+'Content-Disposition: form-data; name="'+_files[i].name+'"; filename="'+_files[i].fileName+'"\r\n'
					+'Content-Type: '+_files[i].mimeType+'\r\n\r\n');

				_writeBytes(_files[i].byteArray);
				_writeString('\r\n');
			}

			// Close
			_writeString('--' + boundary + '--\r\n');

			var r: URLRequest = new URLRequest(_url);
			r.data = _data;
			r.method = URLRequestMethod.POST;
			r.requestHeaders.push( new URLRequestHeader('Content-type', 'multipart/form-data; boundary=' + boundary) );

			return r;

		}
		public function get url():String
		{
			return _url;
		}
		public function set url(value:String):void
		{
			_url = value;
		}

		public function get encoding():String
		{
			return _encoding;
		}

		public function set encoding(value:String):void
		{
			_encoding = value;
		}

		private function _writeString(value:String, encoding:String = 'ascii'):void {
			var b:ByteArray = new ByteArray();
			b.writeMultiByte(value, encoding);
			_data.writeBytes(b, 0, b.length);
		}

		private function _writeBytes(value:ByteArray):void {
			//_writeString("....FILE....");
			value.position = 0;
			_data.writeBytes(value, 0, value.length);
		}
	}
}

Support for not ascii compliant encodings. Also utf-8 as default encoding.


#include "entity.as"

class player : entity {
    player(const glm::vec3& in position, const glm::quat& in rotation, const glm::vec3& in scale,
        const string& in mesh, const string& in skeleton) {
        super(position, rotation, scale);
        impl_.add_animation_component(mesh, skeleton);
        impl_.add_character_physics_component(box_shape(glm::vec3(0.6, 1, 0.3)), 10.f, 2.f);
        auto@ sc = impl_.add_state_component();
        sc.emplace("stand", "scripts/character_states/stand.as");
        sc.emplace("run", "scripts/character_states/run.as");
        sc.emplace("walk_backward", "scripts/character_states/walk_backward.as");
    }
}

#include "entity.as"

class player : entity {
    player(const glm::vec3& in position, const glm::quat& in rotation, const glm::vec3& in scale,
        const string& in mesh, const string& in skeleton) {
        super(position, rotation, scale);
        impl_.add_animation_component(mesh, skeleton);
        impl_.add_character_physics_component(box_shape(glm::vec3(0.6, 1, 0.3)), 10.f, 2.f);
        auto@ sc = impl_.add_state_component();
        sc.emplace("stand", "scripts/character_states/stand.as");
        sc.emplace("run", "scripts/character_states/run.as");
        sc.emplace("walk_backward", "scripts/character_states/walk_backward.as");

        sc.change_state("stand");
    }
}

Set initial player state in players ctor.