Cool Line thing- NOW IN TECHNICOLOR!

Made this cool effect for the start screen of a game I’m working on, liked it so much I thought id share it with you all :slight_smile:
EDIT: NOW IN BEAUTIFUL TECHNICOLOR!!!
EDITEDIT:MANY Thanks to @Ignatz for the (grayscale) shader that makes the (uncolored version of the) finalized effect so glorious!
EDITEDITEDIT: Now in a class so it can be used elsewhere, also now without a lot of the bulk it had earlier! Plus you can now switch between grayscale and color in the overlay!


--# Main
displayMode(OVERLAY)
displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
function setup()
    parameter.boolean("Grayscale",true, function() grayscale = Grayscale end)
    p=PolarAnim()
    m=mesh() 
    m:addRect(math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT),math.min(WIDTH,HEIGHT))
    m.texture=p.img
    m.shader=shader(GrayScaleShader.v,GrayScaleShader.f)
    m.shader.luminance=vec3(0.21,0.72,0.07)
end
function draw()
    if grayscale then
    p:draw()
    m:draw()
    else
        p:draw(true)
    end
end
GrayScaleShader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec3 luminance;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    float c = col.r*luminance.x + col.g*luminance.y + col.b*luminance.z;
    gl_FragColor = vec4(c, c, c, col.a );
}      
]]}
--# PolarAnim
PolarAnim = class()
function PolarAnim:init()
    self.red = true 
    pi,sin,cos,tan,asin,acos,atan,log,ln,abs,sqrt = 
    math.pi,math.sin,math.cos,math.tan,math.asin,
    math.acos,math.atan,math.log,math.ln,math.abs,math.sqrt
    self.xmi,self.xma,self.rangex,self.ymi,self.yma,
    self.rangey,theta,self.blend = 0,0,0,0,0,0,0,0
    self:grid(-200,200,-200,200)
    local thsteptbl = {77.70001,pi/3.3333,pi/4.4444,pi/11.111,pi/2.2222,pi/1.1111,pi/9.9999;
    pi/1.23456,pi/.7777,pi*3.3333,pi*1.1111,pi*4.4444,pi*.757575;
    pi*pi,pi*pi*pi*pi*pi+.000003,pi*.5001,thstep = pi*.1111,thstep = pi*.2222}
    self.thstep = thsteptbl[math.random(#thsteptbl)]
    g = math.random(200)/2
    self.func = "200*cos(g*theta)"
    self.img = image(math.min(WIDTH,HEIGHT),math.min(WIDTH,HEIGHT))
    self.perframe = math.random(10,100)
    self.drawcol = true
    self.frame=0
    self.a = true
end
function PolarAnim:draw(draw)
    if self.drawcol == true then
        self:colorCycle()
        else
        if self.a then
            self.frame = self.frame + 1
            if self.frame == 254 then self.a = false end
            else
            self.frame = self.frame - 1
            if self.frame == 0 then self.a = true end
        end
    end
    background(255)
        for i=0,self.perframe do
            self:calcpoint()
        end
    if draw then
    pushMatrix()
    translate(math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT)/2)
    sprite(self.img,0,0)
    popMatrix()
    end
end
function PolarAnim:calcpoint()
    local dist = loadstring("return "..self.func)()
    local x = dist*math.cos(theta)
    local y = dist*math.sin(theta)
    local lastpoi = poi
    self:convertRawToScreenCoords(x,y)
    setContext(self.img)
    smooth()
    if self.drawcol == false then
        stroke(self.frame%255)
    elseif self.drawcol == true then
    stroke(self.col)
    end
    fill(stroke())
    strokeWidth(1)
        if lastpoi then
            line(lastpoi.x,lastpoi.y,poi.x,poi.y)
        end
    setContext()
    theta = theta + self.thstep
end
function PolarAnim:grid(xmin,xmax,ymin,ymax)
    self.origin = vec2(0,0)
    if xmin<0 and 0<xmax then
        self.xmi = xmin
        self.xma = xmax
        self.rangex = xmax - xmin
        local xaxisloc = 0-xmin
        self.origin.x = (xaxisloc/self.rangex)*math.min(WIDTH,HEIGHT)
        else
        self.xmi = xmin
        self.xma = xmax
        self.rangex = xmax - xmin
        local xaxisloc = 0-xmin
        self.origin.x = (xaxisloc/self.rangex)*math.min(WIDTH,HEIGHT)
    end
    if ymin<0 and 0<ymax then
        self.ymi = ymin
        self.yma = ymax
        self.rangey = ymax-ymin
        local yaxisloc = 0-ymin
        self.origin.y = (yaxisloc/self.rangey)*math.min(WIDTH,HEIGHT)
        else
        self.ymi = ymin
        self.yma = ymax
        self.rangey = ymax-ymin
        local yaxisloc = 0-ymin
        self.origin.y = (yaxisloc/self.rangey)*math.min(WIDTH,HEIGHT)
    end
end
function PolarAnim:convertRawToScreenCoords(x,y)
    local xp = x-self.xmi
    local coord = vec2((xp/self.rangex)*math.min(WIDTH,HEIGHT),0)
    local yp = y-self.ymi
    coord.y = (yp/self.rangey)*math.min(WIDTH,HEIGHT)
    drawpoint = true
    poi = coord
    return coord
end
function PolarAnim:colorCycle()
    self.blend = self.blend + .01
    if self.red then
        local c2 = color(255, 0, 0, 255)
        local c1 = color(255,127.5,0,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.red = false
            self.orange = true
        end
        elseif self.orange then
        local c2 = color(255,127.5,0,255)
        local c1 = color(255,255,0,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.orange = false
            self.yellow = true
        end
        elseif self.yellow then
        local c2 = color(255,255,0,255)
        local c1 = color(127.5,255,0,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.yellow = false
            self.yellowgreen = true
        end
        elseif self.yellowgreen then
        local c2 = color(127.5,255,0,255)
        local c1 = color(0,255,0,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.yellowgreen = false
            self.green = true
        end
        elseif self.green then
        local c2 = color(0,255,0,255)
        local c1 = color(0, 255, 127.5, 255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.green = false
            self.seafoamgreen = true
        end
        elseif self.seafoamgreen then
        local c2 = color(0,255,127.5,255)
        local c1 = color(0, 255, 255, 255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.seafoamgreen = false
            self.teal = true
        end
        elseif self.teal then
        local c2 = color(0,255,255,255)
        local c1 = color(0,127.5,255,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.teal = false
            self.skyblue = true
        end
        elseif self.skyblue then
        local c2 = color(0,127.5,255,255)
        local c1 = color(0,0,255,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.skyblue = false
            self.blue = true
        end
        elseif self.blue then
        local c2 = color(0,0,255,255)
        local c1 = color(127.5,0,255,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.blue = false
            self.purple = true
        end
        elseif self.purple then
        local c2 = color(127.5,0,255,255)
        local c1 = color(255, 0, 255, 255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.pink = true
            self.purple = false
        end
        elseif self.pink then
        local c2 = color(255, 0, 255, 255)
        local c1 = color(255, 0, 127.5, 255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.fuscia = true
            self.pink = false
        end
        elseif self.fuscia then
        local c2 = color(255,0,127.5,255)
        local c1 = color(255,0,0,255)
        self.col = c1:mix(c2,self.blend)
        if self.blend >= 1 then
            self.blend = 0
            self.red = true
            self.fuscia = false
        end
    end
end

@Monkeyman32123 Nice effect.

just a little thing - you’re using loadstring at every draw, which may not matter in this small program, but would slow the program if it were bigger, because loadstring is necessarily a slow function, having to make sense of a text string

why not put it at the end of setup?

Also, some shorthand, you can just say

sin,cos,tan=math.sin,math.cos,math.tan
--etc

ie you don’t need to write separate functions

@dave1707 thank you! I think it’ll make a nice start screen background for a futuristic matrix-wizard demigod RPG game. Yes, that’s a lot of subtypes for a game, but I can’t find one small phrase to describe it. It’s like if Merlin were stuck in the matrix, only more kick-butt.
@Ignatz Hm, honestly I’d never thought of putting it in setup, I just went with what worked when I went on a coding frenzy :stuck_out_tongue: thanks for the tip! (I’ll keep that in mind when I’m making use of load string in more intensive programs)
And I didn’t know you could do that :open_mouth: I always get confused on whether I have to put function … end around function types or not. As in callbacks it has to be function function() end. I’m pretty amateur, so thank you very much for the tips :slight_smile: all the help I can get is useful!

@Monkeyman32123 That’s how we all learned. We shared some code and someone told use a better way of doing something.

@dave1707 and it’s truly a beautiful way of learning

@Monkeyman32123 - the reason that sin=math.sin works, is that function names (like math.sin) don’t store the actual function. They store the memory address of the function, which is a number. So you can assign this number to any variable, and it will run that function.

This is also a little quicker than what you did, because you have written a function that calls the original function. This approach calls the original function immediately.

However, your approach (of a separate function) is useful if you want to do stuff like getting math.sin to handle degrees instead of radians

function sin(x) --x is in degrees
    return math.sin(math.rad(x))
end

You can even get math.sin to handle degrees, by storing its address first, like so

radian_sin=math.sin --store the address of the original sin function
--now make a new function and put its address in math.sin
function math.sin(x) --x is in degrees
    return radian_sin(math.rad(x)) --run the original math.sin function
end
--now math.sin handles degrees instead of radians
print(math.sin(60))

This may look confusing, but just keep in mind that function names only store memory addresses (in the same way your phone stores phone numbers instead of the people they belong to).

Woah, thank you ignatz! That was super helpful, don’t think I’ll be using method two any time soon (complex), but when I get to that level I definitely will!
Also, here’s a version that does randomized patterns, over 300,000 possible combinations!
EDIT: NOW BROUGHT TO YOU IN TECHNICOLOR!
EDIT EDIT: posted the code up there ^ so I cut it out of this post

@Monkeyman32123 try changing LANDSCAPE_ANY to PORTRAIT_ANY. It looks really cool as well!

very nice

@monkeyman32123 you program is so great i’ve messed around with it a little bit.

CAUTION: the following video is so amazingly beautiful it may definitly raise the expectations of the most sensitive of you to such high standards that you’ll be forever depressed about your own achievement capabilities… (note that i am not the author of this program, so i decline any responsibilities… :wink: )

[edit] you are however partially protected thanks to the high compression rate of youtube that degrade the video quality vs the original.

http://www.youtube.com/watch?v=iwJNoik5TyM

Here are my changes


--# Main
--displayMode(OVERLAY)
-- displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
grayscale = true

function setup()
    parameter.integer("sampleDuration",1,20,6)
    showRestart()
end
function showRestart(thstep0, g0, perframe0)
    p=PolarAnim(thstep0, g0, perframe0)
    m=mesh() 
    m:addRect(math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT),math.min(WIDTH,HEIGHT))
    m.texture=p.img
    m.shader=shader(GrayScaleShader.v,GrayScaleShader.f)
    m.shader.luminance=vec3(0.21,0.72,0.07)
    rectMode(CENTER)
end
function draw()
    if grayscale then
    p:draw()
    m:draw()
    else
        p:draw(true)
    end
    fill(0)
    text("color/nb",50,100)
    text("restart",WIDTH - 50,100)
    text("save",WIDTH - 50,HEIGHT- 50)
    text("samples",50,HEIGHT- 50)
end
local abs = math.abs
function touched(t)
    if not (t.state == BEGAN) then return end
    if abs(t.x-50)<50 and abs(t.y-100)<25 then grayscale = not grayscale end
    if abs(t.x-(WIDTH- 50))<50 and abs(t.y-100)<25 then stopSamples = true showRestart() end
    if abs(t.x-(WIDTH- 50))<50 and abs(t.y-(HEIGHT- 50))<25 then p:save() end
    if abs(t.x-(50))<50 and abs(t.y-(HEIGHT- 50))<25 then stopSamples = false runSamples() end
end
local index
function runSamples()
    if stopSamples  then return end
    index = ( (index or 0) % #samples ) 
    local s = samples[index + 1]
    showRestart(s.thstep0, s.g0, s.perframe0)
    grayscale = false
    index = index + 1
    
    tween.delay(sampleDuration,runSamples)
end
GrayScaleShader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec3 luminance;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    float c = col.r*luminance.x + col.g*luminance.y + col.b*luminance.z;
    gl_FragColor = vec4(c, c, c, col.a );
}      
]]}

--# PolarAnim
PolarAnim = class()
function PolarAnim:init(thstep0, g0, perframe0)
    self.red = true 
    poi = nil
    pi,sin,cos,tan,asin,acos,atan,log,ln,abs,sqrt = 
    math.pi,math.sin,math.cos,math.tan,math.asin,
    math.acos,math.atan,math.log,math.ln,math.abs,math.sqrt
    self.xmi,self.xma,self.rangex,self.ymi,self.yma,
    self.rangey,theta,self.blend = 0,0,0,0,0,0,0,0
    self:grid(-200,200,-200,200)
    local thsteptbl = {77.70001,pi/3.3333,pi/4.4444,pi/11.111,pi/2.2222,pi/1.1111,pi/9.9999;
    pi/1.23456,pi/.7777,pi*3.3333,pi*1.1111,pi*4.4444,pi*.757575;
    pi*pi,pi*pi*pi*pi*pi+.000003,pi*.5001,thstep = pi*.1111,thstep = pi*.2222}
    
    self.thstep0 = thstep0 or thsteptbl[math.random(#thsteptbl)]
    self.g0 = g0 or math.random(200)/2
    self.perframe0 = perframe0 or math.random(10,100)
    
    self.thstep = self.thstep0
    self.g=self.g0
    self.theta=theta
    self.func = "200*cos(g*theta)"
    self.func = function(self) return 200*cos(self.g*self.theta) end
    self.img = image(math.min(WIDTH,HEIGHT),math.min(WIDTH,HEIGHT))
    self.perframe = self.perframe0
    self.drawcol = true
    self.frame=0
    self.a = true
end
function PolarAnim:save()
    samples = samples or {}
    table.insert(samples,{thstep0=self.thstep0, g0=self.g0, perframe0=self.perframe0})
    local t = { "samples = {" }
    for i,s in pairs(samples) do
        local str = "{ thstep0=" .. tostring(s.thstep0) ..","
                    .. "g0=" .. tostring(s.g0) .. ","
                    .. "perframe0=" .. tostring(s.perframe0) .. "},"
        table.insert(t,str)
    end
    table.insert(t,"}")
    local str = table.concat(t,"\
")
    saveProjectTab("samples",str)
    print("saved")
end

function PolarAnim:draw(draw)
    if self.drawcol == true then
        self:colorCycle()
        else
        if self.a then
            self.frame = self.frame + 1
            if self.frame == 254 then self.a = false end
            else
            self.frame = self.frame - 1
            if self.frame == 0 then self.a = true end
        end
    end
    background(255)
        for i=0,self.perframe do
            self:calcpoint()
        end
    if draw then
    pushMatrix()
    translate(math.min(WIDTH,HEIGHT)/2,math.min(WIDTH,HEIGHT)/2)
    sprite(self.img,0,0)
    popMatrix()
    end
end
function PolarAnim:calcpoint()
    local dist = self:func()
    local x = dist*math.cos(self.theta)
    local y = dist*math.sin(self.theta)
    local lastpoi = poi
    self:convertRawToScreenCoords(x,y)
    setContext(self.img)
    smooth()
    if self.drawcol == false then
        stroke(self.frame%255)
    elseif self.drawcol == true then
    stroke(self.col)
    end
    fill(stroke())
    strokeWidth(1)
        if lastpoi then
            line(lastpoi.x,lastpoi.y,poi.x,poi.y)
        end
    setContext()
    self.theta = self.theta + self.thstep
end
function PolarAnim:grid(xmin,xmax,ymin,ymax)
    self.origin = vec2(0,0)
    if xmin<0 and 0<xmax then
        self.xmi = xmin
        self.xma = xmax
        self.rangex = xmax - xmin
        local xaxisloc = 0-xmin
        self.origin.x = (xaxisloc/self.rangex)*math.min(WIDTH,HEIGHT)
        else
        self.xmi = xmin
        self.xma = xmax
        self.rangex = xmax - xmin
        local xaxisloc = 0-xmin
        self.origin.x = (xaxisloc/self.rangex)*math.min(WIDTH,HEIGHT)
    end
    if ymin<0 and 0<ymax then
        self.ymi = ymin
        self.yma = ymax
        self.rangey = ymax-ymin
        local yaxisloc = 0-ymin
        self.origin.y = (yaxisloc/self.rangey)*math.min(WIDTH,HEIGHT)
        else
        self.ymi = ymin
        self.yma = ymax
        self.rangey = ymax-ymin
        local yaxisloc = 0-ymin
        self.origin.y = (yaxisloc/self.rangey)*math.min(WIDTH,HEIGHT)
    end
end
function PolarAnim:convertRawToScreenCoords(x,y)
    local xp = x-self.xmi
    local coord = vec2((xp/self.rangex)*math.min(WIDTH,HEIGHT),0)
    local yp = y-self.ymi
    coord.y = (yp/self.rangey)*math.min(WIDTH,HEIGHT)
    drawpoint = true
    poi = coord
    return coord
end
local colors = {
    color(255, 0, 0, 255),
    color(255, 190, 0, 255),
    color(148, 27, 27, 255),
    color(249, 255, 0, 255),
    color(86, 133, 29, 255),
    color(127.5,255,0,255),
    color(31, 104, 109, 255),
    color(0,255,255,255),
    color(0,127.5,255,255),
    color(0,0,255,255),
    color(255, 0, 255, 255),
    color(89, 26, 151, 255),

}
local n = #colors
local floor = math.floor
function PolarAnim:colorCycle()
    self.blend = self.blend + .01
    local blend = self.blend
    local c2 = colors[ floor(blend)%n +1 ]
    local c1 = colors[ floor(blend+1)%n +1 ]
    self.col = c1:mix(c2, blend - floor(blend))
end

--# samples
samples = {
{ thstep0=2.3799920545433,g0=74,perframe0=95},
{ thstep0=1.4137308314237,g0=26,perframe0=65},
{ thstep0=0.31416240698305,g0=50,perframe0=43},
{ thstep0=10.471870792211,g0=8.5,perframe0=44},
{ thstep0=0.31416240698305,g0=3,perframe0=22},
{ thstep0=9.8696044010894,g0=39,perframe0=43},
{ thstep0=3.4906235974036,g0=90,perframe0=21},
{ thstep0=2.5447063355283,g0=3,perframe0=39},
{ thstep0=2.8274616628474,g0=11.5,perframe0=52},
{ thstep0=2.8274616628474,g0=51.5,perframe0=76},
{ thstep0=0.94248722094915,g0=89,perframe0=92},
{ thstep0=0.31416240698305,g0=39,perframe0=20},
{ thstep0=2.8274616628474,g0=25,perframe0=31},
{ thstep0=0.28274616628474,g0=83,perframe0=58},
{ thstep0=0.70686541571186,g0=52,perframe0=13},
{ thstep0=9.8696044010894,g0=66,perframe0=53},
{ thstep0=0.94248722094915,g0=45,perframe0=66},
}

i could spend hours tapping on the restart button

@Monkeyman32123 what about adding random creative music generator now? Any musician among us who wants to take the challenge?

@Jmv38 are there elegant mathematical patterns for music to go with it?
@Monkeyman32123 this is really… Weird. I have spent the last ten minutes hitting restart, surprisingly fast too.

I am not a musician, but harmony is deeply related to mathematics. Bach composition feel mathematic.

@Jmv38 I know what you mean about some of Bach’s compositions, and Handel too but I’m not sure we could accomplish that with Codea’s sound function and a sine wave :-/ I’ll have a look at harmony, not to say anything will come from it.

Edit: Maybe string sounds would be a good start (bit off-topic): https://www.youtube.com/watch?v=V5tUM5aLHPA

@luatee wow! Fantastic link. You’ve completely got my point!

Thank you @Jmv38… And I really like your edit to the code, makes the whole thing a bit more elegant. And I understand, I definitely did not spend multiple hours hitting the restart button, and by did not I mean I did :stuck_out_tongue: also, love your comment about becoming depressed by its beauty! Haha
And @Luatee it is very weird, I spent far too long hitting that restart button
@bothofyou I agree, it NEEDS RANDOM MUSIC :open_mouth: