Spirograph

Wrote this small Spirograph program after I got frustrated with the real thing.

-- Use this function to perform your initial setup
function setup()
    print("Pinch to Zoom the view")
    print("Change Commit to 1 to make it permanent")
    touches = {}
    lastPinchDist = 0
    pinchDelta = 1.0
    pinchCenter = vec2(0,0)
    offset = vec2(0,0)
    zoom = 1
    parameter("step",1,10,5)
    parameter("r1",10,400,150)
    parameter("r2",10,400,50)
    iparameter("wheel",1,8,7)
    iparameter("rot",1,50,10)
    iparameter("R",0,255,0)
    iparameter("G",0,255,255)
    iparameter("B",0,255,0)
    iparameter("Commit",0,1,0)
    img=image(WIDTH,HEIGHT)
    spriteMode(CORNER)
    done=0
end

function drawSpirograph()
    --setContext(img)
    noSmooth()
    stroke(R,G,B, 255)
    strokeWidth(2)
    lineCapMode(PROJECT)
    if wheel==1 then
        nr=25
    elseif wheel==2 then
        nr=32
    elseif wheel==3 then
        nr=45
    elseif wheel==4 then
        nr=52
    elseif wheel==5 then
        nr=60
    elseif wheel==6 then
        nr=21
    elseif wheel==7 then
        nr=75
    elseif wheel==8 then
        nr=84
    end
    local x1,y1=WIDTH/2+r1+r2,HEIGHT/2
    for ang=0,360*rot,step do
        local x2,y2=WIDTH/2+r1*math.cos(ang*math.pi/90.0)+
                r2*math.cos((-ang*105.0/nr)*math.pi/90.0),
                HEIGHT/2+r1*math.sin(ang*math.pi/90.0)+
                r2*math.sin((-ang*105.0/nr)*math.pi/90.0)
        line(x1,y1,x2,y2)
        x1,y1=x2,y2
    end
    --setContext()
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

function processTouches()
    touchArr = {}
    for k,touch in pairs(touches) do
        -- push touches into array
        table.insert(touchArr,touch)
    end

    if #touchArr == 2 then
        t1 = vec2(touchArr[1].x,touchArr[1].y)
        t2 = vec2(touchArr[2].x,touchArr[2].y)

        dist = t1:dist(t2)
        if lastPinchDist > 0 then 
            pinchDelta = dist/lastPinchDist
            
        else
            offset= offset + ((t1 + t2)/2-pinchCenter)/zoom
        end
        pinchCenter = (t1 + t2)/2
        lastPinchDist = dist
    else
        pinchDelta = 1.0
        lastPinchDist = 0
    end
end

function draw()
    
    -- This sets the background color to black
    background(0, 0, 0)
    sprite(img,0,0)
    if (Commit==1) then
        setContext(img)
    end
    -- compute pinch delta
    processTouches()
    -- scale by pinch delta
    zoom = math.max( zoom*pinchDelta, 0.2)

    translate(pinchCenter.x-offset.x*zoom,pinchCenter.y-offset.y*zoom)
    
    scale(zoom,zoom)
    
    --translate(pinchCenter.x,pinchCenter.y)

    pinchDelta = 1.0

    
    drawSpirograph()
    --sprite(img,WIDTH/2,HEIGHT/2)
    if (Commit==1) then
        setContext()
        Commit=0
    end
end

I noticed (when checking for characters that need to be escaped) the selecting the code above for copy and paste, that the selection stops before the ’ #’ character. Anyone knows how this can be avoided?

Seems to be detected as markup by the forum. I would try using — before and after code (replace - with ~) for code blocks.

@John Indeed! I changed it now. Thanks!

Updated version using my Zoom library.

-- Spirograph
-- Herwig Van Marck

function setup()
    print("Play with parameters to change the Spirograph\
Set commit to 1 to fix it\
"..
        "Change parameters again to add another one on top\
")
    zoom=Zoom(0,0)
    parameter("step",1,10,5)
    parameter("r1",10,400,150)
    parameter("r2",10,400,50)
    iparameter("wheel",1,8,7)
    iparameter("rot",1,50,10)
    iparameter("R",0,255,0)
    iparameter("G",0,255,255)
    iparameter("B",0,255,0)
    iparameter("Commit",0,1,0)
    img=image(WIDTH,HEIGHT)
    spriteMode(CORNER)
    done=0
end

