mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:31:12 +08:00
1 parent
64f9ec4241
commit
0be872691d
76 files changed
+1926
-1307
No files matched your search
@@ -0,0 +1,164 @@
|
||||
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package geom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Transform 精确变换控制点和椭圆轴,退化弧沿投影极值分段保留折返
|
||||
// 入参: matrix 仿射变换
|
||||
// 返回: Path 独立路径, error 无效路径或数值溢出
|
||||
func (p Path) Transform(matrix Matrix) (Path, error) {
|
||||
if err := p.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, value := range matrix.Values() {
|
||||
if !finite(value) {
|
||||
return nil, fmt.Errorf("invalid geometry transform")
|
||||
}
|
||||
}
|
||||
matrixScale := math.Max(math.Max(math.Abs(matrix.a), math.Abs(matrix.b)), math.Max(math.Abs(matrix.c), math.Abs(matrix.d)))
|
||||
determinant := 0.0
|
||||
if matrixScale != 0 {
|
||||
determinant = (matrix.a/matrixScale)*(matrix.d/matrixScale) - (matrix.b/matrixScale)*(matrix.c/matrixScale)
|
||||
}
|
||||
transform := func(p Point) Point {
|
||||
x, y := matrix.Transform(p.X, p.Y)
|
||||
return Point{x, y}
|
||||
}
|
||||
var result Path
|
||||
if len(p) > 0 {
|
||||
result = make(Path, 0, len(p))
|
||||
}
|
||||
var current, start Point
|
||||
for _, s := range p {
|
||||
originalSegment := s
|
||||
original := s.End
|
||||
s.End = transform(s.End)
|
||||
switch s.Verb {
|
||||
case Move:
|
||||
start = original
|
||||
case Quad:
|
||||
s.Control1 = transform(s.Control1)
|
||||
case Cubic:
|
||||
s.Control1, s.Control2 = transform(s.Control1), transform(s.Control2)
|
||||
case ClosePath:
|
||||
original, s.End = start, transform(start)
|
||||
case Arc:
|
||||
a, err := centerArc(current, originalSegment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if current == original {
|
||||
current = original
|
||||
continue
|
||||
}
|
||||
if s.RadiusX == 0 || s.RadiusY == 0 {
|
||||
s = Segment{Verb: Line, End: s.End}
|
||||
} else {
|
||||
a.center = transform(a.center)
|
||||
a.u.X, a.u.Y = Vector(matrix, a.u.X, a.u.Y)
|
||||
a.v.X, a.v.Y = Vector(matrix, a.v.X, a.v.Y)
|
||||
scale := math.Max(math.Hypot(a.u.X, a.u.Y), math.Hypot(a.v.X, a.v.Y))
|
||||
if !finite(scale) {
|
||||
return nil, fmt.Errorf("arc transform overflow")
|
||||
}
|
||||
if scale == 0 {
|
||||
if matrixScale != 0 {
|
||||
return nil, fmt.Errorf("arc transform precision exhausted")
|
||||
}
|
||||
s = Segment{Verb: Line, End: s.End}
|
||||
} else {
|
||||
ux, uy, vx, vy := a.u.X/scale, a.u.Y/scale, a.v.X/scale, a.v.Y/scale
|
||||
det := ux*vy - uy*vx
|
||||
if determinant != 0 && det == 0 {
|
||||
return nil, fmt.Errorf("arc transform precision exhausted")
|
||||
}
|
||||
if determinant == 0 {
|
||||
fractions := []float64{}
|
||||
for _, angle := range []float64{math.Atan2(a.v.X, a.u.X), math.Atan2(a.v.Y, a.u.Y)} {
|
||||
for _, angle := range []float64{angle, angle + math.Pi} {
|
||||
if f := a.fraction(angle); f > 0 && f < 1 {
|
||||
fractions = append(fractions, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.Sort(fractions)
|
||||
for _, f := range slices.Compact(fractions) {
|
||||
result = append(result, Segment{Verb: Line, End: a.point(a.theta + f*a.delta)})
|
||||
}
|
||||
s = Segment{Verb: Line, End: s.End}
|
||||
} else {
|
||||
xx, yy, xy := ux*ux+vx*vx, uy*uy+vy*vy, ux*uy+vx*vy
|
||||
radius := math.Sqrt((xx + yy + math.Hypot(xx-yy, 2*xy)) / 2)
|
||||
s.RadiusX, s.RadiusY = scale*radius, scale*math.Abs(det)/radius
|
||||
if s.RadiusY == 0 {
|
||||
return nil, fmt.Errorf("arc transform precision exhausted")
|
||||
}
|
||||
s.Rotation = math.Atan2(2*xy, xx-yy) * 90 / math.Pi
|
||||
if det < 0 {
|
||||
s.Sweep = !s.Sweep
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = append(result, s)
|
||||
current = original
|
||||
}
|
||||
if err := result.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Validate 校验公共路径指令、子路径起点和有限坐标
|
||||
// 返回: error 无效路径
|
||||
func (p Path) Validate() error {
|
||||
started := false
|
||||
for _, s := range p {
|
||||
points := [3]Point{s.End, s.Control1, s.Control2}
|
||||
count := 1
|
||||
switch s.Verb {
|
||||
case Move:
|
||||
started = true
|
||||
case Line:
|
||||
case Quad:
|
||||
count = 2
|
||||
case Cubic:
|
||||
count = 3
|
||||
case Arc:
|
||||
if !finite(s.RadiusX) || !finite(s.RadiusY) || !finite(s.Rotation) || s.RadiusX < 0 || s.RadiusY < 0 {
|
||||
return fmt.Errorf("invalid arc parameters")
|
||||
}
|
||||
case ClosePath:
|
||||
count = 0
|
||||
default:
|
||||
return fmt.Errorf("unsupported geometry command %d", s.Verb)
|
||||
}
|
||||
if !started {
|
||||
return fmt.Errorf("geometry path must start with move")
|
||||
}
|
||||
for _, point := range points[:count] {
|
||||
if !finite(point.X) || !finite(point.Y) {
|
||||
return fmt.Errorf("non-finite geometry coordinate")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user