summary refs log tree commit diff stats
path: root/lib/pure/basic2d.nim
blob: f2fc1566b2f8bc94187c582456e0f52464b2014f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
-- helpers for selecting portions of text

-- Return any intersection of the region from State.selection1 to State.cursor1 (or
-- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
-- currently over a drawing) with the region between {line=line_index, pos=apos}
-- and {line=line_index, pos=bpos}.
-- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order.
-- Result: positions spos,epos between apos,bpos.
function Text.clip_selection(State, line_index, apos, bpos)
  if State.selection1.line == nil then return nil,nil end
  -- min,max = sorted(State.selection1,State.cursor1)
  local minl,minp = State.selection1.line,State.selection1.pos
  local maxl,maxp
  if State.mouse_down then
    maxl,maxp = Text.mouse_pos(State)
  else
    maxl,maxp = State.cursor1.line,State.cursor1.pos
  end
  if Text.lt1({line=maxl, pos=maxp},
              {line=minl, pos=minp}) then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
  end
  -- check if intervals are disjoint
  if line_index < minl then return nil,nil end
  if line_index > maxl then return nil,nil end
  if line_index == minl and bpos <= minp then return nil,nil end
  if line_index == maxl and apos >= maxp then return nil,nil end
  -- compare bounds more carefully (start inclusive, end exclusive)
  local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})
  local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})
  if a_ge and b_lt then
    -- fully contained
    return apos,bpos
  elseif a_ge then
    assert(maxl == line_index, ('maxl %d not equal to line_index %d'):format(maxl, line_index))
    return apos,maxp
  elseif b_lt then
    assert(minl == line_index, ('minl %d not equal to line_index %d'):format(minl, line_index))
    return minp,bpos
  else
    assert(minl == maxl and minl == line_index, ('minl %d, maxl %d and line_index %d are not all equal'):format(minl, maxl, line_index))
    return minp,maxp
  end
end

-- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
-- Creates text objects every time, so use this sparingly.
-- Returns some intermediate computation useful elsewhere.
function Text.draw_highlight(State, line, x,y, pos, lo,hi)
  if lo then
    local lo_offset = Text.offset(line.data, lo)
    local hi_offset = Text.offset(line.data, hi)
    local pos_offset = Text.offset(line.data, pos)
    local lo_px
    if pos == lo then
      lo_px = 0
    else
      local before = line.data:sub(pos_offset, lo_offset-1)
      lo_px = State.font:getWidth(before)
    end
    local s = line.data:sub(lo_offset, hi_offset-1)
    App.color(Highlight_color)
    love.graphics.rectangle('fill', x+lo_px,y, State.font:getWidth(s),State.line_height)
    App.color(Text_color)
    return lo_px
  end
end

function Text.mouse_pos(State)
  local x,y = App.mouse_x(), App.mouse_y()
  if y < State.top then
    return State.screen_top1.line, State.screen_top1.pos
  end
  for line_index,line in ipairs(State.lines) do
    if line.mode == 'text' then
      if Text.in_line(State, line_index, x,y) then
        return line_index, Text.to_pos_on_line(State, line_index, x,y)
      end
    end
  end
  local screen_bottom1 = Text.screen_bottom1(State)
  return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end

function Text.cut_selection_and_record_undo_event(State)
  if State.selection1.line == nil then return end
  local result = Text.selection(State)
  Text.delete_selection_and_record_undo_event(State)
  return result
end

function Text.delete_selection_and_record_undo_event(State)
  if State.selection1.pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#            Nim's Runtime Library
#        (c) Copyright 2013 Robert Persson
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

import math
import strutils


## Basic 2d support with vectors, points, matrices and some basic utilities.
## Vectors are implemented as direction vectors, ie. when transformed with a matrix
## the translation part of matrix is ignored. 
## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented for vectors and scalars.
##
## Quick start example:
##   
##   # Create a matrix wich first rotates, then scales and at last translates
##   
##   var m:TMatrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0)
##   
##   # Create a 2d point at (100,0) and a vector (5,2)
##   
##   var pt:TPoint2d=point2d(100.0,0.0) 
##   
##   var vec:TVector2d=vector2d(5.0,2.0)
##   
##   
##   pt &= m # transforms pt in place
##   
##   var pt2:TPoint2d=pt & m #concatenates pt with m and returns a new point
##   
##   var vec2:TVector2d=vec & m #concatenates vec with m and returns a new vector


