local empty = {}
local vBufferSize = 1000000
local vBufferN = 0
local vBuffer
local vData 
local vPointer
local slice = {}

if useData then 
  ffi.cdef ([[
    typedef struct { 
      float x,  y,  z, w;
      float r,  g,  b, a;
    } strkvec;
  ]])

  if LOVE then 
    vData       = love.data.newByteData(ffi.sizeof("strkvec")*vBufferSize)
    vPointer    = vData:getPointer()
    vBuffer     = ffi.cast("strkvec*",vPointer)
  else
    vBuffer = ffi.new("strkvec[?]",vBufferSize)
    vData = vBuffer
  end
else
  vBuffer =  {} for i=1,vBufferSize,1 do vBuffer[i] = { 0,0,0,0, 0,0,0,0 } end
end

local fillBase = ffi.new(VECTOR_TYPE.."[128000][7]")
local fillEraser  = ffi.new(VECTOR_TYPE.."[128000][7]")
local fillTris = ffi.new(VECTOR_TYPE.."[256000]")

local vBufInsert = useData and 
function(v0,v1,v2,v3,v4,v5,v6,v7)
  vBufferN=vBufferN+1
  if vBufferN>vBufferSize-2 then print("SEGFAULT",vBufferN,vBufferSize) return  end
  vBuffer[vBufferN].x  = v0  
  vBuffer[vBufferN].y  = v1  
  vBuffer[vBufferN].z  = v2+vBufferN /1000000
  vBuffer[vBufferN].w  = v3
  vBuffer[vBufferN].r  = v4  
  vBuffer[vBufferN].g  = v5  
  vBuffer[vBufferN].b  = v6  
  vBuffer[vBufferN].a  = v7  
end
or
function(v1,v2,v3,v4,v5,v6,v7,v8)
  vBufferN=vBufferN+1
  vBuffer[vBufferN][1 ]=v1  
  vBuffer[vBufferN][2 ]=v2  
  vBuffer[vBufferN][3 ]=v3  
  vBuffer[vBufferN][4 ]=v4  
  vBuffer[vBufferN][5 ]=v5  
  vBuffer[vBufferN][6 ]=v6  
  vBuffer[vBufferN][7 ]=v7  
  vBuffer[vBufferN][8 ]=v8  
end

local function startVerts() 
  vBufferN = useData and -1 or 0
end

local function groupSort(a,b)
  return a.group<b.group
end
local function segSort(a,b)
  return a[3] < b[3] 
end

function windingSide(ax,ay, bx,by, x,y)
  return (bx - ax) * (y - ay) - (x - ax) * (y - ay)
end

local dist = math.dist
local sin = math.sin
local cos = math.cos
local tan = math.tan
local atan2 = math.atan2
local sqrt = math.sqrt
local buffer = {}
local buffer2 = {}


