Properly implement MorphShapes with changing move records
This commit is contained in:
parent
f7212e1d16
commit
32980ce1c0
|
@ -15,9 +15,10 @@ class DrawPathList {
|
|||
return new DrawPathList(array_merge($this->commands, $b->commands));
|
||||
}
|
||||
|
||||
public static function fromArray(array $element, StyleList $currentStyles, ?array $otherElement = null): DrawPathList {
|
||||
public static function fromArray(array $firstElement, StyleList $currentStyles, ?array $secondElement = null, bool $flip = false): DrawPathList {
|
||||
|
||||
$converter = new ShapeConverter($element, $currentStyles, $otherElement);
|
||||
$converter = new ShapeConverter($firstElement, $currentStyles, $secondElement);
|
||||
$converter->convert($flip);
|
||||
|
||||
return $converter->commands;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,79 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Shape $a
|
||||
* @param Shape $b
|
||||
* @return \Iterator<RecordPair>|RecordPair[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function iterateShapes(Shape $a, Shape $b) : \Iterator {
|
||||
$recordsA = $a->getRecords();
|
||||
$recordsB = $b->getRecords();
|
||||
reset($recordsA);
|
||||
reset($recordsB);
|
||||
|
||||
|
||||
/** @var ?Record $prevA */
|
||||
$prevA = null;
|
||||
/** @var ?Record $prevB */
|
||||
$prevB = null;
|
||||
do{
|
||||
$a = current($recordsA);
|
||||
$b = current($recordsB);
|
||||
|
||||
if($a === false or $b === false){
|
||||
break;
|
||||
}
|
||||
|
||||
$advanceA = true;
|
||||
$advanceB = true;
|
||||
|
||||
if($prevA !== null and !$prevA->getEnd()->equals($a->getStart())){
|
||||
$advanceA = false;
|
||||
$a = new MoveRecord($a->getStart(), $prevA->getEnd());
|
||||
}
|
||||
if($prevB !== null and !$prevB->getEnd()->equals($b->getStart())){
|
||||
$advanceB = false;
|
||||
$b = new MoveRecord($b->getStart(), $prevB->getEnd());
|
||||
}
|
||||
|
||||
if($a instanceof $b){
|
||||
yield new RecordPair($a, $b);
|
||||
} elseif ($a instanceof LineRecord and $b instanceof QuadraticCurveRecord){
|
||||
yield new RecordPair($a = QuadraticCurveRecord::fromLineRecord($a), $b);
|
||||
} elseif ($a instanceof QuadraticCurveRecord and $b instanceof LineRecord){
|
||||
yield new RecordPair($a, $b = QuadraticCurveRecord::fromLineRecord($b));
|
||||
} elseif ($a instanceof MoveRecord and !($b instanceof MoveRecord)){
|
||||
yield new RecordPair($a, $b = new MoveRecord($b->getStart(), $b->getStart()));
|
||||
$advanceB = false;
|
||||
} elseif (!($a instanceof MoveRecord) and $b instanceof MoveRecord){
|
||||
yield new RecordPair($a = new MoveRecord($a->getStart(), $a->getStart()), $b);
|
||||
$advanceA = false;
|
||||
}else{
|
||||
throw new \Exception("Incompatible " . get_class($a) . " != " . get_class($b));
|
||||
}
|
||||
|
||||
if($advanceA){
|
||||
next($recordsA);
|
||||
}
|
||||
|
||||
if($advanceB){
|
||||
next($recordsB);
|
||||
}
|
||||
|
||||
$prevA = $a;
|
||||
$prevB = $b;
|
||||
}while(true);
|
||||
|
||||
if(!($a === false and $b === false)){
|
||||
//mismatched exit
|
||||
var_dump($a);
|
||||
var_dump($b);
|
||||
throw new \Exception("Incompatible a !== b on exit, both should be false");
|
||||
}
|
||||
}
|
||||
|
||||
public function getShapeList(?float $ratio): DrawPathList {
|
||||
//TODO: cache shapes by ratio
|
||||
//TOD: refactor this to use color transforms (and if able) matrix transforms
|
||||
|
@ -45,16 +118,12 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
$records2 = $c2->commands->getRecords();
|
||||
|
||||
$shape = new Shape();
|
||||
foreach ($records1 as $j => $r1){
|
||||
$r2 = $records2[$j];
|
||||
|
||||
//Convert line records that morph into/from curves
|
||||
if ($r1 instanceof LineRecord and $r2 instanceof QuadraticCurveRecord){
|
||||
$r1 = QuadraticCurveRecord::fromLineRecord($r1);
|
||||
}else if ($r2 instanceof LineRecord and $r1 instanceof QuadraticCurveRecord){
|
||||
$r2 = QuadraticCurveRecord::fromLineRecord($r2);
|
||||
}
|
||||
foreach (self::iterateShapes($c1->commands, $c2->commands) as $recordPair){
|
||||
$r1 = $recordPair->a;
|
||||
$r2 = $recordPair->b;
|
||||
|
||||
//No need to convert types!
|
||||
if($r1 instanceof LineRecord and $r2 instanceof LineRecord){
|
||||
$shape->addRecord(new LineRecord(self::lerpVector2($r1->to, $r2->to, $ratio), self::lerpVector2($r1->start, $r2->start, $ratio)));
|
||||
}else if($r1 instanceof QuadraticCurveRecord and $r2 instanceof QuadraticCurveRecord){
|
||||
|
@ -62,14 +131,13 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
}else if($r1 instanceof MoveRecord and $r2 instanceof MoveRecord){
|
||||
$shape->addRecord(new MoveRecord(self::lerpVector2($r1->to, $r2->to, $ratio), self::lerpVector2($r1->start, $r2->start, $ratio)));
|
||||
}else{
|
||||
var_dump($r1);
|
||||
var_dump($r2);
|
||||
var_dump($records1);
|
||||
var_dump($records2);
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//TODO: morph styles
|
||||
//TODO: morph styles properly
|
||||
if($c1->style instanceof FillStyleRecord and $c2->style instanceof FillStyleRecord){
|
||||
if($c1->style->fill instanceof Color){
|
||||
$drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(self::lerpColor($c1->style->fill, $c2->style->fill, $ratio)), $shape);
|
||||
|
@ -112,8 +180,8 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
static function fromArray(array $element): MorphShapeDefinition {
|
||||
$styles = MorphStyleList::fromArray($element);
|
||||
|
||||
$start = DrawPathList::fromArray($element["startEdges"], $styles->getStartStyleList());
|
||||
$end = DrawPathList::fromArray($element["endEdges"], $styles->getEndStyleList(), $element["startEdges"]);
|
||||
$start = DrawPathList::fromArray($element["startEdges"], $styles->getStartStyleList(), $element["endEdges"], false);
|
||||
$end = DrawPathList::fromArray($element["startEdges"], $styles->getStartStyleList(), $element["endEdges"], true);
|
||||
|
||||
return new MorphShapeDefinition($element["characterId"], Rectangle::fromArray($element["startBounds"]), Rectangle::fromArray($element["endBounds"]), $start, $end);
|
||||
}
|
||||
|
|
13
src/RecordPair.php
Normal file
13
src/RecordPair.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class RecordPair {
|
||||
public Record $a;
|
||||
public Record $b;
|
||||
|
||||
public function __construct(Record $a, Record $b){
|
||||
$this->a = $a;
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
|
@ -18,130 +18,184 @@ class ShapeConverter {
|
|||
|
||||
public DrawPathList $commands;
|
||||
|
||||
private bool $finished = false;
|
||||
|
||||
public function __construct(array $element, StyleList $currentStyles, ?array $otherElement = null) {
|
||||
private array $firstElement;
|
||||
private ?array $secondElement;
|
||||
|
||||
|
||||
public function __construct(array $firstElement, StyleList $currentStyles, ?array $secondElement = null) {
|
||||
$this->styles = $currentStyles;
|
||||
$this->position = new Vector2(0, 0);
|
||||
$this->fills = new PendingPathMap();
|
||||
$this->strokes = new PendingPathMap();
|
||||
|
||||
$this->firstElement = $firstElement;
|
||||
$this->secondElement = $secondElement;
|
||||
|
||||
$this->commands = new DrawPathList([]);
|
||||
}
|
||||
|
||||
$finished = false;
|
||||
public function convert(bool $flipElements = false){
|
||||
if($this->finished){
|
||||
return;
|
||||
}
|
||||
|
||||
reset($element);
|
||||
if($otherElement !== null){
|
||||
reset($otherElement);
|
||||
reset($this->firstElement);
|
||||
if($this->secondElement !== null){
|
||||
reset($this->secondElement);
|
||||
}
|
||||
|
||||
do{
|
||||
$otherNode = current($otherElement ?? []);
|
||||
if($otherNode === false){
|
||||
$otherNode = null;
|
||||
$b = current($this->secondElement ?? []);
|
||||
if($b === false){
|
||||
$b = null;
|
||||
}
|
||||
$node = current($element);
|
||||
if($node === false){
|
||||
$a = current($this->firstElement);
|
||||
if($a === false){
|
||||
if($b !== null){
|
||||
throw new \Exception("a finished, b did not");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$advanceNode = true;
|
||||
if($otherNode !== null){
|
||||
if($otherNode["type"] === "StyleChangeRecord"){
|
||||
if($node["type"] === "StyleChangeRecord"){
|
||||
//Inject style record entries
|
||||
foreach (["stateNewStyles", "stateLineStyle", "stateFillStyle0", "stateFillStyle1", "fillStyles", "lineStyles", "fillStyle0", "fillStyle1", "lineStyle"] as $k){
|
||||
if(isset($otherNode[$k])){
|
||||
$node[$k] = $otherNode[$k];
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//Inject style record
|
||||
$node = $otherNode;
|
||||
$node["stateMoveTo"] = 0;
|
||||
unset($node["moveDeltaX"]);
|
||||
unset($node["moveDeltaY"]);
|
||||
$advanceNode = false;
|
||||
}
|
||||
if($b === null){
|
||||
if($flipElements){
|
||||
throw new \Exception("b finished, a did not");
|
||||
}
|
||||
next($otherElement);
|
||||
$this->handleNode($a);
|
||||
next($this->firstElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
if($advanceNode){
|
||||
next($element);
|
||||
}
|
||||
|
||||
if($finished){
|
||||
var_dump($node);
|
||||
|
||||
if($this->finished){
|
||||
var_dump($a);
|
||||
var_dump($b);
|
||||
throw new \Exception("More paths after end");
|
||||
}
|
||||
if ($node["type"] === "StyleChangeRecord") {
|
||||
if (isset($node["moveDeltaX"])) {
|
||||
$move = MoveRecord::fromArray($node, $this->position);
|
||||
$this->position = $move->to;
|
||||
|
||||
$this->flush_paths();
|
||||
}
|
||||
|
||||
if (isset($node["fillStyles"]) or isset($node["lineStyles"])) {
|
||||
$this->flush_layer();
|
||||
|
||||
$this->styles = StyleList::fromArray($node);
|
||||
}
|
||||
|
||||
|
||||
if (isset($node["fillStyle1"])) {
|
||||
if ($this->fill_style1 !== null) {
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
}
|
||||
|
||||
$id = $node["fillStyle1"];
|
||||
|
||||
|
||||
$this->fill_style1 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if (isset($node["fillStyle0"])) {
|
||||
if ($this->fill_style0 !== null) {
|
||||
if (!$this->fill_style0->segment->is_empty()) {
|
||||
$this->fill_style0->flip();
|
||||
$this->fills->merge_path($this->fill_style0, true);
|
||||
if($a["type"] === $b["type"]){
|
||||
if($a["type"] === "StyleChangeRecord"){
|
||||
foreach (["stateNewStyles", "stateLineStyle", "stateFillStyle0", "stateFillStyle1", "fillStyles", "lineStyles", "fillStyle0", "fillStyle1", "lineStyle"] as $k){
|
||||
if(isset($a[$k])){
|
||||
$b[$k] = $a[$k];
|
||||
}
|
||||
}
|
||||
|
||||
$id = $node["fillStyle0"];
|
||||
$this->fill_style0 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if (isset($node["lineStyle"])) {
|
||||
if ($this->line_style !== null) {
|
||||
$this->strokes->merge_path($this->line_style, false);
|
||||
if(!$flipElements and $a["stateMoveTo"] === 0 and $b["stateMoveTo"] === 1){
|
||||
$a["stateMoveTo"] = $b["stateMoveTo"];
|
||||
$a["moveDeltaX"] = $this->position->x;
|
||||
$a["moveDeltaY"] = $this->position->y;
|
||||
}
|
||||
if($flipElements and $a["stateMoveTo"] === 1 and $b["stateMoveTo"] === 0){
|
||||
$b["stateMoveTo"] = $a["stateMoveTo"];
|
||||
$b["moveDeltaX"] = $this->position->x;
|
||||
$b["moveDeltaY"] = $this->position->y;
|
||||
}
|
||||
|
||||
$id = $node["lineStyle"];
|
||||
|
||||
|
||||
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
$this->handleNode($flipElements ? $b : $a);
|
||||
}
|
||||
next($this->firstElement);
|
||||
next($this->secondElement);
|
||||
}elseif ($a["type"] === "StyleChangeRecord"){
|
||||
$b = $a;
|
||||
if($b["stateMoveTo"] === 1){
|
||||
$b["moveDeltaX"] = $this->position->x;
|
||||
$b["moveDeltaY"] = $this->position->y;
|
||||
}
|
||||
} else if ($node["type"] === "StraightEdgeRecord") {
|
||||
$line = LineRecord::fromArray($node, $this->position);
|
||||
|
||||
$this->visit_point($line->to, false);
|
||||
$this->handleNode($flipElements ? $b : $a);
|
||||
next($this->firstElement);
|
||||
}elseif ($b["type"] === "StyleChangeRecord"){
|
||||
$a = $b;
|
||||
if($a["stateMoveTo"] === 1){
|
||||
$a["moveDeltaX"] = $this->position->x;
|
||||
$a["moveDeltaY"] = $this->position->y;
|
||||
}
|
||||
|
||||
$this->position = $line->to;
|
||||
} else if ($node["type"] === "CurvedEdgeRecord") {
|
||||
$curve = QuadraticCurveRecord::fromArray($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->control, true);
|
||||
$this->visit_point($curve->anchor, false);
|
||||
|
||||
$this->position = $curve->anchor;
|
||||
} else if ($node["type"] === "EndShapeRecord") {
|
||||
$finished = true;
|
||||
$this->handleNode($flipElements ? $b : $a);
|
||||
next($this->secondElement);
|
||||
}else{
|
||||
//Curve/Line records can differ
|
||||
$this->handleNode($flipElements ? $b : $a);
|
||||
next($this->firstElement);
|
||||
next($this->secondElement);
|
||||
}
|
||||
|
||||
}while(true);
|
||||
|
||||
$this->flush_layer();
|
||||
}
|
||||
|
||||
private function handleNode(array $node){
|
||||
if ($node["type"] === "StyleChangeRecord") {
|
||||
if (isset($node["moveDeltaX"])) {
|
||||
$move = MoveRecord::fromArray($node, $this->position);
|
||||
$this->position = $move->to;
|
||||
|
||||
$this->flush_paths();
|
||||
}
|
||||
|
||||
if (isset($node["fillStyles"]) or isset($node["lineStyles"])) {
|
||||
$this->flush_layer();
|
||||
|
||||
$this->styles = StyleList::fromArray($node);
|
||||
}
|
||||
|
||||
|
||||
if (isset($node["fillStyle1"])) {
|
||||
if ($this->fill_style1 !== null) {
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
}
|
||||
|
||||
$id = $node["fillStyle1"];
|
||||
|
||||
|
||||
$this->fill_style1 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if (isset($node["fillStyle0"])) {
|
||||
if ($this->fill_style0 !== null) {
|
||||
if (!$this->fill_style0->segment->is_empty()) {
|
||||
$this->fill_style0->flip();
|
||||
$this->fills->merge_path($this->fill_style0, true);
|
||||
}
|
||||
}
|
||||
|
||||
$id = $node["fillStyle0"];
|
||||
$this->fill_style0 = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
|
||||
if (isset($node["lineStyle"])) {
|
||||
if ($this->line_style !== null) {
|
||||
$this->strokes->merge_path($this->line_style, false);
|
||||
}
|
||||
|
||||
$id = $node["lineStyle"];
|
||||
|
||||
|
||||
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
} else if ($node["type"] === "StraightEdgeRecord") {
|
||||
$line = LineRecord::fromArray($node, $this->position);
|
||||
|
||||
$this->visit_point($line->to, false);
|
||||
|
||||
$this->position = $line->to;
|
||||
} else if ($node["type"] === "CurvedEdgeRecord") {
|
||||
$curve = QuadraticCurveRecord::fromArray($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->control, true);
|
||||
$this->visit_point($curve->anchor, false);
|
||||
|
||||
$this->position = $curve->anchor;
|
||||
} else if ($node["type"] === "EndShapeRecord") {
|
||||
$this->finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function visit_point(Vector2 $coordinate, bool $isBezierControlPoint) {
|
||||
$point = new VisitedPoint($coordinate, $isBezierControlPoint);
|
||||
if ($this->fill_style0 !== null) {
|
||||
|
|
|
@ -87,7 +87,7 @@ class ViewFrame {
|
|||
foreach ($frame->render($depth, $depthChain, $colorTransform, $matrixTransform)->getObjects() as $clipObject) {
|
||||
$clipShape = new ClipPath();
|
||||
foreach ($clipObject->drawPathList->commands as $p) {
|
||||
if ($p->style instanceof FillStyleRecord) { //Only clip with fills
|
||||
if ($p->style instanceof FillStyleRecord) { //Only clip with fills TODO: is this correct?
|
||||
$clipShape->addShape($p->commands);
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ class ViewFrame {
|
|||
|
||||
if ($clipPath !== null) {
|
||||
$clipPaths[$depth] = $clipPath;
|
||||
}else{
|
||||
unset($clipMap[$depth]); //TODO: ????
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue