moved math code to backendbase package so that backends can also use it
This commit is contained in:
parent
066f4f55bb
commit
cc9247c627
9 changed files with 364 additions and 357 deletions
140
backend/backendbase/math.go
Normal file
140
backend/backendbase/math.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package backendbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vec [2]float64
|
||||||
|
|
||||||
|
func (v Vec) String() string {
|
||||||
|
return fmt.Sprintf("[%f,%f]", v[0], v[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Add(v2 Vec) Vec {
|
||||||
|
return Vec{v[0] + v2[0], v[1] + v2[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Sub(v2 Vec) Vec {
|
||||||
|
return Vec{v[0] - v2[0], v[1] - v2[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Mul(v2 Vec) Vec {
|
||||||
|
return Vec{v[0] * v2[0], v[1] * v2[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Mulf(f float64) Vec {
|
||||||
|
return Vec{v[0] * f, v[1] * f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) MulMat(m Mat) Vec {
|
||||||
|
return Vec{
|
||||||
|
m[0]*v[0] + m[2]*v[1] + m[4],
|
||||||
|
m[1]*v[0] + m[3]*v[1] + m[5]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) MulMat2(m Mat2) Vec {
|
||||||
|
return Vec{m[0]*v[0] + m[2]*v[1], m[1]*v[0] + m[3]*v[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Div(v2 Vec) Vec {
|
||||||
|
return Vec{v[0] / v2[0], v[1] / v2[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Divf(f float64) Vec {
|
||||||
|
return Vec{v[0] / f, v[1] / f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Dot(v2 Vec) float64 {
|
||||||
|
return v[0]*v2[0] + v[1]*v2[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Len() float64 {
|
||||||
|
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) LenSqr() float64 {
|
||||||
|
return v[0]*v[0] + v[1]*v[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Norm() Vec {
|
||||||
|
return v.Mulf(1.0 / v.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Atan2() float64 {
|
||||||
|
return math.Atan2(v[1], v[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) Angle() float64 {
|
||||||
|
return math.Pi*0.5 - math.Atan2(v[1], v[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Vec) AngleTo(v2 Vec) float64 {
|
||||||
|
return math.Acos(v.Norm().Dot(v2.Norm()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mat [6]float64
|
||||||
|
|
||||||
|
func (m *Mat) String() string {
|
||||||
|
return fmt.Sprintf("[%f,%f,0,\n %f,%f,0,\n %f,%f,1,]", m[0], m[2], m[4], m[1], m[3], m[5])
|
||||||
|
}
|
||||||
|
|
||||||
|
var MatIdentity = Mat{
|
||||||
|
1, 0,
|
||||||
|
0, 1,
|
||||||
|
0, 0}
|
||||||
|
|
||||||
|
func MatTranslate(v Vec) Mat {
|
||||||
|
return Mat{
|
||||||
|
1, 0,
|
||||||
|
0, 1,
|
||||||
|
v[0], v[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatScale(v Vec) Mat {
|
||||||
|
return Mat{
|
||||||
|
v[0], 0,
|
||||||
|
0, v[1],
|
||||||
|
0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatRotate(radians float64) Mat {
|
||||||
|
s, c := math.Sincos(radians)
|
||||||
|
return Mat{
|
||||||
|
c, s,
|
||||||
|
-s, c,
|
||||||
|
0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mat) Mul(m2 Mat) Mat {
|
||||||
|
return Mat{
|
||||||
|
m[0]*m2[0] + m[1]*m2[2],
|
||||||
|
m[0]*m2[1] + m[1]*m2[3],
|
||||||
|
m[2]*m2[0] + m[3]*m2[2],
|
||||||
|
m[2]*m2[1] + m[3]*m2[3],
|
||||||
|
m[4]*m2[0] + m[5]*m2[2] + m2[4],
|
||||||
|
m[4]*m2[1] + m[5]*m2[3] + m2[5]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mat) Invert() Mat {
|
||||||
|
identity := 1.0 / (m[0]*m[3] - m[2]*m[1])
|
||||||
|
|
||||||
|
return Mat{
|
||||||
|
m[3] * identity,
|
||||||
|
-m[1] * identity,
|
||||||
|
-m[2] * identity,
|
||||||
|
m[0] * identity,
|
||||||
|
(m[2]*m[5] - m[3]*m[4]) * identity,
|
||||||
|
(m[1]*m[4] - m[0]*m[5]) * identity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mat2 [4]float64
|
||||||
|
|
||||||
|
func (m Mat) Mat2() Mat2 {
|
||||||
|
return Mat2{m[0], m[1], m[2], m[3]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mat2) String() string {
|
||||||
|
return fmt.Sprintf("[%f,%f,\n %f,%f]", m[0], m[2], m[1], m[3])
|
||||||
|
}
|
30
canvas.go
30
canvas.go
|
@ -35,7 +35,7 @@ type Canvas struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawState struct {
|
type drawState struct {
|
||||||
transform mat
|
transform backendbase.Mat
|
||||||
fill drawStyle
|
fill drawStyle
|
||||||
stroke drawStyle
|
stroke drawStyle
|
||||||
font *Font
|
font *Font
|
||||||
|
@ -150,7 +150,7 @@ func New(backend backendbase.Backend) *Canvas {
|
||||||
cv.state.globalAlpha = 1
|
cv.state.globalAlpha = 1
|
||||||
cv.state.fill.color = color.RGBA{A: 255}
|
cv.state.fill.color = color.RGBA{A: 255}
|
||||||
cv.state.stroke.color = color.RGBA{A: 255}
|
cv.state.stroke.color = color.RGBA{A: 255}
|
||||||
cv.state.transform = matIdentity
|
cv.state.transform = backendbase.MatIdentity
|
||||||
cv.path.cv = cv
|
cv.path.cv = cv
|
||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
|
@ -170,8 +170,8 @@ func (cv *Canvas) Height() int {
|
||||||
// Size returns the internal width and height of the canvas
|
// Size returns the internal width and height of the canvas
|
||||||
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
|
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
|
||||||
|
|
||||||
func (cv *Canvas) tf(v vec) vec {
|
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
|
||||||
return v.mulMat(cv.state.transform)
|
return v.MulMat(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
const alphaTexSize = 2048
|
const alphaTexSize = 2048
|
||||||
|
@ -380,7 +380,7 @@ func (cv *Canvas) Restore() {
|
||||||
cv.b.ClearClip()
|
cv.b.ClearClip()
|
||||||
for _, st := range cv.stateStack {
|
for _, st := range cv.stateStack {
|
||||||
if len(st.clip.p) > 0 {
|
if len(st.clip.p) > 0 {
|
||||||
cv.clip(&st.clip, matIdentity)
|
cv.clip(&st.clip, backendbase.MatIdentity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cv.state = cv.stateStack[l-1]
|
cv.state = cv.stateStack[l-1]
|
||||||
|
@ -389,27 +389,27 @@ func (cv *Canvas) Restore() {
|
||||||
|
|
||||||
// Scale updates the current transformation with a scaling by the given values
|
// Scale updates the current transformation with a scaling by the given values
|
||||||
func (cv *Canvas) Scale(x, y float64) {
|
func (cv *Canvas) Scale(x, y float64) {
|
||||||
cv.state.transform = matScale(vec{x, y}).mul(cv.state.transform)
|
cv.state.transform = backendbase.MatScale(backendbase.Vec{x, y}).Mul(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate updates the current transformation with a translation by the given values
|
// Translate updates the current transformation with a translation by the given values
|
||||||
func (cv *Canvas) Translate(x, y float64) {
|
func (cv *Canvas) Translate(x, y float64) {
|
||||||
cv.state.transform = matTranslate(vec{x, y}).mul(cv.state.transform)
|
cv.state.transform = backendbase.MatTranslate(backendbase.Vec{x, y}).Mul(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate updates the current transformation with a rotation by the given angle
|
// Rotate updates the current transformation with a rotation by the given angle
|
||||||
func (cv *Canvas) Rotate(angle float64) {
|
func (cv *Canvas) Rotate(angle float64) {
|
||||||
cv.state.transform = matRotate(angle).mul(cv.state.transform)
|
cv.state.transform = backendbase.MatRotate(angle).Mul(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform updates the current transformation with the given matrix
|
// Transform updates the current transformation with the given matrix
|
||||||
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
|
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
|
||||||
cv.state.transform = mat{a, b, c, d, e, f}.mul(cv.state.transform)
|
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}.Mul(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransform replaces the current transformation with the given matrix
|
// SetTransform replaces the current transformation with the given matrix
|
||||||
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
|
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
|
||||||
cv.state.transform = mat{a, b, c, d, e, f}
|
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
|
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
|
||||||
|
@ -456,14 +456,14 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var triBuf [500][2]float64
|
var triBuf [500][2]float64
|
||||||
tris := cv.strokeTris(&cv.path, cv.state.transform.invert(), true, triBuf[:0])
|
tris := cv.strokeTris(&cv.path, cv.state.transform.Invert(), true, triBuf[:0])
|
||||||
|
|
||||||
pt := vec{x, y}
|
pt := backendbase.Vec{x, y}
|
||||||
|
|
||||||
for i := 0; i < len(tris); i += 3 {
|
for i := 0; i < len(tris); i += 3 {
|
||||||
a := vec{tris[i][0], tris[i][1]}
|
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||||
b := vec{tris[i+1][0], tris[i+1][1]}
|
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||||
c := vec{tris[i+2][0], tris[i+2][1]}
|
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||||
if triangleContainsPoint(a, b, c, pt) {
|
if triangleContainsPoint(a, b, c, pt) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
12
gradients.go
12
gradients.go
|
@ -13,7 +13,7 @@ import (
|
||||||
// will correspond to a straight line
|
// will correspond to a straight line
|
||||||
type LinearGradient struct {
|
type LinearGradient struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
from, to vec
|
from, to backendbase.Vec
|
||||||
created bool
|
created bool
|
||||||
loaded bool
|
loaded bool
|
||||||
opaque bool
|
opaque bool
|
||||||
|
@ -27,7 +27,7 @@ type LinearGradient struct {
|
||||||
// will correspond to a circle
|
// will correspond to a circle
|
||||||
type RadialGradient struct {
|
type RadialGradient struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
from, to vec
|
from, to backendbase.Vec
|
||||||
radFrom float64
|
radFrom float64
|
||||||
radTo float64
|
radTo float64
|
||||||
created bool
|
created bool
|
||||||
|
@ -44,8 +44,8 @@ func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
||||||
lg := &LinearGradient{
|
lg := &LinearGradient{
|
||||||
cv: cv,
|
cv: cv,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
from: vec{x0, y0},
|
from: backendbase.Vec{x0, y0},
|
||||||
to: vec{x1, y1},
|
to: backendbase.Vec{x1, y1},
|
||||||
data: make(backendbase.Gradient, 0, 20),
|
data: make(backendbase.Gradient, 0, 20),
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(lg, func(*LinearGradient) {
|
runtime.SetFinalizer(lg, func(*LinearGradient) {
|
||||||
|
@ -62,8 +62,8 @@ func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGr
|
||||||
rg := &RadialGradient{
|
rg := &RadialGradient{
|
||||||
cv: cv,
|
cv: cv,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
from: vec{x0, y0},
|
from: backendbase.Vec{x0, y0},
|
||||||
to: vec{x1, y1},
|
to: backendbase.Vec{x1, y1},
|
||||||
radFrom: r0,
|
radFrom: r0,
|
||||||
radTo: r1,
|
radTo: r1,
|
||||||
data: make(backendbase.Gradient, 0, 20),
|
data: make(backendbase.Gradient, 0, 20),
|
||||||
|
|
18
images.go
18
images.go
|
@ -182,10 +182,10 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var data [4][2]float64
|
var data [4][2]float64
|
||||||
data[0] = cv.tf(vec{dx, dy})
|
data[0] = cv.tf(backendbase.Vec{dx, dy})
|
||||||
data[1] = cv.tf(vec{dx, dy + dh})
|
data[1] = cv.tf(backendbase.Vec{dx, dy + dh})
|
||||||
data[2] = cv.tf(vec{dx + dw, dy + dh})
|
data[2] = cv.tf(backendbase.Vec{dx + dw, dy + dh})
|
||||||
data[3] = cv.tf(vec{dx + dw, dy})
|
data[3] = cv.tf(backendbase.Vec{dx + dw, dy})
|
||||||
|
|
||||||
cv.drawShadow(data[:], nil, false)
|
cv.drawShadow(data[:], nil, false)
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
|
||||||
type ImagePattern struct {
|
type ImagePattern struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
img *Image
|
img *Image
|
||||||
tf mat
|
tf backendbase.Mat
|
||||||
rep imagePatternRepeat
|
rep imagePatternRepeat
|
||||||
ip backendbase.ImagePattern
|
ip backendbase.ImagePattern
|
||||||
}
|
}
|
||||||
|
@ -222,8 +222,8 @@ const (
|
||||||
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
|
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
|
func (ip *ImagePattern) data(tf backendbase.Mat) backendbase.ImagePatternData {
|
||||||
m := tf.invert().mul(ip.tf.invert())
|
m := tf.Invert().Mul(ip.tf.Invert())
|
||||||
return backendbase.ImagePatternData{
|
return backendbase.ImagePatternData{
|
||||||
Image: ip.img.img,
|
Image: ip.img.img,
|
||||||
Transform: [9]float64{
|
Transform: [9]float64{
|
||||||
|
@ -239,7 +239,7 @@ func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
|
||||||
// to the given matrix. The matrix is a 3x3 matrix, but three
|
// to the given matrix. The matrix is a 3x3 matrix, but three
|
||||||
// of the values are always identity values
|
// of the values are always identity values
|
||||||
func (ip *ImagePattern) SetTransform(tf [6]float64) {
|
func (ip *ImagePattern) SetTransform(tf [6]float64) {
|
||||||
ip.tf = mat(tf)
|
ip.tf = backendbase.Mat(tf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePattern creates a new image pattern with the specified
|
// CreatePattern creates a new image pattern with the specified
|
||||||
|
@ -249,7 +249,7 @@ func (cv *Canvas) CreatePattern(src interface{}, repeat imagePatternRepeat) *Ima
|
||||||
cv: cv,
|
cv: cv,
|
||||||
img: cv.getImage(src),
|
img: cv.getImage(src),
|
||||||
rep: repeat,
|
rep: repeat,
|
||||||
tf: mat{1, 0, 0, 1, 0, 0},
|
tf: backendbase.Mat{1, 0, 0, 1, 0, 0},
|
||||||
}
|
}
|
||||||
if ip.img != nil {
|
if ip.img != nil {
|
||||||
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
|
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
|
||||||
|
|
140
math.go
140
math.go
|
@ -1,140 +0,0 @@
|
||||||
package canvas
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vec [2]float64
|
|
||||||
|
|
||||||
func (v vec) String() string {
|
|
||||||
return fmt.Sprintf("[%f,%f]", v[0], v[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) add(v2 vec) vec {
|
|
||||||
return vec{v[0] + v2[0], v[1] + v2[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) sub(v2 vec) vec {
|
|
||||||
return vec{v[0] - v2[0], v[1] - v2[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) mul(v2 vec) vec {
|
|
||||||
return vec{v[0] * v2[0], v[1] * v2[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) mulf(f float64) vec {
|
|
||||||
return vec{v[0] * f, v[1] * f}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) mulMat(m mat) vec {
|
|
||||||
return vec{
|
|
||||||
m[0]*v[0] + m[2]*v[1] + m[4],
|
|
||||||
m[1]*v[0] + m[3]*v[1] + m[5]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) mulMat2(m mat2) vec {
|
|
||||||
return vec{m[0]*v[0] + m[2]*v[1], m[1]*v[0] + m[3]*v[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) div(v2 vec) vec {
|
|
||||||
return vec{v[0] / v2[0], v[1] / v2[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) divf(f float64) vec {
|
|
||||||
return vec{v[0] / f, v[1] / f}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) dot(v2 vec) float64 {
|
|
||||||
return v[0]*v2[0] + v[1]*v2[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) len() float64 {
|
|
||||||
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) lenSqr() float64 {
|
|
||||||
return v[0]*v[0] + v[1]*v[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) norm() vec {
|
|
||||||
return v.mulf(1.0 / v.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) atan2() float64 {
|
|
||||||
return math.Atan2(v[1], v[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) angle() float64 {
|
|
||||||
return math.Pi*0.5 - math.Atan2(v[1], v[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v vec) angleTo(v2 vec) float64 {
|
|
||||||
return math.Acos(v.norm().dot(v2.norm()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type mat [6]float64
|
|
||||||
|
|
||||||
func (m *mat) String() string {
|
|
||||||
return fmt.Sprintf("[%f,%f,0,\n %f,%f,0,\n %f,%f,1,]", m[0], m[2], m[4], m[1], m[3], m[5])
|
|
||||||
}
|
|
||||||
|
|
||||||
var matIdentity = mat{
|
|
||||||
1, 0,
|
|
||||||
0, 1,
|
|
||||||
0, 0}
|
|
||||||
|
|
||||||
func matTranslate(v vec) mat {
|
|
||||||
return mat{
|
|
||||||
1, 0,
|
|
||||||
0, 1,
|
|
||||||
v[0], v[1]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matScale(v vec) mat {
|
|
||||||
return mat{
|
|
||||||
v[0], 0,
|
|
||||||
0, v[1],
|
|
||||||
0, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matRotate(radians float64) mat {
|
|
||||||
s, c := math.Sincos(radians)
|
|
||||||
return mat{
|
|
||||||
c, s,
|
|
||||||
-s, c,
|
|
||||||
0, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mat) mul(m2 mat) mat {
|
|
||||||
return mat{
|
|
||||||
m[0]*m2[0] + m[1]*m2[2],
|
|
||||||
m[0]*m2[1] + m[1]*m2[3],
|
|
||||||
m[2]*m2[0] + m[3]*m2[2],
|
|
||||||
m[2]*m2[1] + m[3]*m2[3],
|
|
||||||
m[4]*m2[0] + m[5]*m2[2] + m2[4],
|
|
||||||
m[4]*m2[1] + m[5]*m2[3] + m2[5]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mat) invert() mat {
|
|
||||||
identity := 1.0 / (m[0]*m[3] - m[2]*m[1])
|
|
||||||
|
|
||||||
return mat{
|
|
||||||
m[3] * identity,
|
|
||||||
-m[1] * identity,
|
|
||||||
-m[2] * identity,
|
|
||||||
m[0] * identity,
|
|
||||||
(m[2]*m[5] - m[3]*m[4]) * identity,
|
|
||||||
(m[1]*m[4] - m[0]*m[5]) * identity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mat2 [4]float64
|
|
||||||
|
|
||||||
func (m mat) mat2() mat2 {
|
|
||||||
return mat2{m[0], m[1], m[2], m[3]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mat2) String() string {
|
|
||||||
return fmt.Sprintf("[%f,%f,\n %f,%f]", m[0], m[2], m[1], m[3])
|
|
||||||
}
|
|
132
path2d.go
132
path2d.go
|
@ -2,6 +2,8 @@ package canvas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Path2D is a type that holds a predefined path which can be drawn
|
// Path2D is a type that holds a predefined path which can be drawn
|
||||||
|
@ -9,13 +11,13 @@ import (
|
||||||
type Path2D struct {
|
type Path2D struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
p []pathPoint
|
p []pathPoint
|
||||||
move vec
|
move backendbase.Vec
|
||||||
cwSum float64
|
cwSum float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathPoint struct {
|
type pathPoint struct {
|
||||||
pos vec
|
pos backendbase.Vec
|
||||||
next vec
|
next backendbase.Vec
|
||||||
flags pathPointFlag
|
flags pathPointFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +42,12 @@ func (cv *Canvas) NewPath2D() *Path2D {
|
||||||
|
|
||||||
// MoveTo (see equivalent function on canvas type)
|
// MoveTo (see equivalent function on canvas type)
|
||||||
func (p *Path2D) MoveTo(x, y float64) {
|
func (p *Path2D) MoveTo(x, y float64) {
|
||||||
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
|
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.p = append(p.p, pathPoint{pos: vec{x, y}, flags: pathMove | pathIsConvex})
|
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}, flags: pathMove | pathIsConvex})
|
||||||
p.cwSum = 0
|
p.cwSum = 0
|
||||||
p.move = vec{x, y}
|
p.move = backendbase.Vec{x, y}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineTo (see equivalent function on canvas type)
|
// LineTo (see equivalent function on canvas type)
|
||||||
|
@ -55,7 +57,7 @@ func (p *Path2D) LineTo(x, y float64) {
|
||||||
|
|
||||||
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
count := len(p.p)
|
count := len(p.p)
|
||||||
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
|
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
|
@ -63,9 +65,9 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prev := &p.p[count-1]
|
prev := &p.p[count-1]
|
||||||
prev.next = vec{x, y}
|
prev.next = backendbase.Vec{x, y}
|
||||||
prev.flags |= pathAttach
|
prev.flags |= pathAttach
|
||||||
p.p = append(p.p, pathPoint{pos: vec{x, y}})
|
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}})
|
||||||
newp := &p.p[count]
|
newp := &p.p[count]
|
||||||
|
|
||||||
if prev.flags&pathIsConvex > 0 {
|
if prev.flags&pathIsConvex > 0 {
|
||||||
|
@ -84,9 +86,9 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
prev2 := &p.p[count-2]
|
prev2 := &p.p[count-2]
|
||||||
cw := (prev.flags & pathIsClockwise) > 0
|
cw := (prev.flags & pathIsClockwise) > 0
|
||||||
|
|
||||||
ln := prev.pos.sub(prev2.pos)
|
ln := prev.pos.Sub(prev2.pos)
|
||||||
lo := vec{ln[1], -ln[0]}
|
lo := backendbase.Vec{ln[1], -ln[0]}
|
||||||
dot := newp.pos.sub(prev2.pos).dot(lo)
|
dot := newp.pos.Sub(prev2.pos).Dot(lo)
|
||||||
|
|
||||||
if (cw && dot <= 0) || (!cw && dot >= 0) {
|
if (cw && dot <= 0) || (!cw && dot >= 0) {
|
||||||
newp.flags |= pathIsConvex
|
newp.flags |= pathIsConvex
|
||||||
|
@ -97,8 +99,8 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
newp.flags |= pathSelfIntersects
|
newp.flags |= pathSelfIntersects
|
||||||
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && checkSelfIntersection && !Performance.IgnoreSelfIntersections {
|
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && checkSelfIntersection && !Performance.IgnoreSelfIntersections {
|
||||||
cuts := false
|
cuts := false
|
||||||
var cutPoint vec
|
var cutPoint backendbase.Vec
|
||||||
b0, b1 := prev.pos, vec{x, y}
|
b0, b1 := prev.pos, backendbase.Vec{x, y}
|
||||||
for i := 1; i < count; i++ {
|
for i := 1; i < count; i++ {
|
||||||
a0, a1 := p.p[i-1].pos, p.p[i].pos
|
a0, a1 := p.p[i-1].pos, p.p[i].pos
|
||||||
var r1, r2 float64
|
var r1, r2 float64
|
||||||
|
@ -108,7 +110,7 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cuts && !isSamePoint(cutPoint, vec{x, y}, samePointTolerance) {
|
if cuts && !isSamePoint(cutPoint, backendbase.Vec{x, y}, samePointTolerance) {
|
||||||
newp.flags |= pathSelfIntersects
|
newp.flags |= pathSelfIntersects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,19 +118,19 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
|
|
||||||
// Arc (see equivalent function on canvas type)
|
// Arc (see equivalent function on canvas type)
|
||||||
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
||||||
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, matIdentity, true)
|
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m mat, ident bool) {
|
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m backendbase.Mat, ident bool) {
|
||||||
checkSelfIntersection := len(p.p) > 0
|
checkSelfIntersection := len(p.p) > 0
|
||||||
|
|
||||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||||
|
|
||||||
if endAngle == startAngle {
|
if endAngle == startAngle {
|
||||||
s, c := math.Sincos(endAngle)
|
s, c := math.Sincos(endAngle)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
|
|
||||||
|
@ -163,26 +165,26 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
|
||||||
if !anticlockwise {
|
if !anticlockwise {
|
||||||
for a := startAngle; a < endAngle; a += step {
|
for a := startAngle; a < endAngle; a += step {
|
||||||
s, c := math.Sincos(a)
|
s, c := math.Sincos(a)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for a := startAngle; a > endAngle; a -= step {
|
for a := startAngle; a > endAngle; a -= step {
|
||||||
s, c := math.Sincos(a)
|
s, c := math.Sincos(a)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s, c := math.Sincos(endAngle)
|
s, c := math.Sincos(endAngle)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
|
|
||||||
|
@ -193,34 +195,34 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
|
||||||
|
|
||||||
// ArcTo (see equivalent function on canvas type)
|
// ArcTo (see equivalent function on canvas type)
|
||||||
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
|
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
|
||||||
p.arcTo(x1, y1, x2, y2, radius, matIdentity, true)
|
p.arcTo(x1, y1, x2, y2, radius, backendbase.MatIdentity, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m mat, ident bool) {
|
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m backendbase.Mat, ident bool) {
|
||||||
if len(p.p) == 0 {
|
if len(p.p) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0, p1, p2 := p.p[len(p.p)-1].pos, vec{x1, y1}, vec{x2, y2}
|
p0, p1, p2 := p.p[len(p.p)-1].pos, backendbase.Vec{x1, y1}, backendbase.Vec{x2, y2}
|
||||||
if !ident {
|
if !ident {
|
||||||
p0 = p0.mulMat(m.invert())
|
p0 = p0.MulMat(m.Invert())
|
||||||
}
|
}
|
||||||
v0, v1 := p0.sub(p1).norm(), p2.sub(p1).norm()
|
v0, v1 := p0.Sub(p1).Norm(), p2.Sub(p1).Norm()
|
||||||
angle := math.Acos(v0.dot(v1))
|
angle := math.Acos(v0.Dot(v1))
|
||||||
// should be in the range [0-pi]. if parallel, use a straight line
|
// should be in the range [0-pi]. if parallel, use a straight line
|
||||||
if angle <= 0 || angle >= math.Pi {
|
if angle <= 0 || angle >= math.Pi {
|
||||||
p.LineTo(x2, y2)
|
p.LineTo(x2, y2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// cv0 and cv1 are vectors that point to the center of the circle
|
// cv0 and cv1 are vectors that point to the center of the circle
|
||||||
cv0 := vec{-v0[1], v0[0]}
|
cv0 := backendbase.Vec{-v0[1], v0[0]}
|
||||||
cv1 := vec{v1[1], -v1[0]}
|
cv1 := backendbase.Vec{v1[1], -v1[0]}
|
||||||
x := cv1.sub(cv0).div(v0.sub(v1))[0] * radius
|
x := cv1.Sub(cv0).Div(v0.Sub(v1))[0] * radius
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
cv0 = cv0.mulf(-1)
|
cv0 = cv0.Mulf(-1)
|
||||||
cv1 = cv1.mulf(-1)
|
cv1 = cv1.Mulf(-1)
|
||||||
}
|
}
|
||||||
center := p1.add(v0.mulf(math.Abs(x))).add(cv0.mulf(radius))
|
center := p1.Add(v0.Mulf(math.Abs(x))).Add(cv0.Mulf(radius))
|
||||||
a0, a1 := cv0.mulf(-1).atan2(), cv1.mulf(-1).atan2()
|
a0, a1 := cv0.Mulf(-1).Atan2(), cv1.Mulf(-1).Atan2()
|
||||||
if x > 0 {
|
if x > 0 {
|
||||||
if a1-a0 > 0 {
|
if a1-a0 > 0 {
|
||||||
a0 += math.Pi * 2
|
a0 += math.Pi * 2
|
||||||
|
@ -239,17 +241,17 @@ func (p *Path2D) QuadraticCurveTo(x1, y1, x2, y2 float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0 := p.p[len(p.p)-1].pos
|
p0 := p.p[len(p.p)-1].pos
|
||||||
p1 := vec{x1, y1}
|
p1 := backendbase.Vec{x1, y1}
|
||||||
p2 := vec{x2, y2}
|
p2 := backendbase.Vec{x2, y2}
|
||||||
v0 := p1.sub(p0)
|
v0 := p1.Sub(p0)
|
||||||
v1 := p2.sub(p1)
|
v1 := p2.Sub(p1)
|
||||||
|
|
||||||
const step = 0.01
|
const step = 0.01
|
||||||
|
|
||||||
for r := 0.0; r < 1; r += step {
|
for r := 0.0; r < 1; r += step {
|
||||||
i0 := v0.mulf(r).add(p0)
|
i0 := v0.Mulf(r).Add(p0)
|
||||||
i1 := v1.mulf(r).add(p1)
|
i1 := v1.Mulf(r).Add(p1)
|
||||||
pt := i1.sub(i0).mulf(r).add(i0)
|
pt := i1.Sub(i0).Mulf(r).Add(i0)
|
||||||
p.LineTo(pt[0], pt[1])
|
p.LineTo(pt[0], pt[1])
|
||||||
}
|
}
|
||||||
p.LineTo(x2, y2)
|
p.LineTo(x2, y2)
|
||||||
|
@ -261,24 +263,24 @@ func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0 := p.p[len(p.p)-1].pos
|
p0 := p.p[len(p.p)-1].pos
|
||||||
p1 := vec{x1, y1}
|
p1 := backendbase.Vec{x1, y1}
|
||||||
p2 := vec{x2, y2}
|
p2 := backendbase.Vec{x2, y2}
|
||||||
p3 := vec{x3, y3}
|
p3 := backendbase.Vec{x3, y3}
|
||||||
v0 := p1.sub(p0)
|
v0 := p1.Sub(p0)
|
||||||
v1 := p2.sub(p1)
|
v1 := p2.Sub(p1)
|
||||||
v2 := p3.sub(p2)
|
v2 := p3.Sub(p2)
|
||||||
|
|
||||||
const step = 0.01
|
const step = 0.01
|
||||||
|
|
||||||
for r := 0.0; r < 1; r += step {
|
for r := 0.0; r < 1; r += step {
|
||||||
i0 := v0.mulf(r).add(p0)
|
i0 := v0.Mulf(r).Add(p0)
|
||||||
i1 := v1.mulf(r).add(p1)
|
i1 := v1.Mulf(r).Add(p1)
|
||||||
i2 := v2.mulf(r).add(p2)
|
i2 := v2.Mulf(r).Add(p2)
|
||||||
iv0 := i1.sub(i0)
|
iv0 := i1.Sub(i0)
|
||||||
iv1 := i2.sub(i1)
|
iv1 := i2.Sub(i1)
|
||||||
j0 := iv0.mulf(r).add(i0)
|
j0 := iv0.Mulf(r).Add(i0)
|
||||||
j1 := iv1.mulf(r).add(i1)
|
j1 := iv1.Mulf(r).Add(i1)
|
||||||
pt := j1.sub(j0).mulf(r).add(j0)
|
pt := j1.Sub(j0).Mulf(r).Add(j0)
|
||||||
p.LineTo(pt[0], pt[1])
|
p.LineTo(pt[0], pt[1])
|
||||||
}
|
}
|
||||||
p.LineTo(x3, y3)
|
p.LineTo(x3, y3)
|
||||||
|
@ -434,7 +436,7 @@ func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
|
||||||
num := 0
|
num := 0
|
||||||
prev := sp[len(sp)-1].pos
|
prev := sp[len(sp)-1].pos
|
||||||
for _, pt := range sp {
|
for _, pt := range sp {
|
||||||
r, dir := pointIsRightOfLine(prev, pt.pos, vec{x, y})
|
r, dir := pointIsRightOfLine(prev, pt.pos, backendbase.Vec{x, y})
|
||||||
prev = pt.pos
|
prev = pt.pos
|
||||||
if !r {
|
if !r {
|
||||||
continue
|
continue
|
||||||
|
@ -464,14 +466,14 @@ func (p *Path2D) IsPointInStroke(x, y float64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var triBuf [500][2]float64
|
var triBuf [500][2]float64
|
||||||
tris := p.cv.strokeTris(p, mat{}, false, triBuf[:0])
|
tris := p.cv.strokeTris(p, backendbase.Mat{}, false, triBuf[:0])
|
||||||
|
|
||||||
pt := vec{x, y}
|
pt := backendbase.Vec{x, y}
|
||||||
|
|
||||||
for i := 0; i < len(tris); i += 3 {
|
for i := 0; i < len(tris); i += 3 {
|
||||||
a := vec{tris[i][0], tris[i][1]}
|
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||||
b := vec{tris[i+1][0], tris[i+1][1]}
|
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||||
c := vec{tris[i+2][0], tris[i+2][1]}
|
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||||
if triangleContainsPoint(a, b, c, pt) {
|
if triangleContainsPoint(a, b, c, pt) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
184
paths.go
184
paths.go
|
@ -2,6 +2,8 @@ package canvas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeginPath clears the current path and starts a new one
|
// BeginPath clears the current path and starts a new one
|
||||||
|
@ -12,19 +14,19 @@ func (cv *Canvas) BeginPath() {
|
||||||
cv.path.p = cv.path.p[:0]
|
cv.path.p = cv.path.p[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSamePoint(a, b vec, maxDist float64) bool {
|
func isSamePoint(a, b backendbase.Vec, maxDist float64) bool {
|
||||||
return math.Abs(b[0]-a[0]) <= maxDist && math.Abs(b[1]-a[1]) <= maxDist
|
return math.Abs(b[0]-a[0]) <= maxDist && math.Abs(b[1]-a[1]) <= maxDist
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveTo adds a gap and moves the end of the path to x/y
|
// MoveTo adds a gap and moves the end of the path to x/y
|
||||||
func (cv *Canvas) MoveTo(x, y float64) {
|
func (cv *Canvas) MoveTo(x, y float64) {
|
||||||
tf := cv.tf(vec{x, y})
|
tf := cv.tf(backendbase.Vec{x, y})
|
||||||
cv.path.MoveTo(tf[0], tf[1])
|
cv.path.MoveTo(tf[0], tf[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineTo adds a line to the end of the path
|
// LineTo adds a line to the end of the path
|
||||||
func (cv *Canvas) LineTo(x, y float64) {
|
func (cv *Canvas) LineTo(x, y float64) {
|
||||||
tf := cv.tf(vec{x, y})
|
tf := cv.tf(backendbase.Vec{x, y})
|
||||||
cv.path.LineTo(tf[0], tf[1])
|
cv.path.LineTo(tf[0], tf[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ func (cv *Canvas) LineTo(x, y float64) {
|
||||||
// means that the line is added anticlockwise
|
// means that the line is added anticlockwise
|
||||||
func (cv *Canvas) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
func (cv *Canvas) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
||||||
ax, ay := math.Sincos(startAngle)
|
ax, ay := math.Sincos(startAngle)
|
||||||
startAngle2 := vec{ay, ax}.mulMat2(cv.state.transform.mat2()).atan2()
|
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2()
|
||||||
endAngle2 := startAngle2 + (endAngle - startAngle)
|
endAngle2 := startAngle2 + (endAngle - startAngle)
|
||||||
cv.path.arc(x, y, radius, startAngle2, endAngle2, anticlockwise, cv.state.transform, false)
|
cv.path.arc(x, y, radius, startAngle2, endAngle2, anticlockwise, cv.state.transform, false)
|
||||||
}
|
}
|
||||||
|
@ -49,17 +51,17 @@ func (cv *Canvas) ArcTo(x1, y1, x2, y2, radius float64) {
|
||||||
// QuadraticCurveTo adds a quadratic curve to the path. It uses the current end
|
// QuadraticCurveTo adds a quadratic curve to the path. It uses the current end
|
||||||
// point of the path, x1/y1 defines the curve, and x2/y2 is the end point
|
// point of the path, x1/y1 defines the curve, and x2/y2 is the end point
|
||||||
func (cv *Canvas) QuadraticCurveTo(x1, y1, x2, y2 float64) {
|
func (cv *Canvas) QuadraticCurveTo(x1, y1, x2, y2 float64) {
|
||||||
tf1 := cv.tf(vec{x1, y1})
|
tf1 := cv.tf(backendbase.Vec{x1, y1})
|
||||||
tf2 := cv.tf(vec{x2, y2})
|
tf2 := cv.tf(backendbase.Vec{x2, y2})
|
||||||
cv.path.QuadraticCurveTo(tf1[0], tf1[1], tf2[0], tf2[1])
|
cv.path.QuadraticCurveTo(tf1[0], tf1[1], tf2[0], tf2[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// BezierCurveTo adds a bezier curve to the path. It uses the current end point
|
// BezierCurveTo adds a bezier curve to the path. It uses the current end point
|
||||||
// of the path, x1/y1 and x2/y2 define the curve, and x3/y3 is the end point
|
// of the path, x1/y1 and x2/y2 define the curve, and x3/y3 is the end point
|
||||||
func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||||
tf1 := cv.tf(vec{x1, y1})
|
tf1 := cv.tf(backendbase.Vec{x1, y1})
|
||||||
tf2 := cv.tf(vec{x2, y2})
|
tf2 := cv.tf(backendbase.Vec{x2, y2})
|
||||||
tf3 := cv.tf(vec{x3, y3})
|
tf3 := cv.tf(backendbase.Vec{x3, y3})
|
||||||
cv.path.BezierCurveTo(tf1[0], tf1[1], tf2[0], tf2[1], tf3[0], tf3[1])
|
cv.path.BezierCurveTo(tf1[0], tf1[1], tf2[0], tf2[1], tf3[0], tf3[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +71,9 @@ func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||||
// are angles in radians, and anticlockwise means that the line is added
|
// are angles in radians, and anticlockwise means that the line is added
|
||||||
// anticlockwise
|
// anticlockwise
|
||||||
func (cv *Canvas) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
|
func (cv *Canvas) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
|
||||||
tf := cv.tf(vec{x, y})
|
tf := cv.tf(backendbase.Vec{x, y})
|
||||||
ax, ay := math.Sincos(startAngle)
|
ax, ay := math.Sincos(startAngle)
|
||||||
startAngle2 := vec{ay, ax}.mulMat2(cv.state.transform.mat2()).atan2()
|
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2()
|
||||||
endAngle2 := startAngle2 + (endAngle - startAngle)
|
endAngle2 := startAngle2 + (endAngle - startAngle)
|
||||||
cv.path.Ellipse(tf[0], tf[1], radiusX, radiusY, rotation, startAngle2, endAngle2, anticlockwise)
|
cv.path.Ellipse(tf[0], tf[1], radiusX, radiusY, rotation, startAngle2, endAngle2, anticlockwise)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +86,7 @@ func (cv *Canvas) ClosePath() {
|
||||||
|
|
||||||
// Stroke uses the current StrokeStyle to draw the current path
|
// Stroke uses the current StrokeStyle to draw the current path
|
||||||
func (cv *Canvas) Stroke() {
|
func (cv *Canvas) Stroke() {
|
||||||
cv.strokePath(&cv.path, cv.state.transform.invert(), true)
|
cv.strokePath(&cv.path, cv.state.transform.Invert(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokePath uses the current StrokeStyle to draw the given path
|
// StrokePath uses the current StrokeStyle to draw the given path
|
||||||
|
@ -94,10 +96,10 @@ func (cv *Canvas) StrokePath(path *Path2D) {
|
||||||
p: make([]pathPoint, len(path.p)),
|
p: make([]pathPoint, len(path.p)),
|
||||||
}
|
}
|
||||||
copy(path2.p, path.p)
|
copy(path2.p, path.p)
|
||||||
cv.strokePath(&path2, mat{}, false)
|
cv.strokePath(&path2, backendbase.Mat{}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) strokePath(path *Path2D, inv mat, doInv bool) {
|
func (cv *Canvas) strokePath(path *Path2D, inv backendbase.Mat, doInv bool) {
|
||||||
if len(path.p) == 0 {
|
if len(path.p) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -111,7 +113,7 @@ func (cv *Canvas) strokePath(path *Path2D, inv mat, doInv bool) {
|
||||||
cv.b.Fill(&stl, tris, true)
|
cv.b.Fill(&stl, tris, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]float64) [][2]float64 {
|
func (cv *Canvas) strokeTris(path *Path2D, inv backendbase.Mat, doInv bool, target [][2]float64) [][2]float64 {
|
||||||
if len(path.p) == 0 {
|
if len(path.p) == 0 {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
@ -126,8 +128,8 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, pt := range path.p {
|
for i, pt := range path.p {
|
||||||
pt.pos = pt.pos.mulMat(inv)
|
pt.pos = pt.pos.MulMat(inv)
|
||||||
pt.next = pt.next.mulMat(inv)
|
pt.next = pt.next.MulMat(inv)
|
||||||
pcopy.p[i] = pt
|
pcopy.p[i] = pt
|
||||||
}
|
}
|
||||||
path = &pcopy
|
path = &pcopy
|
||||||
|
@ -136,7 +138,7 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
|
||||||
dashedPath := cv.applyLineDash(path.p)
|
dashedPath := cv.applyLineDash(path.p)
|
||||||
|
|
||||||
start := true
|
start := true
|
||||||
var p0 vec
|
var p0 backendbase.Vec
|
||||||
for _, p := range dashedPath {
|
for _, p := range dashedPath {
|
||||||
if p.flags&pathMove != 0 {
|
if p.flags&pathMove != 0 {
|
||||||
p0 = p.pos
|
p0 = p.pos
|
||||||
|
@ -145,22 +147,22 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
|
||||||
}
|
}
|
||||||
p1 := p.pos
|
p1 := p.pos
|
||||||
|
|
||||||
v0 := p1.sub(p0).norm()
|
v0 := p1.Sub(p0).Norm()
|
||||||
v1 := vec{v0[1], -v0[0]}.mulf(cv.state.lineWidth * 0.5)
|
v1 := backendbase.Vec{v0[1], -v0[0]}.Mulf(cv.state.lineWidth * 0.5)
|
||||||
v0 = v0.mulf(cv.state.lineWidth * 0.5)
|
v0 = v0.Mulf(cv.state.lineWidth * 0.5)
|
||||||
|
|
||||||
lp0 := p0.add(v1)
|
lp0 := p0.Add(v1)
|
||||||
lp1 := p1.add(v1)
|
lp1 := p1.Add(v1)
|
||||||
lp2 := p0.sub(v1)
|
lp2 := p0.Sub(v1)
|
||||||
lp3 := p1.sub(v1)
|
lp3 := p1.Sub(v1)
|
||||||
|
|
||||||
if start {
|
if start {
|
||||||
switch cv.state.lineCap {
|
switch cv.state.lineCap {
|
||||||
case Butt:
|
case Butt:
|
||||||
// no need to do anything
|
// no need to do anything
|
||||||
case Square:
|
case Square:
|
||||||
lp0 = lp0.sub(v0)
|
lp0 = lp0.Sub(v0)
|
||||||
lp2 = lp2.sub(v0)
|
lp2 = lp2.Sub(v0)
|
||||||
case Round:
|
case Round:
|
||||||
target = cv.addCircleTris(p0, cv.state.lineWidth*0.5, target)
|
target = cv.addCircleTris(p0, cv.state.lineWidth*0.5, target)
|
||||||
}
|
}
|
||||||
|
@ -171,8 +173,8 @@ func (cv *Canvas) strokeTris(path *Path2D, inv mat, doInv bool, target [][2]floa
|
||||||
case Butt:
|
case Butt:
|
||||||
// no need to do anything
|
// no need to do anything
|
||||||
case Square:
|
case Square:
|
||||||
lp1 = lp1.add(v0)
|
lp1 = lp1.Add(v0)
|
||||||
lp3 = lp3.add(v0)
|
lp3 = lp3.Add(v0)
|
||||||
case Round:
|
case Round:
|
||||||
target = cv.addCircleTris(p1, cv.state.lineWidth*0.5, target)
|
target = cv.addCircleTris(p1, cv.state.lineWidth*0.5, target)
|
||||||
}
|
}
|
||||||
|
@ -209,8 +211,8 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v := pp.pos.sub(lp.pos)
|
v := pp.pos.Sub(lp.pos)
|
||||||
vl := v.len()
|
vl := v.Len()
|
||||||
prev := ldo
|
prev := ldo
|
||||||
for vl > 0 {
|
for vl > 0 {
|
||||||
draw := ldp%2 == 0
|
draw := ldp%2 == 0
|
||||||
|
@ -220,7 +222,7 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
|
||||||
ldo = 0
|
ldo = 0
|
||||||
dl := cv.state.lineDash[ldp] - prev
|
dl := cv.state.lineDash[ldp] - prev
|
||||||
dist := dl / vl
|
dist := dl / vl
|
||||||
newp.pos = lp.pos.add(v.mulf(dist))
|
newp.pos = lp.pos.Add(v.Mulf(dist))
|
||||||
vl -= dl
|
vl -= dl
|
||||||
ldp++
|
ldp++
|
||||||
ldp %= len(cv.state.lineDash)
|
ldp %= len(cv.state.lineDash)
|
||||||
|
@ -239,7 +241,7 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
lp = newp
|
lp = newp
|
||||||
v = pp.pos.sub(lp.pos)
|
v = pp.pos.Sub(lp.pos)
|
||||||
}
|
}
|
||||||
lp = pp
|
lp = pp
|
||||||
}
|
}
|
||||||
|
@ -247,50 +249,50 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
|
||||||
return path2
|
return path2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]float64) [][2]float64 {
|
func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 backendbase.Vec, tris [][2]float64) [][2]float64 {
|
||||||
v2 := p1.sub(p2).norm()
|
v2 := p1.Sub(p2).Norm()
|
||||||
v3 := vec{v2[1], -v2[0]}.mulf(cv.state.lineWidth * 0.5)
|
v3 := backendbase.Vec{v2[1], -v2[0]}.Mulf(cv.state.lineWidth * 0.5)
|
||||||
|
|
||||||
switch cv.state.lineJoin {
|
switch cv.state.lineJoin {
|
||||||
case Miter:
|
case Miter:
|
||||||
l1p0 := p2.sub(v3)
|
l1p0 := p2.Sub(v3)
|
||||||
l1p1 := p1.sub(v3)
|
l1p1 := p1.Sub(v3)
|
||||||
l1p2 := p2.add(v3)
|
l1p2 := p2.Add(v3)
|
||||||
l1p3 := p1.add(v3)
|
l1p3 := p1.Add(v3)
|
||||||
|
|
||||||
var ip0, ip1 vec
|
var ip0, ip1 backendbase.Vec
|
||||||
if l0p1.sub(l1p1).lenSqr() < 0.000000001 {
|
if l0p1.Sub(l1p1).LenSqr() < 0.000000001 {
|
||||||
ip0 = l0p1.sub(l1p1).mulf(0.5).add(l1p1)
|
ip0 = l0p1.Sub(l1p1).Mulf(0.5).Add(l1p1)
|
||||||
} else {
|
} else {
|
||||||
var q float64
|
var q float64
|
||||||
ip0, _, q = lineIntersection(l0p0, l0p1, l1p1, l1p0)
|
ip0, _, q = lineIntersection(l0p0, l0p1, l1p1, l1p0)
|
||||||
if q >= 1 {
|
if q >= 1 {
|
||||||
ip0 = l0p1.add(l1p1).mulf(0.5)
|
ip0 = l0p1.Add(l1p1).Mulf(0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dist := ip0.sub(l0p1).lenSqr(); dist > cv.state.miterLimitSqr {
|
if dist := ip0.Sub(l0p1).LenSqr(); dist > cv.state.miterLimitSqr {
|
||||||
l1p1 := p1.sub(v3)
|
l1p1 := p1.Sub(v3)
|
||||||
l1p3 := p1.add(v3)
|
l1p3 := p1.Add(v3)
|
||||||
|
|
||||||
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
||||||
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
||||||
return tris
|
return tris
|
||||||
}
|
}
|
||||||
|
|
||||||
if l0p3.sub(l1p3).lenSqr() < 0.000000001 {
|
if l0p3.Sub(l1p3).LenSqr() < 0.000000001 {
|
||||||
ip1 = l0p3.sub(l1p3).mulf(0.5).add(l1p3)
|
ip1 = l0p3.Sub(l1p3).Mulf(0.5).Add(l1p3)
|
||||||
} else {
|
} else {
|
||||||
var q float64
|
var q float64
|
||||||
ip1, _, q = lineIntersection(l0p2, l0p3, l1p3, l1p2)
|
ip1, _, q = lineIntersection(l0p2, l0p3, l1p3, l1p2)
|
||||||
if q >= 1 {
|
if q >= 1 {
|
||||||
ip1 = l0p3.add(l1p3).mulf(0.5)
|
ip1 = l0p3.Add(l1p3).Mulf(0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dist := ip1.sub(l1p1).lenSqr(); dist > cv.state.miterLimitSqr {
|
if dist := ip1.Sub(l1p1).LenSqr(); dist > cv.state.miterLimitSqr {
|
||||||
l1p1 := p1.sub(v3)
|
l1p1 := p1.Sub(v3)
|
||||||
l1p3 := p1.add(v3)
|
l1p3 := p1.Add(v3)
|
||||||
|
|
||||||
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
||||||
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
||||||
|
@ -302,8 +304,8 @@ func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]fl
|
||||||
cv.tf(p1), cv.tf(l1p3), cv.tf(ip1),
|
cv.tf(p1), cv.tf(l1p3), cv.tf(ip1),
|
||||||
cv.tf(p1), cv.tf(ip1), cv.tf(l0p3))
|
cv.tf(p1), cv.tf(ip1), cv.tf(l0p3))
|
||||||
case Bevel:
|
case Bevel:
|
||||||
l1p1 := p1.sub(v3)
|
l1p1 := p1.Sub(v3)
|
||||||
l1p3 := p1.add(v3)
|
l1p3 := p1.Add(v3)
|
||||||
|
|
||||||
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
tris = append(tris, cv.tf(p1), cv.tf(l0p1), cv.tf(l1p1),
|
||||||
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
cv.tf(p1), cv.tf(l1p3), cv.tf(l0p3))
|
||||||
|
@ -314,7 +316,7 @@ func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris [][2]fl
|
||||||
return tris
|
return tris
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) addCircleTris(center vec, radius float64, tris [][2]float64) [][2]float64 {
|
func (cv *Canvas) addCircleTris(center backendbase.Vec, radius float64, tris [][2]float64) [][2]float64 {
|
||||||
step := 6 / radius
|
step := 6 / radius
|
||||||
if step > 0.8 {
|
if step > 0.8 {
|
||||||
step = 0.8
|
step = 0.8
|
||||||
|
@ -322,26 +324,26 @@ func (cv *Canvas) addCircleTris(center vec, radius float64, tris [][2]float64) [
|
||||||
step = 0.05
|
step = 0.05
|
||||||
}
|
}
|
||||||
centertf := cv.tf(center)
|
centertf := cv.tf(center)
|
||||||
p0 := cv.tf(vec{center[0], center[1] + radius})
|
p0 := cv.tf(backendbase.Vec{center[0], center[1] + radius})
|
||||||
for angle := step; angle <= math.Pi*2+step; angle += step {
|
for angle := step; angle <= math.Pi*2+step; angle += step {
|
||||||
s, c := math.Sincos(angle)
|
s, c := math.Sincos(angle)
|
||||||
p1 := cv.tf(vec{center[0] + s*radius, center[1] + c*radius})
|
p1 := cv.tf(backendbase.Vec{center[0] + s*radius, center[1] + c*radius})
|
||||||
tris = append(tris, centertf, p0, p1)
|
tris = append(tris, centertf, p0, p1)
|
||||||
p0 = p1
|
p0 = p1
|
||||||
}
|
}
|
||||||
return tris
|
return tris
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineIntersection(a0, a1, b0, b1 vec) (vec, float64, float64) {
|
func lineIntersection(a0, a1, b0, b1 backendbase.Vec) (backendbase.Vec, float64, float64) {
|
||||||
va := a1.sub(a0)
|
va := a1.Sub(a0)
|
||||||
vb := b1.sub(b0)
|
vb := b1.Sub(b0)
|
||||||
|
|
||||||
if (va[0] == 0 && vb[0] == 0) || (va[1] == 0 && vb[1] == 0) || (va[0] == 0 && va[1] == 0) || (vb[0] == 0 && vb[1] == 0) {
|
if (va[0] == 0 && vb[0] == 0) || (va[1] == 0 && vb[1] == 0) || (va[0] == 0 && va[1] == 0) || (vb[0] == 0 && vb[1] == 0) {
|
||||||
return vec{}, float64(math.Inf(1)), float64(math.Inf(1))
|
return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
|
||||||
}
|
}
|
||||||
d := va[1]*vb[0] - va[0]*vb[1]
|
d := va[1]*vb[0] - va[0]*vb[1]
|
||||||
if d == 0 {
|
if d == 0 {
|
||||||
return vec{}, float64(math.Inf(1)), float64(math.Inf(1))
|
return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
|
||||||
}
|
}
|
||||||
p := (vb[1]*(a0[0]-b0[0]) - a0[1]*vb[0] + b0[1]*vb[0]) / d
|
p := (vb[1]*(a0[0]-b0[0]) - a0[1]*vb[0] + b0[1]*vb[0]) / d
|
||||||
var q float64
|
var q float64
|
||||||
|
@ -351,21 +353,21 @@ func lineIntersection(a0, a1, b0, b1 vec) (vec, float64, float64) {
|
||||||
q = (a0[0] + p*va[0] - b0[0]) / vb[0]
|
q = (a0[0] + p*va[0] - b0[0]) / vb[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return a0.add(va.mulf(p)), p, q
|
return a0.Add(va.Mulf(p)), p, q
|
||||||
}
|
}
|
||||||
|
|
||||||
func linePointDistSqr(a, b, p vec) float64 {
|
func linePointDistSqr(a, b, p backendbase.Vec) float64 {
|
||||||
v := b.sub(a)
|
v := b.Sub(a)
|
||||||
vl := v.len()
|
vl := v.Len()
|
||||||
vn := v.divf(vl)
|
vn := v.Divf(vl)
|
||||||
d := p.sub(a).dot(vn)
|
d := p.Sub(a).Dot(vn)
|
||||||
c := a.add(vn.mulf(d))
|
c := a.Add(vn.Mulf(d))
|
||||||
return p.sub(c).lenSqr()
|
return p.Sub(c).LenSqr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill fills the current path with the current FillStyle
|
// Fill fills the current path with the current FillStyle
|
||||||
func (cv *Canvas) Fill() {
|
func (cv *Canvas) Fill() {
|
||||||
cv.fillPath(&cv.path, matIdentity)
|
cv.fillPath(&cv.path, backendbase.MatIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillPath fills the given path with the current FillStyle
|
// FillPath fills the given path with the current FillStyle
|
||||||
|
@ -374,7 +376,7 @@ func (cv *Canvas) FillPath(path *Path2D) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillPath fills the given path with the current FillStyle
|
// FillPath fills the given path with the current FillStyle
|
||||||
func (cv *Canvas) fillPath(path *Path2D, tf mat) {
|
func (cv *Canvas) fillPath(path *Path2D, tf backendbase.Mat) {
|
||||||
if len(path.p) < 3 {
|
if len(path.p) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -396,16 +398,16 @@ func (cv *Canvas) fillPath(path *Path2D, tf mat) {
|
||||||
cv.b.Fill(&stl, tris, false)
|
cv.b.Fill(&stl, tris, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]float64 {
|
func appendSubPathTriangles(tris [][2]float64, mat backendbase.Mat, path []pathPoint) [][2]float64 {
|
||||||
last := path[len(path)-1]
|
last := path[len(path)-1]
|
||||||
if last.flags&pathIsConvex != 0 {
|
if last.flags&pathIsConvex != 0 {
|
||||||
p0, p1 := path[0].pos.mulMat(mat), path[1].pos.mulMat(mat)
|
p0, p1 := path[0].pos.MulMat(mat), path[1].pos.MulMat(mat)
|
||||||
lastIdx := len(path)
|
lastIdx := len(path)
|
||||||
if path[0].pos == path[lastIdx-1].pos {
|
if path[0].pos == path[lastIdx-1].pos {
|
||||||
lastIdx--
|
lastIdx--
|
||||||
}
|
}
|
||||||
for i := 2; i < lastIdx; i++ {
|
for i := 2; i < lastIdx; i++ {
|
||||||
p2 := path[i].pos.mulMat(mat)
|
p2 := path[i].pos.MulMat(mat)
|
||||||
tris = append(tris, p0, p1, p2)
|
tris = append(tris, p0, p1, p2)
|
||||||
p1 = p2
|
p1 = p2
|
||||||
}
|
}
|
||||||
|
@ -423,10 +425,10 @@ func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]f
|
||||||
// Clip uses the current path to clip any further drawing. Use Save/Restore to
|
// Clip uses the current path to clip any further drawing. Use Save/Restore to
|
||||||
// remove the clipping again
|
// remove the clipping again
|
||||||
func (cv *Canvas) Clip() {
|
func (cv *Canvas) Clip() {
|
||||||
cv.clip(&cv.path, matIdentity)
|
cv.clip(&cv.path, backendbase.MatIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) clip(path *Path2D, tf mat) {
|
func (cv *Canvas) clip(path *Path2D, tf backendbase.Mat) {
|
||||||
if len(path.p) < 3 {
|
if len(path.p) < 3 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -476,10 +478,10 @@ func (cv *Canvas) Rect(x, y, w, h float64) {
|
||||||
|
|
||||||
// StrokeRect draws a rectangle using the current stroke style
|
// StrokeRect draws a rectangle using the current stroke style
|
||||||
func (cv *Canvas) StrokeRect(x, y, w, h float64) {
|
func (cv *Canvas) StrokeRect(x, y, w, h float64) {
|
||||||
v0 := vec{x, y}
|
v0 := backendbase.Vec{x, y}
|
||||||
v1 := vec{x + w, y}
|
v1 := backendbase.Vec{x + w, y}
|
||||||
v2 := vec{x + w, y + h}
|
v2 := backendbase.Vec{x + w, y + h}
|
||||||
v3 := vec{x, y + h}
|
v3 := backendbase.Vec{x, y + h}
|
||||||
var p [5]pathPoint
|
var p [5]pathPoint
|
||||||
p[0] = pathPoint{pos: v0, flags: pathMove | pathAttach, next: v1}
|
p[0] = pathPoint{pos: v0, flags: pathMove | pathAttach, next: v1}
|
||||||
p[1] = pathPoint{pos: v1, next: v2, flags: pathAttach}
|
p[1] = pathPoint{pos: v1, next: v2, flags: pathAttach}
|
||||||
|
@ -487,15 +489,15 @@ func (cv *Canvas) StrokeRect(x, y, w, h float64) {
|
||||||
p[3] = pathPoint{pos: v3, next: v0, flags: pathAttach}
|
p[3] = pathPoint{pos: v3, next: v0, flags: pathAttach}
|
||||||
p[4] = pathPoint{pos: v0, next: v1, flags: pathAttach}
|
p[4] = pathPoint{pos: v0, next: v1, flags: pathAttach}
|
||||||
path := Path2D{p: p[:]}
|
path := Path2D{p: p[:]}
|
||||||
cv.strokePath(&path, mat{}, false)
|
cv.strokePath(&path, backendbase.Mat{}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillRect fills a rectangle with the active fill style
|
// FillRect fills a rectangle with the active fill style
|
||||||
func (cv *Canvas) FillRect(x, y, w, h float64) {
|
func (cv *Canvas) FillRect(x, y, w, h float64) {
|
||||||
p0 := cv.tf(vec{x, y})
|
p0 := cv.tf(backendbase.Vec{x, y})
|
||||||
p1 := cv.tf(vec{x, y + h})
|
p1 := cv.tf(backendbase.Vec{x, y + h})
|
||||||
p2 := cv.tf(vec{x + w, y + h})
|
p2 := cv.tf(backendbase.Vec{x + w, y + h})
|
||||||
p3 := cv.tf(vec{x + w, y})
|
p3 := cv.tf(backendbase.Vec{x + w, y})
|
||||||
|
|
||||||
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
|
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
|
||||||
|
|
||||||
|
@ -507,10 +509,10 @@ func (cv *Canvas) FillRect(x, y, w, h float64) {
|
||||||
|
|
||||||
// ClearRect sets the color of the rectangle to transparent black
|
// ClearRect sets the color of the rectangle to transparent black
|
||||||
func (cv *Canvas) ClearRect(x, y, w, h float64) {
|
func (cv *Canvas) ClearRect(x, y, w, h float64) {
|
||||||
p0 := cv.tf(vec{x, y})
|
p0 := cv.tf(backendbase.Vec{x, y})
|
||||||
p1 := cv.tf(vec{x, y + h})
|
p1 := cv.tf(backendbase.Vec{x, y + h})
|
||||||
p2 := cv.tf(vec{x + w, y + h})
|
p2 := cv.tf(backendbase.Vec{x + w, y + h})
|
||||||
p3 := cv.tf(vec{x + w, y})
|
p3 := cv.tf(backendbase.Vec{x + w, y})
|
||||||
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
|
data := [4][2]float64{{p0[0], p0[1]}, {p1[0], p1[1]}, {p2[0], p2[1]}, {p3[0], p3[1]}}
|
||||||
|
|
||||||
cv.b.Clear(data)
|
cv.b.Clear(data)
|
||||||
|
|
25
text.go
25
text.go
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/golang/freetype"
|
"github.com/golang/freetype"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
@ -117,8 +118,8 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleX := vec{cv.state.transform[0], cv.state.transform[1]}.len()
|
scaleX := backendbase.Vec{cv.state.transform[0], cv.state.transform[1]}.Len()
|
||||||
scaleY := vec{cv.state.transform[2], cv.state.transform[3]}.len()
|
scaleY := backendbase.Vec{cv.state.transform[2], cv.state.transform[3]}.Len()
|
||||||
scale := (scaleX + scaleY) * 0.5
|
scale := (scaleX + scaleY) * 0.5
|
||||||
fontSize := fixed.Int26_6(math.Round(float64(cv.state.fontSize) * scale))
|
fontSize := fixed.Int26_6(math.Round(float64(cv.state.fontSize) * scale))
|
||||||
|
|
||||||
|
@ -213,10 +214,10 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
w, h := cv.b.Size()
|
w, h := cv.b.Size()
|
||||||
fw, fh := float64(w), float64(h)
|
fw, fh := float64(w), float64(h)
|
||||||
|
|
||||||
p0 := cv.tf(vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
||||||
p1 := cv.tf(vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
||||||
p2 := cv.tf(vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
p2 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
||||||
p3 := cv.tf(vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
p3 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
||||||
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
|
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
|
||||||
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
|
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
|
||||||
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
|
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
|
||||||
|
@ -299,10 +300,10 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
|
|
||||||
// render textImage to the screen
|
// render textImage to the screen
|
||||||
var pts [4][2]float64
|
var pts [4][2]float64
|
||||||
pts[0] = cv.tf(vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
|
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
|
||||||
pts[1] = cv.tf(vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
||||||
pts[2] = cv.tf(vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
pts[2] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
||||||
pts[3] = cv.tf(vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + y})
|
pts[3] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + y})
|
||||||
|
|
||||||
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
|
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
|
||||||
|
|
||||||
|
@ -345,7 +346,7 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cv.runePath(rn, vec{x, y})
|
cv.runePath(rn, backendbase.Vec{x, y})
|
||||||
|
|
||||||
x += float64(advance) / 64
|
x += float64(advance) / 64
|
||||||
}
|
}
|
||||||
|
@ -354,7 +355,7 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
|
||||||
cv.path = prevPath
|
cv.path = prevPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) runePath(rn rune, pos vec) {
|
func (cv *Canvas) runePath(rn rune, pos backendbase.Vec) {
|
||||||
gb := &truetype.GlyphBuf{}
|
gb := &truetype.GlyphBuf{}
|
||||||
gb.Load(cv.state.font.font, cv.state.fontSize, cv.state.font.font.Index(rn), font.HintingNone)
|
gb.Load(cv.state.font.font, cv.state.fontSize, cv.state.font.font.Index(rn), font.HintingNone)
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,13 @@ package canvas
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
const samePointTolerance = 1e-20
|
const samePointTolerance = 1e-20
|
||||||
|
|
||||||
func pointIsRightOfLine(a, b, p vec) (bool, bool) {
|
func pointIsRightOfLine(a, b, p backendbase.Vec) (bool, bool) {
|
||||||
if a[1] == b[1] {
|
if a[1] == b[1] {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
@ -19,13 +21,13 @@ func pointIsRightOfLine(a, b, p vec) (bool, bool) {
|
||||||
if p[1] < a[1] || p[1] >= b[1] {
|
if p[1] < a[1] || p[1] >= b[1] {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
v := b.sub(a)
|
v := b.Sub(a)
|
||||||
r := (p[1] - a[1]) / v[1]
|
r := (p[1] - a[1]) / v[1]
|
||||||
x := a[0] + r*v[0]
|
x := a[0] + r*v[0]
|
||||||
return p[0] > x, dir
|
return p[0] > x, dir
|
||||||
}
|
}
|
||||||
|
|
||||||
func triangleContainsPoint(a, b, c, p vec) bool {
|
func triangleContainsPoint(a, b, c, p backendbase.Vec) bool {
|
||||||
// if point is outside triangle bounds, return false
|
// if point is outside triangle bounds, return false
|
||||||
if p[0] < a[0] && p[0] < b[0] && p[0] < c[0] {
|
if p[0] < a[0] && p[0] < b[0] && p[0] < c[0] {
|
||||||
return false
|
return false
|
||||||
|
@ -56,12 +58,12 @@ func triangleContainsPoint(a, b, c, p vec) bool {
|
||||||
|
|
||||||
const parallelTolerance = 1e-10
|
const parallelTolerance = 1e-10
|
||||||
|
|
||||||
func parallel(a1, b1, a2, b2 vec) bool {
|
func parallel(a1, b1, a2, b2 backendbase.Vec) bool {
|
||||||
ang := b1.sub(a1).angleTo(b2.sub(a2))
|
ang := b1.Sub(a1).AngleTo(b2.Sub(a2))
|
||||||
return math.Abs(ang) < parallelTolerance || math.Abs(ang-math.Pi) < parallelTolerance
|
return math.Abs(ang) < parallelTolerance || math.Abs(ang-math.Pi) < parallelTolerance
|
||||||
}
|
}
|
||||||
|
|
||||||
func polygonContainsLine(polygon []vec, ia, ib int, a, b vec) bool {
|
func polygonContainsLine(polygon []backendbase.Vec, ia, ib int, a, b backendbase.Vec) bool {
|
||||||
for i := range polygon {
|
for i := range polygon {
|
||||||
if i == ia || i == ib {
|
if i == ia || i == ib {
|
||||||
continue
|
continue
|
||||||
|
@ -80,7 +82,7 @@ func polygonContainsLine(polygon []vec, ia, ib int, a, b vec) bool {
|
||||||
|
|
||||||
const onLineToleranceSqr = 1e-20
|
const onLineToleranceSqr = 1e-20
|
||||||
|
|
||||||
func polygonContainsPoint(polygon []vec, p vec) bool {
|
func polygonContainsPoint(polygon []backendbase.Vec, p backendbase.Vec) bool {
|
||||||
a := polygon[len(polygon)-1]
|
a := polygon[len(polygon)-1]
|
||||||
count := 0
|
count := 0
|
||||||
for _, b := range polygon {
|
for _, b := range polygon {
|
||||||
|
@ -95,15 +97,15 @@ func polygonContainsPoint(polygon []vec, p vec) bool {
|
||||||
return count%2 == 1
|
return count%2 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func triangulatePath(path []pathPoint, mat mat, target [][2]float64) [][2]float64 {
|
func triangulatePath(path []pathPoint, mat backendbase.Mat, target [][2]float64) [][2]float64 {
|
||||||
if path[0].pos == path[len(path)-1].pos {
|
if path[0].pos == path[len(path)-1].pos {
|
||||||
path = path[:len(path)-1]
|
path = path[:len(path)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf [500]vec
|
var buf [500]backendbase.Vec
|
||||||
polygon := buf[:0]
|
polygon := buf[:0]
|
||||||
for _, p := range path {
|
for _, p := range path {
|
||||||
polygon = append(polygon, p.pos.mulMat(mat))
|
polygon = append(polygon, p.pos.MulMat(mat))
|
||||||
}
|
}
|
||||||
|
|
||||||
for len(polygon) > 3 {
|
for len(polygon) > 3 {
|
||||||
|
@ -117,7 +119,7 @@ func triangulatePath(path []pathPoint, mat mat, target [][2]float64) [][2]float6
|
||||||
if isSamePoint(a, c, math.SmallestNonzeroFloat64) {
|
if isSamePoint(a, c, math.SmallestNonzeroFloat64) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if len(polygon) > 3 && !polygonContainsPoint(polygon, a.add(c).divf(2)) {
|
if len(polygon) > 3 && !polygonContainsPoint(polygon, a.Add(c).Divf(2)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !polygonContainsLine(polygon, i, ic, a, c) {
|
if !polygonContainsLine(polygon, i, ic, a, c) {
|
||||||
|
@ -156,7 +158,7 @@ type tessNet struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type tessVert struct {
|
type tessVert struct {
|
||||||
pos vec
|
pos backendbase.Vec
|
||||||
attached []int
|
attached []int
|
||||||
count int
|
count int
|
||||||
}
|
}
|
||||||
|
@ -183,7 +185,7 @@ func cutIntersections(path []pathPoint) tessNet {
|
||||||
type cut struct {
|
type cut struct {
|
||||||
from, to int
|
from, to int
|
||||||
ratio float64
|
ratio float64
|
||||||
point vec
|
point backendbase.Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
var cutBuf [50]cut
|
var cutBuf [50]cut
|
||||||
|
@ -296,8 +298,8 @@ func cutIntersections(path []pathPoint) tessNet {
|
||||||
func setPathLeftRightInside(net *tessNet) {
|
func setPathLeftRightInside(net *tessNet) {
|
||||||
for i, e1 := range net.edges {
|
for i, e1 := range net.edges {
|
||||||
a1, b1 := net.verts[e1.a], net.verts[e1.b]
|
a1, b1 := net.verts[e1.a], net.verts[e1.b]
|
||||||
diff := b1.pos.sub(a1.pos)
|
diff := b1.pos.Sub(a1.pos)
|
||||||
mid := a1.pos.add(diff.mulf(0.5))
|
mid := a1.pos.Add(diff.Mulf(0.5))
|
||||||
|
|
||||||
left, right := 0, 0
|
left, right := 0, 0
|
||||||
if math.Abs(diff[1]) > math.Abs(diff[0]) {
|
if math.Abs(diff[1]) > math.Abs(diff[0]) {
|
||||||
|
@ -315,7 +317,7 @@ func setPathLeftRightInside(net *tessNet) {
|
||||||
if mid[1] < a2[1] || mid[1] > b2[1] {
|
if mid[1] < a2[1] || mid[1] > b2[1] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v := b2.sub(a2)
|
v := b2.Sub(a2)
|
||||||
r := (mid[1] - a2[1]) / v[1]
|
r := (mid[1] - a2[1]) / v[1]
|
||||||
x := a2[0] + r*v[0]
|
x := a2[0] + r*v[0]
|
||||||
if mid[0] > x {
|
if mid[0] > x {
|
||||||
|
@ -342,7 +344,7 @@ func setPathLeftRightInside(net *tessNet) {
|
||||||
if mid[0] < a2[0] || mid[0] > b2[0] {
|
if mid[0] < a2[0] || mid[0] > b2[0] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v := b2.sub(a2)
|
v := b2.Sub(a2)
|
||||||
r := (mid[0] - a2[0]) / v[0]
|
r := (mid[0] - a2[0]) / v[0]
|
||||||
y := a2[1] + r*v[1]
|
y := a2[1] + r*v[1]
|
||||||
if mid[1] > y {
|
if mid[1] > y {
|
||||||
|
@ -404,7 +406,7 @@ func selfIntersectingPathParts(p []pathPoint, partFn func(sp []pathPoint) bool)
|
||||||
for limit := 0; limit < len(net.edges); limit++ {
|
for limit := 0; limit < len(net.edges); limit++ {
|
||||||
ecur := net.edges[from]
|
ecur := net.edges[from]
|
||||||
acur, bcur := net.verts[ecur.a], net.verts[ecur.b]
|
acur, bcur := net.verts[ecur.a], net.verts[ecur.b]
|
||||||
dir := bcur.pos.sub(acur.pos)
|
dir := bcur.pos.Sub(acur.pos)
|
||||||
dirAngle := math.Atan2(dir[1], dir[0])
|
dirAngle := math.Atan2(dir[1], dir[0])
|
||||||
minAngleDiff := math.Pi * 2
|
minAngleDiff := math.Pi * 2
|
||||||
var next, nextEdge int
|
var next, nextEdge int
|
||||||
|
@ -421,7 +423,7 @@ func selfIntersectingPathParts(p []pathPoint, partFn func(sp []pathPoint) bool)
|
||||||
if e.b == cur {
|
if e.b == cur {
|
||||||
na, nb = nb, na
|
na, nb = nb, na
|
||||||
}
|
}
|
||||||
ndir := nb.pos.sub(na.pos)
|
ndir := nb.pos.Sub(na.pos)
|
||||||
nextAngle := math.Atan2(ndir[1], ndir[0]) + math.Pi
|
nextAngle := math.Atan2(ndir[1], ndir[0]) + math.Pi
|
||||||
if nextAngle < dirAngle {
|
if nextAngle < dirAngle {
|
||||||
nextAngle += math.Pi * 2
|
nextAngle += math.Pi * 2
|
||||||
|
|
Loading…
Reference in a new issue