Tower of Hanoi in 3D

You can change the number of disks.
As well as set the connection of each pair of the 3 pillars:
1.can move disks from pillar A to pillar B
2.or from B to A
3.or both
4.or both not

function setup()
    parameter.integer("disk number",1,20,7)
    parameter.action("Reset",init)
    parameter.number("camPos x",-50,50,-15)
    parameter.number("camPos y",0,50,15)
    parameter.number("camPos z",0,50,20)
    parameter.integer("Point_Range",0,50,40)
    --[[
    0:both
    1:a to b
    2:b to a
    3:both not
      ]]
    parameter.integer("isCon12",0,3,0)
    parameter.integer("isCon23",0,3,0)
    parameter.integer("isCon13",0,3,0)
    d=5
    pillars={width=1,height=10}
    pillars[1]={x=-d,y=pillars.height/2,z=0,content={}}
    pillars[2]={x=0,y=pillars.height/2,z=3^.5*d,content={}}
    pillars[3]={x=d,y=pillars.height/2,z=0,content={}}
    -------------------------------------------------------------------------
    m=CreateCube()
    m.shader=shader(pointShader.vertexShader,pointShader.fragmentShader)
    m.normals=CalculateNormals(m.vertices) 
    m.shader.pointRange=Point_Range  --NEW
    --user parameters and fixed light settings
    SurfaceColour=color(223, 177, 177, 255) 
    AmbientColour=color(255, 255, 255, 255) 
    AmbientStrength=0.2
    m:setColors(SurfaceColour)
    m.shader.ambientLight = color(AmbientColour.r*AmbientStrength,AmbientColour.g*AmbientStrength,
                                    AmbientColour.b*AmbientStrength)
    DirectColour=color(255,0,0)
    DirectStrength=0.8
    m.shader.directColor=color(DirectColour.r*DirectStrength,DirectColour.g*DirectStrength,
                                DirectColour.b*DirectStrength)
    m.shader.directDirection=vec4(-2, 10, 5, 1) 
    m.shader.specularColor=color(255)
    m.shader.reflect=1 
    m.shader.shiny=0.6
    init()
end 
function init()
    tween.stopAll()
    N=disk_number
    disks={height=math.min(pillars.height*.8/N,2),maxWidth=d*1.8}
    isCon={{},{},{}}
    for i=1,3 do
        for j=1,3 do
            isCon[i][j]=true
        end
        pillars[i].content={}
    end
    for i=1,N do
        disks[i]={x=pillars[1].x,y=(i-.5)*disks.height,z=pillars[1].z,
        width=pillars.width+(N-i+1)*(disks.maxWidth-pillars.width)/N}
        pillars[1].content[i]=i
    end
    if isCon12==1 or isCon12==3 then isCon[2][1]=false end
    if isCon12==2 or isCon12==3 then isCon[1][2]=false end
    if isCon13==1 or isCon13==3 then isCon[3][1]=false end
    if isCon13==2 or isCon13==3 then isCon[1][3]=false end
    if isCon23==1 or isCon23==3 then isCon[3][2]=false end
    if isCon23==2 or isCon23==3 then isCon[2][3]=false end
    instruction=nil
    co=coroutine.create(function() hanoi(1,3,N) end)
    if isCycle() then
        animate()
    else
        alert("The arrows can't form a circle,please change the parameters!","No Solution!")
    end
end
function isCycle()
    local r=true
    for i=1,3 do
        local x,y,z=i,i+1,i+2
        if y>3 then y=y%3 end
        if z>3 then z=z%3 end
        if not(isCon[x][y] or (isCon[x][z] and isCon[z][y])) then r=false end
        if not(isCon[x][z] or (isCon[x][y] and isCon[y][z])) then r=false end
    end
    return r
