Hammond L100 organ. This post is a deeper discussion of the project internals. Orignally it was a little proof of concept of what was and will be going on in the out in the wood working shop.


This gives a former computer engineer a way to exercise his skills long after he's professionally retired from the industry.


Sticking to the POVray project, this post's description timeline runs in reverse; it seems to tell the story better. If I started the design with intent to publish, I'd have done things differently.


ANIMATION WITH SPLINES:

Animation of the chapters allowed me two variables over a chapter sequence: number of frames and the resolution of the activity. Each chapter had a Master_clock which was scaled to clock and frame_number. Thus, I used clock from 0.0 to 1.0 to move the components and camera along paths. Typically there us a data structure for all dynamic components of the scene-- basically an array of uv vectors, for { Action, Origin, Final, Current} . Master_clock runs from 0 - 100, so things happened as a percent of the sequence. Action.u was a start point, Action.v holds when to stop, negative values meant it was completed and I could use a faster object in its place.  I may have one entry as itemA_Position and itemA_Rotate. Extended it to light fades and some textural things. The "Wiring and Harness" Chapter has over 28 components concurrently moving. All of this is independent of frames per second or total frames, which were determined at render time.


This Master_clock global was passed onto an individual object of the scene as an argument for its transformation. Each object had its basic location. Furthermore, if it were subjected to animation, it had a transform associated with it. Thus, I maintained cleaner code when not animating (clock_on = false ) which served a sanity check.


Thanks to Lohmüller's tutorials on splines. They are used throughout to build wires and harnesses as well as transforms. I threw in the likes of Cos_01() macros to manage acceleration. 


Somewhere along my journey I adopted LightSys IV for my lighting. I began to think of light color as a spectrum represented by a spline. Okay. Basically, then a line is a simply defined curve. Time is a curve. This tool can manage it.


** You might be raytracing too long when you cease thinking of light and pigment as a component vector and think of it as a spectral spline curve.


For example, in the MirrorWithStand example, when Master_clock is between 2 and 12, ComponentTimeline tells MirrorWithStand to transform a percentage of the  spline. In RotateMirror_Spline and TranslateMirror_Spline, I've designated a single point 0.3 through its travels it be rotated and translated to a specific node. 


When playing with splines, its imperative to develop a macro ShowSpline(). 

I also have a global, SplineSamples, which controls how many spheres makes up a wire over some interval. There is a reasonable low amount if the spline is developed properly -- to make it smooth without separations.  I  scaled these spheres by 1.5 along the  Z-axis without artifacts, even on tight curves. Making wires by way of stacking spheres, and  SplineSamples = 500, caused the image to have >4million objects. When the wires were not visible, don't parse that area of the code. 


I spent about 20 hours tuning the splines for wiring, harnesses, and respective plug endpoints. THE WIRES DO NOT TOUCH! Had to re-design almost all of the plugs  and their 'connection' origins to the wire endpoints. The next project will be even better. As we say in my Minnesota homeland, "Pret'y near good enough."



SPLINE ANIMATION CODE SNIP:


// Begin and End mark 0 - 100

// Component[0].u = BeginMark

// Component[0].v = EndMark

// Component[1].u= Current position

#declare ComponentTimeline = array[17][2] {

{ < 0,  8>, <0, 0> }, // LegsFromTop

{ < 2, 12>, <0, 0> }, // MirrorWithStand <-----

{ < 9, 16>, <0, 0> }, // BoxSide

};


// a unit cube of a three plys -- backing, reflective surface, and glass

#macro MirrorWithStand( MirrorSz )

union {

box { -0.5 0.5*xy scale <0.98, 0.98, 0.5> texture {T_Carcass}}// Mirror back

box { -0.5 0.5 scale <0.975, 0.975, 0.025>

texture { pigment { rgb 0.8 } // Mirror film

finish { ambient 0.0 diffuse 0.0 brilliance 8 

reflection{ 0.9 metallic } 

metallic phong 0.8 phong_size 120 

}}

}

box { -0.5*xy 0.5 scale <1,1,0.5>

material { texture {Glass} interior {I_Glass} } 

} // Mirror glass

scale MirrorSz

}

#end




// This is the core animation "engine" of the running code

// It updates the ComponentTimeline array to the current clock and frame_number

//

#declare Master_clock = 0;

#if( clock_on )

#declare Master_clock = frame_number / final_frame * 100; 


#for( item, 0, dimension_size(ComponentTimeline, 1) - 1 )

#if ( Master_clock > ComponentTimeline[item][0].v )

#declare ComponentTimeline[item][1] = 1*u;

#else 

#if ( Master_clock >= ComponentTimeline[item][0].u )

