Splinerider

@Saurabh - re your query on moving the screen, i think you just need to translate your position. The Lua jump demo project (the little girl jumping on clouds) does this, and moves the screen as the girl jumps, in the draw function.

I posted the code for the basics from above, its pretty easy to go on from there, I’m going to try to make it in to a game first before I give out my code.

Thanks @Ignatz!!

@Luatee - thanks for sharing the spline code, I was just wondering how best to do that kind of thing.

I think theres an alternative way of making the curve as there are a few ways of constructing bezier curves between points, this being my favorite, thanks!

@Luatee - this is really cool! Think you’ve got a really nice set of game mechanics there - cant wait to see the final game!

Incidentally, I’ve been playing around with splines a fair bit for a game im developing, and im half way through writing a pretty extensive library of classes and functions to help set them up a bit more easily. Basically, it handles an unlimited number of control points, open/closed curves, position and orientation along path etc… Ill probably put the code up on here at somepoint - but an early test is here:

vimeo.com/67845937

@andymac3d Thanks! I wanted to put in an unlimited amount of points at the start but I spoke to my friend who I thought might know being a math wizz and all but he said that if I wanted to put more points in I would have to constantly update the equation I use with more points and that seemed like too much but I suppose it wouldn’t be too bad thinking about it now. You’re class looks really good, but what I’ve been dealing with is FPS drop from drawing too much, how do you deal with this or have you not come to it yet?

Hi @Luatee - I get around some of the performance issues by pre-computing as much as possible (via Tables) and only updating things when ‘something’ changes.

This is quite important when using the ‘Piece-wise’ method of joining together Beziers that i’ve used, as obviously there can be a fair few iterations which can degrade the FPS, although I’ve got some pretty good performance so far with a reasonably large number of points. I guess the worse case is if all your control points are moving constantly, then naturally you’d have to continually recompute.

Similarly, the number of parametric samples you use per span (ie. from 0 → 1) to draw a smooth curve segment is also important. I’m using 30 samples which gives a reasonable approximation - although I guess increasing this will drop the FPS accordingly.

For most uses, where a sprite needs to follow a simple ‘invisible’ static path then most of this is not an issue - as you can pre-compute most of this and not even draw the curve at all - its then basically a simple ‘lookup’ of where it is on the curve to return x,y coords which is pretty fast!

@Luatee and @andymac3d Interesting discussion. I’ve a fair bit of code scattered around relating to drawing curves that I’d be happy to share if you wanted (but I don’t want to tread on any toes …). In short:

  1. The Roller Coaster project included in Codea defines a parametrised track, but then it reparametrises it according to arc length to make it easier to make the motion more realistic.
  2. The Harmony drawing program draws its lines and curves using a mesh. It goes to some length to make the lines look nice and not just a sequence of disjointed lines. In particular, it uses a smoothing algorithm to add more points to the sequence (based on John Hobby’s algorithm) and it handles the line joins.
  3. I also have a bezier shader. This is a mesh that uses a vertex shader to warp it to a cubic bezier shape.

@Andrew_Stacey I didn’t actually think the roller coaster had anything like this in it, but I haven’t looked at many of the examples. I remember looking at harmony when I was making a drawing app and I could use a few things such as creating single images for each splines and drawing them, I can see this would improve the performance by 100 fold quite easily. To do this I would need to find the bounding box of a quadratic Bézier curve, in that case how do I do this?

Also about the shader you mention is this for any shape of cubic? Aswell as that what is the performance like for this compared to normal meshes?

