////////////////////////////////////////////////////////////////////////// // Some Maze Macros // Copyright (c) 2000 by Robert Chaffe ////////////////////////////////////////////////////////////////////////// // Note the debug statements throughout. The GenSquareMaze macro can // generate a lot of these for a large maze when the DoDebug parameter is // set to yes. Recommend that debug output be redirected to a file with // the -GDfile command line option. ////////////////////////////////////////////////////////////////////////// // First, some convenience macros. // Not sure if rand() ever returns a 1.0, which is just a wee bit too // high, so let's try this macro. #macro RandInt( MaxInt, Seed ) min( int( rand( Seed ) * MaxInt ), MaxInt - 1 ) #end // An "or" bitwise operator would be nice. Oh well. #macro HasNorthValue( CellValue ) ( ( CellValue = 1 ) | ( CellValue = 3 ) | ( CellValue = 5 ) | ( CellValue = 7 ) | ( CellValue = 9 ) | ( CellValue = 11 ) | ( CellValue = 13 ) | ( CellValue = 15 ) ? yes : no ) #end #macro HasEastValue( CellValue ) ( ( CellValue = 2 ) | ( CellValue = 3 ) | ( CellValue = 6 ) | ( CellValue = 7 ) | ( CellValue = 10 ) | ( CellValue = 11 ) | ( CellValue = 14 ) | ( CellValue = 15 ) ? yes : no ) #end #macro HasWestValue( CellValue ) ( ( ( CellValue >= 4 ) & ( CellValue <= 7 ) ) | ( CellValue >= 12 ) ? yes : no ) #end #macro HasSouthValue( CellValue ) ( CellValue >= 8 ? yes : no ) #end ////////////////////////////////////////////////////////////////////////// // Parameters for GenSquareMaze macro: // pRows = number of rows in grid // pCols = number of columns in grid // Seed = float value for seed function // DoDebug = boolean indicating if debug messages should be written // FileName = name of file into which the direction data will be written #macro GenSquareMaze ( pRows, pCols, Seed, DoDebug, FileName ) // A few safety checks. #if ( pRows < 4.0 ) #error "GenSquareMaze: The Rows parameter should be 4 or greater." #end #if ( pCols < 4.0 ) #error "GenSquareMaze: The Cols parameter should be 4 or greater." #end #if ( strlen( FileName ) < 1 ) #error "GenSquareMaze: FileName length is zero?" #end #if ( strcmp( strlwr( FileName ), "amaze.inc" ) = 0 ) #error "GenSquareMaze: I will not overwrite myself!!" #end // Create the data file. We don't write to it for awhile, but it would // be a shame to do all the work and then have the open command fail. #fopen MazeDataFH FileName write // Just in case we were not given integers. #local Rows = int( pRows ); #local Cols = int( pCols ); // Initialize randomness. #local S = seed( Seed ); // Initialize array. #if ( DoDebug ) #debug concat("\nGenSquareMaze: Initializing ",str(Rows,0,0),"x",str(Cols,0,0)," grid.\n") #end #local Grid = array[ Rows ][ Cols ] #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #local Grid[R][C] = 0; #local C = C + 1; #end #local R = R + 1; #end // Note the following direction values: // 1 = North // 2 = East // 4 = West // 8 = South // A grid cell with two or more direction values simply contains a sum of // the above values. For example, if a cell contains the value 10 then // there are two directions for leaving that cell - east and south. // Negative values are used while creating a "path." When each path has // been completed, the values in the cells for that path are made positive // (multiplied by -1). In this way, while we are tracing a new path we // know we are finished creating that path when we reach a cell with a // positive value. // Establish start and end cells. #local StartEdge = RandInt( 2, S ); #switch ( StartEdge ) #case ( 0 ) // Start in north, or top row. End in south. #local StartRow = 0; #local StartCol = RandInt( Cols, S ); #local Grid[ StartRow ][ StartCol ] = -1; #local ExitRow = Rows - 1; #local ExitCol = RandInt( Cols, S ); #local Grid[ ExitRow ][ ExitCol ] = 8; #break #else // Start in west, or left column. End in east. #local StartRow = RandInt( Rows, S ); #local StartCol = 0; #local Grid[ StartRow ][ StartCol ] = -4; #local ExitRow = RandInt( Rows, S ); #local ExitCol = Cols - 1; #local Grid[ ExitRow ][ ExitCol ] = 2; #end // These will be useful later when actually creating the object. #local InitialRow = StartRow; #local InitialCol = StartCol; #local OnSolutionPath = yes ; // We're finished when the number of unused cells reaches zero. #local NumUnusedCells = ( Rows * Cols ) - 1; #while ( NumUnusedCells > 0 ) #if ( DoDebug ) #debug concat("GenSquareMaze: Starting new path at ",str(StartRow,0,0),",",str(StartCol,0,0),".\n") #end #local CurrRow = StartRow; #local CurrCol = StartCol; #local NumUnusedCells = NumUnusedCells - 1; #local CreatingPath = yes ; #while ( CreatingPath ) #local Direction = RandInt( 4, S ); #local Attempt = 0; #local Success = 0; #while ( ( Attempt < 4 ) & ( Success = 0 ) ) #if ( DoDebug ) #debug concat("GenSquareMaze: Attempting direction ",str(Direction,0,0),".\n") #end #switch ( Direction ) #case ( 0 ) // North #if ( CurrRow > 0 ) // Proceed if not at edge. #local NextRow = CurrRow - 1; #local NextCol = CurrCol; #local NextCellValue = Grid[ NextRow ][ NextCol ]; #if ( NextCellValue >= 0 ) // Next cell in selected direction not blocked. #local Success = 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] - 1; #if ( NextCellValue = 0 ) // Next cell unused, so set to direction value for current path. #local Grid[ NextRow ][ NextCol ] = -8; #else // Next cell on previous path, so add in the new direction value. #local Grid[ NextRow ][ NextCol ] = NextCellValue + 8; #end #end #end #break #case ( 1 ) // East #if ( CurrCol < ( Cols-1 ) ) #local NextRow = CurrRow; #local NextCol = CurrCol + 1; #local NextCellValue = Grid[ NextRow ][ NextCol ]; #if ( NextCellValue >= 0 ) #local Success = 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] - 2; #if ( NextCellValue = 0 ) #local Grid[ NextRow ][ NextCol ] = -4; #else #local Grid[ NextRow ][ NextCol ] = NextCellValue + 4; #end #end #end #break #case ( 2 ) // West #if ( CurrCol > 0 ) #local NextRow = CurrRow; #local NextCol = CurrCol - 1; #local NextCellValue = Grid[ NextRow ][ NextCol ]; #if ( NextCellValue >= 0 ) #local Success = 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] - 4; #if ( NextCellValue = 0 ) #local Grid[ NextRow ][ NextCol ] = -2; #else #local Grid[ NextRow ][ NextCol ] = NextCellValue + 2; #end #end #end #break #else // South #if ( CurrRow < ( Rows-1 ) ) #local NextRow = CurrRow + 1; #local NextCol = CurrCol; #local NextCellValue = Grid[ NextRow ][ NextCol ]; #if ( NextCellValue >= 0 ) #local Success = 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] - 8; #if ( NextCellValue = 0 ) #local Grid[ NextRow ][ NextCol ] = -1; #else #local Grid[ NextRow ][ NextCol ] = NextCellValue + 1; #end #end #end #end // switch ( Direction ) #if ( Success = 0 ) // Attempted direction is blocked. #local Attempt = Attempt + 1; #local Direction = ( Direction = 3 ? 0 : Direction + 1 ); #else #if ( NextCellValue > 0 ) // Reached a cell of a previous path. #local CreatingPath = no ; #else #local NumUnusedCells = NumUnusedCells - 1; #end #end #end // while ( ( Attempt < 4 ) & ( Success = 0 ) ) #if ( Success = 0 ) // All directions blocked. Mark current cell accordingly and back up. #if ( DoDebug ) #debug "GenSquareMaze: All directions blocked. Backing up!\n" #end #local CurrCellValue = Grid[ CurrRow ][ CurrCol ]; #local Grid[ CurrRow ][ CurrCol ] = -99; #switch ( CurrCellValue ) #case ( -1 ) #local CurrRow = CurrRow - 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] + 8; #break #case ( -2 ) #local CurrCol = CurrCol + 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] + 4; #break #case ( -4 ) #local CurrCol = CurrCol - 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] + 2; #break #else #local CurrRow = CurrRow + 1; #local Grid[ CurrRow ][ CurrCol ] = Grid[ CurrRow ][ CurrCol ] + 1; #end #local NumUnusedCells = NumUnusedCells + 1; #else // Advance to the next cell selected for current path. #if ( DoDebug ) #debug concat("GenSquareMaze: Advancing to ",str(NextRow,0,0),",",str(NextCol,0,0),".\n") #end #local CurrRow = NextRow; #local CurrCol = NextCol; #end #end // while ( CreatingPath ) // Prepare for selection of next path's starting cell. #local NewStartCellNum = RandInt( NumUnusedCells, S ); #local N = 0; // Prepare array for solution path data. #if ( OnSolutionPath ) #local NumSolutionCells = ( Rows * Cols ) - NumUnusedCells; #local SolutionData = array[ NumSolutionCells ] #local SolutionData[ 0 ] = < ExitRow, ExitCol, Grid[ ExitRow ][ ExitCol ] >; #local P = 1; #end // Adjust cell values of current path. Also select next path's starting cell. #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #local CurrCellValue = Grid[R][C]; #if ( CurrCellValue < 0 ) #if ( CurrCellValue = -99 ) #local Grid[R][C] = 0; #else #local Grid[R][C] = -1 * CurrCellValue; #if ( OnSolutionPath ) #local SolutionData[P] = < R, C, Grid[R][C] >; #local P = P + 1; #end #end #end #if ( CurrCellValue = 0 | CurrCellValue = -99 ) #if ( N = NewStartCellNum ) #local StartRow = R; #local StartCol = C; #end #local N = N + 1; #end #local C = C + 1; #end #local R = R + 1; #end #local OnSolutionPath = no ; #if ( DoDebug ) #debug concat("GenSquareMaze: Path complete. Number of unused cells = ",str(NumUnusedCells,0,0),".\n") #end #end // while ( NumUnusedCells > 0 ) // Dump the data into a file for use by the other macros. #write ( MazeDataFH, "\"AMAZE_SQuARE\",", Rows, ",", Cols ) #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #write ( MazeDataFH, ",", Grid[R][C] ) #local C = C + 1; #end #local R = R + 1; #end #write ( MazeDataFH, ",\"SOLuTION\",", NumSolutionCells ) #local P = 0; #while ( P < NumSolutionCells ) #write ( MazeDataFH, ",", SolutionData[P] ) #local P = P + 1; #end #fclose MazeDataFH // End of macro GenSquareMaze #end ////////////////////////////////////////////////////////////////////////// // Parameters for DirectionalPipe macro: // Direction = direction value set by GenSquareMaze // PipeWidth = width of pipe (the diameter!) // PathWidth = width of path (grid cell) #macro DirectionalPipe( Direction, PipeWidth, PathWidth ) #local HalfWidth = PathWidth / 2; #local Gap = ( PathWidth - PipeWidth ) / 2; #local Radius = PipeWidth / 2; #switch ( Direction ) #case ( 1 ) /* North */ union { cylinder { , , Radius } sphere { , Radius } } #break #case ( 2 ) /* East */ union { cylinder { , , Radius } sphere { , Radius } } #break #case ( 3 ) /* North + East */ intersection { torus { HalfWidth, Radius } box { -PathWidth, <0, PathWidth, 0> } translate } #break #case ( 4 ) /* West */ union { cylinder { <0, 0, HalfWidth>, , Radius } sphere { , Radius } } #break #case ( 5 ) /* North + West */ intersection { torus { HalfWidth, Radius } box { <0,-PathWidth, -PathWidth>, } translate <0, 0, PathWidth> } #break #case ( 6 ) /* East + West */ cylinder { <0, 0, HalfWidth>, , Radius } #break #case ( 7 ) /* North + East + West */ union { cylinder { <0, 0, HalfWidth>, , Radius } cylinder { , , Radius } } #break #case ( 8 ) /* South */ union { cylinder { , , Radius } sphere { , Radius } } #break #case ( 9 ) /* North + South */ cylinder { , , Radius } #break #case ( 10 ) /* East + South */ intersection { torus { HalfWidth, Radius } box { <-PathWidth, -PathWidth, 0>, <0, PathWidth, PathWidth> } translate } #break #case ( 11 ) /* North + East + South */ union { cylinder { , , Radius } cylinder { , , Radius } } #break #case ( 12 ) /* West + South */ intersection { torus { HalfWidth, Radius } box { <0, -PathWidth, 0>, PathWidth } } #break #case ( 13 ) /* North + West + South */ union { cylinder { , , Radius } cylinder { <0, 0, HalfWidth>, , Radius } } #break #case ( 14 ) /* East + West + South */ union { cylinder { <0, 0, HalfWidth>, , Radius } cylinder { , , Radius } } #break #case ( 15 ) /* North + East + West + South */ union { cylinder { <0, 0, HalfWidth>, , Radius } cylinder { , , Radius } } #end // switch ( Direction ) // End of macro DirectionalPipe #end ////////////////////////////////////////////////////////////////////////// // Parameters for Maze macro: // FileName = name of file from which the direction data will be read // PathWidth = width of path (grid cell) // WallWidth = width of maze walls // WallHeight = height of maze walls // ExtendEnds = Boolean indicating if path ends should be extended #macro Maze( FileName, PathWidth, WallWidth, WallHeight, ExtendEnds ) // A few safety checks. #if ( PathWidth <= 0.0 ) #error "Maze: The PathWidth parameter should be greater than zero." #end #if ( WallWidth <= 0.0 ) #error "Maze: The WallWidth parameter should be greater than zero." #end #if ( WallWidth > ( PathWidth - 0.1 ) ) #error "Maze: The WallWidth parameter should be at least 0.1 less than the PathWidth" #end #if ( WallHeight <= 0.0 ) #error "Maze: The WallHeight parameter should be greater than zero." #end #if ( strlen( FileName ) < 1 ) #error "Maze: FileName length is zero?" #end #if ( strcmp( strlwr( FileName ), "amaze.inc" ) = 0 ) #error "Maze: I doubt that the maze data is in \"amaze.inc\" !" #end #if ( file_exists( FileName ) = no ) #error "Maze: Did not find specified FileName." #end // Open the file and validate it. #fopen MazeDataFH FileName read #local FileTag = "x" #read ( MazeDataFH, FileTag ) #if ( strcmp( FileTag, "AMAZE_SQuARE" ) != 0 ) #fclose MazeDataFH #error "Maze: Expected name of file written by the GenSquareMaze macro." #end // Extract the maze data. #local Rows = 0; #local Cols = 0; #read ( MazeDataFH, Rows, Cols ) #local Grid = array[ Rows ][ Cols ] #local CellValue = 0; #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #read ( MazeDataFH, CellValue ) #local Grid[R][C] = CellValue; #local C = C + 1; #end #local R = R + 1; #end // And the solution data. #read ( MazeDataFH, FileTag ) // Should be "SOLuTION". Just using inherent type checking. #local NumSolutionCells = 0; #read ( MazeDataFH, NumSolutionCells ) #local SolutionData = array[ NumSolutionCells ] #local SolutionVector = <0,0,0>; #local P = 0; #while ( P < NumSolutionCells ) #read ( MazeDataFH, SolutionVector ) #local SolutionData[P] = SolutionVector; #local P = P + 1; #end // File should close itself after last value is read. Error? #if ( defined( MazeDataFH ) ) #fclose MazeDataFH #end // Now create the maze. union { #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #if ( ( R = 0 ) & ( HasNorthValue( Grid[R][C] ) = no ) ) box { , <(C+1)*PathWidth+WallWidth, WallHeight, Rows*PathWidth+WallWidth> } #end #if ( ( C = 0 ) & ( HasWestValue( Grid[R][C] ) = no ) ) box { <0, 0, (Rows-R-1)*PathWidth>, } #end #if ( HasSouthValue( Grid[R][C] ) = no ) box { , <(C+1)*PathWidth+WallWidth, WallHeight, (Rows-R-1)*PathWidth+WallWidth> } #end #if ( HasEastValue( Grid[R][C] ) = no ) box { <(C+1)*PathWidth, 0, (Rows-R-1)*PathWidth>, <(C+1)*PathWidth+WallWidth, WallHeight, (Rows-R)*PathWidth+WallWidth> } #end // The start and end points. #if ( ExtendEnds ) #if ( ( R = 0 ) & HasNorthValue( Grid[R][C] ) ) box { , } box { <(C+1)*PathWidth, 0, Rows*PathWidth>, <(C+1)*PathWidth+WallWidth, WallHeight, (Rows+1)*PathWidth> } #end #if ( ( R = (Rows-1) ) & HasSouthValue( Grid[R][C] ) ) box { , } box { <(C+1)*PathWidth, 0, -PathWidth>, <(C+1)*PathWidth+WallWidth, WallHeight, 0> } #end #if ( ( C = 0 ) & HasWestValue( Grid[R][C] ) ) box { <-PathWidth, 0, (Rows-R-1)*PathWidth>, <0, WallHeight, (Rows-R-1)*PathWidth+WallWidth> } box { <-PathWidth, 0, (Rows-R)*PathWidth>, <0, WallHeight, (Rows-R)*PathWidth+WallWidth> } #end #if ( ( C = (Cols-1) ) & HasEastValue( Grid[R][C] ) ) box { , <(Cols+1)*PathWidth, WallHeight, (Rows-R-1)*PathWidth+WallWidth> } box { , <(Cols+1)*PathWidth, WallHeight, (Rows-R)*PathWidth+WallWidth> } #end #end #local C = C + 1; #end #local R = R + 1; #end // A slight shift. This will align this maze with a pipe maze that uses the same direction data! translate <-WallWidth/2, 0, -WallWidth/2> } // Here are a couple of solution path objects that the user may use as desired. #local Radius = ( PathWidth - WallWidth ) / 2 - 0.04; #declare MazeSolutionDots = union { #local P = 0; #while ( P < NumSolutionCells ) #local V = SolutionData[P]; sphere { , Radius } #local P = P + 1; #end } #declare MazeSolutionSolid = union { #local P = 0; #while ( P < NumSolutionCells ) #local V = SolutionData[P]; object { DirectionalPipe( V.z, Radius*2, PathWidth ) translate } #local P = P + 1; #end } // End of macro Maze #end ////////////////////////////////////////////////////////////////////////// // Parameters for PipeMaze macro: // FileName = name of file from which the direction data will be read // PathWidth = width of path (grid cell) // PipeWidth = width of pipe (the diameter!) // ExtendEnds = Boolean indicating if path ends should be extended #macro PipeMaze( FileName, PathWidth, PipeWidth, ExtendEnds ) // A few safety checks. #if ( PathWidth <= 0.0 ) #error "PipeMaze: The PathWidth parameter should be greater than zero." #end #if ( PipeWidth <= 0.0 ) #error "PipeMaze: The PipeWidth parameter should be greater than zero." #end #if ( PipeWidth > ( PathWidth - 0.1 ) ) #error "PipeMaze: The PipeWidth parameter should be at least 0.1 less than the PathWidth" #end #if ( strlen( FileName ) < 1 ) #error "PipeMaze: FileName length is zero?" #end #if ( strcmp( strlwr( FileName ), "amaze.inc" ) = 0 ) #error "PipeMaze: I doubt that the maze data is in \"amaze.inc\" !" #end #if ( file_exists( FileName ) = no ) #error "PipeMaze: Did not find specified FileName." #end // Open the file and validate it. #fopen MazeDataFH FileName read #local FileTag = "x" #read ( MazeDataFH, FileTag ) #if ( strcmp( FileTag, "AMAZE_SQuARE" ) != 0 ) #fclose MazeDataFH #error "PipeMaze: Expected name of file written by the GenSquareMaze macro." #end // Extract the maze data. #local Rows = 0; #local Cols = 0; #read ( MazeDataFH, Rows, Cols ) #local Grid = array[ Rows ][ Cols ] #local CellValue = 0; #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) #read ( MazeDataFH, CellValue ) #local Grid[R][C] = CellValue; #local C = C + 1; #end #local R = R + 1; #end // And the solution data. #read ( MazeDataFH, FileTag ) // Should be "SOLuTION". Just using inherent type checking. #local NumSolutionCells = 0; #read ( MazeDataFH, NumSolutionCells ) #local SolutionData = array[ NumSolutionCells ] #local SolutionVector = <0,0,0>; #local P = 0; #while ( P < NumSolutionCells ) #read ( MazeDataFH, SolutionVector ) #local SolutionData[P] = SolutionVector; #local P = P + 1; #end // File should close itself after last value is read. Error? #if ( defined( MazeDataFH ) ) #fclose MazeDataFH #end // Now create the maze. union { #local R = 0; #while ( R < Rows ) #local C = 0; #while ( C < Cols ) object { DirectionalPipe( Grid[R][C], PipeWidth, PathWidth ) translate } // The start and end points. #if ( ExtendEnds ) #if ( ( R = 0 ) & HasNorthValue( Grid[R][C] ) ) object { DirectionalPipe( 9, PipeWidth, PathWidth ) translate } #end #if ( ( R = (Rows-1) ) & HasSouthValue( Grid[R][C] ) ) object { DirectionalPipe( 9, PipeWidth, PathWidth ) translate } #end #if ( ( C = 0 ) & HasWestValue( Grid[R][C] ) ) object { DirectionalPipe( 6, PipeWidth, PathWidth ) translate <-PathWidth, 0, (Rows-R-1)*PathWidth> } #end #if ( ( C = (Cols-1) ) & HasEastValue( Grid[R][C] ) ) object { DirectionalPipe( 6, PipeWidth, PathWidth ) translate } #end #end #local C = C + 1; #end #local R = R + 1; #end // A slight shift. This will align this maze with a regular maze that uses the same direction data! translate <0, PipeWidth/2, 0> } // Here is a solution path object that the user may use as desired. #declare PipeMazeSolution = union { #local P = 0; #while ( P < NumSolutionCells ) #local V = SolutionData[P]; object { DirectionalPipe( V.z, PipeWidth+0.01, PathWidth ) translate } #local P = P + 1; #end } // End of macro PipeMaze #end