UpDownRoad

by ABA
UpDownRoad.as
Display a 3d updown road with 3d particles.
♥16 | Line 387 | Modified 2008-12-26 23:35:07 | MIT License
play

ActionScript3 source code

/**
 * Copyright ABA ( http://wonderfl.net/user/ABA )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/oNKu
 */

// UpDownRoad.as
//  Display a 3d updown road with 3d particles.
package
{
  import flash.display.Sprite;
  import flash.display.BitmapData;
  import flash.display.Bitmap;
  import flash.geom.Rectangle;
  import flash.events.Event;

  [SWF(width="465", height="465", backgroundColor="0x000000", frameRate="30")]
  public class UpDownRoad extends Sprite
  {
    private const SCREEN_WIDTH:int = 465;
    private const SCREEN_HEIGHT:int = 465;
    private const DRAWN_PARTICLES_MAX_COUNT:int = 2000;
    private const PROJECTION_RATIO:Number = 128;
    private const LOD_DISTANCE:Number = 200;
    private const MAX_DEPTH:Number = 500;
    private const ROAD_SPACING:Number = 5;
    private const ROAD_WIDTH:Number = 64;
    private const ROAD_HEIGHT:Number = 6;
    private const SIGHT_HEIGHT:Number = 30;
    private const BACKGROUND_BOX_SIZE:int = 16;
    private const BACKGROUND_R:int = 0x88;
    private const BACKGROUND_G:int = 0xcc;
    private const BACKGROUND_B:int = 0xff;
    private var buffer:BitmapData = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, false, 0);
    private var rect:Rectangle = new Rectangle;
    private var roads:Vector.<Road> = new Vector.<Road>;
    private var particles:Vector.<Particle> = new Vector.<Particle>;
    private var drawnParticles:Vector.<DrawnParticle> = new Vector.<DrawnParticle>;
    private var dpIndex:int;
    private var viewpoint:Vector3 = new Vector3;
    private var yaw:Number, pitch:Number, roll:Number;
    private var offset:Vector3 = new Vector3;
    private var carPosIndex:Number;
    private var roadIndex:int;

    public function UpDownRoad()
    {
      addChild(new Bitmap(buffer));
      yaw = pitch = roll = 0;
      createRoad();
      for (var i:int = 0; i < DRAWN_PARTICLES_MAX_COUNT; i++)
        drawnParticles.push(new DrawnParticle);
      carPosIndex = roads.length - 10;
      addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }

    private function onEnterFrame(evt:Event):void
    {
      buffer.fillRect(buffer.rect, 0x99ffdd);
      carPosIndex += 1.7;
      if (carPosIndex >= roads.length)
        carPosIndex -= roads.length;
      var cpi:int = carPosIndex;
      var ncpi:int = carPosIndex + 1;
      if (ncpi >= roads.length)
        ncpi -= roads.length;
      var co:Number = carPosIndex - cpi;
      var rp:Vector3 = roads[cpi].pos;
      var nrp:Vector3 = roads[ncpi].pos;
      viewpoint.x += (rp.x * (1 - co) + nrp.x * co - viewpoint.x) * 0.2;
      viewpoint.y += (rp.y * (1 - co) + nrp.y * co - viewpoint.y - SIGHT_HEIGHT) * 0.2;
      viewpoint.z += (rp.z * (1 - co) + nrp.z * co - viewpoint.z) * 0.2;
      var oa:Number = roads[ncpi].angle - roads[cpi].angle;
      oa = normalizeAngle(oa);
      oa = roads[cpi].angle + oa * co - yaw;
      oa = normalizeAngle(oa);
      yaw += oa * 0.1;
      yaw = normalizeAngle(yaw);
      pitch += ((roads[cpi].upDown * (1 - co) + roads[ncpi].upDown * co) * 0.5 - pitch) * 0.2;
      roll += (-oa * 0.7 - roll) * 0.1;
      drawBackground();
      dpIndex = 0;
      for each (var p:Particle in particles)
        drawParticle(p);
      drawnParticles.sort(sortOnZ);
      function sortOnZ(v1:DrawnParticle, v2:DrawnParticle):Number
      {
        if (v1.pos.z < v2.pos.z)
          return 1;
        else if (v1.pos.z > v2.pos.z)
          return -1;
        else
          return 0;
      }
      for (var i:int = 0; i < dpIndex; i++)
      {
        var dp:DrawnParticle = drawnParticles[i];
        rect.x = dp.pos.x - dp.width;
        rect.y = dp.pos.y - dp.height;
        rect.width = dp.width * 2;
        rect.height = dp.height * 2;
        buffer.fillRect(rect, dp.color);
      }
    }

    private function drawParticle(p:Particle):void
    {
      offset.x = p.pos.x - viewpoint.x;
      offset.y = p.pos.y - viewpoint.y;
      offset.z = p.pos.z - viewpoint.z;
      offset.rotationYaw(yaw);
      offset.rotationPitch(pitch);
      offset.rotationRoll(roll);
      if (offset.z < -10 || offset.z > MAX_DEPTH)
        return;
      if (p.detail != null && offset.length < LOD_DISTANCE)
      {
        for each (var pp:Particle in p.detail)
          drawParticle(pp);
        return;
      }
      if (offset.z < 1 || p.width <= 0)
        return;
      var ar:Number = 1.0 - offset.z / MAX_DEPTH;
      if (ar < 0)
        return;
      var dp:DrawnParticle = drawnParticles[dpIndex];
      var zr:Number = 1.0 / offset.z * PROJECTION_RATIO;
      dp.pos.x = offset.x * zr + SCREEN_WIDTH / 2;
      dp.pos.y = offset.y * zr + SCREEN_HEIGHT / 2;
      dp.pos.z = offset.z;
      dp.width = p.width * zr;
      dp.height = p.height * zr;
      dp.color = createColor(p.r, p.g, p.b, 250 * ar);
      dpIndex++;
    }

    private function createColor(r:int, g:int, b:int, a:int):int
    {
      return int((r * a + BACKGROUND_R * (256 - a)) / 256) * 0x10000 +
             int((g * a + BACKGROUND_G * (256 - a)) / 256) * 0x100 +
             int((b * a + BACKGROUND_B * (256 - a)) / 256);
    }

    private function drawBackground():void
    {
      offset.x = 0;
      offset.y = 0;
      offset.z = 99999;
      offset.rotationPitch(pitch);
      var zr:Number = 1.0 / offset.z * PROJECTION_RATIO;
      var bc:int = SCREEN_WIDTH / BACKGROUND_BOX_SIZE + 1;
      var bx:Number, by:Number;
      var vby:Number = BACKGROUND_BOX_SIZE * Math.sin(roll);
      var bh:Number, bby:Number;
      for (var j:int = 0; j < 3; j++)
      {
        switch (j)
        {
          case 0:
          {
            bh = 8;
            bby = 0;
            break;
          }
          case 1:
          {
            bh = 16;
            bby = 16;
            break;
          }
          case 2:
          {
            bh = SCREEN_HEIGHT / 3;
            bby = 48;
            break;
          }
        }
        bx = SCREEN_WIDTH / 2 - BACKGROUND_BOX_SIZE * bc / 2;
        by = offset.y * zr + SCREEN_HEIGHT / 2 - vby * bc / 2 + bby + bh;
        rect.width = BACKGROUND_BOX_SIZE;
        rect.height = bh * 2;
        for (var i:int = 0; i < bc; i++)
        {
          rect.x = bx - BACKGROUND_BOX_SIZE / 2;
          rect.y = by - bh;
          buffer.fillRect(rect, createColor(0x88 - j * 0x33, 0x88 - j * 0x33, 0xff, 255));
          bx += BACKGROUND_BOX_SIZE;
          by += vby;
        }
      }
    }

    private var roadPattern:Array = [20, 0, 0.2, 1, 16, 0, -3, 0, 16, 0, 3, 0, 80, 0.05, 0.05, 1, 90, 0, 0, 0, 80, -0.05, -1.0, 1, 20, 0, 1.75, 1];

    private function createRoad():void
    {
      var r:Road = new Road;
      var a:Number = 0;
      var ud:Number = roadPattern[2];
      r.angle = a;
      r.upDown = ud;
      var c:int;
      var ta:Number, tud:Number;
      var polePattern:int;
      var i:int;
      roadIndex = 0;
      for (i = 0; i < roadPattern.length; i += 4)
      {
        c = roadPattern[i];
        ta = roadPattern[i + 1];
        tud = roadPattern[i + 2];
        polePattern = roadPattern[i + 3];
        for (var j:int = 0; j < c; j++)
        {
          a += (ta - a) * 0.2;
          ud += (tud - ud) * 0.1;
          r.angle += a;
          r.angle = normalizeAngle(r.angle);
          r.upDown = ud;
          addRoadAndPole(r, polePattern);
          r = getNextRoad(r);
        }
      }
      c = Math.sqrt(r.pos.x * r.pos.x + r.pos.z * r.pos.z) / ROAD_SPACING;
      var vrp:Vector3 = new Vector3;
      vrp.x = -r.pos.x / c;
      vrp.y = -r.pos.y / c;
      vrp.z = -r.pos.z / c;
      var va:Number = -r.angle / c;
      var vud:Number = -r.upDown / c;
      for (i = 0; i < c; i++)
      {
        addRoadAndPole(r, polePattern);
        var nr:Road = new Road;
        nr.pos.x = r.pos.x + vrp.x;
        nr.pos.y = r.pos.y + vrp.y;
        nr.pos.z = r.pos.z + vrp.z;
        nr.angle = r.angle + va;
        nr.upDown = r.upDown + vud;
        r = nr;
      }
    }

    private function addRoadAndPole(r:Road, polePattern:int):void
    {
      addRoadParticles(r);
      roads.push(r);
      if (polePattern > 0 && roadIndex % 7 == 0)
      {
        addPoleParticles(r);
      }
      roadIndex++;
    }

    private function getNextRoad(r:Road):Road
    {
      var nr:Road = new Road;
      nr.pos.x = r.pos.x + Math.sin(r.angle) * ROAD_SPACING;
      nr.pos.z = r.pos.z + Math.cos(r.angle) * ROAD_SPACING;
      nr.pos.y = r.pos.y + Math.sin(r.upDown) * ROAD_SPACING;
      nr.angle = r.angle;
      nr.upDown = r.upDown;
      return nr;
    }

    private function addRoadParticles(r:Road):void
    {
      for (var j:int = 0; j < 3; j++)
      {
        var p:Particle = new Particle;
        var ox:Number = Math.cos(r.angle) * ROAD_WIDTH / 3 * 2;
        var oz:Number = -Math.sin(r.angle) * ROAD_WIDTH / 3 * 2;
        p.pos.x = r.pos.x + ox * (j - 1);
        p.pos.y = r.pos.y;
        p.pos.z = r.pos.z + oz * (j - 1);
        p.width = ROAD_WIDTH / 3 * 1.05;
        p.height = ROAD_HEIGHT;
        p.detail = new Vector.<Particle>;
        var pc:int = p.width / p.height + 2;
        ox = Math.cos(r.angle) * p.width / pc * 2;
        oz = -Math.sin(r.angle) * p.width / pc * 2;
        var c:int = 0;
        var i:int;
        var pp:Particle;
        for (i = 0; i < pc; i++)
        {
          pp = new Particle;
          pp.pos.x = p.pos.x + ox * (i - pc / 2);
          pp.pos.y = p.pos.y;
          pp.pos.z = p.pos.z + oz * (i - pc / 2);
          pp.width = pp.height = p.height;
          pp.r = pp.g = pp.b = 190 + Math.random() * 30;
          c += pp.r;
          pp.detail = null;
          p.detail.push(pp);
        }
        p.r = p.g = p.b = c / pc;
        particles.push(p);
        if (roadIndex % 3 == 0 && j != 1)
        {
          pp = new Particle;
          pp.pos.x = p.pos.x + ox * pc / 2 * 1.2 * (j - 1);
          pp.pos.y = p.pos.y - p.height * 0.5;
          pp.pos.z = p.pos.z + oz * pc / 2 * 1.2 * (j - 1);
          pp.width = p.height;
          pp.height = p.height * 0.6;
          pp.r = pp.g = pp.b = 32;
          pp.detail = null;
          particles.push(pp);
        }
      }
    }

    private function addPoleParticles(r:Road):void
    {
      for (var i:int = 0; i < 2; i++)
      {
        var p:Particle = new Particle;
        p.width = 4;
        p.height = 36;
        p.pos.x = r.pos.x - Math.cos(r.angle) * ROAD_WIDTH * (i * 2 - 1) * 1.1;
        p.pos.y = r.pos.y - p.height / 2;
        p.pos.z = r.pos.z + Math.sin(r.angle) * ROAD_WIDTH * (i * 2 - 1) * 1.1;
        p.r = 250; p.g = 60; p.b = 60;
        p.detail = new Vector.<Particle>;
        for (var j:int = 0; j < 8; j++)
        {
          var pp:Particle = new Particle;
          pp.pos.x = p.pos.x;
          pp.pos.y = p.pos.y - j * p.height / 8;
          pp.pos.z = p.pos.z;
          pp.width = p.width;
          pp.height = p.height / 8;
          pp.r = p.r;
          pp.g = p.g;
          pp.b = p.b;
          pp.detail = null;
          p.detail.push(pp);
        }
        particles.push(p);
      }
    }

    private function normalizeAngle(v:Number):Number {
      if (v > Math.PI)
        return v - Math.PI * 2;
      else if (v < -Math.PI)
        return v + Math.PI * 2;
      else
        return v;
    }
  }
}

