def O (0,0,0)
def I [1,0,0]
def J [0,1,0]
def K [0,0,1]
def cornerRad 0.1
def tireRad 0.5
def wheelWidth .4
def nArcSegs 3
def nTireSegs 20
def nAxleSegs 6
def axleRad .05
def robotWidth 1.5
def robotLength 2.5
def bodyWidth .5
def bodyHeight .4
def steeringGamma .2
def leftSteeringAngle  atan2(2 * robotLength * steeringGamma, 2 - robotWidth * steeringGamma)
def rightSteeringAngle atan2(2 * robotLength * steeringGamma, 2 + robotWidth * steeringGamma)

def wheelRad tireRad - cornerRad
def wheelXofs wheelWidth/2 - cornerRad

def rightArc put {
 translate([ wheelXofs, wheelRad, 0])
} sweep { nArcSegs, rotate(90 / nArcSegs) } (cornerRad,0)

def leftArc put {
 translate([-wheelXofs, wheelRad, 0])
} sweep { nArcSegs, rotate(90 / nArcSegs) } (0,cornerRad)

def tread line(wheelXofs, tireRad)(-wheelXofs, tireRad)

def hubPlate sweep[fillcolor=lightgray] { nTireSegs<>, rotate(-360 / nTireSegs, (O), [I]) } (0,wheelRad)

def tire sweep[fillcolor=lightgray]{ nTireSegs, rotate(360 / nTireSegs, (O), -[I]) } {
 {leftArc}
 {rightArc}
 {tread}
}

def wheel {
 def leftHubPlate  put { translate( [-wheelWidth/2, 0, 0] ) } {hubPlate}
 def rightHubPlate put { rotate(180, (O), [K]) }          {leftHubPlate}
 {rightHubPlate}
 {leftHubPlate}
 {tire}
}

def axle sweep[fillcolor=darkgray]{ nAxleSegs, rotate(360 / nAxleSegs, (O), -[I]) }
 line (robotWidth/2, axleRad)(-robotWidth/2, axleRad)

def leftSteerableWheel put {
 rotate(leftSteeringAngle, (O), [J]) then
 translate( [-robotWidth/2, 0, 0] )
} {wheel}

def rightSteerableWheel put {
 rotate(rightSteeringAngle, (O), [J]) then
 translate( [robotWidth/2, 0, 0] )
} {wheel}

def steerableAxleAssembly {
 {leftSteerableWheel}
 {rightSteerableWheel}
 {axle}
}

def fixedAxleAssembly {
 put { translate( [-robotWidth/2, 0, 0] ) } {wheel}
 put { translate( [ robotWidth/2, 0, 0] ) } {wheel}
 {axle}
}

def bodyopts [fillcolor=white]

def bodyCorner # first octant "rounded corner"
  sweep [bodyopts]{ nArcSegs, rotate(90 / nArcSegs, (O), [I]) }
  sweep           { nArcSegs, rotate(90 / nArcSegs, (O),-[K]) } (0,cornerRad)

def zBodyEdge
 sweep [bodyopts] { 1, translate([K]) }
 sweep            { nArcSegs, rotate(90 / nArcSegs, (O), -[K]) } (0,cornerRad,0)

def xBodyEdge # positive y-z quadrant about x axis quarter-pipe of unit length
 sweep [bodyopts] { 1, translate([I]) }
 sweep            { nArcSegs, rotate(90 / nArcSegs, (O), -[I]) } (0,0,cornerRad)

def body {
 def xOfs bodyWidth/2 - cornerRad
 def yOfs bodyHeight/2 - cornerRad
 def zOfs robotLength/2 + cornerRad
 def posZend {
   # 4 corners
   put { translate([ xOfs,  yOfs, zOfs]) } {bodyCorner}
   put { rotate(90, (O), [K]) then
         translate([-xOfs,  yOfs, zOfs]) } {bodyCorner}
   put { rotate(180, (O), [K]) then
         translate([-xOfs, -yOfs, zOfs]) } {bodyCorner}
   put { rotate(270, (O), [K]) then
         translate([ xOfs, -yOfs, zOfs]) } {bodyCorner}
   # 4 edges
   put { scale([2*xOfs, 1, 1]) then
         translate([-xOfs, yOfs, zOfs]) } {xBodyEdge}
   put { scale([2*yOfs, 1, 1]) then
         rotate(90, (O), [K]) then
         translate([-xOfs,-yOfs, zOfs]) } {xBodyEdge}
   put { scale([2*xOfs, 1, 1]) then
         rotate(180, (O), [K]) then
         translate([ xOfs,-yOfs, zOfs]) } {xBodyEdge}
   put { scale([2*yOfs, 1, 1]) then
         rotate(270, (O), [K]) then
         translate([ xOfs, yOfs, zOfs]) } {xBodyEdge}
   def z zOfs + cornerRad
   polygon[bodyopts](xOfs,yOfs,z)(-xOfs,yOfs,z)(-xOfs,-yOfs,z)(xOfs,-yOfs,z)
 }
 def top {
   put { scale([1,1,2*zOfs]) then
         translate([xOfs, yOfs, -zOfs]) } {zBodyEdge}
   put { scale([1,1,2*zOfs]) then
         rotate(90) then
         translate([-xOfs, yOfs, -zOfs]) } {zBodyEdge}
   def y bodyHeight/2
   polygon[bodyopts](xOfs,y,zOfs)(xOfs,y,-zOfs)(-xOfs,y,-zOfs)(-xOfs,y,zOfs)
 }
 def posXside {
   def x bodyWidth/2
   polygon[bodyopts](x,yOfs,zOfs)(x,-yOfs,zOfs)(x,-yOfs,-zOfs)(x,yOfs,-zOfs)
 }

 # ends of the body
 {posZend}
 put { rotate(180, (O), [I]) } {posZend}

 {top}
 {posXside}

 # bottom and negative X side
 put { rotate(180) } {
   {top}
   {posXside}
 }
}

