/**********************************************************************************/ /* StairCase.inc - Created by Chris Bartlett April 2006 */ /* */ /* You may re-use this file in original or modified form with or without credit */ /* being given to the original author. You may redistribute on any form of media. */ /* */ /* Description: This file contains definitions and macros designed to enable you */ /* to add a staircase to a POV-Ray scene. */ /* The file StairCase.html describes its use. */ /* Version: Written using POV-Ray 3.5 and tested on 3.6 */ /**********************************************************************************/ #version 3.5; #include "math.inc" // The StairCase Macro generates a staircase based upon control variable settings. // The main variable is SCPostPositions which is an array that holds the positions of the newel // posts that define one side of the staircase (by default the left hand posts). All of the // control variables have default settings. The control variables are desribed in StairCase.html. #macro StairCase() // Default Texture Settings #local TextureSeed = seed(6); #ifndef(SCPostPositions) #local SCPostPositions = array [4] {<0,0,0>,<0,2,3>,<-1,2.5,2>,<-2,3,-2>}; #end #ifndef(SCNewelTexture) #ifndef(Clear) #include "colors.inc" #end #ifndef(T_Wood1) #include "woods.inc" #end #local SCNewelTexture = texture {T_Wood1 scale 0.1 rotate x*90} #end #ifndef(SCBalusterTexture) #local SCBalusterTexture = texture {SCNewelTexture} #end #ifndef(SCStringerTexture) #ifndef(Clear) #include "colors.inc" #end #ifndef(T_Wood30) #include "woods.inc" #end #local SCStringerTexture = texture {T_Wood30 scale 0.1} // rotate <90,0,90>} #end #ifndef(SCRailTexture) #local SCRailTexture = texture {SCNewelTexture rotate -x*90} #end #ifndef(SCTreadTexture) #local SCTreadTexture = texture {SCNewelTexture rotate -z*90} #end #ifndef(SCRiserTexture) #ifndef(Clear) #include "colors.inc" #end #ifndef(T_Wood30) #include "woods.inc" #end #local SCRiserTexture = texture {T_Wood30 scale 0.1 rotate <90,0,90>} #end // Default Control Variable Settings #ifndef(SCMaxBalusterSpacing) #local SCMaxBalusterSpacing = 0.135; #end #ifndef(SCMaxRiserHeight) #local SCMaxRiserHeight = 0.190; #end #ifndef(SCRiserCoverage) #local SCRiserCoverage = 1; #end // 0=No Riser, 1=Full Height Riser #ifndef(SCHandRailHeight) #local SCHandRailHeight = 0.920; #end #ifndef(SCStairWidth) #local SCStairWidth = 1; #end #ifndef(SCStairsOn) #local SCStairsOn = 1; #end // 0=No Stairs, 1=Stairs #ifndef(SCStairHandedness) #local SCStairHandedness = 1; #end //-1=Left, 0=Centred, 1=Right as seen from foot of stairs #ifndef(SCLandingsOn) #local SCLandingsOn = SCStairsOn; #end // 0=No Landings, 1=Add Landings #ifndef(SCLandingThreshold) #local SCLandingThreshold = 50; #end // Threshold angle below which we remove a newel post #ifndef(SCMainBanisterOn) #local SCMainBanisterOn = 1; #end // 1=Main Hand Rail On, 0=No Main Hand Rail #ifndef(SCPairedBanisterOn) #local SCPairedBanisterOn = 1; #end // 1=Paired Hand Rail On, 0=No Paired Hand Rail // Default Stair covering, colour and texture #ifndef(SCStairCarpetOn) #local SCStairCarpetOn = 1; #end #ifndef(SCStairCarpetWidth) #local SCStairCarpetWidth = SCStairWidth; #end #ifndef(SCStairCarpetColour) #local SCStairCarpetColour = <1,1,0>; #end #ifndef(SCStairCarpetTexture) #local SCStairCarpetTexture = texture {pigment {color rgb SCStairCarpetColour} normal{agate 0.2 scale 0.01}} #end // Default staircase components #ifndef(SCStringer ) #local SCStringer = SCDefaultStringer #end #ifndef(SCTread ) #local SCTread = SCDefaultTread #end #ifndef(SCRiser ) #local SCRiser = SCDefaultRiser #end #ifndef(SCStairCarpet) #local SCStairCarpet = SCDefaultStairCarpet #end #ifndef(SCHandRail ) #local SCHandRail = SCDefaultHandRail #end #ifndef(SCBaluster ) #local SCBaluster = SCDefaultBaluster #end #ifndef(SCNewelPost ) #local SCNewelPost = SCDefaultNewelPost #end // At each of the positions defined in the SCPostPositions array we position a newel post and optionaly add stairs // a banister rail and landings. We also add a second set of newel posts and banister rails at the other side of the // stairs. The local variable 'I' holds the array index number of the current newel post. #local I = 0; #while (I} #if (I=0) rotate y*atan2d(SCPostPositions[1].x-SCPostPositions[0].x,SCPostPositions[1].z-SCPostPositions[0].z) #else rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) #end translate SCPostPositions[I] } #end // Place a corresponding Newel Post at the opposite side of the stairs #if (I=0 & SCPairedBanisterOn) #local NextHorizontalOffset = <1,0,1> * (SCPostPositions[I] - SCPostPositions[I+1]); #local Post2 = SCPostPositions[I] + vrotate(vnormalize(NextHorizontalOffset), -y*90*sgn(SCStairHandedness)) * PostSeparation; #end // Starting from the second Newel Post we start drawing stairs and banister rails #if (I>0) #local NewelSeparation = vlength(SCPostPositions[I]-SCPostPositions[I-1]); #local HorizontalNewelSeparation = vlength(); #local VerticalNewelSeparation = SCPostPositions[I].y-SCPostPositions[I-1].y; #if (SCMainBanisterOn) BannisterGen(SCPostPositions[I], SCPostPositions[I-1],SCHandRailHeight) #end #local Post1 = SCPostPositions[I]; #local PreviousHorizontalOffset = <1,0,1> * (SCPostPositions[I] - SCPostPositions[I-1]); #local Post2 = Post1 + vrotate(vnormalize(PreviousHorizontalOffset), y*90*sgn(SCStairHandedness)) * PostSeparation; #if (I * (SCPostPositions[I] - SCPostPositions[I+1]); #local Post4 = Post1 + vrotate(vnormalize(NextHorizontalOffset),-y*90*sgn(SCStairHandedness)) * PostSeparation; #if (abs(VAngleD(Post4-Post1,Post2-Post1))>SCLandingThreshold) #local AddCorner = 1; #else #local AddCorner = 0; #end #if (AddCorner) #local Post3 = Post2 + Post4-Post1; #else #ifdef(Post3) #undef Post3 #end #end #end // The stairs themselves are also optional #if (SCStairsOn!=0) #local TreadCount = div(abs(VerticalNewelSeparation/SCMaxRiserHeight)+0.999,1); #if (TreadCount = 0) box {<0,-0.018,0>,<1,0,vlength(SCPostPositions[I]-SCPostPositions[I-1])> translate -0.5*x translate -x*SCStairHandedness/2 scale texture {SCTreadTexture translate <-20*rand(TextureSeed),0.05+0.05*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate SCPostPositions[I] } #if (SCStairCarpetOn) box {<0,0,0>,<1,0.005,vlength(SCPostPositions[I]-SCPostPositions[I-1])> translate -0.5*x scale translate -x*SCStairWidth*SCStairHandedness/2 texture {SCStairCarpetTexture} rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate SCPostPositions[I] } #end #else #local RiserSpacing = HorizontalNewelSeparation/TreadCount; #local RiserHeight = VerticalNewelSeparation/TreadCount; #local J = 0; #while (J<=TreadCount) #if (!(I=1&J=TreadCount&VerticalNewelSeparation>0)) #if (VerticalNewelSeparation>0) // The Flight of Stairs is going up relative to the start of the flight union {object {SCTread} object {SCTread clipped_by {box {<-1,-1,-0.1><1,0.01,0>}} scale <1,1,10*(RiserSpacing-0.122)>} #if (J=0) clipped_by {plane {-z,0}} #end #if (J>0 & J< TreadCount) clipped_by {plane {-z,0 translate -z*(RiserSpacing-0.122)}} #end #if (J=TreadCount) clipped_by {box {<-1,-1,-(RiserSpacing-0.122)><1,0.01,0>}} #end translate -x*SCStairHandedness/2 scale texture {SCTreadTexture translate <-20*rand(TextureSeed),0.05+0.05*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} translate z*J*RiserSpacing rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate -y*J*VerticalNewelSeparation/TreadCount translate SCPostPositions[I] } #else // The Flight of Stairs is going down relative to the start of the flight object {SCTread #if (J>0) clipped_by {plane {-z,0 translate -z*(RiserSpacing-0.120)}} translate z*(RiserSpacing/2-0.122) rotate y*180 translate -z*(RiserSpacing/2-0.122) #end #if (J=0) clipped_by {box {<-1.01,-0.1,0.122><1.01,0.01,0>}} #end translate -x*SCStairHandedness/2 scale texture {SCTreadTexture translate <-20*rand(TextureSeed),0.05+0.05*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} translate z*J*RiserSpacing rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate -y*J*VerticalNewelSeparation/TreadCount translate SCPostPositions[I] } #end #if (J!=TreadCount) object {SCRiser translate -x*SCStairHandedness/2 scale texture {SCRiserTexture translate <-20*rand(TextureSeed),0.05+0.05*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} translate z*J*RiserSpacing rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate -y*J*VerticalNewelSeparation/TreadCount translate SCPostPositions[I] } #end #if (SCStairCarpetOn) #if (VerticalNewelSeparation>0) union {object {SCStairCarpet} object {SCStairCarpet clipped_by {box {<-1,-1,-0.1><1,0.01,0>}} scale <1,1,10*(RiserSpacing-0.122)>} #if (J=0) clipped_by {plane {-z,0}} #end #if (J>0 & J< TreadCount) clipped_by {plane {-z,0 translate -z*(RiserSpacing-0.122)}} #end #if (J=TreadCount) clipped_by {box {<-1,-1,-(RiserSpacing-0.122)><1,0.01,0>}} #end #if (VerticalNewelSeparation<0) translate z*RiserSpacing/2+0.122 rotate y*180 translate z*RiserSpacing/2+0.122 #end scale translate -x*SCStairWidth*SCStairHandedness/2 texture {SCStairCarpetTexture translate 2*y*J*VerticalNewelSeparation/TreadCount} translate z*J*RiserSpacing rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate -y*J*VerticalNewelSeparation/TreadCount translate SCPostPositions[I] } #else object {SCStairCarpet #if (J>0) clipped_by {plane {-z,0 translate -z*(RiserSpacing-0.120)}} translate z*(RiserSpacing/2-0.122) rotate y*180 translate -z*(RiserSpacing/2-0.122) #end #if (J=0) clipped_by {box {<-1.01,-0.1,0.122><1.01,0.01,0>}} #end scale translate -x*SCStairWidth*SCStairHandedness/2 texture {SCStairCarpetTexture translate 2*y*J*VerticalNewelSeparation/TreadCount} translate z*J*RiserSpacing rotate y*atan2d(SCPostPositions[I-1].x-SCPostPositions[I].x,SCPostPositions[I-1].z-SCPostPositions[I].z) translate -y*J*VerticalNewelSeparation/TreadCount translate SCPostPositions[I] } #end #end #end #local J = J + 1; #end #end // Work out the corners for landings to connect the top of one flight of stairs to the bottom of the next. #local PostToEdge = (SCStairWidth - PostSeparation)/2; #if (I, , , #if (AddCorner) , #end , , } object {Landing texture {SCTreadTexture} translate y*Corner1 } // The carpeting of the landing can be complicated by the shape of the landing and depends upon // the number of Newel Posts required to turn the corner. #if (SCStairCarpetOn) #local CarpetInset = (SCStairWidth-SCStairCarpetWidth)/2; #local Corner1a = Post1 + (CarpetInset-PostToEdge)*vnormalize(Post2-Post1); #local Corner1b = Post1 + (CarpetInset-PostToEdge)*vnormalize(Post4-Post1); #local Corner1 = Corner1a + Corner1b - Post1; #local Corner2 = Post2 - (CarpetInset-PostToEdge)*vnormalize(Post2-Post1); #local Corner4 = Post4 - (CarpetInset-PostToEdge)*vnormalize(Post4-Post1); #if (AddCorner) #local Corner3 = Corner4 + Corner2 - Post1; #end #local LandingCarpet = prism { linear_spline 0,0.005, #if (AddCorner) 7, #else 5, #end , , #if (AddCorner) , #end , , #if (AddCorner) , #end } object {LandingCarpet texture {SCStairCarpetTexture} translate y*Corner1 } #end #end #end #end #end // Create a Newel Post on the opposite side of the staircase #ifdef(Post2) // Temporary Bodge #if (I>0) #local PreviousHorizontalOffset = <1,0,1> * (SCPostPositions[I] - SCPostPositions[I-1]); #local Post2 = SCPostPositions[I] + vrotate(vnormalize(PreviousHorizontalOffset), y*90*sgn(SCStairHandedness)) * PostSeparation; #end #if (SCPairedBanisterOn) #if (I>0) BannisterGen(Post2, SCPostPositions[I-1]+Post2-SCPostPositions[I],SCHandRailHeight) #end #if (I=0) object {SCNewelPost texture {SCNewelTexture translate <0.05+0.05*rand(TextureSeed),20*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} rotate y*atan2d(SCPostPositions[I].x-SCPostPositions[I+1].x,SCPostPositions[I].z-SCPostPositions[I+1].z) translate Post2 } #else object {SCNewelPost texture {SCNewelTexture translate <0.05+0.05*rand(TextureSeed),20*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} rotate y*atan2d(SCPostPositions[I].x-SCPostPositions[I-1].x,SCPostPositions[I].z-SCPostPositions[I-1].z) translate Post2 } #end #end #end #if (I} rotate y*atan2d(SCPostPositions[I].x-SCPostPositions[I+1].x,SCPostPositions[I].z-SCPostPositions[I+1].z) translate Post3 } BannisterGen(Post2,Post3,SCHandRailHeight) #end #end // If doing a landing we may need an additional Newel Post and a bit more banister rail to turn the corner #ifdef(Post4) #if (SCPairedBanisterOn) object {SCNewelPost texture {SCNewelTexture translate <0.05+0.05*rand(TextureSeed),20*rand(TextureSeed),0.1+0.5*rand(TextureSeed)>} rotate y*atan2d(SCPostPositions[I].x-SCPostPositions[I+1].x,SCPostPositions[I].z-SCPostPositions[I+1].z) translate Post4 } #ifdef(Post3) BannisterGen(Post3,Post4,SCHandRailHeight) #else BannisterGen(Post2,Post4,SCHandRailHeight) #end #end #end #end #local I = I + 1; #end #end // The BannisterGen macro is used by the StairCase macro to define the banister rail that connects two newel posts. // Certain variables must be set before this macro is called. This macro is not intended to be called directly from // a scene file. #macro BannisterGen(Start,End,Height) #local NewelSeparation = vlength(Start-End); #local HorizontalNewelSeparation = vlength(); #local VerticalNewelSeparation = Start.y-End.y; #if (NewelSeparation = 0) #local NewelSeparation = 0.0001; #end object {SCHandRail scale <1,1,NewelSeparation> texture {SCRailTexture translate <0.05+0.08*rand(TextureSeed),0.05+0.08*rand(TextureSeed),2000*rand(TextureSeed)>} translate z*0.032*sgn(VerticalNewelSeparation) rotate x*asind(VerticalNewelSeparation/NewelSeparation) translate -z*0.032*sgn(VerticalNewelSeparation) rotate y*atan2d(End.x-Start.x,End.z-Start.z) translate Start+y*Height } // Baluster separation should be a maximum of 135mm #local BalusterCount = div((HorizontalNewelSeparation/SCMaxBalusterSpacing)-0.5,1); #if (BalusterCount>0) #local BalusterSpacing = HorizontalNewelSeparation/BalusterCount; #end #local J = 1; #while (J} translate z*J*BalusterSpacing rotate y*atan2d(End.x-Start.x,End.z-Start.z) translate -y*J*VerticalNewelSeparation/BalusterCount translate Start } #local J = J + 1; #end difference { object {SCStringer scale <1,1,NewelSeparation> texture {SCStringerTexture translate <0.05+0.08*rand(TextureSeed),0.05+0.08*rand(TextureSeed),2000*rand(TextureSeed)>} rotate x*asind(VerticalNewelSeparation/NewelSeparation) } plane { z,0 texture {SCStringerTexture}} plane {-z,0 translate z*HorizontalNewelSeparation texture {SCStringerTexture}} rotate y*atan2d(End.x-Start.x,End.z-Start.z) translate Start } #end // The following object definitions are used by default to construct a staircase. // You would not normally need to alter these defaults. To override the defaults you can set user defined objects using the // control variables exposed for that purpose. So, for example, instead of modifying the SCDefaultNewelPost object here you can // simply declare the obect SCNewelPost in your scene file or in a separate include file prior to calling the StairCase macro. // If the SCNewelPost is defined, the StairCase macro will use that in place of SCDefaultNewelPost. // Default Newel Post /* Set the control points to be used to generate the Newel Posts at the ends of the hand rails. */ #local ProfilePoints = 30; #local LatheArray = array[ProfilePoints] { <0.040, 1.10 >, <0.020, 0.920>, <0.038, 0.90 >, <0.020, 0.897>, // Top square section <0.075, 0.85 >, <0.075, 0.75 >, <0.020, 0.70 >, <0.038, 0.695>, <0.020, 0.690>, <0.038, 0.688>, <0.020, 0.660>, <0.038, 0.655>, <0.025, 0.650>, <0.042, 0.40> , <0.030, 0.38> , <0.030, 0.38> , <0.042, 0.37> , <0.042, 0.37> , <0.030, 0.35> , <0.042, 0.25> , <0.030, 0.245>, <0.030, 0.245>, <0.042, 0.240>, <0.030, 0.235>, <0.010, 0.235>, // Lower square section <0.075, 0.19> , <0.075, -0.05> , <0.075, -0.10> , <0.075, -0.30> , <0.000, -0.30> } #local LatheCut = lathe { cubic_spline ProfilePoints, #local I = 0; #while (I < ProfilePoints-1) LatheArray[I], #local I = I+1; #end LatheArray[I] } #declare SCDefaultNewelPost = union { intersection { box { <-0.041, -0.2, -0.041> < 0.041, 1, 0.041> } object {LatheCut} } sphere {<0,0.95,0>,0.041} translate y*0.135 } // Default Baluster /* Set the control points to be used to generate the balusters beneath the hand rail.*/ #local ProfilePoints = 31; #local LatheArray = array[ProfilePoints] { <0.035, 1.10 >, // Top square section <0.035, 1.10 >, <0.035, 0.80 >, <0.010, 0.75 >, <0.010, 0.75 >, <0.015, 0.747>, <0.010, 0.744>, <0.010, 0.744>, <0.015, 0.730>, <0.011, 0.710>, <0.010, 0.700>, <0.010, 0.700>, <0.015, 0.697>, <0.010, 0.694>, <0.010, 0.694>, <0.015, 0.55 >, // Middle Bulge <0.010, 0.406>, <0.010, 0.406>, <0.015, 0.403>, <0.010, 0.400>, <0.010, 0.400>, <0.011, 0.390>, <0.015, 0.370>, <0.010, 0.356>, <0.010, 0.356>, <0.015, 0.353>, <0.010, 0.35 >, <0.010, 0.35 >, // Lower square section <0.035, 0.30 >, <0.035, 0.00 >, <0.035, -0.10 > } #local LatheCut = lathe { cubic_spline ProfilePoints, #local I = 0; #while (I < ProfilePoints-1) LatheArray[I], #local I = I+1; #end LatheArray[I] } #declare SCDefaultBaluster = intersection { box { <-0.015, -0, -0.015> < 0.015, 1.1, 0.015> } object {LatheCut} translate -y*0.03 } // Default Hand Rail /* Set the control points to be used to generate the hand rail.*/ #local ProfilePoints = 9; #local PrismArray = array[ProfilePoints] { <0.0180, 0.006>, <0.0180, 0.000>, <0.0300, 0.000>, <0.0320, 0.005>, <0.0305, 0.012>, <0.0230, 0.019>, <0.0300, 0.035>, <0.0300, 0.042>, <0.0200, 0.053> } #declare SCDefaultHandRail = prism { cubic_spline 0,1,ProfilePoints*2+3, #local I = 0; <-PrismArray[I].x,PrismArray[I].y>, #while (I < ProfilePoints) , #local I = I+1; #end #local I = ProfilePoints-1; #while (I > 0) <-PrismArray[I].x,PrismArray[I].y>, #local I = I-1; #end <-PrismArray[I].x,PrismArray[I].y>, , #local I = I+1; rotate <-90,180,0> } // Default ShoeRail /* Set the control points to be used to generate the hand rail.*/ #local ProfilePoints = 12; #local PrismArray = array[ProfilePoints] { <0.016, 0.027>, <0.017, 0.027>, <0.017, 0.025>, <0.018, 0.025>, <0.023, 0.025>, <0.029, 0.023>, <0.030, 0.018>, <0.032, 0.017>, <0.032, 0.015>, <0.032, 0.001>, <0.032, 0.000>, <0.031, 0.000> } #local ShoeRail = prism { cubic_spline 0,1,ProfilePoints*2+3, #local I = 0; <-PrismArray[I].x,PrismArray[I].y>, #while (I < ProfilePoints) , #local I = I+1; #end #local I = ProfilePoints-1; #while (I > 0) <-PrismArray[I].x,PrismArray[I].y>, #local I = I-1; #end <-PrismArray[I].x,PrismArray[I].y>, , #local I = I+1; rotate <-90,180,0> } // Default Stringer #declare SCDefaultStringer = union { box {<-0.0175,-0.20,-0.08><0.0175,0.05,1.08>} object {ShoeRail translate y*0.05 scale <1,1,1.16> translate -0.08*z} translate y*0.06 } // Default Tread #declare SCDefaultTread = union { box {<0,-0.018,-1.00><1,0,0.15>} cylinder {<0,-0.009,0.15><1,-0.009,0.15>,0.009} translate -0.5*x } // Default Riser #declare SCDefaultRiser = box {<0,-1,0.122><1,-0.001,0.13> translate -0.5*x} // Default Carpet #local ProfilePoints = 22; #local PrismArray = array[ProfilePoints] { <-0.005,-1.002>, <-0.005,-1.000>, <-0.005,-0.350>, <-0.005,-0.200>, <-0.005, 0.050>, <-0.004, 0.150>, < 0.030, 0.170>, < 0.080, 0.135>, < 0.190, 0.135>, < 0.191, 0.135>, < 0.191, 0.134>, < 0.191, 0.130>, < 0.080, 0.130>, < 0.030, 0.165>, < 0.001, 0.145>, < 0.000, 0.050>, < 0.000,-0.152>, < 0.000,-0.200>, < 0.000,-0.350>, <-0.005,-1.002>, <-0.005,-1.000>, <-0.005,-0.050> } #declare SCDefaultStairCarpet = prism { cubic_spline 0,1,ProfilePoints, #local I = 0; #while (I < ProfilePoints-1) , #local I = I+1; #end rotate -z*90 translate -0.5*x }