Cycling a line

Does anyone have a routine for cycling a dotted line’s dot offset? I was thinking something like marching ants but as I want to be able to control the direction of motion, and the ants only goes one way, I need something a little custom.

Something like the classic loading bar animation, but just thin lines.

Currently, I’ve got this bit of fun:

local function animateDots( parent, x, y, len ) local group = display.newGroup() parent:insert( group ) group.x, group.y = x, y for i=1, len, 18 do local \_x = i local dot = display.newCircle( group, i, 0, 4 ) dot.fill = {.8,.8,.8} transition.to( dot, { time=1000, x=dot.x+18, iterations=0, tag="animateDots", onRepeat=function() dot.x = \_x end } ) end end local function testAnimatedLine() local group = display.newGroup() group.x, group.y = 300, 300 local rect = display.newRoundedRect( group, 0, 0, 300, 50, 25 ) animateDots( group, -125, 0, 250 ) end testAnimatedLine()

I have to admit I don’t quite understand what you want to achieve. :unsure:  What do you mean by cycling in this case? Maybe you could post a simple drawing that explains what you’re looking for?

Something like this:

  stroke-dashed-outline.gif

I was thinking that the dashes would travel around the shape and, for my case, I was hoping the shapes would be any stroke path. The marching ants generator is different, in that it shifts it’s pattern up and left, as opposed to following around the direction of the path itself.

I played with lines and strokes, but those don’t seem to keep around anything like texture information that would allow for a general solution.

A go at rects and circles:

