mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-11-21 20:01:13 +00:00
904 lines
27 KiB
C++
904 lines
27 KiB
C++
|
/*
|
||
|
===========================================================================
|
||
|
|
||
|
Doom 3 BFG Edition GPL Source Code
|
||
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
||
|
|
||
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
||
|
|
||
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
||
|
|
||
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
||
|
|
||
|
===========================================================================
|
||
|
*/
|
||
|
#pragma hdrstop
|
||
|
#include "../idlib/precompiled.h"
|
||
|
#include "float.h"
|
||
|
|
||
|
#pragma warning( disable: 4189 ) // local variable is initialized but not referenced
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ParseShape
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::Parse( idSWFBitStream & bitstream, idSWFShape & shape, int recordType ) {
|
||
|
extendedCount = ( recordType > 1 );
|
||
|
lineStyle2 = ( recordType == 4 );
|
||
|
rgba = ( recordType >= 3 );
|
||
|
morph = false;
|
||
|
|
||
|
bitstream.ReadRect( shape.startBounds );
|
||
|
shape.endBounds = shape.startBounds;
|
||
|
|
||
|
if ( recordType == 4 ) {
|
||
|
swfRect_t edgeBounds;
|
||
|
bitstream.ReadRect( edgeBounds );
|
||
|
bitstream.ReadU8(); // flags (that we ignore)
|
||
|
}
|
||
|
|
||
|
ReadFillStyle( bitstream );
|
||
|
ParseShapes( bitstream, NULL, false );
|
||
|
TriangulateSoup( shape );
|
||
|
|
||
|
shape.lineDraws.SetNum( lineDraws.Num() );
|
||
|
for ( int i = 0; i < lineDraws.Num(); i++ ) {
|
||
|
idSWFShapeDrawLine & ld = shape.lineDraws[i];
|
||
|
swfSPDrawLine_t & spld = lineDraws[i];
|
||
|
ld.style = spld.style;
|
||
|
ld.indices.SetNum( spld.edges.Num() * 3 );
|
||
|
ld.indices.SetNum( 0 );
|
||
|
for ( int e = 0; e < spld.edges.Num(); e++ ) {
|
||
|
int v0 = ld.startVerts.AddUnique( verts[ spld.edges[e].start.v0 ] );
|
||
|
ld.indices.Append( v0 );
|
||
|
ld.indices.Append( v0 );
|
||
|
|
||
|
// Rather then tesselating curves at run time, we do them once here by inserting a vert every 10 units
|
||
|
// It may not wind up being 10 actual pixels when rendered because the shape may have scaling applied to it
|
||
|
if ( spld.edges[e].start.cp != 0xFFFF ) {
|
||
|
assert( spld.edges[e].end.cp != 0xFFFF );
|
||
|
float length1 = ( verts[ spld.edges[e].start.v0 ] - verts[ spld.edges[e].start.v1 ] ).Length();
|
||
|
float length2 = ( verts[ spld.edges[e].end.v0 ] - verts[ spld.edges[e].end.v1 ] ).Length();
|
||
|
int numPoints = 1 + idMath::Ftoi( Max( length1, length2 ) / 10.0f );
|
||
|
for ( int ti = 0; ti < numPoints; ti++ ) {
|
||
|
float t0 = ( ti + 1 ) / ( (float) numPoints + 1.0f );
|
||
|
float t1 = ( 1.0f - t0 );
|
||
|
float c1 = t1 * t1;
|
||
|
float c2 = t0 * t1 * 2.0f;
|
||
|
float c3 = t0 * t0;
|
||
|
|
||
|
idVec2 p1 = c1 * verts[ spld.edges[e].start.v0 ];
|
||
|
p1 += c2 * verts[ spld.edges[e].start.cp ];
|
||
|
p1 += c3 * verts[ spld.edges[e].start.v1 ];
|
||
|
|
||
|
int v1 = ld.startVerts.AddUnique( p1 );
|
||
|
ld.indices.Append( v1 );
|
||
|
ld.indices.Append( v1 );
|
||
|
ld.indices.Append( v1 );
|
||
|
}
|
||
|
}
|
||
|
ld.indices.Append( ld.startVerts.AddUnique( verts[ spld.edges[e].start.v1 ] ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ParseMorph
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::ParseMorph( idSWFBitStream & bitstream, idSWFShape & shape ) {
|
||
|
extendedCount = true;
|
||
|
lineStyle2 = false;
|
||
|
rgba = true;
|
||
|
morph = true;
|
||
|
|
||
|
bitstream.ReadRect( shape.startBounds );
|
||
|
bitstream.ReadRect( shape.endBounds );
|
||
|
|
||
|
uint32 offset = bitstream.ReadU32();
|
||
|
|
||
|
// offset is the byte offset from the current read position to the 'endShape' record
|
||
|
// we read the entire block into 'bitstream1' which moves the read pointer of 'bitstream'
|
||
|
// to the start of the 'endShape' record
|
||
|
|
||
|
idSWFBitStream bitstream1;
|
||
|
bitstream1.Load( (byte *)bitstream.ReadData( offset ), offset, false );
|
||
|
|
||
|
ReadFillStyle( bitstream1 );
|
||
|
ParseShapes( bitstream1, &bitstream, true );
|
||
|
TriangulateSoup( shape );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ParseFont
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::ParseFont( idSWFBitStream & bitstream, idSWFFontGlyph & shape ) {
|
||
|
extendedCount = false;
|
||
|
lineStyle2 = false;
|
||
|
rgba = false;
|
||
|
morph = false;
|
||
|
|
||
|
fillDraws.SetNum( 1 );
|
||
|
|
||
|
ParseShapes( bitstream, NULL, true );
|
||
|
TriangulateSoup( shape );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ParseShapes
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::ParseShapes( idSWFBitStream & bitstream1, idSWFBitStream * bitstream2, bool swap ) {
|
||
|
int32 pen1X = 0;
|
||
|
int32 pen1Y = 0;
|
||
|
int32 pen2X = 0;
|
||
|
int32 pen2Y = 0;
|
||
|
|
||
|
uint8 fillStyle0 = 0;
|
||
|
uint8 fillStyle1 = 0;
|
||
|
uint8 lineStyle = 0;
|
||
|
|
||
|
uint16 baseFillStyle = 0;
|
||
|
uint16 baseLineStyle = 0;
|
||
|
|
||
|
uint8 numBits = bitstream1.ReadU8();
|
||
|
uint8 numFillBits1 = numBits >> 4;
|
||
|
uint8 numLineBits1 = numBits & 0xF;
|
||
|
|
||
|
uint8 numFillBits2 = 0;
|
||
|
uint8 numLineBits2 = 0;
|
||
|
|
||
|
if ( bitstream2 ) {
|
||
|
numBits = bitstream2->ReadU8();
|
||
|
numFillBits2 = numBits >> 4;
|
||
|
numLineBits2 = numBits & 0xF;
|
||
|
}
|
||
|
|
||
|
while ( true ) {
|
||
|
if ( !bitstream1.ReadBool() ) {
|
||
|
bool stateNewStyles = bitstream1.ReadBool();
|
||
|
bool stateLineStyle = bitstream1.ReadBool();
|
||
|
bool stateFillStyle1 = bitstream1.ReadBool();
|
||
|
bool stateFillStyle0 = bitstream1.ReadBool();
|
||
|
bool stateMoveTo = bitstream1.ReadBool();
|
||
|
if ( ( stateNewStyles || stateLineStyle || stateFillStyle1 || stateFillStyle0 || stateMoveTo ) == false ) {
|
||
|
// end record
|
||
|
if ( bitstream2 ) {
|
||
|
uint8 flags = bitstream2->ReadU( 6 );
|
||
|
if ( flags != 0 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 1 ends before 2" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
if ( stateMoveTo ) {
|
||
|
uint8 moveBits = bitstream1.ReadU( 5 );
|
||
|
pen1X = bitstream1.ReadS( moveBits );
|
||
|
pen1Y = bitstream1.ReadS( moveBits );
|
||
|
}
|
||
|
if ( stateFillStyle0 ) {
|
||
|
fillStyle0 = bitstream1.ReadU( numFillBits1 );
|
||
|
}
|
||
|
if ( stateFillStyle1 ) {
|
||
|
fillStyle1 = bitstream1.ReadU( numFillBits1 );
|
||
|
}
|
||
|
if ( stateLineStyle ) {
|
||
|
lineStyle = bitstream1.ReadU( numLineBits1 );
|
||
|
}
|
||
|
if ( stateNewStyles ) {
|
||
|
baseFillStyle = fillDraws.Num();
|
||
|
baseLineStyle = lineDraws.Num();
|
||
|
ReadFillStyle( bitstream1 );
|
||
|
numBits = bitstream1.ReadU8();
|
||
|
numFillBits1 = numBits >> 4;
|
||
|
numLineBits1 = numBits & 0xF;
|
||
|
}
|
||
|
if ( bitstream2 ) {
|
||
|
bool isEdge = bitstream2->ReadBool();
|
||
|
if ( isEdge ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 1 defines style change, but stream 2 does not" );
|
||
|
break;
|
||
|
}
|
||
|
bool stateNewStyles = bitstream2->ReadBool();
|
||
|
bool stateLineStyle = bitstream2->ReadBool();
|
||
|
bool stateFillStyle1 = bitstream2->ReadBool();
|
||
|
bool stateFillStyle0 = bitstream2->ReadBool();
|
||
|
bool stateMoveTo = bitstream2->ReadBool();
|
||
|
if ( stateMoveTo ) {
|
||
|
uint8 moveBits = bitstream2->ReadU( 5 );
|
||
|
pen2X = bitstream2->ReadS( moveBits );
|
||
|
pen2Y = bitstream2->ReadS( moveBits );
|
||
|
}
|
||
|
if ( stateFillStyle0 ) {
|
||
|
if ( bitstream2->ReadU( numFillBits2 ) != fillStyle0 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different fillStyle0 from stream 1" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( stateFillStyle1 ) {
|
||
|
if ( bitstream2->ReadU( numFillBits2 ) != fillStyle1 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different fillStyle1 from stream 1" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( stateLineStyle ) {
|
||
|
if ( bitstream2->ReadU( numLineBits2 ) != lineStyle ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different lineStyle from stream 1" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( stateNewStyles ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 2 defines new styles" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
swfSPMorphEdge_t morphEdge;
|
||
|
|
||
|
ParseEdge( bitstream1, pen1X, pen1Y, morphEdge.start );
|
||
|
if ( bitstream2 ) {
|
||
|
bool isEdge = bitstream2->ReadBool();
|
||
|
if ( !isEdge ) {
|
||
|
idLib::Warning( "idSWFShapeParser: morph stream 1 defines an edge, but stream 2 does not" );
|
||
|
break;
|
||
|
}
|
||
|
ParseEdge( *bitstream2, pen2X, pen2Y, morphEdge.end );
|
||
|
} else {
|
||
|
morphEdge.end = morphEdge.start;
|
||
|
}
|
||
|
|
||
|
// one edge may be a straight edge, and the other may be a curve
|
||
|
// in this case, we turn the straight edge into a curve by adding
|
||
|
// a control point in the middle of the line
|
||
|
if ( morphEdge.start.cp != 0xFFFF ) {
|
||
|
if ( morphEdge.end.cp == 0xFFFF ) {
|
||
|
const idVec2 & v0 = verts[ morphEdge.end.v0 ];
|
||
|
const idVec2 & v1 = verts[ morphEdge.end.v1 ];
|
||
|
morphEdge.end.cp = verts.AddUnique( ( v0 + v1 ) * 0.5f );
|
||
|
}
|
||
|
} else {
|
||
|
if ( morphEdge.end.cp != 0xFFFF ) {
|
||
|
const idVec2 & v0 = verts[ morphEdge.start.v0 ];
|
||
|
const idVec2 & v1 = verts[ morphEdge.start.v1 ];
|
||
|
morphEdge.start.cp = verts.AddUnique( ( v0 + v1 ) * 0.5f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( lineStyle != 0 ) {
|
||
|
lineDraws[ baseLineStyle + lineStyle - 1 ].edges.Append( morphEdge );
|
||
|
}
|
||
|
if ( swap ) {
|
||
|
SwapValues( morphEdge.start.v0, morphEdge.start.v1 );
|
||
|
SwapValues( morphEdge.end.v0, morphEdge.end.v1 );
|
||
|
}
|
||
|
if ( fillStyle1 != 0 ) {
|
||
|
fillDraws[ baseFillStyle + fillStyle1 - 1 ].edges.Append( morphEdge );
|
||
|
}
|
||
|
if ( fillStyle0 != 0 ) {
|
||
|
// for fill style 0, we need to reverse the winding
|
||
|
swfSPMorphEdge_t swapped = morphEdge;
|
||
|
SwapValues( swapped.start.v0, swapped.start.v1 );
|
||
|
SwapValues( swapped.end.v0, swapped.end.v1 );
|
||
|
fillDraws[ baseFillStyle + fillStyle0 - 1 ].edges.Append( swapped );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ParseEdge
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::ParseEdge( idSWFBitStream & bitstream, int32 & penX, int32 & penY, swfSPEdge_t & edge ) {
|
||
|
bool straight = bitstream.ReadBool();
|
||
|
uint8 numBits = bitstream.ReadU( 4 ) + 2;
|
||
|
|
||
|
edge.v0 = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) );
|
||
|
|
||
|
if ( straight ) {
|
||
|
edge.cp = 0xFFFF;
|
||
|
if ( bitstream.ReadBool() ) {
|
||
|
penX += bitstream.ReadS( numBits );
|
||
|
penY += bitstream.ReadS( numBits );
|
||
|
} else {
|
||
|
if ( bitstream.ReadBool() ) {
|
||
|
penY += bitstream.ReadS( numBits );
|
||
|
} else {
|
||
|
penX += bitstream.ReadS( numBits );
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
penX += bitstream.ReadS( numBits );
|
||
|
penY += bitstream.ReadS( numBits );
|
||
|
edge.cp = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) );
|
||
|
penX += bitstream.ReadS( numBits );
|
||
|
penY += bitstream.ReadS( numBits );
|
||
|
}
|
||
|
|
||
|
edge.v1 = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::MakeLoops
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::MakeLoops() {
|
||
|
|
||
|
// At this point, each fill style has an edge soup associated with it
|
||
|
// We want to turn this soup into loops of connected verts
|
||
|
|
||
|
for ( int i = 0; i < fillDraws.Num(); i++ ) {
|
||
|
swfSPDrawFill_t & fill = fillDraws[i];
|
||
|
|
||
|
// first remove degenerate edges
|
||
|
for ( int e = 0; e < fill.edges.Num(); e++ ) {
|
||
|
if ( ( fill.edges[e].start.v0 == fill.edges[e].start.v1 ) || ( fill.edges[e].end.v0 == fill.edges[e].end.v1 ) ) {
|
||
|
fill.edges.RemoveIndexFast( e );
|
||
|
e--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
idList< int > unusedEdges;
|
||
|
unusedEdges.SetNum( fill.edges.Num() );
|
||
|
for ( int e = 0; e < fill.edges.Num(); e++ ) {
|
||
|
unusedEdges[e] = e;
|
||
|
}
|
||
|
while ( unusedEdges.Num() > 0 ) {
|
||
|
swfSPLineLoop_t & loop = fill.loops.Alloc();
|
||
|
loop.hole = false;
|
||
|
|
||
|
int e1 = unusedEdges[ unusedEdges.Num() - 1 ];
|
||
|
unusedEdges.SetNum( unusedEdges.Num() - 1 );
|
||
|
|
||
|
while ( true ) {
|
||
|
loop.vindex1.Append( fill.edges[e1].start.v0 );
|
||
|
loop.vindex2.Append( fill.edges[e1].end.v0 );
|
||
|
|
||
|
// Rather then tesselating curves at run time, we do them once here by inserting a vert every 10 units
|
||
|
// It may not wind up being 10 actual pixels when rendered because the shape may have scaling applied to it
|
||
|
if ( fill.edges[e1].start.cp != 0xFFFF ) {
|
||
|
assert( fill.edges[e1].end.cp != 0xFFFF );
|
||
|
float length1 = ( verts[ fill.edges[e1].start.v0 ] - verts[ fill.edges[e1].start.v1 ] ).Length();
|
||
|
float length2 = ( verts[ fill.edges[e1].end.v0 ] - verts[ fill.edges[e1].end.v1 ] ).Length();
|
||
|
int numPoints = 1 + idMath::Ftoi( Max( length1, length2 ) / 10.0f );
|
||
|
for ( int ti = 0; ti < numPoints; ti++ ) {
|
||
|
float t0 = ( ti + 1 ) / ( (float) numPoints + 1.0f );
|
||
|
float t1 = ( 1.0f - t0 );
|
||
|
float c1 = t1 * t1;
|
||
|
float c2 = t0 * t1 * 2.0f;
|
||
|
float c3 = t0 * t0;
|
||
|
|
||
|
idVec2 p1 = c1 * verts[ fill.edges[e1].start.v0 ];
|
||
|
p1 += c2 * verts[ fill.edges[e1].start.cp ];
|
||
|
p1 += c3 * verts[ fill.edges[e1].start.v1 ];
|
||
|
|
||
|
idVec2 p2 = c1 * verts[ fill.edges[e1].end.v0 ];
|
||
|
p2 += c2 * verts[ fill.edges[e1].end.cp ];
|
||
|
p2 += c3 * verts[ fill.edges[e1].end.v1 ];
|
||
|
|
||
|
loop.vindex1.Append( verts.AddUnique( p1 ) );
|
||
|
loop.vindex2.Append( verts.AddUnique( p2 ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const swfSPEdge_t & edge1 = fill.edges[e1].start;
|
||
|
|
||
|
float bestNormal = FLT_MAX;
|
||
|
int beste = -1;
|
||
|
for ( int e = 0; e < unusedEdges.Num(); e++ ) {
|
||
|
int e2 = unusedEdges[e];
|
||
|
const swfSPEdge_t & edge2 = fill.edges[e2].start;
|
||
|
if ( edge1.v1 != edge2.v0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
assert( edge1.v0 != edge2.v0 );
|
||
|
assert( edge1.v1 != edge2.v1 );
|
||
|
|
||
|
const idVec2 & v1 = verts[ edge1.v0 ];
|
||
|
const idVec2 & v2 = verts[ edge1.v1 ]; // == edge2.v0
|
||
|
const idVec2 & v3 = verts[ edge2.v1 ];
|
||
|
idVec2 a = v1 - v2;
|
||
|
idVec2 b = v3 - v2;
|
||
|
|
||
|
float normal = ( a.x * b.y - a.y * b.x );
|
||
|
if ( normal < bestNormal ) {
|
||
|
bestNormal = normal;
|
||
|
beste = e;
|
||
|
} else {
|
||
|
assert( beste != -1 );
|
||
|
}
|
||
|
}
|
||
|
if ( beste < 0 ) {
|
||
|
// no more edges connect to this one
|
||
|
break;
|
||
|
}
|
||
|
e1 = unusedEdges[beste];
|
||
|
unusedEdges.RemoveIndexFast( beste );
|
||
|
}
|
||
|
if ( loop.vindex1.Num() < 3 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: loop with < 3 verts" );
|
||
|
fill.loops.SetNum( fill.loops.Num() - 1 );
|
||
|
continue;
|
||
|
}
|
||
|
// Use the left most vert to determine if it's a hole or not
|
||
|
float leftMostX = FLT_MAX;
|
||
|
int leftMostIndex = 0;
|
||
|
for ( int j = 0; j < loop.vindex1.Num(); j++ ) {
|
||
|
idVec2 & v = verts[ loop.vindex1[j] ];
|
||
|
if ( v.x < leftMostX ) {
|
||
|
leftMostIndex = j;
|
||
|
leftMostX = v.x;
|
||
|
}
|
||
|
}
|
||
|
const idVec2 & v1 = verts[ loop.vindex1[(loop.vindex1.Num() + leftMostIndex - 1) % loop.vindex1.Num()] ];
|
||
|
const idVec2 & v2 = verts[ loop.vindex1[leftMostIndex] ];
|
||
|
const idVec2 & v3 = verts[ loop.vindex1[(leftMostIndex+1) % loop.vindex1.Num()] ];
|
||
|
idVec2 a = v1 - v2;
|
||
|
idVec2 b = v3 - v2;
|
||
|
float normal = ( a.x * b.y - a.y * b.x );
|
||
|
loop.hole = ( normal > 0.0f );
|
||
|
}
|
||
|
|
||
|
// now we have a series of loops, which define either shapes or holes
|
||
|
// we want to merge the holes into the shapes by inserting edges
|
||
|
// this assumes shapes are either completely contained or not
|
||
|
// we start merging holes starting on the right so nested holes work
|
||
|
while ( true ) {
|
||
|
int hole = -1;
|
||
|
int holeVert = -1;
|
||
|
float rightMostX = -1e10f;
|
||
|
for ( int j = 0; j < fill.loops.Num(); j++ ) {
|
||
|
swfSPLineLoop_t & loop = fill.loops[j];
|
||
|
if ( !loop.hole ) {
|
||
|
continue;
|
||
|
}
|
||
|
for ( int v = 0; v < loop.vindex1.Num(); v++ ) {
|
||
|
if ( verts[ loop.vindex1[v] ].x > rightMostX ) {
|
||
|
hole = j;
|
||
|
holeVert = v;
|
||
|
rightMostX = verts[ loop.vindex1[v] ].x;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ( hole == -1 ) {
|
||
|
break;
|
||
|
}
|
||
|
swfSPLineLoop_t & loopHole = fill.loops[ hole ];
|
||
|
const idVec2 & holePoint = verts[ loopHole.vindex1[ holeVert ] ];
|
||
|
|
||
|
int shape = -1;
|
||
|
for ( int j = 0; j < fill.loops.Num(); j++ ) {
|
||
|
swfSPLineLoop_t & loop = fill.loops[j];
|
||
|
if ( loop.hole ) {
|
||
|
continue;
|
||
|
}
|
||
|
bool inside = false;
|
||
|
for ( int k = 0; k < loop.vindex1.Num(); k++ ) {
|
||
|
const idVec2 & v1 = verts[ loop.vindex1[k] ];
|
||
|
const idVec2 & v2 = verts[ loop.vindex1[(k + 1) % loop.vindex1.Num()] ];
|
||
|
if ( v1.x < holePoint.x && v2.x < holePoint.x ) {
|
||
|
continue; // both on the left of the holePoint
|
||
|
}
|
||
|
if ( ( v1.y < holePoint.y ) == ( v2.y < holePoint.y ) ) {
|
||
|
continue; // both on the same side of the horizon
|
||
|
}
|
||
|
assert( v1 != holePoint );
|
||
|
assert( v2 != holePoint );
|
||
|
inside = !inside;
|
||
|
}
|
||
|
if ( inside ) {
|
||
|
shape = j;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( shape == -1 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: Hole not in a shape" );
|
||
|
fill.loops.RemoveIndexFast( hole );
|
||
|
continue;
|
||
|
}
|
||
|
swfSPLineLoop_t & loopShape = fill.loops[ shape ];
|
||
|
|
||
|
// now that we have a hole and the shape it's inside, merge the two together
|
||
|
|
||
|
// find the nearest vert that's on the right side of holePoint
|
||
|
float bestDist = 1e10f;
|
||
|
int shapeVert = -1;
|
||
|
for ( int j = 0; j < loopShape.vindex1.Num(); j++ ) {
|
||
|
const idVec2 & v1 = verts[ loopShape.vindex1[j] ];
|
||
|
if ( v1.x < holePoint.x ) {
|
||
|
continue; // on the left of the holePoint
|
||
|
}
|
||
|
float dist = ( v1 - holePoint ).Length();
|
||
|
if ( dist < bestDist ) {
|
||
|
shapeVert = j;
|
||
|
bestDist = dist;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// connect holeVert to shapeVert
|
||
|
idList< uint16 > vindex;
|
||
|
vindex.SetNum( loopShape.vindex1.Num() + loopHole.vindex1.Num() + 1 );
|
||
|
vindex.SetNum( 0 );
|
||
|
for ( int j = 0; j <= shapeVert; j++ ) {
|
||
|
vindex.Append( loopShape.vindex1[j] );
|
||
|
}
|
||
|
for ( int j = holeVert; j < loopHole.vindex1.Num(); j++ ) {
|
||
|
vindex.Append( loopHole.vindex1[j] );
|
||
|
}
|
||
|
for ( int j = 0; j <= holeVert; j++ ) {
|
||
|
vindex.Append( loopHole.vindex1[j] );
|
||
|
}
|
||
|
for ( int j = shapeVert; j < loopShape.vindex1.Num(); j++ ) {
|
||
|
vindex.Append( loopShape.vindex1[j] );
|
||
|
}
|
||
|
loopShape.vindex1 = vindex;
|
||
|
|
||
|
vindex.Clear();
|
||
|
for ( int j = 0; j <= shapeVert; j++ ) {
|
||
|
vindex.Append( loopShape.vindex2[j] );
|
||
|
}
|
||
|
for ( int j = holeVert; j < loopHole.vindex2.Num(); j++ ) {
|
||
|
vindex.Append( loopHole.vindex2[j] );
|
||
|
}
|
||
|
for ( int j = 0; j <= holeVert; j++ ) {
|
||
|
vindex.Append( loopHole.vindex2[j] );
|
||
|
}
|
||
|
for ( int j = shapeVert; j < loopShape.vindex2.Num(); j++ ) {
|
||
|
vindex.Append( loopShape.vindex2[j] );
|
||
|
}
|
||
|
loopShape.vindex2 = vindex;
|
||
|
|
||
|
fill.loops.RemoveIndexFast( hole );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::TriangulateSoup
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::TriangulateSoup( idSWFShape & shape ) {
|
||
|
|
||
|
MakeLoops();
|
||
|
|
||
|
// Now turn the (potentially) concave line loops into triangles by using ear clipping
|
||
|
|
||
|
shape.fillDraws.SetNum( fillDraws.Num() );
|
||
|
for ( int i = 0; i < fillDraws.Num(); i++ ) {
|
||
|
swfSPDrawFill_t & spDrawFill = fillDraws[i];
|
||
|
idSWFShapeDrawFill & drawFill = shape.fillDraws[i];
|
||
|
|
||
|
swfFillStyle_t & style = spDrawFill.style;
|
||
|
drawFill.style = spDrawFill.style;
|
||
|
|
||
|
for ( int j = 0; j < spDrawFill.loops.Num(); j++ ) {
|
||
|
swfSPLineLoop_t & loop = spDrawFill.loops[j];
|
||
|
int numVerts = loop.vindex1.Num();
|
||
|
for ( int k = 0; k < numVerts - 2; k++ ) {
|
||
|
int v1 = FindEarVert( loop );
|
||
|
if ( v1 == -1 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: could not find an ear vert" );
|
||
|
break;
|
||
|
}
|
||
|
int num = loop.vindex1.Num();
|
||
|
int v2 = ( v1 + 1 ) % num;
|
||
|
int v3 = ( v1 + 2 ) % num;
|
||
|
|
||
|
AddUniqueVert( drawFill, verts[ loop.vindex1[ v1 ] ], verts[ loop.vindex2[ v1 ] ] );
|
||
|
AddUniqueVert( drawFill, verts[ loop.vindex1[ v2 ] ], verts[ loop.vindex2[ v2 ] ] );
|
||
|
AddUniqueVert( drawFill, verts[ loop.vindex1[ v3 ] ], verts[ loop.vindex2[ v3 ] ] );
|
||
|
|
||
|
loop.vindex1.RemoveIndex( v2 );
|
||
|
loop.vindex2.RemoveIndex( v2 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::TriangulateSoup
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::TriangulateSoup( idSWFFontGlyph & shape ) {
|
||
|
|
||
|
MakeLoops();
|
||
|
|
||
|
// Now turn the (potentially) concave line loops into triangles by using ear clipping
|
||
|
|
||
|
assert( fillDraws.Num() == 1 );
|
||
|
swfSPDrawFill_t & spDrawFill = fillDraws[0];
|
||
|
for ( int j = 0; j < spDrawFill.loops.Num(); j++ ) {
|
||
|
swfSPLineLoop_t & loop = spDrawFill.loops[j];
|
||
|
int numVerts = loop.vindex1.Num();
|
||
|
for ( int k = 0; k < numVerts - 2; k++ ) {
|
||
|
int v1 = FindEarVert( loop );
|
||
|
if ( v1 == -1 ) {
|
||
|
idLib::Warning( "idSWFShapeParser: could not find an ear vert" );
|
||
|
break;
|
||
|
}
|
||
|
int num = loop.vindex1.Num();
|
||
|
int v2 = ( v1 + 1 ) % num;
|
||
|
int v3 = ( v1 + 2 ) % num;
|
||
|
|
||
|
shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v1 ] ] ) );
|
||
|
shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v2 ] ] ) );
|
||
|
shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v3 ] ] ) );
|
||
|
|
||
|
loop.vindex1.RemoveIndex( v2 );
|
||
|
loop.vindex2.RemoveIndex( v2 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct earVert_t {
|
||
|
int i1;
|
||
|
int i2;
|
||
|
int i3;
|
||
|
float cross;
|
||
|
};
|
||
|
class idSort_Ears : public idSort_Quick< earVert_t, idSort_Ears > {
|
||
|
public:
|
||
|
int Compare( const earVert_t & a, const earVert_t & b ) const {
|
||
|
if ( a.cross < b.cross ) {
|
||
|
return -1;
|
||
|
} else if ( a.cross > b.cross ) {
|
||
|
return 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::FindEarVert
|
||
|
========================
|
||
|
*/
|
||
|
int idSWFShapeParser::FindEarVert( const swfSPLineLoop_t & loop ) {
|
||
|
assert( loop.vindex1.Num() == loop.vindex2.Num() );
|
||
|
int num = loop.vindex1.Num();
|
||
|
|
||
|
idList<earVert_t> ears;
|
||
|
ears.SetNum( num );
|
||
|
|
||
|
for ( int i1 = 0; i1 < num; i1++ ) {
|
||
|
int i2 = ( i1 + 1 ) % num;
|
||
|
int i3 = ( i1 + 2 ) % num;
|
||
|
const idVec2 & v1s = verts[ loop.vindex1[ i1 ] ];
|
||
|
const idVec2 & v2s = verts[ loop.vindex1[ i2 ] ];
|
||
|
const idVec2 & v3s = verts[ loop.vindex1[ i3 ] ];
|
||
|
|
||
|
idVec2 a = v1s - v2s;
|
||
|
idVec2 b = v2s - v3s;
|
||
|
|
||
|
ears[i1].cross = a.x * b.y - a.y * b.x;
|
||
|
ears[i1].i1 = i1;
|
||
|
ears[i1].i2 = i2;
|
||
|
ears[i1].i3 = i3;
|
||
|
}
|
||
|
ears.SortWithTemplate( idSort_Ears() );
|
||
|
|
||
|
for ( int i = 0; i < ears.Num(); i++ ) {
|
||
|
if ( ears[i].cross < 0.0f ) {
|
||
|
continue;
|
||
|
}
|
||
|
int i1 = ears[i].i1;
|
||
|
int i2 = ears[i].i2;
|
||
|
int i3 = ears[i].i3;
|
||
|
|
||
|
const idVec2 & v1s = verts[ loop.vindex1[ i1 ] ];
|
||
|
const idVec2 & v2s = verts[ loop.vindex1[ i2 ] ];
|
||
|
const idVec2 & v3s = verts[ loop.vindex1[ i3 ] ];
|
||
|
|
||
|
const idVec2 & v1e = verts[ loop.vindex2[ i1 ] ];
|
||
|
const idVec2 & v2e = verts[ loop.vindex2[ i2 ] ];
|
||
|
const idVec2 & v3e = verts[ loop.vindex2[ i3 ] ];
|
||
|
|
||
|
idMat3 edgeEquations1;
|
||
|
edgeEquations1[0].Set( v1s.x, v1s.y, 1.0f );
|
||
|
edgeEquations1[1].Set( v2s.x, v2s.y, 1.0f );
|
||
|
edgeEquations1[2].Set( v3s.x, v3s.y, 1.0f );
|
||
|
|
||
|
idMat3 edgeEquations2;
|
||
|
edgeEquations2[0].Set( v1e.x, v1e.y, 1.0f );
|
||
|
edgeEquations2[1].Set( v2e.x, v2e.y, 1.0f );
|
||
|
edgeEquations2[2].Set( v3e.x, v3e.y, 1.0f );
|
||
|
|
||
|
edgeEquations1.InverseSelf();
|
||
|
edgeEquations2.InverseSelf();
|
||
|
|
||
|
bool isEar = true;
|
||
|
for ( int j = 0; j < num; j++ ) {
|
||
|
if ( j == i1 || j == i2 || j == i3 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
idVec3 p1;
|
||
|
p1.ToVec2() = verts[ loop.vindex1[j] ];
|
||
|
p1.z = 1.0f;
|
||
|
|
||
|
idVec3 signs1 = p1 * edgeEquations1;
|
||
|
|
||
|
bool b1x = signs1.x > 0;
|
||
|
bool b1y = signs1.y > 0;
|
||
|
bool b1z = signs1.z > 0;
|
||
|
if ( b1x == b1y && b1x == b1z ) {
|
||
|
// point inside
|
||
|
isEar = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
idVec3 p2;
|
||
|
p2.ToVec2() = verts[ loop.vindex2[j] ];
|
||
|
p2.z = 1.0f;
|
||
|
|
||
|
idVec3 signs2 = p2 * edgeEquations2;
|
||
|
|
||
|
bool b2x = signs2.x > 0;
|
||
|
bool b2y = signs2.y > 0;
|
||
|
bool b2z = signs2.z > 0;
|
||
|
if ( b2x == b2y && b2x == b2z ) {
|
||
|
// point inside
|
||
|
isEar = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( isEar ) {
|
||
|
return i1;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::AddUniqueVert
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::AddUniqueVert( idSWFShapeDrawFill & drawFill, const idVec2 & start, const idVec2 & end ) {
|
||
|
if ( morph ) {
|
||
|
for ( int i = 0; i < drawFill.startVerts.Num(); i++ ) {
|
||
|
if ( drawFill.startVerts[i] == start && drawFill.endVerts[i] == end ) {
|
||
|
drawFill.indices.Append( i );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
int index1 = drawFill.startVerts.Append( start );
|
||
|
int index2 = drawFill.endVerts.Append( end );
|
||
|
assert( index1 == index2 );
|
||
|
|
||
|
drawFill.indices.Append( index1 );
|
||
|
} else {
|
||
|
drawFill.indices.Append( drawFill.startVerts.AddUnique( start ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
idSWFShapeParser::ReadFillStyle
|
||
|
========================
|
||
|
*/
|
||
|
void idSWFShapeParser::ReadFillStyle( idSWFBitStream & bitstream ) {
|
||
|
uint16 fillStyleCount = bitstream.ReadU8();
|
||
|
if ( extendedCount && fillStyleCount == 0xFF ) {
|
||
|
fillStyleCount = bitstream.ReadU16();
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < fillStyleCount; i++ ) {
|
||
|
uint8 fillStyleType = bitstream.ReadU8();
|
||
|
|
||
|
swfFillStyle_t & fillStyle = fillDraws.Alloc().style;
|
||
|
fillStyle.type = fillStyleType >> 4;
|
||
|
fillStyle.subType = fillStyleType & 0xF;
|
||
|
|
||
|
if ( fillStyle.type == 0 ) {
|
||
|
if ( morph ) {
|
||
|
bitstream.ReadColorRGBA( fillStyle.startColor );
|
||
|
bitstream.ReadColorRGBA( fillStyle.endColor );
|
||
|
} else {
|
||
|
if ( rgba ) {
|
||
|
bitstream.ReadColorRGBA( fillStyle.startColor );
|
||
|
} else {
|
||
|
bitstream.ReadColorRGB( fillStyle.startColor );
|
||
|
}
|
||
|
fillStyle.endColor = fillStyle.startColor;
|
||
|
}
|
||
|
} else if ( fillStyle.type == 1 ) {
|
||
|
bitstream.ReadMatrix( fillStyle.startMatrix );
|
||
|
if ( morph ) {
|
||
|
bitstream.ReadMatrix( fillStyle.endMatrix );
|
||
|
bitstream.ReadMorphGradient( fillStyle.gradient );
|
||
|
} else {
|
||
|
fillStyle.endMatrix = fillStyle.startMatrix;
|
||
|
bitstream.ReadGradient( fillStyle.gradient, rgba );
|
||
|
}
|
||
|
if ( fillStyle.subType == 3 ) {
|
||
|
assert( morph == false ); // focal gradients aren't allowed in morph shapes
|
||
|
fillStyle.focalPoint = bitstream.ReadFixed8();
|
||
|
}
|
||
|
} else if ( fillStyle.type == 4 ) {
|
||
|
fillStyle.bitmapID = bitstream.ReadU16();
|
||
|
bitstream.ReadMatrix( fillStyle.startMatrix );
|
||
|
if ( morph ) {
|
||
|
bitstream.ReadMatrix( fillStyle.endMatrix );
|
||
|
} else {
|
||
|
fillStyle.endMatrix = fillStyle.startMatrix;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint16 lineStyleCount = bitstream.ReadU8();
|
||
|
if ( extendedCount && lineStyleCount == 0xFF ) {
|
||
|
lineStyleCount = bitstream.ReadU16();
|
||
|
}
|
||
|
|
||
|
lineDraws.SetNum( lineDraws.Num() + lineStyleCount );
|
||
|
lineDraws.SetNum( 0 );
|
||
|
for ( int i = 0; i < lineStyleCount; i++ ) {
|
||
|
swfLineStyle_t & lineStyle = lineDraws.Alloc().style;
|
||
|
lineStyle.startWidth = bitstream.ReadU16();
|
||
|
if ( lineStyle2 ) {
|
||
|
lineStyle.endWidth = lineStyle.startWidth;
|
||
|
|
||
|
uint8 startCapStyle = bitstream.ReadU( 2 );
|
||
|
uint8 joinStyle = bitstream.ReadU( 2 );
|
||
|
bool hasFillFlag = bitstream.ReadBool();
|
||
|
bool noHScaleFlag = bitstream.ReadBool();
|
||
|
bool noVScaleFlag = bitstream.ReadBool();
|
||
|
bool pixelHintingFlag = bitstream.ReadBool();
|
||
|
uint8 reserved = bitstream.ReadU( 5 );
|
||
|
bool noClose = bitstream.ReadBool();
|
||
|
uint8 endCapStyle = bitstream.ReadU( 2 );
|
||
|
if ( joinStyle == 2 ) {
|
||
|
uint16 miterLimitFactor = bitstream.ReadU16();
|
||
|
}
|
||
|
if ( hasFillFlag ) {
|
||
|
// FIXME: read fill style
|
||
|
idLib::Warning( "idSWFShapeParser: Ignoring hasFillFlag" );
|
||
|
} else {
|
||
|
bitstream.ReadColorRGBA( lineStyle.startColor );
|
||
|
lineStyle.endColor = lineStyle.startColor;
|
||
|
}
|
||
|
} else {
|
||
|
if ( morph ) {
|
||
|
lineStyle.endWidth = bitstream.ReadU16();
|
||
|
} else {
|
||
|
lineStyle.endWidth = lineStyle.startWidth;
|
||
|
}
|
||
|
if ( rgba ) {
|
||
|
bitstream.ReadColorRGBA( lineStyle.startColor );
|
||
|
} else {
|
||
|
bitstream.ReadColorRGB( lineStyle.startColor );
|
||
|
}
|
||
|
if ( morph ) {
|
||
|
bitstream.ReadColorRGBA( lineStyle.endColor );
|
||
|
} else {
|
||
|
lineStyle.endColor = lineStyle.startColor;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|