Properly implement MorphShapes with changing move records

This commit is contained in:
DataHoarder 2022-01-08 18:51:57 +01:00
parent f7212e1d16
commit 32980ce1c0
5 changed files with 243 additions and 105 deletions

View file

@ -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;
}

View file

@ -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
View 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;
}
}

View file

@ -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) {

View file

@ -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: ????
}
}
}