Wrapping a texture onto a curve

Hey guys!

This project takes an image or texture and wraps it onto a curve. I used the function for a sine wave to draw the curve, and then used the derivative to find the slope and get the normals for the curve for more accurate mapping. I thought this might be useful if I ever decided to create a snowboarding game or something, like Alto’s Adventure.

If you want to make a new curve, you’ll have use some calculus wizardry and math out the derivative as well.

There is a way to find the length of the curve, using more calculus and integration. As far as I know, though, Codea doesn’t have an integration function, so I haven’t been able to figure it out yet. If anyone could help me figure it out, I would really appreciate it! Right now my code kind of stretches and squishes the texture because I’m only using the x-value to find the texture coordinates. Using the curve length instead would hopefully keep the texture more consistent.

Hope you enjoy! Try playing around with the texture a bit! And thanks for any help you guys can offer!


--# Main
-- Curve Texture

displayMode(FULLSCREEN)
function setup()
    f = function(x)
        x = x + 0.000001
        local y = math.sin(x/50)*50-x+700 -- Base function
        local s = math.cos(x/50)-1 -- Derivative
        -- local d = HOW DO YOU DO INTEGRALS???
        s = -1/s
        s = math.atan(s)
        return y,s
    end
    
    parameter.watch("1/DeltaTime")
    img = readImage("Cargo Bot:Crate Blue 1")
    tex = image(100,50)
    setContext(tex)
    sprite(img,25,25,50)
    sprite(img,75,25,50)
    
    YouWantStripes = false -- CHANGE THIS TO TRUE/FALSE
    
    if YouWantStripes then
        noSmooth()
        fill(255)
        rect(0,0,100,50)
        fill(200)
        rect(0,10,100,50)
        fill(150)
        rect(0,20,100,50)
        fill(100)
        rect(0,30,100,50)
        fill(50)
        rect(0,40,100,50)
        smooth()
    end
    setContext()
    
    m = mesh()
    m.texture = tex
end

function draw()
    background(40,40,50)
    
    res = 10
    
    stroke(255)
    strokeWidth(5)
    
    local verts = {}
    local texCoords = {}
    local cols = {}
    for x = 0,ElapsedTime*200,res do
        local x1 = x
        local y1,s1 = f(x1)
        local x2 = x + res
        local y2,s2 = f(x2)
        local v1,v2 = vec2(x1,y1),vec2(x2,y2)
        local v3 = vec2(x2,y2)-vec2(tex.height):rotate(s2)
        local v4 = vec2(x1,y1)-vec2(tex.height):rotate(s1)
        table.insert(verts,v1)
        table.insert(verts,v2)
        table.insert(verts,v3)
        table.insert(verts,v3)
        table.insert(verts,v4)
        table.insert(verts,v1)
        local c1 = x1%(tex.width/2)/tex.width
        local c2 = c1 + (x2-x1)/tex.width
        table.insert(texCoords,vec2(c1,1))
        table.insert(texCoords,vec2(c2,1))
        table.insert(texCoords,vec2(c2,0))
        table.insert(texCoords,vec2(c2,0))
        table.insert(texCoords,vec2(c1,0))
        table.insert(texCoords,vec2(c1,1))
        for i = 1,6 do table.insert(cols,color(255)) end
    end
    m.vertices = verts
    m.texCoords = texCoords
    m.colors = cols
    
    m:draw()
end


@Dwins Very interesting. I’m sure that will have a lot of uses.

@Dwins Here’s an example that calculates green parallel points on both sides of a red point trail. You don’t need calculus. Two pairs of green points could be used to create a rectangle that can be used for the mesh image. I didn’t bother to code the mesh portion. Drag your finger to draw a red trail. Lift your finger to display the green parallel points. Drag your finger again to draw another red trail.

displayMode(FULLSCREEN)

function setup()
    tab={}
end

function calc(x1,y1,x2,y2)
    mx=(x1+x2)/2
    my=(y1+y2)/2
    v=vec2(x2-x1,y2-y1)
    v1=v:rotate(math.rad(90))
    v1=v1:normalize()*20
    v2=v:rotate(math.rad(270))
    v2=v2:normalize()*20
