Drawing dashed lines

Hi all,
Below you can find my take on a function for drawing dashed lines. Now I’m just curious if any of you perhaps know a shorter, more efficient way of doing this?

function draw()
    background(255)
    xx,yy = CurrentTouch.x,CurrentTouch.y+50
    
    noStroke()
    fill(255, 0, 0, 255)
    ellipse(xx, yy, 20)
    
    stroke(0)
    strokeWidth(4)
    lineCapMode(SQUARE)
    dashed(0,0,xx,yy, 20)
end

function dashed(x1, y1, x2, y2, seg)
    local length = math.sqrt((x2-x1)^2 + (y2-y1)^2)
    -- n, integer number of segements in length
    local n = math.floor(length/seg)
    local angle = math.atan((y2-y1)/(x2-x1))
    -- dx, dy, leftover length shorter than segmentsize seg
    local dx = (length-n*seg)*math.cos(angle)
    local dy = (length-n*seg)*math.sin(angle)
    
    local u1,v1,u2,v2
    if length-n*seg>=seg/2 then
        -- Draw extra segment if leftover is long enough
        m = 1
    else
        -- Else, draw a shorter ending segment
        m = 0
        u1 = x1 + (x2-x1-dx)
        v1 = y1 + (y2-y1-dy)
        u2,v2 = x2,y2
        line(u1,v1,u2,v2)
    end
    
    -- Draw all the line segments
    for i = 0,n-1+m do
        u1 = x1 + i/n*(x2-x1-dx)
        v1 = y1 + i/n*(y2-y1-dy)
        u2 = x1 + (i+.5)/n*(x2-x1-dx)
        v2 = y1 + (i+.5)/n*(y2-y1-dy)
        line(u1,v1,u2,v2)
    end
end

If the lines have to all be the same size, I suggest a texture with a tile map on a mesh. Otherwise make a variable number called n and check if n is a multiple of 2 by doing

if (n%2) == 0 then
 Draw
end

This will draw a line every other time creating a dashed line, sizes may vary a lot.

Not sure about the speed difference, but I made a much smaller function with the same result using some built-in functions to calculate things in less lines.

function setup()
    FPS = 0
    parameter.watch("FPS")
    parameter.boolean("Use New Dashed Function", false)
    parameter.integer("Spacing", 10, 30, 20)
end

function draw()
    FPS = FPS * 0.9 + 0.1 / DeltaTime
    
    background(255)
    xx,yy = CurrentTouch.x,CurrentTouch.y+50

    noStroke()
    fill(255, 0, 0, 255)
    ellipse(xx, yy, 20)

    stroke(0)
    strokeWidth(4)
    lineCapMode(SQUARE)
    if Use_New_Dashed_Function then
        dashed(0,0,xx,yy, Spacing)
    else
        oldDashed(0,0,xx,yy, Spacing)
    end
end

function oldDashed(x1, y1, x2, y2, seg)
    local length = math.sqrt((x2-x1)^2 + (y2-y1)^2)
    -- n, integer number of segements in length
    local n = math.floor(length/seg)
    local angle = math.atan((y2-y1)/(x2-x1))
    -- dx, dy, leftover length shorter than segmentsize seg
    local dx = (length-n*seg)*math.cos(angle)
    local dy = (length-n*seg)*math.sin(angle)

    local u1,v1,u2,v2
    if length-n*seg>=seg/2 then
        -- Draw extra segment if leftover is long enough
        m = 1
    else
        -- Else, draw a shorter ending segment
        m = 0
        u1 = x1 + (x2-x1-dx)
        v1 = y1 + (y2-y1-dy)
        u2,v2 = x2,y2
        line(u1,v1,u2,v2)
    end

    -- Draw all the line segments
    for i = 0,n-1+m do
        u1 = x1 + i/n*(x2-x1-dx)
        v1 = y1 + i/n*(y2-y1-dy)
        u2 = x1 + (i+.5)/n*(x2-x1-dx)
        v2 = y1 + (i+.5)/n*(y2-y1-dy)
        line(u1,v1,u2,v2)
    end
end

function dashed(x1, y1, x2, y2, spacing)
    local length = vec2(x1, y1):dist(vec2(x2, y2)) -- Length of dashed line
    local dir = vec2(x2 - x1, y2 - y1):normalize() -- UV direction pf dashed line
    for i = 0, length, spacing do  -- Iterate through the spacing
        if i >= length - spacing / 2 then -- Checks if the dash is at the end
            line(x1 + dir.x * i, y1 + dir.y * i, x2, y2) -- Trims dash to fit length smoothly
        else -- Dash is not at the end
            line(x1 + dir.x * i, y1 + dir.y * i, x1 + dir.x * i + dir.x * (spacing / 2), y1 + dir.y * i + dir.y * (spacing / 2)) -- Draw the dash as normal
        end
    end
end

@Kjell Modulo is your friend here. Figure out how long a dash and a break are and modulo by that number. Then draw or not draw according to where in the pattern you are. E.g. if you want a 10-point dash and 5-point space, then do (pseudocode)

if N%15 < 10 then draw end

You can use a shader, leave it as an exercise to fix colors and width :slight_smile:

function dashed(x1,y1,x2,y2,seg)
    local m = mesh()
    m.shader = shader("Basic:Blend Images")
    m.shader.fragmentProgram = [[
        varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
        uniform highp float seg;
         
        void main() {
            highp float d = 1.0/(seg-.5);
            if(mod(vTexCoord.x,d) > (d*.5)) { discard; }
            gl_FragColor = vColor;
        }
    ]]
    m.shader.seg = seg
    local a, b = vec2(x1,y1),vec2(x2,y2)
    local v = b - a
    local p = a + v * .5
    m:addRect(p.x, p.y, v:len(), 2, -v:angleBetween(vec2(1,0)))
    m:draw()
end

Haha, it seems that my code doesn’t even work right when x2,y2 < x1,y1, so I’m gonna take a good look at all of your options.

Edit: I’m gonna go with @SkyTheCoder’s way here, I didn’t really think of just using the built-in vector functions. Looking back, it’s amazing how I managed to write so much code to accomplish this.

@tnlogy Your code seems to work fine to, but I’m not that familiar with shaders yet, so I rather not use them. But in the end, are meshes a faster way of drawing lines? Or is the line function of Codea already a mesh at heart?

Also, thank you all for your input!

Yes, meshes are faster according to earlier tests, I think line is implemented in a shader, but I guess it is slower since it handles many special cases such as cap mode? Also, if you want to draw a line for several frames, you can store the mesh value m in my example and only call m:draw() in the draw function. Or add thousands of line to id with addRect. You can return to the code if you run into performance issues.

A fragment shader as above is called for every pixel on the rectangle I’ve defined with a vTexCoord going from x = 0 to x=1. Probably better to define a complementary color than to use discard, since it is a bit slower than to draw the pixel.

@tnlogy oh! You can draw negative colors to the screen with shaders? What happens if the result is <0 or >255?

@Jmv38 - shaders automatically restrict the result to be between 0 and 1 (as a fraction of 255)

@ignatz cool!

@Jmv38 - yes and no. This restriction can lead to false colours.

eg if you get an answer of (510, 100, 100), then restricting the color gives you (255, 100, 100), which dramatically reduces just the red.

Instead, you could scale all the numbers down to get (255, 50, 50)

But I’m not sure there is any perfect way of handling boundary conditions.