local CX, CY = display.contentCenterX, display.contentCenterY do -- Rect effect local kernel = { category = "generator", name = "dashes" } kernel.vertexData = { { name = "number\_of\_spots", index = 0, default = 8 }, -- dashes + spaces between { name = "start\_on\_dash", index = 1, default = 1 } -- start on dash on this side? (set to 0 otherwise) } kernel.isTimeDependent = true kernel.fragment = [[#define DASH\_TIME .37 // How many seconds does it take to cover one dash's worth of distance? P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_UV float offset = CoronaTotalTime / DASH\_TIME; // How many dashes have we covered? offset += CoronaVertexUserData.y; // Are we starting in one or not? offset += uv.x \* CoronaVertexUserData.x; // Which bin are we in? P\_UV float dash = step(mod(offset, 2.), 1.); // Choose white or black return CoronaColorScale(vec4(dash)); }]] graphics.defineEffect(kernel) end local r = display.newRect(CX, CY, 150, 150) local bottom = display.newRect(r.x, r.y + r.height / 2, r.width + 5, 5) local right = display.newRect(r.x + r.width / 2, r.y, r.height, 5) local top = display.newRect(r.x, r.y - r.height / 2, r.width + 5, 5) local left = display.newRect(r.x - r.width / 2, r.y, r.height, 5) right.rotation = 90 bottom.rotation = 180 left.rotation = 270 for \_, rect in ipairs{ bottom, right, top, left } do rect.fill.effect = "generator.custom.dashes" end do -- Circle effect local kernel = { category = "generator", name = "circle\_dashes" } kernel.vertexData = { { name = "cx", index = 0 }, { name = "cy", index = 1 }, { name = "number\_of\_spots", index = 2, default = 30 }, -- dashes + spaces between (should be even) } kernel.isTimeDependent = true kernel.vertex = [[varying P\_POSITION vec2 v\_Diff; P\_POSITION vec2 VertexKernel (P\_POSITION vec2 pos) { v\_Diff = pos - CoronaVertexUserData.xy; return pos; }]] kernel.fragment = [[varying P\_POSITION vec2 v\_Diff; #define DASH\_TIME .37 // How many seconds does it take to cover one dash's worth of distance? P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_UV float offset = CoronaTotalTime / DASH\_TIME; // How many dashes have we covered? P\_UV float pc = atan(v\_Diff.y, v\_Diff.x); // partial circumference / arc length, in [-pi, +pi] offset += CoronaVertexUserData.z \* ((pc / 3.14159 + 1.) / 2.); // remapped to [0, 2], then use to find bin P\_UV float dash = step(mod(offset, 2.), 1.); // Choose white or black return CoronaColorScale(vec4(dash)); } ]] graphics.defineEffect(kernel) end local c = display.newCircle(CX, CY + 250, 90) c.strokeWidth = 5 c.stroke.effect = "generator.custom.circle\_dashes" c.stroke.effect.cx = c.x c.stroke.effect.cy = c.y

While not terribly convenient, it might get you somewhere. The rect technique could be used on any polygonal object too.

Dude you are mr shader!  Now if you could only do tiled water…

Wow! That’s amazing and exactly what I was looking for.

I’m going to have to spend some serious time learning from your snippet there. A really good tutorial on writing shaders is what I need.

Thank you StarCrunch!

Great, glad to hear it.

Some miscellaneous commentary:

  • The rects version, where I’m using additional rects for the borders, does have an underlying texture (basically just a white pixel, by default), and the fill policy honors that: so as you go from left to right (pre-rotation), uv.x goes from 0 to 1.
  • The “start_on_dash” property would let you use an odd number of slots on a given side. Otherwise you’d get an extra-long dash where one side ends and the next begins.
  • Similarly, this is what the “should be even” comment is about for circles. Since it’s all one object, odd counts make it look rather weird at the 0-360 degree fringe.
  • Strokes and lines, as I said, seem to lock all texture coordinates to (0, 0). (You can see this by emitting them as the shader’s output, a fairly typical debugging procedure.) Thus calculating the angle from the vertex positions. Most fragment shaders don’t need to know about their current position, so it isn’t provided by default; this is what the vertex kernel and varying are about. Of course the angle is actually based on where these positions are from the circle’s center, so we need to provide its current value as well.
  • The mod(x, 2) calls snap us down to a range that can fit one dash and one space. step(edge, x) will be 0 if x is less than edge , 1 otherwise (ugh, I always forget which arguments mean what! :) ), so you can choose which of the two you landed on. The vec4() will then spread that value to all four components: all zeroes for black, all ones for white.
  • CoronaColorScale() will then mix in the fill or stroke color with its input.

I hope some of that’s clear.  :stuck_out_tongue: Anyhow, just ask if you have more questions.

Currently, I’ve got this bit of fun:

local function animateDots( parent, x, y, len ) local group = display.newGroup() parent:insert( group ) group.x, group.y = x, y for i=1, len, 18 do local \_x = i local dot = display.newCircle( group, i, 0, 4 ) dot.fill = {.8,.8,.8} transition.to( dot, { time=1000, x=dot.x+18, iterations=0, tag="animateDots", onRepeat=function() dot.x = \_x end } ) end end local function testAnimatedLine() local group = display.newGroup() group.x, group.y = 300, 300 local rect = display.newRoundedRect( group, 0, 0, 300, 50, 25 ) animateDots( group, -125, 0, 250 ) end testAnimatedLine()

I have to admit I don’t quite understand what you want to achieve. :unsure:  What do you mean by cycling in this case? Maybe you could post a simple drawing that explains what you’re looking for?

Something like this:

  stroke-dashed-outline.gif

I was thinking that the dashes would travel around the shape and, for my case, I was hoping the shapes would be any stroke path. The marching ants generator is different, in that it shifts it’s pattern up and left, as opposed to following around the direction of the path itself.

I played with lines and strokes, but those don’t seem to keep around anything like texture information that would allow for a general solution.

A go at rects and circles:

local CX, CY = display.contentCenterX, display.contentCenterY do -- Rect effect local kernel = { category = "generator", name = "dashes" } kernel.vertexData = { { name = "number\_of\_spots", index = 0, default = 8 }, -- dashes + spaces between { name = "start\_on\_dash", index = 1, default = 1 } -- start on dash on this side? (set to 0 otherwise) } kernel.isTimeDependent = true kernel.fragment = [[#define DASH\_TIME .37 // How many seconds does it take to cover one dash's worth of distance? P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_UV float offset = CoronaTotalTime / DASH\_TIME; // How many dashes have we covered? offset += CoronaVertexUserData.y; // Are we starting in one or not? offset += uv.x \* CoronaVertexUserData.x; // Which bin are we in? P\_UV float dash = step(mod(offset, 2.), 1.); // Choose white or black return CoronaColorScale(vec4(dash)); }]] graphics.defineEffect(kernel) end local r = display.newRect(CX, CY, 150, 150) local bottom = display.newRect(r.x, r.y + r.height / 2, r.width + 5, 5) local right = display.newRect(r.x + r.width / 2, r.y, r.height, 5) local top = display.newRect(r.x, r.y - r.height / 2, r.width + 5, 5) local left = display.newRect(r.x - r.width / 2, r.y, r.height, 5) right.rotation = 90 bottom.rotation = 180 left.rotation = 270 for \_, rect in ipairs{ bottom, right, top, left } do rect.fill.effect = "generator.custom.dashes" end do -- Circle effect local kernel = { category = "generator", name = "circle\_dashes" } kernel.vertexData = { { name = "cx", index = 0 }, { name = "cy", index = 1 }, { name = "number\_of\_spots", index = 2, default = 30 }, -- dashes + spaces between (should be even) } kernel.isTimeDependent = true kernel.vertex = [[varying P\_POSITION vec2 v\_Diff; P\_POSITION vec2 VertexKernel (P\_POSITION vec2 pos) { v\_Diff = pos - CoronaVertexUserData.xy; return pos; }]] kernel.fragment = [[varying P\_POSITION vec2 v\_Diff; #define DASH\_TIME .37 // How many seconds does it take to cover one dash's worth of distance? P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_UV float offset = CoronaTotalTime / DASH\_TIME; // How many dashes have we covered? P\_UV float pc = atan(v\_Diff.y, v\_Diff.x); // partial circumference / arc length, in [-pi, +pi] offset += CoronaVertexUserData.z \* ((pc / 3.14159 + 1.) / 2.); // remapped to [0, 2], then use to find bin P\_UV float dash = step(mod(offset, 2.), 1.); // Choose white or black return CoronaColorScale(vec4(dash)); } ]] graphics.defineEffect(kernel) end local c = display.newCircle(CX, CY + 250, 90) c.strokeWidth = 5 c.stroke.effect = "generator.custom.circle\_dashes" c.stroke.effect.cx = c.x c.stroke.effect.cy = c.y

While not terribly convenient, it might get you somewhere. The rect technique could be used on any polygonal object too.

Dude you are mr shader!  Now if you could only do tiled water…

Wow! That’s amazing and exactly what I was looking for.

I’m going to have to spend some serious time learning from your snippet there. A really good tutorial on writing shaders is what I need.

Thank you StarCrunch!

Great, glad to hear it.

Some miscellaneous commentary:

  • The rects version, where I’m using additional rects for the borders, does have an underlying texture (basically just a white pixel, by default), and the fill policy honors that: so as you go from left to right (pre-rotation), uv.x goes from 0 to 1.
  • The “start_on_dash” property would let you use an odd number of slots on a given side. Otherwise you’d get an extra-long dash where one side ends and the next begins.
  • Similarly, this is what the “should be even” comment is about for circles. Since it’s all one object, odd counts make it look rather weird at the 0-360 degree fringe.
  • Strokes and lines, as I said, seem to lock all texture coordinates to (0, 0). (You can see this by emitting them as the shader’s output, a fairly typical debugging procedure.) Thus calculating the angle from the vertex positions. Most fragment shaders don’t need to know about their current position, so it isn’t provided by default; this is what the vertex kernel and varying are about. Of course the angle is actually based on where these positions are from the circle’s center, so we need to provide its current value as well.
  • The mod(x, 2) calls snap us down to a range that can fit one dash and one space. step(edge, x) will be 0 if x is less than edge , 1 otherwise (ugh, I always forget which arguments mean what! :) ), so you can choose which of the two you landed on. The vec4() will then spread that value to all four components: all zeroes for black, all ones for white.
  • CoronaColorScale() will then mix in the fill or stroke color with its input.

I hope some of that’s clear.  :stuck_out_tongue: Anyhow, just ask if you have more questions.