class GiftTube : Model { val outerD = 40.0 val chamfer = 2.0 // The thickness of the inner tube (the part with the nub, not the maze) var thickness = 1.0 // The outer tube is thicker, because it has the maze cut into it. // This is also the thickness of the top and bottom surfaces. var outerThickness = 2.0 val mainHeight = 60.0 val endHeight = 5.0 var slack = 0.4 var grooveSlack = 0.5 var sides = 40 // Must be a multiple of resXY var nubSize = 2.0 // The size of the maze in the "x/y" circular direction var resXY = 10 // The size of the maze in the z direction. var resZ = 10 var grooveP : Shape2d = null // Created at the start of build() fun createGrooveP() { grooveP = Circle( nubSize ).sides(4) } var pathBuilder : Path3dBuilder // Converts a maze x,y coordinate into a 3d coordinate of the outer tube. fun to3d( x : int, y : int ) : Vector3 { val degrees = 360 / resXY * x val radius = outerD / 2 - outerThickness - grooveSlack return Vector3( Degrees.cos( degrees ) * radius, Degrees.sin( degrees ) * radius, mainHeight - mainHeight / resXY * y ) } var currentX = 0 var currentY = 0 fun moveTo( x : double, y : double ) { currentX = x currentY = y pathBuilder = Path3dBuilder().apply { moveTo( to3d( x, y ) ) } } fun vertical( n : double ) { currentY += n pathBuilder.lineTo( to3d( currentX, currentY ) ) } fun horizontal( n : double ) { val start = to3d( currentX, currentY ) currentX += n var steps = sides * n / resXY var delta = 360 / sides if (steps < 0) { steps = -steps delta = -delta } println( "H Steps $steps" ) for ( i in 1 .. steps ) { val m = Matrix3d.rotateZ( delta * i / 180 * Math.PI ) val to = m * start println( "H $i : $to" ) pathBuilder.lineTo( to ) } } fun makeGroove() : Shape3d { return grooveP.extrude( pathBuilder.build(), true ) } fun outer() : Shape3d { val main = ExtrusionBuilder().apply { crossSection( Circle( outerD/2 - chamfer ).sides(sides) ) forward(chamfer) crossSection( Circle( outerD/2 ).sides(sides) ) forward( mainHeight - chamfer ) crossSection() crossSection( -outerThickness ) forward( -mainHeight + outerThickness + chamfer ) crossSection() forward(-chamfer) crossSection( -chamfer ) }.build() return main.color( "Yellow" ) } fun inner() : Shape3d { val h = mainHeight - outerThickness - chamfer - slack val nubP = Circle( nubSize ).sides(4) / Square( nubSize*3 ).centerY() val nub = nubP.revolve(180) .rotateZ(-90) .translateZ( endHeight + h +slack ) .translateX( outerD/2 - outerThickness - slack*2 ) val main = ExtrusionBuilder().apply { crossSection( Circle( outerD/2 - chamfer ).sides(sides) ) forward(chamfer) crossSection( Circle( outerD/2 ).sides(sides) ) forward( endHeight ) crossSection() crossSection( Circle( outerD/2 - outerThickness - slack ).sides(sides) ) forward( h ) crossSection() crossSection( -thickness ) forward( -h - endHeight + outerThickness ) crossSection() forward( -chamfer ) crossSection( -chamfer ) }.build() return nub + main.color( "Orange" ) } fun maze() : Shape3d { moveTo( 0,-1 ) // Start "outside" vertical( 3 ) horizontal( 5 ) vertical( 1 ) horizontal( 2 ) vertical( 2 ) horizontal( -3 ) vertical( 1 ) horizontal( -6 ) vertical( 2 ) horizontal( 1 ) vertical( 1 ) horizontal( 9 ) // Follow the yellow brick road ! val solution = makeGroove().color( "Yellow" ) moveTo( 0, 1 ) horizontal( -2 ) vertical(1) horizontal( -2 ) val end1a = makeGroove().color( "Red" ) moveTo( -3, 2 ) vertical( -1 ) val end1b = makeGroove().color( "Red" ).darker() val end1 = end1a + end1b moveTo( 0, 2 ) vertical( 1 ) horizontal( 2 ) vertical( 1 ) horizontal( -3 ) vertical( -2 ) horizontal( 1 ) val end2 = makeGroove().color( "Green" ) moveTo( 3, 2 ) vertical( 3 ) horizontal( -5 ) vertical( -2 ) val end3 = makeGroove().color( "Orange" ) moveTo( -2, 8 ) horizontal( -2 ) vertical( -1 ) horizontal( 1 ) val end4 = makeGroove().color( "Blue" ) moveTo( -2, 6 ) horizontal( -3 ) vertical( 2 ) horizontal( -1 ) val end5a = makeGroove().color( "Indigo" ) moveTo( -5, 7 ) horizontal( -2 ) vertical( 1 ) horizontal( -1 ) val end5b = makeGroove().color( "Indigo" ).darker() val end5 = end5a + end5b moveTo( 2, 6 ) vertical( 1 ) horizontal( -4 ) val end6a = makeGroove().color( "Violet" ) moveTo( 0, 7 ) vertical( 1 ) horizontal( 1 ) val end6b = makeGroove().color( "Violet" ).darker() val end6 = end6a + end6b moveTo( 3, 4 ) horizontal( 1 ) vertical( -1 ) val end7 = makeGroove().color( "Salmon" ) moveTo( 5, 3 ) vertical( 1 ) horizontal( 1 ) val end8 = makeGroove().color( "DarkKhaki" ) moveTo( 2, 2 ) vertical( -1 ) horizontal( 1 ) val end9a = makeGroove().color( "Silver" ) moveTo( 4, 2 ) vertical( -1 ) horizontal( 1 ) val end9b = makeGroove().color( "Silver" ) val end9 = end9a + end9b return solution + end1 + end2 + end3 + end4 + end5 + end6 + end7 + end8 + end9 } override fun build() : Shape3d { createGrooveP() val maze : Shape3d = maze() val inner : Shape3d = inner() val outer : Shape3d = outer() - maze val both = outer + inner .mirrorZ() .translateZ(mainHeight + endHeight + thickness + slack + 1) return outer + inner.translateX( 60 ) + (both / Cube( 200,1,200).center()).translateX( 120 ) + maze.translateX( 180 ) } } class Maze( val seed : double, val width : int, val depth : int ) { // The even parts of the grid denote if this location has been visited, // The odd parts denote if there is a path from one location to a neighbour. // Note that "diagonals" (both indicies are odd) will never be used. val grid = listOf() var completed = 0 val random = Random( seed ) init { for (y in 0 until depth*2 ) { for (x in 0 until width *2 ) { grid.add( false ) } } } fun get( i : int, j : int ) : bool { return if ( j < 0 || j >= depth * 2) { true } else if ( i < 0 ) { get( width * 2, j ) } else if ( i >= width * 2 ) { get( i - width * 2, j ) } else { grid.get( j * width + i ) } } fun set( i : int, j : int ) { if ( i < 0 ) { set( width * 2, j ) } else if ( i >= width * 2 ) { set( i - width * 2, j ) } else { grid.set( j * width + i, true ) } } fun isOccupied( x : int, y : int ) : bool { return get( x * 2, y * 2 ) } fun visit( x : int, y : int, dx : int, dy : int ) { set( x * 2, y * 2 ) set( x * 2 + dx, y * 2 + dy ) } fun grow() { set( 0, 0 ) growFrom( 0, 0 ) } fun growFrom( x : int, y : int ) { } }