const
  DEG360* = PI * 2.0
    ## 360 degrees in radians.
  DEG270* = PI * 1.5
    ## 270 degrees in radians.
  DEG180* = PI
    ## 180 degrees in radians.
  DEG90* = PI / 2.0
    ## 90 degrees in radians.
  DEG60* = PI / 3.0
    ## 60 degrees in radians.
  DEG45* = PI / 4.0
    ## 45 degrees in radians.
  DEG30* = PI / 6.0
    ## 30 degrees in radians.
  DEG15* = PI / 12.0
    ## 15 degrees in radians.
  RAD2DEGCONST = 180.0 / PI
    ## used internally by DegToRad and RadToDeg

type
    TMatrix2d* = object
      ## Implements a row major 2d matrix, which means
      ## transformations are applied the order they are concatenated.
      ## The rightmost column of the 3x3 matrix is left out since normally
      ## not used for geometric transformations in 2d.
      ax*,ay*,bx*,by*,tx*,ty*:float
    TPoint2d* = object
      ## Implements a non-homegeneous 2d point stored as 
      ## an `x` coordinate and an `y` coordinate.
      x*,y*:float
    TVector2d* = object 
      ## Implements a 2d **direction vector** stored as 
      ## an `x` coordinate and an `y` coordinate. Direction vector means, 
      ## that when transforming a vector with a matrix, the translational
      ## part of the matrix is ignored.
      x*,y*:float
 


# Some forward declarations...
proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d {.noInit.}
  ## Creates a new matrix. 
  ## `ax`,`ay` is the local x axis
  ## `bx`,`by` is the local y axis
  ## `tx`,`ty` is the translation
proc vector2d*(x,y:float):TVector2d {.noInit,inline.}
  ## Returns a new vector (`x`,`y`)
proc point2d*(x,y:float):TPoint2d {.noInit,inline.}
  ## Returns a new point (`x`,`y`)



let
  IDMATRIX*:TMatrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0)
    ## Quick access to an identity matrix
  ORIGO*:TPoint2d=point2d(0.0,0.0)
    ## Quick acces to point (0,0)
  XAXIS*:TVector2d=vector2d(1.0,0.0)
    ## Quick acces to an 2d x-axis unit vector
  YAXIS*:TVector2d=vector2d(0.0,1.0)
    ## Quick acces to an 2d y-axis unit vector

  
# ***************************************
#     Private utils
# ***************************************

proc rtos(val:float):string=
  return formatFloat(val,ffDefault,0)

proc safeArccos(v:float):float=
  ## assumes v is in range 0.0-1.0, but clamps
  ## the value to avoid out of domain errors
  ## due to rounding issues
  return arccos(clamp(v,-1.0,1.0))


template makeBinOpVector(s:expr)= 
  ## implements binary operators + , - , * and / for vectors
  proc s*(a,b:TVector2d):TVector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
  proc s*(a:TVector2d,b:float):TVector2d {.inline,noInit.}  = vector2d(s(a.x,b),s(a.y,b))
  proc s*(a:float,b:TVector2d):TVector2d {.inline,noInit.}  = vector2d(s(a,b.x),s(a,b.y))
  