#declare Interval = (1/(ComponentTimeline[item][0].v-ComponentTimeline[item][0].u));

#declare IntervalClock = Master_clock - ComponentTimeline[item][0].u;

#declare ComponentTimeline[item][1] = u * Cos_01( Interval * IntervalClock);

#else 

#declare ComponentTimeline[item][1] = <-1, -1>;

#end

#end

#end

#else

#for( item, 0, dimension_size(ComponentTimeline, 1) - 1 )

#declare ComponentTimeline[item][1] = ComponentTimeline[item][0].v*v;

#end

#end


// Actual instantiation of object

object { MirrorWithStand(<46, 24, 1>)  XfrmMirror(ComponentTimeline[1][1].u) }



#macro XfrmMirror(Clock)

transform { 

rotate rotateMirror_Spline( Clock )

translate TranslateMirror_Spline( Clock ) 

} 

#end


#declare RotateMirror_Spline = 

spline { natural_spline

0.0, <  60, 30, 0>, //start

0.3, <  40, 5, 0>, 

1.0, <  -8, 0, 0 >, // end

};

#declare TranslateMirror_Spline = 

spline { natural_spline

-0.2, <  -50, 60, -54>, 

0.0, <  -48, 54, -48>, //start

0.3, <  -24, 48, -22>, 

1.0, <0, 36, -20 >, // end

};



Again, this would be a little more streamlined if it were initially designed to be animated. Its just one way to extend a basic object to an animated object.



HARNESS

Building a harness of wires sent me on a journey into Fibonacci series, phyllotaxis and close packed arrays. I basically calculated the valences manually since I didn't look for and adapt an algorithm. The first 7 steps in the sequence of wire bundles is described in ClosePackedArray for each layer of wires.


The following example code is for HarnessSegment():


#declare ClosePackedArray = array[ 7 ] {

1, 

2*(1+2), 

2*(1+2+3), 

2*(2+3+4), 

2*(3+4+5), 

2*(4+5+6), 

2*(5+6+7)

};

#macro ClosePackedArrayXfrm( wireNumber, Layer, WireSz )

#local Rotate = 0*z;

#if ( Layer > 1 ) #local K = wireNumber - ClosePackedArray[Layer-1];

#end

#switch ( Layer )

#range ( 0, 1 )

#local Offset = 4*cosd(60);

#local Rotate = 30*z;

#break

#case ( 2 )

#if ( mod( K, Layer ) ) #local Offset = 4*cosd(60);

#else #local Offset = 2*sind(60); #end

#break

#case ( 3 )

#if ( mod ( K, Layer ) ) #local Offset = 3.5* cosd(60);

#else #local Offset = 2.25* sind(60); #end

#local Rotate = 30*z;

#break

#case ( 4 )

#if ( mod ( K, Layer ) ) #local Offset = 3.5* cosd(60);

#else #local Offset = 2.25* sind(60); #end

#break

#case ( 5 ) // There's errors here, but close enough

#if ( mod ( K, Layer ) ) #local Offset = 3.625* cosd(60);

#else #local Offset = 2.25* sind(60); #end

#local Rotate = 18*z;

#break

#else // 91

#debug "Haven't got here, yet.\n"

translate wireSz*Layer*x

#end

#local Translate = WireSz* Offset * Layer *

<sind(360*(wireNumber/ClosePackedArray[Layer])), 

cosd(360*(wireNumber/ClosePackedArray[Layer])), 0>;

// #debug concat( VStr( Translate ) )

translate Translate

rotate Rotate

#end


#macro HarnessSegment( Conductors, WireSz, NrSamples, Spline,  Color, VaryColor, Begin, End )

// #if ( End<1)

// #debug concat("HarnessSegment(..,", Str(Begin), ",", Str(End),")\n" )

// #end

// #if ( (Begin<0) | (End<0) ) #error "HarnessSegment()\tEndpoint < 0\n" #end

// #if ( (Begin>1) | (End>1) ) #error "HarnessSegment()\tEndpoint > 1\n" #end

// #if (Begin>End) #error "HarnessSegment()\tBegin > End\n" #end


#if( Conductors = 1 )

object { Wire( WireSz, Color, VaryColor ) }

#else

#declare Iter = Conductors;

#declare wireCount = 0;

#declare Layer = 0;

#declare MaxLayers = 0;

#for ( i, 0, dimension_size( ClosePackedArray, 1 ) - 1)

#declare MaxLayers = MaxLayers + ClosePackedArray[i];

#if ( MaxLayers > Conductors ) #break #end

#end


#local HarnessSilhouette =

