195 lines
6.9 KiB
PHP
195 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace swf2ass;
|
|
|
|
/*
|
|
* Contains adapted code from http://antigrain.com/research/adaptive_bezier/index.html
|
|
* Anti-Grain Geometry (AGG) - Version 2.5
|
|
* A high quality rendering engine for C++
|
|
* Copyright (C) 2002-2006 Maxim Shemanarev
|
|
* Contact: mcseem@antigrain.com
|
|
* mcseemagg@yahoo.com
|
|
* http://antigrain.com
|
|
*
|
|
* AGG is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* AGG is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
class QuadraticCurveRecord implements Record {
|
|
|
|
private const RECURSION_LIMIT = 32;
|
|
private const CURVE_COLLINEARITY_EPSILON = PHP_FLOAT_EPSILON; //1E-30?
|
|
private const CURVE_ANGLE_TOLERANCE_EPSILON = 0.01;
|
|
|
|
public Vector2 $start;
|
|
public Vector2 $control;
|
|
public Vector2 $anchor;
|
|
|
|
public function __construct(Vector2 $control, Vector2 $anchor, Vector2 $start) {
|
|
$this->control = $control;
|
|
$this->anchor = $anchor;
|
|
$this->start = $start;
|
|
}
|
|
|
|
public function getStart(): Vector2 {
|
|
return $this->start;
|
|
}
|
|
|
|
public function getEnd(): Vector2 {
|
|
return $this->anchor;
|
|
}
|
|
|
|
public function reverse(): QuadraticCurveRecord {
|
|
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
|
|
}
|
|
|
|
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): QuadraticCurveRecord {
|
|
return new QuadraticCurveRecord($transform->applyToVector($this->control, $applyTranslation), $transform->applyToVector($this->anchor, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
|
}
|
|
|
|
public static function fromArray(array $element, Vector2 $cursor): QuadraticCurveRecord {
|
|
$control = $cursor->add(new Vector2($element["controlDeltaX"], $element["controlDeltaY"]));
|
|
$anchor = $control->add(new Vector2($element["anchorDeltaX"], $element["anchorDeltaY"]));
|
|
|
|
return new QuadraticCurveRecord($control, $anchor, $cursor);
|
|
}
|
|
|
|
public static function fromLineRecord(LineRecord $l): QuadraticCurveRecord {
|
|
$delta = $l->to->sub($l->start)->divide(2);
|
|
return new QuadraticCurveRecord(
|
|
$l->start->add($delta),
|
|
$l->start->add($delta->multiply(2)),
|
|
$l->start
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return LineRecord[]
|
|
*/
|
|
public function toLineRecords(float $scale = 1.0) : array{
|
|
$points = [];
|
|
$distance_tolerance_square = 0.5 / $scale;
|
|
$distance_tolerance_square *= $distance_tolerance_square;
|
|
|
|
self::recursive_bezier($points, 0.0, $distance_tolerance_square, $this->start, $this->control, $this->anchor, 0);
|
|
$points[] = $this->anchor;
|
|
|
|
$result = [];
|
|
$current = $this->start;
|
|
foreach ($points as $point){
|
|
$result[] = new LineRecord($point, $current);
|
|
$current = $point;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param Vector2[] $points
|
|
* @param float $angle_tolerance
|
|
* @param float $distance_tolerance_square
|
|
* @param Vector2 $v1
|
|
* @param Vector2 $v2
|
|
* @param Vector2 $v3
|
|
* @param int $level
|
|
*/
|
|
private static function recursive_bezier(array &$points, float $angle_tolerance, float $distance_tolerance_square, Vector2 $v1, Vector2 $v2, Vector2 $v3, int $level){
|
|
if($level > self::RECURSION_LIMIT){
|
|
return;
|
|
}
|
|
|
|
// Calculate all the mid-points of the line segments
|
|
//----------------------
|
|
$x12 = ( $v1->x + $v2->x) / 2;
|
|
$y12 = ( $v1->y + $v2->y) / 2;
|
|
$x23 = ( $v2->x + $v3->x) / 2;
|
|
$y23 = ( $v2->y + $v3->y) / 2;
|
|
$x123 = ( $x12 + $x23) / 2;
|
|
$y123 = ( $y12 + $y23) / 2;
|
|
|
|
$dx = $v3->x- $v1->x;
|
|
$dy = $v3->y- $v1->y;
|
|
$d = abs((( $v2->x - $v3->x) * $dy - ( $v2->y - $v3->y) * $dx));
|
|
|
|
if($d > self::CURVE_COLLINEARITY_EPSILON)
|
|
{
|
|
// Regular case
|
|
//-----------------
|
|
if($d * $d <= $distance_tolerance_square * ( $dx* $dx + $dy* $dy))
|
|
{
|
|
// If the curvature doesn't exceed the distance_tolerance value
|
|
// we tend to finish subdivisions.
|
|
//----------------------
|
|
if($angle_tolerance < self::CURVE_ANGLE_TOLERANCE_EPSILON)
|
|
{
|
|
$points[] = new Vector2($x123, $y123);
|
|
return;
|
|
}
|
|
|
|
// Angle & Cusp Condition
|
|
//----------------------
|
|
$da = abs(atan2( $v3->y - $v2->y, $v3->x - $v2->x) - atan2( $v2->y - $v1->y, $v2->x - $v1->x));
|
|
if($da >= M_PI){
|
|
$da = 2*M_PI - $da;
|
|
}
|
|
|
|
if($da < $angle_tolerance)
|
|
{
|
|
// Finally we can stop the recursion
|
|
//----------------------
|
|
$points[] = new Vector2($x123, $y123);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Collinear case
|
|
//------------------
|
|
$da = $dx*$dx + $dy*$dy;
|
|
if($da == 0)
|
|
{
|
|
$d = $v1->distanceSquare($v2);
|
|
}
|
|
else
|
|
{
|
|
$d = (($v2->x - $v1->x)*$dx + ($v2->y - $v1->y)*$dy) / $da;
|
|
if($d > 0 && $d < 1)
|
|
{
|
|
// Simple collinear case, 1---2---3
|
|
// We can leave just two endpoints
|
|
return;
|
|
}
|
|
if($d <= 0){
|
|
$d = $v2->distanceSquare($v1);
|
|
}else if($d >= 1){
|
|
$d = $v2->distanceSquare($v3);
|
|
}else{
|
|
$d = $v2->distanceSquare($v1->add(new Vector2($d * $dx, $d * $dy)));
|
|
}
|
|
}
|
|
if($d < $distance_tolerance_square)
|
|
{
|
|
$points[] = $v2;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Continue subdivision
|
|
//----------------------
|
|
self::recursive_bezier( $points, $angle_tolerance, $distance_tolerance_square, $v1, new Vector2($x12, $y12), new Vector2($x123, $y123), $level + 1);
|
|
self::recursive_bezier( $points, $angle_tolerance, $distance_tolerance_square, new Vector2($x123, $y123), new Vector2($x23, $y23), $v3, $level + 1);
|
|
}
|
|
|
|
public function equals(Record $other): bool {
|
|
return $other instanceof $this and $this->start->equals($other->start) and $this->control->equals($other->control) and $this->anchor->equals($other->anchor);
|
|
}
|
|
} |