def robot {
 {fixedAxleAssembly}
 put { translate([0, 0, -robotLength]) } {steerableAxleAssembly}
 put { translate([0, 0, -robotLength/2])   } {body}
}

def centerXform
      translate(robotLength/2 * [K])
 then rotate(90, (O), [J])

def winWidth
 <zoomed_in> 0.6
 <zoomed_out> 1.5
 <> 1

def foreshorteningFactor
 <fish_eye> .3
 <parallel> 30
 <> 1 # base

def pai 8
def panAngle
 <vai>   -pai
 <vaii>  -pai
 <vaiii> -pai
 <vaiv>   pai
 <vav>    0
 <vavi>  -pai
 <vavii>  pai
 <vaviii> pai
 <vaix>  pai
 <>       0

def panAxis
 <vai>    [-1,-1,0]
 <vaii>   [-1,0,0]
 <vaiii>  [-1,1,0]
 <vaiv>   [0,1,0]
 <vav>    [0,1,0]
 <vavi>   [0,1,0]
 <vavii>  [-1,1,0]
 <vaviii> [-1,0,0]
 <vaix>  [-1,-1,0]
 <>       [0,1,0]

def eyeAxis [10,7,10]
def eyeI (O) + .5 * foreshorteningFactor * [eyeAxis]
def dvI (O) - (eyeI)

def upRot
 <tilt_left>  -20
 <tilt_right> 20
 <> 0

def up [0,1,0] then rotate(upRot, (O), -[eyeAxis])

def viewXformI
 <parallel>
    view ((eyeI), [dvI], [up])
    then rotate(-panAngle, (O), [panAxis])
    then perspective(|[dvI]|)
    then scale(1.5/winWidth)
    then scale([1,1,1000])
 <> view ((eyeI), [dvI], [up])
    then rotate(-panAngle, (O), [panAxis])
    then perspective(|[dvI]|)
    then scale(1.5/winWidth)

def dashframe {
 line[linestyle=dashed](-1,-1, 1)( 1,-1, 1)(1,1, 1)(-1,1, 1)(-1,-1, 1)
 line[linestyle=dashed](-1,-1,-1)( 1,-1,-1)(1,1,-1)(-1,1,-1)(-1,-1,-1)
 line[linestyle=dashed](-1,-1, 1)(-1,-1,-1)
 line[linestyle=dashed]( 1,-1, 1)( 1,-1,-1)
 line[linestyle=dashed]( 1, 1, 1)( 1, 1,-1)
 line[linestyle=dashed](-1, 1, 1)(-1, 1,-1)
}

def floor_grid {
 def opts [linewidth=.2pt]
 def nX 7
 def nZ 5
 def dx (robotLength+tireRad*2) + 2
 def dz (robotWidth+wheelWidth) + 2
 def y -tireRad-.001

 repeat { nX+1, translate([dx/nX,0,0]) } line[opts](-dx/2,y,-dz/2)(-dx/2,y,dz/2)
 repeat { nZ+1, translate([0,0,dz/nZ]) } line[opts](-dx/2,y,-dz/2)(dx/2,y,-dz/2)
}

put { [[viewXformI]] } {
 put { [[centerXform]] } {robot}
 {floor_grid}
# put { scale([(robotLength+tireRad*2)/2, tireRad, (robotWidth+wheelWidth)/2]) } {dashframe}
}

#def frameOfs 2
#put { [[viewXformI]] then translate(-frameOfs * [I]) } {robot}
#put { [[viewXformI]] then translate(frameOfs * [I]) } {robot}
#put { [[simpleViewXform]] then translate(2*frameOfs * [I]) } {robot}


global {
 set [linewidth=.3pt]

 % set up a bounding box
 def w 3
 def ar 3/2.2
 def cbp1 (-w,-w/ar,0)
 def cbp2 (w,w/ar,0)

 picturebox [.5] (cbp1)(cbp2)
 frame
}