function drawSpirograph()
    --setContext(img)
    noSmooth()
    stroke(R,G,B, 255)
    strokeWidth(2)
    lineCapMode(PROJECT)
    if wheel==1 then
        nr=25
    elseif wheel==2 then
        nr=32
    elseif wheel==3 then
        nr=45
    elseif wheel==4 then
        nr=52
    elseif wheel==5 then
        nr=60
    elseif wheel==6 then
        nr=21
    elseif wheel==7 then
        nr=75
    elseif wheel==8 then
        nr=84
    end
    local x1,y1=WIDTH/2+r1+r2,HEIGHT/2
    for ang=0,360*rot,step do
        local x2,y2=WIDTH/2+r1*math.cos(ang*math.pi/90.0)+
                r2*math.cos((-ang*105.0/nr)*math.pi/90.0),
                HEIGHT/2+r1*math.sin(ang*math.pi/90.0)+
                r2*math.sin((-ang*105.0/nr)*math.pi/90.0)
        line(x1,y1,x2,y2)
        x1,y1=x2,y2
    end
    --setContext()
end

function touched(touch)
    zoom:touched(touch)
end

function draw()
    zoom:draw()
    -- This sets the background color to black
    background(0, 0, 0)
    sprite(img,0,0)
    if (Commit==1) then
        zoom:clear()
        resetMatrix()
        zoom:draw()
        setContext(img)
    end

    drawSpirograph()
    --sprite(img,WIDTH/2,HEIGHT/2)
    if (Commit==1) then
        setContext()
        Commit=0
    end
end

-- Zoom library
-- Herwig Van Marck
-- usage:
--[[
function setup()
    zoom=Zoom(WIDTH/2,HEIGHT/2)
end
function touched(touch)
    zoom:touched(touch)
end
function draw()
    zoom:draw()
end
]]--

Zoom = class()

function Zoom:init(x,y)
    -- you can accept and set parameters here
    self.touches = {}
    self.initx=x or 0;
    self.inity=y or 0;
    self:clear()
    print("Tap and drag to move\
Pinch to zoom\
Double tap to reset")
end

function Zoom:clear()
    self.lastPinchDist = 0
    self.pinchDelta = 1.0
    self.center = vec2(self.initx,self.inity)
    self.offset = vec2(0,0)
    self.zoom = 1
    self.started = false
    self.started2 = false
end

function Zoom:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == ENDED then
        self.touches[touch.id] = nil
    else
        self.touches[touch.id] = touch
        if (touch.tapCount==2) then
            self:clear()
        end
    end
end

function Zoom:processTouches()
    local touchArr = {}
    for k,touch in pairs(self.touches) do
        -- push touches into array
        table.insert(touchArr,touch)
    end

    if #touchArr == 2 then
        self.started = false
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        local t2 = vec2(touchArr[2].x,touchArr[2].y)

        local dist = t1:dist(t2)
        if self.started2 then
        --if self.lastPinchDist > 0 then 
            self.pinchDelta = dist/self.lastPinchDist          
        else
            self.offset= self.offset + ((t1 + t2)/2-self.center)/self.zoom
            self.started2 = true
        end
        self.center = (t1 + t2)/2
        self.lastPinchDist = dist
    elseif (#touchArr == 1) then
        self.started2 = false
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        if not(self.started) then
            self.offset = self.offset + (t1-self.center)/self.zoom
            self.started = true
        end
        self.center=t1
    else
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        self.started = false
        self.started2 = false
    end
end

function Zoom:clip(x,y,w,h)
    clip(x*self.zoom+self.center.x- self.offset.x*self.zoom,
        y*self.zoom+self.center.y- self.offset.y*self.zoom,
        w*self.zoom+1,h*self.zoom+1)
end

function Zoom:text(str,x,y)
    local fSz = fontSize()
    local xt=x*self.zoom+self.center.x- self.offset.x*self.zoom
    local yt=y*self.zoom+self.center.y- self.offset.y*self.zoom
    fontSize(fSz*self.zoom)
    local xtsz,ytsz=textSize(str)
    tsz=xtsz
    if tsz<ytsz then tsz=ytsz end
    if (tsz>2048) then
        local eZoom= tsz/2048.0
        fontSize(fSz*self.zoom/eZoom)
        pushMatrix()
        resetMatrix()
        translate(xt,yt)
        scale(eZoom)
        text(str,0,0)
        popMatrix()
        fontSize(fSz)
    else
        pushMatrix()
        resetMatrix()
        fontSize(fSz*self.zoom)
        text(str,xt,yt)
        popMatrix()
        fontSize(fSz)
    end
end

function Zoom:draw()
    -- compute pinch delta
    self:processTouches()
    -- scale by pinch delta
    self.zoom = math.max( self.zoom*self.pinchDelta, 0.2 )

    translate(self.center.x- self.offset.x*self.zoom,
        self.center.y- self.offset.y*self.zoom)
    
    scale(self.zoom,self.zoom)

    self.pinchDelta = 1.0
end