Nice one @Andrew_Stacey - feel free to chip in! Would be nice to get some feedback as I progress with the Spline-Lib (having a Mathematician on-board is always a good thing! :slight_smile: .

p.s. I really liked the roller-coaster example, I’ll check again at how you reparameterised your curve as obviously using simple parametric values across multiple spans with different spacing can cause velocity problems (computing the arc-length is really the proper way to do this)

@Luatee I was about to say that the thing about the roller coaster is that the path isn’t drawn, but then I remembered that it is drawn: as the roller coaster track. One of the time-savers there is that the track is static so once it’s been computed and the mesh sent off to the GPU then there’s no further computations to be done. When drawn as slabs, the track has 3672 vertices and the stars have 9600. The frame rate is around 60fps. I have a variant where I replace the track by a tube with upwards of 72,000 vertices and still a frame rate of about 60fps.

The exact bounding box of a Bézier curve can be difficult to compute but it is always contained in the convex hull of the control points. So use those (add a little something for line width).

The shader is for a cubic Bézier in that that’s how it is specified. But any cubic segment can be parametrised as a cubic Bézier so that’s no hindrance. I actually use it in the Harmony app to do colour curves for images and there I convert between an ordinary cubic and a Bézier cubic.

The trick with Harmony was to use meshes while a line is being drawn, and then when the user stops drawing it then to render it to an image. Rendering as it is being drawn takes too long, but keeping it all in a single mesh proved complicated as well. By doing it when the user stops drawing then they don’t notice if the frame rate dips momentarily.

@andymac3d I decided to do the reparametrisation using a step-by-step approach. That is, I step along the curve at incredibly small intervals and test to see when I get more than the desired step along and mark that point. Solving the actual arc-length reparametrisation is only possible for “nice” curves and even then it can be a pain to compute so I figured this was a better way to be able to handle all curves.

What does your library do with curves? (As in, what functionality does it provide?)

@Luatee Incidentally, what would be a reasonable test for my cubic shader? I could throw in a number of shaders with varying parameters and see what frame rates I get. What would be a reasonable number of cubics to draw?

I just tried it with a single mesh (200 interpolation points, so mesh size of 1200 vertices) and redrawing it n number of times where I pass the control points to the GPU on each draw cycle (and I made them vary so that they had to be passed anew to the GPU). I had reasonable FPS for up to n = 150 (reasonable meaning above 50). By 200, the FPS had dropped to the mid 40s. Mind you, by 200 then the whole system is pretty much filling the screen!

@Andrew_Stacey I’m not really in to 3d at the moment, so I haven’t looked at the roller coaster much. Although I did do something similar using a lua coding environment in 3d before where I created the track infront of the train using keys and rotating the direction and such but I have tested the meshes a lot and I found that its more dynamic the mesh then the slower it seems to run but if its just sat there static then it doesn’t take up much memory at all.

Finding the Bézier bounding box shouldn’t be hard if I give it the four points, I think what makes it hard is crossing over the points so they aren’t in order (from the algorithm I used anyway) so any help off you for that would be great.

I’m quite interested in the shader you’ve got for that but it wouldn’t be applicable as I’m using a quadratic curve unless its easy to change. For me a reasonable test would be about 100 curves, as I get just over 40fps with about 50 curves on the screen but then when the simulation is run it always runs at 60fps, despite the amount of curves (this will probably / most definitely change with higher numbers, ~100)

I saw this and I continued to integrate that sort of system of drawing in to my old drawing app which worked out well, the reason why I need to get the bounding box of the spline is to create an image of that size to store it in unless there is a better way for me to do this.

You’re test seems to have better outcomes than mine, but the issue I face is which a frame rate drop when the draw function is running whilst not in simulation. If you have a way of me integrating that it would be useful, although I wouldn’t use it in my actual game as I’d like to do the creating of it myself.

Here’s my shader with two auxiliary drawing functions for quadratic and cubic beziers. 200 step points (nstep in the code) seems plenty, at 100 then with a full-sized curve you can tell it is segmented, 150 looks fine.

function setup()
    --displayMode(FULLSCREEN)
    local width = 10

    pts = {vec2(width/2,0),
        vec2(width/2,3*HEIGHT-width/2),
        vec2(WIDTH - width/2,-2*HEIGHT+width/2),
        vec2(WIDTH - width/2,HEIGHT)}

    --[[
    curves = {}
    local n = 200
    for k=1,n do
        table.insert(curves,{
            function(t) return vec2(0,HEIGHT*(1+math.sin(t + 2*math.pi*k/n))/2) end,
            function(t) return vec2(WIDTH/3,HEIGHT*(1+math.sin(t + 2*math.pi*(k+1)/n))/2) end,
            function(t) return vec2(2*WIDTH/3,HEIGHT*(1+math.sin(t + 2*math.pi*(k+2)/n))/2) end,
            function(t) return vec2(WIDTH,HEIGHT*(1+math.sin(t + 2*math.pi*(k+3)/n))/2) end
                })
    end
    --]]
    fps = {}
    for k=1,20 do
        table.insert(fps,1/60)
    end
    afps = 60
    parameter.watch("math.floor(20/afps)")
end

function draw()
    table.remove(fps,1)
    table.insert(fps,DeltaTime)
    afps = 0
    for k,v in ipairs(fps) do
        afps = afps + v
    end
    background(75, 104, 90, 255)
    strokeWidth(5)

    stroke(151, 115, 115, 255)
    for i=1,3 do
        line(pts[i].x,pts[i].y,pts[i+1].x,pts[i+1].y)
    end
    fill(160, 172, 22, 255)
    noStroke()
    for i = 1,4 do
        ellipse(pts[i].x,pts[i].y,20)
    end
    strokeWidth(5)
    stroke(255, 255, 255, 255)
    cbezier(pts)
    stroke(81, 255, 0, 255)
    qbezier(vec2(10,10),vec2(WIDTH/2,2*HEIGHT-20),vec2(WIDTH-10,10))
    --[[
    for _,v in ipairs(curves) do
        cbezier({v[1](ElapsedTime),v[2](ElapsedTime),v[3](ElapsedTime),v[4](ElapsedTime)})
    end
    --]]
end

function touched(touch)
    if touch.state == BEGAN then
    for k,v in ipairs(pts) do
        if v:distSqr(vec2(touch.x,touch.y)) < 900 then
            pt = k
        end
    end
    elseif pt then
        pts[pt].x = touch.x
        pts[pt].y = touch.y
    end
    if touch.state == ENDED then
        pt = nil
    end
end

local m = mesh()
m.shader = shader([[
//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

uniform float len;
uniform float width;
uniform float blur;
float twidth = width+blur;
uniform vec2 pts[4];

void main()
{
    highp float t = position.y/len;
    highp float tt = 1.0 - t;
    highp vec2 bpos = tt*tt*tt*pts[0] + 3.0*tt*tt*t*pts[1] 
    + 3.0*tt*t*t*pts[2] + t*t*t*pts[3];
    highp vec2 bdir = tt*tt*(pts[1]-pts[0])
         + 2.0*tt*t*(pts[2]-pts[1]) + t*t*(pts[3]-pts[2]);
    bdir = vec2(bdir.y,-bdir.x);
    bdir = twidth*position.x*normalize(bdir);
    bpos = bpos + bdir;
    highp vec4 bzpos = vec4(bpos.x,bpos.y,0,1);
    //Pass the mesh color to the fragment shader
    vTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * bzpos;
}
]],[[
//
// A basic fragment shader
//

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform highp float width;
uniform highp float blur;
highp float edge = blur/(width+blur);
uniform lowp vec4 colour;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = colour;
    //if (vTexCoord.x < edge)
    //    col.a = col.a*vTexCoord.x/edge;
    //if (vTexCoord.x > 1. - edge)
    //    col.a = col.a*(1.-vTexCoord.x)/edge;
    //Set the output color to the texture color

    col.a = mix( 0., col.a,
    smoothstep( 0., edge, min(vTexCoord.x,1. - vTexCoord.x) ) );
    gl_FragColor = col;
}
]])

