mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
internal/stroke,op/clip: don't import op/clip from internal/stroke
To avoid an import cycle in a future change, internal/stroke can no longer import op/clip. Move required op/clip functionality to internal/stroke and duplicate the remaining types. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+7
-1
@@ -708,7 +708,13 @@ func encodePath(p *pathOp) encoder {
|
|||||||
verts := p.pathVerts
|
verts := p.pathVerts
|
||||||
if p.stroke.Width > 0 && !supportsStroke(p) {
|
if p.stroke.Width > 0 && !supportsStroke(p) {
|
||||||
quads := decodeToStrokeQuads(verts)
|
quads := decodeToStrokeQuads(verts)
|
||||||
quads = quads.Stroke(p.stroke, p.dashes)
|
ss := stroke.StrokeStyle{
|
||||||
|
Width: p.stroke.Width,
|
||||||
|
Miter: p.stroke.Miter,
|
||||||
|
Cap: stroke.StrokeCap(p.stroke.Cap),
|
||||||
|
Join: stroke.StrokeJoin(p.stroke.Join),
|
||||||
|
}
|
||||||
|
quads = quads.Stroke(ss, p.dashes)
|
||||||
for _, quad := range quads {
|
for _, quad := range quads {
|
||||||
q := quad.Quad
|
q := quad.Quad
|
||||||
enc.quad(q.From, q.Ctrl, q.To)
|
enc.quad(q.From, q.Ctrl, q.To)
|
||||||
|
|||||||
+9
-3
@@ -1349,7 +1349,7 @@ func (d *drawOps) writeVertCache(n int) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
|
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
|
||||||
func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, stroke clip.StrokeStyle, dashes stroke.DashOp) (verts []byte, bounds f32.Rectangle) {
|
func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str clip.StrokeStyle, dashes stroke.DashOp) (verts []byte, bounds f32.Rectangle) {
|
||||||
inf := float32(math.Inf(+1))
|
inf := float32(math.Inf(+1))
|
||||||
d.qs.bounds = f32.Rectangle{
|
d.qs.bounds = f32.Rectangle{
|
||||||
Min: f32.Point{X: inf, Y: inf},
|
Min: f32.Point{X: inf, Y: inf},
|
||||||
@@ -1359,10 +1359,16 @@ func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, str
|
|||||||
startLength := len(d.vertCache)
|
startLength := len(d.vertCache)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case stroke.Width > 0:
|
case str.Width > 0:
|
||||||
// Stroke path.
|
// Stroke path.
|
||||||
quads := decodeToStrokeQuads(pathData)
|
quads := decodeToStrokeQuads(pathData)
|
||||||
quads = quads.Stroke(stroke, dashes)
|
ss := stroke.StrokeStyle{
|
||||||
|
Width: str.Width,
|
||||||
|
Miter: str.Miter,
|
||||||
|
Cap: stroke.StrokeCap(str.Cap),
|
||||||
|
Join: stroke.StrokeJoin(str.Join),
|
||||||
|
}
|
||||||
|
quads = quads.Stroke(ss, dashes)
|
||||||
for _, quad := range quads {
|
for _, quad := range quads {
|
||||||
d.qs.contour = quad.Contour
|
d.qs.contour = quad.Contour
|
||||||
quad.Quad = quad.Quad.Transform(tr)
|
quad.Quad = quad.Quad.Transform(tr)
|
||||||
|
|||||||
+141
-35
@@ -29,10 +29,32 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/ops"
|
)
|
||||||
"gioui.org/internal/scene"
|
|
||||||
"gioui.org/op"
|
// The following are copies of types from op/clip to avoid a circular import of
|
||||||
"gioui.org/op/clip"
|
// that package.
|
||||||
|
// TODO: when the old renderer is gone, this package can be merged with
|
||||||
|
// op/clip, eliminating the duplicate types.
|
||||||
|
type StrokeStyle struct {
|
||||||
|
Width float32
|
||||||
|
Miter float32
|
||||||
|
Cap StrokeCap
|
||||||
|
Join StrokeJoin
|
||||||
|
}
|
||||||
|
|
||||||
|
type StrokeCap uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoundCap StrokeCap = iota
|
||||||
|
FlatCap
|
||||||
|
SquareCap
|
||||||
|
)
|
||||||
|
|
||||||
|
type StrokeJoin uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoundJoin StrokeJoin = iota
|
||||||
|
BevelJoin
|
||||||
)
|
)
|
||||||
|
|
||||||
// strokeTolerance is used to reconcile rounding errors arising
|
// strokeTolerance is used to reconcile rounding errors arising
|
||||||
@@ -90,22 +112,18 @@ func (qs *StrokeQuads) lineTo(pt f32.Point) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
func (qs *StrokeQuads) arc(f1, f2 f32.Point, angle float32) {
|
||||||
var (
|
const segments = 16
|
||||||
p clip.Path
|
pen := qs.pen()
|
||||||
o = new(op.Ops)
|
m := ArcTransform(pen, f1.Add(pen), f2.Add(pen), angle, segments)
|
||||||
)
|
for i := 0; i < segments; i++ {
|
||||||
p.Begin(o)
|
p0 := qs.pen()
|
||||||
p.Move(qs.pen())
|
p1 := m.Transform(p0)
|
||||||
beg := len(o.Data())
|
p2 := m.Transform(p1)
|
||||||
p.Arc(f1, f2, angle)
|
ctl := p1.Mul(2).Sub(p0.Add(p2).Mul(.5))
|
||||||
end := len(o.Data())
|
|
||||||
raw := o.Data()[beg:end]
|
|
||||||
|
|
||||||
for qi := 0; len(raw) >= (scene.CommandSize + 4); qi++ {
|
|
||||||
quad := decodeQuad(raw[4:])
|
|
||||||
raw = raw[scene.CommandSize+4:]
|
|
||||||
*qs = append(*qs, StrokeQuad{
|
*qs = append(*qs, StrokeQuad{
|
||||||
Quad: quad,
|
Quad: QuadSegment{
|
||||||
|
From: p0, Ctrl: ctl, To: p2,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +152,7 @@ func (qs StrokeQuads) split() []StrokeQuads {
|
|||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs StrokeQuads) Stroke(stroke clip.StrokeStyle, dashes DashOp) StrokeQuads {
|
func (qs StrokeQuads) Stroke(stroke StrokeStyle, dashes DashOp) StrokeQuads {
|
||||||
if !IsSolidLine(dashes) {
|
if !IsSolidLine(dashes) {
|
||||||
qs = qs.dash(dashes)
|
qs = qs.dash(dashes)
|
||||||
}
|
}
|
||||||
@@ -171,7 +189,7 @@ func (qs StrokeQuads) Stroke(stroke clip.StrokeStyle, dashes DashOp) StrokeQuads
|
|||||||
// offset returns the right-hand and left-hand sides of the path, offset by
|
// offset returns the right-hand and left-hand sides of the path, offset by
|
||||||
// the half-width hw.
|
// the half-width hw.
|
||||||
// The stroke handles how segments are joined and ends are capped.
|
// The stroke handles how segments are joined and ends are capped.
|
||||||
func (qs StrokeQuads) offset(hw float32, stroke clip.StrokeStyle) (rhs, lhs StrokeQuads) {
|
func (qs StrokeQuads) offset(hw float32, stroke StrokeStyle) (rhs, lhs StrokeQuads) {
|
||||||
var (
|
var (
|
||||||
states []strokeState
|
states []strokeState
|
||||||
beg = qs[0].Quad.From
|
beg = qs[0].Quad.From
|
||||||
@@ -317,12 +335,6 @@ func (q QuadSegment) Transform(t f32.Affine2D) QuadSegment {
|
|||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeQuad(d []byte) (q QuadSegment) {
|
|
||||||
cmd := ops.DecodeCommand(d)
|
|
||||||
q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// strokePathNorm returns the normal vector at t.
|
// strokePathNorm returns the normal vector at t.
|
||||||
func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
||||||
switch t {
|
switch t {
|
||||||
@@ -533,15 +545,15 @@ func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32
|
|||||||
|
|
||||||
// strokePathJoin joins the two paths rhs and lhs, according to the provided
|
// strokePathJoin joins the two paths rhs and lhs, according to the provided
|
||||||
// stroke operation.
|
// stroke operation.
|
||||||
func strokePathJoin(stroke clip.StrokeStyle, rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
func strokePathJoin(stroke StrokeStyle, rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
if stroke.Miter > 0 {
|
if stroke.Miter > 0 {
|
||||||
strokePathMiterJoin(stroke, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathMiterJoin(stroke, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch stroke.Join {
|
switch stroke.Join {
|
||||||
case clip.BevelJoin:
|
case BevelJoin:
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
case clip.RoundJoin:
|
case RoundJoin:
|
||||||
strokePathRoundJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathRoundJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
default:
|
default:
|
||||||
panic("impossible")
|
panic("impossible")
|
||||||
@@ -579,7 +591,7 @@ func strokePathRoundJoin(rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Po
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func strokePathMiterJoin(stroke clip.StrokeStyle, rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
func strokePathMiterJoin(stroke StrokeStyle, rhs, lhs *StrokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
if n0 == n1.Mul(-1) {
|
if n0 == n1.Mul(-1) {
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
return
|
return
|
||||||
@@ -621,13 +633,13 @@ func strokePathMiterJoin(stroke clip.StrokeStyle, rhs, lhs *StrokeQuads, hw floa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// strokePathCap caps the provided path qs, according to the provided stroke operation.
|
// strokePathCap caps the provided path qs, according to the provided stroke operation.
|
||||||
func strokePathCap(stroke clip.StrokeStyle, qs *StrokeQuads, hw float32, pivot, n0 f32.Point) {
|
func strokePathCap(stroke StrokeStyle, qs *StrokeQuads, hw float32, pivot, n0 f32.Point) {
|
||||||
switch stroke.Cap {
|
switch stroke.Cap {
|
||||||
case clip.FlatCap:
|
case FlatCap:
|
||||||
strokePathFlatCap(qs, hw, pivot, n0)
|
strokePathFlatCap(qs, hw, pivot, n0)
|
||||||
case clip.SquareCap:
|
case SquareCap:
|
||||||
strokePathSquareCap(qs, hw, pivot, n0)
|
strokePathSquareCap(qs, hw, pivot, n0)
|
||||||
case clip.RoundCap:
|
case RoundCap:
|
||||||
strokePathRoundCap(qs, hw, pivot, n0)
|
strokePathRoundCap(qs, hw, pivot, n0)
|
||||||
default:
|
default:
|
||||||
panic("impossible")
|
panic("impossible")
|
||||||
@@ -659,3 +671,97 @@ func strokePathRoundCap(qs *StrokeQuads, hw float32, pivot, n0 f32.Point) {
|
|||||||
c := pivot.Sub(qs.pen())
|
c := pivot.Sub(qs.pen())
|
||||||
qs.arc(c, c, math.Pi)
|
qs.arc(c, c, math.Pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArcTransform computes a transformation that can be used for generating quadratic bézier
|
||||||
|
// curve approximations for an arc.
|
||||||
|
//
|
||||||
|
// The math is extracted from the following paper:
|
||||||
|
// "Drawing an elliptical arc using polylines, quadratic or
|
||||||
|
// cubic Bezier curves", L. Maisonobe
|
||||||
|
// An electronic version may be found at:
|
||||||
|
// http://spaceroots.org/documents/ellipse/elliptical-arc.pdf
|
||||||
|
func ArcTransform(p, f1, f2 f32.Point, angle float32, segments int) f32.Affine2D {
|
||||||
|
c := f32.Point{
|
||||||
|
X: 0.5 * (f1.X + f2.X),
|
||||||
|
Y: 0.5 * (f1.Y + f2.Y),
|
||||||
|
}
|
||||||
|
|
||||||
|
// semi-major axis: 2a = |PF1| + |PF2|
|
||||||
|
a := 0.5 * (dist(f1, p) + dist(f2, p))
|
||||||
|
|
||||||
|
// semi-minor axis: c^2 = a^2+b^2 (c: focal distance)
|
||||||
|
f := dist(f1, c)
|
||||||
|
b := math.Sqrt(a*a - f*f)
|
||||||
|
|
||||||
|
var rx, ry, alpha, start float64
|
||||||
|
switch {
|
||||||
|
case a > b:
|
||||||
|
rx = a
|
||||||
|
ry = b
|
||||||
|
default:
|
||||||
|
rx = b
|
||||||
|
ry = a
|
||||||
|
}
|
||||||
|
|
||||||
|
var x float64
|
||||||
|
switch {
|
||||||
|
case f1 == c || f2 == c:
|
||||||
|
// degenerate case of a circle.
|
||||||
|
alpha = 0
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case f1.X > c.X:
|
||||||
|
x = float64(f1.X - c.X)
|
||||||
|
alpha = math.Acos(x / f)
|
||||||
|
case f1.X < c.X:
|
||||||
|
x = float64(f2.X - c.X)
|
||||||
|
alpha = math.Acos(x / f)
|
||||||
|
case f1.X == c.X:
|
||||||
|
// special case of a "vertical" ellipse.
|
||||||
|
alpha = math.Pi / 2
|
||||||
|
if f1.Y < c.Y {
|
||||||
|
alpha = -alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start = math.Acos(float64(p.X-c.X) / dist(c, p))
|
||||||
|
if c.Y > p.Y {
|
||||||
|
start = -start
|
||||||
|
}
|
||||||
|
start -= alpha
|
||||||
|
|
||||||
|
var (
|
||||||
|
θ = angle / float32(segments)
|
||||||
|
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
||||||
|
rot f32.Affine2D // rotation matrix for each segment
|
||||||
|
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
||||||
|
)
|
||||||
|
ref = ref.Offset(f32.Point{}.Sub(c))
|
||||||
|
ref = ref.Rotate(f32.Point{}, float32(-alpha))
|
||||||
|
ref = ref.Scale(f32.Point{}, f32.Point{
|
||||||
|
X: float32(1 / rx),
|
||||||
|
Y: float32(1 / ry),
|
||||||
|
})
|
||||||
|
inv = ref.Invert()
|
||||||
|
rot = rot.Rotate(f32.Point{}, float32(0.5*θ))
|
||||||
|
|
||||||
|
// Instead of invoking math.Sincos for every segment, compute a rotation
|
||||||
|
// matrix once and apply for each segment.
|
||||||
|
// Before applying the rotation matrix rot, transform the coordinates
|
||||||
|
// to a frame centered to the ellipse (and warped into a unit circle), then rotate.
|
||||||
|
// Finally, transform back into the original frame.
|
||||||
|
return inv.Mul(rot).Mul(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dist(p1, p2 f32.Point) float64 {
|
||||||
|
var (
|
||||||
|
x1 = float64(p1.X)
|
||||||
|
y1 = float64(p1.Y)
|
||||||
|
x2 = float64(p2.X)
|
||||||
|
y2 = float64(p2.Y)
|
||||||
|
dx = x2 - x1
|
||||||
|
dy = y2 - y1
|
||||||
|
)
|
||||||
|
return math.Hypot(dx, dy)
|
||||||
|
}
|
||||||
|
|||||||
+2
-95
@@ -11,6 +11,7 @@ import (
|
|||||||
"gioui.org/internal/opconst"
|
"gioui.org/internal/opconst"
|
||||||
"gioui.org/internal/ops"
|
"gioui.org/internal/ops"
|
||||||
"gioui.org/internal/scene"
|
"gioui.org/internal/scene"
|
||||||
|
"gioui.org/internal/stroke"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ func (p *Path) Arc(f1, f2 f32.Point, angle float32) {
|
|||||||
f1 = f1.Add(p.pen)
|
f1 = f1.Add(p.pen)
|
||||||
f2 = f2.Add(p.pen)
|
f2 = f2.Add(p.pen)
|
||||||
const segments = 16
|
const segments = 16
|
||||||
m := arcTransform(p.pen, f1, f2, angle, segments)
|
m := stroke.ArcTransform(p.pen, f1, f2, angle, segments)
|
||||||
|
|
||||||
for i := 0; i < segments; i++ {
|
for i := 0; i < segments; i++ {
|
||||||
p0 := p.pen
|
p0 := p.pen
|
||||||
@@ -185,100 +186,6 @@ func (p *Path) Arc(f1, f2 f32.Point, angle float32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dist(p1, p2 f32.Point) float64 {
|
|
||||||
var (
|
|
||||||
x1 = float64(p1.X)
|
|
||||||
y1 = float64(p1.Y)
|
|
||||||
x2 = float64(p2.X)
|
|
||||||
y2 = float64(p2.Y)
|
|
||||||
dx = x2 - x1
|
|
||||||
dy = y2 - y1
|
|
||||||
)
|
|
||||||
return math.Hypot(dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// arcTransform computes a transformation that can be used for generating quadratic bézier
|
|
||||||
// curve approximations for an arc.
|
|
||||||
//
|
|
||||||
// The math is extracted from the following paper:
|
|
||||||
// "Drawing an elliptical arc using polylines, quadratic or
|
|
||||||
// cubic Bezier curves", L. Maisonobe
|
|
||||||
// An electronic version may be found at:
|
|
||||||
// http://spaceroots.org/documents/ellipse/elliptical-arc.pdf
|
|
||||||
func arcTransform(p, f1, f2 f32.Point, angle float32, segments int) f32.Affine2D {
|
|
||||||
c := f32.Point{
|
|
||||||
X: 0.5 * (f1.X + f2.X),
|
|
||||||
Y: 0.5 * (f1.Y + f2.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
// semi-major axis: 2a = |PF1| + |PF2|
|
|
||||||
a := 0.5 * (dist(f1, p) + dist(f2, p))
|
|
||||||
|
|
||||||
// semi-minor axis: c^2 = a^2+b^2 (c: focal distance)
|
|
||||||
f := dist(f1, c)
|
|
||||||
b := math.Sqrt(a*a - f*f)
|
|
||||||
|
|
||||||
var rx, ry, alpha, start float64
|
|
||||||
switch {
|
|
||||||
case a > b:
|
|
||||||
rx = a
|
|
||||||
ry = b
|
|
||||||
default:
|
|
||||||
rx = b
|
|
||||||
ry = a
|
|
||||||
}
|
|
||||||
|
|
||||||
var x float64
|
|
||||||
switch {
|
|
||||||
case f1 == c || f2 == c:
|
|
||||||
// degenerate case of a circle.
|
|
||||||
alpha = 0
|
|
||||||
default:
|
|
||||||
switch {
|
|
||||||
case f1.X > c.X:
|
|
||||||
x = float64(f1.X - c.X)
|
|
||||||
alpha = math.Acos(x / f)
|
|
||||||
case f1.X < c.X:
|
|
||||||
x = float64(f2.X - c.X)
|
|
||||||
alpha = math.Acos(x / f)
|
|
||||||
case f1.X == c.X:
|
|
||||||
// special case of a "vertical" ellipse.
|
|
||||||
alpha = math.Pi / 2
|
|
||||||
if f1.Y < c.Y {
|
|
||||||
alpha = -alpha
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start = math.Acos(float64(p.X-c.X) / dist(c, p))
|
|
||||||
if c.Y > p.Y {
|
|
||||||
start = -start
|
|
||||||
}
|
|
||||||
start -= alpha
|
|
||||||
|
|
||||||
var (
|
|
||||||
θ = angle / float32(segments)
|
|
||||||
ref f32.Affine2D // transform from absolute frame to ellipse-based one
|
|
||||||
rot f32.Affine2D // rotation matrix for each segment
|
|
||||||
inv f32.Affine2D // transform from ellipse-based frame to absolute one
|
|
||||||
)
|
|
||||||
ref = ref.Offset(f32.Point{}.Sub(c))
|
|
||||||
ref = ref.Rotate(f32.Point{}, float32(-alpha))
|
|
||||||
ref = ref.Scale(f32.Point{}, f32.Point{
|
|
||||||
X: float32(1 / rx),
|
|
||||||
Y: float32(1 / ry),
|
|
||||||
})
|
|
||||||
inv = ref.Invert()
|
|
||||||
rot = rot.Rotate(f32.Point{}, float32(0.5*θ))
|
|
||||||
|
|
||||||
// Instead of invoking math.Sincos for every segment, compute a rotation
|
|
||||||
// matrix once and apply for each segment.
|
|
||||||
// Before applying the rotation matrix rot, transform the coordinates
|
|
||||||
// to a frame centered to the ellipse (and warped into a unit circle), then rotate.
|
|
||||||
// Finally, transform back into the original frame.
|
|
||||||
return inv.Mul(rot).Mul(ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cube records a cubic Bézier from the pen through
|
// Cube records a cubic Bézier from the pen through
|
||||||
// two control points ending in to.
|
// two control points ending in to.
|
||||||
func (p *Path) Cube(ctrl0, ctrl1, to f32.Point) {
|
func (p *Path) Cube(ctrl0, ctrl1, to f32.Point) {
|
||||||
|
|||||||
Reference in New Issue
Block a user