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

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

RGB/HSV/HSL Color Cube

An improved version of my RGB cube (which I forked by accident, causing me to create this).

Interaction:
A-D toggles through rgb,hsv and hsl color models (D toggles right, A toggles left, starting at rgb).
Space stops the automatic rotation.
Left/Right spins the cube around manually.

The red, green and blue lines represent the three color channels (rgb, hsv, hsl) respectively.

My friends and I had some discussions about mixing color theory and linear algebra. One of the things we talked about was having color components as base vectors in a coordinate system, and this is pretty much just a visualization of that.

Rendered in some faux-3d kind of way, nothing too interesting there.
// Beware of sloppy coding. I'm full of it!
// Do you like descriptive, or at least reasonably
// logical variable names? Good for you, but you won't
// find such things here. ;)
package {
    import flash.events.KeyboardEvent;
    import flash.geom.Point;
    import flash.filters.ColorMatrixFilter;
    import flash.events.Event;
    import flash.display.Sprite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    [SWF(width = "465", height = "465", backgroundColor = "0x000000")]
    public class FlashTest extends Sprite {
        public function FlashTest() {
            bmd = new BitmapData(BMD_HEIGHT,BMD_WIDTH,true,0xff000000);
            stage.addChild(new Bitmap(bmd));
            
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
            stage.addEventListener(Event.ENTER_FRAME, doStuff);
        }
        
        private function keyDown(e:KeyboardEvent):void {
            if(e.keyCode == 68) {
                mode = (mode+1) % 3;
            } else if (e.keyCode == 65) {
                mode = (mode+2) % 3;
            }
            
            if(e.keyCode == 32) {
                if(spinning) {
                    forcedTheta = time;
                } else {
                    time = forcedTheta;
                }
                spinning = !spinning;
            }
            
            if(e.keyCode == 37) {
                dT += 0.01;
            } else if (e.keyCode == 39) {
                dT -= 0.01;
            }
        }
        
        private var mode:int = 0;
        
        private var spinning:Boolean = true;
        private var dT:Number = 0;
        private var forcedTheta:Number = 0; 
        
        private var bmd:BitmapData;
        private var time:Number = 0;
        private const TIMESTEP:Number = 0.01;
        private const FRAMES_BETWEEN_ACTIONS:int = 0;
        private var count:int = FRAMES_BETWEEN_ACTIONS;
        
        private const BG_COLOR:uint = 0xff222222;
        private const BMD_HEIGHT:int = 464;
        private const BMD_WIDTH:int = 464;
        private const BMD_NUMPIX:int = BMD_HEIGHT*BMD_WIDTH;
        private var pixels:Vector.<uint> = new Vector.<uint>(BMD_HEIGHT*BMD_WIDTH, true);
        
        private var cmf:ColorMatrixFilter = new ColorMatrixFilter(
                                [0.9, 0, 0, 0, 0,
                                 0, 0.9, 0, 0, 0,
                                 0, 0, 0.9, 0, 0,
                                 0, 0, 0, 0.97, 0]);
        
        private function doStuff(e:Event):void {
            if(spinning)
                time += TIMESTEP;
            else {
                forcedTheta += dT;
                dT *= 0.95;
                dT = (dT<0 ? -dT : dT) < 0.0001 ? 0 : dT;
            }
            
            if(++count > FRAMES_BETWEEN_ACTIONS) {
                count = 0;
                
                if(spinning)
                    drawStuff(time);
                else
                    drawStuff(forcedTheta);
            }
            
        }
        
        private function drawStuff(theta:Number):void { 
            // A density constant, fun to play around with!
            var D:int = 11;
            
            // Half width height and depth
            var hW:int, hH:int, hD:int;
            hW = hH = hD = D;
            var X:Number, Y:Number, Z:Number;
            var index:int, color:uint;
            var sc:Number = 110/D;
            
            // Precalculate trig values, because calculating cos and sin of theta
            // (2D+1)^3 [or in this case 12167] times per frame instead of one time
            // is just plain stupid!
            var sinTheta:Number = Math.sin(theta);
            var cosTheta:Number = Math.cos(theta);
            
            var x:Number, y:Number, z:Number;
            
            // Clear vector, or use trails?
            if(true) {
                var l:int = pixels.length;
                while(--l >= 0) {
                    pixels[l] = BG_COLOR;
                }
            } else {
                bmd.applyFilter(bmd, bmd.rect, new Point(), cmf);
                pixels = bmd.getVector(bmd.rect);
            }
            
            for (X = -hW; X<=hW; ++X) {
                for (Y = -hH; Y<=hH; ++Y) {
                    for (Z = -hD; Z<=hD; ++Z) {
                        // Set color based on coordinate
                        if(mode == 0)
                            color = rgbaToHex((X+hW)/(2*hW), (Y+hH)/(2*hH), (Z+hD)/(2*hD), 1);
                        else if(mode == 1)
                            color = hsvaToHex((X+hW)/(2*hW), (Y+hH)/(2*hH), (Z+hD)/(2*hD), 1);
                        else
                            color = hslaToHex((X+hW)/(2*hW), (Y+hH)/(2*hH), (Z+hD)/(2*hD), 1);
                        
                        // Get transformed coordinate index
                        index = transPtIndex(X,Y,Z,sinTheta,cosTheta,sc);
                        
                        // Color a 2x2 square
                        if(index+BMD_WIDTH+1 < BMD_NUMPIX && index > 0) {
                            pixels[index] = color;
                            pixels[index+1] = color;
                            pixels[index+BMD_WIDTH] = color;
                            pixels[index+BMD_WIDTH+1] = color;
                        }
                    }
                }    
            }
            
            // Time to actually draw stuff
            bmd.lock();
            // Draw all colored points
            bmd.setVector(bmd.rect, pixels);
            
            // Define corners
            var p1:Point = transPt(new Point(-hW, -hH), sinTheta, cosTheta, sc, -hD);
            var p2:Point = transPt(new Point(-hW,  hH), sinTheta, cosTheta, sc, -hD);
            var p3:Point = transPt(new Point( hW, z hH), sinTheta, cosTheta, sc, -hD);
            var p4:Point = transPt(new Point( hW, -hH), sinTheta, cosTheta, sc, -hD);
            var p5:Point = transPt(new Point(-hW, -hH), sinTheta, cosTheta, sc,  hD);
            var p6:Point = transPt(new Point(-hW,  hH), sinTheta, cosTheta, sc,  hD);
            var p7:Point = transPt(new Point( hW,  hH), sinTheta, cosTheta, sc,  hD);
            var p8:Point = transPt(new Point( hW, -hH), sinTheta, cosTheta, sc,  hD);
            
            // Draw lines between corners to create a boundary cube
            
            var red:uint = 0xaa0000;
            var gre:uint = 0x00aa00;
            var blu:uint = 0x0000aa;
            var col:uint = 0xaaaaaa;
            efla(p1,p2,gre,bmd);
            efla(p2,p3,col,bmd);
            efla(p3,p4,col,bmd);
            efla(p4,p1,red,bmd);
            
            efla(p5,p6,col,bmd);
            efla(p6,p7,col,bmd);
            efla(p7,p8,col,bmd);
            efla(p8,p5,col,bmd);
            
            efla(p1,p5,blu,bmd);
            efla(p2,p6,col,bmd);
            efla(p3,p7,col,bmd);
            efla(p4,p8,col,bmd);
            
            bmd.unlock();
        }
        
        private function hslaToHex(h:Number,s:Number,l:Number,a:Number):uint {
            var C:Number, X:Number, m:Number, H:Number;
            m = 2*l-1;
            m = m<0 ? -m : m;
            C = (1-m)*s;
            
            H = 6*h;
            m = H%2 - 1;
            m = m<0 ? -m : m;
            X = C*(1-m);
            
            m = l - C/2;
            
            if(H <= 1) {
                return rgbaToHex(C+m,X+m,m,  a);
            } else if (H <= 2) {
                return rgbaToHex(X+m,C+m,m,  a);
            } else if (H <= 3) {
                return rgbaToHex(m,  C+m,X+m,a);
            } else if (H <= 4) {
                return rgbaToHex(m,  X+m,C+m,a);
            } else if (H <= 5) {
                return rgbaToHex(X+m,m,  C+m,a);
            } else {
                return rgbaToHex(C+m,m,  X+m,a);
            }
            
            return 0x00000000;
        }
        
        private function hsvaToHex(h:Number,s:Number,v:Number,a:Number):uint {
            var C:Number, X:Number, m:Number, H:Number;
            C = v*s;
            H = 6*h;
            m = H%2 - 1;
            m = m<0 ? -m : m;
            X = C*(1-m);
            m = v-C;
            
            if(H <= 1) {
                return rgbaToHex(C+m,X+m,m,  a);
            } else if (H <= 2) {
                return rgbaToHex(X+m,C+m,m,  a);
            } else if (H <= 3) {
                return rgbaToHex(m,  C+m,X+m,a);
            } else if (H <= 4) {
                return rgbaToHex(m,  X+m,C+m,a);
            } else if (H <= 5) {
                return rgbaToHex(X+m,m,  C+m,a);
            } else {
                return rgbaToHex(C+m,m,  X+m,a);
            }
            
            return 0x00000000;
        }
        
        private function rgbaToHex(r:Number,g:Number,b:Number,a:Number):uint {
            return (0xff*a << 24) | (0xff*r << 16) | (0xff*g << 8) | 0xff*b;
        }

        
        // Function to transform a single point
        private function transPt(p:Point, stheta:Number, ctheta:Number, sc:Number, z:Number):Point {
            var x:Number = p.x*ctheta + p.y*stheta;
            var y:Number = p.y*ctheta - p.x*stheta;
            z = -z;
            
            p.x = BMD_WIDTH/2+(x+y)*sc;
            p.y = BMD_HEIGHT/2+((x-y)/3 + 1.2*z)*sc;
            
            return p;
        }
        
        private function transPtIndex(x:Number, y:Number, z:Number, stheta:Number, ctheta:Number, sc:Number):int {
            var p:Point = transPt(new Point(x,y), stheta, ctheta, sc, z);
            
            return int(p.x) + BMD_WIDTH*int(p.y);
        }

        // An implementation of a "Extremely Fast Line Algoritm"
        // Just google it and you'll find the code I based it off
        private function efla(p1:Point, p2:Point, color:uint, bmd:BitmapData): void
        {
            var x:int =  p1.x; var y:int =  p1.y;
            var x2:int = p2.x; var y2:int = p2.y;
            
            var shortLen:int = y2-y;
            var longLen:int = x2-x;
 
            if ((shortLen ^ (shortLen >> 31)) - (shortLen >> 31) > (longLen ^ (longLen >> 31)) - (longLen >> 31)) {
                shortLen ^= longLen;
                longLen ^= shortLen;
                shortLen ^= longLen;
 
                var yLonger:Boolean = true;
            } else
                yLonger = false;
 
            var inc:int = longLen < 0 ? -1 : 1;
 
            var multDiff:Number = longLen == 0 ? shortLen : shortLen / longLen;
 
            if (yLonger)
                for (var i:int = 0; i != longLen; i += inc)
                    bmd.setPixel(x + i*multDiff, y+i, color);
            else
                for (i = 0; i != longLen; i += inc)
                    bmd.setPixel(x+i, y+i*multDiff, color);
        }
    }
}