Gouraud shading using GradientFill

by crunchy
Gouraud shading using GradientFill
♥2 | Line 266 | Modified 2009-10-09 04:25:58 | MIT License
play

ActionScript3 source code

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

// Gouraud shading using GradientFill

package {
	import __AS3__.vec.Vector;
	
	import flash.display.BitmapData;
	import flash.display.GradientType;
	import flash.display.Graphics;
	import flash.display.GraphicsEndFill;
	import flash.display.GraphicsGradientFill;
	import flash.display.GraphicsPath;
	import flash.display.GraphicsPathCommand;
	import flash.display.GraphicsSolidFill;
	import flash.display.GraphicsTrianglePath;
	import flash.display.IGraphicsData;
	import flash.display.Shape;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.PerspectiveProjection;
	import flash.geom.Point;
	import flash.geom.Utils3D;
	import flash.geom.Vector3D;
	import flash.utils.getTimer;
	
	[SWF(width="800", height="800", frameRate="60", backgroundColor="#0000FF")]

	public class GradientTesting extends Sprite {
		static private const MAX_X:int = 20;
		static private const MAX_Y:int = 20;
		static private const MULT:Number = 50;

		static private const MOVE_SIN:int = 0;
		static private const MOVE_PERLIN:int = 1;
		static private const NUM_MOVE:int = 2;

		public var shape:Shape = new Shape;

		public var movementType_:int = MOVE_SIN;
		public var lNW_:Vector3D;
		public var vsW_:Vector.<Number> = new Vector.<Number>;
		public var vNW_:Vector.<Vector3D> = new Vector.<Vector3D>;
		public var light_:Vector.<Number> = new Vector.<Number>;
		public var vsS_:Vector.<Number> = new Vector.<Number>;
		public var uvt_:Vector.<Number> = new Vector.<Number>;
		public var index_:Vector.<int> = new Vector.<int>;

		public function GradientTesting() {
			addChild(shape);
			
			lNW_ = new Vector3D(1, 1, 1);
			lNW_.normalize();
			
			var faces:Vector.<Face> = new Vector.<Face>;
			for (var y:Number = 0; y <= MAX_Y; y++) {
				for (var x:Number = 0; x <= MAX_X; x++) {
					vsW_.push((x - MAX_X / 2) * MULT, (y - MAX_Y / 2) * MULT, 
						-1000);
					uvt_.push(x / MAX_X, y / MAX_Y, 0);
					if (x != MAX_X && y != MAX_Y) {
						faces.push(new Face(y * (MAX_Y + 1) + x, 
							y * (MAX_Y + 1) + (x + 1), 
							(y + 1) * (MAX_Y + 1) + x));
						faces.push(new Face(y * (MAX_Y + 1) + (x + 1), 
							(y + 1) * (MAX_Y + 1) + (x + 1), 
							(y + 1) * (MAX_Y + 1) + x));
					}
				}
			}
			faces.sort(faceCompFunc);
			
			for each (var f:Face in faces) {
				index_.push(f.index_[0], f.index_[1], f.index_[2]);
			}

			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
		}

		private function onAddedToStage(event:Event):void {
			shape.x = stage.stageWidth / 2;
			shape.y = stage.stageHeight / 2;

			addEventListener(Event.ENTER_FRAME, onEnterFrame);

			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			stage.addEventListener(Event.MOUSE_LEAVE, onMouseLeave);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
		}
		
		private function onRemovedFromStage(event:Event):void {
			removeEventListener(Event.ENTER_FRAME, onEnterFrame);
			
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			stage.removeEventListener(Event.MOUSE_LEAVE, onMouseLeave);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
		}

		private function onMouseDown(event:Event):void {
			movementType_ = (movementType_ + 1) % NUM_MOVE;
		}

		private function onMouseLeave(event:Event):void {
			lNW_ = new Vector3D(1, 1, 1);
			lNW_.normalize();
		}

		private function onMouseMove(event:MouseEvent):void {
			lNW_ = new Vector3D(-event.stageX / stage.stageWidth * 6 + 3, 
								-event.stageY / stage.stageHeight * 6 + 3, 1);
			lNW_.normalize();
		}

		private function faceCompFunc(f0:Face, f1:Face):Number {
			return minDistSq(f0) - minDistSq(f1);
		}
		
		private function minDistSq(f:Face):Number {
			var minDist:Number = Number.MAX_VALUE;
			for each (var i:int in f.index_) {
				var dist:Number = vsW_[i * 3] * vsW_[i * 3] + 
					vsW_[i * 3 + 1] * vsW_[i * 3 + 1];
				if (dist < minDist) {
					minDist = dist;
				}
			}
			return minDist;
		}
		
		private function onEnterFrame(event:Event):void {
			switch(movementType_) {
				case MOVE_SIN:
					moveSin();
					break;
				case MOVE_PERLIN:
					movePerlin();
					break;
			}
			update();
			draw();
		}
		
		public function moveSin():void {
			var xdiv:Number = 4 + Math.sin(getTimer() / 3000) * 2;
			var ydiv:Number = 4 + Math.cos(getTimer() / 3000) * 2;

			var h:Number;
			for (var y:Number = 0; y <= MAX_Y; y++) {
				for (var x:Number = 0; x <= MAX_X; x++) {
					h = 1000 + 
						Math.sin(x/xdiv - getTimer() / 500) * 100 + 
						Math.sin(y/ydiv - getTimer() / 500) * 100;
					vsW_[(y * (MAX_X + 1) + x) * 3 + 2] = h;
				}
			}
		}
		
		private var perlinBitmapData:BitmapData = 
			new BitmapData(MAX_X + 1, MAX_Y + 1, false);

		public function movePerlin():void {
			var offsets:Array = [
				new Point(-getTimer() / 100, -getTimer() / 100), 	
				new Point(getTimer() / 100, getTimer() / 100),
				new Point(-getTimer() / 200, -getTimer() / 200)];
			
			perlinBitmapData.perlinNoise(MAX_X, MAX_Y, 3, 1, false, true, 7, 
				true, offsets);

			for (var y:Number = 0; y <= MAX_Y; y++) {
				for (var x:Number = 0; x <= MAX_X; x++) {
					var h:Number = perlinBitmapData.getPixel(x, y) & 0xFF;
					h = 800 + h * 2;
					vsW_[(y * (MAX_X + 1) + x) * 3 + 2] = h;
				}
			}
		}

		public function update():void {
			vNW_.length = 0;
			var n:int;
			for (n = 0; n < vsW_.length / 3; n++) {
				vNW_.push(new Vector3D(0, 0, 0, 0));
			}
			
			var faceNW:Vector3D = new Vector3D;
			for (var i:int = 0; i < index_.length; i += 3) {
				computeNormal(
					vsW_[index_[i] * 3], vsW_[index_[i] * 3 + 1], 
						vsW_[index_[i] * 3 + 2], 
					vsW_[index_[i + 1] * 3], vsW_[index_[i + 1] * 3 + 1], 
						vsW_[index_[i + 1] * 3 + 2], 
					vsW_[index_[i + 2] * 3], vsW_[index_[i + 2] * 3 + 1], 
						vsW_[index_[i + 2] * 3 + 2], 
					faceNW);
				vNW_[index_[i]] = vNW_[index_[i]].add(faceNW);
				vNW_[index_[i + 1]] = vNW_[index_[i + 1]].add(faceNW);
				vNW_[index_[i + 2]] = vNW_[index_[i + 2]].add(faceNW);
			}
			light_.length = 0;
			for (n = 0; n < vNW_.length; n++) {
				vNW_[n].normalize();
				light_.push(Math.max(0, Math.min(1, 
					lNW_.dotProduct(vNW_[n]))));
			}
		}
		
		public function draw():void {
			// create matrix
			var m:Matrix3D = new Matrix3D();
			var pp:PerspectiveProjection = new PerspectiveProjection();
			pp.focalLength = 3;
			pp.fieldOfView = 48;
			 
			m = pp.toMatrix3D();

			// compute screen coords
			vsS_.length = 0;
			Utils3D.projectVectors(m, vsW_, vsS_, uvt_);
			
			// draw triangles
			var graphicsData:Vector.<IGraphicsData> = 
				new Vector.<IGraphicsData>;
			
			//graphicsData.push(new GraphicsTrianglePath(vsS_, index_, uvt_));
			for (var i:int = 0; i < index_.length; i += 3) {
				drawShadedTriangle(graphicsData, 
					new Vector3D(vsS_[index_[i] * 2], 
						vsS_[index_[i] * 2 + 1], 0),
					new Vector3D(vsS_[index_[i + 1] * 2], 
						vsS_[index_[i + 1] * 2 + 1], 0),
					new Vector3D(vsS_[index_[i + 2] * 2], 
						vsS_[index_[i + 2] * 2 + 1], 0),
					light_[index_[i]], light_[index_[i + 1]], 
					light_[index_[i + 2]]);
			}
			
			var g:Graphics = shape.graphics;
			g.clear();
			//g.lineStyle(1, 0x00FF00);
			g.drawGraphicsData(graphicsData);
		}

		static public function computeNormal(
				p0x:Number, p0y:Number, p0z:Number,
				p1x:Number, p1y:Number, p1z:Number,
				p2x:Number, p2y:Number, p2z:Number, 
				result:Vector3D):void {
			result.x = (p1y - p0y) * (p2z - p0z) - (p1z - p0z) * (p2y - p0y);
			result.y = (p1z - p0z) * (p2x - p0x) - (p1x - p0x) * (p2z - p0z);
			result.z = (p1x - p0x) * (p2y - p0y) - (p1y - p0y) * (p2x - p0x);
		}

		static private const SHADE_SIZE:Number = 819.2; // 2^13 / 10 = 819.2
		static private const fillType:String = GradientType.LINEAR;
		static private const colors:Array = [0x000000, 0x000000];
		static private const alphas:Array = [1, 0];
		static private const ratios:Array = [0x00, 0xFF];
		static private const spreadMethod:String = SpreadMethod.PAD;
		static private const commands:Vector.<int> = Vector.<int>(
				[GraphicsPathCommand.MOVE_TO, GraphicsPathCommand.LINE_TO, 
				 GraphicsPathCommand.LINE_TO, GraphicsPathCommand.LINE_TO]);

		private function drawShadedTriangle(
				graphicsData:Vector.<IGraphicsData>, 
				p0S:Vector3D, p1S:Vector3D, p2S:Vector3D, 
				s0:Number, s1:Number, s2:Number):void {
			
			var s0s1:Boolean = s0 - s1 < .001 && s0 - s1 > -.001;
			var s1s2:Boolean = s1 - s2 < .001 && s1 - s2 > -.001;
			
			if (s0s1 && s1s2) {
				graphicsData.push(new GraphicsSolidFill(0x000000, 1 - s0));
			} else {
				if (s0s1) {
					var pTempS:Vector3D = p0S;
					var sTemp:Number = s0;
					p0S = p1S;
					s0 = s1;
					p1S = p2S;
					s1 = s2;
					p2S = pTempS;
					s2 = sTemp;
				}
					
				var tempM:Matrix = new Matrix(
					p1S.x - p0S.x, p1S.y - p0S.y, 
					p2S.x - p0S.x, p2S.y - p0S.y, 
					p0S.x, p0S.y);
				var a11:Number = SHADE_SIZE * 2 * (s1 - s0);
				var a21:Number = SHADE_SIZE * 2 * (s2 - s0);
				var a31:Number = -SHADE_SIZE + SHADE_SIZE * 2 * s0;
	
				var m:Matrix = 
					new Matrix(1 / a11, 0, -a21 / a11, 1, -a31 / a11, 0);
	
				m.concat(tempM);
	
				graphicsData.push(new GraphicsGradientFill(fillType, colors, 
					alphas, ratios, m, spreadMethod));
			}

			graphicsData.push(new GraphicsPath(commands, 
				Vector.<Number>([p0S.x, p0S.y, p1S.x, p1S.y, 
					p2S.x, p2S.y, p0S.x, p0S.y])));
			graphicsData.push(new GraphicsEndFill);
		}
	}
}

import __AS3__.vec.Vector;

class Face {
	public var index_:Vector.<int>;
	
	public function Face(i0:int, i1:int, i2:int):void {
		index_ = Vector.<int>([i0, i1, i2]);
	}
}