Files
HickLab3D_Dashboard/app/Services/ThreeMfTransform.php
T

110 lines
3.4 KiB
PHP

<?php
namespace App\Services;
use RuntimeException;
/**
* A 3MF `transform` attribute is 12 space-separated finite floats describing
* an affine map as three row basis vectors plus a translation: [Xx Xy Xz Yx
* Yy Yz Zx Zy Zz Tx Ty Tz]. A point is mapped as
* p' = p.x*X + p.y*Y + p.z*Z + T (row-vector convention). Composing two
* transforms (e.g. a build item transform applied on top of a component's
* own transform) must yield the same result as applying the inner transform
* first and then the outer one to the resulting point.
*
* @phpstan-type Matrix array{0:float,1:float,2:float,3:float,4:float,5:float,6:float,7:float,8:float,9:float,10:float,11:float}
*/
class ThreeMfTransform
{
/**
* @return Matrix
*/
public static function identity(): array
{
return [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0];
}
/**
* @return Matrix
*/
public static function parse(string $raw): array
{
$parts = preg_split('/\s+/', trim($raw)) ?: [];
if (count($parts) !== 12) {
throw new RuntimeException('3MF transform must have exactly 12 values.');
}
$values = [];
foreach ($parts as $part) {
if (! is_numeric($part)) {
throw new RuntimeException('3MF transform contains a non-numeric value.');
}
$value = (float) $part;
if (! is_finite($value)) {
throw new RuntimeException('3MF transform contains a non-finite value.');
}
$values[] = $value;
}
/** @var Matrix $values */
return $values;
}
/**
* Composes transforms so that apply(compose($outer, $inner), $p) ===
* apply($outer, apply($inner, $p)) for every point $p.
*
* @param Matrix $outer
* @param Matrix $inner
* @return Matrix
*/
public static function compose(array $outer, array $inner): array
{
$innerX = [$inner[0], $inner[1], $inner[2]];
$innerY = [$inner[3], $inner[4], $inner[5]];
$innerZ = [$inner[6], $inner[7], $inner[8]];
$innerT = [$inner[9], $inner[10], $inner[11]];
$newX = self::applyLinear($outer, $innerX);
$newY = self::applyLinear($outer, $innerY);
$newZ = self::applyLinear($outer, $innerZ);
$newT = self::apply($outer, $innerT);
return [
$newX[0], $newX[1], $newX[2],
$newY[0], $newY[1], $newY[2],
$newZ[0], $newZ[1], $newZ[2],
$newT[0], $newT[1], $newT[2],
];
}
/**
* @param Matrix $m
* @param array{0:float,1:float,2:float} $p
* @return array{0:float,1:float,2:float}
*/
public static function apply(array $m, array $p): array
{
$linear = self::applyLinear($m, $p);
return [$linear[0] + $m[9], $linear[1] + $m[10], $linear[2] + $m[11]];
}
/**
* Applies only the linear (basis) part of the transform, ignoring translation.
*
* @param Matrix $m
* @param array{0:float,1:float,2:float} $p
* @return array{0:float,1:float,2:float}
*/
private static function applyLinear(array $m, array $p): array
{
return [
$p[0] * $m[0] + $p[1] * $m[3] + $p[2] * $m[6],
$p[0] * $m[1] + $p[1] * $m[4] + $p[2] * $m[7],
$p[0] * $m[2] + $p[1] * $m[5] + $p[2] * $m[8],
];
}
}