local nsteps = 200
for n=1,nsteps do
    m:addRect(0,(n-.5),1,1)
end
m.shader.len = nsteps
m.shader.blur = 2

function cbezier(a,b,c,d)
    if type(a) ~= "table" then
        a = {a,b,c,d}
    end
    m.shader.width = strokeWidth()
    m.shader.colour = color(stroke())
    m.shader.pts = a
    m:draw()
end

function qbezier(a,b,c)
    if type(a) == "table" then
        a,b,c = unpack(a)
    end
    b = 2*b/3
    m.shader.width = strokeWidth()
    m.shader.colour = color(stroke())
    m.shader.pts = {a,a/3 + b,b + c/3,c}
    m:draw()
end

Thanks @Andrew_Stacey, can see myself playing about with that for a bit! Once ive put it in ill post the results and run some tests on speed and others, might help if I made quadratic splines aswell as cubic, I didnt really think I needed this though as I get a nice quadratic looking curve when I put the points in the middle together, but this sort of curve has a gradient change thats too steep, causing the bike to slow down

@Luatee The qbezier function in my code is a quadratic bezier, and it’s the right conversion from a quadratic to a cubic so you’ll get exactly the same curve as you would with the quadratic bezier.

I just tried the code @Andrew_Stacey and it works like a f1 car compared to the drawing I had going on before, thanks!

@Luatee Great!

I’m working on making it into a library of bézier-related commands. I think that there’s a slight speed saving by creating a new mesh for a new bézier. That is, if you have a dozen curves and they don’t change their parameters then it’s quicker to create a dozen separate meshes and keep drawing them than to have one mesh and change its parameters each time.