union {

#while( (Iter) & (wireCount < ClosePackedArray[ Layer ]) )

sphere { 0, WireSz scale < 1, 1, 1.75 >

transform { ClosePackedArrayXfrm( Iter, Layer, WireSz ) }

texture { T_HarnessVaryColor( Color, VaryColor ) }

}

#declare wireCount = wireCount + 1;

#if( wireCount = ClosePackedArray[ Layer ] )

#declare wireCount = 0;

#declare Layer = Layer + 1;

#end

#declare Iter = Iter - 1;

#end

};


#local HarnessTwist = 180+NrSamples;

#local Foresight = 0.1; #local Banking = 0.5;

#if ( End <= Begin + 1/NrSamples )

object { HarnessSilhouette

Spline_Trans( Spline, Begin, y, Foresight, Banking ) 

}

#else

union {

#for ( Nr, Begin, End, 1/NrSamples )

object { HarnessSilhouette

rotate Nr* HarnessTwist*z

Spline_Trans( Spline, Nr, y, Foresight, Banking ) 

}

#end

texture { pigment{ color  Color} finish { phong 1.0 } }

} 

#end

#end

#end




TEXTURES

Could have done better with finer textures since antialiasing took most of the compute time. The brushed aluminum trim shimmered too much-- turn off reflection next time.


I tried turning on the subsurface translucency on for the white keys. The realism increased, so I use it on my keyboards in some other scenes, but the compute time made it prohibitive. 


Adding fog to the final chapter sped things up and reduced the grid-goes-to-infinity problems.



BUILDING COMPONENTS


Build for re-use. I have several keyboards accurately modeled but they are each slightly different. The core object was built. Each type was then classed from there. Sometimes its a quick and dirty block copy with search and replace, but the more I'm at this stuff, its apparent that its a lot easier to build things up this way.


Let's look at the mirror object-- a unit cube of three hard-coded components, the backing object, the reflective surface, and the cover glass. It then easily manipulated to a base CSG object, scaled and trimmed, mounted on front or behind something. Instead of remembering a location, let the computer find min_extent() or max_extent() and rotate about one of these points.



A SHORT DIVERSION

Spiny Norman was a humorous diversion, late in the project, but a challenge as to how  to quickly implement it:

1) Find the image.

2) Manipulate the image file it so that lent itself for transparency layer.

3) Apply the image as an image_map on a unit box

4) Create an animation path spline

5) Create a ComponentTimeline instance for when it would move

6) Scale it


This took a total of 40 minutes! Yes, I later went back and tweaked scaling and path.


These are located in my spline collection .inc file for that chapter:

#macro XfrmWiringHedgehog(Clock)

transform {  translate TranslateWiringHedgehog_Spline( Clock ) } 

#end


#declare TranslateWiringHedgehog_Spline =

spline { natural_spline

0.00, <-25, 10, 48>,

0.20, <-20, 32, 36 >,

0.45, < -10, 37, 30 >,

0.75, < 10, 34, 36 >,

1.00, < 25, 10, 48 >,

};


And this was located in the scene objects for the chapter:

box { 0 1 pigment { image_map { gif "spinynorman.gif" map_type 0 interpolate 2

once filter 0, 1 } }

translate -0.5*xy

scale <-18, 18, 0.001>

XfrmWiringHedgehog(WiringTimeline[24][1].u)  

}



When building the amp components and applying them to the chassis I quickly implemented their locations. The tubes were designed with sockets, as were RCA jacks and jack pairs.


DO use descriptors long enough. 

DO use enough arguments, but allow for "not used" or "don't care" (e.g 0, or -1)

DO use local variable lists. Helps when moving stuff around to change one variable called ItemLoc than to pick through its locations.


The idea is to be creative and not have to think. If you can't type well, learn to type, and then be creative. 


The sample code shows they were first cut out of the chassis and then instantiated:


#declare  MainAmpChassis = {

union {

// Cut the holes into the chassis

#local ShowItem = false;

difference {

ChassisBase

object { Tube1{ ShowItem ) translate Tube1Loc }

object { Tube2{ ShowItem ) translate Tube2Loc }

object { Tube3{ ShowItem ) translate Tube3Loc }

object { RCAjack{ ShowItem, Red ) translate RCA1Loc }

object { RCAjacks{ ShowItem, White, Grey50 ) translate RCA2Loc }

}

// once the above is written, then copy and paste here!

// change one flag, ant the items appear!

#local ShowItem = true;

object { Tube1{ ShowItem ) translate Tube1Loc }

object { Tube2{ ShowItem ) translate Tube2Loc }

object { Tube3{ ShowItem ) translate Tube3Loc }

object { RCAjack{ ShowItem, Red ) translate RCA1Loc }

object { RCAjacks{ ShowItem, White, Grey50 ) translate RCA2Loc }

}