package com.dafishinsea.tutorials.normalmap { import com.bit101.components.HSlider; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Shader; import flash.display.ShaderJob; import flash.display.Shape; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.display.TriangleCulling; import flash.events.Event; import flash.filters.ShaderFilter; import flash.geom.Matrix3D; import flash.geom.PerspectiveProjection; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.Utils3D; import flash.geom.Vector3D; import flash.utils.ByteArray; import net.hires.debug.Stats; [SWF(backgroundColor="0x000000", width="800", height="600", frameRate="50")] public class NormalMap4 extends Sprite { private var mapWidth:int = 640; private var mapHeight:int = 640; private var texture:BitmapData = new BitmapData(mapWidth,mapHeight,false,0xFFFFFF); private var light:Vector3D = new Vector3D(100,100,100); private var normalMap:BitmapData = new BitmapData(mapWidth,mapHeight,false,0xFFFFFF); private var normalBmp:Bitmap = new Bitmap(normalMap); private var normalMapShp:Shape = new Shape(); private var vertices:Vector.; private var indices:Vector.; private var uvtData:Vector.; private var perspective: PerspectiveProjection; private var projectionMatrix : Matrix3D; private var rotationMatrix : Matrix3D; private var projectedVerts:Vector.; private var focalLength:Number = 50; private var container:Sprite; private const PI:Number = Math.PI;//half revolution in radians private const HALFPI:Number = Math.PI/2;//1/4 revolution in radians private const TWOPI:Number = 2*Math.PI;//full revolution in radians //x,yz rotation private var rx:Number = 60; private var ry:Number = 40; private var rz:Number = 30; private var testBmp:Bitmap; private var r:Number = 0;//yrotation of sphere private var normalMapShader:Shader; private var shaderJob:ShaderJob; private var input:ByteArray; private var output:ByteArray; //insert stage size as vars to prevent browser rendering glitches private var stageWidth:Number = 800; private var stageHeight:Number = 600; [Embed(source="NormalMapShader.pbj", mimeType="application/octet-stream")] private var NormalMapShader:Class; [Embed(source="jupiter640x640.jpg")] public var jupiter640x640:Class; private var jupiterMed:BitmapData = Bitmap(new jupiter640x640()).bitmapData; public function NormalMap4() { init(); } private function init():void { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; vertices = new Vector.(); indices = new Vector.(); uvtData = new Vector.(); //set up perspective perspective = new PerspectiveProjection(); perspective.fieldOfView = 50; //3D transformation matrix - used to rotate object projectionMatrix = perspective.toMatrix3D(); rotationMatrix = new Matrix3D();//used to keep track of rotation of the sphere projectedVerts = new Vector.(); //container to hold scene container = new Sprite(); container.x = stageWidth/2; container.y = stageHeight/2; addChild(container); createSphere(100, 20, 40); createNormalMap(); createShaderJob(); addEventListener(Event.ENTER_FRAME, onEnterFrame); addEventListener(Event.RENDER, render, false, 0, true); makeGUI(); addChild(new Stats()); } /** * create sphere geometry (vertices/indices) and texture coordinates (uvt) */ private function createSphere(radius:Number, rows:int, cols:int):void { var lon_incr:Number = TWOPI/cols; var lat_incr:Number = PI/rows; var lat:Number = 0;//angle of rotation around the x axis var lon:Number = 0;//angle of rotation around the y axis, *in radians* var x:Number, y:Number, z:Number; var vnum:int = 0; var ind:int = 0; for(var h:int = 0; h <= rows; h++) { lon = 0; y = radius*Math.cos(lat); for(var v:int = 0; v <= cols; v++) { x = radius*Math.cos(lon)*Math.sin(lat); z = radius*Math.sin(lon)*Math.sin(lat);//seen from above, z = y //1. add vertex triplet vertices[vnum] = x; vertices[vnum+1] = y; vertices[vnum+2] = z; //2. uvts uvtData[vnum] = v/cols; uvtData[vnum+1] = h/rows; uvtData[vnum+2] = 1; vnum+=3; //3. add indices if(h < rows && v < cols){ indices.push(ind, ind+1, ind + cols+1); indices.push(ind + cols+1, ind+1, ind + cols + 2); } ind+=1; lon += lon_incr; } lat += lat_incr; } } /** * create ShaderJob for use later */ private function createShaderJob():void { normalMapShader = new Shader(new NormalMapShader()); normalMapShader.data.src.width = mapWidth; normalMapShader.data.src.height = mapHeight; normalMapShader.data.src.input = normalMap; normalMapShader.data.texture.width = mapWidth; normalMapShader.data.texture.height = mapHeight; normalMapShader.data.texture.input = jupiterMed ; shaderJob = new ShaderJob(normalMapShader, texture, mapWidth, mapHeight); shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler); shaderJob.start(); } /** * shade job complete */ private function shaderJobCompleteHandler(event:Event):void { //update params and restart job var lightDir:Vector3D = rotationMatrix.transformVector(light); lightDir.normalize(); normalMapShader.data.xr.value = [lightDir.x]; normalMapShader.data.yr.value = [lightDir.y]; normalMapShader.data.zr.value = [lightDir.z]; shaderJob = new ShaderJob(normalMapShader, texture, mapWidth, mapHeight); shaderJob.addEventListener(Event.COMPLETE, shaderJobCompleteHandler); shaderJob.start(); } /** * create normal map --evaluate normal for each pixel based lat/lon at that position in the sphere */ private function createNormalMap():void { var radius:Number = 1; var lon:Number = 0; var lat:Number = 0; for(var v:int = 0; v < mapHeight; ++v) { lat = (v/mapHeight)*PI; var y3d:Number = radius*Math.cos(lat); for(var u:int = 0; u < mapWidth; ++u) { lon = (u/mapWidth)*TWOPI; var x3d:Number = radius*Math.cos(lon)*Math.sin(lat); var z3d:Number = radius*Math.sin(lon)*Math.sin(lat); //normal is vector from center of sphere (consider = origin) to this point var norm:Vector3D = new Vector3D(x3d,y3d,z3d); norm.normalize(); var r:uint = 255*(norm.x + 1)/2; var g:uint = 255*(norm.y + 1)/2; var b:uint = 255*(norm.z + 1)/2; var normalColor:uint = r << 16 | g << 8 | b; normalMap.setPixel(u,v,normalColor); } } } /** * makeGUI */ private function makeGUI():void { //lightPos sliders var lightXposSldr:HSlider = new HSlider(this, 490, 10, onLightXposChange); lightXposSldr.value = 100; var lightYposSldr:HSlider = new HSlider(this, 490, 30, onLightYposChange); lightYposSldr.value = 100; var lightZposSldr:HSlider = new HSlider(this, 490, 50, onLightZposChange); lightZposSldr.value = 100; } /** * change handlers for light pos */ private function onLightXposChange(event:Event):void { light.x = (event.target.value-50)*2; } private function onLightYposChange(event:Event):void { light.y = (event.target.value-50)*2; } private function onLightZposChange(event:Event):void { light.z = (event.target.value-50)*2; } private function onEnterFrame(event:Event):void { update(); stage.invalidate();//trigger render event } private function update():void { r+=0.5; rotationMatrix = new Matrix3D(); rotationMatrix.prependRotation(-r, new Vector3D(0,1,0)) projectionMatrix = perspective.toMatrix3D(); projectionMatrix.prependTranslation(0.0,0.0,250); projectionMatrix.prependRotation(r, new Vector3D(0,1,0)) } private function render(e:Event=null):void { container.graphics.clear(); Utils3D.projectVectors(projectionMatrix, vertices, projectedVerts, uvtData); container.graphics.beginBitmapFill(texture,null, false, false); container.graphics.drawTriangles(projectedVerts, indices, uvtData, TriangleCulling.POSITIVE); container.graphics.endFill(); } } }