end

function draw()
    background(40, 40, 50)
    fill(255)
    text("Drag your finger to create a red trail",WIDTH/2,HEIGHT-50)
    text("Lift your finger to see a green outline",WIDTH/2,HEIGHT-100)
    for z=1,#tab do
        fill(255,0,0)
        ellipse(tab[z].x,tab[z].y,4)
        if done then
            if z>1 then
                calc(tab[z-1].x,tab[z-1].y,tab[z].x,tab[z].y)  
                fill(0,255,0) 
                ellipse(v1.x+mx,v1.y+my,3)
                ellipse(v2.x+mx,v2.y+my,3)    
            end
        end
    end
end

function touched(t)
    if t.state==BEGAN then
        tab={}
        done=false
    end
    if t.state==MOVING then
        table.insert(tab,vec2(t.x,t.y))
    end
    if t.state==ENDED then
        done=true
    end    
end

@dave1707 Oh, that is a bit easier than using calculus…although I think calculus is a bit more precise because it takes the slope at the point instead of the slope between two points. Thanks though, that’s a really cool example!

Any ideas on how to keep the texture from stretching?

I’m just starting my Senior year in High School, so I think my brain is still in use-calculus-for-everything mode

@Dwins I don’t think it matters that much which point is used. It don’t think it needs to be that accurate. I don’t think you can keep an image from stretching or squeezing if you take a rectangular image and put it on a curved surface. Anyways, I like the stretching and squeezing.

Got it.


--# Main
-- Curve Texture

displayMode(FULLSCREEN)
function setup()
    f = function(x)
        x = x + 0.000001
        local y = math.sin(x/50)*50-x+700 -- Base function
        local s = math.cos(x/50)-1 -- Derivative
        s = -1/s
        s = math.atan(s)
        return y,s
    end
    
    parameter.watch("1/DeltaTime")
    img = readImage("Cargo Bot:Crate Blue 1")
    tex = image(100,50)
    setContext(tex)
    sprite(img,25,25,50)
    sprite(img,75,25,50)
    
    YouWantStripes = false -- CHANGE THIS TO TRUE/FALSE
    
    if YouWantStripes then
        noSmooth()
        fill(255)
        rect(0,0,100,50)
        fill(200)
        rect(0,10,100,50)
        fill(150)
        rect(0,20,100,50)
        fill(100)
        rect(0,30,100,50)
        fill(50)
        rect(0,40,100,50)
        smooth()
    end
    setContext()
    
    m = mesh()
    m.texture = tex
    
end

function draw()
    background(40,40,50)
    
    res = 16
    
    stroke(255)
    strokeWidth(5)
    
    local verts = {}
    local texCoords = {}
    local cols = {}
    d = 0
    for x = 0,ElapsedTime*200,res do
        local x1 = x
        local y1,s1 = f(x1)
        local x2 = x + res
        local y2,s2 = f(x2)
        local v1,v2 = vec2(x1,y1),vec2(x2,y2)
        local v3 = vec2(x2,y2)-vec2(tex.height):rotate(s2)
        local v4 = vec2(x1,y1)-vec2(tex.height):rotate(s1)
        table.insert(verts,v1)
        table.insert(verts,v2)
        table.insert(verts,v3)
        table.insert(verts,v3)
        table.insert(verts,v4)
        table.insert(verts,v1)
        local d1 = d
        d = d + math.sqrt((x2-x1)^2+(y2-y1)^2)
        local d2 = d
        local c1 = d1%(tex.width/2)/tex.width
        local c2 = c1 + (d2-d1)/tex.width
        table.insert(texCoords,vec2(c1,1))
        table.insert(texCoords,vec2(c2,1))
        table.insert(texCoords,vec2(c2,0))
        table.insert(texCoords,vec2(c2,0))
        table.insert(texCoords,vec2(c1,0))
        table.insert(texCoords,vec2(c1,1))
        for i = 1,6 do table.insert(cols,color(255)) end
    end
    m.vertices = verts
    m.texCoords = texCoords
    m.colors = cols
    
    m:draw()
    
end