template makeBinOpAssignVector(s:expr)= 
  ## implements inplace binary operators += , -= , /= and *= for vectors
  proc s*(a:var TVector2d,b:TVector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
  proc s*(a:var TVector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)


# ***************************************
#     TMatrix2d implementation
# ***************************************

proc setElements*(t:var TMatrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
  ## Sets arbitrary elements in an existing matrix.
  t.ax=ax
  t.ay=ay
  t.bx=bx
  t.by=by
  t.tx=tx
  t.ty=ty

proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d =
  result.setElements(ax,ay,bx,by,tx,ty)

proc `&`*(a,b:TMatrix2d):TMatrix2d {.noInit.} = #concatenate matrices
  ## Concatenates matrices returning a new matrix.
  
  # | a.AX a.AY 0 |   | b.AX b.AY 0 |
  # | a.BX a.BY 0 | * | b.BX b.BY 0 |
  # | a.TX a.TY 1 |   | b.TX b.TY 1 |
  result.setElements(
    a.ax * b.ax + a.ay * b.bx,
    a.ax * b.ay + a.ay * b.by,
    a.bx * b.ax + a.by * b.bx,
    a.bx * b.ay + a.by * b.by,
    a.tx * b.ax + a.ty * b.bx + b.tx, 
    a.tx * b.ay + a.ty * b.by + b.ty)


proc scale*(s:float):TMatrix2d {.noInit.} =
  ## Returns a new scale matrix.
  result.setElements(s,0,0,s,0,0)

proc scale*(s:float,org:TPoint2d):TMatrix2d {.noInit.} =
  ## Returns a new scale matrix using, `org` as scale origin.
  result.setElements(s,0,0,s,org.x-s*org.x,org.y-s*org.y)

proc stretch*(sx,sy:float):TMatrix2d {.noInit.} =
  ## Returns new a stretch matrix, which is a
  ## scale matrix with non uniform scale in x and y.
  result.setElements(sx,0,0,sy,0,0)
    
proc stretch*(sx,sy:float,org:TPoint2d):TMatrix2d {.noInit.} =
  ## Returns a new stretch matrix, which is a
  ## scale matrix with non uniform scale in x and y.
  ## `org` is used as stretch origin.
  result.setElements(sx,0,0,sy,org.x-sx*org.x,org.y-sy*org.y)
    
proc move*(dx,dy:float):TMatrix2d {.noInit.} =
  ## Returns a new translation matrix.
  result.setElements(1,0,0,1,dx,dy)

proc move*(v:TVector2d):TMatrix2d {.noInit.} =
  ## Returns a new translation matrix from a vector.
  result.setElements(1,0,0,1,v.x,v.y)

proc rotate*(rad:float):TMatrix2d {.noInit.} =
  ## Returns a new rotation matrix, which
  ## represents a rotation by `rad` radians
  let 
    s=sin(rad)
    c=cos(rad)
  result.setElements(c,s,-s,c,0,0)

proc rotate*(rad:float,org:TPoint2d):TMatrix2d {.noInit.} =
  ## Returns a new rotation matrix, which
  ## represents a rotation by `rad` radians around
  ## the origin `org`
  let
    s=sin(rad)
    c=cos(rad)
  result.setElements(c,s,-s,c,org.x+s*org.y-c*org.x,org.y-c*org.y-s*org.x)
  
proc mirror*(v:TVector2d):TMatrix2d {.noInit.} =
  ## Returns a new mirror matrix, mirroring
  ## around the line that passes through origo and
  ## has the direction of `v`
  let
    sqx=v.x*v.x
    sqy=v.y*v.y
    nd=1.0/(sqx+sqy) #used to normalize invector
    xy2=v.x*v.y*2.0*nd
    sqd=nd*(sqx-sqy)
    
  if nd==Inf or nd==NegInf:
    return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity

  result.setElements(
    sqd,xy2,
    xy2,-sqd,
    0.0,0.0)

proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} =
  ## Returns a new mirror matrix, mirroring
  ## around the line that passes through `org` and
  ## has the direction of `v`
  let
    sqx=v.x*v.x
    sqy=v.y*v.y
    nd=1.0/(sqx+sqy) #used to normalize invector
    xy2=v.x*v.y*2.0*nd
    sqd=nd*(sqx-sqy)
    
  if nd==Inf or nd==NegInf:
    return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity

  result.setElements(
    sqd,xy2,
    xy2,-sqd,
    org.x-org.y*xy2-org.x*sqd,org.y-org.x*xy2+org.y*sqd)
  


proc skew*(xskew,yskew:float):TMatrix2d {.noInit.} =
  ## Returns a new skew matrix, which has its 
  ## x axis rotated `xskew` radians from the local x axis, and
  ## y axis rotated `yskew` radians from the local y axis
  result.setElements(cos(yskew),sin(yskew),-sin(xskew),cos(xskew),0,0)
  

proc `$`* (t:TMatrix2d):string {.noInit.} =
  ## Returns a string representation of the matrix
  return rtos(t.ax) & "," & rtos(t.ay) &
    "," & rtos(t.bx) & "," & rtos(t.by) & 
    "," & rtos(t.tx) & "," & rtos(t.ty)

proc isUniform*(t:TMatrix2d,tol=1.0e-6):bool=
  ## Checks if the transform is uniform, that is 
  ## perpendicular axes of equal lenght, which means (for example)
  ## it cannot transform a circle into an ellipse.
  ## `tol` is used as tolerance for both equal length comparison 
  ## and perp. comparison.
  
  #dot product=0 means perpendicular coord. system:
  if abs(t.ax*t.bx+t.ay*t.by)<=tol:   
    #subtract squared lengths of axes to check if uniform scaling:
    if abs((t.ax*t.ax+t.ay*t.ay)-(t.bx*t.bx+t.by*t.by))<=tol:
      return true
  return false
    
proc determinant*(t:TMatrix2d):float=
  ## Computes the determinant of the matrix.
  
  #NOTE: equivalent with perp.dot product for two 2d vectors
  return t.ax*t.by-t.bx*t.ay  

proc isMirroring* (m:TMatrix2d):bool=
  ## Checks if the `m` is a mirroring matrix,
  ## which means it will reverse direction of a curve transformed with it
  return m.determinant<0.0
  
proc inverse*(m:TMatrix2d):TMatrix2d {.noInit.} =
  ## Returns a new matrix, which is the inverse of the matrix
  ## If the matrix is not invertible (determinant=0), an EDivByZero
  ## will be raised.
  let d=m.determinant
  if d==0.0:
    raise newException(DivByZeroError,"Cannot invert a zero determinant matrix")
    
  result.setElements(
    m.by/d,-m.ay/d,
    -m.bx/d,m.ax/d,
    (m.bx*m.ty-m.by*m.tx)/d,
    (m.ay*m.tx-m.ax*m.ty)/d)

proc equals*(m1:TMatrix2d,m2:TMatrix2d,tol=1.0e-6):bool=
  ## Checks if all elements of `m1`and `m2` is equal within
  ## a given tolerance `tol`.
  return 
    abs(m1.ax-m2.ax)<=tol and
    abs(m1.ay-m2.ay)<=tol and
    abs(m1.bx-m2.bx)<=tol and
    abs(m1.by-m2.by)<=tol and
    abs(m1.tx-m2.tx)<=tol and
    abs(m1.ty-m2.ty)<=tol
    
proc `=~`*(m1,m2:TMatrix2d):bool=
  ## Checks if `m1`and `m2` is aproximately equal, using a
  ## tolerance of 1e-6.
  equals(m1,m2)

proc isIdentity*(m:TMatrix2d,tol=1.0e-6):bool=
  ## Checks is a matrix is approximately an identity matrix,
  ## using `tol` as tolerance for each element.
  return equals(m,IDMATRIX,tol)

proc apply*(m:TMatrix2d,x,y:var float,translate=false)=
  ## Applies transformation `m` onto `x`,`y`, optionally
  ## using the translation part of the matrix.
  if translate: # positional style transform
    let newx=x*m.ax+y*m.bx+m.tx
    y=x*m.ay+y*m.by+m.ty
    x=newx
  else: # delta style transform
    let newx=x*m.ax+y*m.bx
    y=x*m.ay+y*m.by
    x=newx



# ***************************************
#     TVector2d implementation
# ***************************************
proc vector2d*(x,y:float):TVector2d = #forward decl.
  result.x=x
  result.y=y

proc polarVector2d*(ang:float,len:float):TVector2d {.noInit.} =
  ## Returns a new vector with angle `ang` and magnitude `len`
  result.x=cos(ang)*len
  result.y=sin(ang)*len

proc slopeVector2d*(slope:float,len:float):TVector2d {.noInit.} =
  ## Returns a new vector having slope (dy/dx) given by
  ## `slope`, and a magnitude of `len`
  let ang=arctan(slope)
  result.x=cos(ang)*len
  result.y=sin(ang)*len

proc len*(v:TVector2d):float {.inline.}=
  ## Returns the length of the vector.
  sqrt(v.x*v.x+v.y*v.y)
  
proc `len=`*(v:var TVector2d,newlen:float) {.noInit.} =
  ## Sets the length of the vector, keeping its angle.
  let fac=newlen/v.len
  
  if newlen==0.0:
    v.x=0.0
    v.y=0.0
    return
  
  if fac==Inf or fac==NegInf:
    #to short for float accuracy
    #do as good as possible:
    v.x=newlen
    v.y=0.0
  else:
    v.x*=fac
    v.y*=fac
  
proc sqrLen*(v:TVector2d):float {.inline.}=
  ## Computes the squared length of the vector, which is
  ## faster than computing the absolute length.
  v.x*v.x+v.y*v.y
  
proc angle*(v:TVector2d):float=
  ## Returns the angle of the vector. 
  ## (The counter clockwise plane angle between posetive x axis and `v`)
  result=arctan2(v.y,v.x)
  if result<0.0: result+=DEG360
  
proc `$` *(v:TVector2d):string=
  ## String representation of `v`
  result=rtos(v.x)
  result.add(",")
  result.add(rtos(v.y))
  
  
proc `&` *(v:TVector2d,m:TMatrix2d):TVector2d {.noInit.} =
  ## Concatenate vector `v` with a transformation matrix.
  ## Transforming a vector ignores the translational part
  ## of the matrix.
  
  #             | AX AY 0 |
  # | X Y 1 | * | BX BY 0 |
  #             | 0  0  1 |
  result.x=v.x*m.ax+v.y*m.bx
  result.y=v.x*m.ay+v.y*m.by


proc `&=`*(v:var TVector2d,m:TMatrix2d) {.inline.}=
  ## Applies transformation `m` onto `v` in place.
  ## Transforming a vector ignores the translational part
  ## of the matrix.
  
  #             | AX AY 0 |
  # | X Y 1 | * | BX BY 0 |
  #             | 0  0  1 |
  let newx=v.x*m.ax+v.y*m.bx
  v.y=v.x*m.ay+v.y*m.by
  v.x=newx


proc tryNormalize*(v:var TVector2d):bool= 
  ## Modifies `v` to have a length of 1.0, keeping its angle.
  ## If `v` has zero length (and thus no angle), it is left unmodified and 
  ## false is returned, otherwise true is returned.

  let mag=v.len

  if mag==0.0:
    return false
  
  v.x/=mag
  v.y/=mag
  return true


proc normalize*(v:var TVector2d) {.inline.}= 
  ## Modifies `v` to have a length of 1.0, keeping its angle.
  ## If  `v` has zero length, an EDivByZero will be raised.
  if not tryNormalize(v):
    raise newException(DivByZeroError,"Cannot normalize zero length vector")
  
proc transformNorm*(v:var TVector2d,t:TMatrix2d)=
  ## Applies a normal direction transformation `t` onto `v` in place.
  ## The resulting vector is *not* normalized.  Transforming a vector ignores the 
  ## translational part of the matrix. If the matrix is not invertible 
  ## (determinant=0), an EDivByZero will be raised.

  # transforming a normal is done by transforming
  # by the transpose of the inverse of the original matrix
  # this can be heavily optimized by precompute and inline
  #             | | AX AY 0 | ^-1| ^T
  # | X Y 1 | * | | BX BY 0 |    |
  #             | | 0  0  1 |    |
  let d=t.determinant
  if(d==0.0):
    raise newException(DivByZeroError,"Matrix is not invertible")
  let newx = (t.by*v.x-t.ay*v.y)/d
  v.y = (t.ax*v.y-t.bx*v.x)/d
  v.x = newx

proc transformInv*(v:var TVector2d,t:TMatrix2d)=
  ## Applies inverse of a transformation `t` to `v` in place.
  ## This is faster than creating an inverse matrix and apply() it.
  ## Transforming a vector ignores the translational part
  ## of the matrix. If the matrix is not invertible (determinant=0), an EDivByZero
  ## will be raised.
  let d=t.determinant

  if(d==0.0):
    raise newException(DivByZeroError,"Matrix is not invertible")

  let newx=(t.by*v.x-t.bx*v.y)/d
  v.y = (t.ax*v.y-t.ay*v.x)/d
  v.x = newx

proc transformNormInv*(v:var TVector2d,t:TMatrix2d)=
  ## Applies an inverse normal direction transformation `t` onto `v` in place.
  ## This is faster than creating an inverse 
  ## matrix and transformNorm(...) it. Transforming a vector ignores the 
  ## translational part of the matrix.

  # normal inverse transform is done by transforming
  # by the inverse of the transpose of the inverse of the org. matrix
  # which is equivalent with transforming with the transpose.
  #             | | | AX AY 0 |^-1|^T|^-1                | AX BX 0 |
  # | X Y 1 | * | | | BX BY 0 |   |  |    =  | X Y 1 | * | AY BY 0 | 
  #             | | | 0  0  1 |   |  |                   | 0  0  1 | 
  # This can be heavily reduced to:
  let newx=t.ay*v.y+t.ax*v.x
  v.y=t.by*v.y+t.bx*v.x
  v.x=newx

proc rotate90*(v:var TVector2d) {.inline.}=
  ## Quickly rotates vector `v` 90 degrees counter clockwise,
  ## without using any trigonometrics.
  swap(v.x,v.y)
  v.x= -v.x
  
proc rotate180*(v:var TVector2d){.inline.}=
  ## Quickly rotates vector `v` 180 degrees counter clockwise,
  ## without using any trigonometrics.
  v.x= -v.x
  v.y= -v.y
  
proc rotate270*(v:var TVector2d) {.inline.}=
  ## Quickly rotates vector `v` 270 degrees counter clockwise,
  ## without using any trigonometrics.
  swap(v.x,v.y)
  v.y= -v.y
  
proc rotate*(v:var TVector2d,rad:float) =
  ## Rotates vector `v` `rad` radians in place.
  let
    s=sin(rad)
    c=cos(rad)
    newx=c*v.x-s*v.y
  v.y=c*v.y+s*v.x
  v.x=newx
  
proc scale*(v:var TVector2d,fac:float){.inline.}=
  ## Scales vector `v` `rad` radians in place.
  v.x*=fac
  v.y*=fac
  
proc stretch*(v:var TVector2d,facx,facy:float){.inline.}=
  ## Stretches vector `v` `facx` times horizontally,
  ## and `facy` times vertically.
  v.x*=facx
  v.y*=facy
  
proc mirror*(v:var TVector2d,mirrvec:TVector2d)=
  ## Mirrors vector `v` using `mirrvec` as mirror direction.
  let
    sqx=mirrvec.x*mirrvec.x
    sqy=mirrvec.y*mirrvec.y
    nd=1.0/(sqx+sqy) #used to normalize invector
    xy2=mirrvec.x*mirrvec.y*2.0*nd
    sqd=nd*(sqx-sqy)
    
  if nd==Inf or nd==NegInf:
    return #mirroring around a zero vector is arbitrary=>keep as is is fastest
  
  let newx=xy2*v.y+sqd*v.x
  v.y=v.x*xy2-sqd*v.y
  v.x=newx
    
 
proc `-` *(v:TVector2d):TVector2d=
  ## Negates a vector
  result.x= -v.x
  result.y= -v.y
    
# declare templated binary operators
makeBinOpVector(`+`)
makeBinOpVector(`-`)
makeBinOpVector(`*`)
makeBinOpVector(`/`)
makeBinOpAssignVector(`+=`)
makeBinOpAssignVector(`-=`)
makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`)


proc dot*(v1,v2:TVector2d):float=
  ## Computes the dot product of two vectors. 
  ## Returns 0.0 if the vectors are perpendicular.
  return v1.x*v2.x+v1.y*v2.y
  
proc cross*(v1,v2:TVector2d):float=
  ## Computes the cross product of two vectors, also called
  ## the 'perpendicular dot product' in 2d. Returns 0.0 if the vectors
  ## are parallel.
  return v1.x*v2.y-v1.y*v2.x
  
proc equals*(v1,v2:TVector2d,tol=1.0e-6):bool=
  ## Checks if two vectors approximately equals with a tolerance.
  return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol
  
proc `=~` *(v1,v2:TVector2d):bool=
  ## Checks if two vectors approximately equals with a 
  ## hardcoded tolerance 1e-6
  equals(v1,v2)
  
proc angleTo*(v1,v2:TVector2d):float=
  ## Returns the smallest of the two possible angles 
  ## between `v1` and `v2` in radians.
  var
    nv1=v1
    nv2=v2
  if not nv1.tryNormalize or not nv2.tryNormalize:
    return 0.0 # zero length vector has zero angle to any other vector
  return safeArccos(dot(nv1,nv2))
  
proc angleCCW*(v1,v2:TVector2d):float=
  ## Returns the counter clockwise plane angle from `v1` to `v2`,
  ## in range 0 - 2*PI
  let a=v1.angleTo(v2)
  if v1.cross(v2)>=0.0:
    return a
  return DEG360-a
  
proc angleCW*(v1,v2:TVector2d):float=
  ## Returns the clockwise plane angle from `v1` to `v2`,
  ## in range 0 - 2*PI
  let a=v1.angleTo(v2)
  if v1.cross(v2)<=0.0:
    return a
  return DEG360-a

proc turnAngle*(v1,v2:TVector2d):float=
  ## Returns the amount v1 should be rotated (in radians) to equal v2,
  ## in range -PI to PI
  let a=v1.angleTo(v2)
  if v1.cross(v2)<=0.0:
    return -a
  return a

proc bisect*(v1,v2:TVector2d):TVector2d {.noInit.}=
  ## Computes the bisector between v1 and v2 as a normalized vector.
  ## If one of the input vectors has zero length, a normalized version
  ## of the other is returned. If both input vectors has zero length, 
  ## an arbitrary normalized vector is returned.
  var
    vmag1=v1.len
    vmag2=v2.len
    
  # zero length vector equals arbitrary vector, just change to magnitude to one to
  # avoid zero division
  if vmag1==0.0: 
    if vmag2==0: #both are zero length return any normalized vector
      return XAXIS
    vmag1=1.0
  if vmag2==0.0: vmag2=1.0    
    
  let
    x1=v1.x/vmag1
    y1=v1.y/vmag1
    x2=v2.x/vmag2
    y2=v2.y/vmag2
    
  result.x=(x1 + x2) * 0.5
  result.y=(y1 + y2) * 0.5
  
  if not result.tryNormalize():
    # This can happen if vectors are colinear. In this special case
    # there are actually two bisectors, we select just 
    # one of them (x1,y1 rotated 90 degrees ccw).
    result.x = -y1
    result.y = x1



# ***************************************
#     TPoint2d implementation
# ***************************************

proc point2d*(x,y:float):TPoint2d =
  result.x=x
  result.y=y
  
proc sqrDist*(a,b:TPoint2d):float=
  ## Computes the squared distance between `a` and `b`
  let dx=b.x-a.x
  let dy=b.y-a.y
  result=dx*dx+dy*dy
  
proc dist*(a,b:TPoint2d):float {.inline.}=
  ## Computes the absolute distance between `a` and `b`
  result=sqrt(sqrDist(a,b))

proc angle*(a,b:TPoint2d):float=
  ## Computes the angle of the vector `b`-`a`
  let dx=b.x-a.x
  let dy=b.y-a.y
  result=arctan2(dy,dx)
  if result<0:
    result += DEG360

proc `$` *(p:TPoint2d):string=
  ## String representation of `p`
  result=rtos(p.x)
  result.add(",")
  result.add(rtos(p.y))
  
proc `&`*(p:TPoint2d,t:TMatrix2d):TPoint2d {.noInit,inline.} =
  ## Concatenates a point `p` with a transform `t`,
  ## resulting in a new, transformed point.
  
  #             | AX AY 0 |
  # | X Y 1 | * | BX BY 0 |
  #             | TX TY 1 |
  result.x=p.x*t.ax+p.y*t.bx+t.tx
  result.y=p.x*t.ay+p.y*t.by+t.ty

proc `&=` *(p:var TPoint2d,t:TMatrix2d) {.inline.}=
  ## Applies transformation `t` onto `p` in place.
  let newx=p.x*t.ax+p.y*t.bx+t.tx
  p.y=p.x*t.ay+p.y*t.by+t.ty
  p.x=newx


proc transformInv*(p:var TPoint2d,t:TMatrix2d){.inline.}=
  ## Applies the inverse of transformation `t` onto `p` in place.
  ## If the matrix is not invertable (determinant=0) , EDivByZero will
  ## be raised.
  
  #             | AX AY 0 | ^-1
  # | X Y 1 | * | BX BY 0 |
  #             | TX TY 1 |
  let d=t.determinant
  if d==0.0:
    raise newException(DivByZeroError,"Cannot invert a zero determinant matrix")
  let 
    newx= (t.bx*t.ty-t.by*t.tx+p.x*t.by-p.y*t.bx)/d
  p.y = -(t.ax*t.ty-t.ay*t.tx+p.x*t.ay-p.y*t.ax)/d
  p.x=newx
  
  
proc `+`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} =
  ## Adds a vector `v` to a point `p`, resulting 
  ## in a new point.
  result.x=p.x+v.x
  result.y=p.y+v.y

proc `+=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} =
  ## Adds a vector `v` to a point `p` in place.
  p.x+=v.x
  p.y+=v.y