function splitPolyLine(line,retry)
  local splits = {{line[1],line[2]}}
  local current = 1
  local splitsFinished = {}
  local splitStart = 3
  local px,py
  if retry then 
  --   table.insert(line,lerp(line[#line-1],line[1],0.99))
  --   table.insert(line,lerp(line[#line],  line[2],0.99))
  end
  local tests = 0
  for l1 = 1,#line-2,2 do
    local inters = {}
    for l2 = 1,#line-2,2 do
      if l2<l1-1 or l2>l1+1 then 
        local x,y 
        if line[l1] == line[l2] and line[l1+1]==line[l2+1] then
          x,y = line[l1],line[l1+1]
        else 
          x,y= intersect(line[l1],line[l1+1],line[l1+2],line[l1+3], line[l2],line[l2+1],line[l2+2],line[l2+3])
        end
        if x then 
          tests = tests +1
          if retry then 
            print("CROSSSSSSSSSSSSSSSSSS")
          end
          table.insert(inters,{x,y,dist(line[l1],line[l1+1],x,y)})
        end
      end
    end

    if #inters>0 then 
      table.sort(inters,segSort)

      for i=1,#inters,1 do 
        local x,y = inters[i][1],inters[i][2]
        local continue
        local finish

        for s=1,#splits,1 do 
          if not splitsFinished[s] then 
            local split = splits[s]
            if split[1] == x and split[2] == y then --end split
              splitsFinished[s]=true
              finish = true
            end
            if s~=current and split[#split-1] == x and split[#split] == y then -- continue
              continue = s
            end
          end
        end

        concat(splits[current],splice(line,splitStart,l1+1))
        if not finish then 
          table.insert(splits[current],x)
          table.insert(splits[current],y)
        end

        if continue  then 
          current=continue
        else
          table.insert(splits,{x,y})
          current = #splits
        end
        px,py = x,y
      end
      splitStart = l1+2 
    end
  end
  -- table.remove(line,#line)
  -- table.remove(line,#line)
  concat(splits[current],splice(line,splitStart,#line))
  if retry and tests>0 then 
    print("intersections",tests)
  end
  return splits
end
function polyArea(poly)
  local N = #poly+1
  table.insert(poly,poly[1])
  table.insert(poly,poly[2])
  table.insert(poly,poly[3])
  table.insert(poly,poly[4])

  local area = 0
  
  for  i = 2,#poly-3, 2  do 
    area = area + poly[i]*( poly[i+3] - poly[i-1] )
  end
  area = area/2

  return area
end

local function splitSort(a,b)
  return #a>#b
end

local tmpBez = mth.newBezierCurve(0,0, 0,0, 0,0, 0,0)


function bezierLine(line,detail,fill)
  local dist = math.dist
  local b,b2,w = 0,0,1
  local x,y,sx,sy
  local widths
  local testTris = 0
  if line[1][7] then 
    widths=buffer2
  end

  if detail==0 then 
    for l = 1, #line,1 do
      buffer[b+1],buffer[b+2] = line[l][1],line[l][2]; b=b+2
      if widths then 
        widths[w]=line[l][7]; w=w+1
      end
      -- print(l,b)
    end
    if not fill then 
      return buffer,b,widths
    end
  else 
    local stop  = #line   
    for l = 2,stop,1 do 
      local ax,ay =  line[l-1][1],line[l-1][2]
      local curve  = line[l-1][5] and line[l-1][5]~=0 and line[l][3] and line[l][3]~=0
      buffer[b+1],buffer[b+2] = ax,ay; b=b+2

      if widths then 
        widths[w]=line[l-1][7]; w=w+1
      end


      if detail>0 and curve then 
        local bx,by,cx,cy,dx,dy =
            line[l-1][5], line[l-1][6],
            line[l][3],   line[l][4],
            line[l][1],   line[l][2]
        -- local len = dist(ax,ay,dx,dy)
        -- if    (distPointToLineSegment2(bx, by, ax, ay, dx, dy, true)<0.1 and (dist(bx,by,ax,ay)+dist(bx,by,dx,dy))>len*1.1)
        --    or (distPointToLineSegment2(cx, cy, ax, ay, dx, dy, true)<0.1 and (dist(cx,cy,ax,ay)+dist(cx,cy,dx,dy))>len*1.1)
        --     then 
        --   local mx,my = (ax+dx)/2,(ay+dy)/2
        --   bx,by = mx,my
        --   cx,cy = mx,my
        -- elseif    (distPointToLineSegment2(bx, by, ax, ay, dx, dy, true)<0.5 and (dist(bx,by,ax,ay)+dist(bx,by,dx,dy))>len*1.1)
        --    or (distPointToLineSegment2(cx, cy, ax, ay, dx, dy, true)<0.5 and (dist(cx,cy,ax,ay)+dist(cx,cy,dx,dy))>len*1.1)
        --     then 
        --   -- local mx,my = (ax+dx)/2,(ay+dy)/2
        --   bx,by = (ax+bx)/2,(ay+by)/2
        --   cx,cy = (cx+dx)/2,(cy+dy)/2
        -- end
        -- local vx,vy = (dx-ax),(dy-ay)
        tmpBez:setControlPoint(1,  ax,   ay)
        tmpBez:setControlPoint(2,  bx,   by)
        tmpBez:setControlPoint(3,  cx,   cy)
        tmpBez:setControlPoint(4,  dx,   dy)

        local steps = mu(detail*0.03*((10+dist(ax,ay,bx,by)+dist(bx,by,cx,cy)+dist(cx,cy,dx,dy)+dist(ax,ay,dx,dy))))

        for i = 0,steps,1 do 
          if i>0 and i<steps then 
            local t = i/steps
            local t2 = (i+1)/steps
            x,y = tmpBez:evaluate(t)

            buffer[b+1],buffer[b+2] = x,y
            b=b+2

            if widths then 
              widths[w]=lerp(line[l-1][7],line[l][7],t)
              w=w+1
            end
          end
        end
      end
    end
  end

  if fill then 
    local splits = splitPolyLine(splice(buffer,1,b))
    table.sort(splits,splitSort)
    -- if #splits>1 and polyArea(splits[2])>polyArea(splits[1])*0.5 then 
      -- editor.addTestLines(splits)
      -- joinSplits(split[1],split[2])
    -- end
    return {splits[1]}
  else
    buffer[b+1],buffer[b+2] = line[#line][1],line[#line][2]; b=b+2
    if widths then 
      widths[w]=line[#line][7]
    end
  end

  return buffer,b,widths
end

function fillOutline(lines,cat)
  local outlines = {}
  for l,line in ipairs(lines) do 
    line.bez = true
    if l>1 then 
      line[1][3],line[1][4],line[#line][3],line[#line][4]=0,0,0,0
    end
    editor.addTestLines({line})
    -- table.insert(animationPanel.f.strokes,{size=0.1,color={r=1,g=0,b=0,a=1}, debug=true, group="note", line=splice(line,1,math.max(1,#line-#line+2))})
    local marked = {}
    for i=1,#line,1 do 
      local p = line[i]
      local a,d = line[i==1 and #line or i-1], line[i==#line and 1 or i+1]
      if not (p[3]==0 and p[5]==0) then 
        local ax,ay = a[5]~=0 and a[5] or a[1],a[6]~=0 and a[6] or a[2]
        local bx,by = p[3]~=0 and p[3] or p[1],p[4]~=0 and p[4] or p[2]
        local cx,cy = p[5]~=0 and p[5] or p[1],p[6]~=0 and p[6] or p[2]
        local dx,dy = d[3]~=0 and d[3] or d[1],d[4]~=0 and d[4] or d[2]
        local mx,my = (a[1]+d[1])/2,(a[2]+d[2])/2
        local x,y = intersect(ax,ay,bx,by, cx,cy,dx,dy) 
        if not x then 
          -- editor.addTestLines({{ax,ay,bx,by},{ p[1],p[2],cx,cy}})
          -- editor.addTestLines({{dx,dy,cx,cy},{ p[1],p[2],bx,by}})
          x,y = intersect(ax,ay,bx,by, p[1],p[2],cx,cy) 
          if not x then 
            x,y = intersect(dx,dy,cx,cy, p[1],p[2],bx,by) 
          end
          if x then 
            -- editor.addTestLines({{p[1]-0.1,p[2],p[1]+0.1,p[2],p[1],p[2]-0.1}})
          end
        end
        if x then
          marked[i]=true
        else 
          if    (distPointToLineSegment2(bx, by, a[1], a[2], d[1], d[2], true)<1)
             or (distPointToLineSegment2(cx, cy, a[1], a[2], d[1], d[2], true)<1)
              then 
            local x,y = p[1],p[2]
            -- editor.addTestLines({{x-0.1,y,x+0.1,y,x,y-0.1}})
          end
        end
      end
    end
    for l=1,#line,1 do 
      if marked[l] then 
        line[l][5],line[l][6],line[l][3],line[l][4] = line[l][3],line[l][4],line[l][5],line[l][6]
      end
    end
    concat(outlines,bezierLine(line,cat,true))
    -- editor.addTestLines({outlines[#outlines]})
  end
  if animationPanel.debugView and #lines>1 then 
    -- editor.addTestLines(outlines)
  end
  local shape = #outlines>1 and fuseLines(outlines) or outlines[1]

  if animationPanel.debugView and #lines>1 then 
    print("#shape",#shape)
    -- editor.addTestLines({shape})
    -- editor.addTestLines(outlines)
  end
  return shape
end

local function clockwise(line)
  local sum=0
  for l=1,#line-3,2 do 
    sum=sum + (line[l+2]-line[l])*(line[l+3]-line[l+1])
  end
  return sum>0 and true or false 
end

local function invert(line)
  local line2 = {}
  for l=#line-1,1,-2 do 
    table.insert(line2,line[l])
    table.insert(line2,line[l+1])
  end
  return line2
end

function pointSort(a,b)
  if a[1]==b[1] then 
    return a[2]<b[2] 
  else
    return a[1]<b[1] 
  end
end
function fuseLines (lines)
  -- to combine holes with coincident points
  -- store reduced set of shared points w/ what hole they are part of
  -- then convert to list of connections
  -- try to join the connected holes
  local red,blue = {r=1,g=0,b=0},{r=0,g=0,b=1}
  while true do 
    local points = {}
    for l=#lines,1,-1 do
      local line = lines[l]
      for i=1,#line-1,2 do 
        local x,y= line[i],line[i+1]
        table.insert(points,{x,y,l,i})
      end
    end
    table.sort(points,pointSort)
    local connect
    for p=1,#points,1 do 
      local x,y,l = points[p][1],points[p][2],points[p][3]
      if p>1 and (x==points[p-1][1] and y==points[p-1][2] and l~=points[p-1][3]) then 
        connect = {points[p-1],points[p]}
        break
      end
    end
    
    if not connect then 
      -- editor.addTestLines(lines)
      break 
    end

    local line1,line2,l1,l2 = lines[connect[1][3]],lines[connect[2][3]],  connect[1][4],connect[2][4]
    local hole1a,hole1b = splice(line1,1,l1+1),  splice(line1,l1+2) or {}
    local hole2a,hole2b = splice(line2,1,l2+1),  splice(line2,l2+2) or {}
      
    -- print(#line1,"  ",#line2)
    -- print(#hole1a, #hole1b, #hole2a, #hole2b)
    -- editor.addTestLines({hole1a, hole1b,hole2a, hole2b})
    -- editor.addTestLines({{x1,y1, hole2a[#hole2a-1],hole2a[#hole2a] }}) 
    concat(hole1b,hole1a)
    concat(hole2b,hole2a)
    -- hole2b = clockwise(hole2b) and invert(hole2b) or hole2b
    -- hole1b = clockwise(hole1b) and invert(hole1b) or hole1b
    -- editor.addTestLines({{hole1b[#hole1b-3],hole1b[#hole1b-2],hole2b[3],hole2b[4]}})
    -- editor.addTestLines({{hole2b[#hole2b-3],hole2b[#hole2b-2],hole1b[3],hole1b[4]}})
    local x,y = intersect(
        hole1b[#hole1b-3],hole1b[#hole1b-2],hole2b[3],hole2b[4],
        hole2b[#hole2b-3],hole2b[#hole2b-2],hole1b[3],hole1b[4]
      )
    if x then 
      -- editor.addTestLines({{x-1,y,x+1,y,x,y-1}})
      hole2b = invert(hole2b)
    end
    concat(hole1b,hole2b)
    
    table.remove(lines,math.max(connect[1][3],connect[2][3]))
    table.remove(lines,math.min(connect[1][3],connect[2][3]))
    table.insert(lines,math.min(connect[1][3],connect[2][3]),hole1b)
  end


  -- combine lines with slice method
  local start = {0,0}
  local int = {0,0}
  for l=#lines,2,-1 do
    local line = lines[l] 
    local hole

    -- find the topmost point of the hole
    local tx,ty,top = line[1],line[2],1
    for i=1,#line-1,2 do 
      if line[i+1]<ty then 
        top = i
        tx = line[i]
        ty = line[i+1]
      end
    end
    start[1],start[2] = tx,ty

    -- find intersections of line going straight up 
    local closest = 9999999
    local ix,iy,n
    for l2=1,#lines,1 do 
      if l2 ~=l then 
        local line2 = lines[l2]
        for i=1,#line2-1,2 do 
          local x2,y2 = i==#line2-1 and 1 or i+2,i==#line2-1 and 2 or i+3
          local x,y = intersect(tx,ty,tx,-999999, line2[i],line2[i+1],line2[x2],line2[y2])
          if x then 
            local d = dist(tx,ty,x,y)
            if d<closest then 
              closest = d 
              hole = l2
              ix,iy,n = x,y,i
            end
          end
        end
      end
    end
    int[1],int[2] = ix,iy

    -- pick closest one and combine holes or outline
    -- also flip order of points if nessssssss
    if hole then 
      local hole1a,hole1b = splice(lines[hole],1,n+1),splice(lines[hole],n+2) or {}
      local hole2a,hole2b = splice(line,1,top+1),splice(line,top+2) or {}
      local x1,y1,x2,y2   = hole1b[1] or hole1a[1],hole1b[2] or hole1a[2],hole2b[1] or hole2a[1],hole2b[2] or hole2a[2]
      -- local x,y = intersect(
      --   x1,y1, hole2a[#hole2a-1],hole2a[#hole2a],
      --   hole1a[#hole1a-1],hole1a[#hole1a],x2,y2
      -- )
      
      -- editor.addTestLines({{ hole2a[#hole2a-1],hole2a[#hole2a] ,})
      -- editor.addTestLines({{ hole1a[#hole1a-1],hole1a[#hole1a],hole2a[#hole2a-1],hole2a[#hole2a]}})
      local ax1,ay1,bx1,by1,cx1,cy1 = 
        hole1b[1] or hole1a[1],hole1b[2] or hole1a[2],  
        hole2a[#hole2a-1],hole2a[#hole2a],    
        hole1a[#hole1a-1],hole1a[#hole1a]
      local ax2,ay2,bx2,by2,cx2,cy2 = 
        hole2a[#hole2a-1],hole2a[#hole2a] ,  
        hole2a[#hole2a-3] or hole2a[1],hole2a[#hole2a-2] or hole2a[2],   
        hole2b[1] or hole2a[#hole2a-1],hole2b[2] or hole2a[#hole2a]
      cx1,cy1 = (bx1+cx1)/2,(by1+cy1)/2
      cx2,cy2 = (bx2+cx2)/2,(by2+cy2)/2

      local side1 = windingSide(ax1,ay1,bx1,by1,cx1,cy1) >0
      local side2 = windingSide(ax2,ay2,bx2,by2,cx2,cy2) >0

      -- print(side1,side2)
      -- editor.addTestLines({{ color = side1 and red or blue, ax1,ay1,bx1,by1,cx1,cy1 }})
      -- editor.addTestLines({{ color = side2 and red or blue, ax2,ay2,bx2,by2,cx2,cy2 }})

      local flip = side1~= side2 
      local x,y = hole2a[#hole2a-1],hole2a[#hole2a]
      -- editor.addTestLines({{x1,y1,x2,y2}})
      prepend(hole1b,int)
      concat(hole1b,hole1a)
      concat(hole1b,int)
      prepend(hole2b,start)
      concat(hole2b,hole2a)
      if   flip then 
        -- editor.addTestLines({{color={1,0,0,1},x-0.2,y,x+0.2,y,x,y-0.2}})
        hole2b = invert(hole2b)
      end
      concat(hole1b,hole2b)
      lines[hole]=hole1b
      table.remove(lines,l)
    end
  end

  return lines[1]
end

function triangulateFill(shape,fill,index)
  if fill and not index then 
    fill[0][0]=0
    index = 0
  end

  local succ,tris = pcall(mth.triangulate,shape)
  if succ then 
    for t=1,#tris,1 do 
      local tri = tris[t]
      fill[0][0]=fill[0][0]+1
      local f = fill[fill[0][0]]
      f[1],f[2],f[3],f[4],f[5],f[6] =
        tri[1],tri[2],tri[3],tri[4],tri[5],tri[6]
      f[7] = index 
      
    end
    -- print("SUCC",fill[0][0])
    return fill 
  else
    print("FAAIIIIIILL",time)
    return false
  end
end


local function renderStroke(stroke,layer,frame,cat,transform)
  if not stroke.line[1] then return end
  local r,g,b,a=unpackColor(stroke.color)
  local a = (animationPanel.debugView and stroke.group == "line" and not stroke.debug) and 0.5 or a or 1
  local w=  stroke.size/2
  local w0= 0.2
  local z =  0+({rough=0,line=0.5,note=0.9})[stroke.group]

  local line,length,widths = bezierLine(stroke.line,stroke.debug and 0 or (cat-1))
  
  line = adjLine(line, frame, transform ,length,line)
  
  w0= stroke.ends=="tapered" and 0 or 1
  local endLength = 0
  local mid=vec2()
  local ax1,ay1,ax2,ay2,
        bx1,by1,bx2,by2,
        cx1,cy1,cx2,cy2,
        dx1,dy1,dx2,dy2

  if layer.shouldWobble then
    local rx,ry = (-0.5+math.random()),(-0.5+math.random())
    for i=1,length-3,2 do 
      vx,vy = (line[i+2]-line[i]),(line[i+3]-line[i+1]) 
      if sqrt(vx*vx+vy*vy)>1 then 
        rx,ry = (rx+(-0.5+math.random()))/2, (ry+(-0.5+math.random()))/2
      end
      line[i]  =line[i]  +rx*layer.wobble*3
      line[i+1]=line[i+1]+ry*layer.wobble*3
    end
  end
  for i=1,length-3,2 do 
    vx,vy = (line[i+2]-line[i]),(line[i+3]-line[i+1]) 

    len = math.max(0.00000001,sqrt(vx * vx + vy * vy))

    local w1,w2 = w,w
    
    if widths then 
      w1,w2 = math.max(widths[(i+1)/2],0.5),math.max(widths[(i+1)/2+1 ],0.5)
    end

    w1 = layer.onePixel and 0 or layer.lineWidth * (layer.lineWidthOverride and 1 or w1*w)
    w2 = layer.onePixel and 0 or layer.lineWidth * (layer.lineWidthOverride and 1 or w2*w)

    ax1,ay1,ax2,ay2 = 
      line[i]+(vy/len)*w1, line[i+1]-(vx/len)*w1, 
      line[i]-(vy/len)*w1, line[i+1]+(vx/len)*w1
    bx1,by1,bx2,by2 = 
      line[i+2]+(vy/len)*w2, line[i+3]-(vx/len)*w2, 
      line[i+2]-(vy/len)*w2, line[i+3]+(vx/len)*w2
    
    mid.x,mid.y =(ax1+ax2)/2,(ay1+ay2)/2

    if i>2 then 
      local px,py = (line[i]-line[i-2]), (line[i+1]-line[i-1]) 
      local x1,y1 = intersect(ax1,ay1,bx1,by1, cx1,cy1,dx1,dy1)
      local x2,y2 = intersect(ax2,ay2,bx2,by2, cx2,cy2,dx2,dy2)

      if x1 then 
        vBuffer[vBufferN-1].x,vBuffer[vBufferN-1].y=x1,y1
        ax1,ay1=x1,y1
        vBufInsert( x1,y1,   z,0  , r,g,b,a)
        vBufInsert( dx2,dy2, z,0  , r,g,b,a)
        vBufInsert( ax2,ay2, z,0  , r,g,b,a)

      elseif x2 then 
        vBuffer[vBufferN-2].x,vBuffer[vBufferN-2].y=x2,y2
        vBuffer[vBufferN-3].x,vBuffer[vBufferN-3].y=x2,y2
        ax2,ay2=x2,y2
        vBufInsert( ax1,ay1, z,0  , r,g,b,a)
        vBufInsert( dx1,dy1, z,0  , r,g,b,a)
        vBufInsert( x2,y2,   z,0  , r,g,b,a)

      else 
        local dot = (-vy) * px + vx * py 
        local _x1,_y1,_x2,_y2 
        if dot<=0 then 
          _x1,_y1,_x2,_y2 = ax1,ay1,dx1,dy1
        else 
          _x1,_y1,_x2,_y2 = ax2,ay2,dx2,dy2
        end   

        local angle = atan2(vy, vx) - atan2(py, px)
        angle = angle>pi and 2*pi-angle or angle
        angle = angle<-pi and 2*pi+angle or angle
        local divs =mf((stroke.size/6)*math.abs(angle)+1)
        local inc = angle/divs
        local ang = -math.angle(bx1,by1,bx2,by2)-(dot<=0 and pi/2 or -pi/2)
        local step = 0
        for k=0,divs-1,1 do 
          local radius = w1 -- TEMP
          vBufInsert( mid.x,mid.y, z,0  , r,g,b,a)
          vBufInsert( mid.x+sin(ang+step)*radius, mid.y+cos(ang+step)*radius, z,0  , r,g,b,a)
          step= step+inc
          vBufInsert( mid.x+sin(ang+step)*radius, mid.y+cos(ang+step)*radius, z,0  , r,g,b,a)
        end
        -- if dot<=0 then 
        --   vBuffer[vBufferN-2].x,vBuffer[vBufferN-2].y=dx2,dy2
        --   -- ax1,ay1=dx2,dy2
        --   -- vBufInsert( ax2,ay2, z,0  , r,g,b,a)
        --   -- vBufInsert( dx2,dy2, z,0  , r,g,b,a)
        --   -- vBufInsert( x1,y1,   z,0  , r,g,b,a)
        -- end
        -- if x2 then 
        --   -- vBuffer[vBufferN-2].x,vBuffer[vBufferN-2].y=x2,y2
        --   -- vBuffer[vBufferN-3].x,vBuffer[vBufferN-3].y=x2,y2
        --   -- ax2,ay2=x2,y2
        -- end
      end
    end
    
    if stroke.ends=="round" then 
      local doubleEnds = length==4 and 2 or 1
      for ends=1, doubleEnds, 1 do 
        local startTip = i==1 and ends==1
        if startTip or i==length-3 then 
          local _x1,_y1,_x2,_y2 
          if startTip then 
            _x1,_y1,_x2,_y2 = ax1,ay1,ax2,ay2
          else 
            _x1,_y1,_x2,_y2 = bx1,by1,bx2,by2
          end
          mid.x,mid.y =(_x1+_x2)/2,(_y1+_y2)/2
          local divs =mf(stroke.size/3+3)
          local inc = pi/divs
          local ang = -math.angle(_x1,_y1,_x2,_y2)-(startTip and pi/2 or -pi/2)


          local step = 0
          for k=0,divs-1,1 do 
            vBufInsert( mid.x,mid.y, z,0  , r,g,b,a)
            vBufInsert( mid.x+sin(ang+step)*w2, mid.y+cos(ang+step)*w2, z,0  , r,g,b,a)
            step= step+inc
            vBufInsert( mid.x+sin(ang+step)*w2, mid.y+cos(ang+step)*w2, z,0  , r,g,b,a)
          end
        end
      end
    end

    vBufInsert( ax1,ay1, z, 0   , r,g,b,a)
    vBufInsert( ax2,ay2, z, 0   , r,g,b,a)
    vBufInsert( bx2,by2, z, 0   , r,g,b,a)

    vBufInsert( bx2,by2, z, 0   , r,g,b,a)
    vBufInsert( bx1,by1, z, 0   , r,g,b,a)
    vBufInsert( ax1,ay1, z, 0   , r,g,b,a)

    
    cx1,cy1,cx2,cy2 = ax1,ay1,ax2,ay2
    dx1,dy1,dx2,dy2 = bx1,by1,bx2,by2
  end
end



function scene.renderVectorFrame(panel,layer,frame,isOnion)
  -- for s,stroke in lume.ripairs(panel.f.strokes) do
  --   if stroke.debug then
  --     table.remove(panel.f.strokes,s)
  --   end
  -- end
  local dvec = panel.drawVector and panel.drawVector>0 and panel.f == frame
  frame.strokeVerts = frame.strokeVerts or {}


  frame.strokes = type(frame.strokes)=="table" and frame.strokes or {}
  if (frame.strokes and #frame.strokes>0 and not frame.strokeMeshes) or panel.sudoForceRestroke then 
    editor.refreshStrokes()
  end

  layer.shouldWobble = layer.wobble>0 and panel.playing and panel.ticked2

  local fillIndex=-1
  local restroke = panel.forceRestroke or (layer.shouldWobble)
  
  local cat = config.catmull>0 and 
    (config.catmull or 0)+(panel.cat or 0) 
    or 0
  if panel.scalingSelection or panel.movingSelection or panel.rotatingSelection then 
    cat = math.max(0,cat-2)
  end
  if frame.strokes and (restroke or dvec) then
    local vx,vy,len=0,0,0

    local vertCounts= {
      fill  = 0,
      line  = 0,
      rough = 0,
      note = 0
    }

    local black = {r=0,g=0,b=0,a=1}
    frame.strokeMeshes = frame.strokeMeshes or {}

    if dvec and not isOnion and #frame.strokes>0 then

      local stroke = panel.previewStroke 

      -- local adj = adjLine({panel.brushCoords.ax,panel.brushCoords.ay},frame )
      table.insert(stroke.line,{
        panel.brushCoords.ax,panel.brushCoords.ay,  
        panel.brushCoords.ax,panel.brushCoords.ay,  
        panel.brushCoords.ax,panel.brushCoords.ay,  
        drawingPanel.pressure
      })
      
      startVerts()
      
      -------------------------------------------------------------------------------- start of code repeat
      renderStroke(stroke,layer,frame,cat)
      -------------------------------------------------------------------------------- end of code repeat
      
      table.remove(stroke.line,#stroke.line)
        

      if frame.strokePreview then 
        frame.strokePreview:release()
        frame.strokePreview=nil
      end
      if vBufferN>1 then 
        frame.strokePreview =  gfx.newMesh(zformat3, vBufferN, "triangles")
        frame.strokePreview:setVertices(vData,1)
      end

      -- table.remove(stroke.widths,#stroke.widths)
    elseif restroke   then 
      frame.released = false
      -- table.sort(frame.strokes,groupSort)
      -- if not (animationPanel.exporting and animationPanel.exportLinesOnly) or g =="line" then 

      startVerts()
      local grouped = {}
      for g,group in ipairs({"rough","fill","line","note"}) do
        for s,stroke in ipairs(frame.strokes) do
          if stroke.group == group  then 
            table.insert(grouped,stroke)
          end
        end
      end

      for s,stroke in ipairs(grouped) do           
        local transform = not isOnion and layer==panel.l and panel.selection.strokes and panel.selection.strokes[s] and panel.selection
        local z =  0.1
        if stroke.debug and not animationPanel.debugView then 

        elseif stroke.group =="fill" then
          local r,g,b,a=0,0,0,1
          r,g,b,a=unpackColor(stroke.color or black)
          a= 1
          
          if fillIndex==-1 then 
           fillIndex=vBufferN+1
          end
          
          stroke.outline = stroke.outline or fillOutline(stroke.line,math.max(2,cat))
          stroke.tris = stroke.tris or triangulateFill(stroke.outline,ffi.new(VECTOR_TYPE.."[?][7]",#stroke.outline*2))

          if stroke.tris then 
            for i=1,stroke.tris[0][0],1 do 
              local f = (i-1)*6
              fillTris[f+1],fillTris[f+2],fillTris[f+3],fillTris[f+4],fillTris[f+5],fillTris[f+6] =
                stroke.tris[i][1],stroke.tris[i][2],stroke.tris[i][3],stroke.tris[i][4],stroke.tris[i][5],stroke.tris[i][6]
            end
            fillTris[0] = stroke.tris[0][0]*6

            tris = adjLine(fillTris, frame, transform, fillTris[0],fillTris)

            if tris then 
              for t=1,fillTris[0]-5,6 do
               vBufInsert( tris[t+0],tris[t+1],z,0 , r,g,b,1 )
               vBufInsert( tris[t+2],tris[t+3],z,0 , r,g,b,1 )
               vBufInsert( tris[t+4],tris[t+5],z,0 , r,g,b,1 )
              end
            end
          end

        else
          -- print(not isOnion, l==panel.layer , panel.selection and panel.selection.strokes , panel.selection and  panel.selection.strokes and panel.selection.strokes[s])
          -- print("transform",inspect(transform))
          renderStroke(stroke,layer,frame,cat,transform)
        end
      end

      local vertCount = vBufferN
      local format,mode = zformat3 , "triangles" 

      frame.strokeMesh = vertCount>0 and frame.strokeMesh or gfx.newMesh(format,math.max(vertCount+3,1),mode)
      local full,vcount = true,frame.strokeMesh:getVertexCount()
      -- print(vertCount,vcount)
      if vertCount>vcount then 
        frame.strokeMesh:release()
        frame.strokeMesh = gfx.newMesh(format, vertCount+3, mode)
      end

      if useData then 
        local n = frame.strokeMesh:setVertices(vData,nil,nil,nil,true)
        -- print(string.format("set %i verts",n))
      else 
        slice = slice or {}
        for i=1,vertCount,1 do 
          slice[i] = vBuffer[i]
        end
        for i=vertCount+1,#slice,1 do 
          slice[i]=nil
        end
        frame.strokeMesh:setVertices(slice)
      end
      frame.strokeMesh:setDrawRange( 1, vertCount+3 )
    end

  end
  layer.fillIndex  = fillIndex or layer.fillIndex
end
