Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Drawing dashed lines

edited January 2014 in Questions Posts: 82

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
Tagged:

Comments

  • Posts: 1,595

    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.

  • Posts: 1,976

    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
    
  • edited January 2014 Posts: 140

    @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
    
  • edited January 2014 Posts: 521

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

    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
    
  • edited January 2014 Posts: 82

    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!

  • Posts: 521

    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.

  • Jmv38Jmv38 Mod
    Posts: 3,297

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

  • IgnatzIgnatz Mod
    Posts: 5,396

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

  • Jmv38Jmv38 Mod
    Posts: 3,297

    @ignatz cool!

  • IgnatzIgnatz Mod
    edited January 2014 Posts: 5,396

    @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.

Sign In or Register to comment.