end
function animate()
    coroutine.resume(co)--call up the producer
    if instruction then
        if instruction[1]=="move" then
            local s,e=instruction[2],instruction[3]
            instruction=nil
            local i=table.remove(pillars[s].content)--the index of the disk to move
            table.insert(pillars[e].content,i)
            tween(.4,disks[i],{y=pillars.height+disks.height},tween.easing.cubicIn,function()
                tween(.4,disks[i],{x=pillars[e].x,z=pillars[e].z},tween.easing.linear,function()
                    tween(.4,disks[i],{y=(#pillars[e].content-.5)*disks.height},tween.easing.cubicOut,function()
                        animate()
                    end)
                end)
            end)
        end
    else
        --init()
        print("Complete!")
    end
end
function hanoi(x,y,n)--move n disks from x to y
    local z=6-(x+y)
    if n==1 then
        if isCon[x][y] then
            instruction={"move",x,y}
        else
            instruction={"move",x,z}
            coroutine.yield(co)--once get a instruction?yield itself(ie the producer)
            instruction={"move",z,y}
        end
        coroutine.yield(co)--once get a instruction?yield itself(ie the producer)
    else
        if isCon[x][y] then
            hanoi(x,z,n-1)
            hanoi(x,y,1)
            hanoi(z,y,n-1)
        else
            hanoi(x,y,n-1)
            hanoi(x,z,1)
            hanoi(y,x,n-1)
            hanoi(z,y,1)
            hanoi(x,y,n-1)
        end
    end
end
function draw()
    background()
    perspective()
    camera(camPos_x, camPos_y, camPos_z, 0, pillars.height/2, 0)
    m.shader.eyePosition=vec4(camPos_x, camPos_y, camPos_z) 
    m.shader.pointRange=Point_Range
    pushMatrix()
    rotate(90,1,0,0)
    --sprite("Cargo Bot:Starry Background")
    resetMatrix()
    --move and draw cube
    for i=1,N do
        translate(disks[i].x,disks[i].y,disks[i].z)
        scale(disks[i].width,disks.height,disks[i].width)
        m.shader.mModel = modelMatrix()
        m:draw()
        resetMatrix()
    end
    for i=1,3 do
        translate(pillars[i].x,pillars[i].y,pillars[i].z)
        scale(1,pillars.height,1)
        m.shader.mModel = modelMatrix()
        m:draw()
        resetMatrix()
    end
    translate(0,-.01,0)
    scale(100,.02,100)
    m.shader.mModel = modelMatrix()
    m:draw()
    resetMatrix()
    popMatrix()
end
function touched(touch)
    if touch.state==BEGAN then
        --animate()
    end
end
function CreateCube() --NEW
   local m = mesh()

    --vertices for the corners of the cube (stolen from 3d lab)
    local vertices = {
      vec3(-0.5, -0.5,  0.5), -- Left  bottom front
      vec3( 0.5, -0.5,  0.5), -- Right bottom front
      vec3( 0.5,  0.5,  0.5), -- Right top    front
      vec3(-0.5,  0.5,  0.5), -- Left  top    front
      vec3(-0.5, -0.5, -0.5), -- Left  bottom back
      vec3( 0.5, -0.5, -0.5), -- Right bottom back
      vec3( 0.5,  0.5, -0.5), -- Right top    back
      vec3(-0.5,  0.5, -0.5), -- Left  top    back
    }

    -- now construct a cube out of the vertices above
    m.vertices = {
      -- Front
      vertices[1], vertices[2], vertices[3],
      vertices[1], vertices[3], vertices[4],
      -- Right
      vertices[2], vertices[6], vertices[7],
      vertices[2], vertices[7], vertices[3],
      -- Back
      vertices[6], vertices[5], vertices[8],
      vertices[6], vertices[8], vertices[7],
      -- Left
      vertices[5], vertices[1], vertices[4],
      vertices[5], vertices[4], vertices[8],
      -- Top
      vertices[4], vertices[3], vertices[7],
      vertices[4], vertices[7], vertices[8],
      -- Bottom
      vertices[5], vertices[6], vertices[2],
      vertices[5], vertices[2], vertices[1],
    }

    --now texture it
    -- all the unique texture positions needed
    local q=.03
    local texvertices = { vec2(q,q),
                          vec2(1-q,q),
                          vec2(q,1-q),
                          vec2(1-q,1-q) }

    -- apply the texture coordinates to each triangle
    m.texCoords = {
      -- Front
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
      -- Right
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
      -- Back
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
      -- Left
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
      -- Top
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
      -- Bottom
      texvertices[1], texvertices[2], texvertices[4],
      texvertices[1], texvertices[4], texvertices[3],
    } 
    return m
end
pointShader = {

vertexShader = [[

uniform mat4 modelViewProjection;
uniform mat4 mModel; //matrix to convert from object to world space
uniform vec4 directColor; 
uniform vec4 directDirection; 

attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying vec4 vDirectDiffuse; 
varying lowp vec4 vPosition; 
varying lowp vec4 vNormal; 

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    vPosition = mModel * position; 
    vNormal = mModel * vec4( normal, 0.0 );
    vec4 norm = normalize(vNormal); 
    vec4 d = normalize( directDirection - vPosition );
    vDirectDiffuse = directColor * max( 0.0, dot( norm, d ));   
    gl_Position = modelViewProjection * position;
}

]],
fragmentShader = [[

precision highp float;

uniform vec4 ambientLight; 
uniform vec4 directDirection; 
uniform float pointRange;  //--NEW
uniform float reflect;
uniform float shiny;
uniform vec4 specularColor;
uniform vec4 eyePosition;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying vec4 vDirectDiffuse; 
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;

vec4 normalizedNormal = normalize(vNormal);   

vec4 GetSpecularColor(vec4 lightPosition)
{
    vec4 lightDirection = normalize( lightPosition - vPosition );
    vec4 cameraDirection = normalize( eyePosition - vPosition );
    vec4 halfAngle = normalize( cameraDirection + lightDirection );
    vec4 specularColor = min(specularColor + 0.5, 1.0);
    float spec = pow( max( 0.0, dot( normalizedNormal, halfAngle)), 32.0 );
    return specularColor * spec;
}  

void main()
{
    vec4 pixel = vColor;
    vec4 ambient = ambientLight;
    vec4 diffuse = vDirectDiffuse;
    vec4 specular = GetSpecularColor( directDirection );
    //--NEW now calculate attenuation
    float attenuation = max( 0.0, 1.0 - length( directDirection - vPosition ) / pointRange );
    vec4 totalColor = clamp(pixel * reflect * attenuation * (ambient + diffuse + specular * shiny),0.,1.);
    totalColor.a=1.;
    gl_FragColor=totalColor;
}
]]
}
function CalculateNormals(vertices)
    local norm = {}
    for i=1, #vertices,3 do 
        local n = ((vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])):normalize()
        norm[i] = n 
        norm[i+1] = n
        norm[i+2] = n
    end
return norm
end

You haven’t included the shader “pointShader” so it won’t run.

@Ignatz

I copied the lighting code from your ebook!

I thought I recognized it…

Put three ~~~ on a blank line before and after your code to make it look nice, I fixed it above

I will do it…It’s my first post,so

It looks real nice, well done, I bet you’re pleased with that! B)

thank you

I added the 3~'s just in case you wonder how they got there. Nice job with the program.

I was inspired by the recursive algorithm to solve the hanoi problem,and I extend it to solve all the conditions.

<3

Goodjob

It would seem better if replace the cube with cylinder,but there are some troubles in dealing with the normals for me.And I’d like to add some arrows to indicate the connection between each pair of the pillars.

@yahuishuo - The normals are actually very easy for a cylinder.

Calculate them in the vertex shader. Give it the bottom centre position, and then for any vertex, you calculate the centre point of the cylinder at the same height as the vertex.

eg if the bottom centre is at (10,14,20), and the vertex is at (25,40,10), then the centre point at the same height is (10,40,20).

The normal is then your vertex LESS this centre point, normalised (although there’s no need to normalise because you will need to do it again in the fragment shader anyway).