swf2ass/src/MorphShapeDefinition.php

174 lines
7.2 KiB
PHP

<?php
namespace swf2ass;
class MorphShapeDefinition implements ObjectDefinition {
public int $id;
public Rectangle $startBounds;
public Rectangle $endBounds;
public DrawPathList $startShapeList;
public DrawPathList $endShapeList;
public function __construct(int $id, Rectangle $startBounds, Rectangle $endBounds, DrawPathList $startShapeList, DrawPathList $endShapeList) {
$this->id = $id;
$this->startBounds = $startBounds;
$this->endBounds = $endBounds;
$this->startShapeList = $startShapeList;
$this->endShapeList = $endShapeList;
if(count($this->startShapeList->commands) !== count($this->endShapeList->commands)){
throw new \Exception("Morph command count is different: start " . count($this->startShapeList->commands) . " != end " . count($this->endShapeList->commands));
}
}
public function getObjectId(): int {
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
if($ratio === null or abs($ratio) < Constants::EPSILON){
return $this->startShapeList;
}
if(abs($ratio - 1.0) < Constants::EPSILON){
return $this->endShapeList;
}
$drawPathList = new DrawPathList();
foreach ($this->startShapeList->commands as $i => $c1){
$c2 = $this->endShapeList->commands[$i];
$records1 = $c1->commands->getRecords();
$records2 = $c2->commands->getRecords();
$shape = new Shape();
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(Utils::lerpVector2($r1->to, $r2->to, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio)));
}else if($r1 instanceof QuadraticCurveRecord and $r2 instanceof QuadraticCurveRecord){
$shape->addRecord(new QuadraticCurveRecord(Utils::lerpVector2($r1->control, $r2->control, $ratio), Utils::lerpVector2($r1->anchor, $r2->anchor, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio)));
}else if($r1 instanceof MoveRecord and $r2 instanceof MoveRecord){
$shape->addRecord(new MoveRecord(Utils::lerpVector2($r1->to, $r2->to, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio)));
}else{
var_dump($records1);
var_dump($records2);
throw new \Exception();
}
}
//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(Utils::lerpColor($c1->style->fill, $c2->style->fill, $ratio)), $shape);
}else if($c1->style->fill instanceof Gradient){
//TODO: proper gradients
$drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(Utils::lerpColor($c1->style->fill->getItems()[0]->color, $c2->style->fill->getItems()[0]->color, $ratio)), $shape);
}else{
var_dump($c1->style);
var_dump($c2->style);
throw new \Exception();
}
}else if($c1->style instanceof LineStyleRecord and $c2->style instanceof LineStyleRecord){
$drawPathList->commands[] = DrawPath::stroke(new LineStyleRecord(Utils::lerpInteger($c1->style->width, $c2->style->width, $ratio), Utils::lerpColor($c1->style->color, $c2->style->color, $ratio)), $shape);
}else{
var_dump($c1->style);
var_dump($c2->style);
throw new \Exception();
}
}
return $drawPathList;
}
static function fromArray(array $element): MorphShapeDefinition {
$styles = MorphStyleList::fromArray($element);
$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);
}
public function getSafeObject() : MorphShapeDefinition{
return $this;
}
}