Tool_NewDuplicator/scripts/server/symmetrytable.cs
2022-10-05 16:02:11 -06:00

693 lines
16 KiB
C#

// Analyzes brick geometry to detect common symmetry planes.
// -------------------------------------------------------------------
//Begin generating the symmetry table
function ndCreateSymmetryTable()
{
if($ND::SymmetryTableCreating)
return;
//Tell everyone what is happening
messageAll('', "\c6(\c3New Duplicator\c6) \c6Creating brick symmetry table...");
//Make sure we have the uiname table for manual symmetry
if(!$UINameTableCreated)
createUINameTable();
//Delete previous data
deleteVariables("$ND::Symmetry*");
$ND::SymmetryTableCreated = false;
$ND::SymmetryTableCreating = true;
$ND::SymmetryTableStarted = getRealTime();
$NDT::SimpleCount = 0;
$NDT::MeshCount = 0;
$NDT::AsymXCountTotal = 0;
$NDT::AsymZCountTotal = 0;
echo("ND: Start building brick symmetry table...");
echo("==========================================================================");
ndTickCreateSymmetryTable(0, getDatablockGroupSize());
}
//Process symmetry for 6 datablocks each tick
function ndTickCreateSymmetryTable(%lastIndex, %max)
{
%processed = 0;
%limit = $Server::Dedicated ? 400 : 200;
for(%i = %lastIndex; %i < %max; %i++)
{
%db = getDatablock(%i);
if(%db.getClassName() $= "FxDtsBrickData")
{
%processed += ndTestBrickSymmetry(%db);
if(%processed > %limit)
{
schedule(30, 0, ndTickCreateSymmetryTable, %i + 1, %max);
return;
}
}
}
%simple = $NDT::SimpleCount;
%mesh = $NDT::MeshCount;
%asymx = $NDT::AsymXCountTotal;
%asymz = $NDT::AsymZCountTotal;
echo("==========================================================================");
echo("ND: Finished basic symmetry tests: " @ %simple @ " simple, " @ %mesh @ " with mesh, " @ %asymx @ " asymmetric, " @ %asymz @ " z-asymmetric");
ndFindSymmetricPairs();
}
//Attempt to find symmetric pairs between asymmetric bricks
function ndFindSymmetricPairs()
{
echo("ND: Starting X symmetric pair search...");
echo("==========================================================================");
for(%i = 0; %i < $NDT::AsymXCountTotal; %i++)
{
%index = $NDT::AsymXBrick[%i];
if(!$NDT::SkipAsymX[%index])
ndFindSymmetricPairX(%index);
}
echo("==========================================================================");
echo("ND: Finished finding symmetric pairs");
echo("ND: Starting Z symmetric pair search...");
echo("==========================================================================");
for(%i = 0; %i < $NDT::AsymZCountTotal; %i++)
{
%index = $NDT::AsymZBrick[%i];
if(!$NDT::SkipAsymZ[%index])
ndFindSymmetricPairZ(%index);
}
//Delete temporary arrays
deleteVariables("$NDT::*");
echo("==========================================================================");
echo("ND: Finished finding Z symmetric pairs");
echo("ND: Symmetry table complete in " @ (getRealTime() - $ND::SymmetryTableStarted) / 1000 @ " seconds");
$ND::SymmetryTableCreated = true;
$ND::SymmetryTableCreating = false;
//We're done!
%seconds = mFloatLength((getRealTime() - $ND::SymmetryTableStarted) / 1000, 0);
messageAll('', "\c6(\c3New Duplicator\c6) \c6Created brick symmetry table in " @ %seconds @ " seconds.");
}
//Test symmetry of a single blb file
function ndTestBrickSymmetry(%datablock)
{
//Open blb file
%file = new FileObject();
%file.openForRead(%datablock.brickFile);
//Skip brick size - irrelevant
%file.readLine();
//Simple bricks are always fully symmetric
if(%file.readLine() $= "BRICK")
{
$NDT::SimpleCount++;
$ND::Symmetry[%datablock] = 1;
$ND::SymmetryZ[%datablock] = true;
%file.close();
%file.delete();
return 2;
}
//Not simple, get mesh data index in temp arrays
%dbi = $NDT::MeshCount;
$NDT::MeshCount++;
//Load mesh from blb file
%faces = 0;
%points = 0;
while(!%file.isEOF())
{
//Find start of face
%line = %file.readLine();
if(getSubStr(%line, 0, 4) $= "TEX:")
{
%tex = trim(getSubStr(%line, 4, strLen(%line)));
//Top and bottom faces have different topology, skip
if(%tex $= "TOP" || %tex $= "BOTTOMLOOP" || %tex $= "BOTTOMEDGE")
continue;
//Add face
$NDT::FaceTexId[%dbi, %faces] = (%tex $= "SIDE" ? 0 : (%tex $= "RAMP" ? 1 : 2));
//Skip useless lines
while(trim(%file.readLine()) !$= "POSITION:") {}
//Add the 4 points
for(%i = 0; %i < 4; %i++)
{
//Read next line
%line = %file.readLine();
//Skip useless blank lines
while(!strLen(%line))
%line = %file.readLine();
//Remove formatting from point
%pos = vectorAdd(%line, "0 0 0");
//Round down two digits to fix float errors
%pos = mFloatLength(getWord(%pos, 0), 3) * 1.0
SPC mFloatLength(getWord(%pos, 1), 3) * 1.0
SPC mFloatLength(getWord(%pos, 2), 3) * 1.0;
//Get index of this point
if(!%ptIndex = $NDT::PtAtPosition[%dbi, %pos])
{
//Points array is 1-indexed so we can quickly test !PtAtPosition[...]
%points++;
%ptIndex = %points;
//Add new point to array
$NDT::PtPosition[%dbi, %points] = %pos;
$NDT::PtAtPosition[%dbi, %pos] = %points;
}
//Add face to point
if(!$NDT::PtInFace[%dbi, %faces, %ptIndex])
{
//Increase first then subtract 1 to get 0 the first time
%fIndex = $NDT::FacesAtPt[%dbi, %ptIndex]++ - 1;
$NDT::FaceAtPt[%dbi, %ptIndex, %fIndex] = %faces;
}
//Add point to face
$NDT::FacePt[%dbi, %faces, %i] = %ptIndex;
$NDT::PtInFace[%dbi, %faces, %ptIndex] = true;
}
//Added face
%faces++;
}
}
$NDT::FaceCount[%dbi] = %faces;
$NDT::Datablock[%dbi] = %datablock;
%file.close();
%file.delete();
//Possible symmetries:
// 0: asymmetric
// 1: x & y
// 2: x
// 3: y
// 4: x+y
// 5: x-y
//We will test in the following order:
// X
// Y
// X+Y
// X-Y
// Z
//Check manual symmetry first
%sym = $ND::ManualSymmetry[%datablock.uiname];
if(%sym !$= "")
{
if(!%sym)
{
//Try to find the other brick
%otherdb = $UINameTable[$ND::ManualSymmetryDB[%datablock.uiname]];
%offset = $ND::ManualSymmetryOffset[%datablock.uiname];
//...
if(!isObject(%otherdb))
{
%otherdb = "";
%offset = 0;
echo("ND: " @ %datablock.uiname @ " has manual symmetry but the paired brick does not exist");
}
$ND::SymmetryXDatablock[%datablock] = %otherdb;
$ND::SymmetryXOffset[%datablock] = %offset;
}
%manualSym = true;
}
else
{
%failX = ndTestSelfSymmetry(%dbi, 0);
%failY = ndTestSelfSymmetry(%dbi, 1);
//Diagonals are only needed if the brick isn't symmetric to the axis
if(%failX && %failY)
%failXY = ndTestSelfSymmetry(%dbi, 3);
//One diagonal is enough, only test second if first one fails
if(%failXY)
%failYX = ndTestSelfSymmetry(%dbi, 4);
//X, Y symmetry
if(!%failX && !%failY)
%sym = 1;
else if(!%failX)
%sym = 2;
else if(!%failY)
%sym = 3;
else if(!%failXY)
%sym = 4;
else if(!%failYX)
%sym = 5;
else
%sym = 0;
}
//Check manual symmetry first
%symZ = $ND::ManualSymmetryZ[%datablock.uiname];
//Z symmetry
if(%symZ !$= "")
{
if(!%symZ)
{
//Try to find the other brick
%otherdb = $UINameTable[$ND::ManualSymmetryZDB[%datablock.uiname]];
%offset = $ND::ManualSymmetryZOffset[%datablock.uiname];
//...
if(!isObject(%otherdb))
{
%otherdb = "";
%offset = 0;
echo("ND: " @ %datablock.uiname @ " has manual Z symmetry but the paired brick does not exist");
}
$ND::SymmetryZDatablock[%datablock] = %otherdb;
$ND::SymmetryZOffset[%datablock] = %offset;
}
%manualZSym = true;
}
else
%symZ = !ndTestSelfSymmetry(%dbi, 2);
if(!%manualSym && !%sym)
{
//Add to lookup table of X-asymmetric bricks of this type
%bIndex = $NDT::AsymXCount[%faces, %symZ]++;
$NDT::AsymXBrick[%faces, %symZ, %bIndex] = %dbi;
//Add to list of asymmetric bricks
$NDT::AsymXBrick[$NDT::AsymXCountTotal] = %dbi;
$NDT::AsymXCountTotal++;
}
if(!%manualZSym && !%symZ)
{
//Add to lookup table of Z-asymmetric bricks of this type
%bIndex = $NDT::AsymZCount[%faces, %sym]++;
$NDT::AsymZBrick[%faces, %sym, %bIndex] = %dbi;
//Add to list of Z-asymmetric bricks
$NDT::AsymZBrick[$NDT::AsymZCountTotal] = %dbi;
$NDT::AsymZCountTotal++;
}
//Save symmetries
$ND::Symmetry[%datablock] = %sym;
$ND::SymmetryZ[%datablock] = %symZ;
//Return processed faces
return %faces;
}
//Find symmetric pair between two bricks on X axis
function ndFindSymmetricPairX(%dbi)
{
if($NDT::SkipAsymX[%dbi])
return;
%datablock = $NDT::Datablock[%dbi];
%zsym = $ND::SymmetryZ[%datablock];
%faces = $NDT::FaceCount[%dbi];
%count = $NDT::AsymXCount[%faces, %zsym];
//Only potential match is the brick itself - fail
if(%count == 1)
{
echo("ND: No X match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")");
return;
}
%off = -1;
$NDT::SkipAsymX[%dbi] = true;
for(%i = 1; %i <= %count; %i++)
{
%other = $NDT::AsymXBrick[%faces, %zsym, %i];
//Don't compare with bricks that already have a pair
if($NDT::SkipAsymX[%other])
continue;
//Test all 4 possible rotations
//Not using loop due to lack of goto command
if(!ndTestPairSymmetry(%dbi, %other, true, 0))
{
%off = 0;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, true, 1))
{
%off = 1;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, true, 2))
{
%off = 2;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, true, 3))
{
%off = 3;
break;
}
}
if(%off != -1)
{
%otherdb = $NDT::Datablock[%other];
//Save symmetry
$ND::SymmetryXDatablock[%datablock] = %otherdb;
$ND::SymmetryXOffset[%datablock] = %off;
$ND::SymmetryXDatablock[%otherdb] = %datablock;
$ND::SymmetryXOffset[%otherdb] = -%off;
//No need to process the other brick again
$NDT::SkipAsymX[%other] = true;
}
else
echo("ND: No X match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")");
}
//Find symmetric pair between two bricks on Z axis
function ndFindSymmetricPairZ(%dbi)
{
if($NDT::SkipAsymZ[%dbi])
return;
%datablock = $NDT::Datablock[%dbi];
%sym = $ND::Symmetry[%datablock];
%faces = $NDT::FaceCount[%dbi];
%count = $NDT::AsymZCount[%faces, %sym];
//Only potential match is the brick itself - fail
if(%count == 1)
{
echo("ND: No Z match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")");
return;
}
%off = -1;
for(%i = 1; %i <= %count; %i++)
{
%other = $NDT::AsymZBrick[%faces, %sym, %i];
//Don't compare with bricks that already have a pair
if($NDT::SkipAsymZ[%other])
continue;
//Test all 4 possible rotations
//Not using loop due to lack of goto command
if(!ndTestPairSymmetry(%dbi, %other, false, 0))
{
%off = 0;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, false, 1))
{
%off = 1;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, false, 2))
{
%off = 2;
break;
}
if(!ndTestPairSymmetry(%dbi, %other, false, 3))
{
%off = 3;
break;
}
}
//It's possible for a brick to match itself rotated
//here, so only mark it after the search
$NDT::SkipAsymZ[%dbi] = true;
if(%off != -1)
{
%otherdb = $NDT::Datablock[%other];
//Save symmetry
$ND::SymmetryZDatablock[%datablock] = %otherdb;
$ND::SymmetryZOffset[%datablock] = %off;
$ND::SymmetryZDatablock[%otherdb] = %datablock;
$ND::SymmetryZOffset[%otherdb] = -%off;
//No need to process the other brick again
$NDT::SkipAsymZ[%other] = true;
}
else
echo("ND: No Z match for " @ %datablock.getName() @ " (" @ %datablock.category @ "/" @ %datablock.subCategory @ "/" @ %datablock.uiname @ ")");
}
//Test a mesh for a single symmetry plane in itself
function ndTestSelfSymmetry(%dbi, %plane)
{
%fail = false;
%faces = $NDT::FaceCount[%dbi];
for(%i = 0; %i < %faces; %i++)
{
//If this face was already used by another mirror, skip
if(%skipFace[%i])
continue;
//Attempt to find the mirrored points
for(%j = 0; %j < 4; %j++)
{
%pt = $NDT::FacePt[%dbi, %i, %j];
//Do we already know the mirrored one?
if(%mirrPt[%pt])
{
%mirr[%j] = %mirrPt[%pt];
continue;
}
//Get position of point
%v = $NDT::PtPosition[%dbi, %pt];
//Get point at mirrored position based on plane
switch$(%plane)
{
//Flip X
case 0: %mirr = $NDT::PtAtPosition[%dbi, -firstWord(%v) SPC restWords(%v)];
//Flip Y
case 1: %mirr = $NDT::PtAtPosition[%dbi, getWord(%v, 0) SPC -getWord(%v, 1) SPC getWord(%v, 2)];
//Flip Z
case 2: %mirr = $NDT::PtAtPosition[%dbi, getWords(%v, 0, 1) SPC -getWord(%v, 2)];
//Mirror along X+Y
case 3: %mirr = $NDT::PtAtPosition[%dbi, -getWord(%v, 1) SPC -getWord(%v, 0) SPC getWord(%v, 2)];
//Mirror along X-Y
default: %mirr = $NDT::PtAtPosition[%dbi, getWord(%v, 1) SPC getWord(%v, 0) SPC getWord(%v, 2)];
}
if(%mirr)
{
%mirrPt[%pt] = %mirr;
%mirrPt[%mirr] = %pt;
%mirr[%j] = %mirr;
}
else
{
%fail = true;
break;
}
}
if(%fail)
break;
//Test whether the points have a common face
%fail = true;
%count = $NDT::FacesAtPt[%dbi, %mirr0];
for(%j = 0; %j < %count; %j++)
{
%potentialFace = $NDT::FaceAtPt[%dbi, %mirr0, %j];
//Mirrored face must have the same texture id
if($NDT::FaceTexId[%dbi, %i] != $NDT::FaceTexId[%dbi, %potentialFace])
continue;
//Check whether remaining points are in the face
if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr1])
continue;
if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr2])
continue;
if(!$NDT::PtInFace[%dbi, %potentialFace, %mirr3])
continue;
//We found a matching face!
%skipFace[%potentialFace] = true;
%fail = false;
break;
}
if(%fail)
break;
}
return %fail;
}
//Test X or Z symmetry between two meshes with rotation offset
function ndTestPairSymmetry(%dbi, %other, %plane, %rotation)
{
%fail = false;
%faces = $NDT::FaceCount[%dbi];
for(%i = 0; %i < %faces; %i++)
{
//Attempt to find the mirrored points
for(%j = 0; %j < 4; %j++)
{
%pt = $NDT::FacePt[%dbi, %i, %j];
//Do we already know the mirrored one?
if(%mirrPt[%pt])
{
%mirr[%j] = %mirrPt[%pt];
continue;
}
//Get position of point
%v = $NDT::PtPosition[%dbi, %pt];
//true = X, false = Z
if(%plane)
{
//Get point at mirrored position based on rotation
switch(%rotation)
{
//Flip X
case 0: %mirr = $NDT::PtAtPosition[%other, -firstWord(%v) SPC restWords(%v)];
//Flip X, rotate 90
case 1: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 1) SPC getWord(%v, 0) SPC getWord(%v, 2)];
//Flip X, rotate 180
case 2: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 0) SPC -getWord(%v, 1) SPC getWord(%v, 2)];
//Flip X, rotate 270
default: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 1) SPC -getWord(%v, 0) SPC getWord(%v, 2)];
}
}
else
{
//Get point at mirrored position based on rotation
switch(%rotation)
{
//Flip Z
case 0: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 0) SPC getWord(%v, 1) SPC -getWord(%v, 2)];
//Flip Z, rotate 90
case 1: %mirr = $NDT::PtAtPosition[%other, getWord(%v, 1) SPC -getWord(%v, 0) SPC -getWord(%v, 2)];
//Flip Z, rotate 180
case 2: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 0) SPC -getWord(%v, 1) SPC -getWord(%v, 2)];
//Flip Z, rotate 270
default: %mirr = $NDT::PtAtPosition[%other, -getWord(%v, 1) SPC getWord(%v, 0) SPC -getWord(%v, 2)];
}
}
if(%mirr)
{
%mirrPt[%pt] = %mirr;
%mirr[%j] = %mirr;
}
else
{
%fail = true;
break;
}
}
if(%fail)
break;
//Test whether the points have a common face
%fail = true;
%count = $NDT::FacesAtPt[%other, %mirr0];
for(%j = 0; %j < %count; %j++)
{
%potentialFace = $NDT::FaceAtPt[%other, %mirr0, %j];
//Mirrored face must have the same texture id
if($NDT::FaceTexId[%dbi, %i] != $NDT::FaceTexId[%other, %potentialFace])
continue;
//Check whether remaining points are in the face
if(!$NDT::PtInFace[%other, %potentialFace, %mirr1])
continue;
if(!$NDT::PtInFace[%other, %potentialFace, %mirr2])
continue;
if(!$NDT::PtInFace[%other, %potentialFace, %mirr3])
continue;
//We found a matching face!
%fail = false;
break;
}
if(%fail)
break;
}
return %fail;
}