class OctNode { OctNode Parent; OctNode[] children; int NumChildren = 0; PVector Center; PVector[] bounds; PVector Dim; color Color; float Opacity = 10; float Intensity; float IntensityLastFrame; Timer resting; PVector euler; PVector target; PVector direction; final float ROTATION_THRESH = 1.0; final float ROTATION_VELOCITY = .005; final float MININTENSITY = .005; final float BLEED = .004; final int MINREST = 3000; final int MAXREST = 30000; boolean drawnThisFrame = false; OctNode( OctNode parent, int levels, PVector inner, PVector outer ) { this.Parent = parent; resting = new Timer( (int)random( MINREST, MAXREST ) ); direction = new PVector(); euler = new PVector(); target = new PVector(); CalculateBounds( inner, outer ); AddLevel( levels - 1 ); Color = color( 255, 255, 255 ); } void CalculateBounds( PVector inner, PVector outer) { NumChildren = 8; bounds = new PVector[NumChildren]; bounds[0] = inner; bounds[1] = new PVector( inner.x, inner.y, outer.z ); bounds[2] = new PVector( inner.x, outer.y, inner.z ); bounds[3] = new PVector( inner.x, outer.y, outer.z ); bounds[4] = new PVector( outer.x, inner.y, inner.z ); bounds[5] = new PVector( outer.x, inner.y, outer.z ); bounds[6] = new PVector( outer.x, outer.y, inner.z ); bounds[7] = outer; Center = PVector.add( inner, outer ); Center.mult( .5 ); Dim = abs( PVector.sub(inner,outer) ); } void AddLevel( int levels ) { if ( levels > 0 ) { this.children = new OctNode[NumChildren]; for( int i = 0; i < NumChildren; i++ ) { children[i] = new OctNode( this, levels, new PVector(), PVector.sub( Center, bounds[i] ) ); } } } void GetLeafNodes( ArrayList accumulator ) { if ( children == null ) { accumulator.add( this ); } else { for( int i = 0; i < NumChildren; i++ ) { children[i].GetLeafNodes( accumulator ); } } } void GetAllNodes( ArrayList accumulator ) { accumulator.add( this ); if( children != null ) { for( int i = 0; i < NumChildren; i++ ) { children[i].GetAllNodes( accumulator ); } } } // BOTTOM UP UPDATE CALL // Because we aggregate data from the bottom up... void Tap(float intensity) { float delta = intensity / ( NumChildren + 1 ); Intensity += delta; if( Parent != null ) { Parent.Tap( intensity ); } if ( Intensity > ROTATION_THRESH) { Kick(); } } void Update( int delta ) { IntensityLastFrame = Intensity - BLEED * delta; Intensity = 0; resting.Update( delta ); Traverse( delta ); if ( children != null ) { for( int i = 0; i < NumChildren; i++ ) { children[i].Update( delta ); } } } // TOP DOWN DRAW CALL // This rips down from the Root in most cases; void Draw() { pushMatrix(); translate( Center.x, Center.y, Center.z ); rotateX( euler.x ); rotateY( euler.y ); rotateZ( euler.z ); //Resolve the true intensity at the last posisble section; Intensity = max( Intensity, IntensityLastFrame ); if ( Intensity > MININTENSITY && Parent != null) { fill( Color, Opacity * min( 1, Intensity ) ); box( Dim.x, Dim.y, Dim.z ); //popMatrix(); } if ( children != null ) { for( int i = 0; i < NumChildren; i++ ) { children[i].Draw(); } } popMatrix(); } void Kick() { if ( resting.IsDone() ) { int axis = (int)random(6); switch( axis ) { case 0: direction.x = 1; break; case 1: direction.y = 1; break; case 2: direction.z = 1; break; case 3: direction.x = -1; break; case 4: direction.y = -1; break; case 5: direction.z = -1; break; } target = PVector.add( euler, PVector.mult( direction, HALF_PI ) ); resting.Reset(); } } void Reset() { direction.set( 0, 0, 0 ); } void Traverse( int delta ) { PVector pre = PVector.sub( target, euler ); euler.add( PVector.mult( direction, delta * ROTATION_VELOCITY )); PVector post = PVector.sub( target, euler ); if( CrossedBound( pre.x, post.x, direction.x ) || CrossedBound( pre.y, post.y, direction.y ) || CrossedBound( pre.z, post.z, direction.z ) ) { euler.set( target.x, target.y, target.z ); Reset(); } } boolean CrossedBound(float pre, float post, float movement ) { if ( movement > .01 || movement < -.01 ) { // Moving in current direction boolean signPre = (pre > 0 ); boolean signPost = ( post > 0 ); return (signPre != signPost); } return false; } }