proc `-`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} =
  ## Subtracts a vector `v` from a point `p`, resulting 
  ## in a new point.
  result.x=p.x-v.x
  result.y=p.y-v.y

proc `-`*(p1,p2:TPoint2d):TVector2d {.noInit,inline.} =
  ## Subtracts `p2`from `p1` resulting in a difference vector.
  result.x=p1.x-p2.x
  result.y=p1.y-p2.y

proc `-=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} =
  ## Subtracts a vector `v` from a point `p` in place.
  p.x-=v.x
  p.y-=v.y
  
proc equals(p1,p2:TPoint2d,tol=1.0e-6):bool {.inline.}=
  ## Checks if two points approximately equals with a tolerance.
  return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol

proc `=~`*(p1,p2:TPoint2d):bool {.inline.}=
  ## Checks if two vectors approximately equals with a 
  ## hardcoded tolerance 1e-6
  equals(p1,p2)

proc polar*(p:TPoint2d,ang,dist:float):TPoint2d {.noInit.} =
  ## Returns a point with a given angle and distance away from `p`
  result.x=p.x+cos(ang)*dist
  result.y=p.y+sin(ang)*dist

proc rotate*(p:var TPoint2d,rad:float)=
  ## Rotates a point in place `rad` radians around origo.
  let
    c=cos(rad)
    s=sin(rad)
    newx=p.x*c-p.y*s
  p.y=p.y*c+p.x*s
  p.x=newx
    
proc rotate*(p:var TPoint2d,rad:float,org:TPoint2d)=
  ## Rotates a point in place `rad` radians using `org` as
  ## center of rotation.
  let
    c=cos(rad)
    s=sin(rad)
    newx=(p.x - org.x) * c - (p.y - org.y) * s + org.x
  p.y=(p.y - org.y) * c + (p.x - org.x) * s + org.y
  p.x=newx
  
proc scale*(p:var TPoint2d,fac:float) {.inline.}=
  ## Scales a point in place `fac` times with world origo as origin.
  p.x*=fac
  p.y*=fac
  
proc scale*(p:var TPoint2d,fac:float,org:TPoint2d){.inline.}=
  ## Scales the point in place `fac` times with `org` as origin.
  p.x=(p.x - org.x) * fac + org.x
  p.y=(p.y - org.y) * fac + org.y

proc stretch*(p:var TPoint2d,facx,facy:float){.inline.}=
  ## Scales a point in place non uniformly `facx` and `facy` times with 
  ## world origo as origin.
  p.x*=facx
  p.y*=facy

proc stretch*(p:var TPoint2d,facx,facy:float,org:TPoint2d){.inline.}=
  ## Scales the point in place non uniformly `facx` and `facy` times with 
  ## `org` as origin.
  p.x=(p.x - org.x) * facx + org.x
  p.y=(p.y - org.y) * facy + org.y

proc move*(p:var TPoint2d,dx,dy:float){.inline.}=
  ## Translates a point `dx`, `dy` in place.
  p.x+=dx
  p.y+=dy

proc move*(p:var TPoint2d,v:TVector2d){.inline.}=
  ## Translates a point with vector `v` in place.
  p.x+=v.x
  p.y+=v.y

proc sgnArea*(a,b,c:TPoint2d):float=
  ## Computes the signed area of the triangle thru points `a`,`b` and `c`
  ## result>0.0 for counter clockwise triangle
  ## result<0.0 for clockwise triangle
  ## This is commonly used to determinate side of a point with respect to a line.
  return ((b.x - c.x) * (b.y - a.y)-(b.y - c.y) * (b.x - a.x))*0.5

proc area*(a,b,c:TPoint2d):float=
  ## Computes the area of the triangle thru points `a`,`b` and `c`
  return abs(sgnArea(a,b,c))

proc closestPoint*(p:TPoint2d,pts:varargs[TPoint2d]):TPoint2d=
  ## Returns a point selected from `pts`, that has the closest 
  ## euclidean distance to `p`
  assert(pts.len>0) # must have at least one point
  
  var 
    bestidx=0
    bestdist=p.sqrDist(pts[0])
    curdist:float
    
  for idx in 1..high(pts):
    curdist=p.sqrDist(pts[idx])
    if curdist<bestdist:
      bestidx=idx
      bestdist=curdist
  
  result=pts[bestidx]


# ***************************************
#     Misc. math utilities that should
#     probably be in another module.
# ***************************************
proc normAngle*(ang:float):float=
  ## Returns an angle in radians, that is equal to `ang`,
  ## but in the range 0 to <2*PI
  if ang>=0.0 and ang<DEG360:
    return ang

  return ang mod DEG360
  
proc degToRad*(deg:float):float {.inline.}=
  ## converts `deg` degrees to radians
  deg / RAD2DEGCONST

proc radToDeg*(rad:float):float {.inline.}=
  ## converts `rad` radians to degrees
  rad * RAD2DEGCONST