class Road
{
  public var pos:Vector3 = new Vector3;
  public var angle:Number, upDown:Number;
}

class Particle
{
  public var pos:Vector3 = new Vector3;
  public var width:Number;
  public var height:Number;
  public var r:int, g:int, b:int;
  public var detail:Vector.<Particle>;
}

class DrawnParticle
{
  public var pos:Vector3 = new Vector3;
  public var width:Number;
  public var height:Number;
  public var color:int;
}

class Vector3
{
  public var x:Number = 0;
  public var y:Number = 0;
  public var z:Number = 0;

  public function rotationYaw(v:Number):void
  {
    var sv:Number = Math.sin(v);
    var cv:Number = Math.cos(v);
    var rx:Number = cv * x - sv * z;
    z = sv * x + cv * z;
    x = rx;
  }

  public function rotationPitch(v:Number):void
  {
    var sv:Number = Math.sin(v);
    var cv:Number = Math.cos(v);
    var ry:Number = cv * y - sv * z;
    z = sv * y + cv * z;
    y = ry;
  }

  public function rotationRoll(v:Number):void
  {
    var sv:Number = Math.sin(v);
    var cv:Number = Math.cos(v);
    var rx:Number = cv * x - sv * y;
    y = sv * x + cv * y;
    x = rx;
  }

  public function get length():Number
  {
    return Math.sqrt(x * x + y * y + z * z);
  }
}

Forked