mirror of
https://github.com/kristov/ldraw2stl.git
synced 2025-05-15 22:30:10 -07:00
Changes to handling BFC
There are two major changes here: 1) Previously I was inverting the winding of verticies only when calculating the normal. This was the wrong approach and I instead needed to invert the order of the verticies before calculating the normal. This is because most STL processing software (eg: Slic3r) will recalculate the normals based off the winding (correctly). 2) I *think* I am doing proper BFC handling now, although TBH the BFC handling logic in ldraw is super complex to me. See: https://www.ldraw.org/article/415 ("Rendering Engine Guidelines").
This commit is contained in:
parent
c05cf2c4da
commit
708cca9627
@ -14,12 +14,13 @@ sub new {
|
|||||||
invert => $args->{invert} // 0,
|
invert => $args->{invert} // 0,
|
||||||
debug => $args->{debug} // 0,
|
debug => $args->{debug} // 0,
|
||||||
d_indent => $args->{d_indent} // 0,
|
d_indent => $args->{d_indent} // 0,
|
||||||
|
_invertnext => 0,
|
||||||
}, $class);
|
}, $class);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _getter_setter {
|
sub _getter_setter {
|
||||||
my ($self, $key, $value) = @_;
|
my ($self, $key, $value) = @_;
|
||||||
if ($value) {
|
if (defined $value) {
|
||||||
$self->{$key} = $value;
|
$self->{$key} = $value;
|
||||||
}
|
}
|
||||||
return $self->{$key};
|
return $self->{$key};
|
||||||
@ -92,7 +93,6 @@ sub parse_line {
|
|||||||
}
|
}
|
||||||
elsif ( $line_type == 1 ) {
|
elsif ( $line_type == 1 ) {
|
||||||
$self->parse_sub_file_reference( $rest );
|
$self->parse_sub_file_reference( $rest );
|
||||||
$self->invert( 0 );
|
|
||||||
}
|
}
|
||||||
elsif ( $line_type == 2 ) {
|
elsif ( $line_type == 2 ) {
|
||||||
$self->parse_line_command( $rest );
|
$self->parse_line_command( $rest );
|
||||||
@ -127,10 +127,24 @@ sub handle_bfc_command {
|
|||||||
|
|
||||||
my $first = shift @items;
|
my $first = shift @items;
|
||||||
|
|
||||||
if ( $first && $first eq 'INVERTNEXT' ) {
|
if (!$first) {
|
||||||
$self->invert(1);
|
$self->DEBUG('META: invalid BFC');
|
||||||
$self->DEBUG('handle_bfc_command(): inverted model');
|
return;
|
||||||
}
|
}
|
||||||
|
if ($first eq 'INVERTNEXT') {
|
||||||
|
$self->{_invertnext} = 1;
|
||||||
|
$self->DEBUG('META: INVERTNEXT found while invert[%d]', $self->invert);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($first eq 'CERTIFY') {
|
||||||
|
if (!$items[0]) {
|
||||||
|
$self->DEBUG('META: CERTIFY with no winding - default CCW');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#$self->DEBUG('META: BFC CERTIFY %s', $items[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$self->DEBUG('META: Unknown BFC: %s', $items[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub parse_sub_file_reference {
|
sub parse_sub_file_reference {
|
||||||
@ -193,23 +207,34 @@ sub parse_sub_file_reference {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->DEBUG('parse_sub_file_reference(): parsing subfile "%s" with inverted: %d', $subpart_filename, $self->invert);
|
my $det = mat4determinant($mat);
|
||||||
|
my $invert = $self->invert;
|
||||||
|
$self->DEBUG('FILE: %s BEFORE det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
||||||
|
if ($det < 0) {
|
||||||
|
$invert = 1;
|
||||||
|
}
|
||||||
|
elsif ($self->{_invertnext}) {
|
||||||
|
$invert = $invert ? 0 : 1;
|
||||||
|
}
|
||||||
|
$self->DEBUG('FILE: %s AFTER det[%d], invert[%d] _invertnext[%d]', $subpart_filename, $det, $invert, $self->{_invertnext});
|
||||||
|
|
||||||
my $subparser = __PACKAGE__->new( {
|
my $subparser = __PACKAGE__->new( {
|
||||||
file => $subpart_filename,
|
file => $subpart_filename,
|
||||||
ldraw_path => $self->ldraw_path,
|
ldraw_path => $self->ldraw_path,
|
||||||
debug => $self->debug,
|
debug => $self->debug,
|
||||||
|
invert => $invert,
|
||||||
d_indent => $self->d_indent + 2,
|
d_indent => $self->d_indent + 2,
|
||||||
} );
|
} );
|
||||||
$subparser->parse;
|
$subparser->parse;
|
||||||
|
$self->{_invertnext} = 0;
|
||||||
|
|
||||||
for my $triangle ( @{ $subparser->{triangles} } ) {
|
for my $triangle ( @{ $subparser->{triangles} } ) {
|
||||||
for my $vec ( @{ $triangle } ) {
|
for my $vec ( @{ $triangle } ) {
|
||||||
my @new_vec = max4xv3( $mat, $vec );
|
my @new_vec = mat4xv3( $mat, $vec );
|
||||||
$vec->[0] = $new_vec[0];
|
$vec->[0] = $new_vec[0];
|
||||||
$vec->[1] = $new_vec[1];
|
$vec->[1] = $new_vec[1];
|
||||||
$vec->[2] = $new_vec[2];
|
$vec->[2] = $new_vec[2];
|
||||||
}
|
}
|
||||||
$triangle->[4] = [ $self->calc_surface_normal($triangle->[0], $triangle->[1], $triangle->[2]) ];
|
|
||||||
push @{ $self->{triangles} }, $triangle;
|
push @{ $self->{triangles} }, $triangle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,11 +248,20 @@ sub parse_triange_command {
|
|||||||
# 16 8.9 -10 58.73 6.36 -10 53.64 9 -10 55.5
|
# 16 8.9 -10 58.73 6.36 -10 53.64 9 -10 55.5
|
||||||
my @items = split( /\s+/, $rest );
|
my @items = split( /\s+/, $rest );
|
||||||
my $color = shift @items;
|
my $color = shift @items;
|
||||||
my $p1 = [ $items[0], $items[1], $items[2] ];
|
if ($self->invert) {
|
||||||
my $p2 = [ $items[3], $items[4], $items[5] ];
|
$self->_add_triangle([
|
||||||
my $p3 = [ $items[6], $items[7], $items[8] ];
|
[$items[0], $items[1], $items[2]],
|
||||||
my $n = [ $self->calc_surface_normal( $p1, $p2, $p3 ) ];
|
[$items[6], $items[7], $items[8]],
|
||||||
push @{ $self->{triangles} }, [ $p1, $p2, $p3, $n ];
|
[$items[3], $items[4], $items[5]],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->_add_triangle([
|
||||||
|
[$items[0], $items[1], $items[2]],
|
||||||
|
[$items[3], $items[4], $items[5]],
|
||||||
|
[$items[6], $items[7], $items[8]],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub parse_quadrilateral_command {
|
sub parse_quadrilateral_command {
|
||||||
@ -247,35 +281,36 @@ sub parse_quadrilateral_command {
|
|||||||
my $x4 = shift @items;
|
my $x4 = shift @items;
|
||||||
my $y4 = shift @items;
|
my $y4 = shift @items;
|
||||||
my $z4 = shift @items;
|
my $z4 = shift @items;
|
||||||
my $na = [ $self->calc_surface_normal( [ $x1, $y1, $z1 ], [ $x2, $y2, $z2 ], [ $x3, $y3, $z3 ] ) ];
|
if ($self->invert) {
|
||||||
my $nb = [ $self->calc_surface_normal( [ $x3, $y3, $z3 ], [ $x4, $y4, $z4 ], [ $x1, $y1, $z1 ] ) ];
|
$self->_add_triangle([
|
||||||
push @{ $self->{triangles} }, [
|
[$x1, $y1, $z1],
|
||||||
[ $x1, $y1, $z1 ],
|
[$x3, $y3, $z3],
|
||||||
[ $x2, $y2, $z2 ],
|
[$x2, $y2, $z2],
|
||||||
[ $x3, $y3, $z3 ],
|
]);
|
||||||
$na,
|
$self->_add_triangle([
|
||||||
];
|
[$x3, $y3, $z3],
|
||||||
push @{ $self->{triangles} }, [
|
[$x1, $y1, $z1],
|
||||||
[ $x3, $y3, $z3 ],
|
[$x4, $y4, $z4],
|
||||||
[ $x4, $y4, $z4 ],
|
]);
|
||||||
[ $x1, $y1, $z1 ],
|
}
|
||||||
$nb,
|
else {
|
||||||
];
|
$self->_add_triangle([
|
||||||
|
[$x1, $y1, $z1],
|
||||||
|
[$x2, $y2, $z2],
|
||||||
|
[$x3, $y3, $z3],
|
||||||
|
]);
|
||||||
|
$self->_add_triangle([
|
||||||
|
[$x3, $y3, $z3],
|
||||||
|
[$x4, $y4, $z4],
|
||||||
|
[$x1, $y1, $z1],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub WTF_parse_quadrilateral_command {
|
sub _add_triangle {
|
||||||
my ( $self, $rest ) = @_;
|
my ($self, $points) = @_;
|
||||||
# 16 1.27 10 68.9 -6.363 10 66.363 10.6 10 79.2 7.1 10 73.27
|
$points->[3] = $self->calc_surface_normal($points);
|
||||||
my @items = split( /\s+/, $rest );
|
push @{$self->{triangles}}, $points;
|
||||||
my $color = shift @items;
|
|
||||||
my $p1 = [ $items[0], $items[1], $items[2] ];
|
|
||||||
my $p2 = [ $items[3], $items[4], $items[5] ];
|
|
||||||
my $p3 = [ $items[6], $items[7], $items[8] ];
|
|
||||||
my $p4 = [ $items[9], $items[10], $items[11] ];
|
|
||||||
my $na = [ $self->calc_surface_normal( $p1, $p2, $p3 ) ];
|
|
||||||
my $nb = [ $self->calc_surface_normal( $p3, $p4, $p1 ) ];
|
|
||||||
push @{ $self->{triangles} }, [ $p1, $p2, $p3, $na ];
|
|
||||||
push @{ $self->{triangles} }, [ $p3, $p4, $p1, $nb ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub parse_optional {
|
sub parse_optional {
|
||||||
@ -283,12 +318,8 @@ sub parse_optional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub calc_surface_normal {
|
sub calc_surface_normal {
|
||||||
my ( $self, $ip1, $ip2, $ip3 ) = @_;
|
my ($self, $points) = @_;
|
||||||
|
my ($p1, $p2, $p3) = ($points->[0], $points->[1], $points->[2]);
|
||||||
my ( $p1, $p2, $p3 ) = ( $ip1, $ip2, $ip3 );
|
|
||||||
if ( $self->invert ) {
|
|
||||||
( $p1, $p2, $p3 ) = ( $ip1, $ip3, $ip2 );
|
|
||||||
}
|
|
||||||
|
|
||||||
my ( $N, $U, $V ) = ( [], [], [] );
|
my ( $N, $U, $V ) = ( [], [], [] );
|
||||||
|
|
||||||
@ -304,10 +335,10 @@ sub calc_surface_normal {
|
|||||||
$N->[Y] = $U->[Z] * $V->[X] - $U->[X] * $V->[Z];
|
$N->[Y] = $U->[Z] * $V->[X] - $U->[X] * $V->[Z];
|
||||||
$N->[Z] = $U->[X] * $V->[Y] - $U->[Y] * $V->[X];
|
$N->[Z] = $U->[X] * $V->[Y] - $U->[Y] * $V->[X];
|
||||||
|
|
||||||
return ( $N->[X], $N->[Y], $N->[Z] );
|
return [$N->[X], $N->[Y], $N->[Z]];
|
||||||
}
|
}
|
||||||
|
|
||||||
sub max4xv3 {
|
sub mat4xv3 {
|
||||||
my ( $mat, $vec ) = @_;
|
my ( $mat, $vec ) = @_;
|
||||||
|
|
||||||
my ( $a1, $a2, $a3, $a4,
|
my ( $a1, $a2, $a3, $a4,
|
||||||
@ -323,6 +354,40 @@ sub max4xv3 {
|
|||||||
return ( $x_new, $y_new, $z_new );
|
return ( $x_new, $y_new, $z_new );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub mat4determinant {
|
||||||
|
my ($mat) = @_;
|
||||||
|
my $a00 = $mat->[0];
|
||||||
|
my $a01 = $mat->[1];
|
||||||
|
my $a02 = $mat->[2];
|
||||||
|
my $a03 = $mat->[3];
|
||||||
|
my $a10 = $mat->[4];
|
||||||
|
my $a11 = $mat->[5];
|
||||||
|
my $a12 = $mat->[6];
|
||||||
|
my $a13 = $mat->[7];
|
||||||
|
my $a20 = $mat->[8];
|
||||||
|
my $a21 = $mat->[9];
|
||||||
|
my $a22 = $mat->[10];
|
||||||
|
my $a23 = $mat->[11];
|
||||||
|
my $a30 = $mat->[12];
|
||||||
|
my $a31 = $mat->[13];
|
||||||
|
my $a32 = $mat->[14];
|
||||||
|
my $a33 = $mat->[15];
|
||||||
|
my $b00 = $a00 * $a11 - $a01 * $a10;
|
||||||
|
my $b01 = $a00 * $a12 - $a02 * $a10;
|
||||||
|
my $b02 = $a00 * $a13 - $a03 * $a10;
|
||||||
|
my $b03 = $a01 * $a12 - $a02 * $a11;
|
||||||
|
my $b04 = $a01 * $a13 - $a03 * $a11;
|
||||||
|
my $b05 = $a02 * $a13 - $a03 * $a12;
|
||||||
|
my $b06 = $a20 * $a31 - $a21 * $a30;
|
||||||
|
my $b07 = $a20 * $a32 - $a22 * $a30;
|
||||||
|
my $b08 = $a20 * $a33 - $a23 * $a30;
|
||||||
|
my $b09 = $a21 * $a32 - $a22 * $a31;
|
||||||
|
my $b10 = $a21 * $a33 - $a23 * $a31;
|
||||||
|
my $b11 = $a22 * $a33 - $a23 * $a32;
|
||||||
|
|
||||||
|
return $b00 * $b11 - $b01 * $b10 + $b02 * $b09 + $b03 * $b08 - $b04 * $b07 + $b05 * $b06;
|
||||||
|
}
|
||||||
|
|
||||||
sub to_stl {
|
sub to_stl {
|
||||||
my ( $self ) = @_;
|
my ( $self ) = @_;
|
||||||
|
|
||||||
@ -350,3 +415,90 @@ sub to_stl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
|
||||||
|
## In handler for "!LDRAW":
|
||||||
|
|
||||||
|
// If the scale of the object is negated then the triangle winding order
|
||||||
|
// needs to be flipped.
|
||||||
|
var matrix = currentParseScope.matrix;
|
||||||
|
if (
|
||||||
|
matrix.determinant() < 0 && (
|
||||||
|
scope.separateObjects && isPrimitiveType( type ) ||
|
||||||
|
! scope.separateObjects
|
||||||
|
) ) {
|
||||||
|
|
||||||
|
currentParseScope.inverted = ! currentParseScope.inverted;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
triangles = currentParseScope.triangles;
|
||||||
|
lineSegments = currentParseScope.lineSegments;
|
||||||
|
conditionalSegments = currentParseScope.conditionalSegments;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
## Handling sub-file:
|
||||||
|
|
||||||
|
// Line type 1: Sub-object file
|
||||||
|
case '1':
|
||||||
|
|
||||||
|
var material = parseColourCode( lp );
|
||||||
|
|
||||||
|
var posX = parseFloat( lp.getToken() );
|
||||||
|
var posY = parseFloat( lp.getToken() );
|
||||||
|
var posZ = parseFloat( lp.getToken() );
|
||||||
|
var m0 = parseFloat( lp.getToken() );
|
||||||
|
var m1 = parseFloat( lp.getToken() );
|
||||||
|
var m2 = parseFloat( lp.getToken() );
|
||||||
|
var m3 = parseFloat( lp.getToken() );
|
||||||
|
var m4 = parseFloat( lp.getToken() );
|
||||||
|
var m5 = parseFloat( lp.getToken() );
|
||||||
|
var m6 = parseFloat( lp.getToken() );
|
||||||
|
var m7 = parseFloat( lp.getToken() );
|
||||||
|
var m8 = parseFloat( lp.getToken() );
|
||||||
|
|
||||||
|
var matrix = new Matrix4().set(
|
||||||
|
m0, m1, m2, posX,
|
||||||
|
m3, m4, m5, posY,
|
||||||
|
m6, m7, m8, posZ,
|
||||||
|
0, 0, 0, 1
|
||||||
|
);
|
||||||
|
|
||||||
|
var fileName = lp.getRemainingString().trim().replace( /\\/g, "/" );
|
||||||
|
|
||||||
|
if ( scope.fileMap[ fileName ] ) {
|
||||||
|
|
||||||
|
// Found the subobject path in the preloaded file path map
|
||||||
|
fileName = scope.fileMap[ fileName ];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Standardized subfolders
|
||||||
|
if ( fileName.startsWith( 's/' ) ) {
|
||||||
|
|
||||||
|
fileName = 'parts/' + fileName;
|
||||||
|
|
||||||
|
} else if ( fileName.startsWith( '48/' ) ) {
|
||||||
|
|
||||||
|
fileName = 'p/' + fileName;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
subobjects.push( {
|
||||||
|
material: material,
|
||||||
|
matrix: matrix,
|
||||||
|
fileName: fileName,
|
||||||
|
originalFileName: fileName,
|
||||||
|
locationState: LDrawLoader.FILE_LOCATION_AS_IS,
|
||||||
|
url: null,
|
||||||
|
triedLowerCase: false,
|
||||||
|
inverted: bfcInverted !== currentParseScope.inverted,
|
||||||
|
startingConstructionStep: startingConstructionStep
|
||||||
|
} );
|
||||||
|
|
||||||
|
bfcInverted = false;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user