diff --git a/opennurbs_3dm_settings.cpp b/opennurbs_3dm_settings.cpp
index 860cf2fb..1eaaca66 100644
--- a/opennurbs_3dm_settings.cpp
+++ b/opennurbs_3dm_settings.cpp
@@ -3409,8 +3409,11 @@ bool ON_EarthAnchorPoint::ModelLocationIsSet() const
{
return (
m_model_point.IsValid()
- && m_model_north.IsValid()
- && m_model_east.IsValid()
+ && m_model_north.IsNotZero()
+ && m_model_east.IsNotZero()
+ && m_model_north.Length() > ON_ZERO_TOLERANCE
+ && m_model_east.Length() > ON_ZERO_TOLERANCE
+ && fabs(m_model_north.UnitVector()* m_model_east.UnitVector()) <= 1e-8
);
}
@@ -3800,6 +3803,134 @@ void ON_EarthAnchorPoint::SetModelLocation(
SetModelEast(model_east);
}
+const ON_Xform ON_EarthAnchorPoint::Internal_KMLOrientationXform() const
+{
+ if (false == this->ModelLocationIsSet())
+ {
+ ON_ERROR("Corrupt model location.");
+ return ON_Xform::Nan;
+ }
+
+ ON_Plane model_directions;
+ if (false == model_directions.CreateFromFrame(ON_3dPoint::Origin, ModelEast(), ModelNorth()))
+ return ON_Xform::Nan;
+ if (false == model_directions.IsValid())
+ return ON_Xform::Nan;
+
+ // The KML orientation moves the model so that
+ // east = x-axis
+ // north = Y-axis
+ // up = Z-axis
+ // https://developers.google.com/kml/documentation/kmlreference#orientation
+ // "Describes rotation of a 3D model's coordinate system to position the object in Google Earth."
+
+ ON_Xform KMLorientation;
+ KMLorientation.Rotation(model_directions, ON_Plane::World_xy);
+
+ const ON_3dPoint M[4] = {
+ ON_3dPoint::Origin,
+ ON_3dPoint::Origin + ModelEast().UnitVector(),
+ ON_3dPoint::Origin + ModelNorth().UnitVector(),
+ ON_3dPoint::Origin + ON_CrossProduct(ModelEast().UnitVector(), ModelNorth().UnitVector())
+ };
+ const ON_3dPoint E[sizeof(M)/sizeof(M[0])] = {
+ ON_3dPoint::Origin,
+ ON_3dPoint::Origin + ON_3dVector::XAxis,
+ ON_3dPoint::Origin + ON_3dVector::YAxis,
+ ON_3dPoint::Origin + ON_3dVector::ZAxis
+ };
+ ON_3dPoint rM[sizeof(M) / sizeof(M[0])] = {};
+ double err[sizeof(M) / sizeof(M[0])] = {};
+ double maxerr = 0.0;
+ for (size_t i = 0; i < sizeof(M) / sizeof(M[0]); ++i)
+ {
+ rM[i] = KMLorientation * M[i];
+ double d = E[i].DistanceTo(rM[i]);
+ if (d != d || d > err[i])
+ err[i] = d;
+ if (err[i] != err[i] || err[i] > maxerr)
+ maxerr = err[i];
+ }
+
+ if (false == maxerr <= ON_ZERO_TOLERANCE)
+ {
+ ON_ERROR("Sloppy rotation matrix.");
+ }
+
+ return KMLorientation;
+}
+
+bool ON_EarthAnchorPoint::GetKMLOrientationAnglesRadians(double& heading_radians, double& tilt_radians, double& roll_radians) const
+{
+ heading_radians = ON_DBL_QNAN;
+ tilt_radians = ON_DBL_QNAN;
+ roll_radians = ON_DBL_QNAN;
+ const ON_Xform rot = Internal_KMLOrientationXform();
+ return rot.GetKMLOrientationAnglesRadians(heading_radians, tilt_radians, roll_radians);
+}
+
+bool ON_EarthAnchorPoint::GetKMLOrientationAnglesDegrees(double& heading_degrees, double& tilt_degrees, double& roll_degrees) const
+{
+ heading_degrees = ON_DBL_QNAN;
+ tilt_degrees = ON_DBL_QNAN;
+ roll_degrees = ON_DBL_QNAN;
+ const ON_Xform rot = Internal_KMLOrientationXform();
+ return rot.GetKMLOrientationAnglesDegrees(heading_degrees, tilt_degrees, roll_degrees);
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationHeadingAngleRadians() const
+{
+ double heading_radians = ON_DBL_QNAN;
+ double tilt_radians = ON_DBL_QNAN;
+ double roll_radians = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesRadians(heading_radians, tilt_radians, roll_radians);
+ return rc ? heading_radians : ON_DBL_QNAN;
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationTiltAngleRadians() const
+{
+ double heading_radians = ON_DBL_QNAN;
+ double tilt_radians = ON_DBL_QNAN;
+ double roll_radians = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesRadians(heading_radians, tilt_radians, roll_radians);
+ return rc ? tilt_radians : ON_DBL_QNAN;
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationRollAngleRadians() const
+{
+ double heading_radians = ON_DBL_QNAN;
+ double tilt_radians = ON_DBL_QNAN;
+ double roll_radians = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesRadians(heading_radians, tilt_radians, roll_radians);
+ return rc ? roll_radians : ON_DBL_QNAN;
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationHeadingAngleDegrees() const
+{
+ double heading_degrees = ON_DBL_QNAN;
+ double tilt_degrees = ON_DBL_QNAN;
+ double roll_degrees = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesDegrees(heading_degrees, tilt_degrees, roll_degrees);
+ return rc ? heading_degrees : ON_DBL_QNAN;
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationTiltAngleDegrees() const
+{
+ double heading_degrees = ON_DBL_QNAN;
+ double tilt_degrees = ON_DBL_QNAN;
+ double roll_degrees = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesDegrees(heading_degrees, tilt_degrees, roll_degrees);
+ return rc ? tilt_degrees : ON_DBL_QNAN;
+}
+
+const double ON_EarthAnchorPoint::KMLOrientationRollAngleDegrees() const
+{
+ double heading_degrees = ON_DBL_QNAN;
+ double tilt_degrees = ON_DBL_QNAN;
+ double roll_degrees = ON_DBL_QNAN;
+ const bool rc = GetKMLOrientationAnglesDegrees(heading_degrees, tilt_degrees, roll_degrees);
+ return rc ? roll_degrees : ON_DBL_QNAN;
+}
//////////////////////////////////////////////////////////////////////////////////////////
//
diff --git a/opennurbs_3dm_settings.h b/opennurbs_3dm_settings.h
index 8f33490f..2687ac1b 100644
--- a/opennurbs_3dm_settings.h
+++ b/opennurbs_3dm_settings.h
@@ -1122,6 +1122,138 @@ public:
ON_3dVector model_east
);
+ /*
+ Description:
+ Find the Keyhole Markup Language (KML) orientation angles (in radians) of a rotation
+ transformation that maps model (east,north,up) to ((1,0,0),(0,1,0),(0,0,1)).
+ KML Earth Z axis = up, KML Earth X axis = east, KML Earth Y axis = north.
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ Parameters:
+ heading_radians - [out]
+ angle (in radians) of rotation around KML Earth Z axis (Earth up).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ tilt_radians - [out]
+ angle (in radians) of rotation around KML Earth X axis (Earth east).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ roll_radians - [out]
+ angle (in radians) of rotation around KML Earth Y axis (Earth north).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ Returns:
+ True if the model location is set (this->ModelLocationIsSet() is true)
+ and the KML orientation angles are returned.
+ Otherwise false is returned and all of the angle values are ON_DLB_QNAN.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ bool GetKMLOrientationAnglesRadians(
+ double& heading_radians,
+ double& tilt_radians,
+ double& roll_radians
+ ) const;
+
+ /*
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation heading angle in radians is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationHeadingAngleRadians() const;
+
+ /*
+ Returns:
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation tilt angle in radians is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationTiltAngleRadians() const;
+
+ /*
+ Returns:
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation roll angle in radians is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationRollAngleRadians() const;
+
+ /*
+ Description:
+ Find the Keyhole Markup Language (KML) orientation angles (in degrees) of a rotation
+ transformation that maps model (east,north,up) to ((1,0,0),(0,1,0),(0,0,1)).
+ KML Earth Z axis = up, KML Earth X axis = east, KML Earth Y axis = north.
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ Parameters:
+ heading_degrees - [out]
+ angle (in degrees) of rotation around KML Earth Z axis (Earth up).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ tilt_degrees - [out]
+ angle (in degrees) of rotation around KML Earth X axis (Earth east).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ roll_degrees - [out]
+ angle (in degrees) of rotation around KML Earth Y axis (Earth north).
+ NOTE WELL: In KML, postive rotations are CLOCKWISE looking down
+ specied axis vector towards the origin. This is rotation direction
+ is opposite the conventional "right hand rule."
+ Returns:
+ True if the model location is set (this->ModelLocationIsSet() is true)
+ and the KML orientation angles are returned.
+ Otherwise false is returned and all of the angle values are ON_DLB_QNAN.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ bool GetKMLOrientationAnglesDegrees(
+ double& heading_degrees,
+ double& tilt_degrees,
+ double& roll_degrees
+ ) const;
+
+ /*
+ Returns:
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation heading angle in degrees is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationHeadingAngleDegrees() const;
+
+ /*
+ Returns:
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation tilt angle in degrees is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationTiltAngleDegrees() const;
+
+ /*
+ If the model location is set (this->ModelLocationIsSet() is true), then the
+ Keyhole Markup Language orientation roll angle in degrees is returned.
+ Otherwise ON_DBL_QNAN is returned.
+ See Also:
+ https://developers.google.com/kml/documentation/kmlreference#orientation
+ */
+ const double KMLOrientationRollAngleDegrees() const;
+
private:
// Point on the Earth
// Latitude (degrees): +90 = north pole, 0 = equator, -90 = south pole
@@ -1203,6 +1335,9 @@ public:
const ON_UnitSystem& model_unit_system,
ON_Xform& model_to_earth
) const;
+
+private:
+ const ON_Xform Internal_KMLOrientationXform() const;
};
diff --git a/opennurbs_archive.h b/opennurbs_archive.h
index 2bcc47a6..852a8284 100644
--- a/opennurbs_archive.h
+++ b/opennurbs_archive.h
@@ -2004,7 +2004,7 @@ public:
Returns:
Endian-ness of the cpu reading this file.
Remarks:
- 3dm files are alwasy saved with little endian byte order.
+ 3dm files are always saved with little endian byte order.
*/
ON::endian Endian() const; // endian-ness of cpu
@@ -4801,7 +4801,7 @@ private:
private:
// endian-ness of the cpu reading this file.
- // 3dm files are alwasy saved with little endian byte order.
+ // 3dm files are always saved with little endian byte order.
const ON::endian m_endian = ON::Endian();
const ON::archive_mode m_mode = ON::archive_mode::unset_archive_mode;
diff --git a/opennurbs_archive_manifest.cpp b/opennurbs_archive_manifest.cpp
index a66f24f3..a3942aad 100644
--- a/opennurbs_archive_manifest.cpp
+++ b/opennurbs_archive_manifest.cpp
@@ -3190,7 +3190,7 @@ const ON_ComponentManifestItem_PRIVATE* ON_ComponentManifestTableIndex::SystemIt
{
if (m_bIndexedComponent && sytem_item_index < 0 && sytem_item_index > ON_UNSET_INT_INDEX )
{
- // The linked list of system items is alwasy short - hash tables and sorting are a waste of time
+ // The linked list of system items is always short - hash tables and sorting are a waste of time
for (const ON_ComponentManifestItem_PRIVATE* system_item = m_first_system_item; nullptr != system_item; system_item = system_item->m_next)
{
if ( system_item->Index() == sytem_item_index )
diff --git a/opennurbs_array.h b/opennurbs_array.h
index 63ad5677..ec3e5864 100644
--- a/opennurbs_array.h
+++ b/opennurbs_array.h
@@ -1633,5 +1633,162 @@ void ON_SHA1_Accumulate3dVectorArray(
// will work on the template functions.
#include "opennurbs_array_defs.h"
+class ON_CLASS ON_Big5UnicodePair
+{
+public:
+ ON_Big5UnicodePair() = default;
+ ~ON_Big5UnicodePair() = default;
+ ON_Big5UnicodePair(const ON_Big5UnicodePair&) = default;
+ ON_Big5UnicodePair& operator=(const ON_Big5UnicodePair&) = default;
+
+public:
+ ///
+ /// An array sorted by BIG5 code points and useful for converting BIG5 code points to Unicode code points.
+ ///
+ /// Returns an array sorted by ON_Big5UnicodePair::CompareBig5AndUnicodeCodePoints()
+ static const ON_SimpleArray< ON_Big5UnicodePair >& Big5ToUnicode();
+
+ ///
+ /// An array sorted by Unicode code points and useful for converting Unicode code points to BIG5 code points.
+ ///
+ /// Returns an array sorted by ON_Big5UnicodePair::CompareUnicodeAndBig5CodePoints()
+ static const ON_SimpleArray< ON_Big5UnicodePair >& UnicodeToBig5();
+
+public:
+ static const ON_Big5UnicodePair Null;
+
+ ///
+ /// ON_Big5UnicodePair::Error.Big5() = ON_Big5CodePoint::Error and ON_Big5UnicodePair::Error.Unicode() = ON_UnicodeShortCodePoint::Error.
+ ///
+ static const ON_Big5UnicodePair Error;
+
+ ///
+ /// Create a BIG5 - Unicode code point pair.
+ ///
+ ///
+ /// BIG5 code point.
+ ///
+ ///
+ /// Unicode code point.
+ ///
+ ///
+ static const ON_Big5UnicodePair Create(
+ ON_Big5CodePoint big5_code_point,
+ ON_UnicodeShortCodePoint unicode_code_point
+ );
+
+ static const ON_Big5UnicodePair Create(
+ unsigned int big5_code_point,
+ unsigned int unicode_code_point
+ );
+
+ ///
+ /// Determine if both code points in this pair are 0.
+ ///
+ /// True if both code points are 0.
+ bool IsNull() const;
+
+ ///
+ /// Determing if the values stored as the BIG5 and Unicode code points are equal nonzero ASCII code points.
+ /// ASCII code point are in the range 0-0x7F (127 decimal).
+ /// Unicode extends ASCII. Strictly speaking, BIG5 does not extend ASCII, but it is common to mix
+ /// single bytes ASCII and double byte BIG5 encodings in the same char string.
+ /// BIG5 is a double byte string encoding with the first byte in the range 0x81 to 0xFE, the
+ /// minimum BIG5 code point is 0x8140 and the maximum BIG5 code point is 0xFEFE.
+ /// Thus it is possible to mix ASCII and BIG5 encodings in the same char string.
+ ///
+ ///
+ /// Value to return if both code points are 0.
+ ///
+ /// True if both code points are equal and ASCII code points (0 to 0x7F).
+ bool IsASCII(bool bNullIsASCII) const;
+
+ ///
+ /// Determine if the pair of code points is valid.
+ /// If the values for BIG5 and Unicode code point values are < 0xFF and equal, the pair is considered valid.
+ /// Use IsASCII() if you need to treat nonzero ASCII code points differently.
+ ///
+ ///
+ /// Value to return if this pair is null.
+ ///
+ ///
+ /// Value to return if this pair is an ASCII code point.
+ ///
+ /// True if the BIG5 and Unicode code points are both valid or IsASCII() is true.
+ bool IsValid(bool bNullIsValid, bool bASCIICodePointIsValid) const;
+
+ const ON_Big5CodePoint Big5() const;
+ const ON_UnicodeShortCodePoint Unicode() const;
+
+ unsigned int Big5CodePoint() const;
+ unsigned int UnicodeCodePoint() const;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Value to return if this pair is null.
+ ///
+ ///
+ /// Value to return if this pair is an ASCII code point.
+ ///
+ ///
+ bool IsStandard(bool bNullIsValid, bool bASCIICodePointIsStandard) const;
+
+ ///
+ ///
+ ///
+ /// Returns true if this pair is valid and at least one of the code points is a private use code point.
+ ///
+ bool IsPrivateUse() const;
+
+ ///
+ /// Compares the BIG5 code point.
+ ///
+ ///
+ ///
+ ///
+ static int CompareBig5CodePoint(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs);
+
+ ///
+ /// Compares the Unicode code point.
+ ///
+ ///
+ ///
+ ///
+ static int CompareUnicodeCodePoint(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs);
+
+ ///
+ /// Dictionary compare (BIG5 code point first, Unicode code point second).
+ ///
+ ///
+ ///
+ ///
+ static int CompareBig5AndUnicodeCodePoints(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs);
+
+ ///
+ /// Dictionary compare (Unicode code point first, BIG5 code point second).
+ ///
+ ///
+ ///
+ ///
+ static int CompareUnicodeAndBig5CodePoints(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs);
+
+private:
+ ON_Big5CodePoint m_big5;
+ ON_UnicodeShortCodePoint m_unicode;
+};
+
+ON_DECL bool operator==(ON_Big5UnicodePair lhs, ON_Big5UnicodePair rhs);
+ON_DECL bool operator!=(ON_Big5UnicodePair lhs, ON_Big5UnicodePair rhs);
+
+#if defined(ON_DLL_TEMPLATE)
+
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+
+#endif
#endif
diff --git a/opennurbs_brep.cpp b/opennurbs_brep.cpp
index 985f6471..87810754 100644
--- a/opennurbs_brep.cpp
+++ b/opennurbs_brep.cpp
@@ -1665,7 +1665,9 @@ bool ON_Brep::Transform( const ON_Xform& xform )
if ( 1 != tmp.IsSimilarity() )
bEvAnalysisMesh = true;
}
- if ( bEvAnalysisMesh && face.m_analysis_mesh->EvaluateMeshGeometry(*srf) )
+ // 1 Sept 2021, Mikko, RH-65468:
+ // Added null check to prevent a crash.
+ if ( bEvAnalysisMesh && nullptr != srf && face.m_analysis_mesh->EvaluateMeshGeometry(*srf) )
{
// 28 Sept 2012, Mikko:
// Apply the "29 September 2003 Dale Lear" fix above also to analysis meshes.
@@ -1688,10 +1690,12 @@ bool ON_Brep::Transform( const ON_Xform& xform )
{
if ( face.m_bRev )
{
- int ni, ncnt = face.m_analysis_mesh->m_N.Count();
+ // 10 June 2021, Mikko, RH-64582:
+ // Fixed typo that caused a crash, changed "m_analysis_mesh" to "m_preview_mesh".
+ int ni, ncnt = face.m_preview_mesh->m_N.Count();
for ( ni = 0; ni < ncnt; ni++ )
{
- face.m_analysis_mesh->m_N[ni] = -face.m_analysis_mesh->m_N[ni];
+ face.m_preview_mesh->m_N[ni] = -face.m_preview_mesh->m_N[ni];
}
}
}
@@ -5468,47 +5472,75 @@ bool ON_Brep::IsValid( ON_TextLog* text_log ) const
}
- // GBA 28-Aug-20 RH-60112 and RH-58462
- // Adding bounding box (m_bbox) validation tests.
- // Brep bounding box is cached and persists across sessions (since at least Rhino 6).
- // In Rhino6 and earlier, the bounding box included entire underlying surface.
- // Rhino7 bounding box calculation now uses "shrinked" surfaces.
- // A bounding box is now reported as invalid if it could be significantly
- // reduced by being recalculated.
- if (!m_bbox.IsEmpty())
+ // Dale Lear Fix https://mcneel.myjetbrains.com/youtrack/issue/RH-64277
+ // The commented out tests might have helped in debugging make2d, but they are making a mess of things now.
+ // In my view, destroying/remaking a box is beyond the scope of an appropriate IsValid() test.
+ // In addintion to replacing the test below with a warning Greg can use while debugging
+ // make2d, I've fixed the IO code in ON_Brep::Read() so old boxes are now reliably updated
+ // to their smaller versions.
+
+ //////// GBA 28-Aug-20 RH-60112 and RH-58462
+ //////// Adding bounding box (m_bbox) validation tests.
+ //////// Brep bounding box is cached and persists across sessions (since at least Rhino 6).
+ //////// In Rhino6 and earlier, the bounding box included entire underlying surface.
+ //////// Rhino7 bounding box calculation now uses "shrinked" surfaces.
+ //////// A bounding box is now reported as invalid if it could be significantly
+ //////// reduced by being recalculated.
+ //////if (!m_bbox.IsEmpty())
+ //////{
+ ////// if (!m_bbox.IsValid())
+ ////// {
+ ////// if (text_log)
+ ////// text_log->Print("Bounding Box is not valid.\n");
+ ////// return ON_BrepIsNotValid();
+ ////// }
+ ////// else
+ ////// {
+ ////// ON_BoundingBox orig_box = m_bbox;
+ ////// ON_BoundingBox computed_box;
+ ////// {
+ ////// ON_Brep* this_nonconst = const_cast(this);
+ ////// this_nonconst->ClearBoundingBox();
+ ////// computed_box = BoundingBox();
+ ////// this_nonconst->m_bbox = orig_box; // restore box as it as it was.
+ ////// }
+ ////// // expand the computed_box before we do the incusion test
+ ////// // I'm trying to avoid making a lot of objects created in Rhino 6 and earlier
+ ////// // reporting as Invalid objects in Rhino 7.
+ ////// computed_box.Expand(computed_box.Diagonal() + ON_3dVector(1.0, 1.0, 1.0));
+
+ ////// if (!computed_box.Includes(orig_box))
+ ////// {
+ ////// if (text_log)
+ ////// text_log->Print("Stored Bounding Box extends far outside of computed bounding box.\n");
+ ////// return ON_BrepIsNotValid();
+
+ ////// }
+
+ ////// }
+ //////}
+
+ if (m_bbox.IsNotEmpty())
{
- if (!m_bbox.IsValid())
+
+ // new_brep_bbox is calculated from scratch WITHOUT changing existing values on ON_BrepFace.m_bbox and ON_Brep.m_bbox.
+ const ON_BoundingBox new_brep_bbox = this->InternalBrepBoundingBox(false, false);
+ if (new_brep_bbox.IsNotEmpty())
{
- if (text_log)
- text_log->Print("Bounding Box is not valid.\n");
- return ON_BrepIsNotValid();
- }
- else
- {
- ON_BoundingBox orig_box = m_bbox;
- ON_BoundingBox computed_box;
+ ON_BoundingBox triple_size_new_brep_bbox = new_brep_bbox;
+ triple_size_new_brep_bbox.Expand(new_brep_bbox.Diagonal() + ON_3dVector(1.0, 1.0, 1.0));
+ const ON_BoundingBox cached_brep_bbox = m_bbox;
+ if (false == triple_size_new_brep_bbox.Includes(cached_brep_bbox, false))
{
- ON_Brep* this_nonconst = const_cast(this);
- this_nonconst->ClearBoundingBox();
- computed_box = BoundingBox();
- this_nonconst->m_bbox = orig_box; // restore box as it as it was.
+ if (nullptr != text_log)
+ text_log->Print("WARNING: The cached ON_Brep.m_bbox is much larger than one cacluated from current ON_BrepFace extents.\n");
+ ON_WARNING("The cached ON_Brep.m_bbox is much larger than one cacluated from current ON_BrepFace extents. This might effect make2d performance.");
+ // DO NOT RETURN FALSE HERE!
}
- // expand the computed_box before we do the incusion test
- // I'm trying to avoid making a lot of objects created in Rhino 6 and earlier
- // reporting as Invalid objects in Rhino 7.
- computed_box.Expand(computed_box.Diagonal() + ON_3dVector(1.0, 1.0, 1.0));
-
- if (!computed_box.Includes(orig_box))
- {
- if (text_log)
- text_log->Print("Stored Bounding Box extends far outside of computed bounding box.\n");
- return ON_BrepIsNotValid();
-
- }
-
}
}
+
#if 0
// validate ON_BrepTrim.m_pline
for ( ti = 0; ti < trim_count; ti++ )
@@ -6428,7 +6460,133 @@ void ON_BrepFace::ClearBoundingBox()
}
-// ON_BrepFace::GetBBox performs lazy evaluation. Namely, if m_bbox is invalid then the bounding box is
+const ON_BoundingBox ON_BrepFace::InternalFaceBoundingBox(bool bLazy, bool bUpdateCachedBBox) const
+{
+ if (bLazy && m_bbox.IsNotEmpty())
+ return m_bbox;
+
+ for (;;)
+ {
+ // Make sure this face is valid enough to query the trims and proxy surface
+ if (nullptr == m_brep)
+ break;
+ if (m_face_index < 0)
+ break;
+ if (m_face_index >= m_brep->m_F.Count())
+ break;
+ if (&m_brep->m_F[m_face_index] != this)
+ break;
+
+ const ON_Surface* proxy_srf = ProxySurface();
+ if (nullptr == proxy_srf)
+ break;
+ if (proxy_srf == this)
+ break;
+
+ ON_BoundingBox pbox = ON_BoundingBox::NanBoundingBox;
+ for (int li = 0; li < LoopCount(); li++)
+ {
+ ON_BrepLoop* loop = Loop(li);
+ if (loop && loop->m_type == ON_BrepLoop::outer)
+ {
+ m_brep->SetTrimBoundingBoxes(*loop, true); // sets loop->m_pbox
+ if (false == loop->GetBoundingBox(pbox, pbox.IsValid()))
+ pbox = ON_BoundingBox::UnsetBoundingBox;
+ break;
+ }
+ }
+
+
+ ON_BoundingBox face_bbox = ON_BoundingBox::NanBoundingBox;
+ if (pbox.IsNotEmpty())
+ {
+ ON_Interval pudom(pbox[0].x, pbox[1].x);
+ ON_Interval pvdom(pbox[0].y, pbox[1].y);
+ // fatten up invervals to get slightly larger boxes...
+ pudom.Expand(0.1 * pudom.Length());
+ pvdom.Expand(0.1 * pvdom.Length());
+ ON_Interval Sdom[] = { Domain(0), Domain(1) };
+ // but don't let the fattened intervals extend beyond Sdom
+ pudom.Intersection(Sdom[0]);
+ pvdom.Intersection(Sdom[1]);
+ if (pbox.IsValid() &&
+ (Sdom[0].Includes(pudom, true) || Sdom[1].Includes(pvdom, true))
+ )
+ {
+ ON_Surface* temp_srf = DuplicateSurface();
+ if (nullptr != temp_srf)
+ {
+ if (Sdom[0].Includes(pudom, true))
+ temp_srf->Trim(0, pudom);
+ if (Sdom[1].Includes(pvdom, true))
+ temp_srf->Trim(1, pvdom);
+ if (false == temp_srf->GetBoundingBox(face_bbox, false))
+ face_bbox = ON_BoundingBox::NanBoundingBox;
+ delete temp_srf;
+ temp_srf = nullptr;
+ }
+ }
+ }
+
+ if (false == face_bbox.IsNotEmpty())
+ {
+ if (false == proxy_srf->GetBoundingBox(face_bbox, false))
+ break;
+ if (false == face_bbox.IsNotEmpty())
+ break;
+ }
+
+ if (bUpdateCachedBBox)
+ const_cast(this)->m_bbox = face_bbox;
+ return face_bbox;
+ }
+
+ // ON_Brep code has always used ON_BoundingBox::EmptyBoundingBox
+ // to indicate a bounding box is not set. If it were to be written
+ // in modern times, it would have used Nans.
+ return ON_BoundingBox::EmptyBoundingBox;
+}
+
+const ON_BoundingBox ON_Brep::InternalBrepBoundingBox(bool bLazy, bool bUpdateCachedBBox) const
+{
+ if (bLazy && m_bbox.IsNotEmpty())
+ return m_bbox;
+
+ ON_BoundingBox brep_bbox;
+ const int face_count = m_F.Count();
+ int fi;
+ for (fi = 0; fi < face_count; fi++)
+ {
+ if (m_F[fi].m_face_index == -1)
+ continue;
+
+ //GBA 20 May 2020. RH-58462. Brep box now computed from face boxes, instead of surface boxes.
+ const ON_BrepFace* f = Face(fi);
+ if (nullptr == f)
+ continue;
+
+ const ON_BoundingBox face_bbox = f->InternalFaceBoundingBox(bLazy, bUpdateCachedBBox);
+ if (false == face_bbox.IsNotEmpty())
+ continue;
+
+ brep_bbox.Union(face_bbox);
+ }
+
+ if (false == brep_bbox.IsNotEmpty())
+ {
+ // ON_Brep code has always used ON_BoundingBox::EmptyBoundingBox
+ // to indicate a bounding box is not set. If it were to be written
+ // in modern times, it would have used Nans.
+ return ON_BoundingBox::EmptyBoundingBox;
+ }
+
+ if (bUpdateCachedBBox)
+ const_cast(this)->m_bbox = brep_bbox;
+ return brep_bbox;
+}
+
+// ON_BrepFace::GetBBox performs lazy evaluation.
+// Namely, if m_bbox is invalid then the bounding box is
// computed and the value is stored in m_bbox to speed future calls.
bool ON_BrepFace::GetBBox(
double* box_min, // [3],
@@ -6436,65 +6594,11 @@ bool ON_BrepFace::GetBBox(
bool bGrowBox // = false
) const
{
- if ( !m_bbox.IsValid()
- && 0 != m_brep
- && m_face_index >= 0
- && m_face_index < m_brep->m_F.Count()
- && &m_brep->m_F[m_face_index] == this
- )
- {
- ON_BoundingBox pbox;
-
- for (int li = 0; li < LoopCount(); li++)
- {
- ON_BrepLoop* loop = Loop(li);
- if (loop && loop->m_type==ON_BrepLoop::outer)
- {
- m_brep->SetTrimBoundingBoxes( *loop, true); // sets loop->m_pbox
- loop->GetBoundingBox(pbox, pbox.IsValid());
- break;
- }
- }
-
- ON_Interval pudom(pbox[0].x, pbox[1].x);
- ON_Interval pvdom(pbox[0].y, pbox[1].y);
- // fatten up invervals to get slightly larger boxes...
- pudom.Expand(.1 * pudom.Length());
- pvdom.Expand(.1 * pvdom.Length());
- ON_Interval Sdom[]= { Domain(0), Domain(1) };
- // but don't let the fattened intervals extend beyond Sdom
- pudom.Intersection(Sdom[0]);
- pvdom.Intersection(Sdom[1]);
- bool Used_pbox = false;
- if (pbox.IsValid() &&
- (Sdom[0].Includes(pudom, true) || Sdom[1].Includes(pvdom, true)))
- {
- ON_Surface* temp_srf = DuplicateSurface();
- if (temp_srf)
- {
- if (Sdom[0].Includes(pudom, true))
- temp_srf->Trim(0, pudom);
- if (Sdom[1].Includes(pvdom, true))
- temp_srf->Trim(1, pvdom);
- temp_srf->GetBoundingBox(const_cast(this)->m_bbox, false);
- delete temp_srf;
- temp_srf = nullptr;
- Used_pbox = true;
- }
- }
- if (!Used_pbox)
- {
- const ON_Surface* srf = ProxySurface();
- if(srf)
- srf->GetBoundingBox(const_cast(this)->m_bbox, false);
- }
-
- }
-
- bool rc = m_bbox.IsValid();
+ ON_BoundingBox bbox = this->InternalFaceBoundingBox(true, true);
+
+ bool rc = bbox.IsValid();
if (rc)
{
- ON_BoundingBox bbox = m_bbox;
if ( bGrowBox && box_min && box_max && box_min[0] <= box_max[0] )
{
bbox.Union( ON_BoundingBox( ON_3dPoint(box_min), ON_3dPoint(box_max) ) );
@@ -6522,26 +6626,9 @@ bool ON_Brep::GetBBox(
bool bGrowBox // = false
) const
{
- ON_BoundingBox bbox;
- if ( !m_bbox.IsValid() )
- {
- const int face_count = m_F.Count();
- int fi;
- for ( fi = 0; fi < face_count; fi++ )
- {
- if ( m_F[fi].m_face_index == -1 )
- continue;
+ ON_BoundingBox bbox = this->InternalBrepBoundingBox(true, true);
- //GBA 20 May 2020. RH-58462. Brep box now computed from face boxes, instead of surface boxes.
- const ON_BrepFace* f = Face(fi);
- if(f)
- f->GetBoundingBox( bbox, bbox.IsValid() );
- }
- ON_Brep* ptr = const_cast(this);
- ptr->m_bbox = bbox;
- }
-
- bool rc = m_bbox.IsValid();
+ bool rc = bbox.IsValid();
if (rc)
{
bbox = m_bbox;
diff --git a/opennurbs_brep.h b/opennurbs_brep.h
index 4419e721..4e7cd095 100644
--- a/opennurbs_brep.h
+++ b/opennurbs_brep.h
@@ -1319,7 +1319,7 @@ private:
mutable ON_Color m_per_face_color = ON_Color::UnsetColor;
private:
- ON_BoundingBox m_bbox; // 3d bounding box
+ ON_BoundingBox m_bbox; // 3d bounding box (should be declared mutable and const_cast<> is used to make it fake mutable)
ON_Interval m_domain[2]; // rectangular bounds of 2d curves
ON_Mesh* m_render_mesh = nullptr;
ON_Mesh* m_analysis_mesh = nullptr;
@@ -1330,6 +1330,18 @@ private:
friend class ON_Brep;
ON_Brep* m_brep = nullptr;
ON_BrepFace( const ON_BrepFace& ) = delete;
+
+private:
+ /*
+ Parameters:
+ bLazy - [in]
+ If true and if ON_BrepFace.m_bbox is not empty, then ON_BrepFace.m_bbox is returned.
+ In all other cases the bbox is calculated from scratch.
+ bUpdateCachedBBox - [in]
+ If true and the bounding box is calculated, then the value is saved in ON_BrepFace.m_bbox
+ so future lazy evaluations can use the value.
+ */
+ const ON_BoundingBox InternalFaceBoundingBox(bool bLazy, bool bUpdateCachedBBox) const;
};
class ON_CLASS ON_BrepFaceSide : public ON_Object
@@ -4128,6 +4140,18 @@ public:
// The ON_Brep code increments ON_Brep::ErrorCount everytime something
// unexpected happens. This is useful for debugging.
static unsigned int ErrorCount;
+
+private:
+ /*
+ Parameters:
+ bLazy - [in]
+ If true and if ON_BrepFace.m_bbox is not empty, then ON_BrepFace.m_bbox is returned.
+ In all other cases the bbox is calculated from scratch.
+ bUpdateCachedBBox - [in]
+ If true and the bounding box is calculated, then the value is saved in ON_BrepFace.m_bbox
+ so future lazy evaluations can use the value.
+ */
+ const ON_BoundingBox InternalBrepBoundingBox(bool bLazy, bool bUpdateCachedBBox) const;
};
///////////////////////////////////////////////////////////////////////////////
diff --git a/opennurbs_brep_io.cpp b/opennurbs_brep_io.cpp
index 41894ea6..68f7305a 100644
--- a/opennurbs_brep_io.cpp
+++ b/opennurbs_brep_io.cpp
@@ -861,6 +861,28 @@ void ReadFillInMissingBoxes( ON_Brep& brep )
}
}
+static const ON_BoundingBox Internal_BestBoundingBox(
+ const ON_BoundingBox archive_bbox,
+ const ON_BoundingBox new_bbox
+)
+{
+ if (false == new_bbox.IsNotEmpty())
+ return archive_bbox; // don't have a new bbox
+
+ if (false == archive_bbox.IsValid())
+ return new_bbox;
+
+ if (archive_bbox.Includes(new_bbox, false))
+ return new_bbox;
+
+ // The best code currently available calculated new_box.
+ // I should be good enough. If it is different
+ // from archive then you need to understand WHY it is different
+ // and fix the problem at the source - not mask it at read time.
+ return new_bbox; // <- Good place to debug bbox reading bugs
+
+}
+
bool ON_Brep::Read( ON_BinaryArchive& file )
{
int i;
@@ -1105,16 +1127,41 @@ bool ON_Brep::Read( ON_BinaryArchive& file )
// In V6 and earlier bounding boxes of faces were bounding box of underlying surface.
// This can result in enourmous boxes which mess up make2d
// In V7 bounding boxes use the trim pbox resulting in resonable boxes.
- if (file.Archive3dmVersion() < 70 || file.ArchiveOpenNURBSVersion() < 2382395020)
+ ////if (file.Archive3dmVersion() < 70 || file.ArchiveOpenNURBSVersion() < 2382395020)
+ ////{
+ //// if (!m_bbox.IsEmpty())
+ //// {
+ //// ON_BoundingBox orig_box = m_bbox;
+ //// ClearBoundingBox();
+ //// BoundingBox(); // compute bounding box
+ //// m_bbox.Intersection(orig_box);
+ //// }
+ ////}
+
+ // Dale Lear https://mcneel.myjetbrains.com/youtrack/issue/RH-64277
+ // The test used to fix RH-60112 was not adequate and now the set of
+ // active v7 3dm files has lots of cases with breps that have
+ // oversized bounding boxes by the new standard avter Greg's RH-60112 changes.
+ // We have already started writing V8 files and those versions of V8
+ // have been used by some of our Discourse early adopters.
+ // At this point, I'm going to assume a bounding box read from a V7 or V8 file
+ // can be too big. By the time V9 rolls around, the boxes should be cleaned up.
+ if (file.Archive3dmVersion() < 90 && m_bbox.IsNotEmpty() )
{
- if (!m_bbox.IsEmpty())
+ const ON_BoundingBox archive_brep_bbox = m_bbox;
+ ON_BoundingBox new_brep_bbox = ON_BoundingBox::EmptyBoundingBox;
+ const unsigned int face_count = m_F.UnsignedCount();
+ for (unsigned fi = 0; fi < face_count; ++fi)
{
- ON_BoundingBox orig_box = m_bbox;
- ClearBoundingBox();
- BoundingBox(); // compute bounding box
- m_bbox.Intersection(orig_box);
+ ON_BrepFace& f = this->m_F[fi];
+ const ON_BoundingBox archive_face_bbox = f.m_bbox;
+ ON_BoundingBox new_face_bbox = f.InternalFaceBoundingBox(false, false);
+ f.m_bbox = Internal_BestBoundingBox(archive_face_bbox, new_face_bbox);
+ new_brep_bbox.Union(f.m_bbox);
}
+ m_bbox = Internal_BestBoundingBox(archive_brep_bbox, new_brep_bbox);
}
+
return rc;
}
diff --git a/opennurbs_color.h b/opennurbs_color.h
index a3d68386..8f5af218 100644
--- a/opennurbs_color.h
+++ b/opennurbs_color.h
@@ -38,6 +38,7 @@ public:
static const ON_Color SaturatedYellow; // 0x0000FFFFu on little endan, 0xFFFF0000u on big endian
static const ON_Color SaturatedCyan; // 0x00FFFF00u on little endan, 0x00FFFF00u on big endian
static const ON_Color SaturatedMagenta; // 0x00FF00FFu on little endan, 0xFF00FF00u on big endian
+ static const ON_Color SaturatedGold; // 0x0000BFFFu on little endan, 0xFFBF0000u on big endian
static const ON_Color Gray105; // R = G = B = 105 (medium dark)
static const ON_Color Gray126; // R = G = B = 128 (medium)
static const ON_Color Gray160; // R = G = B = 160 (medium light)
diff --git a/opennurbs_defines.h b/opennurbs_defines.h
index 5f646f20..fa156bae 100644
--- a/opennurbs_defines.h
+++ b/opennurbs_defines.h
@@ -273,7 +273,7 @@ double ON_RadiansFromDegrees(
#define ON_UNSET_POSITIVE_FLOAT 1.234321e+38f
#define ON_UNSET_FLOAT -ON_UNSET_POSITIVE_FLOAT
-// When unsinged int values are used in a context where
+// When unsigned int values are used in a context where
// 0 is a valid index and there needs to be a value that
// indicates the index is not set, use ON_UNSET_UINT_INDEX.
#define ON_UNSET_UINT_INDEX 0xFFFFFFFFU
@@ -2707,6 +2707,65 @@ public:
int m_index;
};
+class ON_CLASS ON_ComponentIndexAndNumber
+{
+public:
+ ON_ComponentIndexAndNumber() = default;
+ ~ON_ComponentIndexAndNumber() = default;
+ ON_ComponentIndexAndNumber(const ON_ComponentIndexAndNumber&) = default;
+ ON_ComponentIndexAndNumber& operator=(const ON_ComponentIndexAndNumber&) = default;
+
+public:
+ static const ON_ComponentIndexAndNumber UnsetAndNan;
+ static const ON_ComponentIndexAndNumber UnsetAndZero;
+ static const ON_ComponentIndexAndNumber Create(
+ ON_COMPONENT_INDEX ci,
+ double x
+ );
+
+public:
+
+ /*
+ Description:
+ Compare Component() using ON_COMPONENT_INDEX::Compare().
+ */
+ static int CompareComponent(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+ );
+
+
+ /*
+ Description:
+ Compare Number() nans are treated as equal and sort last.
+ */
+ static int CompareNumber(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+ );
+
+ /*
+ Description:
+ Dictionary compare Component() 1st and Number() 2nd.
+ */
+ static int CompareComponentAndNumber(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+ );
+
+
+public:
+ const ON_COMPONENT_INDEX Component() const;
+ void SetComponent(ON_COMPONENT_INDEX ci);
+
+ double Number() const;
+ void SetNumber(double x);
+
+public:
+ ON_COMPONENT_INDEX m_ci = ON_COMPONENT_INDEX::UnsetComponentIndex;
+ double m_x = ON_DBL_QNAN;
+};
+
#endif
ON_BEGIN_EXTERNC
diff --git a/opennurbs_dimension.cpp b/opennurbs_dimension.cpp
index ce8be3be..47252177 100644
--- a/opennurbs_dimension.cpp
+++ b/opennurbs_dimension.cpp
@@ -3526,6 +3526,7 @@ bool ON_DimAngular::GetDisplayLines(
isline[0] = isline[1] = isarc[0] = isarc[1] = false;
double eo = style->ExtOffset() * dimscale;
double ee = style->ExtExtension() * dimscale;
+ double el = style->FixedExtensionLen() * dimscale;
double radius = Radius();
if (1.0e-8 > radius)
return false;
@@ -3542,14 +3543,24 @@ bool ON_DimAngular::GetDisplayLines(
{
double eo0 = eo;
double ee0 = ee;
+ double el0 = el;
if (m_ext_offset_1 > radius)
{
eo0 = -eo0;
ee0 = -ee0;
+ el0 = -el0;
+ }
+ ON_2dPoint lpt0, lpt1;
+ if (style->FixedExtensionLenOn())
+ {
+ lpt1 = m_vec_1 * (radius + ee0);
+ lpt0 = lpt1 + (-m_vec_1 * (el0 + ee0));
+ }
+ else
+ {
+ lpt0 = m_vec_1 * (m_ext_offset_1 + eo0);
+ lpt1 = m_vec_1 * (radius + ee0);
}
- ON_2dPoint lpt0 = m_vec_1 * (m_ext_offset_1 + eo0);
- ON_2dPoint lpt1 = m_vec_1 * (radius + ee0);
-
lines[0].from = m_plane.PointAt(lpt0.x, lpt0.y);
lines[0].to = m_plane.PointAt(lpt1.x, lpt1.y);
isline[0] = true;
@@ -3559,13 +3570,24 @@ bool ON_DimAngular::GetDisplayLines(
{
double eo1 = eo;
double ee1 = ee;
+ double el1 = el;
if (m_ext_offset_2 > radius)
{
eo1 = -eo1;
ee1 = -ee1;
+ el1 = -el1;
+ }
+ ON_2dPoint lpt0, lpt1;
+ if (style->FixedExtensionLenOn())
+ {
+ lpt1 = m_vec_2 * (radius + ee1);
+ lpt0 = lpt1 + (-m_vec_2 * (el1 + ee1));
+ }
+ else
+ {
+ lpt0 = m_vec_2 * (m_ext_offset_2 + eo1);
+ lpt1 = m_vec_2 * (radius + ee1);
}
- ON_2dPoint lpt0 = m_vec_2 * (m_ext_offset_2 + eo1);
- ON_2dPoint lpt1 = m_vec_2 * (radius + ee1);
lines[1].from = m_plane.PointAt(lpt0.x, lpt0.y);
lines[1].to = m_plane.PointAt(lpt1.x, lpt1.y);
isline[1] = true;
diff --git a/opennurbs_dimensionstyle.cpp b/opennurbs_dimensionstyle.cpp
index 80f202e4..ef04b7bc 100644
--- a/opennurbs_dimensionstyle.cpp
+++ b/opennurbs_dimensionstyle.cpp
@@ -4342,10 +4342,10 @@ void ON_DimStyle::SetZeroSuppress(ON_DimStyle::suppress_zero s)
m_zero_suppress = s;
Internal_ContentChange();
}
- else
- {
- ON_ERROR("Attempting to set zero suppression to a value that doesn't match length display.");
- }
+ //else
+ //{
+ // ON_ERROR("Attempting to set zero suppression to a value that doesn't match length display.");
+ //}
}
Internal_SetOverrideDimStyleCandidateFieldOverride(ON_DimStyle::field::ZeroSuppress);
}
diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp
index bc3f6f7b..3867fa59 100644
--- a/opennurbs_extensions.cpp
+++ b/opennurbs_extensions.cpp
@@ -1204,6 +1204,8 @@ int ONX_Model::AddDefaultDimensionStyle(
{
system_dimstyle = &ON_DimStyle::Default;
}
+
+ source_dimstyle = system_dimstyle;
}
ON_DimStyle* default_dimstyle = new ON_DimStyle(*source_dimstyle);
diff --git a/opennurbs_instance.cpp b/opennurbs_instance.cpp
index 19cfdf67..cd0308c5 100644
--- a/opennurbs_instance.cpp
+++ b/opennurbs_instance.cpp
@@ -852,6 +852,9 @@ void ON_InstanceDefinition::Internal_Copy(const ON_InstanceDefinition& src)
m_linked_component_appearance = src.m_linked_component_appearance;
if (nullptr != src.m_linked_idef_component_settings)
m_linked_idef_component_settings = new ON_ReferencedComponentSettings(*src.m_linked_idef_component_settings);
+
+ //This omission was causing part of https://mcneel.myjetbrains.com/youtrack/issue/RH-47128
+ m_object_uuid = src.m_object_uuid;
}
diff --git a/opennurbs_internal_Vx_annotation.cpp b/opennurbs_internal_Vx_annotation.cpp
index a3c5ecac..28bab459 100644
--- a/opennurbs_internal_Vx_annotation.cpp
+++ b/opennurbs_internal_Vx_annotation.cpp
@@ -596,8 +596,10 @@ ON_OBSOLETE_V5_TextObject* ON_OBSOLETE_V5_TextObject::CreateFromV6TextObject(
if (textgeom->HasWrappedRuns())
{
- const ON_wString plaintextFields = textgeom->WrappedPlainTextWithFields();
- V5_text_object->SetTextFormula(plaintextFields);
+ // RH-64471 - WrappedPlainTextWithFields() adds multiple copies of the entire
+ // text string for each wrapped run.
+ //const ON_wString plaintextFields = textgeom->WrappedPlainTextWithFields();
+ //V5_text_object->SetTextFormula(plaintextFields);
const ON_wString plaintext = textgeom->WrappedPlainText();
V5_text_object->SetTextValue(plaintext);
}
diff --git a/opennurbs_layer.cpp b/opennurbs_layer.cpp
index 171ba841..65453db2 100644
--- a/opennurbs_layer.cpp
+++ b/opennurbs_layer.cpp
@@ -375,6 +375,16 @@ bool ON_Layer::Read(
if ( rc ) rc = file.ReadInt( &obsolete_value1 );
if ( rc ) rc = file.ReadColor( m_color );
+ // 25 Aug 2021 S. Baer (RH-65410)
+ // Pre-V7 files ignored alpha on layer colors and in some cases
+ // alpha was being set to completely transparent. In this case,
+ // make the color opaque. Even V7 files with completely transparent
+ // color is strange, but it can be intentionally set for some reason
+ if (rc && m_color.Alpha() == 255 && m_color != ON_Color::UnsetColor && file.Archive3dmVersion() < 70)
+ {
+ m_color.SetAlpha(0);
+ }
+
{
// OBSOLETE line style was never used - read and discard the next 20 bytes
short s;
diff --git a/opennurbs_lookup.h b/opennurbs_lookup.h
index ef019a04..c3c70744 100644
--- a/opennurbs_lookup.h
+++ b/opennurbs_lookup.h
@@ -359,7 +359,7 @@ private:
ON__UINT64 m_sn_count = 0; // total number of elements
ON__UINT64 m_sn_purged = 0; // total number of purged elements
- // The blocks in m_sn_list[] are alwasy sorted, disjoint,
+ // The blocks in m_sn_list[] are always sorted, disjoint,
// and in increasing order. m_sn_list is used when
// m_sn_block0.m_sn[] is not large enough.
// The sn list is partitioned into blocks to avoid
diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp
index b1099f86..4c78dab2 100644
--- a/opennurbs_material.cpp
+++ b/opennurbs_material.cpp
@@ -2256,6 +2256,7 @@ ON_Texture::MAPPING_CHANNEL ON_Texture::BuiltInMappingChannelFromUnsigned(
const ON_SHA1_Hash ON_Texture::ContentHash() const
{
ON_SHA1 sha1;
+ sha1.AccumulateUnsigned32(m_mapping_channel_id);
sha1.AccumulateSubHash(m_image_file_reference.FullPathHash());
sha1.AccumulateBool(m_bOn);
sha1.AccumulateUnsigned32(static_cast(m_type));
@@ -3761,7 +3762,7 @@ bool ON_TextureMapping::HasMatchingTextureCoordinates(
// values are independent of the 3d location
// of the mesh. The ON_TextureMapping::TYPE::srfp_mapping != m_type
// check is used because these mappings are
- // alwasy independent of 3d location but
+ // always independent of 3d location but
// the transformations are often set.
if ( ON_TextureMapping::TYPE::srfp_mapping != m_type
&& mesh_xform
diff --git a/opennurbs_math.cpp b/opennurbs_math.cpp
index babb5422..43d3cb89 100644
--- a/opennurbs_math.cpp
+++ b/opennurbs_math.cpp
@@ -168,7 +168,7 @@ bool ON_PassesNanTest()
const double zero = 0.0;
const double one = 1.0;
- // nan != * and * != nan should alwasy be true
+ // nan != * and * != nan should always be true
const bool b_NE_test
= nan1 != nan1
&& nan1 != nan2
@@ -2566,7 +2566,7 @@ double ON_SolveNxN(bool bFullPivot, bool bNormalize, int n, double* M[], double
// The Xdex_buffer[] hoo haa is here to avoid an potentially time
// consuming call to heap services when the matrix is small.
// When n > 64 the numerical portion of the computation is
- // long enough that the time to call onmalloc() is negligable.
+ // long enough that the time to call onmalloc() is negligible.
// (When n > 10-ish, this calculation is likely to return junk
// unless you have a special case matrix, in which case this
// function will be much slower than one that is designed to
diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h
index 31121f5d..40d902aa 100644
--- a/opennurbs_mesh.h
+++ b/opennurbs_mesh.h
@@ -4029,7 +4029,7 @@ Returns:
array of vertex indices
bNoDuplicates - [in]
If true, then only one edges[] is added for each edge,
- the first vertex index will alwasy be less than the
+ the first vertex index will always be less than the
second, and the returned elements are sorted in dictionary
order.
If false and an edge is shared by multiple faces, then
@@ -5492,7 +5492,7 @@ public:
Returns:
True if no ON_Mesh is being managed by this ON_MeshRef.
Remarks:
- It is alwasy the case that exactly one of IsEmpty() and IsNotEmpty() is true.
+ It is always the case that exactly one of IsEmpty() and IsNotEmpty() is true.
Both are provided so code using ON_MeshRef can be clean and easily read.
*/
bool IsEmpty() const;
@@ -5501,7 +5501,7 @@ public:
Returns:
True if an ON_Mesh is being managed by this ON_MeshRef.
Remarks:
- It is alwasy the case that exactly one of IsEmpty() and IsNotEmpty() is true.
+ It is always the case that exactly one of IsEmpty() and IsNotEmpty() is true.
Both are provided so code using ON_MeshRef can be clean and easily read.
*/
bool IsNotEmpty() const;
diff --git a/opennurbs_mesh_ngon.cpp b/opennurbs_mesh_ngon.cpp
index ad8ae61c..ac80123b 100644
--- a/opennurbs_mesh_ngon.cpp
+++ b/opennurbs_mesh_ngon.cpp
@@ -516,7 +516,7 @@ static char* ToStringHelper( const char* src_str, char* dst_str, const char* dst
static char* ToStringHelper( unsigned int u, char* dst_str, const char* dst_str_end )
{
- char ubuffer[32]; // unsinged int to string storage buffer
+ char ubuffer[32]; // unsigned int to string storage buffer
unsigned int i, j;
if ( ON_UNSET_UINT_INDEX == u )
diff --git a/opennurbs_object_history.cpp b/opennurbs_object_history.cpp
index e05a7438..a3d06de4 100644
--- a/opennurbs_object_history.cpp
+++ b/opennurbs_object_history.cpp
@@ -2921,6 +2921,22 @@ void ON_HistoryRecord::RemapObjectIds( const ON_SimpleArray& id_rem
objrev_v->m_value[j].RemapObjectId(id_remap);
}
}
+ // 24 May 2021, Mikko, RH-56171:
+ // Some commands like Offset use an UUID list to map inputs and outputs.
+ // Similar to remapping the objref ids, the uuid lists also need to be
+ // updated to use new ids.
+ // Other commands that currently use UUID lists are Divide, Slab, Ribbon,
+ // FilletSrf, ChamferSrf, ArrayCrv, Hatch.
+ else if (v && ON_Value::uuid_value == v->m_value_type)
+ {
+ ON_UuidValue* uuid_v = static_cast(v);
+ for (j = 0; j < uuid_v->m_value.Count(); j++)
+ {
+ int index = id_remap.BinarySearch((const ON_UuidPair*)&uuid_v->m_value[j], ON_UuidPair::CompareFirstUuid);
+ if (index >= 0)
+ uuid_v->m_value[j] = id_remap[index].m_uuid[1];
+ }
+ }
}
}
}
diff --git a/opennurbs_objref.cpp b/opennurbs_objref.cpp
index ca348b4f..b653c4d6 100644
--- a/opennurbs_objref.cpp
+++ b/opennurbs_objref.cpp
@@ -558,6 +558,75 @@ bool ON_COMPONENT_INDEX::operator>=(const ON_COMPONENT_INDEX& other) const
return (ON_COMPONENT_INDEX::Compare(this,&other) >= 0);
}
+const ON_ComponentIndexAndNumber ON_ComponentIndexAndNumber::Create(
+ ON_COMPONENT_INDEX ci,
+ double x
+)
+{
+ ON_ComponentIndexAndNumber cx;
+ cx.m_ci = ci;
+ cx.m_x = x;
+ return cx;
+}
+
+int ON_ComponentIndexAndNumber::CompareComponent(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+)
+{
+ if (a == b)
+ return 0;
+ if (nullptr == a)
+ return -1; // nulls sort last
+ if (nullptr == b)
+ return -1; // nulls sort last
+ return ON_COMPONENT_INDEX::Compare(&a->m_ci, &b->m_ci);
+}
+
+int ON_ComponentIndexAndNumber::CompareNumber(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+)
+{
+ if (a == b)
+ return 0;
+ if (nullptr == a)
+ return -1; // nulls sort last
+ if (nullptr == b)
+ return -1; // nulls sort last
+ return ON_CompareDouble(a->m_x, b->m_x);
+}
+
+int ON_ComponentIndexAndNumber::CompareComponentAndNumber(
+ const ON_ComponentIndexAndNumber* a,
+ const ON_ComponentIndexAndNumber* b
+)
+{
+ const int rc = ON_ComponentIndexAndNumber::CompareComponent(a, b);
+ return (0 == rc) ? ON_ComponentIndexAndNumber::CompareNumber(a,b) : rc;
+}
+
+
+const ON_COMPONENT_INDEX ON_ComponentIndexAndNumber::Component() const
+{
+ return m_ci;
+}
+
+void ON_ComponentIndexAndNumber::SetComponent(ON_COMPONENT_INDEX ci)
+{
+ m_ci = ci;
+}
+
+double ON_ComponentIndexAndNumber::Number() const
+{
+ return m_x;
+}
+
+void ON_ComponentIndexAndNumber::SetNumber(double x)
+{
+ m_x = x;
+}
+
ON_ObjRefEvaluationParameter::ON_ObjRefEvaluationParameter()
: m_t_type(0)
, m_reserved(0)
diff --git a/opennurbs_parse_number.cpp b/opennurbs_parse_number.cpp
index 461ba077..6372b3cf 100644
--- a/opennurbs_parse_number.cpp
+++ b/opennurbs_parse_number.cpp
@@ -988,7 +988,7 @@ static int ON_ParseFunctionHelper(
if ( f_parameter_capacity < f->m_function_parameter_count )
break;
- // The angle parameters passed to trig functions alwasy have
+ // The angle parameters passed to trig functions always have
// an "implicit" angle system of radians. It is intentional that
// this cannot be changed by parse settings. The reason is
// to insure that the same script will create the same values
diff --git a/opennurbs_point.cpp b/opennurbs_point.cpp
index 70f0234d..b4423387 100644
--- a/opennurbs_point.cpp
+++ b/opennurbs_point.cpp
@@ -362,7 +362,7 @@ int ON_4dPoint::ProjectiveCompare(
if (0.0 != lhs.w && 0.0 != rhs.w)
{
// neither lhs.w nor rhs.w is a nan
- return ON_3dPoint::Compare(ON_3dPoint(lhs), ON_3dPoint(lhs));
+ return ON_3dPoint::Compare(ON_3dPoint(lhs), ON_3dPoint(rhs));
}
if (0.0 != lhs.w && 0.0 == rhs.w)
diff --git a/opennurbs_pointcloud.cpp b/opennurbs_pointcloud.cpp
index 1077109d..922d2d42 100644
--- a/opennurbs_pointcloud.cpp
+++ b/opennurbs_pointcloud.cpp
@@ -634,15 +634,15 @@ ON_PointCloud* ON_PointCloud::RandomSubsample(
// For (min <= r < max): min + RandomNumber() % (max-min)
const int point_index = gen.RandomNumber() % last_point_count;
- destination_point_cloud->m_P.Swap(point_index, last_point_count);
+ destination_point_cloud->m_P.Swap(point_index, last_point_count - 1);
if (bHaveNormals)
- destination_point_cloud->m_N.Swap(point_index, last_point_count);
+ destination_point_cloud->m_N.Swap(point_index, last_point_count - 1);
if (bHaveColors)
- destination_point_cloud->m_C.Swap(point_index, last_point_count);
+ destination_point_cloud->m_C.Swap(point_index, last_point_count - 1);
if (bHaveValues)
- destination_point_cloud->m_V.Swap(point_index, last_point_count);
+ destination_point_cloud->m_V.Swap(point_index, last_point_count - 1);
if (bHaveHidden)
- destination_point_cloud->m_H.Swap(point_index, last_point_count);
+ destination_point_cloud->m_H.Swap(point_index, last_point_count - 1);
last_point_count--;
if (last_point_count <= 0)
diff --git a/opennurbs_polycurve.cpp b/opennurbs_polycurve.cpp
index 3e258f5c..2ff9f437 100644
--- a/opennurbs_polycurve.cpp
+++ b/opennurbs_polycurve.cpp
@@ -3719,7 +3719,23 @@ bool ON_PolyCurve::SynchronizeSegmentDomains()
return rc;
}
+ON_Curve* ON_PolyCurve::ExplodeSingleSegmentCurve() const
+{
+ if (Count() != 1)
+ return 0;
+ ON_Curve* pSeg = SegmentCurve(0)->DuplicateCurve();
+ if (!pSeg)
+ return 0;
+ ON_PolyCurve* pSegPly = ON_PolyCurve::Cast(pSeg);
+ if (pSegPly){
+ delete pSeg;
+ return 0;
+ }
+ pSeg->SetDomain(Domain());
+ pSeg->CopyUserData(*this,ON_nil_uuid,ON_Object::UserDataConflictResolution::source_object);
+ return pSeg;
+}
bool ON_PolyCurve::RemoveNesting( )
{
diff --git a/opennurbs_polycurve.h b/opennurbs_polycurve.h
index 1e48e48a..39db2a91 100644
--- a/opennurbs_polycurve.h
+++ b/opennurbs_polycurve.h
@@ -693,6 +693,19 @@ public:
*/
bool IsNested() const;
+ /*
+ Description:
+ If this has a single segment, return that single segment with user_data copied,
+ reversed if necessary so the sense of the result is the same as this,
+ and domain adjusted to match this->Domain. If the single segment is a polycurve,
+ RemoveNesting should be called before calling ExplodeSingleSegmentCurve.
+ Returns:
+ NULL if not a single span polycurve
+ The single span, adjusted as described above;
+ */
+
+ ON_Curve* ExplodeSingleSegmentCurve() const;
+
/*
Description:
Removes the nested of polycurves. The result will have not
diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h
index c365e1e0..f0462c07 100644
--- a/opennurbs_public_version.h
+++ b/opennurbs_public_version.h
@@ -6,7 +6,7 @@
// To update version numbers, edit ..\build\build_dates.msbuild
#define RMA_VERSION_MAJOR 7
-#define RMA_VERSION_MINOR 6
+#define RMA_VERSION_MINOR 11
////////////////////////////////////////////////////////////////
//
@@ -14,9 +14,9 @@
// first step in each build.
//
#define RMA_VERSION_YEAR 2021
-#define RMA_VERSION_MONTH 5
-#define RMA_VERSION_DATE 7
-#define RMA_VERSION_HOUR 19
+#define RMA_VERSION_MONTH 10
+#define RMA_VERSION_DATE 12
+#define RMA_VERSION_HOUR 13
#define RMA_VERSION_MINUTE 0
////////////////////////////////////////////////////////////////
@@ -35,8 +35,8 @@
// 3 = build system release build
#define RMA_VERSION_BRANCH 0
-#define VERSION_WITH_COMMAS 7,6,21127,19000
-#define VERSION_WITH_PERIODS 7.6.21127.19000
+#define VERSION_WITH_COMMAS 7,11,21285,13000
+#define VERSION_WITH_PERIODS 7.11.21285.13000
#define COPYRIGHT "Copyright (C) 1993-2021, Robert McNeel & Associates. All Rights Reserved."
#define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library."
@@ -44,11 +44,11 @@
#define RMA_VERSION_NUMBER_MAJOR_WSTRING L"7"
#define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"6"
-#define RMA_VERSION_NUMBER_SR_STRING "SR6"
-#define RMA_VERSION_NUMBER_SR_WSTRING L"SR6"
+#define RMA_VERSION_NUMBER_SR_STRING "SR11"
+#define RMA_VERSION_NUMBER_SR_WSTRING L"SR11"
-#define RMA_VERSION_WITH_PERIODS_STRING "7.6.21127.19000"
-#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.6.21127.19000"
+#define RMA_VERSION_WITH_PERIODS_STRING "7.11.21285.13000"
+#define RMA_VERSION_WITH_PERIODS_WSTRING L"7.11.21285.13000"
diff --git a/opennurbs_rtree.cpp b/opennurbs_rtree.cpp
index 412b6db9..45d5437f 100644
--- a/opennurbs_rtree.cpp
+++ b/opennurbs_rtree.cpp
@@ -672,17 +672,25 @@ bool ON_RTree::CreateMeshFaceTree( const ON_Mesh* mesh )
return (0 != m_root);
}
-bool ON_SubDRTree::CreateSubDVertexRTree(
- const ON_SubD& subd,
- ON_SubDComponentLocation vertex_location
+
+bool ON_SubDRTree::CreateSubDEmptyRTree(
+ const ON_SubD& subd
)
{
// ShareContentsFrom() increments the reference count on m_subdimple_sp
// so vertex pointers one this RTree's nodes will be valid for the duration
// of the rtree's existence.
m_subd.ShareContentsFrom(const_cast(subd));
-
this->RemoveAll();
+ return true;
+}
+
+bool ON_SubDRTree::CreateSubDVertexRTree(
+ const ON_SubD& subd,
+ ON_SubDComponentLocation vertex_location
+)
+{
+ CreateSubDEmptyRTree(subd);
ON_SubDVertexIterator vit(m_subd);
@@ -698,6 +706,17 @@ bool ON_SubDRTree::CreateSubDVertexRTree(
return (nullptr != this->Root());
}
+bool ON_SubDRTree::AddVertex(
+ const ON_SubDVertex* v,
+ ON_SubDComponentLocation vertex_location
+)
+{
+ const ON_3dPoint P = (nullptr != v) ? v->Point(vertex_location) : ON_3dPoint::NanPoint;
+ return P.IsValid() ? this->Insert(&P.x, &P.x, (void*)v) : false;
+}
+
+
+
const ON_SubDVertex* ON_SubDRTree::FindVertexAtPoint(
const ON_3dPoint P,
const double distance_tolerance
@@ -760,6 +779,30 @@ const ON_SubDVertex* ON_SubDRTree::FindUnmarkedVertexAtPoint(
return vf.m_v;
}
+const ON_SubDVertex* ON_SubDRTree::FindVertex(
+ const class ON_SubDRTreeVertexFinder& vertex_finder,
+ const double distance_tolerance
+) const
+{
+ ON_SubDRTreeVertexFinder vf(vertex_finder);
+ if (false == vf.m_P.IsValid())
+ return nullptr;
+
+ vf.m_distance = ON_SubDRTreeVertexFinder::Unset.m_distance;
+ vf.m_v = ON_SubDRTreeVertexFinder::Unset.m_v;
+
+ ON_BoundingBox rbox;
+ const ON_3dVector rtol(distance_tolerance, distance_tolerance, distance_tolerance);
+
+ rbox.m_min = vf.m_P - rtol;
+ rbox.m_max = vf.m_P + rtol;
+ // vtree.Search() can return true (found nearby) and false (found exact) because
+ // ON_SubDRTreeVertexFinder::Callback() cancels the search when a vertex at the exact location
+ // is found.
+ this->Search(&rbox.m_min.x, &rbox.m_max.x, ON_SubDRTreeVertexFinder::Callback, &vf);
+ return vf.m_v;
+}
+
void ON_SubDRTree::Clear()
{
RemoveAll(); // clear the rtree
@@ -786,14 +829,63 @@ const ON_SubDRTreeVertexFinder ON_SubDRTreeVertexFinder::Create(const ON_3dPoint
return vf;
}
+const ON_SubDRTreeVertexFinder ON_SubDRTreeVertexFinder::Create(
+ const ON_3dPoint P,
+ ON_SubDRTreeVertexFinder::MarkBitsFilter mark_bits_filter,
+ ON__UINT8 mark_bits
+)
+{
+ ON_SubDRTreeVertexFinder vf = ON_SubDRTreeVertexFinder::Create(P);
+ vf.m_mark_bits_filter = mark_bits_filter;
+ vf.m_mark_bits = mark_bits;
+ return vf;
+}
+
+
bool ON_SubDRTreeVertexFinder::Callback(void* a_context, ON__INT_PTR a_id)
{
for (;;)
{
ON_SubDRTreeVertexFinder* vf = (ON_SubDRTreeVertexFinder*)a_context;
const ON_SubDVertex* v = (const ON_SubDVertex*)a_id;
- if (nullptr == v || (vf->m_bMarkFilterEnabled && vf->m_bMarkFilter != v->Mark()))
- break; // when m_bMarkFilterEnabled is true, only vertices with v->Mark() == m_bMarkFilter can be found.
+ if (nullptr == v )
+ break;
+
+ if (vf->m_bMarkFilterEnabled && vf->m_bMarkFilter != v->Mark())
+ {
+ // v is not eligable.
+ // Returning true means continue searching for other vertices
+ return true;
+ }
+
+ if (ON_SubDRTreeVertexFinder::MarkBitsFilter::None != vf->m_mark_bits_filter)
+ {
+ const ON__UINT8 v_mark_bits = v->MarkBits();
+ switch(vf->m_mark_bits_filter)
+ {
+ case ON_SubDRTreeVertexFinder::MarkBitsFilter::Equal:
+ if (vf->m_mark_bits != v_mark_bits)
+ {
+ // v is not eligable.
+ // Returning true means continue searching for other vertices
+ return true;
+ }
+ break;
+
+ case ON_SubDRTreeVertexFinder::MarkBitsFilter::NotEqual:
+ if (vf->m_mark_bits == v_mark_bits)
+ {
+ // v is not eligable.
+ // Returning true means continue searching for other vertices
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
const double d = (vf->m_P - v->ControlNetPoint()).MaximumCoordinate();
if (d >= 0.0)
{
diff --git a/opennurbs_statics.cpp b/opennurbs_statics.cpp
index b80801ec..bc23eb8f 100644
--- a/opennurbs_statics.cpp
+++ b/opennurbs_statics.cpp
@@ -29,7 +29,6 @@ static unsigned int ON_LibraryStatusInit()
unsigned int ON::m_opennurbs_library_status = ON_LibraryStatusInit();
-
unsigned int ON_MemoryAllocationTracking::m_g_stack_depth = 0;
int ON_MemoryAllocationTracking::m_g_crt_dbg_flag0 = 0;
@@ -110,6 +109,20 @@ std::atomic ON_ModelComponent::Internal_RuntimeSerialNumberGenerator
std::atomic ON_SubDimple::Internal_RuntimeSerialNumberGenerator;
+const double ON_SubDExpandEdgesParameters::OffsetTolerance = 0.001;
+
+const double ON_SubDExpandEdgesParameters::MinimumOffset = 0.05;
+
+const double ON_SubDExpandEdgesParameters::MaximumOffset = 0.95;
+
+const double ON_SubDExpandEdgesParameters::SmallOffset = 0.125;
+
+const double ON_SubDExpandEdgesParameters::MediumOffset = 0.25;
+
+const double ON_SubDExpandEdgesParameters::LargeOffset = 0.5;
+
+const ON_SubDExpandEdgesParameters ON_SubDExpandEdgesParameters::Default ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDExpandEdgesParameters);
+
const ON_SubDComponentLocation ON_SubD::DefaultSubDAppearance = ON_SubDComponentLocation::Surface;
// The default type must be packed, unpacked, zero, or nan and should be packed or upacked.
@@ -152,6 +165,9 @@ const ON_SubDToBrepParameters ON_SubDToBrepParameters::DefaultUnpacked = Interna
const ON_SubDToBrepParameters ON_SubDToBrepParameters::DefaultPacked = Internal_SubDToBrepParameters(true);
+const ON_SubDRTreeVertexFinder ON_SubDRTreeVertexFinder::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDRTreeVertexFinder);
+
+
ON_ClassId* ON_ClassId::m_p0 = 0; // static pointer to first id in list
ON_ClassId* ON_ClassId::m_p1 = 0; // static pointer to last id in list
int ON_ClassId::m_mark0 = 0;
@@ -265,6 +281,8 @@ const ON_UUID ON_opennurbs_id = ON_opennurbs7_id;
const ON_UuidPairList ON_UuidPairList::EmptyList;
const ON_COMPONENT_INDEX ON_COMPONENT_INDEX::UnsetComponentIndex;
+const ON_ComponentIndexAndNumber ON_ComponentIndexAndNumber::UnsetAndNan = ON_ComponentIndexAndNumber::Create(ON_COMPONENT_INDEX::UnsetComponentIndex, ON_DBL_QNAN);
+const ON_ComponentIndexAndNumber ON_ComponentIndexAndNumber::UnsetAndZero = ON_ComponentIndexAndNumber::Create(ON_COMPONENT_INDEX::UnsetComponentIndex, 0.0);
// All opennurbs static members are initialized here so that initialization happens in a predictable order.
/*
@@ -515,11 +533,24 @@ static struct ON_UnicodeErrorParameters ON_Internal_UnicodeErrorParameters_Defau
const struct ON_UnicodeErrorParameters ON_UnicodeErrorParameters::MaskErrors = ON_Internal_UnicodeErrorParameters_Default(0xFFFFFFFF);
const struct ON_UnicodeErrorParameters ON_UnicodeErrorParameters::FailOnErrors = ON_Internal_UnicodeErrorParameters_Default(0);
+
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::Null = ON_UnicodeShortCodePoint::Create(0);
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::ReplacementCharacter = ON_UnicodeShortCodePoint::Create(0xFFFD);
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::ByteOrderMark = ON_UnicodeShortCodePoint::Create(0xFFFE);
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::Error = ON_UnicodeShortCodePoint::Create(0xFFFF);
+
+const ON_Big5CodePoint ON_Big5CodePoint::Null = ON_Big5CodePoint::Create(0);
+const ON_Big5CodePoint ON_Big5CodePoint::WindowsEuro = ON_Big5CodePoint::Create(0xA3E1);
+const ON_Big5CodePoint ON_Big5CodePoint::Error = ON_Big5CodePoint::Create(0xFFFF);
+
+const ON_Big5UnicodePair ON_Big5UnicodePair::Null = ON_Big5UnicodePair::Create(ON_Big5CodePoint::Null, ON_UnicodeShortCodePoint::Null);
+const ON_Big5UnicodePair ON_Big5UnicodePair::Error = ON_Big5UnicodePair::Create(ON_Big5CodePoint::Error, ON_UnicodeShortCodePoint::Error);
+
const ON_String ON_String::EmptyString;
static const ON_String ON_Internal_ByteOrderMark()
{
// UTF-8 encoded byte order mark.
- const unsigned char bom[3] = {0xEFU,0xBBU,0xBFU};
+ const unsigned char bom[4] = {0xEFU,0xBBU,0xBFU,0x00U};
return ON_String((const char*)bom);
}
const ON_String ON_String::ByteOrderMark(ON_Internal_ByteOrderMark());
@@ -843,6 +874,7 @@ const ON_Color ON_Color::SaturatedBlue(0, 0, 255);
const ON_Color ON_Color::SaturatedYellow(255, 255, 0);
const ON_Color ON_Color::SaturatedCyan(0, 255, 255);
const ON_Color ON_Color::SaturatedMagenta(255, 0, 255);
+const ON_Color ON_Color::SaturatedGold(255, 191, 0);
const ON_Color ON_Color::Gray105(105, 105, 105);
const ON_Color ON_Color::Gray126(126, 126, 126);
const ON_Color ON_Color::Gray160(160, 160, 160);
@@ -2657,6 +2689,13 @@ const ON_SubDEdgePtr ON_SubDEdgePtr::Null = { 0 };
const ON_SubDFacePtr ON_SubDFacePtr::Null = { 0 };
const ON_SubDComponentPtr ON_SubDComponentPtr::Null = { 0 };
const ON_SubDComponentPtrPair ON_SubDComponentPtrPair::Null = ON_SubDComponentPtrPair::Create(ON_SubDComponentPtr::Null,ON_SubDComponentPtr::Null);
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::NullAndNan = ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Null, ON_DBL_QNAN);
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::NullAndZero = ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Null, 0.0);
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::NullAndNan = ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Null, ON_3dPoint::NanPoint);
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::NullAndOrigin = ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Null, ON_3dPoint::Origin);
+
const ON_SubDComponentList ON_SubDComponentList::Empty ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubDComponentList);
const ON_SubD_ComponentIdTypeAndTag ON_SubD_ComponentIdTypeAndTag::Unset ON_CLANG_CONSTRUCTOR_BUG_INIT(ON_SubD_ComponentIdTypeAndTag);
diff --git a/opennurbs_string.cpp b/opennurbs_string.cpp
index 7d771da8..f41051b7 100644
--- a/opennurbs_string.cpp
+++ b/opennurbs_string.cpp
@@ -1658,6 +1658,437 @@ ON_String ON_String::Right(int count) const
}
+static bool ON_IsBig5Encoded(const char* buffer, int buffer_length)
+{
+ if (nullptr == buffer)
+ return false;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return false;
+
+ int db_count = 0;
+
+ int delta_i = 0;
+ for (int i = 0; i < buffer_length; i += (delta_i > 0) ? delta_i : 1)
+ {
+ delta_i = 1;
+ const char c = buffer[i];
+ if (c >= 0 && c <= 0x7F)
+ continue; // ASCII single byte
+ if (i + 2 <= buffer_length)
+ {
+ ON_Big5CodePoint big5_cp = ON_Big5CodePoint::Error;
+ const char* b2 = ON_Big5CodePoint::Decode(
+ buffer + i,
+ 2,
+ false, false,
+ &big5_cp
+ );
+ if (b2 == buffer + i + 2 && big5_cp.IsValid(false, false))
+ {
+ delta_i = 2;
+ ++db_count;
+ continue;
+ }
+ }
+ return false;
+ }
+
+ return (db_count > 0);
+}
+
+
+
+static unsigned Internal_Big5ToUTF32(
+ const char* buffer,
+ int buffer_length,
+ ON_SimpleArray& utf32
+)
+{
+ unsigned replacement_count = 0;
+
+ utf32.SetCount(0);
+
+ if (nullptr == buffer)
+ return false;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return false;
+
+ utf32.Reserve(buffer_length);
+
+ int db_count = 0;
+
+ int delta_i = 0;
+ for (int i = 0; i < buffer_length; i += (delta_i > 0) ? delta_i : 1)
+ {
+ delta_i = 1;
+ const char c = buffer[i];
+ if (c >= 0 && c <= 0x7F)
+ {
+ // ASCII single byte
+ utf32.Append((unsigned char)c);
+ continue;
+ }
+ if (i + 2 <= buffer_length)
+ {
+ ON_Big5CodePoint big5_cp = ON_Big5CodePoint::Error;
+ const char* b2 = ON_Big5CodePoint::Decode(
+ buffer + i,
+ 2,
+ false, false,
+ &big5_cp
+ );
+ if (b2 == buffer + i + 2 && big5_cp.IsValid(false, false))
+ {
+ const ON_UnicodeShortCodePoint u = ON_UnicodeShortCodePoint::CreateFromBig5(big5_cp, ON_UnicodeShortCodePoint::ReplacementCharacter);
+ if (u.IsReplacementCharacter())
+ ++replacement_count;
+ utf32.Append(u.UnicodeCodePoint());
+ delta_i = 2;
+ ++db_count;
+ continue;
+ }
+ }
+ utf32.Append(ON_UnicodeCodePoint::ON_ReplacementCharacter);
+ ++replacement_count;
+ }
+
+ return replacement_count;
+}
+
+static bool ON_IsUTF8Encoded(bool bSloppy, const char* buffer, int buffer_length)
+{
+ if (nullptr == buffer)
+ return false;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return false;
+
+ struct ON_UnicodeErrorParameters e0
+ = bSloppy
+ ? ON_UnicodeErrorParameters::FailOnErrors
+ : ON_UnicodeErrorParameters::MaskErrors;
+ e0.m_error_code_point = ON_UnicodeCodePoint::ON_InvalidCodePoint;
+ ON__UINT32 unicode_code_point;
+ int delta_i = 0;
+ for (int i = 0; i < buffer_length; i += (delta_i > 0) ? delta_i : 1)
+ {
+ struct ON_UnicodeErrorParameters e = e0;
+ unicode_code_point = ON_UnicodeCodePoint::ON_InvalidCodePoint;
+ delta_i = ON_DecodeUTF8(buffer + i, buffer_length - i, &e, &unicode_code_point);
+ if (delta_i > 0 && ON_IsValidUnicodeCodePoint(unicode_code_point) && i + delta_i <= buffer_length)
+ continue;
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool ON_IsASCIIEncoded(const char* buffer, int buffer_length)
+{
+ if (nullptr == buffer)
+ return false;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return false;
+
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return false;
+
+ for (int i = 0; i < buffer_length; ++i)
+ {
+ char c = buffer[i];
+ if (c >= 0 && c <= 127)
+ continue;
+ return false;
+ }
+
+ return true;
+}
+
+
+bool ON_String::IsPossibleEncoding(
+ ON_String::Encoding encoding,
+ const char* buffer,
+ int buffer_length
+)
+{
+ // In practice, this is used to choose between BIG5 and UTF when parsing strings that
+ // are names of components.
+ //
+ // If you need something more clever like NOTPOAD++ encoding detection, then you need
+ // to find a library that uses some sampling and linguistic analysis.
+ if (ON_String::Encoding::Unset == encoding)
+ return false;
+ if (ON_String::Encoding::Unknown == encoding)
+ return false;
+ if (nullptr == buffer)
+ return false;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (0 == buffer_length)
+ return true;
+ if (buffer_length < 0)
+ return false;
+
+ switch (encoding)
+ {
+ case ON_String::Encoding::ASCII:
+ return ON_IsASCIIEncoded(buffer, buffer_length);
+ case ON_String::Encoding::UTF8:
+ return ON_IsUTF8Encoded(false, buffer, buffer_length);
+ case ON_String::Encoding::BIG5andASCII:
+ return ON_IsBig5Encoded(buffer, buffer_length);
+ case ON_String::Encoding::SloppyUTF8:
+ return ON_IsUTF8Encoded(false, buffer, buffer_length);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool ON_String::IsPossibleEncoding(
+ ON_String::Encoding encoding
+) const
+{
+ return ON_String::IsPossibleEncoding(encoding, this->Array(), this->Length());
+}
+
+ON_String::Encoding ON_String::ProbableEncoding() const
+{
+ return ON_String::ProbableEncoding(this->Array(), this->Length());
+}
+
+ON_String::Encoding ON_String::ProbableEncoding(
+ const char* buffer,
+ int buffer_length
+)
+{
+ if (nullptr == buffer)
+ return ON_String::Encoding::Unknown;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+
+ if (buffer_length <= 0)
+ return ON_String::Encoding::Unknown;
+
+ if (ON_String::IsPossibleEncoding(ON_String::Encoding::ASCII, buffer, buffer_length))
+ return ON_String::Encoding::ASCII;
+ if (ON_String::IsPossibleEncoding(ON_String::Encoding::UTF8, buffer, buffer_length))
+ return ON_String::Encoding::UTF8;
+ if (ON_String::IsPossibleEncoding(ON_String::Encoding::BIG5andASCII, buffer, buffer_length))
+ return ON_String::Encoding::BIG5andASCII;
+ if (ON_String::IsPossibleEncoding(ON_String::Encoding::SloppyUTF8, buffer, buffer_length))
+ return ON_String::Encoding::SloppyUTF8;
+
+ return ON_String::Encoding::Unknown;
+}
+
+const ON_String ON_String::ToUTF8(
+ const char* buffer,
+ int buffer_length
+)
+{
+ if (nullptr == buffer)
+ return ON_String::EmptyString;
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0)
+ return ON_String::EmptyString;
+
+ unsigned bad_utf32_count = 0;
+ ON_SimpleArray utf32;
+
+ const ON_String::Encoding e = ON_String::ProbableEncoding(buffer, buffer_length);
+ switch (e)
+ {
+ case ON_String::Encoding::ASCII:
+ case ON_String::Encoding::UTF8:
+ return ON_String(buffer, buffer_length);
+ break;
+
+ case ON_String::Encoding::SloppyUTF8:
+ // ON_String -> ON_wString cleans up the slop.
+ // ON_wString -> ON_String converts the wchar_t UTF-X encoding to UTF-8.
+ return ON_String(ON_wString(ON_String(buffer, buffer_length)));
+ break;
+
+ case ON_String::Encoding::BIG5andASCII:
+ bad_utf32_count = Internal_Big5ToUTF32(buffer, buffer_length, utf32);
+ break;
+ }
+
+ const unsigned utf32_count = utf32.UnsignedCount();
+ if (utf32_count > 0 && utf32_count >= 2* bad_utf32_count)
+ {
+ unsigned int error_status = 0;
+ const unsigned int error_mask = 0xFFFFFFFFu;
+ const int utf8_count = ON_ConvertUTF32ToUTF8(
+ false, // bTestByteOrder,
+ utf32.Array(),
+ utf32.Count(),
+ nullptr, // char* sUTF8,
+ 0, // int sUTF8_count,
+ &error_status,
+ error_mask,
+ ON_UnicodeCodePoint::ON_ReplacementCharacter,
+ nullptr //const ON__UINT32 * *sNextUTF32
+ );
+ if (utf8_count > 0)
+ {
+ error_status = 0;
+ ON_String utf8;
+ utf8.ReserveArray(utf8_count);
+ utf8.SetLength(utf8_count);
+ ON_ConvertUTF32ToUTF8(
+ false, // bTestByteOrder,
+ utf32.Array(),
+ utf32.Count(),
+ utf8.Array(),
+ utf8_count,
+ &error_status,
+ error_mask,
+ ON_UnicodeCodePoint::ON_ReplacementCharacter,
+ nullptr //const ON__UINT32 * *sNextUTF32
+ );
+ return utf8;
+ }
+ }
+
+ return ON_String::EmptyString;
+}
+
+const ON_String ON_String::ToUTF8() const
+{
+ if (IsPossibleEncoding(ON_String::Encoding::UTF8))
+ return *this;
+ return ON_String::ToUTF8( this->Array(), this->Length() );
+}
+
+
+const ON_String ON_String::ToBIG5(
+ const char* buffer,
+ int buffer_length,
+ int* error_count
+)
+{
+ if (nullptr != error_count)
+ *error_count = 0;
+
+ if (nullptr == buffer)
+ return ON_String::EmptyString;
+
+ if (-1 == buffer_length)
+ buffer_length = ON_String::Length(buffer);
+ if (buffer_length <= 0 || buffer_length > ON_String::MaximumStringLength)
+ return ON_String::EmptyString;
+
+ switch (ON_String::ProbableEncoding(buffer,buffer_length))
+ {
+ case ON_String::Encoding::ASCII:
+ case ON_String::Encoding::BIG5andASCII:
+ return ON_String(buffer,buffer_length);
+ break;
+
+ case ON_String::Encoding::UTF8:
+ case ON_String::Encoding::SloppyUTF8:
+ {
+ const ON_SimpleArray< ON_Big5UnicodePair>& unicode_to_big5 = ON_Big5UnicodePair::UnicodeToBig5();
+
+ const int big5_capacity = 2 * buffer_length;
+ int big5_len = 0;
+ ON_String big5;
+ char* big5_buffer = big5.ReserveArray(big5_capacity + 1);
+
+ int code_point_count = 0;
+ int big5_code_point_count = 0;
+ int fail_count = 0;
+ int delta_i = 0;
+ for (int i = 0; i < buffer_length; i += (delta_i > 0 ? delta_i : 1))
+ {
+ ON_UnicodeErrorParameters e = ON_UnicodeErrorParameters::MaskErrors;
+ e.m_error_code_point = ON_UnicodeCodePoint::ON_InvalidCodePoint;
+ ON__UINT32 unicode_code_point = e.m_error_code_point;
+ delta_i = ON_DecodeUTF8(
+ buffer + i,
+ buffer_length - i,
+ &e,
+ &unicode_code_point
+ );
+ ++code_point_count;
+
+ ON_Big5UnicodePair pair = ON_Big5UnicodePair::Error;
+ for (;;)
+ {
+ if (delta_i <= 0)
+ break;
+ if (false == ON_IsStandardUnicodeCodePoint(unicode_code_point))
+ break;
+
+ if (unicode_code_point >= 0 && unicode_code_point <= 0x7F)
+ {
+ // ASCII code point.
+ pair = ON_Big5UnicodePair::Create(unicode_code_point, unicode_code_point);
+ break;
+ }
+
+ const ON_Big5UnicodePair key = ON_Big5UnicodePair::Create(0, unicode_code_point);
+ if (unicode_code_point != key.UnicodeCodePoint())
+ break;
+ const int k = unicode_to_big5.BinarySearch(&key, ON_Big5UnicodePair::CompareUnicodeCodePoint);
+ if (k < 0)
+ break;
+
+ // BIG5 code point
+ pair = unicode_to_big5[k];
+ break;
+ }
+
+ const int big5_delta = pair.IsValid(true, true) ? pair.Big5().Encode(big5_buffer + big5_len, big5_capacity - big5_len) : 0;
+ if (1 == big5_delta || 2 == big5_delta)
+ {
+ if (2 == big5_delta)
+ ++big5_code_point_count;
+ big5_len += big5_delta;
+ }
+ else
+ {
+ big5_buffer[big5_len++] = '?';
+ ++fail_count;
+ }
+ }
+ if (big5_code_point_count > 0 && 2 * fail_count < code_point_count)
+ {
+ big5.SetLength(big5_len);
+ return big5;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ return ON_String::EmptyString;
+}
+
+const ON_String ON_String::ToBIG5(
+ int* error_count
+) const
+{
+ const ON_String::Encoding e = this->ProbableEncoding();
+ if (ON_String::Encoding::ASCII == e || ON_String::Encoding::BIG5andASCII == e)
+ return *this;
+
+ return ON_String::ToBIG5(this->Array(), this->Length(), error_count);
+}
+
ON_CheckSum::ON_CheckSum()
{
Zero();
diff --git a/opennurbs_string.h b/opennurbs_string.h
index 3a83b09e..cafc2a7a 100644
--- a/opennurbs_string.h
+++ b/opennurbs_string.h
@@ -1016,7 +1016,21 @@ public:
True if c is '0', '1', ..., or '9'.
*/
static bool IsDecimalDigit(char c);
-
+
+ /*
+ Description:
+ Decode this char string using the encoding specified by windows_code_page
+ to a UTF encoded wide character string.
+ Parameters:
+ windows_code_page - [in]
+ WIndows code page. For example, big5 = 950.
+ Returns:
+ A UTF encoded wide charater string.
+ See Also:
+ ON_wString::MultiByteEncode()
+ */
+ const ON_wString MultiByteDecode(int windows_code_page) const;
+
private:
// Use IsEmpty() or IsNotEmpty() when you want a bool
// to test for the empty string.
@@ -1090,13 +1104,13 @@ public:
/*
Returns:
- number of nonzero elements in string.
+ number of char elements in string not including the null terminator.
*/
int Length() const;
/*
Returns:
- number of nonzero elements in the string.
+ number of char elements in string not including the null terminator.
*/
unsigned int UnsignedLength() const;
@@ -2082,6 +2096,122 @@ public:
ON_String* ext
);
+public:
+
+ enum class Encoding : unsigned int
+ {
+ ///
+ /// Encoding not set.
+ ///
+ Unset = 0,
+
+ ///
+ /// Encoding not known or cannot be determined.
+ ///
+ Unknown = 1,
+
+ ///
+ /// All byte values are in the range 0 to 0x7F (127)
+ ///
+ ASCII = 2,
+
+ ///
+ /// The sequence of byte values could be a UTF-8 encode string.
+ /// UTF-8 extends ASCII encoding.
+ ///
+ UTF8 = 3,
+
+ ///
+ /// The sequence of byte values could be a UTF-8 encoding with oversized
+ /// encodings or surrogate pair code points.
+ ///
+ SloppyUTF8 = 4,
+
+ ///
+ /// The sequence of byte values could be a BIG5 encode string
+ /// that may also contain single byte ASCII code points.
+ /// BIG5 is a double byte encoding and the first byte
+ /// always has the high bit set.
+ ///
+ BIG5andASCII = 5,
+ };
+
+ /*
+ Description:
+ Determine if the string's buffer can be parsed using the specified encoding.
+ */
+ bool IsPossibleEncoding(
+ ON_String::Encoding encoding
+ ) const;
+
+ /*
+ Description:
+ Determine if the buffer can be parsed using the specified encoding.
+ */
+ static bool IsPossibleEncoding(
+ ON_String::Encoding encoding,
+ const char* buffer,
+ int buffer_length
+ );
+
+ ON_String::Encoding ProbableEncoding() const;
+
+ static ON_String::Encoding ProbableEncoding(
+ const char* buffer,
+ int buffer_length
+ );
+
+ /*
+ Description:
+ Make an educated guess at the encoding and convert the string to UTF-8 encoding.
+ Returns:
+ If the string encoding could be determined and parsed, a UTF-8 encoded string is returned.
+ Otherwise EmptyString is returned.
+ */
+ static const ON_String ToUTF8(
+ const char* buffer,
+ int buffer_length
+ );
+
+ /*
+ Description:
+ Make an educated guess at the encoding and convert the string to UTF-8 encoding.
+ Returns:
+ If the string encoding could be determined and parsed, a UTF-8 encoded string is returned.
+ Otherwise EmptyString is returned.
+ */
+ const ON_String ToUTF8() const;
+
+ /*
+ Description:
+ Make an educated guess at the encoding and convert the string to BIG5 encoding.
+ Parameters:
+ error_count - [out]
+ number of errors in encoding (question mark is used as a replacement character).
+ Returns:
+ If the string encoding could be determined and parsed, a BIG5 encoded string is returned.
+ Otherwise EmptyString is returned.
+ */
+ static const ON_String ToBIG5(
+ const char* buffer,
+ int buffer_length,
+ int* error_count
+ );
+
+ /*
+ Description:
+ Make an educated guess at the encoding and convert the string to BIG5 encoding.
+ Parameters:
+ error_count - [out]
+ number of errors in encoding (question mark is used as a replacement character).
+ Returns:
+ If the string encoding could be determined and parsed, a BIG5 encoded string is returned.
+ Otherwise EmptyString is returned.
+ */
+ const ON_String ToBIG5(
+ int* error_count
+ ) const;
+
public:
~ON_String();
@@ -2250,6 +2380,25 @@ public:
///
static const ON_wString ByteOrderMark;
+ /*
+ Parameters:
+ bom_candidate - [in]
+ Returns;
+ 1: bom_candidate = ON_UnicodeCodePoint::ON_ByteOrderMark
+ -1: After swapping bytes, bom_candidate = ON_UnicodeCodePoint::ON_ByteOrderMark
+ 0: otherwise
+ */
+ static int ByteOrder(wchar_t bom_candidate);
+
+ /*
+ Parameters:
+ w - [in]
+ wchar_t value to swap bytes
+ Returns:
+ w with swapped byte order
+ */
+ static wchar_t SwapByteOrder(wchar_t w);
+
///
/// Identifies a built in string that can be used for testing.
///
@@ -2699,6 +2848,21 @@ public:
*/
static bool IsHorizontalSpace(wchar_t c);
+ /*
+ Description:
+ Encode this UTF encoded wide charater string to a char string
+ using the multibyte character set (MBCS) specified by windows_code_page.
+ Parameters:
+ windows_code_page - [in]
+ WIndows code page. For example, big5 = 950.
+ Returns:
+ A char string with the specified MBCS encoding.
+ Carefully control the scope and use of the returned string. Lacking context,
+ opennurbs assumes ON_Strings use the UTF-8 encoded.
+ See Also:
+ ON_String::MultiByteDecode()
+ */
+ const ON_String MultiByteEncode(int windows_code_page) const;
private:
// Use IsEmpty() or IsNotEmpty() when you want a bool
diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp
index adbb9e10..6db57394 100644
--- a/opennurbs_subd.cpp
+++ b/opennurbs_subd.cpp
@@ -574,6 +574,22 @@ ON__UINT_PTR ON_SubDVertexPtr::VertexDirection() const
return ON_SUBD_VERTEX_DIRECTION(m_ptr);
}
+void ON_SubDVertexPtr::ClearSavedSubdivisionPoints() const
+{
+ const ON_SubDVertex* v = ON_SUBD_VERTEX_POINTER(m_ptr);
+ if (nullptr != v)
+ v->ClearSavedSubdivisionPoints();
+}
+
+void ON_SubDVertexPtr::ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+) const
+{
+ const ON_SubDVertex* v = ON_SUBD_VERTEX_POINTER(m_ptr);
+ if (nullptr != v)
+ v->ClearSavedSubdivisionPoints(bClearNeighborhood);
+}
+
const ON_ComponentStatus ON_SubDVertexPtr::Status() const
{
const ON_SubDVertex* vertex = ON_SUBD_VERTEX_POINTER(m_ptr);
@@ -667,11 +683,36 @@ bool ON_SubDEdgePtr::EdgeIsDartCrease() const
return (nullptr != e) ? e->IsDartCrease() : false;
}
+bool ON_SubDEdgePtr::HasInteriorEdgeTopology(
+ bool bRequireOppositeFaceDirections
+) const
+{
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_ptr);
+ return (nullptr != e) ? e->HasInteriorEdgeTopology(bRequireOppositeFaceDirections) : false;
+}
+
+
ON__UINT_PTR ON_SubDEdgePtr::EdgeDirection() const
{
return ON_SUBD_EDGE_DIRECTION(m_ptr);
}
+void ON_SubDEdgePtr::ClearSavedSubdivisionPoints() const
+{
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_ptr);
+ if (nullptr != e)
+ e->ClearSavedSubdivisionPoints();
+}
+
+void ON_SubDEdgePtr::ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+) const
+{
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_ptr);
+ if (nullptr != e)
+ e->ClearSavedSubdivisionPoints(bClearNeighborhood);
+}
+
const class ON_SubDVertex* ON_SubDEdgePtr::RelativeVertex(
int relative_vertex_index
) const
@@ -690,6 +731,13 @@ const class ON_SubDVertex* ON_SubDEdgePtr::RelativeVertex(
return nullptr;
}
+
+unsigned ON_SubDEdgePtr::RelativeVertexId(int relative_vertex_index) const
+{
+ const ON_SubDVertex* v = this->RelativeVertex(relative_vertex_index);
+ return (nullptr != v) ? v->m_id : 0U;
+}
+
bool ON_SubDEdgePtr::RelativeVertexMark(
int relative_vertex_index,
bool missing_vertex_return_value
@@ -762,6 +810,64 @@ const ON_3dVector ON_SubDEdgePtr::RelativeDirection() const
return ON_3dVector::NanVector;
}
+const class ON_SubDFace* ON_SubDEdgePtr::RelativeFace(
+ int relative_face_index
+) const
+{
+ if (relative_face_index < 0 || relative_face_index > 1)
+ return nullptr; // invalid input
+
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(m_ptr);
+ if (nullptr == e )
+ return nullptr; // null input
+
+ if (nullptr == e || e->m_face_count > 2)
+ return nullptr; // nonmanifold edge
+
+ const ON_SubDFace* LR[2] = {};
+ for (unsigned short efi = 0; efi < e->m_face_count; ++efi)
+ {
+ const ON__UINT_PTR ptr = e->m_face2[efi].m_ptr;
+ const ON_SubDFace* f = ON_SUBD_FACE_POINTER(ptr);
+ if (nullptr == f)
+ continue;
+ if (0 == ON_SUBD_FACE_DIRECTION(ptr))
+ {
+ if (nullptr != LR[0])
+ return nullptr; // not an oriented manifold interior edge
+ LR[0] = f;
+ }
+ else
+ {
+ if (nullptr != LR[1])
+ return nullptr; // not an oriented manifold interior edge
+ LR[1] = f;
+ }
+ }
+
+ if (0 != ON_SUBD_EDGE_DIRECTION(m_ptr))
+ relative_face_index = 1 - relative_face_index;
+ return LR[relative_face_index];
+}
+
+bool ON_SubDEdgePtr::RelativeFaceMark(
+ int relative_face_index,
+ bool missing_face_return_value
+) const
+{
+ const ON_SubDFace* f = this->RelativeFace(relative_face_index);
+ return (nullptr != f) ? f->Mark() : missing_face_return_value;
+}
+
+ON__UINT8 ON_SubDEdgePtr::RelativeFaceMarkBits(
+ int relative_face_index,
+ ON__UINT8 missing_face_return_value
+) const
+{
+ const ON_SubDFace* f = this->RelativeFace(relative_face_index);
+ return (nullptr != f) ? f->MarkBits() : missing_face_return_value;
+}
+
const ON_ComponentStatus ON_SubDEdgePtr::Status() const
{
const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_ptr);
@@ -849,6 +955,23 @@ ON_SubDFace* ON_SubDFacePtr::Face() const
return ON_SUBD_FACE_POINTER(m_ptr);
}
+void ON_SubDFacePtr::ClearSavedSubdivisionPoints() const
+{
+ const ON_SubDFace* f = ON_SUBD_FACE_POINTER(m_ptr);
+ if (nullptr != f)
+ f->ClearSavedSubdivisionPoints();
+}
+
+void ON_SubDFacePtr::ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+) const
+{
+ const ON_SubDFace* f = ON_SUBD_FACE_POINTER(m_ptr);
+ if (nullptr != f)
+ f->ClearSavedSubdivisionPoints(bClearNeighborhood);
+}
+
+
unsigned int ON_SubDFacePtr::FaceId() const
{
const ON_SubDFace* f = ON_SUBD_FACE_POINTER(m_ptr);
@@ -954,6 +1077,64 @@ unsigned int ON_SubDComponentPtr::ComponentId() const
return (nullptr != c) ? c->m_id : 0U;
}
+void ON_SubDComponentPtr::ClearSavedSubdivisionPoints() const
+{
+ switch (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr)
+ {
+ case ON_SUBD_COMPONENT_TYPE_VERTEX:
+ {
+ const ON_SubDVertex* v = Vertex();
+ if (nullptr != v)
+ v->ClearSavedSubdivisionPoints();
+ }
+ break;
+ case ON_SUBD_COMPONENT_TYPE_EDGE:
+ {
+ const ON_SubDEdge* e = Edge();
+ if (nullptr != e)
+ e->ClearSavedSubdivisionPoints();
+ }
+ break;
+ case ON_SUBD_COMPONENT_TYPE_FACE:
+ {
+ const ON_SubDFace* f = Face();
+ if (nullptr != f)
+ f->ClearSavedSubdivisionPoints();
+ }
+ break;
+ }
+}
+
+void ON_SubDComponentPtr::ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+) const
+{
+ switch (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr)
+ {
+ case ON_SUBD_COMPONENT_TYPE_VERTEX:
+ {
+ const ON_SubDVertex* v = Vertex();
+ if (nullptr != v)
+ v->ClearSavedSubdivisionPoints(bClearNeighborhood);
+ }
+ break;
+ case ON_SUBD_COMPONENT_TYPE_EDGE:
+ {
+ const ON_SubDEdge* e = Edge();
+ if (nullptr != e)
+ e->ClearSavedSubdivisionPoints(bClearNeighborhood);
+ }
+ break;
+ case ON_SUBD_COMPONENT_TYPE_FACE:
+ {
+ const ON_SubDFace* f = Face();
+ if (nullptr != f)
+ f->ClearSavedSubdivisionPoints(bClearNeighborhood);
+ }
+ break;
+ }
+}
+
const ON_3dPoint ON_SubDComponentPtr::ControlNetCenterPoint() const
{
switch (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr)
@@ -1657,11 +1838,7 @@ const ON_SubDVertexPtr ON_SubDComponentPtr::VertexPtr() const
ON__UINT_PTR element_type = ON_SUBD_COMPONENT_TYPE(m_ptr);
if ( ON_SUBD_COMPONENT_TYPE_VERTEX == element_type)
return ON_SubDVertexPtr::Create(Vertex(), ComponentDirection());
-
- if ( 0 == element_type )
- return ON_SubDVertexPtr::Null;
-
- return ON_SUBD_RETURN_ERROR(ON_SubDVertexPtr::Null);
+ return ON_SubDVertexPtr::Null;
}
const ON_SubDEdgePtr ON_SubDComponentPtr::EdgePtr() const
@@ -1669,11 +1846,7 @@ const ON_SubDEdgePtr ON_SubDComponentPtr::EdgePtr() const
ON__UINT_PTR element_type = ON_SUBD_COMPONENT_TYPE(m_ptr);
if ( ON_SUBD_COMPONENT_TYPE_EDGE == element_type)
return ON_SubDEdgePtr::Create(Edge(), ComponentDirection());
-
- if ( 0 == element_type )
- return ON_SubDEdgePtr::Null;
-
- return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null);
+ return ON_SubDEdgePtr::Null;
}
const ON_SubDFacePtr ON_SubDComponentPtr::FacePtr() const
@@ -1681,11 +1854,22 @@ const ON_SubDFacePtr ON_SubDComponentPtr::FacePtr() const
ON__UINT_PTR element_type = ON_SUBD_COMPONENT_TYPE(m_ptr);
if ( ON_SUBD_COMPONENT_TYPE_FACE == element_type)
return ON_SubDFacePtr::Create(Face(), ComponentDirection());
+ return ON_SubDFacePtr::Null;
+}
- if ( 0 == element_type )
- return ON_SubDFacePtr::Null;
+const bool ON_SubDComponentPtr::IsVertex() const
+{
+ return (ON_SUBD_COMPONENT_TYPE_VERTEX == (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr));
+}
- return ON_SUBD_RETURN_ERROR(ON_SubDFacePtr::Null);
+const bool ON_SubDComponentPtr::IsEdge() const
+{
+ return (ON_SUBD_COMPONENT_TYPE_EDGE == (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr));
+}
+
+const bool ON_SubDComponentPtr::IsFace() const
+{
+ return (ON_SUBD_COMPONENT_TYPE_FACE == (ON_SUBD_COMPONENT_TYPE_MASK & m_ptr));
}
const ON_SubDComponentPtr ON_SubDComponentPtr::Create(
@@ -1864,6 +2048,291 @@ int ON_SubDComponentPtr::CompareComponentAndDirection(
return rc;
}
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ ON_SubDComponentPtr cptr,
+ double x
+)
+{
+ ON_SubDComponentAndNumber cx;
+ cx.m_cptr = cptr;
+ cx.m_x = x;
+ return cx;
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDVertex* v,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(v), x);
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDEdge* e,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(e), x);
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDFace* f,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(f), x);
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDVertexPtr vptr,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(vptr), x);
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDEdgePtr eptr,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(eptr), x);
+}
+
+const ON_SubDComponentAndNumber ON_SubDComponentAndNumber::Create(
+ const ON_SubDFacePtr fptr,
+ double x
+)
+{
+ return ON_SubDComponentAndNumber::Create(ON_SubDComponentPtr::Create(fptr), x);
+}
+
+
+const ON_SubDComponentPtr ON_SubDComponentAndNumber::Component() const
+{
+ return m_cptr;
+}
+
+void ON_SubDComponentAndNumber::SetComponent(ON_SubDComponentPtr cptr)
+{
+ m_cptr = cptr;
+}
+
+double ON_SubDComponentAndNumber::Number() const
+{
+ return m_x;
+}
+
+void ON_SubDComponentAndNumber::SetNumber(double x)
+{
+ m_x = x;
+}
+
+int ON_SubDComponentAndNumber::CompareComponent(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+)
+{
+ if (a == b)
+ return 0;
+ if (nullptr == a)
+ return -1;
+ if (nullptr == b)
+ return 1;
+ return ON_SubDComponentPtr::CompareComponent(&a->m_cptr, &b->m_cptr);
+}
+
+//
+int ON_SubDComponentAndNumber::CompareComponentAndDirection(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+)
+{
+ if (a == b)
+ return 0;
+ if (nullptr == a)
+ return -1;
+ if (nullptr == b)
+ return 1;
+ return ON_SubDComponentPtr::CompareComponentAndDirection(&a->m_cptr, &b->m_cptr);
+}
+
+int ON_SubDComponentAndNumber::CompareNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+)
+{
+ if (a == b)
+ return 0;
+ if (nullptr == a)
+ return -1;
+ if (nullptr == b)
+ return 1;
+ return ON_CompareDouble(a->m_x, b->m_x);
+}
+
+int ON_SubDComponentAndNumber::CompareComponentAndNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+)
+{
+ const int rc = ON_SubDComponentAndNumber::CompareComponent(a, b);
+ return (0 != rc) ? rc : ON_SubDComponentAndNumber::CompareNumber(a, b);
+}
+
+int ON_SubDComponentAndNumber::CompareComponentAndDirectionAndNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+)
+{
+ const int rc = ON_SubDComponentAndNumber::CompareComponentAndDirection(a, b);
+ return (0 != rc) ? rc : ON_SubDComponentAndNumber::CompareNumber(a, b);
+}
+
+
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ ON_SubDComponentPtr cptr,
+ ON_3dPoint P
+)
+{
+ ON_SubDComponentAndPoint cx;
+ cx.m_cptr = cptr;
+ cx.m_P = P;
+ return cx;
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDVertex* v,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(v), P);
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDEdge* e,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(e), P);
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDFace* f,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(f), P);
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDVertexPtr vptr,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(vptr), P);
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDEdgePtr eptr,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(eptr), P);
+}
+
+const ON_SubDComponentAndPoint ON_SubDComponentAndPoint::Create(
+ const ON_SubDFacePtr fptr,
+ ON_3dPoint P
+)
+{
+ return ON_SubDComponentAndPoint::Create(ON_SubDComponentPtr::Create(fptr), P);
+}
+
+
+const ON_SubDComponentPtr ON_SubDComponentAndPoint::Component() const
+{
+ return m_cptr;
+}
+
+void ON_SubDComponentAndPoint::SetComponent(ON_SubDComponentPtr cptr)
+{
+ m_cptr = cptr;
+}
+
+const ON_3dPoint ON_SubDComponentAndPoint::Point() const
+{
+ return m_P;
+}
+
+void ON_SubDComponentAndPoint::SetPoint(ON_3dPoint P)
+{
+ m_P = P;
+}
+
+int ON_SubDComponentAndPoint::CompareComponent(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+)
+{
+ if (lhs == rhs)
+ return 0;
+ if (nullptr == lhs)
+ return -1;
+ if (nullptr == rhs)
+ return 1;
+ return ON_SubDComponentPtr::CompareComponent(&lhs->m_cptr, &rhs->m_cptr);
+}
+
+//
+int ON_SubDComponentAndPoint::CompareComponentAndDirection(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+)
+{
+ if (lhs == rhs)
+ return 0;
+ if (nullptr == lhs)
+ return -1;
+ if (nullptr == rhs)
+ return 1;
+ return ON_SubDComponentPtr::CompareComponentAndDirection(&lhs->m_cptr, &rhs->m_cptr);
+}
+
+int ON_SubDComponentAndPoint::ComparePoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+)
+{
+ if (lhs == rhs)
+ return 0;
+ if (nullptr == lhs)
+ return -1;
+ if (nullptr == rhs)
+ return 1;
+ return ON_3dPoint::Compare(lhs->m_P, rhs->m_P);
+}
+
+int ON_SubDComponentAndPoint::CompareComponentAndPoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+)
+{
+ const int rc = ON_SubDComponentAndPoint::CompareComponent(lhs, rhs);
+ return (0 != rc) ? rc : ON_SubDComponentAndPoint::ComparePoint(lhs, rhs);
+}
+
+int ON_SubDComponentAndPoint::CompareComponentAndDirectionAndPoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+)
+{
+ const int rc = ON_SubDComponentAndPoint::CompareComponentAndDirection(lhs, rhs);
+ return (0 != rc) ? rc : ON_SubDComponentAndPoint::ComparePoint(lhs, rhs);
+}
+
+
static const bool Internal_FirstIsPartOfSecond(
const ON_SubDComponentPoint& first,
const ON_SubDComponentPoint& second
@@ -6975,8 +7444,6 @@ static void Internal_AccumulateVertexHash(
{
sha1.AccumulateBytes(&v->m_vertex_tag, sizeof(v->m_vertex_tag));
sha1.AccumulateDoubleArray(3, v->m_P);
- if (v->SubdivisionDisplacementIsNonzero())
- sha1.Accumulate3dVector(v->SubdivisionDisplacement());
}
}
@@ -7034,12 +7501,6 @@ static void Internal_AccumulateEdgeHash(
// Changing edge crease/smooth attributes often changes the regions used in face packing and exploding.
sha1.AccumulateBool(e->IsCrease());
}
-
- if (ON_SubDHashType::Geometry == hash_type)
- {
- if (e->SubdivisionDisplacementIsNonzero())
- sha1.Accumulate3dVector(e->SubdivisionDisplacement());
- }
}
@@ -7103,11 +7564,6 @@ static void Internal_AccumulateFaceHash(
sha1.AccumulateInteger32(eptr->EdgeId());
if (0 != ON_SUBD_EDGE_DIRECTION(eptr->m_ptr))
sha1.AccumulateBool(true);
- if (ON_SubDHashType::Geometry == hash_type)
- {
- if (f->SubdivisionDisplacementIsNonzero())
- sha1.Accumulate3dVector(f->SubdivisionDisplacement());
- }
}
}
@@ -7942,10 +8398,6 @@ unsigned int ON_SubDLevel::DumpTopology(
text_log.PushIndent();
- ON_3dVector D(ON_3dVector::NanVector);
- if (v->GetSubdivisionDisplacement(&D.x) && D.IsValid())
- text_log.Print( "v.SubdivisionDisplacement: (%g, %g, %g)\n", D.x, D.y, D.z );
-
ON_3dPoint P1(ON_3dPoint::NanPoint);
if (v->GetSavedSubdivisionPoint(&P1.x) && P1.IsValid())
text_log.Print( "v.SubdivisionPoint: (%g, %g, %g)\n", P1.x, P1.y, P1.z );
@@ -8171,10 +8623,6 @@ unsigned int ON_SubDLevel::DumpTopology(
text_log.PushIndent();
- ON_3dVector D(ON_3dVector::NanVector);
- if (e->GetSubdivisionDisplacement(&D.x) && D.IsValid())
- text_log.Print( "e.SubdivisionDisplacement: (%g, %g, %g)\n", D.x, D.y, D.z );
-
ON_3dPoint P1(ON_3dPoint::NanPoint);
if (e->GetSavedSubdivisionPoint(&P1.x) && P1.IsValid())
text_log.Print( "e.SubdivisionPoint: (%g, %g, %g)\n", P1.x, P1.y, P1.z );
@@ -8324,10 +8772,6 @@ unsigned int ON_SubDLevel::DumpTopology(
text_log.PushIndent();
- ON_3dVector D(ON_3dVector::NanVector);
- if (f->GetSubdivisionDisplacement(&D.x) && D.IsValid())
- text_log.Print( "f.SubdivisionDisplacement: (%g, %g, %g)\n", D.x, D.y, D.z );
-
ON_3dPoint P1(ON_3dPoint::NanPoint);
if (f->GetSavedSubdivisionPoint(&P1.x) && P1.IsValid())
text_log.Print( "f.SubdivisionPoint: (%g, %g, %g)\n", P1.x, P1.y, P1.z );
@@ -10155,9 +10599,6 @@ bool ON_SubDFace::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
if (count < 3)
return ON_SubDFace_GetSubdivisionPointError(this, subdivision_point, true);
- double displacementV[3] = { 0 };
- const bool bApplyDisplacement = GetSubdivisionDisplacement(displacementV);
-
const class ON_SubDEdgePtr* edge_ptr = m_edge4;
ON__UINT_PTR e_ptr;
@@ -10191,14 +10632,6 @@ bool ON_SubDFace::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = (vertexP[0][0] + vertexP[1][0] + vertexP[2][0] + vertexP[3][0])*0.25;
subdivision_point[1] = (vertexP[0][1] + vertexP[1][1] + vertexP[2][1] + vertexP[3][1])*0.25;
subdivision_point[2] = (vertexP[0][2] + vertexP[1][2] + vertexP[2][2] + vertexP[3][2])*0.25;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -10208,14 +10641,6 @@ bool ON_SubDFace::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = (vertexP[0][0] + vertexP[1][0] + vertexP[2][0]) / 3.0;
subdivision_point[1] = (vertexP[0][1] + vertexP[1][1] + vertexP[2][1]) / 3.0;
subdivision_point[2] = (vertexP[0][2] + vertexP[1][2] + vertexP[2][2]) / 3.0;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -10272,13 +10697,6 @@ bool ON_SubDFace::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = faceP[0] / n;
subdivision_point[1] = faceP[1] / n;
subdivision_point[2] = faceP[2] / n;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
return true;
}
@@ -10960,77 +11378,31 @@ bool ON_SubDEdge::EdgeSurfaceCurveIsSet() const
bool ON_SubDComponentBase::SubdivisionDisplacementIsNonzero() const
{
- return (0 != ON_SUBD_CACHE_DISPLACEMENT_FLAG(m_saved_points_flags))
- ? (
- (0.0 != m_displacement_V[0] || 0.0 != m_displacement_V[1] || 0.0 != m_displacement_V[2])
- && ON_IS_VALID(m_displacement_V[0]) && ON_IS_VALID(m_displacement_V[1]) && ON_IS_VALID(m_displacement_V[2])
- )
- : false;
+ // deprecated - never used.
+ return false;
}
-bool ON_SubDComponentBase::SetSubdivisionDisplacement(
- const double displacement[3]
- )
+bool ON_SubDComponentBase::SetSubdivisionDisplacement(const double*)
{
- if (
- nullptr == displacement
- || (0.0 == displacement[0] && 0.0 == displacement[1] && 0.0 == displacement[2])
- )
- {
- ClearSubdivisionDisplacement();
- return true;
- }
-
- if ( ON_IsValid(displacement[0]) && ON_IsValid(displacement[1]) && ON_IsValid(displacement[2]) )
- {
- m_saved_points_flags |= ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT;
- m_displacement_V[0] = displacement[0];
- m_displacement_V[1] = displacement[1];
- m_displacement_V[2] = displacement[2];
- return true;
- }
-
- ClearSubdivisionDisplacement();
-
- return ON_SUBD_RETURN_ERROR(false);
+ // deprecated - never used.
+ return false;
}
void ON_SubDComponentBase::ClearSubdivisionDisplacement() const
{
- if (0 != (m_saved_points_flags & ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT))
- {
- ON_SUBD_CACHE_CLEAR_POINT_FLAG(m_saved_points_flags);
- ON_SUBD_CACHE_CLEAR_DISPLACEMENT_FLAG(m_saved_points_flags);
- }
+ // deprecated - never used.
}
-bool ON_SubDComponentBase::GetSubdivisionDisplacement(
- double displacement[3]
- ) const
+bool ON_SubDComponentBase::GetSubdivisionDisplacement(double*) const
{
- const bool rc = (0 != (ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT & m_saved_points_flags));
- if (nullptr != displacement)
- {
- if (rc)
- {
- displacement[0] = m_displacement_V[0];
- displacement[1] = m_displacement_V[1];
- displacement[2] = m_displacement_V[2];
- }
- else
- {
- displacement[0] = 0.0;
- displacement[1] = 0.0;
- displacement[2] = 0.0;
- }
- }
- return rc;
+ // deprecated - never used.
+ return false;
}
const ON_3dVector ON_SubDComponentBase::SubdivisionDisplacement() const
{
- ON_3dVector v(ON_3dVector::ZeroVector);
- return GetSubdivisionDisplacement(&v.x) ? v : ON_3dVector::ZeroVector;
+ // deprecated - never used.
+ return ON_3dVector::ZeroVector;
}
void ON_SubDComponentBase::Internal_SetSavedSurfacePointFlag(bool bSavedSurfacePointFlag) const
@@ -11316,9 +11688,6 @@ bool ON_SubDEdge::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
if (nullptr == edge_vertex[0] || nullptr == edge_vertex[1])
return ON_SubDEdge_GetSubdivisionPointError(this, subdivision_point, nullptr, true);
- double displacementV[3] = { 0 };
- const bool bApplyDisplacement = GetSubdivisionDisplacement(displacementV);
-
const double* edgeP[2] = { edge_vertex[0]->m_P, edge_vertex[1]->m_P };
const double edgePsum[3] = { edgeP[0][0] + edgeP[1][0], edgeP[0][1] + edgeP[1][1], edgeP[0][2] + edgeP[1][2] };
@@ -11386,14 +11755,6 @@ bool ON_SubDEdge::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = EP[0] + 0.0625*(facePsum[0][0] + facePsum[1][0]);
subdivision_point[1] = EP[1] + 0.0625*(facePsum[0][1] + facePsum[1][1]);
subdivision_point[2] = EP[2] + 0.0625*(facePsum[0][2] + facePsum[1][2]);
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -11408,14 +11769,6 @@ bool ON_SubDEdge::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = EP[0] + (0.5*edgePsum[0] + facePsum[0][0] + facePsum[1][0]) / 12.0;
subdivision_point[1] = EP[1] + (0.5*edgePsum[1] + facePsum[0][1] + facePsum[1][1]) / 12.0;
subdivision_point[2] = EP[2] + (0.5*edgePsum[2] + facePsum[0][2] + facePsum[1][2]) / 12.0;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -11433,14 +11786,6 @@ bool ON_SubDEdge::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = EP[0] + x * edgePsum[0] + facePsum[0][0] / f0 + facePsum[1][0] / f1;
subdivision_point[1] = EP[1] + x * edgePsum[1] + facePsum[0][1] / f0 + facePsum[1][1] / f1;
subdivision_point[2] = EP[2] + x * edgePsum[2] + facePsum[0][2] / f0 + facePsum[1][2] / f1;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -11449,14 +11794,6 @@ bool ON_SubDEdge::EvaluateCatmullClarkSubdivisionPoint(double subdivision_point[
subdivision_point[0] = 0.5*edgePsum[0];
subdivision_point[1] = 0.5*edgePsum[1];
subdivision_point[2] = 0.5*edgePsum[2];
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -12062,9 +12399,6 @@ bool ON_SubDVertex::EvaluateCatmullClarkSubdivisionPoint(double subdivision_poin
if (nullptr == subdivision_point )
return ON_SubDVertex_GetSubdivisionPointError(this, subdivision_point, nullptr, false);
- double displacementV[3] = { 0 };
- const bool bApplyDisplacement = GetSubdivisionDisplacement(displacementV);
-
const double* vertexP = m_P;
const unsigned int n = (nullptr != m_edges ? m_edge_count : 0);
@@ -12120,14 +12454,6 @@ bool ON_SubDVertex::EvaluateCatmullClarkSubdivisionPoint(double subdivision_poin
subdivision_point[0] = (vertexP[0] * 6.0 + (edgeP[0][0] + edgeP[1][0]))*0.125;
subdivision_point[1] = (vertexP[1] * 6.0 + (edgeP[0][1] + edgeP[1][1]))*0.125;
subdivision_point[2] = (vertexP[2] * 6.0 + (edgeP[0][2] + edgeP[1][2]))*0.125;
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -12140,14 +12466,6 @@ bool ON_SubDVertex::EvaluateCatmullClarkSubdivisionPoint(double subdivision_poin
subdivision_point[0] = vertexP[0];
subdivision_point[1] = vertexP[1];
subdivision_point[2] = vertexP[2];
-
- if (bApplyDisplacement)
- {
- subdivision_point[0] += displacementV[0];
- subdivision_point[1] += displacementV[1];
- subdivision_point[2] += displacementV[2];
- }
-
return true;
}
@@ -14190,6 +14508,104 @@ const ON_SubDEdge* ON_SubD::SplitEdge(
return subdimple->SplitEdge(edge,vertex_location);
}
+const ON_SubDEdgePtr ON_SubD::SplitEdge(
+ ON_SubDEdgePtr eptr,
+ ON_3dPoint vertex_location,
+ unsigned new_edge_end
+)
+{
+ if ( 0 != new_edge_end && 1 != new_edge_end)
+ return ON_SubDEdgePtr::Null;
+ ON_SubDEdge* e = eptr.Edge();
+ if (nullptr == e)
+ return ON_SubDEdgePtr::Null;
+ const ON_SubDVertex* v[3] = { e->m_vertex[0],nullptr,e->m_vertex[1] };
+ ON_SubDEdge* e1 = const_cast(this->SplitEdge(e, vertex_location));
+ if ( nullptr == e1)
+ return ON_SubDEdgePtr::Null;
+
+ const ON__UINT_PTR eptr_dir = eptr.EdgeDirection();
+ const ON_SubDEdgePtr e1ptr = ON_SubDEdgePtr::Create(e1, eptr_dir);
+
+ if (v[0] != e->m_vertex[0] || v[2] != e1->m_vertex[1])
+ {
+ ON_SUBD_ERROR("Error in SplitEdge(ON_SubDEdge*,...).");
+ return e1ptr;
+ }
+
+ v[1] = e->m_vertex[1];
+ if ( nullptr == v[1] || v[1] != e1->m_vertex[0] || 2 != v[1]->m_edge_count)
+ {
+ ON_SUBD_ERROR("Error in SplitEdge(ON_SubDEdge*,...).");
+ return e1ptr;
+ }
+
+ if (e->m_face_count != e1->m_face_count)
+ {
+ ON_SUBD_ERROR("Error in SplitEdge(ON_SubDEdge*,...).");
+ return e1ptr;
+ }
+
+ const unsigned v0edex = v[0]->EdgeArrayIndex(e);
+ const unsigned v2edex = v[2]->EdgeArrayIndex(e1);
+ if (v0edex >= v[0]->EdgeCount() || v2edex >= v[2]->EdgeCount())
+ {
+ ON_SUBD_ERROR("Error in SplitEdge(ON_SubDEdge*,...).");
+ return e1ptr;
+ }
+
+ if (0 == eptr_dir && 1 == new_edge_end)
+ {
+ return e1ptr;
+ }
+
+ if (1 == eptr_dir && 0 == new_edge_end)
+ {
+ return e1ptr;
+ }
+
+ // swap e and e1 topology connections to put the "new edge" where the caller wants it.
+
+ for (unsigned short efi = 0; efi < e->m_face_count; ++efi)
+ {
+ ON_SubDFace* f = const_cast(e->Face(efi));
+ if (nullptr == f)
+ continue;
+ const unsigned edge_count = f->EdgeCount();
+ const unsigned fei = f->EdgeArrayIndex(e);
+ const unsigned fe1i = f->EdgeArrayIndex(e1);
+ if (fei >= edge_count || fe1i >= edge_count)
+ continue;
+
+ const ON_SubDEdgePtr feptr = ON_SubDEdgePtr::Create(e1, f->EdgePtr(fei).EdgeDirection()); // e1 is correct - we are swapping
+ if (fei < 4)
+ f->m_edge4[fei] = feptr;
+ else
+ f->m_edgex[fei - 4] = feptr;
+
+ const ON_SubDEdgePtr fe1ptr = ON_SubDEdgePtr::Create(e, f->EdgePtr(fe1i).EdgeDirection()); // e is correct - we are swapping
+ if (fe1i < 4)
+ f->m_edge4[fe1i] = fe1ptr;
+ else
+ f->m_edgex[fe1i - 4] = fe1ptr;
+ }
+
+ const ON_SubDEdgePtr new_v0eptr = ON_SubDEdgePtr::Create(e1, v[0]->EdgePtr(v0edex).EdgeDirection());
+ const ON_SubDEdgePtr new_v2eptr = ON_SubDEdgePtr::Create(e, v[2]->EdgePtr(v2edex).EdgeDirection());
+ v[0]->m_edges[v0edex] = new_v0eptr;
+ v[1]->m_edges[0] = new_v0eptr.Reversed();
+ v[1]->m_edges[1] = new_v2eptr.Reversed();
+ v[2]->m_edges[v2edex] = new_v2eptr;
+ e1->m_vertex[0] = v[0];
+ e1->m_vertex[1] = v[1];
+ e->m_vertex[0] = v[1];
+ e->m_vertex[1] = v[2];
+
+
+ return e1ptr;
+}
+
+
const ON_SubDEdge * ON_SubD::SplitFace(
ON_SubDFace * face,
const ON_SubDVertex * v0,
@@ -14240,6 +14656,351 @@ const ON_SubDEdge* ON_SubD::SplitFace(
return subdimple->SplitFace(face,fvi0,fvi1);
}
+static void Internal_SplitFaceSwapFacePtr(
+ ON_SubDFace* f,
+ ON__UINT_PTR pairA[2],
+ ON__UINT_PTR pairB[2]
+ )
+{
+ if (nullptr == f)
+ return;
+
+ if (pairA[0] == pairB[0] || pairA[1] == pairB[0] || pairA[0] == pairB[1] || pairA[1] == pairB[1])
+ {
+ ON_SUBD_ERROR("pairA[] and pairB[] must be disjoint sets of values.");
+ return;
+ }
+
+ ON_SubDEdgePtr* feptr = f->m_edge4;
+ for (unsigned short fei = 0; fei < f->m_edge_count; ++fei, ++feptr)
+ {
+ if (4 == fei)
+ {
+ feptr = f->m_edgex;
+ if (nullptr == feptr)
+ break;
+ }
+
+ ON_SubDEdge* fe = ON_SUBD_EDGE_POINTER(feptr->m_ptr);
+ if (nullptr == fe)
+ continue;
+
+ ON_SubDFacePtr* efptr = fe->m_face2;
+ for (unsigned short efi = 0; efi < fe->m_face_count; ++efi, ++efptr)
+ {
+ if (2 == efi)
+ {
+ efptr = fe->m_facex;
+ if (nullptr == efptr)
+ break;
+ }
+ const ON__UINT_PTR x = (efptr->m_ptr & ON_SUBD_COMPONENT_POINTER_MASK);
+ const ON__UINT_PTR d = (efptr->m_ptr & ON_SUBD_COMPONENT_DIRECTION_MASK);
+ if (x == pairA[0])
+ efptr->m_ptr = (pairA[1] | d);
+ else if (x == pairB[0])
+ efptr->m_ptr = (pairB[1] | d);
+ }
+
+
+ ON_SubDVertex* v = const_cast(feptr->RelativeVertex(0));
+ if (nullptr != v && nullptr != v->m_faces)
+ {
+ for (unsigned short vfi = 0; vfi < v->m_face_count; ++vfi)
+ {
+ const ON__UINT_PTR x = (ON__UINT_PTR)v->m_faces[vfi];
+ if (x == pairA[0])
+ v->m_faces[vfi] = (ON_SubDFace*)pairA[1];
+ else if (x == pairB[0])
+ v->m_faces[vfi] = (ON_SubDFace*)pairB[1];
+ }
+ }
+ }
+}
+
+static bool Internal_ValidateFaceTopology(
+ const ON_SubDFace* f
+)
+{
+ if (nullptr == f)
+ return false;
+
+ if (f->m_edge_count < 3 || 0 == f->m_id)
+ return ON_SUBD_RETURN_ERROR(false);
+
+ const ON_SubDVertex* firstv = nullptr;
+ const ON_SubDVertex* ev[2] = {};
+ bool rc = true;
+ unsigned short fdex;
+ const ON_SubDEdgePtr* feptr = f->m_edge4;
+ for (unsigned short fei = 0; fei < f->m_edge_count; ++fei, ++feptr)
+ {
+ if (4 == fei)
+ {
+ feptr = f->m_edgex;
+ if (nullptr == feptr)
+ {
+ ON_SUBD_ERROR("face m_edge_count > 4 and m_edgex is nullptr.");
+ rc = false;
+ break;
+ }
+ }
+
+ ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(feptr->m_ptr);
+ if (nullptr == e || 0 == e->m_id)
+ {
+ ON_SUBD_ERROR("null edge in face.");
+ rc = false;
+ continue;
+ }
+
+ if (e != f->Edge(fei))
+ {
+ ON_SUBD_ERROR("face->Edge(fei) != e");
+ rc = false;
+ }
+
+ for (unsigned short k = fei + 1; k < f->m_edge_count; ++k)
+ {
+ if (e == f->Edge(k))
+ {
+ ON_SUBD_ERROR("face references e 2 or more times.");
+ rc = false;
+ }
+ }
+
+ const ON__UINT_PTR fedir = ON_SUBD_FACE_DIRECTION(feptr->m_ptr);
+
+ if (0 == fei)
+ {
+ firstv = e->m_vertex[fedir];
+ ev[1] = firstv;
+ }
+
+ if (ev[1] != e->m_vertex[fedir])
+ {
+ ON_SUBD_ERROR("consecutive edge pointers do not have the same vertex at the end/start.");
+ rc = false;
+ }
+
+ ev[0] = e->m_vertex[fedir];
+ ev[1] = e->m_vertex[1-fedir];
+ if (nullptr == ev[0] || nullptr == ev[1])
+ {
+ ON_SUBD_ERROR("edge has null vertex pointers.");
+ rc = false;
+ }
+ else if (ev[0] == ev[1])
+ {
+ ON_SUBD_ERROR("edge begins and ends at the same vertex.");
+ rc = false;
+ }
+
+ fdex = e->m_face_count;
+ const ON_SubDFacePtr* efptr = e->m_face2;
+ for (unsigned short efi = 0; efi < e->m_face_count; ++efi, ++efptr)
+ {
+ if (2 == efi)
+ {
+ efptr = e->m_facex;
+ if (nullptr == efptr)
+ {
+ ON_SUBD_ERROR("null edge in face.");
+ rc = false;
+ break;
+ }
+ }
+
+ const ON_SubDFace* ef = ON_SUBD_FACE_POINTER(efptr->m_ptr);
+ if (f == ef)
+ {
+ if (fdex < e->m_face_count)
+ {
+ ON_SUBD_ERROR("ef references face 2 or more times.");
+ rc = false;
+ break;
+ }
+ fdex = efi;
+ ON__UINT_PTR efdir = ON_SUBD_FACE_DIRECTION(efptr->m_ptr);
+ if (efdir != fedir)
+ {
+ ON_SUBD_ERROR("face and edge have inconsistent direction settings.");
+ rc = false;
+ break;
+ }
+ }
+ }
+ if (fdex >= e->m_face_count)
+ {
+ ON_SUBD_ERROR("The face references and edge that does not reference the face.");
+ rc = false;
+ }
+
+ const ON_SubDVertex* v = ev[0];
+ if (nullptr != v )
+ {
+ if (v != f->Vertex(fei))
+ {
+ ON_SUBD_ERROR("face->Vertex(fei) != v");
+ rc = false;
+ }
+
+ fdex = v->m_face_count;
+ for (unsigned short vfi = 0; vfi < v->m_face_count; ++vfi)
+ {
+ const ON_SubDFace* vf = v->m_faces[vfi];
+ if (f == vf)
+ {
+ if (fdex < v->m_face_count)
+ {
+ ON_SUBD_ERROR("ef references face 2 or more times.");
+ rc = false;
+ break;
+ }
+ fdex = vfi;
+ }
+ }
+ if (fdex >= v->m_face_count)
+ {
+ ON_SUBD_ERROR("The face references a vertex that does not reference the face.");
+ rc = false;
+ }
+
+ for (unsigned short k = fei + 1; k < f->m_edge_count; ++k)
+ {
+ if (v == f->Vertex(k))
+ {
+ ON_SUBD_ERROR("face references v 2 or more times.");
+ rc = false;
+ }
+ }
+ }
+ }
+
+ if (firstv != ev[1])
+ {
+ ON_SUBD_ERROR("The vertices at the end ofthe final edge and start of the initial edges are different.");
+ rc = false;
+ }
+
+ return rc;
+}
+
+const ON_SubDEdgePtr ON_SubD::SplitFace(
+ class ON_SubDFace* face,
+ unsigned int fvi0,
+ unsigned int fvi1,
+ unsigned new_face_side
+)
+{
+ if (new_face_side != 0 && new_face_side != 1)
+ return ON_SUBD_RETURN_ERROR(ON_SubDEdgePtr::Null);
+ face->ClearSavedSubdivisionPoints(true);
+ ON_SubDEdge* e = const_cast(this->SplitFace(face, fvi0, fvi1));
+ if (nullptr == e)
+ return ON_SubDEdgePtr::Null;
+
+ const ON_SubDEdgePtr eptr = ON_SubDEdgePtr::Create(e, 0);
+
+ ON_SubDFace* f2[2] = { const_cast(eptr.RelativeFace(0)), const_cast(eptr.RelativeFace(1)) };
+ if (nullptr == f2[0] || nullptr == f2[1] || f2[0] == f2[1])
+ return ON_SUBD_RETURN_ERROR(eptr);
+ if (face != f2[0] && face != f2[1])
+ return ON_SUBD_RETURN_ERROR(eptr);
+
+ if ( face == f2[new_face_side])
+ {
+
+ // swap faces
+
+ // markA and markB need to be different, must be values no valid heap pointer will
+ // ever have, and need to be chosen so that debug runtime pointer checking code will not
+ // throw exceptions. Values like 1 and 2 would work just fine except debug runtime
+ // pointer checking code is mindlessly diligent. the addresses of marking_faces[0]
+ // and marking_faces[1] "tricks" the debug runtime pointer checking code and
+ // are values no valid app heap will ever have (they point to stack
+ // used by this call).
+ const ON_SubDFace marking_faces[2] = {};
+ const ON__UINT_PTR markA = (ON__UINT_PTR)(&marking_faces[0]);
+ const ON__UINT_PTR markB = (ON__UINT_PTR)(&marking_faces[1]);
+
+ // The mark first, then change approach must be used because f2[0] and f2[1]
+ // have multiple vertex and edge references and we must avoid swapping twice.
+
+ // First change face references f2[0] to markA and f2[1] to markB.
+ ON__UINT_PTR pairA[2] = { (ON__UINT_PTR)f2[0], markA };
+ ON__UINT_PTR pairB[2] = { (ON__UINT_PTR)f2[1], markB };
+ for (unsigned fdex = 0; fdex < 2; ++fdex)
+ Internal_SplitFaceSwapFacePtr(f2[fdex], pairA, pairB);
+
+ // ... then change markA to f2[1] and markB to f2[0].
+ pairA[0] = markA; pairA[1] = (ON__UINT_PTR)f2[1];
+ pairB[0] = markB; pairB[1] = (ON__UINT_PTR)f2[0];
+ for (unsigned fdex = 0; fdex < 2; ++fdex)
+ Internal_SplitFaceSwapFacePtr(f2[fdex], pairA, pairB);
+
+ // At this point, all the vertex and edge references to f2[0] and f2[1] have been swapped.
+
+ // All that is left is to wwap edge references on f2[0] and f2[1].
+ const unsigned short edge_count[2] = { f2[1]->m_edge_count, f2[0]->m_edge_count };
+ const unsigned short edgex_capacity[2] = { f2[1]->m_edgex_capacity, f2[0]->m_edgex_capacity };
+ ON_SubDEdgePtr edge4[2][4] = {
+ {f2[1]->m_edge4[0],f2[1]->m_edge4[1],f2[1]->m_edge4[2],f2[1]->m_edge4[3]},
+ {f2[0]->m_edge4[0],f2[0]->m_edge4[1],f2[0]->m_edge4[2],f2[0]->m_edge4[3]}
+ };
+ ON_SubDEdgePtr* edgex[2] = { f2[1]->m_edgex, f2[0]->m_edgex };
+ for (unsigned i = 0; i < 2; ++i)
+ {
+ f2[i]->m_edge_count = edge_count[i];
+ f2[i]->m_edgex_capacity = edgex_capacity[i];
+ f2[i]->m_edge4[0] = edge4[i][0];
+ f2[i]->m_edge4[1] = edge4[i][1];
+ f2[i]->m_edge4[2] = edge4[i][2];
+ f2[i]->m_edge4[3] = edge4[i][3];
+ f2[i]->m_edgex = edgex[i];
+ }
+
+ //// The Internal_SplitFaceSwapFacePtr() calls above have switched the
+ //// ON_SubDFace pointer values in e->m_face2[]. Because the faces
+ //// are switching sides, we need to swap the directions in e->m_face2[].
+ ////
+ //e->m_face2[0] = e->m_face2[0].Reversed();
+ //e->m_face2[1] = e->m_face2[1].Reversed();
+ }
+
+
+ if (2 == e->m_face_count && face == e->m_face2[new_face_side].Face())
+ {
+ // Adjust the e->m_face2[] so it jibes with the relative face values exactly.
+ ON_SubDFacePtr tmp = e->m_face2[0];
+ e->m_face2[0] = e->m_face2[1];
+ e->m_face2[1] = tmp;
+ }
+
+ if (false == Internal_ValidateFaceTopology(f2[0])
+ || false == Internal_ValidateFaceTopology(f2[1])
+ || f2[0] == f2[1]
+ )
+ {
+ ON_SUBD_ERROR("Invalid faces.");
+ }
+
+
+ return eptr; // new face is on the requested side
+}
+
+const ON_SubDEdgePtr ON_SubD::SplitFace(
+ class ON_SubDFace* face,
+ const class ON_SubDVertex* v0,
+ const class ON_SubDVertex* v1,
+ unsigned new_face_side
+)
+{
+ if (nullptr == face)
+ return ON_SubDEdgePtr::Null;
+ return this->SplitFace(face, face->VertexIndex(v0), face->VertexIndex(v1), new_face_side);
+}
+
void ON_SubD::MarkAggregateComponentStatusAsNotCurrent() const
{
const ON_SubDLevel* level = ActiveLevelConstPointer();
@@ -19046,6 +19807,260 @@ unsigned int ON_UniqueTester::Count() const
return (unsigned int)count;
}
+bool ON_SubDEdgeChain::GetSideComponents(
+ unsigned relative_side,
+ ON_SimpleArray& side_components
+) const
+{
+ return ON_SubDEdgeChain::GetSideComponents(EdgeChain(), relative_side, side_components);
+}
+
+bool ON_SubDEdgeChain::GetSideComponents(
+ const ON_SimpleArray& edge_chain,
+ unsigned relative_side,
+ ON_SimpleArray& side_components
+)
+{
+ side_components.SetCount(0);
+ if (relative_side > 1U)
+ return false;
+
+ const unsigned edge_count = edge_chain.UnsignedCount();
+ if (edge_count < 1)
+ return false;
+
+ if (false == ON_SubDEdgeChain::IsValidEdgeChain(edge_chain, true))
+ return false; // bozo vaccine
+
+ const bool bClosedLoop = edge_count >= 3 && edge_chain[0].RelativeVertex(0) == edge_chain[edge_count - 1].RelativeVertex(1);
+
+ ON_SubDSectorIterator sit;
+ const ON__INT_PTR sitdir = ((unsigned)1 - relative_side);
+
+ ON_SubDEdgePtr chain_eptr1 = ON_SubDEdgePtr::Null;
+ const ON_SubDFace* f1 = nullptr;
+ const ON_SubDVertex* v = nullptr;
+ if (false == bClosedLoop)
+ {
+ // first edge/face pair at the chain's initial vertex
+ chain_eptr1 = edge_chain[0];
+ f1 = chain_eptr1.RelativeFace(relative_side);
+ if (nullptr != f1)
+ {
+ v = chain_eptr1.RelativeVertex(0);
+ if (nullptr != v && v == sit.Initialize(f1, sitdir, v))
+ {
+ ON_SubDEdgePtr e = sit.CurrentEdgePtr(0);
+ if (e.IsNotNull())
+ {
+ side_components.Append(ON_SubDComponentPtr::Create(e));
+ side_components.Append(ON_SubDComponentPtr::Create(f1));
+ }
+ }
+ }
+ }
+ else
+ {
+ chain_eptr1 = edge_chain[edge_count-1];
+ f1 = chain_eptr1.RelativeFace(relative_side);
+ }
+
+ // first_side_components_edge is needed for closed loops.
+ ON_SubDComponentPtr first_side_components_edge = ON_SubDComponentPtr::Null;
+ for (unsigned j = bClosedLoop ? 0 : 1; j < edge_count; ++j)
+ {
+ const ON_SubDEdgePtr chain_eptr0 = chain_eptr1;
+ chain_eptr1 = edge_chain[j];
+ v = chain_eptr1.RelativeVertex(0);
+ const ON_SubDFace* f0 = f1;
+ f1 = chain_eptr1.RelativeFace(relative_side);
+ if (nullptr != f0 && f0 == f1)
+ {
+ // side_components[] = ...f0,v,f1.
+ unsigned n = side_components.UnsignedCount();
+ if (0 == n || f0 != side_components[n-1].Face())
+ side_components.Append(ON_SubDComponentPtr::Create(f0));
+ side_components.Append(ON_SubDComponentPtr::Create(v));
+ side_components.Append(ON_SubDComponentPtr::Create(f1));
+ continue;
+ }
+
+ // sit_dex < sit_max prevents possiblity of an infinite loop if the chain or subd is corrupt
+ const unsigned sit_max = v->FaceCount();
+ unsigned sit_dex = 1;
+
+ const ON_SubDFace* f = nullptr;
+ for (;;)
+ {
+ if (nullptr == f0)
+ break;
+
+ if (nullptr == v || v != sit.Initialize(f0, sitdir, v))
+ break;
+
+ ON_SubDEdgePtr eptr = sit.CurrentEdgePtr(1);
+ if (eptr.IsNull())
+ break;
+
+ unsigned n = side_components.UnsignedCount();
+ if (0 == n || f0 != side_components[n - 1].Face())
+ side_components.Append(ON_SubDComponentPtr::Create(f0));
+ side_components.Append(ON_SubDComponentPtr::Create(eptr));
+ if (first_side_components_edge.IsNull())
+ first_side_components_edge = ON_SubDComponentPtr::Create(eptr); // needed for closed loops
+
+ // The HasInteriorEdgeTopology() checks prevent getting garbage from
+ // non-oriented SubDs. If people complain, they need to orient
+ // their SubDs before asking "side" questions.
+ // Some math nerd yokel is sure to stuff the boundary of
+ // a mobius strip boundary into this function and then complain.
+ // Too bad for them. [Dale Lear 2021]
+ if (eptr.HasInteriorEdgeTopology(true))
+ {
+ for (f = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary), sit_dex = 1; nullptr != f && sit_dex < sit_max; f = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary), ++sit_dex)
+ {
+ if (f == f0)
+ break;
+ eptr = sit.CurrentEdgePtr(1);
+ side_components.Append(ON_SubDComponentPtr::Create(f));
+ if (f == f1)
+ break;
+ side_components.Append(ON_SubDComponentPtr::Create(eptr));
+ if (false == eptr.HasInteriorEdgeTopology(true))
+ break;
+ }
+ }
+ break;
+ }
+
+ if (f == f1)
+ continue;
+
+ for (;;)
+ {
+ if (nullptr == f1)
+ break;
+ if (nullptr == v || v != sit.Initialize(f1, 1-sitdir, v))
+ break;
+ const ON_SubDEdgePtr eptr1 = sit.CurrentEdgePtr(1);
+ const unsigned count0 = side_components.UnsignedCount();
+ if (eptr1.HasInteriorEdgeTopology(true))
+ {
+ const ON_SubDFace* f2 = nullptr;
+ for (f = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary), sit_dex = 1; nullptr != f && sit_dex < sit_max; f = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary), ++sit_dex)
+ {
+ if (f == f0 || f == f1)
+ {
+ f = nullptr;
+ break;
+ }
+ f2 = f;
+ if (false == sit.CurrentEdgePtr(1).HasInteriorEdgeTopology(true))
+ break;
+ }
+ if (nullptr == f2)
+ break;
+ if (v != sit.Initialize(f2, sitdir, v))
+ break;
+ for (f = sit.CurrentFace(), sit_dex = 1; nullptr != f && sit_dex < sit_max; f = sit.NextFace(ON_SubDSectorIterator::StopAt::Boundary), ++sit_dex)
+ {
+ ON_SubDEdgePtr eptr = sit.CurrentEdgePtr(0);
+ if (f == f0 || eptr.IsNull())
+ {
+ f = nullptr;
+ break;
+ }
+ side_components.Append(ON_SubDComponentPtr::Create(eptr));
+ side_components.Append(ON_SubDComponentPtr::Create(f));
+ if (f == f1)
+ break;
+ eptr = sit.CurrentEdgePtr(1);
+ if (false == eptr.HasInteriorEdgeTopology(true))
+ {
+ f = nullptr;
+ break;
+ }
+ }
+ if (f != f1)
+ {
+ side_components.SetCount(count0);
+ }
+ }
+ if ( count0 == side_components.UnsignedCount() && eptr1.IsNotNull() )
+ {
+ side_components.Append(ON_SubDComponentPtr::Create(eptr1));
+ side_components.Append(ON_SubDComponentPtr::Create(f1));
+ }
+ break;
+ }
+ }
+
+ if (bClosedLoop)
+ {
+ const unsigned side_components_count = side_components.UnsignedCount();
+ if (side_components_count >= 6
+ && first_side_components_edge.IsEdge()
+ && side_components[0].IsFace()
+ && side_components[0].Face() == side_components[side_components_count-1].Face()
+ )
+ {
+ // rearrange side_components[] so it begins with first_side_components_eptr.
+ // This elimnates the complexity of having to begin/end a SubD fillet
+ // at a ... f,v,f ... location.
+ const ON_SubDFace* f = side_components[0].Face();
+ if (nullptr == f)
+ {
+ ON_SUBD_ERROR("f should be non-null.");
+ }
+ else
+ {
+ // set j0 = current side_components[] index of the first edge
+ unsigned j0 = side_components_count;
+ for (unsigned j = 0; j < side_components_count; ++j)
+ {
+ if (first_side_components_edge.m_ptr == side_components[j].m_ptr)
+ {
+ j0 = j;
+ break;
+ }
+ }
+ if (j0 > 0 && j0 < side_components_count)
+ {
+ ON_SimpleArray tail;
+ tail.Append((int)(j0-1), side_components.Array() + 1);
+ for (unsigned k = 0; k + j0 < side_components_count; ++k)
+ side_components[k] = side_components[j0 + k];
+ side_components.SetCount(side_components_count - j0);
+ side_components.Append(tail.Count(), tail.Array());
+ side_components.Append(first_side_components_edge);
+ }
+ }
+ }
+ }
+ else
+ {
+ // last edge at the chain's final vertex
+ chain_eptr1 = edge_chain[edge_count - 1];
+ f1 = chain_eptr1.RelativeFace(relative_side);
+ if (nullptr != f1)
+ {
+ v = chain_eptr1.RelativeVertex(1);
+ if (nullptr != v && v == sit.Initialize(f1, sitdir, v))
+ {
+ ON_SubDEdgePtr eptr = sit.CurrentEdgePtr(1);
+ if (eptr.IsNotNull())
+ {
+ unsigned n = side_components.UnsignedCount();
+ if (0 == n || f1 != side_components[n - 1].Face())
+ side_components.Append(ON_SubDComponentPtr::Create(f1));
+ side_components.Append(ON_SubDComponentPtr::Create(eptr));
+ }
+ }
+ }
+ }
+
+ return side_components.UnsignedCount() > 0;
+}
const ON_SimpleArray& ON_SubDEdgeChain::EdgeChain() const
{
@@ -19588,6 +20603,25 @@ const ON_SubDVertex* ON_SubDEdgeChain::Vertex(int vertex_index) const
return nullptr;
}
+const ON_3dPoint ON_SubDEdgeChain::FirstControlNetPoint() const
+{
+ const ON_SubDVertex* v = FirstVertex();
+ return (nullptr != v) ? v->ControlNetPoint() : ON_3dPoint::NanPoint;
+}
+
+const ON_3dPoint ON_SubDEdgeChain::LastControlNetPoint() const
+{
+ const ON_SubDVertex* v = LastVertex();
+ return (nullptr != v) ? v->ControlNetPoint() : ON_3dPoint::NanPoint;
+}
+
+const ON_3dPoint ON_SubDEdgeChain::ControlNetPoint(int vertex_index) const
+{
+ const ON_SubDVertex* v = Vertex(vertex_index);
+ return (nullptr != v) ? v->ControlNetPoint() : ON_3dPoint::NanPoint;
+}
+
+
unsigned int ON_SubDEdgeChain::AddOneNeighbor(
ON_ChainDirection direction,
@@ -19896,6 +20930,527 @@ bool ON_SubD::IsManifold() const
return IsManifold(bIsOriented, bHasBoundary);
}
+#ifdef ON_DEBUG
+static bool Internal_UnrollChain(const ON_SimpleArray& chain) {
+ const unsigned imax{chain.UnsignedCount()};
+ ON_SimpleArray reversed_dirs{(size_t)imax};
+ ON_SimpleArray edges{(size_t)imax};
+ ON_SimpleArray vertices{(size_t)2 * (size_t)imax};
+ ON_SimpleArray edge_ids{(size_t)imax};
+ ON_SimpleArray vertice_ids{(size_t)2 * (size_t)imax};
+ const unsigned int max_id = 0xFFFFFFFFU;
+ for (unsigned i = 0; i < imax; ++i) {
+ ON_SubDEdgePtr eptr = chain[i];
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr);
+ if (nullptr == e) {
+ reversed_dirs.Append(false);
+ edges.Append(nullptr);
+ vertices.Append(nullptr);
+ vertices.Append(nullptr);
+ edge_ids.Append(max_id);
+ vertice_ids.Append(max_id);
+ vertice_ids.Append(max_id);
+ continue;
+ }
+ edges.Append(e);
+ edge_ids.Append(e->m_id);
+ ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(eptr.m_ptr);
+ reversed_dirs.Append(edir == 0 ? false : true);
+ const ON_SubDVertex* v[2] = {e->m_vertex[edir], e->m_vertex[1 - edir]};
+ vertices.Append(v[0]);
+ vertices.Append(v[1]);
+ vertice_ids.Append(v[0] == nullptr ? max_id : v[0]->m_id);
+ vertice_ids.Append(v[1] == nullptr ? max_id : v[1]->m_id);
+ }
+ return true;
+}
+
+static bool Internal_VerifyEdgeChain(
+ const ON_SimpleArray& chain, ON_2udex& chaindex,
+ unsigned* edge_count = nullptr) {
+ bool valid_chain{true};
+ unsigned valid_edge_count{0};
+ const unsigned jmax{chain.UnsignedCount()};
+ ON_SubDEdgePtr eptr = chain[chaindex.i];
+ const ON_SubDEdge* e = ON_SUBD_EDGE_POINTER(eptr.m_ptr);
+ if (nullptr == e) {
+ if (edge_count != nullptr) *edge_count = 0;
+ return false;
+ }
+ ON__UINT_PTR edir = ON_SUBD_EDGE_DIRECTION(eptr.m_ptr);
+ const ON_SubDVertex* v[2] = {e->m_vertex[edir], e->m_vertex[1 - edir]};
+ if (nullptr == v[0] || nullptr == v[1] || v[0] == v[1]) {
+ if (edge_count != nullptr) *edge_count = 0;
+ return false;
+ }
+ ++valid_edge_count;
+ for (chaindex.j = chaindex.i + 1; chaindex.j < jmax; ++chaindex.j) {
+ valid_chain = false;
+ eptr = chain[chaindex.j];
+ e = ON_SUBD_EDGE_POINTER(eptr.m_ptr);
+ if (nullptr == e) {
+ // End of chain marker
+ valid_chain = true;
+ if (chaindex.j < jmax) ++chaindex.j;
+ break;
+ }
+ if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1] ||
+ e->m_vertex[0] == e->m_vertex[1]) {
+ break;
+ }
+ edir = ON_SUBD_EDGE_DIRECTION(eptr.m_ptr);
+ if (v[1] != e->m_vertex[edir]) {
+ if (edge_count == nullptr) valid_chain = true;
+ break;
+ }
+ v[1] = e->m_vertex[1 - edir];
+ ++valid_edge_count;
+ valid_chain = true;
+ }
+ if (edge_count != nullptr && *edge_count != valid_edge_count) {
+ *edge_count = valid_edge_count;
+ return false;
+ }
+ return valid_edge_count > 0 && valid_chain;
+}
+
+static bool Internal_CountAndVerifyEdgeChains(
+ const ON_SimpleArray& sorted_edges,
+ unsigned* chain_count = nullptr) {
+ if (chain_count != nullptr && chain_count == 0) return false;
+ unsigned valid_chain_count{0};
+ bool valid_chains{false};
+ const unsigned edge_count{sorted_edges.UnsignedCount()};
+ ON_2udex chaindex{0, edge_count};
+ for (chaindex = {0, edge_count}; chaindex.i < edge_count;
+ chaindex.i = (chaindex.j > chaindex.i) ? chaindex.j : (chaindex.i + 1)) {
+ valid_chains = false;
+ if (!Internal_VerifyEdgeChain(sorted_edges, chaindex)) break;
+ ++valid_chain_count;
+ valid_chains = true;
+ }
+ if (chain_count != nullptr &&
+ (*chain_count != valid_chain_count || chaindex.j != edge_count)) {
+ *chain_count = valid_chain_count;
+ return false;
+ }
+ return valid_chain_count > 0 && valid_chains;
+}
+#endif
+
+//////////////////////////
+//
+// Better edge sorter
+//
+
+
+static unsigned Internal_MuchImprovedSortEdgesIntoChains(
+ const ON__UINT_PTR* unsorted_edges,
+ size_t unsorted_edges_count,
+ ON_SimpleArray& sorted_edges
+)
+{
+ // NOTE: sorted_edges and unsorted_edges should not point to the same array by this point
+ sorted_edges.SetCount(0);
+ sorted_edges.Reserve(unsorted_edges_count + 128U);
+
+ if (unsorted_edges_count < 1 || nullptr == unsorted_edges)
+ return 0;
+
+ const unsigned unsorted_count = (unsigned)unsorted_edges_count;
+ unsigned chain_count = 0;
+
+ // Set MarkBits = 0 for every vertex and for every edge attached to a vertex in the edge chain.
+ for (unsigned i = 0; i < unsorted_count; ++i)
+ {
+ const ON_SubDEdge* e = (const ON_SubDEdge*)(ON_SUBD_COMPONENT_POINTER_MASK & unsorted_edges[i]);
+ if (nullptr == e)
+ continue;
+ e->ClearMarkBits();
+ if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1])
+ continue;
+ if (e->m_vertex[0] == e->m_vertex[1])
+ continue;
+ for (unsigned evi = 0; evi < 2; ++evi)
+ {
+ const ON_SubDVertex* v = e->m_vertex[evi];
+ v->ClearMarkBits();
+ for (unsigned short vei = 0; vei < v->m_edge_count; ++vei)
+ {
+ const ON_SubDEdge* ve = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr);
+ if (nullptr != ve)
+ ve->ClearMarkBits();
+ }
+ }
+ }
+
+ // Set e->MarkBits = 1 for every valid edge in the edge chain.
+ for (unsigned i = 0; i < unsorted_count; ++i)
+ {
+ const ON_SubDEdge* e = (const ON_SubDEdge*)(ON_SUBD_COMPONENT_POINTER_MASK & unsorted_edges[i]);
+ if (nullptr == e)
+ continue;
+ if (nullptr == e->m_vertex[0] || nullptr == e->m_vertex[1])
+ continue;
+ if (e->m_vertex[0] == e->m_vertex[1])
+ continue;
+ e->SetMarkBits(1);
+ }
+
+ // Set v->MarkBits() = number of unsorted_edges[] that have v as a vertex with 3 meaning 3 or more.
+ for (unsigned i = 0; i < unsorted_count; ++i)
+ {
+ const ON_SubDEdge* e = (const ON_SubDEdge*)(ON_SUBD_COMPONENT_POINTER_MASK & unsorted_edges[i]);
+ if (nullptr == e)
+ continue;
+ if (0 == e->MarkBits())
+ continue;
+ for (unsigned evi = 0; evi < 2; ++evi)
+ {
+ const ON_SubDVertex* v = e->m_vertex[evi];
+ const ON__UINT8 ecount = v->MarkBits();
+ if (ecount < 3)
+ v->SetMarkBits(ecount + 1);
+ }
+ }
+
+ // Go through the unsorted edges and use the MarkBits values to quickly build the edge chains.
+ ON_SimpleArray chain((int)unsorted_edges_count);
+
+ for (unsigned i = 0; i < unsorted_edges_count; ++i)
+ {
+ const ON_SubDEdge* seed_edge = (const ON_SubDEdge*)(ON_SUBD_COMPONENT_POINTER_MASK & unsorted_edges[i]);
+ if (nullptr == seed_edge || 1 != seed_edge->MarkBits())
+ continue; // seed_edge is invalid or already assigned to an edge chain
+
+ // this edge pointer direcion will be reversed when the for loop evi = 1.
+ chain.SetCount(0);
+ chain.Append(ON_SubDEdgePtr::Create(seed_edge, 1)); // dir=1 is correct. The direcion will be reversed in the for loop below when evi = 1.
+ seed_edge->ClearMarkBits(); // seed_edge is now in a chain
+
+ for (unsigned evi = 0; evi < 2; ++evi)
+ {
+ if (1 == evi)
+ {
+ ON_SubDEdgeChain::ReverseEdgeChain(chain);
+ if (chain[0].RelativeVertex(0) == chain[chain.UnsignedCount() - 1].RelativeVertex(1))
+ break; // we found a closed loop
+ }
+
+ const ON_SubDVertex* v = chain[chain.UnsignedCount() - 1].RelativeVertex(1);
+ if (nullptr == v || 2 != v->MarkBits())
+ continue; // edge chain cannot continue through v
+
+ for (const ON_SubDVertex* v1 = nullptr; nullptr != v && 2 == v->MarkBits(); v = v1)
+ {
+ v->ClearMarkBits(); // clearing v->MarkBits indicates this v has been used.
+ v1 = nullptr;
+ for (unsigned short vei = 0; vei < v->m_edge_count; ++vei)
+ {
+ const ON_SubDEdge* ve = ON_SUBD_EDGE_POINTER(v->m_edges[vei].m_ptr);
+ if (nullptr == ve)
+ continue;
+ if (1 != ve->MarkBits())
+ continue; // this ve was not in unsorted_edges[] or has already been assigned to a chain.
+ if (v == ve->m_vertex[0])
+ {
+ ve->SetMarkBits(0); // MarkBits() = 0 indicates ve is now in a chain
+ chain.Append(ON_SubDEdgePtr::Create(ve, 0));
+ v1 = ve->m_vertex[1];
+ }
+ else if (v == ve->m_vertex[1])
+ {
+ ve->SetMarkBits(0); // MarkBits() = 0 indicates ve is now in a chain
+ chain.Append(ON_SubDEdgePtr::Create(ve, 1));
+ v1 = ve->m_vertex[0];
+ }
+ else
+ {
+ ON_SUBD_ERROR("Corrupt edge/vertex topology.");
+ }
+ }
+ v = v1;
+ }
+ }
+
+#ifdef ON_DEBUG
+ unsigned temp_chain_edge_count{chain.UnsignedCount()};
+ ON_2udex temp_udex{0U, (unsigned)(chain.UnsignedCount() - 1)};
+ if (!Internal_VerifyEdgeChain(chain, temp_udex, &temp_chain_edge_count)) {
+ temp_chain_edge_count = 0;
+ }
+ Internal_UnrollChain(chain);
+#endif
+ sorted_edges.Append(chain.Count(), chain.Array());
+ sorted_edges.Append(ON_SubDEdgePtr::Null); // end of chain marker
+ ++chain_count;
+ }
+
+ // clear all the mark bits that may still be set when the input is corrupt.
+ for (size_t i = 0; i < unsorted_edges_count; ++i)
+ {
+ const ON_SubDEdge* e = (const ON_SubDEdge*)(ON_SUBD_COMPONENT_POINTER_MASK & unsorted_edges[i]);
+ if (nullptr == e)
+ continue;
+ e->ClearMarkBits();
+ if (nullptr != e->m_vertex[0])
+ e->m_vertex[0]->ClearMarkBits();
+ if (nullptr != e->m_vertex[1])
+ e->m_vertex[1]->ClearMarkBits();
+ }
+
+#ifdef ON_DEBUG
+ Internal_UnrollChain(sorted_edges);
+ if (!Internal_CountAndVerifyEdgeChains(sorted_edges, &chain_count)) {
+ return chain_count;
+ }
+#endif
+ return chain_count;
+}
+
+unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< ON_SubDEdgePtr >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+)
+{
+ unsigned int chain_count = 0;
+ if (unsorted_edges.Array() == sorted_edges.Array())
+ {
+ const ON_SimpleArray< ON_SubDEdgePtr > local_unsorted_edges(unsorted_edges);
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR * )local_unsorted_edges.Array(),
+ local_unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ else
+ {
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)unsorted_edges.Array(),
+ unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ return chain_count;
+}
+
+unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< const ON_SubDEdge* >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+)
+{
+ unsigned int chain_count = 0;
+ if ((const void*)unsorted_edges.Array() == (const void*)sorted_edges.Array())
+ {
+ const ON_SimpleArray< const ON_SubDEdge* > local_unsorted_edges(unsorted_edges);
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)local_unsorted_edges.Array(),
+ local_unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ else
+ {
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)unsorted_edges.Array(),
+ unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ return chain_count;
+}
+
+unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< ON_SubDComponentPtr >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+)
+{
+ unsigned int chain_count = 0;
+ if ((const void*)unsorted_edges.Array() == (const void*)sorted_edges.Array())
+ {
+ const ON_SimpleArray< ON_SubDComponentPtr > local_unsorted_edges(unsorted_edges);
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)local_unsorted_edges.Array(),
+ local_unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ else
+ {
+ chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)unsorted_edges.Array(),
+ unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+ }
+ return chain_count;
+}
+
+unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
+ const ON_SubD& subd,
+ const ON_SimpleArray< ON_COMPONENT_INDEX >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+)
+{
+ const unsigned unsorted_count = unsorted_edges.UnsignedCount();
+ ON_SimpleArray< const ON_SubDEdge* > local_unsorted_edges(unsorted_count);
+ for (unsigned i = 0; i < unsorted_count; ++i)
+ {
+ const ON_COMPONENT_INDEX ci = unsorted_edges[i];
+ if (ON_COMPONENT_INDEX::TYPE::subd_edge != ci.m_type)
+ continue;
+ if (0 == ci.m_index || -1 == ci.m_index)
+ continue;
+ const ON_SubDEdge* e = subd.EdgeFromId((unsigned)ci.m_index);
+ if (nullptr == e)
+ continue;
+ local_unsorted_edges.Append(e);
+ }
+
+ return Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)local_unsorted_edges.Array(),
+ unsorted_edges.UnsignedCount(),
+ sorted_edges
+ );
+}
+
+bool ON_SubDEdgeChain::IsSingleEdgeChain(
+ const ON_SimpleArray& edges,
+ bool& bIsClosed,
+ bool& bIsSorted
+)
+{
+ bIsSorted = false;
+ bIsClosed = false;
+
+ const unsigned count = edges.UnsignedCount();
+ if (count <= 1U)
+ {
+ const ON_SubDEdge* e = (1U == count) ? edges[0].Edge() : nullptr;
+ if (
+ nullptr != e
+ && nullptr != e->m_vertex[0]
+ && nullptr != e->m_vertex[1]
+ && e->m_vertex[0] != e->m_vertex[1]
+ )
+ {
+ // 1 valid edge
+ bIsSorted = true;
+ return true;
+ }
+ return false;
+ }
+
+ // save MarkBits() values on edges[] components so they can be restored after testing.
+ // Internal_MuchImprovedSortEdgesIntoChains() uses MarkBits().
+ union
+ {
+ ON__UINT32 u32;
+ ON__UINT8 u8[4];
+ } u;
+ ON_SimpleArray saved_markbits(count);
+ for (unsigned i = 0; i < count; ++i)
+ {
+ u.u32 = 0;
+ const ON_SubDEdge* e = edges[i].Edge();
+ if (nullptr != e)
+ {
+ u.u8[0] = e->MarkBits();
+ if (e->m_vertex[0])
+ u.u8[1] = e->m_vertex[0]->MarkBits();
+ if (e->m_vertex[1])
+ u.u8[2] = e->m_vertex[1]->MarkBits();
+ }
+ saved_markbits.Append(u.u32);
+ }
+
+ bool bIsSingleEdgeChain = false;
+ for (;;)
+ {
+ ON_SimpleArray sorted_edges;
+ const unsigned chain_count = Internal_MuchImprovedSortEdgesIntoChains(
+ (const ON__UINT_PTR*)edges.Array(),
+ count,
+ sorted_edges
+ );
+
+ if (1U != chain_count)
+ break; // edges[] is not a contiguouse set of edges or it self intersects.
+ if (count + 1U != sorted_edges.UnsignedCount())
+ break; // edges[] contained null edges
+
+ // determine edges[] sorts into a closed edge chain.
+ if (count >= 3 && sorted_edges[0].RelativeVertex(0) == sorted_edges[count - 1].RelativeVertex(1))
+ bIsClosed = true;
+
+ // Determine edges[] is was already sorted.
+ // Note that this test does not detect self interections and that's
+ // why ON_SubDEdgeChain::SortEdgesIntoEdgeChains() is called above.
+ const ON_SubDVertex* v0 = edges[0].RelativeVertex(0);
+ if (nullptr != v0)
+ {
+ const ON_SubDVertex* edges_ends[2] = { edges[0].RelativeVertex(0), edges[count - 1].RelativeVertex(1) };
+ const ON_SubDVertex* sorted_ends[2] = { sorted_edges[0].RelativeVertex(0), sorted_edges[count - 1].RelativeVertex(1) };
+ if (bIsClosed)
+ {
+ if (edges_ends[0] == edges_ends[1] && sorted_ends[0] == sorted_ends[1])
+ bIsSorted = true;
+ }
+ else
+ {
+ // sorted_eges[] may be reversed.
+ if (edges_ends[0] == sorted_ends[0] && edges_ends[1] == sorted_ends[1])
+ bIsSorted = true;
+ else if (edges_ends[0] == sorted_ends[1] && edges_ends[1] == sorted_ends[0])
+ bIsSorted = true;
+ }
+
+ for (unsigned i = 0; bIsSorted && i < count; ++i)
+ {
+ const ON_SubDVertex* ev[2] = { edges[i].RelativeVertex(0), edges[i].RelativeVertex(1) };
+ if (v0 != ev[0] || nullptr == ev[1] || ev[0] == ev[1])
+ {
+ bIsSorted = false;
+ break;
+ }
+ v0 = ev[1];
+ }
+ }
+
+
+ bIsSingleEdgeChain = true;
+ break;
+ }
+
+
+ // restore MarkBits() values on edges[] components.
+ for (unsigned i = 0; i < count; ++i)
+ {
+ u.u32 = saved_markbits[i];
+ const ON_SubDEdge* e = edges[i].Edge();
+ if (nullptr != e)
+ {
+ e->SetMarkBits(u.u8[0]);
+ if (e->m_vertex[0])
+ e->m_vertex[0]->SetMarkBits(u.u8[1]);
+ if (e->m_vertex[1])
+ e->m_vertex[1]->SetMarkBits(u.u8[2]);
+ }
+ }
+
+ return bIsSingleEdgeChain;
+}
+
+bool ON_SubDEdgeChain::IsSingleEdgeChain(
+ const ON_SimpleArray& edges
+)
+{
+ bool bIsClosed = false;
+ bool bIsSorted = false;
+ return ON_SubDEdgeChain::IsSingleEdgeChain(edges, bIsClosed, bIsSorted);
+}
+
+
class ON_SubDEdgePtrLink
{
public:
@@ -20319,11 +21874,12 @@ unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
chain.Append(epl.m_ep);
}
- const bool bClosedChain = (epl.m_index == epl1.m_index);
- const bool bReverseFinalChain
- = false == bClosedChain
- && unset_nbr1_index == epl.m_nbr_index
- && (0 == (epl.m_index % 2));
+ const bool bClosedChain =
+ chain.UnsignedCount() > 0 && (epl.m_index == epl1.m_index);
+ const bool bFirstEdgeIsReversed = !bClosedChain &&
+ unset_nbr1_index <= epl.m_nbr_index &&
+ (0 == (epl.m_index % 2));
+ bool bLastEdgeIsReversed{false};
if (false == bClosedChain)
{
chain.Reverse();
@@ -20365,8 +21921,15 @@ unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
chain.Append(epl.m_ep.Reversed());
}
- if (bReverseFinalChain)
- chain.Reverse();
+ // No need to update bClosedChain here: if it was closed, then the circle
+ // was found with the edges "before" edges[i/2]
+
+ // First and last edge are reversed: reverse chain direction to keep first
+ // edge direction the same as in unsorted_edges
+ bLastEdgeIsReversed = epl.m_index % 2 == 0;
+ if (bFirstEdgeIsReversed && bLastEdgeIsReversed) {
+ ReverseEdgeChain(chain);
+ }
}
const unsigned int chain_edge_count = chain.UnsignedCount();
@@ -20421,20 +21984,97 @@ unsigned int ON_SubDEdgeChain::SortEdgesIntoEdgeChains(
}
break;
}
- if (chain.UnsignedCount() >= minimum_chain_length)
- {
- ++chain_count;
- sorted_edges.Append(chain.Count(), chain.Array());
+#ifdef ON_DEBUG
+ unsigned temp_chain_edge_count{chain_edge_count};
+ ON_2udex temp_udex{0U, (unsigned)(chain_edge_count - 1)};
+ if (!Internal_VerifyEdgeChain(chain, temp_udex, &temp_chain_edge_count)) {
+ temp_chain_edge_count = 0;
+ }
+ Internal_UnrollChain(chain);
+#endif
+ if (chain.UnsignedCount() >= minimum_chain_length) {
+ const ON_SubDVertex* c0{chain[0].RelativeVertex(0)};
+ const ON_SubDVertex* s1{sorted_edges.Last() == nullptr
+ ? nullptr
+ : sorted_edges.Last()->RelativeVertex(1)};
+ if (c0 != nullptr && c0 == s1) {
+ // If the first vertex of the chain is a corner, or a vertex with more
+ // than 2 edges referencing it, we might have artificially split the
+ // chain at that vertex and the last vertex of the previous chain is
+ // the same as ours. Try a few tricks to put a gap between these two
+ // vertices.
+ const ON_SubDVertex* c1{
+ chain[chain_edge_count - 1].RelativeVertex(1)};
+ const ON_SubDVertex* s0{
+ sorted_edges.First() == nullptr
+ ? nullptr
+ : sorted_edges.First()->RelativeVertex(0)};
+ // By order of importance, try to:
+ // - Keep the orientation of the first edge the same as in unsorted_edges
+ // - Not use ReverseEdgeChain()
+ // - Append rather than Prepend to sorted_edges
+ if (c1 != s0) {
+ ++chain_count;
+ sorted_edges.Prepend(chain.Count(), chain.Array());
+ } else if (c1 != s1 && !bClosedChain &&
+ ((bFirstEdgeIsReversed && !bLastEdgeIsReversed) ||
+ (!bFirstEdgeIsReversed && bLastEdgeIsReversed))) {
+ ReverseEdgeChain(chain);
+ ++chain_count;
+ sorted_edges.Append(chain.Count(), chain.Array());
+ } else if (c0 != s0 && !bClosedChain &&
+ ((bFirstEdgeIsReversed && !bLastEdgeIsReversed) ||
+ (!bFirstEdgeIsReversed && bLastEdgeIsReversed))) {
+ ReverseEdgeChain(chain);
+ ++chain_count;
+ sorted_edges.Prepend(chain.Count(), chain.Array());
+ } else if (c1 != s1 && !bClosedChain) {
+ ReverseEdgeChain(chain);
+ ++chain_count;
+ sorted_edges.Append(chain.Count(), chain.Array());
+ } else if (c0 != s0 && !bClosedChain) {
+ ReverseEdgeChain(chain);
+ ++chain_count;
+ sorted_edges.Prepend(chain.Count(), chain.Array());
+ } else {
+ // We tried, but nothing works easily enough: append the chain as
+ // usual, this means the two chains are merged and chain_count does
+ // not increase.
+ sorted_edges.Append(chain.Count(), chain.Array());
+ }
+ } else {
+ ++chain_count;
+ sorted_edges.Append(chain.Count(), chain.Array());
+ }
}
}
if ( link_count == 2*sorted_edges.UnsignedCount() )
break; // we've used all the links - no need to "skip over the rest".
}
-
+#ifdef ON_DEBUG
+ Internal_UnrollChain(sorted_edges);
+ if (!Internal_CountAndVerifyEdgeChains(sorted_edges, &chain_count)) {
+ return chain_count;
+ }
+#endif
return chain_count;
}
+const ON_SHA1_Hash ON_SubDEdgeChain::Hash() const
+{
+ ON_SHA1 sha1;
+ const unsigned edge_count = this->EdgeCount();
+ for (unsigned i = 0; i < edge_count; ++i)
+ {
+ const ON_SubDEdgePtr eptr = EdgePtr(i);
+ sha1.AccumulateInteger32(eptr.EdgeId());
+ sha1.AccumulateInteger32(eptr.RelativeVertexId(0));
+ sha1.AccumulateInteger32(eptr.RelativeVertexId(1));
+ }
+ return sha1.Hash();
+}
+
unsigned int ON_SubDEdgeChain::OrientEdgesIntoEdgeChains(
const ON_SubD& subd,
const ON_SimpleArray< ON_COMPONENT_INDEX >& edges,
@@ -21744,6 +23384,207 @@ bool ON_Symmetry::SetSymmetricObject(const ON_SubDimple* subdimple) const
return rc;
}
+double ON_SubDExpandEdgesParameters::ConstantOffset() const
+{
+ return this->m_constant_offset;
+}
+
+void ON_SubDExpandEdgesParameters::SetConstantOffset(double offset)
+{
+ offset = ON_SubDExpandEdgesParameters::CleanupOffset(offset);
+ if (ON_SubDExpandEdgesParameters::IsValidConstantOffset(offset))
+ this->m_constant_offset = offset;
+}
+
+
+bool ON_SubDExpandEdgesParameters::IsValidForHalfOffset(
+ const ON_SimpleArray& edge_chain
+)
+{
+ const unsigned count = edge_chain.UnsignedCount();
+ for (unsigned i = 0; i < count; ++i)
+ {
+ if (false == edge_chain[i].HasInteriorEdgeTopology(true))
+ return false;
+ }
+ return ON_SubDEdgeChain::IsSingleEdgeChain(edge_chain);
+}
+
+
+bool ON_SubDExpandEdgesParameters::IsValidForVariableOffset(
+ const ON_SimpleArray& edge_chain
+)
+{
+ bool bIsClosed = false;
+ bool bIsSorted = false;
+ const bool IsSingleEdgeChain = ON_SubDEdgeChain::IsSingleEdgeChain(edge_chain, bIsClosed, bIsSorted);
+ return IsSingleEdgeChain && false == bIsClosed;
+}
+
+const ON_Interval ON_SubDExpandEdgesParameters::VariableOffset() const
+{
+ return this->m_variable_offsets;
+}
+
+void ON_SubDExpandEdgesParameters::SetVariableOffset(ON_Interval variable_offsets)
+{
+ variable_offsets[0] = ON_SubDExpandEdgesParameters::CleanupOffset(variable_offsets[0]);
+ variable_offsets[1] = ON_SubDExpandEdgesParameters::CleanupOffset(variable_offsets[1]);
+ if (ON_SubDExpandEdgesParameters::IsValidVariableOffset(variable_offsets))
+ {
+ this->m_variable_offsets = variable_offsets;
+ }
+ else
+ {
+ // invalid input
+ ClearVariableOffset();
+ if (ON_SubDExpandEdgesParameters::IsValidConstantOffset(variable_offsets[0])
+ && fabs(variable_offsets[0] - variable_offsets[1]) <= ON_SubDExpandEdgesParameters::OffsetTolerance
+ )
+ {
+ SetConstantOffset(variable_offsets[0]);
+ }
+ }
+}
+
+void ON_SubDExpandEdgesParameters::ClearVariableOffset()
+{
+ this->m_variable_offsets = ON_Interval::Nan;
+}
+
+bool ON_SubDExpandEdgesParameters::IsVariableOffset() const
+{
+ return ON_SubDExpandEdgesParameters::IsValidVariableOffset(this->m_variable_offsets);
+}
+
+bool ON_SubDExpandEdgesParameters::IsValidConstantOffset(
+ double constant_offset_candidate
+)
+{
+ return constant_offset_candidate >= ON_SubDExpandEdgesParameters::MinimumOffset && constant_offset_candidate <= ON_SubDExpandEdgesParameters::MaximumOffset;
+}
+
+bool ON_SubDExpandEdgesParameters::IsValidVariableOffset(
+ ON_Interval variable_offset_candidate
+)
+{
+ for (int i = 0; i < 2; i++)
+ {
+ const double x[2] = { variable_offset_candidate[i], variable_offset_candidate[1 - i] };
+ if (false == ON_SubDExpandEdgesParameters::IsValidConstantOffset(x[0]))
+ {
+ if (0.0 == x[0])
+ return (x[1] >= ON_SubDExpandEdgesParameters::MinimumOffset && x[1] <= 1.0);
+ if (1.0 == x[0])
+ return (x[1] >= 0.0 && x[1] <= ON_SubDExpandEdgesParameters::MaximumOffset);
+ return false;
+ }
+ }
+ return fabs(variable_offset_candidate[0] - variable_offset_candidate[1]) > ON_SubDExpandEdgesParameters::OffsetTolerance;
+}
+
+
+ON_SubDExpandEdgesParameters::Style ON_SubDExpandEdgesParameters::FaceStyle() const
+{
+ return this->m_face_style;
+}
+
+void ON_SubDExpandEdgesParameters::SetFaceStyle(ON_SubDExpandEdgesParameters::Style face_style)
+{
+ this->m_face_style = face_style;
+}
+
+bool ON_SubDExpandEdgesParameters::IsHalfFaceStyle() const
+{
+ return
+ ON_SubDExpandEdgesParameters::Style::HalfLeft == this->m_face_style
+ || ON_SubDExpandEdgesParameters::Style::HalfRight == this->m_face_style
+ ;
+}
+
+const ON_Color ON_SubDExpandEdgesParameters::FaceColor() const
+{
+ return this->m_face_color;
+}
+
+void ON_SubDExpandEdgesParameters::SetFaceColor(ON_Color face_color)
+{
+ this->m_face_color = face_color;
+}
+
+const ON_ComponentStatus ON_SubDExpandEdgesParameters::FaceStatus() const
+{
+ return this->m_face_status;
+}
+
+void ON_SubDExpandEdgesParameters::SetFaceStatus(ON_ComponentStatus face_status)
+{
+ this->m_face_status = ON_ComponentStatus::NoneSet;
+ this->m_face_status.SetRuntimeMark(face_status.SetRuntimeMark());
+ this->m_face_status.SetMarkBits(face_status.MarkBits());
+ if (face_status.IsSelectedPersistent())
+ this->m_face_status.SetSelectedState(ON_ComponentState::SelectedPersistent, face_status.IsHighlighted());
+ else if (face_status.IsSelected())
+ this->m_face_status.SetSelectedState(ON_ComponentState::Selected, face_status.IsHighlighted());
+ else if (face_status.IsHighlighted())
+ this->m_face_status.SetHighlightedState(true);
+}
+
+const ON_SHA1_Hash ON_SubDExpandEdgesParameters::Hash() const
+{
+ ON_SHA1 sha1;
+ sha1.AccumulateDouble(ConstantOffset());
+ sha1.AccumulateDouble(VariableOffset().m_t[0]);
+ sha1.AccumulateDouble(VariableOffset().m_t[1]);
+ sha1.AccumulateInteger32((unsigned int)FaceColor());
+ sha1.AccumulateBytes(&m_face_status, sizeof(m_face_status));
+ sha1.AccumulateInteger32((unsigned int)FaceStyle());
+ return sha1.Hash();
+}
+
+bool operator==(const ON_SubDExpandEdgesParameters& lhs, const ON_SubDExpandEdgesParameters& rhs)
+{
+ return lhs.Hash() == rhs.Hash();
+}
+
+bool operator!=(const ON_SubDExpandEdgesParameters& lhs, const ON_SubDExpandEdgesParameters& rhs)
+{
+ return lhs.Hash() != rhs.Hash();
+}
+
+
+double ON_SubDExpandEdgesParameters::CleanupOffset(double x)
+{
+
+ const double r[] = {
+ 0.0,
+ 1.0,
+ ON_SubDExpandEdgesParameters::SmallOffset,
+ ON_SubDExpandEdgesParameters::MediumOffset,
+ ON_SubDExpandEdgesParameters::LargeOffset,
+ ON_SubDExpandEdgesParameters::MinimumOffset,
+ ON_SubDExpandEdgesParameters::MaximumOffset
+ };
+
+ const size_t c = sizeof(r) / sizeof(r[0]);
+ for (size_t i = 0; i < c; ++i)
+ {
+ if (fabs(x - r[i]) <= ON_SubDExpandEdgesParameters::OffsetTolerance)
+ return r[i];
+ }
+
+ if (x > 0.0 && x < ON_SubDExpandEdgesParameters::MinimumOffset)
+ x = ON_SubDExpandEdgesParameters::MinimumOffset;
+ else if (x < 1.0 && x > ON_SubDExpandEdgesParameters::MaximumOffset)
+ x = ON_SubDExpandEdgesParameters::MaximumOffset;
+
+ if (x >= 0.0 && x <= 1.0)
+ return x;
+
+ return ON_DBL_QNAN;
+}
+
+
#if defined(ON_SUBD_CENSUS)
//////////////////////////////////////////////////////////////////////////
diff --git a/opennurbs_subd.h b/opennurbs_subd.h
index 0571afef..3d925d7d 100644
--- a/opennurbs_subd.h
+++ b/opennurbs_subd.h
@@ -768,6 +768,20 @@ public:
) const;
ON__UINT8 ClearMarkBits() const;
+
+ void ClearSavedSubdivisionPoints() const;
+
+ /*
+ Description:
+ Clears saved subdivision and limit surface information for this component.
+ Parameters:
+ bClearNeighborhood - [in]
+ If true, all components attached to this component are also cleared.
+ */
+ void ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+ ) const;
+
};
@@ -863,6 +877,15 @@ public:
*/
bool EdgeIsDartCrease() const;
+ /*
+ Returns:
+ If Edge() is not nullptr, Edge()->HasInteriorEdgeTopology(bRequireOppositeFaceDirections) is returned.
+ Otherwise, false is returned.
+ */
+ bool HasInteriorEdgeTopology(
+ bool bRequireOppositeFaceDirections
+ ) const;
+
/*
Returns:
0: this ON_SubDEdgePtr is oriented from Edge()->Vertex(0) to Edge()->Vertex(1).
@@ -883,6 +906,19 @@ public:
int relative_vertex_index
) const;
+ /*
+ Parameters:
+ relative_vertex_index - [in]
+ 0: return Edge()->Vertex(EdgeDirection())
+ 1: return Edge()->Vertex(1-EdgeDirection())
+ Returns:
+ The requested id of the vertex with EdgeDirection() taken into account.
+ 0 if relative_vertex_index, Edge() is nullptr, or Edge()->Vertex() is nullptr.
+ */
+ unsigned RelativeVertexId(
+ int relative_vertex_index
+ ) const;
+
/*
Parameters:
relative_vertex_index - [in]
@@ -927,6 +963,46 @@ public:
int relative_vertex_index
) const;
+ /*
+ Description:
+ Get the face on the left or right side of an oriented manifold or boundary edge.
+ A face is on the "left side" if this ON_SubDEdgePtr is oriented so it
+ points in the same direction as the face's oriented boundary.
+ A face is on the "right side" if this ON_SubDEdgePtr is oriented so it
+ points in the opposite direction as the face's oriented boundary.
+ If an edge is nonmanifold (3 or more faces), then nullptr is always returned.
+ If an edge has two faces that do not attach to this edge with opposite orientations
+ (nonoriented manifold edge), then nullptr is returned.
+ Parameters:
+ relative_face_index - [in]
+ 0: return face on the left side of the edge with respect to EdgeOrientation().
+ 1: return face on the right side of the edge with respect to EdgeOrientation().
+ Returns:
+ The requested face.
+ */
+ const class ON_SubDFace* RelativeFace(
+ int relative_face_index
+ ) const;
+
+ /*
+ Returns:
+ this->RelativeFace(relative_face_index)->Mark();
+ */
+ bool RelativeFaceMark(
+ int relative_face_index,
+ bool missing_face_return_value
+ ) const;
+
+
+ /*
+ Returns:
+ this->RelativeFace(relative_face_index)->MarkBits();
+ */
+ ON__UINT8 RelativeFaceMarkBits(
+ int relative_face_index,
+ ON__UINT8 missing_face_return_value
+ ) const;
+
/*
Returns:
The vector from RelativeVertex(0)->ControlNetPoint() to RelativeVertex(1)->ControlNetPoint(),
@@ -946,6 +1022,20 @@ public:
*/
const ON_SubDEdgePtr Reversed() const;
+ void ClearSavedSubdivisionPoints() const;
+
+ /*
+ Description:
+ Clears saved subdivision and limit surface information for this component.
+ Parameters:
+ bClearNeighborhood - [in]
+ If true, all components attached to this component are also cleared.
+ */
+ void ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+ ) const;
+
+
/*
Parameters:
@@ -1200,6 +1290,20 @@ public:
) const;
ON__UINT8 ClearMarkBits() const;
+
+ void ClearSavedSubdivisionPoints() const;
+
+ /*
+ Description:
+ Clears saved subdivision and limit surface information for this component.
+ Parameters:
+ bClearNeighborhood - [in]
+ If true, all components attached to this component are also cleared.
+ */
+ void ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+ ) const;
+
};
#if defined(ON_DLL_TEMPLATE)
@@ -1328,6 +1432,23 @@ public:
const ON_SubDEdgePtr EdgePtr() const;
const ON_SubDFacePtr FacePtr() const;
+ const bool IsVertex() const;
+ const bool IsEdge() const;
+ const bool IsFace() const;
+
+ void ClearSavedSubdivisionPoints() const;
+
+ /*
+ Description:
+ Clears saved subdivision and limit surface information for this component.
+ Parameters:
+ bClearNeighborhood - [in]
+ If true, all components attached to this component are also cleared.
+ */
+ void ClearSavedSubdivisionPoints(
+ bool bClearNeighborhood
+ ) const;
+
unsigned int ComponentId() const;
const ON_COMPONENT_INDEX ComponentIndex() const;
@@ -1575,6 +1696,239 @@ public:
ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
#endif
+//////////////////////////////////////////////////////////////////////////
+//
+// ON_SubDComponentPtrAndNumber
+//
+
+class ON_CLASS ON_SubDComponentAndNumber
+{
+public:
+ ON_SubDComponentAndNumber() = default;
+ ~ON_SubDComponentAndNumber() = default;
+ ON_SubDComponentAndNumber(const ON_SubDComponentAndNumber&) = default;
+ ON_SubDComponentAndNumber& operator=(const ON_SubDComponentAndNumber&) = default;
+
+public:
+ static const ON_SubDComponentAndNumber NullAndNan;
+ static const ON_SubDComponentAndNumber NullAndZero;
+ static const ON_SubDComponentAndNumber Create(
+ ON_SubDComponentPtr cptr,
+ double x
+ );
+
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDVertex* v,
+ double x
+ );
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDEdge* e,
+ double x
+ );
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDFace* f,
+ double x
+ );
+
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDVertexPtr vptr,
+ double x
+ );
+
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDEdgePtr eptr,
+ double x
+ );
+
+ static const ON_SubDComponentAndNumber Create(
+ const ON_SubDFacePtr fptr,
+ double x
+ );
+
+public:
+
+ /*
+ Description:
+ Compare Component() using ON_SubDComponentPtr::CompareComponent().
+ */
+ static int CompareComponent(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+ );
+
+ /*
+ Description:
+ Compare Component() using ON_SubDComponentPtr::CompareComponentAndDirection().
+ */
+ static int CompareComponentAndDirection(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+ );
+
+
+ /*
+ Description:
+ Compare Number() nans are treated as equal and sort last.
+ */
+ static int CompareNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+ );
+
+ /*
+ Description:
+ Dictionary compare Component() and Number() in that order.
+ */
+ static int CompareComponentAndNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+ );
+
+ /*
+ Description:
+ Dictionary compare using ON_SubDComponentAndNumber::CompareComponentAndDirection() and ON_SubDComponentAndNumber::Number() in that order.
+ */
+ static int CompareComponentAndDirectionAndNumber(
+ const ON_SubDComponentAndNumber* a,
+ const ON_SubDComponentAndNumber* b
+ );
+
+
+public:
+ const ON_SubDComponentPtr Component() const;
+ void SetComponent(ON_SubDComponentPtr cptr);
+
+ double Number() const;
+ void SetNumber(double x);
+
+public:
+ ON_SubDComponentPtr m_cptr = ON_SubDComponentPtr::Null;
+ double m_x = ON_DBL_QNAN;
+};
+
+#if defined(ON_DLL_TEMPLATE)
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//
+// ON_SubDComponentAndPoint
+//
+
+class ON_CLASS ON_SubDComponentAndPoint
+{
+public:
+ ON_SubDComponentAndPoint() = default;
+ ~ON_SubDComponentAndPoint() = default;
+ ON_SubDComponentAndPoint(const ON_SubDComponentAndPoint&) = default;
+ ON_SubDComponentAndPoint& operator=(const ON_SubDComponentAndPoint&) = default;
+
+public:
+ static const ON_SubDComponentAndPoint NullAndNan;
+ static const ON_SubDComponentAndPoint NullAndOrigin;
+ static const ON_SubDComponentAndPoint Create(
+ ON_SubDComponentPtr cptr,
+ ON_3dPoint P
+ );
+
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDVertex* v,
+ ON_3dPoint P
+ );
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDEdge* e,
+ ON_3dPoint P
+ );
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDFace* f,
+ ON_3dPoint P
+ );
+
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDVertexPtr vptr,
+ ON_3dPoint P
+ );
+
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDEdgePtr eptr,
+ ON_3dPoint P
+ );
+
+ static const ON_SubDComponentAndPoint Create(
+ const ON_SubDFacePtr fptr,
+ ON_3dPoint P
+ );
+
+public:
+
+ /*
+ Description:
+ Compare Component() using ON_SubDComponentPtr::CompareComponent().
+ */
+ static int CompareComponent(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+ );
+
+ /*
+ Description:
+ Compare Component() using ON_SubDComponentPtr::CompareComponentAndDirection().
+ */
+ static int CompareComponentAndDirection(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+ );
+
+
+ /*
+ Description:
+ Compare Point() uses ON_3dPoint::Compare() to compare lhs and rhs Point() values.
+ */
+ static int ComparePoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+ );
+
+ /*
+ Description:
+ Dictionary compare Component() and Point() in that order.
+ */
+ static int CompareComponentAndPoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+ );
+
+ /*
+ Description:
+ Dictionary compare using ON_SubDComponentAndPoint::CompareComponentAndDirection() and ON_SubDComponentAndPoint::Point() in that order.
+ */
+ static int CompareComponentAndDirectionAndPoint(
+ const ON_SubDComponentAndPoint* lhs,
+ const ON_SubDComponentAndPoint* rhs
+ );
+
+
+public:
+ const ON_SubDComponentPtr Component() const;
+ void SetComponent(ON_SubDComponentPtr cptr);
+
+ const ON_3dPoint Point() const;
+ void SetPoint(ON_3dPoint P);
+
+public:
+ ON_SubDComponentPtr m_cptr = ON_SubDComponentPtr::Null;
+ ON_3dPoint m_P = ON_3dPoint::NanPoint;
+};
+
+#if defined(ON_DLL_TEMPLATE)
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//
+// ON_SubDComponentTest
+//
+
/*
Description:
ON_SubDComponentTest is used in contexts where custom testing or filtering of
@@ -2928,6 +3282,221 @@ const ON_wString ON_SubDEndCapStyleToString(
ON_SubDEndCapStyle subd_cap_style
);
+//////////////////////////////////////////////////////////////////////////
+//
+// ON_SubD
+//
+//// ON_WIP_SDK
+//// This class is in the WIP SDK but the [[deprecated]] tag is
+//// failing to compile in tl_precompiledheader.cpp
+class ON_CLASS ON_SubDExpandEdgesParameters
+{
+public:
+ ON_SubDExpandEdgesParameters() = default;
+ ~ON_SubDExpandEdgesParameters() = default;
+ ON_SubDExpandEdgesParameters(const ON_SubDExpandEdgesParameters&) = default;
+ ON_SubDExpandEdgesParameters& operator=(const ON_SubDExpandEdgesParameters&) = default;
+
+ static const ON_SubDExpandEdgesParameters Default;
+
+ ///
+ /// ON_SubDExpandEdgesParameters::Style specifies options for how faces are inserted along input edges.
+ ///
+ enum class Style : unsigned char
+ {
+ ///
+ /// Indicates the variable has not be initialized.
+ ///
+ Unset = 0,
+
+ ///
+ /// One quad replaces each input manifold edge. Corner cases where three or more edges meet
+ /// receive special handling.
+ ///
+ Single = 1,
+
+ ///
+ /// Two quads are added for each input manifold edge. Corner cases where three or more edges meet
+ /// receive special handling.
+ ///
+ Double = 2,
+
+ ///
+ /// This option applies only when the the input is an array of ON_SubDEdgePtrs
+ /// that form a single oriented edge chain of manifold interior edges.
+ /// A single quad is added to the left of the input edges.
+ /// (The left side of of an oriented interior manifold edge is the face
+ /// whose natural boundary orientation is the same as with the ON_SubDEdgePtr direction.)
+ ///
+ HalfLeft = 3,
+
+ ///
+ /// This option applies only when the the input is an array of ON_SubDEdgePtrs
+ /// that form a single oriented edge chain of manifold interior edges.
+ /// A single quad is added to the right of the input edges.
+ /// (The right side of of an oriented interior manifold edge is the face
+ /// whose natural boundary orientation is opposite the ON_SubDEdgePtr direction.)
+ ///
+ HalfRight = 4,
+ };
+
+public:
+ /// OffsetTolerance = 0.001
+ static const double OffsetTolerance;
+
+ /// Minimum permitted offset value (0.05)
+ static const double MinimumOffset;
+
+ /// Maximum permitted offset value (0.95)
+ static const double MaximumOffset;
+
+ /// Small offset value (0.125)
+ static const double SmallOffset;
+
+ /// Medium offset value (0.25)
+ static const double MediumOffset;
+
+ /// Large offset value (0.5)
+ static const double LargeOffset;
+
+public:
+ ///
+ /// This SHA1 hash can be used to determine if sets of parameters are the same.
+ ///
+ /// A SHA1 hash of all parameter values.
+ const ON_SHA1_Hash Hash() const;
+
+public:
+ static double CleanupOffset(double x);
+
+
+ ///
+ /// Normalized constant offset parameter for inserting edges parallel to an input edge.
+ /// Smaller values create narrower offsets.
+ ///
+ /// Offset value in the range ON_SubDExpandEdgesParameters::MinimumOffset to ON_SubDExpandEdgesParameters::MaximumOffset.
+ double ConstantOffset() const;
+
+ ///
+ /// Set the constant offset value. Values within OffsetTolerance of a
+ /// predefined offset value are set to the predefined offset value.
+ ///
+ ///
+ /// ON_SubDExpandEdgesParameters::MinimumOffset <= offset <= ON_SubDExpandEdgesParameters::MaximumOffset;
+ ///
+ ///
+ void SetConstantOffset(double offset);
+
+
+ ///
+ /// Determine if the set of ON_SubDEdgePtrs can be sorted into a
+ /// single edge chain that supports half side offsets.
+ ///
+ ///
+ /// True if variable offsets can be applied to edge_chain.
+ static bool IsValidForHalfOffset(
+ const ON_SimpleArray& edges
+ );
+
+ ///
+ /// Determine if the set of ON_SubDEdgePtrs can be sorted into a
+ /// single open edge chain supports variable offsets.
+ ///
+ ///
+ /// True if variable offsets can be applied to edge_chain.
+ static bool IsValidForVariableOffset(
+ const ON_SimpleArray& edges
+ );
+
+ /// This option applies only when the the input is an array of ON_SubDEdgePtrs
+ /// that form a single oriented edge chain. You may use
+ /// ON_SubDExpandEdgesParameters::IsValidForVariableOffset() to determine if an
+ /// array of ON_SubDEdgePtrs meets the variable offset requirements.
+ /// In all other cases, the constant Offset() is used.
+ /// To apply variable offsets to several edge chains, expand them one at a time.
+ const ON_Interval VariableOffset() const;
+
+ /*
+ Description:
+ This option applies only when the the input is an array of ON_SubDEdgePtrs
+ that form a single oriented edge chain. You may use
+ ON_SubDExpandEdgesParameters::IsValidForVariableOffset() to determine if an
+ array of ON_SubDEdgePtrs meets the variable offset requirements.
+ In all other cases, the constant Offset() is used.
+ To apply variable offsets to several edge chains, expand them one at a time.
+
+ Parameters:
+ variable_offsets - [in]
+ The two values must be between 0 and 1 and differ by more than ON_SubDExpandEdgesParameters::OffsetTolerance.
+ */
+ void SetVariableOffset(ON_Interval variable_offsets);
+
+ void ClearVariableOffset();
+
+ static bool IsValidConstantOffset(
+ double constant_offset_candidate
+ );
+
+ static bool IsValidVariableOffset(
+ ON_Interval variable_offset_candidate
+ );
+
+ /*
+ Returns:
+ True if variable offsets are set.
+ */
+ bool IsVariableOffset() const;
+
+
+ ON_SubDExpandEdgesParameters::Style FaceStyle() const;
+
+ ///
+ /// Set the style for new faces.
+ ///
+ ///
+ /// Style for new faces.
+ void SetFaceStyle(ON_SubDExpandEdgesParameters::Style face_style);
+
+ /// True if the FaceStyle() is HalfLeft of HalfRight.
+ bool IsHalfFaceStyle() const;
+
+ /// Per face color for new faces.
+ const ON_Color FaceColor() const;
+
+ ///
+ /// Set the perf face color for new faces.
+ ///
+ /// Color for new face. Pass ON_Color::Unset for no per face color.
+ void SetFaceColor(ON_Color face_color);
+
+ /// Status for new faces.
+ const ON_ComponentStatus FaceStatus() const;
+
+ ///
+ /// Set the Mark(), MarkBits(), Selected(), and Highlighted() status for new faces.
+ ///
+ /// Status for new faces.
+ void SetFaceStatus(ON_ComponentStatus face_status);
+
+private:
+ double m_constant_offset = ON_SubDExpandEdgesParameters::MediumOffset;
+ ON_Interval m_variable_offsets = ON_Interval::Nan;
+ ON_Color m_face_color = ON_Color::UnsetColor;
+ ON_ComponentStatus m_face_status;
+ ON_SubDExpandEdgesParameters::Style m_face_style = ON_SubDExpandEdgesParameters::Style::Double;
+
+private:
+ unsigned char m_reserved1 = 0;
+
+private:
+ ON__UINT64 m_reserved2 = 0;
+ ON__UINT64 m_reserved3 = 0;
+};
+
+bool operator==(const ON_SubDExpandEdgesParameters& lhs, const ON_SubDExpandEdgesParameters& rhs);
+bool operator!=(const ON_SubDExpandEdgesParameters& lhs, const ON_SubDExpandEdgesParameters& rhs);
+
+
//////////////////////////////////////////////////////////////////////////
//
// ON_SubD
@@ -3030,7 +3599,7 @@ public:
bForceUpdate - [in]
When in doubt pass false.
The SubD hashes are mutable and cached. When code properly manages GeometryContentSerialNumber(),
- then SubDHash(hash_type,false) will alwasys return the correct answer. This is the expected behavior.
+ then SubDHash(hash_type,false) will always return the correct answer. This is the expected behavior.
If code directly modifies SubD components and fails to call ChangeGeometryContentSerialNumberForExperts(),
then it is possible a stale hash will be returned. Setting bForceUpdate = true forces the SHA1
values to be recalculated from scratch. For extremely large SubDs, this recalculation can be time consuming.
@@ -3310,8 +3879,8 @@ public:
component_ring[component_count-1] = last face
Example:
- unsinged int component_ring_count = GetVertexComponentRing(vit,component_ring);
- unsinged int N = component_ring_count/2; // number of edges in ring
+ unsigned int component_ring_count = GetVertexComponentRing(vit,component_ring);
+ unsigned int N = component_ring_count/2; // number of edges in ring
const bool bSectorHasCreaseBoundary = (0 == (component_ring_count % 2));
*/
static unsigned int GetSectorComponentRing(
@@ -3350,8 +3919,8 @@ public:
component_ring[component_count-1] = last face
Example:
- unsinged int component_ring_count = GetVertexComponentRing(vit,component_ring);
- unsinged int N = component_ring_count/2; // number of edges in ring
+ unsigned int component_ring_count = GetVertexComponentRing(vit,component_ring);
+ unsigned int N = component_ring_count/2; // number of edges in ring
const bool bSectorHasCreaseBoundary = (0 == (component_ring_count % 2));
*/
static unsigned int GetSectorComponentRing(
@@ -4598,6 +5167,16 @@ public:
*/
bool HasPerFaceColorsFromSymmetryMotif() const;
+ /*
+ Description:
+ Sets ON_SubDComponent MarkBits() to
+ 0: component is not in a symmetry set motif
+ n>=1:
+ The component is the the n-th element in the symmetry set
+ with n=1 indicating the component in the primary motif.
+ */
+ void SetComponentMarkBitsFromSymmetryMotif() const;
+
///*
//Description:
@@ -4760,37 +5339,62 @@ public:
/*
Description:
- Split and edge.
- The input edge is modifed to terminate at the input vertex.
- The new edge begins at the input vertex and ends at the final vertex
- of the original input edge.
+ Divide an edge into two contiguous edges.
+ The input edge is modifed to terminate at the new vertex.
+ The new edge begins at the new vertex and ends at the orginal edge's m_vertex[1].
edge - [in]
edge to split.
vertex_location - [in]
- location of inserted vertex.
+ location of the new vertex vertex.
If vertex_location == ON_ON_3dPoint::UnsetPoint,
then the edge's midpoint is used.
Returns:
A pointer to the new edge or nullptr if the input is not valid.
- Remarks:
+ Remarks:
After all editing operations are completed, you must call this->UpdateEdgeSectorCoefficients(true) before
evaluation.
*/
const class ON_SubDEdge* SplitEdge(
class ON_SubDEdge* edge,
ON_3dPoint vertex_location
- );
+ );
/*
Description:
- Split a face into two faces by inserting and edge connecting the
- specified vertices.
+ Divide an edge into two contiguous edges.
+ edge - [in]
+ edge to split.
+ vertex_location - [in]
+ location of the new vertex vertex.
+ If vertex_location == ON_ON_3dPoint::UnsetPoint,
+ then the edge's midpoint is used.
+ new_edge_end - [in]
+ This paratmer is 0 or 1 and dtermines where the new edge is inserted.
+ Below v0 = input eptr.RelativeVertex(0), v1 = new vertex, v2 = input eptr.RelativeVertex(1),
+ and new_eptr = returned edge pointer.
+ If edge_end is 0, new_eptr is first: v0 = new_eptr.RelativeVertex(0), v1 = new_eptr.RelativeVertex(1)=eptr.RelativeVertex(0), v2 = eptr.RelativeVertex(1).
+ If edge_end is 1, new_eptr is last: v0 = eptr.RelativeVertex(0), v1 = eptr.RelativeVertex(1)=new_eptr.RelativeVertex(0), v2 = new_eptr.RelativeVertex(1).
+ Returns:
+ A pointer to the new edge or ON_SubDEdgePtr::Null if the input is not valid.
+ Remarks:
+ After all editing operations are completed, you must clear the evaluation cache
+ and call this->UpdateEdgeSectorCoefficients(true).
+ */
+ const ON_SubDEdgePtr SplitEdge(
+ ON_SubDEdgePtr eptr,
+ ON_3dPoint vertex_location,
+ unsigned new_edge_end
+ );
+
+ /*
+ Description:
+ Divide a face into two faces by inserting an edge connecting the specified vertices.
Parameters:
face - [in]
A face with at least four edges.
fvi0 - [in]
fvi1 - [in]
- Indices of the inserted edge ends.
+ Face vertex indices of the inserted edge's ends.
Returns:
A pointer to the inserted edge.
The inserted edge runs from face->Vertex(fvi0) to face->Vertex(fvi1).
@@ -4806,14 +5410,13 @@ public:
/*
Description:
- Split a face into two faces by inserting and edge connecting the
- specified vertices.
+ Divide a face into two faces by inserting an edge connecting the specified vertices.
Parameters:
face - [in]
A face with at least four edges.
v0 - [in]
v1 - [in]
- Vertices on the face boundary.
+ Face vertices of the inserted edge's ends.
Returns:
A pointer to the inserted edge.
The inserted edge runs from v0 to v1.
@@ -4828,6 +5431,55 @@ public:
const class ON_SubDVertex* v1
);
+
+ /*
+ Description:
+ Divide a face into two faces by inserting an edge connecting the specified vertices.
+ Parameters:
+ face - [in]
+ A face with at least four edges.
+ fvi0 - [in]
+ fvi1 - [in]
+ Face vertex indices of the inserted edge's ends.
+ new_face_side - [in]
+ 0: The new face will be on the left side of the inserted edge.
+ 0: The new face will be on the right side of the inserted edge.
+ Returns:
+ The edge and edgeptr are both being af both oriented
+ The inserted edge and returned edge ptr runs from face->Vertex(fvi0) to face->Vertex(fvi1).
+ the new face is SplitFace(...).RelativeFace(new_face_side) and the original face is SplitFace(...).RelativeFace(new_face_side).
+ */
+ const ON_SubDEdgePtr SplitFace(
+ class ON_SubDFace* face,
+ unsigned int fvi0,
+ unsigned int fvi1,
+ unsigned new_face_side
+ );
+
+ /*
+ Description:
+ Divide a face into two faces by inserting an edge connecting the specified vertices.
+ Parameters:
+ face - [in]
+ A face with at least four edges.
+ v0 - [in]
+ v1 - [in]
+ Face vertices of the inserted edge's ends.
+ new_face_side - [in]
+ 0: The new face will be on the left side of the inserted edge.
+ 0: The new face will be on the right side of the inserted edge.
+ Returns:
+ The edge and edgeptr are both being af both oriented
+ The inserted edge and returned edge ptr runs from face->Vertex(fvi0) to face->Vertex(fvi1).
+ the new face is SplitFace(...).RelativeFace(new_face_side) and the original face is SplitFace(...).RelativeFace(new_face_side).
+ */
+ const ON_SubDEdgePtr SplitFace(
+ class ON_SubDFace* face,
+ const class ON_SubDVertex* v0,
+ const class ON_SubDVertex* v1,
+ unsigned new_face_side
+ );
+
/*
Description:
Replace a face with a triangle fan by adding a single new vertex at fan_center_point
@@ -6912,6 +7564,12 @@ private:
#pragma ON_PRAGMA_WARNING_POP
};
+
+#if defined(ON_DLL_TEMPLATE)
+ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray;
+#endif
+
+
//////////////////////////////////////////////////////////////////////////
//
// ON_SubDComponentList
@@ -7204,7 +7862,7 @@ public:
//
// Q = 3/8 * (A + B) = 3/4 * (1/2*A + 1/2*B)
//
- // A crease edge's subdivision point is alwasy the edge's midpoint.
+ // A crease edge's subdivision point is always the edge's midpoint.
/*
Description:
Calculates sector weight value for the sector type
@@ -10361,32 +11019,19 @@ public:
*/
bool SavedSubdivisionPointIsSet() const;
- //////////////////////////////////////////////////////////////
- //
- // displacement applied to subdivision point
- //
- bool SetSubdivisionDisplacement(
- const double displacement[3]
- );
+ ON_DEPRECATED_MSG("Does nothing. Returns false.")
+ bool SetSubdivisionDisplacement(const double*);
- bool GetSubdivisionDisplacement(
- double displacement[3]
- ) const;
+ ON_DEPRECATED_MSG("Does nothing. Returns false.")
+ bool GetSubdivisionDisplacement(double*) const;
- /*
- Returns:
- subdivision displacement. If a displacement has not been set,
- ON_3dPoint::ZeroVector is returned.
- */
+ ON_DEPRECATED_MSG("Does nothing. Returns nans.")
const ON_3dVector SubdivisionDisplacement() const;
- /*
- Returns:
- True if the subdivision displacment vector is valid and not zero.
- */
+ ON_DEPRECATED_MSG("Does nothing. Returns false.")
bool SubdivisionDisplacementIsNonzero() const;
-
+ ON_DEPRECATED_MSG("Does nothing.")
void ClearSubdivisionDisplacement() const;
protected:
@@ -10394,9 +11039,6 @@ protected:
friend class ON_SubDHeap;
enum SavedPointsFlags : unsigned char
{
- // if ( 0 != (m_saved_points_flags & SubDDisplacementVIsSet), then m_displacementV is set.
- SubdivisionDisplacementBit = 0x20,
-
// if ( 0 != (m_saved_points_flags & SubdivisionPointBit), then m_cache_subd_P is set.
SubdivisionPointBit = 0x40,
@@ -10407,8 +11049,8 @@ protected:
// Unset means any information in the fields identified above is invalid and must be recalculated.
SurfacePointBit = 0x80,
- // ControlNetFragmentBit | SubdivisionPointBit | SubdivisionDisplacementBit | SurfacePointBit
- CachedPointMask = 0xF0
+ // SubdivisionPointBit | SurfacePointBit
+ CachedPointMask = 0xC0
};
enum ModifiedFlags : unsigned char
@@ -10484,9 +11126,11 @@ protected:
// GetSubdivisionPoint( bUseSavedSubdivisionPoint=true ) can change the value of m_cache_subd_P
mutable double m_saved_subd_point1[3]; // saved subdivision point
-protected:
- // optional displacement applied to standard subdivision point.
- double m_displacement_V[3];
+private:
+ // Reserved for future use for attributes that apply to allSubD components (ON_SubDVertex, ON_SubDEdge, and ON_SubDFace).
+ ON__UINT64 m_reserved8bytes1;
+ ON__UINT64 m_reserved8bytes2;
+ ON__UINT64 m_reserved8bytes3;
public:
/*
@@ -14505,12 +15149,30 @@ public:
/*
+ Description:
+
+ CurrentEdge(1)
+ |
+ |
+ NextFace() | CurrentFace()
+ |
+ |
+ *------------- CurrentEdge(0)
+ PrevFace()
+
+ The asterisk (*) marks the center vertex.
+ The diagram is With respect to the initial iterator orientation.
+
Parameters:
face_side_index - [in]
- 0: Return the edge entering the center vertex.
- 1: Return the edge leaving the center vertex.
- Returns:
- The requested edge.
+ CurrentEdge(0) = edge on the clockwise (PrevFace) side of the current face
+ CurrentEdge(1) = edge on the counterclockwise (NextFace) side of the current face
+ The directions "counterclockwise" and "clockwise" are with respect to the
+ initial iterator orientation.
+
+ Returns:
+ The requested edge. The edge pointer is oriented such that
+ RelativeVertex(0) is the center vertex.
*/
ON_SubDEdgePtr CurrentEdgePtr(
unsigned int face_side_index
@@ -16234,6 +16896,225 @@ public:
//
// Edge chain tools
//
+
+ /*
+ Description:
+ Unconditionally sort edges into chains.
+
+ Parameters:
+ unsorted_edges - [in]
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ no edge chain will pass through that vertex.
+ To sort an array in place, pass the same array as both parameters.
+
+ sorted_edges - [out]
+ The sorted_edges[] has the edges grouped into edge chains.
+ A value of ON_SubDEdgePtr::Null is at the end of every chain
+ including the last chain.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ sorted_edges[i].RelativeVertex(1) == sorted_edges[i+1].RelativeVertex(0).
+ Returns:
+ Number of chains in edge_chains[].
+ Remarks:
+ This version of SortEdgesIntoEdgeChains() uses MarkBits() on the edges
+ and vertices in unsorted_edges[]. The output values of MarkBits()
+ on these components are zero.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static unsigned int SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< ON_SubDEdgePtr >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+ );
+
+ /*
+ Description:
+ Unconditionally sort edges into chains.
+
+ Parameters:
+ unsorted_edges - [in]
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ no edge chain will pass through that vertex.
+
+ sorted_edges - [out]
+ The sorted_edges[] has the edges grouped into edge chains.
+ A value of ON_SubDEdgePtr::Null is at the end of every chain
+ including the last chain.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ sorted_edges[i].RelativeVertex(1) == sorted_edges[i+1].RelativeVertex(0).
+ Returns:
+ Number of chains in edge_chains[].
+ Remarks:
+ This version of SortEdgesIntoEdgeChains() uses MarkBits() on the edges
+ and vertices in unsorted_edges[]. The output values of MarkBits()
+ on these components are zero.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static unsigned int SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< const ON_SubDEdge* >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+ );
+
+ /*
+ Description:
+ Unconditionally sort edges into chains.
+
+ Parameters:
+ unsorted_edges - [in]
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ no edge chain will pass through that vertex.
+
+ sorted_edges - [out]
+ The sorted_edges[] has the edges grouped into edge chains.
+ A value of ON_SubDEdgePtr::Null is at the end of every chain
+ including the last chain.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ sorted_edges[i].RelativeVertex(1) == sorted_edges[i+1].RelativeVertex(0).
+ Returns:
+ Number of chains in edge_chains[].
+ Remarks:
+ This version of SortEdgesIntoEdgeChains() uses MarkBits() on the edges
+ and vertices in unsorted_edges[]. The output value of MarkBits()
+ on these components is zero.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static unsigned int SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< ON_SubDComponentPtr >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+ );
+
+ /*
+ Description:
+ Unconditionally sort edges into chains.
+
+ Parameters:
+ unsorted_edges - [in]
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ no edge chain will pass through that vertex.
+
+ sorted_edges - [out]
+ The sorted_edges[] has the edges grouped into edge chains.
+ A value of ON_SubDEdgePtr::Null is at the end of every chain
+ including the last chain.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ sorted_edges[i].RelativeVertex(1) == sorted_edges[i+1].RelativeVertex(0).
+ Returns:
+ Number of chains in edge_chains[].
+ Remarks:
+ This version of SortEdgesIntoEdgeChains() uses MarkBits() on the edges
+ and vertices in unsorted_edges[]. The output value of MarkBits()
+ on these components is zero.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static unsigned int SortEdgesIntoEdgeChains(
+ const ON_SubD& subd,
+ const ON_SimpleArray< ON_COMPONENT_INDEX >& unsorted_edges,
+ ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
+ );
+
+ /*
+ Description:
+ Determin if the array of ON_SubDEdgePtrs in edges[] can be sorted
+ into a single edge chain.
+ Parameters:
+ edges - [in]
+ Set of edges to test.
+ bIsClosed - [out]
+ If true is returned and edges[] sorts into a closed edge chain,
+ then bIsClosed is true. Otherwise bIsClosed is false.
+ bIsSorted - [out]
+ If true is returned and edges[] is currently sorted into an edge chain,
+ then bIsSorted is true. Otherwise bIsSorted is false.
+ Returns:
+ If the array of edges[] can be sorted into a single edge chain
+ with no self intersections, then true is returned. Otherwise false
+ is returned.
+ Remarks:
+ This test usesthe MarkBits() values on the edges and vertices and
+ restores the values to the input state.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static bool IsSingleEdgeChain(
+ const ON_SimpleArray& edges,
+ bool& bIsClosed,
+ bool& bIsSorted
+ );
+
+ /*
+ Description:
+ Determin if the array of ON_SubDEdgePtrs in edges[] can be sorted
+ into a single edge chain.
+ Parameters:
+ edges - [in]
+ Set of edges to test.
+ Returns:
+ If the array of edges[] can be sorted into a single edge chain
+ with no self intersections, then true is returned. Otherwise false
+ is returned.
+ Remarks:
+ This test usesthe MarkBits() values on the edges and vertices and
+ restores the values to the input state.
+ Multiple threads may not simultaneously use any SubD tools on that rely
+ on markbits on the same ON_SubD.
+ */
+ static bool IsSingleEdgeChain(
+ const ON_SimpleArray& edges
+ );
+
+ /*
+ Description:
+ Sort edges into a chains
+
+ Parameters:
+ unsorted_edges - [in]
+ To sort an array in place, pass the same array as both parameters.
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ then all of the edges that share that vertex are ignored.
+ The edges can be from one or more SubDs.
+
+ unsigned int minimum_chain_length - [in]
+ minimum number of edges to consider for a chain.
+
+ edge_chains - [out]
+ The edge_chains[] has the edges grouped into edge chains.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ edge_chains[i].RelativeVertex(1) == edge_chains[i+1].RelativeVertex(0).
+
+ When edge_chains[i].RelativeVertex(1) != edge_chains[i+1].RelativeVertex(0),
+ a chain ends at edge_chains[i] and another begins at edge_chains[i+1].
+
+ Reasonnable effort is made to keep the first edge in every chain with the
+ same orientation as the input edge from edge_chains[]. However, this is not
+ possible in every case, for example if the input is two edges sharing the
+ same starting vertex.
+
+ NB: Reasonnable effort is made to keep the corner vertices on the exterior
+ of the chains, however some chains will have corners in their interior,
+ especially closed chains.
+
+ Returns:
+ Number of chains in edge_chains[].
+
+ Remarks:
+ When the orientation of the input edges is not intentionally set,
+ the versions of SortEdgesIntoEdgeChains() above without a minimum_chain_length
+ variable are a better choice.
+ */
+ static unsigned int SortEdgesIntoEdgeChains(
+ const ON_SimpleArray< ON_SubDEdgePtr >& unsorted_edges,
+ unsigned int minimum_chain_length,
+ ON_SimpleArray< ON_SubDEdgePtr >& edge_chains
+ );
+
/*
Description:
Sort edges into a chains
@@ -16262,25 +17143,92 @@ public:
Returns:
Number of chains in edge_chains[].
- */
- static unsigned int SortEdgesIntoEdgeChains(
- const ON_SimpleArray< ON_SubDEdgePtr >& unsorted_edges,
- unsigned int minimum_chain_length,
- ON_SimpleArray< ON_SubDEdgePtr >& edge_chains
- );
+ Remarks:
+ When the orientation of the input edges is not intentionally set,
+ the versions of SortEdgesIntoEdgeChains() above without a minimum_chain_length
+ variable are a better choice.
+ */
static unsigned int SortEdgesIntoEdgeChains(
const ON_SimpleArray< const ON_SubDEdge* >& unsorted_edges,
unsigned int minimum_chain_length,
ON_SimpleArray< ON_SubDEdgePtr >& edge_chains
);
+ /*
+ Description:
+ Sort edges into a chains
+
+ Parameters:
+ unsorted_edges - [in]
+ To sort an array in place, pass the same array as both parameters.
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ then all of the edges that share that vertex are ignored.
+ The edges can be from one or more SubDs.
+
+ unsigned int minimum_chain_length - [in]
+ minimum number of edges to consider for a chain.
+
+ edge_chains - [out]
+ The edge_chains[] has the edges grouped into edge chains.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ edge_chains[i].RelativeVertex(1) == edge_chains[i+1].RelativeVertex(0).
+
+ When edge_chains[i].RelativeVertex(1) != edge_chains[i+1].RelativeVertex(0),
+ a chain ends at edge_chains[i] and another begins at edge_chains[i+1].
+
+ The first edge in every chain has the same orientation as the input edge
+ from edge_chains[].
+
+ Returns:
+ Number of chains in edge_chains[].
+
+ Remarks:
+ When the orientation of the input edges is not intentionally set,
+ the versions of SortEdgesIntoEdgeChains() above without a minimum_chain_length
+ variable are a better choice.
+ */
static unsigned int SortEdgesIntoEdgeChains(
const ON_SimpleArray< ON_SubDComponentPtr >& unsorted_edges,
unsigned int minimum_chain_length,
ON_SimpleArray< ON_SubDEdgePtr >& sorted_edges
);
+ /*
+ Description:
+ Sort edges into a chains
+
+ Parameters:
+ unsorted_edges - [in]
+ To sort an array in place, pass the same array as both parameters.
+ If unsorted_edges[] contains three or more edges that share a common vertex,
+ then all of the edges that share that vertex are ignored.
+ The edges can be from one or more SubDs.
+
+ unsigned int minimum_chain_length - [in]
+ minimum number of edges to consider for a chain.
+
+ edge_chains - [out]
+ The edge_chains[] has the edges grouped into edge chains.
+
+ In an edge chain subsequent edges share a common vertex; i.e.
+ edge_chains[i].RelativeVertex(1) == edge_chains[i+1].RelativeVertex(0).
+
+ When edge_chains[i].RelativeVertex(1) != edge_chains[i+1].RelativeVertex(0),
+ a chain ends at edge_chains[i] and another begins at edge_chains[i+1].
+
+ The first edge in every chain has the same orientation as the input edge
+ from edge_chains[].
+
+ Returns:
+ Number of chains in edge_chains[].
+
+ Remarks:
+ When the orientation of the input edges is not intentionally set,
+ the versions of SortEdgesIntoEdgeChains() above without a minimum_chain_length
+ variable are a better choice.
+ */
static unsigned int SortEdgesIntoEdgeChains(
const ON_SubD& subd,
const ON_SimpleArray< ON_COMPONENT_INDEX >& unsorted_edges,
@@ -16322,6 +17270,15 @@ public:
ON_SimpleArray< ON_SubDEdgePtr >& edge_chains
);
+ /*
+ Returns:
+ A SHA1 hash of the edge and vertex ids.
+ Useful for determining when two edge chains from different
+ subds (one generally a modified copy) involve the same
+ edges and vertices.
+ */
+ const ON_SHA1_Hash Hash() const;
+
/////////////////////////////////////////////////////////
//
@@ -16449,6 +17406,75 @@ public:
bool bCheckForDuplicateEdges
);
+
+ /*
+ Description:
+ Get the edges and faces on a specified side of the edge chain.
+ Parameters:
+ relative_side - [in]
+ 0: Get edges and faces on the ON_SubDEdgePtr::RelativeFace(0) side (left).
+ 1: Get edges and faces on the ON_SubDEdgePtr::RelativeFace(1) side (right).
+
+ side_components - [out]
+ side_components[] is a sequence of made of vertices, edges, and faces that
+ record the edges and faces that are on the specified side of the edge chain.
+
+ When a vertex is in side_components[], ON_SubDComponentPtr.Vertex() is not nullptr
+ and the vertex is between two conscutive edges in the edge chain.
+
+ When an edge is in side_components[], ON_SubDComponentPtr.EdgePtr() is not nullptr,
+ the edge is on the specified side of the edge chain (not in the edge chain),
+ and ON_SubDComponentPtr.EdgePtr().RelativeVertex(0) is a vertex on the edge chain.
+
+ When a face is in side_components[], ON_SubDComponentPtr.Face() is not nullptr,
+ then at least one vertex of f is part of the edge chain and f is on the specified
+ side of the edge chain.
+
+ If a vertex v is side_components[], then it is preceded and followed by the same
+ face (...f,v,f,...), there are consecutive edges in the edge chain (...e0,e1,...),
+ and e0 and e1 are consecutive edges in f's boundary.
+
+ If ...eptr0,f,eptr1,... is in side_components[],
+ v0 = eptr0.RelativeVertex(0),
+ v1 = eptr0.RelativeVertex(0),
+ and v0 == v1,
+ then eptr0 and eptr1 are conecutive edges in the boundary of f
+ and v0==v1 is a vertex in the edge chain.
+
+ If ...eptr0,f,eptr1,... is in side_components[],
+ v0 = eptr0.RelativeVertex(0),
+ v1 = eptr0.RelativeVertex(0),
+ and v0 != v1,
+ then there is an edge c from v0 to v1 that is in the edge chain and
+ eptr0,c,eptr1 are conecutive edges in the boundary of f.
+
+ If ...eptr0,f,v,... is in side_components[] and v0 = eptr0.RelativeVertex(0),
+ and then v0 != v, then there is an edge c from v0 to v that is in the edge chain,
+ and eptr0,c are conecutive edges in the boundary of f.
+
+ If ...v,f,eptr1,... is in side_components[] and v1 = eptr1.RelativeVertex(0),
+ and then v != v1, then there is an edge c from v to v1 that is in the edge chain,
+ and c,eptr1 conecutive edges in the boundary of f.
+
+ Remarks:
+ If the SubD is not an oriented manifold along the entire side of the chain,
+ then there may be gaps in chain_side[]. When there are face fans at a chain
+ vertex, there will be faces that do not have an edge on the chain.
+ */
+ bool GetSideComponents(
+ unsigned relative_side,
+ ON_SimpleArray& side_components
+ ) const;
+
+ /*
+ See above.
+ */
+ static bool GetSideComponents(
+ const ON_SimpleArray& edge_chain,
+ unsigned relative_side,
+ ON_SimpleArray& side_components
+ );
+
public:
/*
Returns:
@@ -16618,6 +17644,10 @@ public:
const ON_SubDEdgePtr LastEdgePtr() const;
const ON_SubDEdgePtr EdgePtr(int edge_index) const;
+ const ON_3dPoint FirstControlNetPoint() const;
+ const ON_3dPoint LastControlNetPoint() const;
+ const ON_3dPoint ControlNetPoint(int vertex_index) const;
+
const ON_SubDEdge* FirstEdge() const;
const ON_SubDEdge* LastEdge() const;
const ON_SubDEdge* Edge(int edge_index) const;
@@ -16950,6 +17980,15 @@ public:
ON_SubDComponentLocation vertex_location
);
+ bool CreateSubDEmptyRTree(
+ const ON_SubD& subd
+ );
+
+ bool AddVertex(
+ const ON_SubDVertex* v,
+ ON_SubDComponentLocation vertex_location
+ );
+
const ON_SubDVertex* FindVertexAtPoint(
const ON_3dPoint P,
const double distance_tolerance
@@ -16965,6 +18004,11 @@ public:
const double distance_tolerance
) const;
+ const ON_SubDVertex* FindVertex(
+ const class ON_SubDRTreeVertexFinder& vertex_finder,
+ const double distance_tolerance
+ ) const;
+
const ON_SubD& SubD() const;
/*
@@ -16988,6 +18032,8 @@ public:
ON_SubDRTreeVertexFinder(const ON_SubDRTreeVertexFinder&) = default;
ON_SubDRTreeVertexFinder& operator=(const ON_SubDRTreeVertexFinder&) = default;
+ static const ON_SubDRTreeVertexFinder Unset;
+
public:
static const ON_SubDRTreeVertexFinder Create(const ON_3dPoint P);
@@ -16999,6 +18045,20 @@ public:
*/
static const ON_SubDRTreeVertexFinder Create(const ON_3dPoint P, bool bMarkFilter);
+ enum class MarkBitsFilter : unsigned char
+ {
+ None = 0,
+ Equal = 1,
+ NotEqual = 2
+ };
+
+ static const ON_SubDRTreeVertexFinder Create(
+ const ON_3dPoint P,
+ ON_SubDRTreeVertexFinder::MarkBitsFilter mark_bits_filter,
+ ON__UINT8 mark_bits
+ );
+
+
public:
ON_3dPoint m_P = ON_3dPoint::NanPoint;
double m_distance = ON_DBL_QNAN;
@@ -17008,14 +18068,17 @@ public:
// to be found and vertices with Mark() != m_bMarkFilter are ignored.
bool m_bMarkFilterEnabled = false;
bool m_bMarkFilter = false;
+
+public:
+ ON_SubDRTreeVertexFinder::MarkBitsFilter m_mark_bits_filter = ON_SubDRTreeVertexFinder::MarkBitsFilter::None;
+ ON__UINT8 m_mark_bits = 0;
+
private:
- unsigned short m_reserved1 = 0;
unsigned int m_reserved2 = 0;
public:
static bool Callback(void* a_context, ON__INT_PTR a_id);
-
};
diff --git a/opennurbs_subd.natvis b/opennurbs_subd.natvis
new file mode 100644
index 00000000..9451f386
--- /dev/null
+++ b/opennurbs_subd.natvis
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {{vertex id={m_id}}}
+
+
+
+ {{edge id={m_id}}}
+
+ - nullptr!=m_vertex[0] ? m_vertex[0]->m_id : 0
+ - nullptr!=m_vertex[1] ? m_vertex[1]->m_id : 0
+
+
+
+
+ {{face id={m_id}}}
+
+
+
+
+
+ null
+ vertex(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ vertex(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ edge(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ edge(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ face(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ face(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+
+ - ((m_ptr>8) && (2==(m_ptr&7)))?(((ON_SubDVertex*)(m_ptr&0xFFFFFFFFFFFFFFF8))):((ON_SubDVertex*)nullptr)
+ - ((m_ptr>8) && (4==(m_ptr&7)))?(*((ON_SubDEdgePtr*)(m_ptr&0xFFFFFFFFFFFFFFF8))):ON_SubDEdgePtr::Null
+ - ((m_ptr>8) && (6==(m_ptr&7)))?(((ON_SubDFace*)(m_ptr&0xFFFFFFFFFFFFFFF8))):((ON_SubDFace*)nullptr)
+
+
+
+
+
+ null
+ vertex(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ vertex(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+
+ - m_ptr>=8?(((ON_SubDVertex*)(m_ptr&0xFFFFFFFFFFFFFFF8))):nullptr
+
+
+
+
+
+ null
+ edge(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ edge(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+
+ - (m_ptr>8)?(((ON_SubDEdge*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_vertex[m_ptr%2]->m_id):0
+ - (m_ptr>8)?(((ON_SubDEdge*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_vertex[1-(m_ptr%2)]->m_id):0
+ - m_ptr>=8?(((ON_SubDEdge*)(m_ptr&0xFFFFFFFFFFFFFFF8))):nullptr
+
+
+
+
+
+ null
+ face(+) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+ face(-) id={((ON_SubDComponentBase*)(m_ptr&0xFFFFFFFFFFFFFFF8))->m_id}
+
+ - m_ptr>=8?(((ON_SubDFace*)(m_ptr&0xFFFFFFFFFFFFFFF8))):nullptr
+
+
+
+
diff --git a/opennurbs_subd_archive.cpp b/opennurbs_subd_archive.cpp
index d93d8c18..68b4ea32 100644
--- a/opennurbs_subd_archive.cpp
+++ b/opennurbs_subd_archive.cpp
@@ -212,9 +212,8 @@ static bool WriteBase(
if (archive.Archive3dmVersion() < 70)
{
// version 6 3dm files
- double P[3], V[3];
+ double P[3];
const bool bHaveP = base->GetSavedSubdivisionPoint(P);
- const bool bHaveV = base->GetSubdivisionDisplacement(V);
unsigned char cP = bHaveP ? 4U : 0U;
if (!archive.WriteChar(cP))
@@ -225,29 +224,17 @@ static bool WriteBase(
break;
}
- unsigned char cV = bHaveV ? 4U : 0U;
- if (!archive.WriteChar(cV))
+ unsigned char deprecated_and_never_used_zero = 0U;
+ if (!archive.WriteChar(deprecated_and_never_used_zero))
break;
- if (0 != cV)
- {
- if (!Internal_WriteDouble3(V, archive))
- break;
- }
return true;
}
// version 7 3dm files and later
- // 24 byte displacement addition
- double V[3];
- const bool bWriteDisplacement = base->GetSubdivisionDisplacement(V);
- if ( false == Internal_WriteComponentAdditionSize(bWriteDisplacement,archive,24) )
+ // never used displacement
+ if ( false == Internal_WriteComponentAdditionSize(false,archive,24) )
break;
- if (bWriteDisplacement)
- {
- if (!archive.WriteDouble(3,V))
- break;
- }
// 4 byte group id addition
const bool bWriteGroupId = base->m_group_id > 0;
@@ -300,8 +287,8 @@ static bool ReadBase(
if (archive.Archive3dmVersion() < 70)
{
unsigned char cP = 0U;
- unsigned char cV = 0U;
- double P[3], V[3];
+ unsigned char deprecated_and_never_used_char = 0U;
+ double P[3];
if (!archive.ReadChar(&cP))
break;
@@ -311,26 +298,16 @@ static bool ReadBase(
break;
}
- if (!archive.ReadChar(&cV))
+ if (!archive.ReadChar(&deprecated_and_never_used_char))
break;
- if (0 != cV)
+ if (0 != deprecated_and_never_used_char)
{
- if (!Internal_ReadDouble3(archive, V))
+ double deprecated_and_never_used_V[3];
+ if (!Internal_ReadDouble3(archive, deprecated_and_never_used_V))
break;
- const unsigned int no_displacement_version = ON_VersionNumberConstruct(7, 0, 2019, 6, 12, 0);
- const unsigned int archive_opennurbs_version = archive.ArchiveOpenNURBSVersion();
- if (archive_opennurbs_version <= no_displacement_version)
- {
- cV = 0;
- V[0] = 0.0;
- V[1] = 0.0;
- V[2] = 0.0;
- }
}
if (4 == cP)
base.SetSavedSubdivisionPoint(P);
- if (4 == cV)
- base.SetSubdivisionDisplacement(V);
return true;
}
@@ -338,6 +315,7 @@ static bool ReadBase(
unsigned char sz;
// 24 byte displacement addition
+ // This addition was a deprecated prototype that was never used in commercial Rhino.
sz = 0;
if (false == Internal_ReadComponentAdditionSize(archive, 24, &sz))
break;
@@ -345,10 +323,9 @@ static bool ReadBase(
return true; // end of additions
if (0 != sz)
{
- double V[3] = {};
- if (!archive.ReadDouble(V))
+ double deprecated_and_never_used_V[3] = {};
+ if (!archive.ReadDouble(3, deprecated_and_never_used_V))
break;
- base.SetSubdivisionDisplacement(V);
}
// 4 byte group id addition
diff --git a/opennurbs_subd_data.cpp b/opennurbs_subd_data.cpp
index efe4d0a9..75865874 100644
--- a/opennurbs_subd_data.cpp
+++ b/opennurbs_subd_data.cpp
@@ -733,9 +733,6 @@ void ON_SubDComponentBase::Internal_TransformComponentBase(
const class ON_Xform& xform
)
{
- if (0 != ON_SUBD_CACHE_DISPLACEMENT_FLAG(m_saved_points_flags))
- TransformVector(&xform.m_xform[0][0],m_displacement_V);
-
if ( SavedSubdivisionPointIsSet() )
{
if (bTransformationSavedSubdivisionPoint)
diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h
index 351a9b0f..852d58cd 100644
--- a/opennurbs_subd_data.h
+++ b/opennurbs_subd_data.h
@@ -462,18 +462,15 @@ void ON_SubDIncrementErrorCount(); // defined in opennurbs_subd.cpp
// m_saved_points_flags
//
#define ON_SUBD_CACHE_POINT_FLAG_BIT ON_SubDComponentBase::SavedPointsFlags::SubdivisionPointBit
-#define ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT ON_SubDComponentBase::SavedPointsFlags::SubdivisionDisplacementBit
#define ON_SUBD_CACHE_LIMITLOC_FLAG_BIT ON_SubDComponentBase::SavedPointsFlags::SurfacePointBit
#define ON_SUBD_CACHE_FLAGS_MASK ON_SubDComponentBase::SavedPointsFlags::CachedPointMask
#define ON_SUBD_CACHE_FLAGS(cache_subd_flags) (ON_SUBD_CACHE_FLAGS_MASK&(cache_subd_flags))
#define ON_SUBD_CACHE_POINT_FLAG(cache_subd_flags) (ON_SUBD_CACHE_POINT_FLAG_BIT&(cache_subd_flags))
-#define ON_SUBD_CACHE_DISPLACEMENT_FLAG(cache_subd_flags) (ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT&(cache_subd_flags))
#define ON_SUBD_CACHE_LIMITLOC_FLAG(cache_subd_flags) (ON_SUBD_CACHE_LIMITLOC_FLAG_BIT&(cache_subd_flags))
-#define ON_SUBD_CACHE_CLEAR_POINT_FLAG(cache_subd_flags) (cache_subd_flags &= (ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT|ON_SUBD_CACHE_LIMITLOC_FLAG_BIT|ON_SubDComponentBase::ModifiedFlags::ModifiedFlagsMask))
-#define ON_SUBD_CACHE_CLEAR_DISPLACEMENT_FLAG(cache_subd_flags) (cache_subd_flags &= (ON_SUBD_CACHE_POINT_FLAG_BIT|ON_SUBD_CACHE_LIMITLOC_FLAG_BIT|ON_SubDComponentBase::ModifiedFlags::ModifiedFlagsMask))
-#define ON_SUBD_CACHE_CLEAR_LIMITLOC_FLAG(cache_subd_flags) (cache_subd_flags &= (ON_SUBD_CACHE_POINT_FLAG_BIT|ON_SUBD_CACHE_DISPLACEMENT_FLAG_BIT|ON_SubDComponentBase::ModifiedFlags::ModifiedFlagsMask))
+#define ON_SUBD_CACHE_CLEAR_POINT_FLAG(cache_subd_flags) (cache_subd_flags &= (ON_SUBD_CACHE_LIMITLOC_FLAG_BIT|ON_SubDComponentBase::ModifiedFlags::ModifiedFlagsMask))
+#define ON_SUBD_CACHE_CLEAR_LIMITLOC_FLAG(cache_subd_flags) (cache_subd_flags &= (ON_SUBD_CACHE_POINT_FLAG_BIT|ON_SubDComponentBase::ModifiedFlags::ModifiedFlagsMask))
//////////////////////////////////////////////////////////////////////////
@@ -1651,7 +1648,7 @@ public:
+ m_fsp_oddball_fragments.SizeOfUnusedElements()
+ m_fsp_limit_curves.SizeOfUnusedElements();
- // It has alwasy been the case that count0 = count1 = ON_SubDDisplayParameters::MaximumDensity + 1.
+ // It has always been the case that count0 = count1 = ON_SubDDisplayParameters::MaximumDensity + 1.
// But a wrong answer is better than crashing if somebody incorrectly modifies ON_SubDHeap
// in the far future.
const size_t count0 = sizeof(m_unused_fragments) / sizeof(m_unused_fragments[0]);
diff --git a/opennurbs_subd_fragment.cpp b/opennurbs_subd_fragment.cpp
index a0e8ecf9..e1d2ceef 100644
--- a/opennurbs_subd_fragment.cpp
+++ b/opennurbs_subd_fragment.cpp
@@ -2658,7 +2658,7 @@ public:
else
{
// this is an inteior edge of a partial fragment and it
- // is alwasy sealed with its neighbor when it is created.
+ // is always sealed with its neighbor when it is created.
break;
}
}
diff --git a/opennurbs_subd_frommesh.cpp b/opennurbs_subd_frommesh.cpp
index 2aab9faa..459d5bf8 100644
--- a/opennurbs_subd_frommesh.cpp
+++ b/opennurbs_subd_frommesh.cpp
@@ -1977,7 +1977,7 @@ bool ON_NgonBoundaryChecker::IsSimpleNgon(
for (ON_NgonBoundaryComponent* e = (ON_NgonBoundaryComponent * )fspit.FirstElement(); nullptr != e; e = (ON_NgonBoundaryComponent * )fspit.NextElement())
{
if (1 != e->m_face_count)
- continue; // vertex components alwasy have m_face_count = 0;
+ continue; // vertex components always have m_face_count = 0;
// e is a boundary edge
if (bBoundaryIsMarked)
diff --git a/opennurbs_subd_texture.cpp b/opennurbs_subd_texture.cpp
index 5c41c527..40a408cd 100644
--- a/opennurbs_subd_texture.cpp
+++ b/opennurbs_subd_texture.cpp
@@ -743,6 +743,11 @@ void ON_SubD::ClearFragmentTextureCoordinatesTextureSettingsHash() const
Internal_SetFragmentTextureCoordinatesTextureSettingsHash(ON_SHA1_Hash::EmptyContentHash);
}
+static bool SetGridMeshMappingTCs(const unsigned int pointCount, const double* P, const size_t P_stride, const double* N, const size_t N_stride, double* T, const size_t T_stride, const ON_TextureMapping& mapping, const ON_Xform* P_xform, const ON_Xform* N_xform)
+{
+ return false;
+}
+
bool ON_SubD::SetFragmentTextureCoordinates(
const class ON_TextureMapping& mapping,
bool bLazy
@@ -771,8 +776,6 @@ bool ON_SubD::SetFragmentTextureCoordinates(
if (bApplySubDXform)
subd_xform.GetMappingXforms(P_xform, N_xform);
- const bool bApplyUVW = ON_MappingTag::TransformTreatedIsIdentity(&mapping.m_uvw) ? false : true;
-
ON_3dPoint tc;
ON_SubDMeshFragmentIterator frit(*this);
for (const ON_SubDMeshFragment* fragment = frit.FirstFragment(); nullptr != fragment; fragment = frit.NextFragment())
@@ -797,18 +800,19 @@ bool ON_SubD::SetFragmentTextureCoordinates(
const unsigned N_count = fragment->NormalCount();
const double* N = (N_count == P_count) ? fragment->m_N : &ON_3dVector::ZeroVector.x;
const size_t N_stride = (N_count == P_count) ? fragment->m_N_stride : 0;
- for (double* T1 = T + T_stride * T_count; T < T1; T += T_stride, P += P_stride, N += N_stride)
+ if (T_count != P_count || N_count != P_count || !SetGridMeshMappingTCs(P_count, P, P_stride, N, N_stride, T, T_stride, mapping, bApplySubDXform ? &P_xform : nullptr, bApplySubDXform ? &N_xform : nullptr))
{
- const bool ok = bApplySubDXform ?
- mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc, P_xform, N_xform) :
- mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc);
- if(!ok)
- tc = ON_3dPoint::NanPoint;
- else if (bApplyUVW)
- tc = mapping.m_uvw * tc;
- T[0] = tc.x;
- T[1] = tc.y;
- T[2] = tc.z;
+ for (double* T1 = T + T_stride * T_count; T < T1; T += T_stride, P += P_stride, N += N_stride)
+ {
+ const bool ok = bApplySubDXform ?
+ mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc, P_xform, N_xform) :
+ mapping.Evaluate(ON_3dPoint(P), ON_3dVector(N), &tc);
+ if (!ok)
+ tc = ON_3dPoint::NanPoint;
+ T[0] = tc.x;
+ T[1] = tc.y;
+ T[2] = tc.z;
+ }
}
}
@@ -1047,6 +1051,12 @@ bool ON_SubD::HasPerFaceColorsFromPackId() const
+
+void ON_SubD::SetComponentMarkBitsFromSymmetryMotif() const
+{
+ this->ClearComponentMarkBits(true, true, true);
+}
+
void ON_SubD::SetPerFaceColorsFromSymmetryMotif() const
{
if (FaceCount() < 1)
diff --git a/opennurbs_symmetry.cpp b/opennurbs_symmetry.cpp
index a3e1e337..952650ca 100644
--- a/opennurbs_symmetry.cpp
+++ b/opennurbs_symmetry.cpp
@@ -51,7 +51,7 @@ const ON_wString ON_Symmetry::SymmetryTypeToString(ON_Symmetry::Type symmetry_ty
case ON_Symmetry::Type::Reflect:
s = L"Reflect";
break;
- case ON_Symmetry::Type::Rotate:
+ break; case ON_Symmetry::Type::Rotate:
s = L"Rotate";
break;
case ON_Symmetry::Type::ReflectAndRotate:
@@ -75,13 +75,17 @@ ON_Symmetry::Region ON_Symmetry::SymmetryRegionFromUnsigned(unsigned int region_
switch (region_as_unsigned)
{
ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::Unset);
- ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::In);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::AboveReflectionPlane);
ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationAxis);
ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnReflectionPlane);
- ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationZeroPlane);
- ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotationOnePlane);
- ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::InAndOut);
- ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::Out);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::CrossesReflectionPlane);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::BelowReflectionPlane);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OffRotationAxis);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnReflectionHalfPlane);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnSupplementalHalfPlane);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OnRotateAndReflectHalfPlane);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::InsideRotateAndReflect);
+ ON_ENUM_FROM_UNSIGNED_CASE(ON_Symmetry::Region::OutsideRotateAndReflect);
}
ON_ERROR("Invalid region_as_unsigned parameter");
@@ -96,53 +100,53 @@ const ON_wString SymmetryRegionToString(ON_Symmetry::Region r)
case ON_Symmetry::Region::Unset:
return ON_wString(L"Unset");
break;
- case ON_Symmetry::Region::In:
- return ON_wString(L"In");
+ case ON_Symmetry::Region::AboveReflectionPlane:
+ return ON_wString(L"AboveReflectionPlane");
break;
case ON_Symmetry::Region::OnRotationAxis:
return ON_wString(L"OnRotationAxis");
break;
+ case ON_Symmetry::Region::OffRotationAxis:
+ return ON_wString(L"OffRotationAxis");
+ break;
case ON_Symmetry::Region::OnReflectionPlane:
return ON_wString(L"OnReflectionPlane");
break;
- case ON_Symmetry::Region::OnRotationZeroPlane:
- return ON_wString(L"OnRotationZeroPlane");
+ case ON_Symmetry::Region::CrossesReflectionPlane:
+ return ON_wString(L"CrossesReflectionPlane");
break;
- case ON_Symmetry::Region::OnRotationOnePlane:
- return ON_wString(L"OnRotationOnePlane");
+ case ON_Symmetry::Region::BelowReflectionPlane:
+ return ON_wString(L"BelowReflectionPlane");
break;
- case ON_Symmetry::Region::InAndOut:
- return ON_wString(L"InAndOut");
+ case ON_Symmetry::Region::OnReflectionHalfPlane:
+ return ON_wString(L"OnReflectionHalfPlane");
break;
- case ON_Symmetry::Region::Out:
- return ON_wString(L"Out");
+ case ON_Symmetry::Region::OnSupplementalHalfPlane:
+ return ON_wString(L"OnSupplementalHalfPlane");
+ break;
+ case ON_Symmetry::Region::OnRotateAndReflectHalfPlane:
+ return ON_wString(L"OnRotateAndReflectHalfPlane");
+ break;
+ case ON_Symmetry::Region::InsideRotateAndReflect:
+ return ON_wString(L"InsideRotateAndReflect");
+ break;
+ case ON_Symmetry::Region::OutsideRotateAndReflect:
+ return ON_wString(L"OutsideRotateAndReflect");
break;
}
return ON_wString::EmptyString;
}
-bool ON_Symmetry::IsOn(ON_Symmetry::Region r, bool bInAndOutResult)
-{
- const unsigned char max = bInAndOutResult ? static_cast(ON_Symmetry::Region::Out) : static_cast(ON_Symmetry::Region::InAndOut);
- return static_cast(r) > static_cast(ON_Symmetry::Region::In) && static_cast(r) < max;
-}
-
-bool ON_Symmetry::IsInOrOn(ON_Symmetry::Region r, bool bInAndOutResult)
-{
- const unsigned char max = bInAndOutResult ? static_cast(ON_Symmetry::Region::Out) : static_cast(ON_Symmetry::Region::InAndOut);
- return static_cast(r) >= static_cast(ON_Symmetry::Region::In) && static_cast(r) < max;
-}
-
-bool ON_Symmetry::IsNotInOrOn(ON_Symmetry::Region r, bool bInAndOutResult)
-{
- return ON_Symmetry::IsInOrOn(r,!bInAndOutResult) ? false : true;
-}
-
ON_Symmetry::Region ON_Symmetry::PointRegion(ON_3dPoint point, bool bUseCleanupTolerance) const
{
- if (point.IsValid())
+ for(;;)
{
+ if (false == point.IsValid())
+ break;
+
const double tol = bUseCleanupTolerance ? CleanupTolerance() : ON_Symmetry::ZeroTolerance;
+ if (false == ON_IsValid(tol))
+ break;
double h[2];
switch (m_type)
@@ -150,44 +154,73 @@ ON_Symmetry::Region ON_Symmetry::PointRegion(ON_3dPoint point, bool bUseCleanupT
case ON_Symmetry::Type::Reflect:
h[0] = ReflectionPlane().ValueAt(point);
if (h[0] < -tol)
- return ON_Symmetry::Region::Out;
+ return ON_Symmetry::Region::BelowReflectionPlane;
if (h[0] <= tol)
return ON_Symmetry::Region::OnReflectionPlane;
if (h[0] > tol)
- return ON_Symmetry::Region::In;
+ return ON_Symmetry::Region::AboveReflectionPlane;
+
+ // ... else nans
break;
case ON_Symmetry::Type::Rotate:
- h[0] = RotationZeroPlane().ValueAt(point);
- if (h[0] < -tol)
- return ON_Symmetry::Region::Out;
- h[1] = RotationOnePlane().ValueAt(point);
- if (h[1] < -tol)
- return ON_Symmetry::Region::Out;
- if (h[0] >= -tol && h[1] >= -tol)
- {
- // h[0] and h[1] are not nans ...
- if (h[0] <= tol)
- return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationAxis : ON_Symmetry::Region::OnRotationZeroPlane;
- return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationOnePlane : ON_Symmetry::Region::In;
- }
- break;
+ {
+ const ON_Line axis = RotationAxis();
+ const ON_3dPoint A = axis.ClosestPointTo(point);
+ const double d = fabs((point - A).MaximumCoordinate());
+ if (d <= tol)
+ return ON_Symmetry::Region::OnRotationAxis;
+ if (d > tol)
+ return ON_Symmetry::Region::OffRotationAxis;
+
+ // ... else nans
+ }
+ break;
case ON_Symmetry::Type::ReflectAndRotate:
- h[0] = ReflectionPlane().ValueAt(point);
- if (h[0] < -tol)
- return ON_Symmetry::Region::Out;
- h[1] = RotationZeroPlane().ValueAt(point);
- if (h[1] < -tol)
- return ON_Symmetry::Region::Out;
- if (h[0] >= -tol && h[1] >= -tol)
+ {
+ const ON_Line axis = RotationAxis();
+ const ON_3dPoint A = axis.ClosestPointTo(point);
+ const double d = fabs((point - A).MaximumCoordinate());
+ if (d <= tol)
+ return ON_Symmetry::Region::OnRotationAxis;
+
+ const ON_PlaneEquation M = ReflectAndRotatePlane(0); // reflection plane
+ const ON_PlaneEquation S = ReflectAndRotatePlane(1); // supplental plane
+ h[0] = M.ValueAt(point);
+ h[1] = S.ValueAt(point);
+ if (h[0] > tol && h[1] > tol)
+ return ON_Symmetry::Region::InsideRotateAndReflect;
+ if (fabs(h[0]) <= tol && fabs(h[0]) <= fabs(h[1]))
{
- // h[0] and h[1] are not nans ...
- if (h[0] <= tol)
- return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationAxis : ON_Symmetry::Region::OnReflectionPlane;
- return (h[1] <= tol) ? ON_Symmetry::Region::OnRotationZeroPlane : ON_Symmetry::Region::In;
+ return (h[1] >= 0.0) ? ON_Symmetry::Region::OnReflectionHalfPlane : ON_Symmetry::Region::OnRotateAndReflectHalfPlane;
}
- break;
+ if (fabs(h[1]) <= tol)
+ {
+ return (h[0] >= 0.0) ? ON_Symmetry::Region::OnSupplementalHalfPlane : ON_Symmetry::Region::OnRotateAndReflectHalfPlane;
+ }
+
+ if (h[0] == h[0] && h[1] == h[1])
+ {
+ const ON_Xform R = this->RotationTransformation();
+ const unsigned Rcount = this->RotationCount();
+ for (unsigned i = 0; i < Rcount; ++i)
+ {
+ point = R * point;
+ h[0] = S.ValueAt(point);
+ if (fabs(h[0]) <= tol)
+ return ON_Symmetry::Region::OnRotateAndReflectHalfPlane;
+ h[0] = M.ValueAt(point);
+ if (fabs(h[0]) <= tol)
+ return ON_Symmetry::Region::OnRotateAndReflectHalfPlane;
+ }
+ return ON_Symmetry::Region::OutsideRotateAndReflect;
+ }
+
+
+ // ... else nans
+ }
+ break;
}
}
@@ -195,6 +228,16 @@ ON_Symmetry::Region ON_Symmetry::PointRegion(ON_3dPoint point, bool bUseCleanupT
return ON_Symmetry::Region::Unset;
}
+bool ON_Symmetry::SymmetryRegionHasFixedPoints(ON_Symmetry::Region symmetry_region)
+{
+ return
+ ON_Symmetry::Region::OnRotationAxis == symmetry_region
+ || ON_Symmetry::Region::OnReflectionPlane == symmetry_region
+ || ON_Symmetry::Region::OnReflectionHalfPlane == symmetry_region
+ || ON_Symmetry::Region::OnSupplementalHalfPlane == symmetry_region
+ || ON_Symmetry::Region::OnRotateAndReflectHalfPlane == symmetry_region
+ ;
+}
ON_Symmetry::Coordinates ON_Symmetry::SymmetryCoordinatesFromUnsigned(unsigned int symmetry_coordinates_as_unsigned)
{
@@ -254,7 +297,7 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const
if (false == archive.WriteUuid(m_id))
break;
- if (archive.BeginWrite3dmAnonymousChunk(2))
+ if (archive.BeginWrite3dmAnonymousChunk(3))
{
switch (m_type)
{
@@ -264,8 +307,9 @@ bool ON_Symmetry::Write(ON_BinaryArchive& archive) const
rc = archive.WritePlaneEquation(m_fixed_plane);
break;
case ON_Symmetry::Type::Rotate:
- // fixed plane added for chunk version >= 2
- rc = archive.WriteLine(m_rotation_axis) && archive.WritePlaneEquation(m_fixed_plane);
+ // The plane is written but not read so the files saved afeter June 15, 2021
+ // can be read by code compiled before June 15, 2021.
+ rc = archive.WriteLine(m_rotation_axis) && archive.WritePlaneEquation(ON_PlaneEquation::NanPlaneEquation);
break;
case ON_Symmetry::Type::ReflectAndRotate:
rc = archive.WritePlaneEquation(m_fixed_plane) && archive.WriteLine(m_rotation_axis);
@@ -424,51 +468,202 @@ bool ON_Symmetry::IsMotifBoundarySubDVertex(const class ON_SubDVertex* v, bool b
return false;
if (false == v->IsCreaseOrCorner())
return false;
+
+ const ON_Symmetry::Type symmetry_type = this->SymmetryType();
if (false == v->HasBoundaryVertexTopology())
{
- const ON_Symmetry::Type symmetry_type = this->SymmetryType();
- if (ON_Symmetry::Type::Reflect == symmetry_type)
- return false; // easy case.
- else if (ON_Symmetry::Type::Rotate == symmetry_type)
+ switch (symmetry_type)
{
+ case ON_Symmetry::Type::Reflect:
+ return false; // easy case.
+ break;
+
+ case ON_Symmetry::Type::Rotate:
if (v->HasInteriorVertexTopology())
return false;
if (ON_SubDVertexTag::Corner != v->m_vertex_tag)
return false;
- // We have to keep "funky" corners - RH-63789
- }
- else if (ON_Symmetry::Type::ReflectAndRotate == symmetry_type)
- {
- // Probably much harder than this - future work
- return false; // easy case.
- }
- else
+ // We have to keep nonmanifold corners - RH-63789
+ break;
+
+ case ON_Symmetry::Type::ReflectAndRotate:
+ return false;
+
+ default:
return false;
+ break;
+ }
}
const ON_3dPoint P = v->ControlNetPoint();
const double tol = bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance;
- double d;
- switch (this->SymmetryType())
+
+ switch (symmetry_type)
{
case ON_Symmetry::Type::Reflect:
- d = fabs(this->ReflectionPlane().ValueAt(P));
- break;
+ {
+ const double d = fabs(this->ReflectionPlane().ValueAt(P));
+ return (d <= tol);
+ }
+ break;
+
case ON_Symmetry::Type::Rotate:
// All boundary vertices must be eligable for joining.
- // The pinwheel motifs in RH-63376 show why.
- d = P.IsValid() ? 0.0 : ON_DBL_QNAN;
+ // The pinwheel motifs in RH-63376 shows why.
+ return true;
break;
+
case ON_Symmetry::Type::ReflectAndRotate:
- // to be determined when ReflectAndRotate support is added
- d = ON_DBL_QNAN;
+ // The ReflectAndRotatePlane(2) test makes sure we are in the correct half space
+ // where the ReflectAndRotate reflection plane is active.
+ if ( this->ReflectAndRotatePlane(2).ValueAt(P) >= -tol )
+ {
+ // The d0 test is correct for certain
+ const double d0 = fabs(this->ReflectAndRotatePlane(0).ValueAt(P));
+ if (d0 <= tol)
+ return true;
+
+ // The d1 test is probably correct
+ const double d1 = fabs(this->ReflectAndRotatePlane(1).ValueAt(P));
+ if (d1 <= tol)
+ return true;
+ }
break;
+
default:
- d = ON_DBL_QNAN;
break;
}
- return (d <= tol);
+ return false;
+}
+
+bool ON_Symmetry::IsFixedSubDComponent(
+ const class ON_SubDComponentPtr& subd_component,
+ bool bUseCleanupTolerance
+) const
+{
+ const unsigned motif_count = this->MotifCount();
+ if (motif_count < 2 || motif_count > ON_Symmetry::MaximumOrder)
+ return false;
+
+ const ON_SubDVertex* v = nullptr;
+ const ON_SubDEdge* e = nullptr;
+ const ON_SubDFace* f = nullptr;
+ ON_3dPoint C = ON_3dPoint::NanPoint;
+
+ switch (subd_component.ComponentType())
+ {
+ case ON_SubDComponentPtr::Type::Vertex:
+ v = subd_component.Vertex();
+ if (nullptr != v)
+ C = v->ControlNetPoint();
+ break;
+
+ case ON_SubDComponentPtr::Type::Edge:
+ e = subd_component.Edge();
+ if (nullptr != e)
+ C = e->ControlNetCenterPoint();
+ break;
+
+ case ON_SubDComponentPtr::Type::Face:
+ f = subd_component.Face();
+ if (nullptr != f)
+ C = f->ControlNetCenterPoint();
+ break;
+
+ default:
+ break;
+ }
+
+ if (false == C.IsValid())
+ return false;
+
+ const double dtol = bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance;
+ // quick test of center point
+ double d = C.DistanceTo(this->MotifTransformation(1) * C);
+ if (false == (d <= dtol))
+ return false;
+
+ ON_SimpleArray P(nullptr != f ? f->EdgeCount() : 2U);
+ if (nullptr != f)
+ {
+ for (unsigned short fvi = 0; fvi < f->m_edge_count; ++fvi)
+ P.Append(f->ControlNetPoint(fvi));
+ }
+ else if (nullptr != e)
+ {
+ P.Append(e->ControlNetPoint(0));
+ P.Append(e->ControlNetPoint(1));
+ }
+ else if (nullptr != v)
+ {
+ P.Append(C);
+ }
+
+ const unsigned Pcount = P.UnsignedCount();
+ ON_SimpleArray hit_array(Pcount);
+ hit_array.SetCount(Pcount);
+ bool* bHit = hit_array.Array();
+
+ for (unsigned j = 1; j < motif_count; ++j)
+ {
+ const ON_Xform& motif_xform = this->MotifTransformation(j);
+ hit_array.Zero();
+ unsigned hit_count = 0;
+ for (unsigned i = 0; i < Pcount; ++i)
+ {
+ const ON_3dPoint Q = motif_xform * P[i];
+ for (unsigned k = 0; k < Pcount; ++k)
+ {
+ if (false == bHit[k])
+ {
+ d = Q.DistanceTo(P[k]);
+ if (d <= dtol)
+ {
+ bHit[k] = true;
+ ++hit_count;
+ }
+ }
+ }
+ }
+ if (hit_count != Pcount)
+ return false;
+ }
+
+ return true;
+}
+
+bool ON_Symmetry::IsValidRotationAxis(
+ ON_Line rotation_axis,
+ unsigned int rotation_count
+)
+{
+ for (;;)
+ {
+ if (rotation_count < 2)
+ break;
+ if (rotation_count > ON_Symmetry::MaximumOrder)
+ break;
+ if (false == rotation_axis.IsValid())
+ break;
+ if (false == (rotation_axis.Length() > ON_Symmetry::ZeroTolerance))
+ break;
+
+ // Make sure a unitized axis can be reliably used for evaluations.
+ const ON_Line unit_axis(rotation_axis.from, rotation_axis.from + rotation_axis.Tangent());
+ if (false == unit_axis.IsValid())
+ break;
+ const double d0 = unit_axis.DistanceTo(rotation_axis.to);
+ if (false == (d0 <= ON_Symmetry::ZeroTolerance))
+ break;
+ const double d1 = rotation_axis.DistanceTo(unit_axis.to);
+ if (false == (d1 <= ON_Symmetry::ZeroTolerance))
+ break;
+
+ return true;
+ }
+
+ return false;
}
bool ON_Symmetry::IsValidRotationAxisAndFixedPlane(
@@ -680,17 +875,16 @@ static bool Internal_InventSymmetryFixedPlane(const double zero_tolerance, ON_Xf
return false;
}
-static bool Internal_InventRotationFixedPlane(const double zero_tolerance, const ON_Line rotation_axis, unsigned cyclic_order, ON_PlaneEquation& fixed_plane)
-{
- const ON_3dPoint F[2] = { rotation_axis.from, rotation_axis.to };
- const ON_3dVector N = rotation_axis.Tangent().Perpendicular(ON_3dVector::NanVector);
- return Internal_CreateAndValidateFixedPlane(F[0], N, fixed_plane, zero_tolerance, 2, F);
-}
+////static bool Internal_InventRotationFixedPlane(const double zero_tolerance, const ON_Line rotation_axis, unsigned cyclic_order, ON_PlaneEquation& fixed_plane)
+////{
+//// const ON_3dPoint F[2] = { rotation_axis.from, rotation_axis.to };
+//// const ON_3dVector N = rotation_axis.Tangent().Perpendicular(ON_3dVector::NanVector);
+//// return Internal_CreateAndValidateFixedPlane(F[0], N, fixed_plane, zero_tolerance, 2, F);
+////}
bool ON_Symmetry::Read(ON_BinaryArchive& archive)
{
*this = ON_Symmetry::Unset;
-
int chunk_version = 0;
if (false == archive.BeginRead3dmAnonymousChunk(&chunk_version))
return false;
@@ -709,9 +903,21 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive)
{
if (chunk_version <= 0)
break;
+
unsigned char utype = 0;
if (false == archive.ReadChar(&utype))
break;
+
+ bool bNewRotatePrototype = false;
+ if (113 == utype)
+ {
+ // There was a period of time in June 2021 when ON_Symmetry::Type::NewRotate = 113
+ // was used to prototype a replacement for an earlier version of rotate
+ // that did not work well. The protyped replacement worked much better
+ // and became the standard rotate on June 15, 2021.
+ bNewRotatePrototype = true;
+ utype = static_cast(ON_Symmetry::Type::Rotate);
+ }
symmetry_type = ON_Symmetry::SymmetryTypeFromUnsigned(utype);
if (ON_Symmetry::Type::Unset == symmetry_type)
{
@@ -719,7 +925,6 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive)
break;
}
-
if (false == archive.ReadInt(&inversion_order))
break;
if (false == archive.ReadInt(&cyclic_order))
@@ -727,6 +932,9 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive)
if (false == archive.ReadUuid(symmetry_id))
break;
+ // Before June 1, 2021: Chunk verson 2 for rotations that had a rotation plane.
+ // June 1, 2021: Chunk version 3 for symmetry_type=113 prototyping rotations with no rotation plane
+ // Future: Chunk version 4 for final rotations not using a rotation plane.
int inner_chunk_version = 0;
if (false == archive.BeginRead3dmAnonymousChunk(&inner_chunk_version))
break;
@@ -748,12 +956,13 @@ bool ON_Symmetry::Read(ON_BinaryArchive& archive)
case ON_Symmetry::Type::Rotate:
rc = archive.ReadLine(rotation_axis);
- if (inner_chunk_version >= 2)
+ if (inner_chunk_version >= 2 && false == bNewRotatePrototype)
rc = archive.ReadPlaneEquation(fixed_plane);
- else
- rc = Internal_InventRotationFixedPlane(ON_Symmetry::ZeroTolerance, rotation_axis, cyclic_order, fixed_plane);
if (rc)
- symmetry = ON_Symmetry::CreateRotateSymmetry(rotation_axis, cyclic_order, fixed_plane, symmetry_coordinates);
+ {
+ // the fixed plane is intentionally ignored.
+ symmetry = ON_Symmetry::CreateRotateSymmetry(rotation_axis, cyclic_order, symmetry_coordinates);
+ }
break;
case ON_Symmetry::Type::ReflectAndRotate:
@@ -982,6 +1191,7 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c
}
break;
+
case ON_Symmetry::Type::Rotate:
{
if (false == m_rotation_axis.IsValid())
@@ -990,13 +1200,7 @@ const ON_Symmetry ON_Symmetry::TransformUnconditionally(const ON_Xform& xform) c
a.Transform(xform);
if (false == a.IsValid())
break;
- if (false == m_fixed_plane.IsValid())
- break;
- ON_PlaneEquation e = m_fixed_plane;
- e.Transform(xform);
- if (false == e.IsValid())
- break;
- return ON_Symmetry::CreateRotateSymmetry(a, RotationCount(), e, m_coordinates);
+ return ON_Symmetry::CreateRotateSymmetry(a, RotationCount(), m_coordinates);
}
break;
@@ -1157,15 +1361,6 @@ int ON_Symmetry::CompareSymmetryTransformation(const ON_Symmetry* lhs, const ON_
return ON_Symmetry::Compare(lhs, rhs);
}
-const ON_Symmetry ON_Symmetry::CreateInversionSymmetry(
- ON_UUID symmetry_id,
- ON_Xform inversion_transform,
- ON_Symmetry::Coordinates symmetry_coordinates
-)
-{
- ON_ERROR("Use CreateRotationSymmetry(axis,2,...) or CreateReflectionSymmetry()");
- return ON_Symmetry::Unset;
-}
const ON_PlaneEquation ON_Symmetry::Internal_UnitizePlaneEquationParameter(ON_PlaneEquation e)
{
@@ -1224,16 +1419,78 @@ const ON_Symmetry ON_Symmetry::Internal_CreateInversionSymmetry(
return ON_Symmetry::Unset;
}
-
-
-const ON_Symmetry ON_Symmetry::CreateCyclicSymmetry(
+const ON_Symmetry ON_Symmetry::Internal_CreateCyclicSymmetry(
ON_UUID symmetry_id,
ON_Xform cyclic_transform,
unsigned int cyclic_order,
+ ON_Line fixed_line,
ON_Symmetry::Coordinates symmetry_coordinates
)
{
- ON_ERROR("Use CreateRotationSymmetry() or CreateReflectionSymmetry()");
+ for (;;)
+ {
+ if (false == fixed_line.IsValid())
+ break;
+
+ const ON_Line unit_axis(fixed_line.from, fixed_line.from + fixed_line.Tangent());
+ if (false == unit_axis.IsValid())
+ break;
+
+ if (false == ON_Symmetry::IsValidCyclicTranformation(cyclic_transform, cyclic_order))
+ break;
+
+ const double det = cyclic_transform.Determinant();
+ if (2 == cyclic_order || 1 == (cyclic_order % 2))
+ {
+ if (false == (det > 0.0))
+ break;
+ }
+ else
+ {
+ if (false == (det != 0.0))
+ break;
+ }
+
+ if (false == (ON_nil_uuid == symmetry_id))
+ {
+ // prohibit using built-in ids
+ if (ON_Symmetry::ReflectId == symmetry_id)
+ break;
+ if (ON_Symmetry::RotateId == symmetry_id)
+ break;
+ if (ON_Symmetry::ReflectAndRotateId == symmetry_id)
+ break;
+ }
+
+
+ // Make sure cyclic_transform fixes the axis.
+ const ON_3dPoint P[3] = { fixed_line.from, fixed_line.to, unit_axis.to };
+ const unsigned Pcount = (unsigned)(sizeof(P) / sizeof(P[0]));
+ ON_Xform T = cyclic_transform;
+ for (unsigned int i = 0; i < cyclic_order; ++i)
+ {
+ for (unsigned j = 0; j < Pcount; ++j)
+ {
+ const ON_3dPoint Q = T * P[j];
+ const double d = P[j].DistanceTo(Q);
+ if (false == (d <= ON_Symmetry::ZeroTolerance))
+ return ON_Symmetry::Unset;
+ }
+ T = T * cyclic_transform;
+ }
+
+ ON_Symmetry symmetry;
+ symmetry.m_type = ON_Symmetry::Type::Cyclic;
+ symmetry.m_coordinates = symmetry_coordinates;
+ symmetry.m_inversion_order = 1;
+ symmetry.m_cyclic_order = cyclic_order;
+ symmetry.m_id = symmetry_id;
+ symmetry.m_inversion_transform = ON_Xform::IdentityTransformation;
+ symmetry.m_cyclic_transform = cyclic_transform;
+ symmetry.m_fixed_plane = ON_PlaneEquation::NanPlaneEquation;
+ return symmetry;
+ }
+
return ON_Symmetry::Unset;
}
@@ -1323,26 +1580,13 @@ const ON_Symmetry ON_Symmetry::CreateRotateSymmetry(
unsigned int rotation_count,
ON_Symmetry::Coordinates symmetry_coordinates
)
-{
- // You must use the version that has a rotation_plane parameter.
- ON_ERROR("Obsolete function.");
- return ON_Symmetry::Unset;
-}
-
-const ON_Symmetry ON_Symmetry::CreateRotateSymmetry(
- ON_Line rotation_axis,
- unsigned int rotation_count,
- ON_PlaneEquation zero_rotation_plane,
- ON_Symmetry::Coordinates symmetry_coordinates
-)
{
for (;;)
{
- zero_rotation_plane = ON_Symmetry::Internal_UnitizePlaneEquationParameter(zero_rotation_plane);
- if (false == ON_Symmetry::IsValidRotationAxisAndFixedPlane(rotation_axis, rotation_count, zero_rotation_plane))
+ if (false == ON_Symmetry::IsValidRotationAxis(rotation_axis, rotation_count))
break;
const ON_Xform R = Internal_RotationXform(rotation_axis, 1, rotation_count);
- ON_Symmetry symmetry = ON_Symmetry::Internal_CreateCyclicSymmetry(ON_nil_uuid, R, rotation_count, zero_rotation_plane, symmetry_coordinates);
+ ON_Symmetry symmetry = ON_Symmetry::Internal_CreateCyclicSymmetry(ON_nil_uuid, R, rotation_count, rotation_axis, symmetry_coordinates);
if (ON_Symmetry::Type::Cyclic != symmetry.m_type)
break;
symmetry.m_type = ON_Symmetry::Type::Rotate;
@@ -1353,7 +1597,6 @@ const ON_Symmetry ON_Symmetry::CreateRotateSymmetry(
return ON_Symmetry::Unset;
}
-
const ON_Symmetry ON_Symmetry::CreateReflectAndRotateSymmetry(
ON_PlaneEquation reflection_plane,
ON_Line rotation_axis,
@@ -1369,7 +1612,7 @@ const ON_Symmetry ON_Symmetry::CreateReflectAndRotateSymmetry(
if (ON_Symmetry::Type::Reflect != reflection.SymmetryType())
break;
- const ON_Symmetry rotation = CreateRotateSymmetry(rotation_axis,rotation_count, reflection_plane, symmetry_coordinates);
+ const ON_Symmetry rotation = CreateRotateSymmetry(rotation_axis,rotation_count, symmetry_coordinates);
if (ON_Symmetry::Type::Rotate != rotation.SymmetryType())
break;
@@ -1453,25 +1696,21 @@ int ON_Symmetry::Compare(const ON_Symmetry* lhs, const ON_Symmetry* rhs)
if (0U == lhs->m_inversion_order || 0U == lhs->m_cyclic_order)
return 0;
- if (ON_Symmetry::Type::Unset != lhs->m_type)
+ if (lhs->Internal_RequiresFixedPlane() || rhs->Internal_RequiresFixedPlane())
{
const int rc = ON_Symmetry::Internal_CompareDouble(&lhs->m_fixed_plane.x, &rhs->m_fixed_plane.x, 4);
if (0 != rc)
return rc;
}
- if (ON_Symmetry::Type::Rotate == lhs->m_type || ON_Symmetry::Type::ReflectAndRotate == lhs->m_type)
+ if (lhs->Internal_RequiresRotationAxis() || rhs->Internal_RequiresRotationAxis())
{
const int rc = ON_Symmetry::Internal_CompareDouble(&lhs->m_rotation_axis.from.x, &rhs->m_rotation_axis.from.x, 6);
if (0 != rc)
return rc;
}
- if (
- ON_Symmetry::Type::Reflect == lhs->m_type
- || ON_Symmetry::Type::Rotate == lhs->m_type
- || ON_Symmetry::Type::ReflectAndRotate == lhs->m_type
- )
+ if (lhs->Internal_IsStandardType() && rhs->Internal_IsStandardType())
return 0;
if (lhs->m_inversion_order > 1)
@@ -1498,6 +1737,21 @@ ON_Symmetry::Type ON_Symmetry::SymmetryType() const
return m_type;
}
+bool ON_Symmetry::IsRotate() const
+{
+ return ON_Symmetry::Type::Rotate == m_type;
+}
+
+bool ON_Symmetry::IsReflect() const
+{
+ return ON_Symmetry::Type::Reflect == m_type;
+}
+
+bool ON_Symmetry::IsReflectAndRotate() const
+{
+ return ON_Symmetry::Type::ReflectAndRotate == m_type;
+}
+
ON_Symmetry::Coordinates ON_Symmetry::SymmetryCoordinates() const
{
return m_coordinates;
@@ -1550,23 +1804,11 @@ const ON_Xform ON_Symmetry::InversionTransformation() const
return IsSet() ? m_inversion_transform : ON_Xform::Nan;
}
-const ON_Xform ON_Symmetry::InversionTransform() const
-{
- // OBSOLETE use ON_Symmetry::InversionTransformation()
- return ON_Xform::Nan;
-}
-
const ON_Xform ON_Symmetry::CyclicTransformation() const
{
return IsSet() ? m_cyclic_transform : ON_Xform::Nan;
}
-const ON_Xform ON_Symmetry::CyclicTransform() const
-{
- // OBSOLETE use ON_Symmetry::CyclicTransformation()
- return ON_Xform::Nan;
-}
-
const ON_SHA1_Hash ON_Symmetry::Hash() const
{
for(;;)
@@ -1585,13 +1827,13 @@ const ON_SHA1_Hash ON_Symmetry::Hash() const
sha1.AccumulateInteger32(InversionOrder());
sha1.AccumulateInteger32(CyclicOrder());
- if (ON_Symmetry::Type::Unset != m_type)
+ if ( this->Internal_RequiresFixedPlane())
sha1.AccumulateDoubleArray(4, &m_fixed_plane.x);
- if (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ if ( this->Internal_RequiresRotationAxis() )
sha1.AccumulateDoubleArray(6, &m_rotation_axis.from.x);
- if (ON_Symmetry::Type::Reflect != m_type && ON_Symmetry::Type::Rotate != m_type && ON_Symmetry::Type::ReflectAndRotate != m_type)
+ if ( false == this->Internal_IsStandardType() )
{
if (InversionOrder() > 1)
sha1.AccumulateDoubleArray(16, &m_inversion_transform.m_xform[0][0]);
@@ -1611,6 +1853,65 @@ const ON_PlaneEquation ON_Symmetry::ReflectionPlane() const
: ON_PlaneEquation::NanPlaneEquation;
}
+const ON_PlaneEquation ON_Symmetry::ReflectAndRotatePlane(
+ unsigned int plane_index
+) const
+{
+ if ( IsSet() && ON_Symmetry::Type::ReflectAndRotate == m_type || plane_index >= 0 && plane_index <= 2)
+ {
+ const ON_PlaneEquation fixed_plane = FixedPlane();
+ if (0 == plane_index)
+ return fixed_plane;
+
+ // 2nd plane
+ ON_Xform R;
+ const double a = (1 == plane_index) ? -0.5 * RotationAngleRadians() : 0.5 * ON_PI;
+ R.Rotation(a, RotationAxisTangent(), RotationAxisPoint());
+ ON_PlaneEquation e = fixed_plane;
+ e.Transform(R);
+ return (1 == plane_index) ? e.NegatedPlaneEquation() : e;
+ }
+
+ return ON_PlaneEquation::NanPlaneEquation;
+}
+
+bool ON_Symmetry::OnReflectAndRotateFixedPlane( ON_3dPoint P, bool bUseCleanupTolerance) const
+{
+ if (ON_Symmetry::Type::ReflectAndRotate != m_type)
+ return false;
+ if (false == P.IsValid())
+ return false;
+ const double tol = this->Tolerance(bUseCleanupTolerance);
+
+ const ON_PlaneEquation M = this->ReflectAndRotatePlane(0);
+ double h = M.ValueAt(P);
+ if (fabs(h) <= tol)
+ return true;
+
+ const ON_PlaneEquation S = this->ReflectAndRotatePlane(1);
+ h = S.ValueAt(P);
+ if (fabs(h) <= tol)
+ return true;
+
+ const unsigned Rcount = this->RotationCount();
+ if (Rcount > 2)
+ {
+ const ON_Xform R = this->RotationTransformation();
+ for (unsigned i = 1; i < Rcount; ++i)
+ {
+ P = R * P;
+ h = S.ValueAt(P);
+ if (fabs(h) <= tol)
+ return true;
+ h = M.ValueAt(P);
+ if (fabs(h) <= tol)
+ return true;
+ }
+ }
+
+ return false;
+}
+
const ON_Xform ON_Symmetry::ReflectionTransformation() const
{
return (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
@@ -1625,97 +1926,74 @@ const ON_PlaneEquation ON_Symmetry::FixedPlane() const
: ON_PlaneEquation::NanPlaneEquation;
}
+bool ON_Symmetry::Internal_RequiresRotationAxis() const
+{
+ return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type);
+}
+
+bool ON_Symmetry::Internal_RequiresFixedPlane() const
+{
+ return (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type);
+}
+
+bool ON_Symmetry::Internal_IsStandardType() const
+{
+ return (ON_Symmetry::Type::Reflect == m_type || ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type);
+}
+
const ON_Line ON_Symmetry::RotationAxis() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? m_rotation_axis
: ON_Line::NanLine;
}
const ON_3dPoint ON_Symmetry::RotationAxisPoint() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? m_rotation_axis.from
: ON_3dPoint::NanPoint;
}
const ON_3dVector ON_Symmetry::RotationAxisDirection() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? m_rotation_axis.Direction()
: ON_3dVector::NanVector;
}
const ON_3dVector ON_Symmetry::RotationAxisTangent() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? m_rotation_axis.Tangent()
: ON_3dVector::NanVector;
}
unsigned int ON_Symmetry::RotationCount() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? m_cyclic_order
: 0U;
}
double ON_Symmetry::RotationAngleDegrees() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? (360.0 / ((double)RotationCount()))
: ON_DBL_QNAN;
}
double ON_Symmetry::RotationAngleRadians() const
{
- return (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
+ return (Internal_RequiresRotationAxis())
? ((2.0*ON_PI) / ((double)RotationCount()))
: ON_DBL_QNAN;
}
-const ON_PlaneEquation ON_Symmetry::RotationZeroPlane() const
-{
- if (ON_Symmetry::Type::Rotate == m_type)
- {
- return this->m_fixed_plane;
- }
- if (ON_Symmetry::Type::ReflectAndRotate == m_type)
- {
- const double a = RotationAngleRadians();
- if (a > 0.0 && a <= ON_PI)
- {
- ON_Xform rot;
- rot.Rotation(-0.5*a, this->RotationAxisDirection(), this->RotationAxisPoint());
- ON_PlaneEquation e(this->m_fixed_plane);
- e.Transform(rot);
- return e.NegatedPlaneEquation();
- }
- }
- return ON_PlaneEquation::NanPlaneEquation;
-}
-
-
-const ON_PlaneEquation ON_Symmetry::RotationOnePlane() const
-{
- if (ON_Symmetry::Type::Rotate == m_type || ON_Symmetry::Type::ReflectAndRotate == m_type)
- {
- const ON_PlaneEquation r0 = RotationZeroPlane();
- if (r0.IsSet())
- {
- const ON_PlaneEquation r1 = (RotationTransformation() * r0).NegatedPlaneEquation();
- if (r1.IsSet())
- return r1;
- }
- }
-
- return ON_PlaneEquation::NanPlaneEquation;
-}
-
const ON_Xform ON_Symmetry::RotationTransformation() const
{
- if (ON_Symmetry::Type::Rotate == m_type)
+ if ( ON_Symmetry::Type::Rotate == m_type )
{
return MotifTransformation(1);
}
@@ -1769,6 +2047,12 @@ const ON_Xform ON_Symmetry::Internal_RotationXform(
sin_angle = 0.0;
cos_angle = -1.0;
}
+ else if (3 * rotation_index == rotation_count)
+ {
+ // angle = 2pi/3
+ sin_angle = 0.5*sqrt(3.0);
+ cos_angle = -0.5;
+ }
else if (4 * rotation_index == rotation_count)
{
// angle = pi/2
@@ -1897,11 +2181,6 @@ const ON_Xform ON_Symmetry::Internal_ReflectAndRotateTransformation(unsigned ind
return r;
}
-ON_SHA1_Hash ON_Symmetry::Sha1Hash() const
-{
- return SymmetryHash();
-}
-
const ON_SHA1_Hash ON_Symmetry::SymmetryHash() const
{
ON_SHA1 sha1;
@@ -1918,14 +2197,6 @@ const ON_SHA1_Hash ON_Symmetry::SymmetryHash() const
return sha1.Hash();
}
-void ON_Symmetry::SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const
-{
- // OBSOLETE - You must use SetSymmetricObject().
- ON_ERROR("Obsolete function.");
-
- ClearSymmetricObject(); // strongly discourage use by erasing all SymmetricContent settings.
-}
-
void ON_Symmetry::ClearSymmetricObject() const
{
m_symmetric_object_content_serial_number = 0U;
@@ -1933,18 +2204,6 @@ void ON_Symmetry::ClearSymmetricObject() const
m_symmetric_object_topology_hash = ON_SHA1_Hash::ZeroDigest;
}
-void ON_Symmetry::ClearSymmetricObjectContentSerialNumber() const
-{
- ClearSymmetricObject();
-}
-
-ON__UINT64 ON_Symmetry::SymmetricObjectContentSerialNumber() const
-{
- // OBSOLETE - You must use SameSymmetricObjectGeometry().
- ON_ERROR("Obsolete function.");
- return 0;
-}
-
void ON_Symmetry::SetCleanupTolerance(
double cleanup_tolerance
)
@@ -1964,3 +2223,8 @@ double ON_Symmetry::CleanupTolerance() const
// read from old archives.
return (m_cleanup_tolerance >= ON_Symmetry::ZeroTolerance) ? m_cleanup_tolerance : ON_Symmetry::ZeroTolerance;
}
+
+double ON_Symmetry::Tolerance(bool bUseCleanupTolerance) const
+{
+ return bUseCleanupTolerance ? CleanupTolerance() : ON_Symmetry::ZeroTolerance;
+}
diff --git a/opennurbs_symmetry.h b/opennurbs_symmetry.h
index f46e259f..a988effd 100644
--- a/opennurbs_symmetry.h
+++ b/opennurbs_symmetry.h
@@ -37,6 +37,11 @@ public:
MaximumOrder = 4096
};
+
+ // NOTE never use the value 113 for a new ON_Symmetry::Type.
+ // This value was used breifly in June 2021 for a prototype symmetry type
+ // and reusing it will break SHA1 hashes and 3dm archive IO.
+
enum class Type : unsigned char
{
Unset = 0,
@@ -50,8 +55,8 @@ public:
///
/// Rotation around an axis.
- /// The symmetric object has reflection copies of the motif.
- /// Points on the axis are fixed.
+ /// The symmetric object has rotated copies of the motif.
+ /// Points on the rotation axis are fixed.
///
Rotate = 2,
@@ -78,7 +83,7 @@ public:
/// When N is even and greater than 2, Det(cyclic transformation) = 1 or -1.
/// The symmetric object has N copies of the motif.
///
- Cyclic = 5,
+ Cyclic = 5
};
static ON_Symmetry::Type SymmetryTypeFromUnsigned(unsigned int symmetry_type_as_unsigned);
@@ -118,81 +123,69 @@ public:
Unset = 0,
///
- /// Inside the primary motif region.
+ /// Above the reflection plane and inside the primary reflect motif region.
+ /// Applies to Reflect symmetries.
///
- In = 1,
+ AboveReflectionPlane = 1,
///
- /// On the rotation axis. Applies to Reflect and ReflectAndRotate symmetries.
+ /// On the reflection plane. Applies to Reflect symmetries.
///
- OnRotationAxis = 2,
+ OnReflectionPlane = 2,
///
- /// On the reflection plane. Applies to Reflect and ReflectAndRotate symmetries.
+ /// Below the reflection plane and outside the primary reflect motif region.
+ /// Applies to Reflect symmetries.
///
- OnReflectionPlane = 3,
+ BelowReflectionPlane = 3,
///
- /// On the zero angle plane. Applies to Reflect and ReflectAndRotate symmetries.
+ /// Something like a line segment that has points above and below the reflection plane.
///
- OnRotationZeroPlane = 4,
+ CrossesReflectionPlane = 4,
///
- /// On the the rotation of the zero angle plane. Applies to Rotate symmetries.
+ /// On the rotation axis. Applies to Rotate and ReflectAndRotate symmetries.
///
- OnRotationOnePlane = 5,
+ OnRotationAxis = 5,
///
- /// Something like a line segment that has points both inside and outside the primary motif region.
+ /// Off of (not on) the rotation axis. Applies to Rotate symmetries.
///
- InAndOut = 6,
+ OffRotationAxis = 6,
///
- /// Outside the primary motif region.
+ /// On the reflection half plane. Applies to ReflectAndRotate symmetries.
///
- Out = 7
+ OnReflectionHalfPlane = 7,
+
+ ///
+ /// On the supplemental half plane. Applies to ReflectAndRotate symmetries.
+ ///
+ OnSupplementalHalfPlane = 8,
+
+ ///
+ /// The half planes outside the primary motif region that are rotations
+ /// of either the reflection half plane or the supplemental half plane.
+ /// Applies to ReflectAndRotate symmetries.
+ ///
+ OnRotateAndReflectHalfPlane = 10,
+
+ ///
+ /// Above both the reflection plane and the supplemental plane.
+ /// Applies to ReflectAndRotate symmetries.
+ ///
+ InsideRotateAndReflect = 11,
+
+ ///
+ /// Outside the primary motif region and and on one of the fixed point planes
+ /// Applies to ReflectAndRotate symmetries.
+ ///
+ OutsideRotateAndReflect = 12
};
static ON_Symmetry::Region SymmetryRegionFromUnsigned(unsigned int region_as_unsigned);
- static const ON_wString SymmetryRegionToString(ON_Symmetry::Region r);
-
- /*
- Returns:
- (r == ON_Symmetry::Region::OnRotationZeroPlane || r == ON_Symmetry::Region::OnRotationOnePlane).
- */
- static bool SymmetryRegionIsRotationPlane(ON_Symmetry::Region r);
-
- /*
- Paramaters:
- r - [in]
- bInAndOutResult - [in]
- Value to return when ON_Symmetry::Region::InandOut == r.
- Returns:
- True if r is OnRotationAxis, OnReflectionPlane, OnZeroAnglePlane, or OnOneAnglePlane.
- */
- static bool IsOn(ON_Symmetry::Region r, bool bInAndOutResult);
-
- /*
- Paramaters:
- r - [in]
- bInAndOutResult - [in]
- Value to return when ON_Symmetry::Region::InandOut == r.
- Returns:
- ON_Symmetry::Region::In == r || IsOn(r).
- */
- static bool IsInOrOn(ON_Symmetry::Region r, bool bInAndOutResult);
-
- /*
- Paramaters:
- r - [in]
- bInAndOutResult - [in]
- Value to return when ON_Symmetry::Region::InandOut == r.
- Returns:
- !ON_Symmetry::IsInOrOn(r)
- */
- static bool IsNotInOrOn(ON_Symmetry::Region r, bool bInAndOutResult);
-
/*
Parameters:
point - [in]
@@ -206,6 +199,22 @@ public:
*/
ON_Symmetry::Region PointRegion(ON_3dPoint point, bool bUseCleanupTolerance) const;
+ /*
+ Description:
+ A symmetry region has fixed points if one or more motif transforms of the
+ applicable symmetry type fixes points in the region.
+ Points in these regions typically need special processing when the symmetric
+ object is edited.
+ Returns:
+ True if symmetry_region is one of
+ ON_Symmetry::Region::OnRotationAxis,
+ ON_Symmetry::Region::OnReflectionPlane,
+ ON_Symmetry::Region::OnReflectionHalfPlane,
+ ON_Symmetry::Region::OnSupplementalHalfPlane,
+ or ON_Symmetry::Region::OnRotateAndReflectFixedPlane.
+ */
+ static bool SymmetryRegionHasFixedPoints(ON_Symmetry::Region symmetry_region);
+
static ON_Symmetry::Coordinates SymmetryCoordinatesFromUnsigned(unsigned int coordinates_as_unsigned);
static const ON_wString SymmetryCoordinatesToString(
@@ -225,8 +234,23 @@ public:
True if v is a vertex that might possibly be merged when
transformed motifs are joined to create a symmetric SubD.
*/
- bool IsMotifBoundarySubDVertex(const class ON_SubDVertex* v, bool bUseCleanupTolerance) const;
+ bool IsMotifBoundarySubDVertex(
+ const class ON_SubDVertex* v,
+ bool bUseCleanupTolerance
+ ) const;
+ /*
+ Description:
+ Examples of fixed SubD components include vertices on a reflection plane or
+ rotation axis, faces and edges straddling a reflection plane,
+ and faces centered about a rotation axis.
+ Returns:
+ True if the component is transformed to itself by every motif transformation.
+ */
+ bool IsFixedSubDComponent(
+ const class ON_SubDComponentPtr& subd_component,
+ bool bUseCleanupTolerance
+ ) const;
static const ON_UUID ReflectId;
static const ON_UUID RotateId;
@@ -292,10 +316,11 @@ public:
rotation_count - [in]
fixed_plane - [in]
Returns:
- True if these 3 conditions are satisified.
+ True if these 4 conditions are satisified.
1. rotation_axis is a valid line.
- 2. rotation_count > 0
- 3. fixed_plane contains the rotation axis.
+ 2. rotation_count >= 2
+ 3. rotation_count <= ON_Symmetry::MaximumOrder
+ 4. fixed_plane contains the rotation axis.
Remarks:
The value ON_Symmetry::ZeroTolerance is used for all "zero tolerances."
*/
@@ -305,6 +330,24 @@ public:
ON_PlaneEquation fixed_plane
);
+ /*
+ Parameters:
+ rotation_axis - [in]
+ rotation_count - [in]
+ fixed_plane - [in]
+ Returns:
+ True if these 3 conditions are satisified.
+ 1. rotation_axis is a valid line.
+ 2. rotation_count >= 2
+ 3. rotation_count <= ON_Symmetry::MaximumOrder
+ Remarks:
+ The value ON_Symmetry::ZeroTolerance is used for all "zero tolerances."
+ */
+ static bool IsValidRotationAxis(
+ ON_Line rotation_axis,
+ unsigned int rotation_count
+ );
+
/*
Description:
Reflection about a plane.
@@ -327,41 +370,29 @@ public:
ON_Symmetry::Coordinates symmetry_coordinates
);
- ON_DEPRECATED_MSG("Does nothing. Use ON_Symmetry::CreateRotateSymmetry(axis,count,plane,coordinates.")
- static const ON_Symmetry CreateRotateSymmetry(
- ON_Line rotation_axis,
- unsigned int rotation_count,
- ON_Symmetry::Coordinates symmetry_coordinates
- );
-
/*
Description:
Rotation around an axis.
The symmetric object has reflection_count copies of the motif.
- Points on the axis are fixed.
+ Points on the axis are fixed.
Parameters:
rotation_axis - [in]
rotation_count - [in]
rotation_count must be >= 2 and the rotation angle is (360/rotation_count) degrees.
- zero_rotation_plane - [in]
- zero_rotation_plane plane is used to define the primary motif region.
- A point P is in the primary motif when zero_rotation_plane.ValueAt(P) >= 0 && (MotifTransformation(1)*zero_rotation_plane).ValueAt(P) <= 0.
- The rotation axis must be in zero_rotation_plane (zero_rotation_plane.ValueAt(rotation_axis) <= ON_ZERO_TOLERANCE and zero_rotation_plane.ValueAt(rotation_axis) <= ON_ZERO_TOLERANCE).
symmetry_coordinates - [in]
object or world.
Example:
- If the rotation axis is the z-axis and the order is N, then the generator is
+ If the rotation axis is the z-axis and the order is N, then the generator is
then (x,y,z) -> (r*cos(a), r*sin(a), z) -> (r*cos(2*a), r*sin(2*a), z) -> ... -> (x,y,z),
where r = sqrt(x*x + y*y) and a = (360 degrees)/N.
- Any plane containing the z-axis can be used as the zero_rotation_plane.
Remarks:
CyclicTransformation() = rotation.
InversionTransformation() = identity.
*/
+ ////ON_WIP_SDK
static const ON_Symmetry CreateRotateSymmetry(
ON_Line rotation_axis,
unsigned int rotation_count,
- ON_PlaneEquation zero_rotation_plane,
ON_Symmetry::Coordinates symmetry_coordinates
);
@@ -386,6 +417,7 @@ public:
InversionTransformation() = reflection.
CyclicTransformation() = rotation by (360/rotation_count) degrees.
*/
+ ////ON_WIP_SDK
static const ON_Symmetry CreateReflectAndRotateSymmetry(
ON_PlaneEquation reflection_plane,
ON_Line rotation_axis,
@@ -393,21 +425,6 @@ public:
ON_Symmetry::Coordinates symmetry_coordinates
);
- ON_DEPRECATED_MSG("Use CreateRotationSymmetry(axis,2,...) or CreateReflectionSymmetry()")
- static const ON_Symmetry CreateInversionSymmetry(
- ON_UUID symmetry_id,
- ON_Xform inversion_transform,
- ON_Symmetry::Coordinates symmetry_coordinates
- );
-
- ON_DEPRECATED_MSG("Use CreateRotationSymmetry() or CreateReflectionSymmetry()")
- static const ON_Symmetry CreateCyclicSymmetry(
- ON_UUID symmetry_id,
- ON_Xform cyclic_transform,
- unsigned int cyclic_order,
- ON_Symmetry::Coordinates symmetry_coordinates
- );
-
private:
/*
Description:
@@ -466,6 +483,41 @@ private:
ON_PlaneEquation zero_plane,
ON_Symmetry::Coordinates symmetry_coordinates
);
+
+
+ /*
+ Description:
+ Create a cyclic symmetry from a transformation.
+ The symmetric object has order copies of the modif.
+ Parameters:
+ symmetry_id - [in]
+ An id you can assign to the symmetry
+ cyclic_transform - [in]
+ cyclic_transform^order = identity
+ cyclic_transform^i != identity when 0 < i < order
+ If order is even and at least 4, then Det(cyclic_transform) = 1 or -1.
+ Otherwise Det(cyclic_transform) = 1.
+ cyclic_order - [in]
+ cyclic_order >= 2
+ fixed_line - [in]
+ fixed_line must contain all fixed points of cyclic_transform.
+ For any point P, F = P + cyclic_transform*P + ... + cyclic_transform^(cyclic_order-1)*P
+ is a fixed point of cyclic_transform.
+ symmetry_coordinates - [in]
+ object or world.
+ Remarks:
+ If cyclic_transform is a rotation, use CreateRotationSymmetry().
+ If cyclic_transform is a reflection, use CreateReflectionSymmetry().
+ If 2 = cyclic_order and Det(cyclic_transform) = -1, use CreateInversionSymmetry().
+ */
+ static const ON_Symmetry Internal_CreateCyclicSymmetry(
+ ON_UUID symmetry_id,
+ ON_Xform cyclic_transform,
+ unsigned int cyclic_order,
+ ON_Line fixed_line,
+ ON_Symmetry::Coordinates symmetry_coordinates
+ );
+
public:
/*
@@ -490,6 +542,24 @@ public:
*/
ON_Symmetry::Type SymmetryType() const;
+ /*
+ Returns:
+ True if SymmetryType() is ON_Symmetry::Type::Rotate
+ */
+ bool IsRotate() const;
+
+ /*
+ Returns:
+ True if SymmetryType() is ON_Symmetry::Type::Reflect
+ */
+ bool IsReflect() const;
+
+ /*
+ Returns:
+ True if SymmetryType() is ON_Symmetry::Type::ReflectAndRotage
+ */
+ bool IsReflectAndRotate() const;
+
/*
Returns:
Symmetry type.
@@ -594,9 +664,6 @@ public:
*/
const ON_Xform InversionTransformation() const;
- ON_DEPRECATED_MSG("Returns nans. Call InversionTransformation()")
- const ON_Xform InversionTransform() const;
-
/*
Returns:
The cyclic transformation is returned.
@@ -612,9 +679,6 @@ public:
*/
const ON_Xform CyclicTransformation() const;
- ON_DEPRECATED_MSG("Return nans. Call CyclicTransformation()")
- const ON_Xform CyclicTransform() const;
-
/*
Returns:
A plane containing all the fixed points of the symmetry.
@@ -645,6 +709,29 @@ public:
*/
const ON_Xform ReflectionTransformation() const;
+
+private:
+ /*
+ Returns:
+ True if the symmetry type requires a valid rotation axis.
+ False otherwide
+ */
+ bool Internal_RequiresRotationAxis() const;
+
+ /*
+ Returns:
+ True if the symmetry type requires a valid fixed plane.
+ False otherwide
+ */
+ bool Internal_RequiresFixedPlane() const;
+
+ /*
+ Returns:
+ True if the symmetry type is Reflect, Rotate, ReflectAndRotate.
+ */
+ bool Internal_IsStandardType() const;
+
+public:
/*
Returns:
If the symmetry is type is Rotate or ReflectAndRotate, then the rotation axis is returned.
@@ -707,29 +794,39 @@ public:
*/
double RotationAngleRadians() const;
- /*
- Returns:
- If the symmetry is type is Rotate or ReflectAndRotate, then the plane defining the zero angle or rotation is returned.
- Othewise ON_PlaneEquation::NanPlaneEquation is returned.
- Remarks:
- For a Rotate symmetry, the region on or above RotationZeroPlane() and above RotationOnePlane()
- is the default primary motif region.
- For a RotateAndReflect symmetry, the region on or above RotationZeroPlane() and on or above ReflectionPlane()
- is the default primary motif region.
- */
- const ON_PlaneEquation RotationZeroPlane() const;
/*
+ Description:
+ A reflect and rotate symmetry has three important planes.
+ * The reflection plane.
+ This plane is returned by ReflectAndRotatePlane(0).
+ * The supplemental plane. This plane is the reflection plane rotated
+ around the rotation axis by angle = -0.5*RotationAngle() and then reversed.
+ This plane is returned by ReflectAndRotatePlane(1).
+ * The reflection plane rotated around the rotation axis by pi/2.
+ This plane is returned by ReflectAndRotatePlane(2).
+
+ The primary motif region for a reflect and rotate symmetry is region above both
+ the reflection plane and the supplemental plane.
+
+ The normals of all planes point into the primary motif region.
+
+ Parameters:
+ plane_index - [in]
+ Selects the plane.
+ 0: returns the reflection plane.
+ 1: returns the supplemental plane.
+ 2: returns the half space bounding plane.
+
Returns:
- If the symmetry is type is Rotate or ReflectAndRotate, then (RotationTransformation() * RotationOnePlane()).NegatedPlaneEquation() is returned.
- Othewise ON_PlaneEquation::NanPlaneEquation is returned.
- Remarks:
- For a Rotate symmetry, the region on or above RotationZeroPlane() and above RotationOnePlane()
- is the default primary motif region.
- For a RotateAndReflect symmetry, the region on or above RotationZeroPlane() and on or above ReflectionPlane()
- is the default primary motif region.
+ If the symmetry type is ReflectAndRotate and plane_index is valid, the requested plane is returned.
+ Otherwise ON_PlaneEquation::Nan is returned.
*/
- const ON_PlaneEquation RotationOnePlane() const;
+ const ON_PlaneEquation ReflectAndRotatePlane(
+ unsigned int plane_index
+ ) const;
+
+ bool OnReflectAndRotateFixedPlane( ON_3dPoint P, bool bUseCleanupTolerance) const;
public:
bool Write(class ON_BinaryArchive&) const;
@@ -763,9 +860,6 @@ public:
*/
const ON_SHA1_Hash SymmetryHash() const;
- ON_DEPRECATED_MSG("Use SymmetryHash()")
- ON_SHA1_Hash Sha1Hash() const;
-
/*
Description:
If the SubD is known to have the symmetry specified by this ON_Symmetry,
@@ -828,15 +922,6 @@ public:
bool SameSymmetricObjectTopology(const class ON_SubDimple* subdimple) const;
- ON_DEPRECATED_MSG("OBSOLETE AND DESTROYS SYMMETRIC OBJECT INFORMATION. Use SetSymmetricObject()")
- void SetSymmetricObjectContentSerialNumber(ON__UINT64 symmetric_object_content_serial_number) const;
-
- ON_DEPRECATED_MSG("Use ClearSymmetricObject()")
- void ClearSymmetricObjectContentSerialNumber() const;
-
- ON_DEPRECATED_MSG("ALWAYS RETURNS ZERO. Use SameSymmetricObjectGeometry(...)")
- ON__UINT64 SymmetricObjectContentSerialNumber() const;
-
public:
// ON_Symmetry::ZeroTolerance is a 3d tolerance use to validate
// transformations, rotation axis locations, and plane equations.
@@ -878,6 +963,12 @@ public:
*/
double CleanupTolerance() const;
+ /*
+ Returns:
+ bUseCleanupTolerance ? this->CleanupTolerance() : ON_Symmetry::ZeroTolerance;
+ */
+ double Tolerance(bool bUseCleanupTolerance) const;
+
private:
ON_Symmetry::Type m_type = ON_Symmetry::Type::Unset;
@@ -910,7 +1001,7 @@ private:
// m_rotation_axis always lies in m_plane.
ON_Line m_rotation_axis = ON_Line::NanLine;
- // Using 0.0 insures the default returned by CleanupTolerance() is alwasy ON_Symmetry::ZeroTolerance.
+ // Using 0.0 insures the default returned by CleanupTolerance() is always ON_Symmetry::ZeroTolerance.
double m_cleanup_tolerance = 0.0;
private:
diff --git a/opennurbs_text.h b/opennurbs_text.h
index bc41cd34..e978ab44 100644
--- a/opennurbs_text.h
+++ b/opennurbs_text.h
@@ -198,7 +198,7 @@ public:
/*
Returns:
Rich text suitable for initializing SDK controls on the current platform (Windows or Apple OS X).
- The returned string is alwasy rich text (never plain text).
+ The returned string is always rich text (never plain text).
The returned rich text is always generated directly from the runs.
This text is typically different from the Rhino clean rich text returned by the RichText() command.
*/
@@ -210,7 +210,7 @@ public:
rich_text_style - [in]
Type of rich text to return.
Returns:
- The returned string is alwasy rich text (never plain text).
+ The returned string is always rich text (never plain text).
The returned rich text is always generated directly from the runs.
This text is typically different from the Rhino clean rich text returned by the RichText() command.
*/
diff --git a/opennurbs_textiterator.cpp b/opennurbs_textiterator.cpp
index a1309b36..1284bf38 100644
--- a/opennurbs_textiterator.cpp
+++ b/opennurbs_textiterator.cpp
@@ -3291,8 +3291,9 @@ bool RtfComposer::Compose(
run_strings += L" ";
}
- if (run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0)
+ if (ON_TextRun::RunType::kField != run->Type() && run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0)
{
+ // RH-64720 - Only do this if the stack isn't from a field
run_strings += L"[[";
run_strings += run->m_stacked_text->m_separator;
GetRunText(run->m_stacked_text->m_top_run, run_strings, make_rtf);
@@ -3302,9 +3303,9 @@ bool RtfComposer::Compose(
}
else if (ON_TextRun::RunType::kField == run->Type())
{
- run_strings += L"%<";
- GetRunText(run, run_strings, make_rtf);
- run_strings += L">%";
+ //run_strings += L"%<";
+ //GetRunText(run, run_strings, make_rtf);
+ //run_strings += L">%";
}
else
{
diff --git a/opennurbs_unicode.cpp b/opennurbs_unicode.cpp
index 2deebfb1..82edc5d6 100644
--- a/opennurbs_unicode.cpp
+++ b/opennurbs_unicode.cpp
@@ -26,7 +26,88 @@
int ON_IsValidUnicodeCodePoint(ON__UINT32 u)
{
- return (u < 0xD800 || (u >= 0xE000 && u <= 0x10FFFF));
+ if (u < 0xD800)
+ return true;
+
+ if (u < 0xE000)
+ {
+ // 0xD800 <= u < 0xDC00 is used for the first value in a UTF-16 surrogate pair.
+ // 0xDC00 <= u < 0xE000 is used for the second value in a UTF-16 surrogate pair.
+ return false;
+ }
+
+ if (u < 0xFFFE)
+ return true;
+
+ if (u == 0xFFFE || u == 0xFFFF)
+ {
+ // The Unicode specification states these values are .
+ // 0xFFFE is a swapped Unicode byte order mark (U+FEFF).
+ // https://www.unicode.org/charts/PDF/UFFF0.pdf
+ return false;
+ }
+
+ if (u < 0xFFFFE)
+ return true;
+
+ if (u == 0xFFFFE || u == 0xFFFFF)
+ {
+ // The Unicode specification states these values are .
+ // https://www.unicode.org/charts/PDF/UFFF80.pdf
+ return false;
+ }
+
+ if (u < 0x10FFFE)
+ return true;
+
+ if (u == 0x10FFFE || u == 0x10FFFF)
+ {
+ // The Unicode specification states these values are .
+ // https://www.unicode.org/charts/PDF/U10FF80.pdf
+ return false;
+ }
+
+ // All valid Unicode code points are < 0x110000
+ return false;
+}
+
+bool ON_IsPrivateUseUnicodeCodePoint(ON__UINT32 unicode_code_point)
+{
+ // Unicode code points in the ranges U+E000–U+F8FF, U+F0000–U+FFFFD,
+ // and U+100000–U+10FFFD are for private use (user defined characters).
+
+ if (unicode_code_point < 0xE000U)
+ return false;
+
+ if (unicode_code_point <= 0xF8FFU)
+ return true; // Private use area E000-F8FF
+
+ if (unicode_code_point < 0xF0000U)
+ return false;
+
+ if (unicode_code_point <= 0xFFFFDU)
+ return true; // Supplementary Private Use Area-A F0000-FFFFD
+
+ // FFFFE and FFFFF are
+
+ if (unicode_code_point < 0x100000U)
+ return false;
+
+ if (unicode_code_point <= 0x10FFFDU)
+ return true; // Supplementary Private Use Area-B 100000-10FFFD
+
+ // 10FFFE and 10FFFF are
+
+ return false;
+}
+
+bool ON_IsStandardUnicodeCodePoint(ON__UINT32 unicode_code_point)
+{
+ return
+ 0 != ON_IsValidUnicodeCodePoint(unicode_code_point)
+ && ON_UnicodeCodePoint::ON_ByteOrderMark != unicode_code_point
+ && false == ON_IsPrivateUseUnicodeCodePoint(unicode_code_point)
+ ;
}
int ON_IsUnicodeSpaceCodePoint(
@@ -2891,7 +2972,11 @@ int ON_ConvertMSMBCPToWideChar(
if ( 0 != error_status )
*error_status = 0;
- bool bNullTerminated = false;
+ if (nullptr != sWideChar && sWideChar_capacity > 0)
+ sWideChar[0] = 0;
+
+ bool bNullTerminated = false; // Mac OS code needs bNullTerminated
+
if ( -1 == sMBCS_count && nullptr != sMBCS )
{
for ( sMBCS_count = 0; true; sMBCS_count++)
@@ -2925,11 +3010,8 @@ int ON_ConvertMSMBCPToWideChar(
{
sWideChar_capacity = 0;
}
- else
- {
- sWideChar[0] = 0;
- }
+ // ASCII check
const char* c = sMBCS;
const char* c1 = c + sMBCS_count;
wchar_t* w = sWideChar;
@@ -3088,6 +3170,294 @@ int ON_ConvertMSMBCPToWideChar(
}
+int ON_wString::ByteOrder(wchar_t bom_candidate)
+{
+ if (ON_UnicodeCodePoint::ON_ByteOrderMark == bom_candidate)
+ return 1;
+ if (ON_UnicodeCodePoint::ON_ByteOrderMark == ON_wString::SwapByteOrder(bom_candidate))
+ return -1;
+ return 0;
+}
+
+wchar_t ON_wString::SwapByteOrder(wchar_t w)
+{
+ unsigned char* b = (unsigned char*)(&w);
+ unsigned char tmp;
+#if (2 == ON_SIZEOF_WCHAR_T)
+ tmp = b[0]; b[0] = b[1]; b[1] = tmp;
+#elif (4 == ON_SIZEOF_WCHAR_T)
+ tmp = b[0]; b[0] = b[3]; b[3] = tmp;
+ tmp = b[1]; b[1] = b[2]; b[2] = tmp;
+#endif
+ return w;
+}
+
+int ON_ConvertWideCharToMSMBCP(
+ const wchar_t* sWideChar,
+ int sWideChar_count,
+ ON__UINT32 windows_code_page,
+ char* sMBCS,
+ int sMBCS_capacity,
+ unsigned int* error_status
+)
+{
+ if (0 != error_status)
+ *error_status = 0;
+
+ if (nullptr != sMBCS && sMBCS_capacity > 0)
+ sMBCS[0] = 0;
+
+ if (-1 == sWideChar_count && nullptr != sWideChar)
+ {
+ for (sWideChar_count = 0; true; sWideChar_count++)
+ {
+ if (0 == sWideChar[sWideChar_count])
+ break;
+ }
+ }
+
+ if (nullptr == sWideChar || sWideChar_count < 0)
+ {
+ if (0 != error_status)
+ *error_status |= 1;
+ return 0;
+ }
+
+ const int byte_order_mark = (nullptr != sWideChar && sWideChar_count > 0) ? ON_wString::ByteOrder(sWideChar[0]) : 0;
+ if (0 != byte_order_mark)
+ {
+ // handle leading byte order mark.
+ ++sWideChar;
+ --sWideChar_count;
+
+ while (sWideChar_count > 0 && 0 != ON_wString::ByteOrder(sWideChar[0]))
+ {
+ // Ignore additional byte order marks. If this is causing trouble,
+ // you need to clean up your input before calling this function.
+ ++sWideChar;
+ --sWideChar_count;
+ }
+
+ if (sWideChar_count <= 0)
+ {
+ // The entire input string was byte order marks. Nothing to convert
+ return 0;
+ }
+
+ if (-1 == byte_order_mark)
+ {
+ // Recursively call this function 1 time with swapped bytes.
+ wchar_t* swappedWideChar = (wchar_t*)onmalloc((sWideChar_count + 1) * sizeof(swappedWideChar[0]));
+ for (int i = 0; i < sWideChar_count; ++i)
+ swappedWideChar[i] = ON_wString::SwapByteOrder(sWideChar[i]);
+ swappedWideChar[sWideChar_count] = 0;
+ if (0 == ON_wString::ByteOrder(swappedWideChar[0]))
+ {
+ // The 0 == ON_wString::ByteOrder(swappedWideChar[0]) condition insures we will not recurse again.
+ const int rc = ON_ConvertWideCharToMSMBCP(swappedWideChar, sWideChar_count, windows_code_page, sMBCS, sMBCS_capacity, error_status);
+ onfree(swappedWideChar);
+ return rc;
+ }
+ else
+ {
+ // A modification to this code made after May 2021 introduced a bug.
+ // Depending on the bug and the string length, a possible stack overflow recursion crash was prevented by this check.
+ ON_ERROR("A bug was added after May 2021.");
+ onfree(swappedWideChar);
+ }
+ }
+ }
+
+ if (0 == sWideChar_count)
+ {
+ return 0;
+ }
+
+ if (sMBCS_capacity <= 0)
+ {
+ sMBCS_capacity = 0;
+ sMBCS = nullptr;
+ }
+ else if (nullptr == sMBCS)
+ {
+ sMBCS_capacity = 0;
+ }
+
+ // ASCII check
+ const wchar_t* w = sWideChar;
+ const wchar_t* w1 = w + sWideChar_count;
+ char* c = sMBCS;
+ char* c1 = c + sMBCS_capacity;
+ while (w < w1 && *w >= 0 && *w <= 127)
+ {
+ if (nullptr != c)
+ {
+ if (c >= c1)
+ break;
+ *c++ = (char)*w;
+ }
+ c++;
+ }
+ if (w == w1)
+ {
+ if (c < c1)
+ *c = 0;
+ return sWideChar_count;
+ }
+
+ // UTF wide char to UTF-8 - use opennurbs converter
+ const int utf8_code_page = 65001; // UTF-8
+ if (utf8_code_page == windows_code_page)
+ {
+ const unsigned int error_mask = 0xFFFFFFFFU;
+ return ON_ConvertWideCharToUTF8(false, sWideChar, sWideChar_count, sMBCS, sMBCS_capacity, error_status, error_mask, ON_UnicodeCodePoint::ON_ReplacementCharacter, nullptr);
+ }
+
+#if defined(ON_RUNTIME_WIN)
+ // Starting with Windows Vista, the function does not drop illegal code points when dwFlags=0.
+ // It replaces illegal sequences with U+FFFD (encoded as appropriate for the specified codepage).
+ const char default_char = '?';
+ BOOL bUsedDefaultChar = 0;
+
+ const char* pdefault_char = nullptr;
+ BOOL* pbUsedDefaultChar = nullptr;
+ DWORD dwFlags = 0;
+
+ switch (windows_code_page)
+ {
+ case 42: // Symbol
+ case 50220:
+ case 50221:
+ case 50222:
+ case 50225:
+ case 50227:
+ case 50229:
+ case 57002:
+ case 57003:
+ case 57004:
+ case 57005:
+ case 57006:
+ case 57007:
+ case 57008:
+ case 57009:
+ case 57010:
+ case 57011:
+ case 65000: // UTF-7
+ // For these code pages listed below, dwFlags must be 0. Otherwise, WideCharToMultiByte() fails with ERROR_INVALID_FLAGS.
+ break;
+
+ case 54936: // GB18030
+ case 65001: // UTF-8
+ // dwFlags must be set to either 0 or WC_ERR_INVALID_CHARS. Otherwise, the function fails with ERROR_INVALID_FLAGS.
+ break;
+
+ default:
+ pdefault_char = &default_char;
+ pbUsedDefaultChar = &bUsedDefaultChar;
+ dwFlags |= WC_NO_BEST_FIT_CHARS;
+ dwFlags |= WC_COMPOSITECHECK;
+ dwFlags |= WC_DEFAULTCHAR;
+ break;
+ }
+
+ int sMBCS_count = ::WideCharToMultiByte(windows_code_page, dwFlags, sWideChar, sWideChar_count, sMBCS, sMBCS_capacity, pdefault_char, pbUsedDefaultChar);
+ if (sMBCS_count < 0)
+ sMBCS_count = 0;
+
+ if (nullptr == sMBCS)
+ return sMBCS_count;
+
+ for (int i = 0; i < sMBCS_count; i++)
+ {
+ if (0 == sMBCS[i])
+ {
+ sMBCS_count = i;
+ break;
+ }
+ }
+ if (bUsedDefaultChar)
+ {
+ if (nullptr != error_status)
+ *error_status |= 16;
+ }
+
+ if (sMBCS_count < sMBCS_capacity)
+ sMBCS[sMBCS_count] = 0;
+ return sMBCS_count;
+
+#elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE)
+ // Add support for other platforms as needed.
+ ON_ERROR("Time to figure out how to create Windows code page encodings in MacOS");
+ return 0;
+#else
+ // Add support for other platforms as needed.
+ return 0;
+#endif
+}
+
+const ON_String ON_wString::MultiByteEncode(int windows_code_page) const
+{
+ unsigned int error_status0 = 0;
+ const int sChar_count0 = ON_ConvertWideCharToMSMBCP(
+ this->Array(),
+ this->Length(),
+ windows_code_page,
+ nullptr, // wchar_t* sWideChar,
+ 0, //int sWideChar_capacity,
+ &error_status0
+ );
+ if (sChar_count0 <= 0)
+ return ON_String::EmptyString;
+
+ ON_String s;
+ s.ReserveArray(((size_t)sChar_count0) + 1);
+ unsigned int error_status1 = 0;
+ const int sChar_count1 = ON_ConvertWideCharToMSMBCP(
+ this->Array(),
+ this->Length(),
+ windows_code_page,
+ s.Array(),
+ sChar_count0 + 1,
+ &error_status1
+ );
+ if (sChar_count1 <= 0)
+ return ON_String::EmptyString;
+
+ s.SetLength(sChar_count1);
+ return s;
+}
+
+const ON_wString ON_String::MultiByteDecode(int windows_code_page) const
+{
+ unsigned int error_status0 = 0;
+ const int sWideChar_count0 = ON_ConvertMSMBCPToWideChar(
+ windows_code_page,
+ this->Array(),
+ this->Length(),
+ nullptr, // wchar_t* sWideChar,
+ 0, //int sWideChar_capacity,
+ &error_status0
+ );
+ if (sWideChar_count0 <= 0)
+ return ON_wString::EmptyString;
+
+ ON_wString s;
+ s.ReserveArray(((size_t)sWideChar_count0) + 1);
+ unsigned int error_status1 = 0;
+ const int sWideChar_count1 = ON_ConvertMSMBCPToWideChar(
+ windows_code_page,
+ this->Array(),
+ this->Length(),
+ s.Array(),
+ sWideChar_count0+1,
+ &error_status1
+ );
+ if (sWideChar_count1 <= 0)
+ return ON_wString::EmptyString;
+
+ s.SetLength(sWideChar_count1);
+ return s;
+}
unsigned ON_UnicodeSuperscriptFromCodePoint(
@@ -3286,3 +3656,14429 @@ unsigned ON_UnicodeSubcriptFromCodePoint(
return cp;
}
+const char* ON_Big5CodePoint::Decode(
+ const char* buffer,
+ size_t buffer_count,
+ bool bParseNull,
+ bool bParseASCII,
+ ON_Big5CodePoint* big5_code_point
+)
+{
+ ON_Big5CodePoint local_cp;
+ if (nullptr == big5_code_point)
+ big5_code_point = &local_cp;
+ for (;;)
+ {
+ if (nullptr == buffer)
+ break;
+ if (-1 == buffer_count)
+ buffer_count = 2; // buffer[] is null terminated
+ if (buffer_count <= 0)
+ break;
+
+ if (0 == buffer[0])
+ {
+ if (bParseNull)
+ {
+ *big5_code_point = ON_Big5CodePoint::Null;
+ return buffer + 1;
+ }
+ break;
+ }
+
+ if (buffer[0] > 0 && buffer[0] <= 0x7F)
+ {
+ if (bParseASCII)
+ {
+ *big5_code_point = ON_Big5CodePoint::Create((ON__UINT16)buffer[0]);
+ return buffer + 1;
+ }
+ break;
+ }
+
+ if (buffer_count < 2)
+ break;
+ const ON__UINT16 db[2] = { ((const unsigned char*)buffer)[0], ((const unsigned char*)buffer)[1] };
+ if (
+ (db[0] >= 0x81 && db[0] <= 0xFE)
+ &&
+ ((db[1] >= 0x40 && db[1] <= 0x7E) || (db[1] >= 0xA1 && db[1] <= 0xFE))
+ )
+ {
+ // valid BIG5 double double byte encoding.
+ *big5_code_point = ON_Big5CodePoint::Create(db[0] * 0x100 + db[1]);
+ return buffer + 2;
+ }
+
+ break;
+ }
+
+ *big5_code_point = ON_Big5CodePoint::Error;
+ return nullptr;
+}
+
+int ON_Big5CodePoint::Encode(
+ char* buffer,
+ size_t buffer_capacity
+) const
+{
+ if (nullptr == buffer || buffer_capacity <= 0)
+ return 0;
+
+ if (IsValid(true, true))
+ {
+ if (m_big5_code_point <= 0x7F)
+ {
+ buffer[0] = (char)m_big5_code_point;
+ if (buffer_capacity > 1)
+ buffer[1] = 0;
+ return 1;
+ }
+
+ if (buffer_capacity < 2)
+ return 0;
+
+ const unsigned db[2] = { m_big5_code_point / 0x100U, m_big5_code_point % 0x100U };
+ if (
+ (db[0] >= 0x81 && db[0] <= 0xFE)
+ &&
+ ((db[1] >= 0x40 && db[1] <= 0x7E) || (db[1] >= 0xA1 && db[1] <= 0xFE))
+ )
+ {
+ ((unsigned char*)buffer)[0] = (unsigned char)(db[0]);
+ ((unsigned char*)buffer)[1] = (unsigned char)(db[1]);
+ if (buffer_capacity > 2)
+ buffer[2] = 0;
+ return 2;
+ }
+
+ ON_ERROR("Bug in tis if() scope.");
+ }
+
+ return 0;
+
+}
+
+const ON_Big5CodePoint ON_Big5CodePoint::Create(unsigned int big5_code_point)
+{
+ if (big5_code_point > 0xFFFF)
+ return ON_Big5CodePoint::Error;
+ ON_Big5CodePoint cp;
+ cp.m_big5_code_point = (big5_code_point <= 0xFFFF) ? ((ON__UINT16)big5_code_point) : ((ON__UINT16)0xFFFF);
+ return (cp.IsValid(true, true) || 0xFFFF==cp.m_big5_code_point) ? cp : ON_Big5CodePoint::Error;
+}
+
+bool ON_Big5CodePoint::IsNull() const
+{
+ return 0 == m_big5_code_point;
+}
+
+bool ON_Big5CodePoint::IsASCII(bool bNullIsASCII) const
+{
+ if (0 == m_big5_code_point)
+ return bNullIsASCII ? true : false;
+ return (m_big5_code_point > 0 && m_big5_code_point <= 0x007F);
+}
+
+bool ON_Big5CodePoint::IsValid(bool bNullIsValid, bool bASCIICodePointIsValid) const
+{
+ if (0 == m_big5_code_point)
+ return bNullIsValid ? true : false;
+
+ if (m_big5_code_point <= 0x007F)
+ return bASCIICodePointIsValid ? true : false;
+
+ if (m_big5_code_point >= 0xA3C0 && m_big5_code_point <= 0xA3FE)
+ {
+ // Some older versions of Windows use the reserved BIG5 code point 0xA3E1 for the EURO CURRENCY SYMBOL
+ // The primary use of ON_Big5CodePoint is to convert legacy BIG5 encoded information
+ // into Unicode encoded information and ON_Big5CodePoint supports this legacy Windows mapping.
+ if (0xA3E1 != m_big5_code_point)
+ return false; // reserved for future use
+ }
+
+ const ON__UINT16 db[2] = { (ON__UINT16)(m_big5_code_point / 0x100U), (ON__UINT16)(m_big5_code_point % 0x100U) };
+ return
+ (db[0] >= 0x81U && db[0] <= 0xFEU)
+ &&
+ ((db[1] >= 0x40U && db[1] <= 0x7EU) || (db[1] >= 0xA1U && db[1] <= 0xFEU))
+ ;
+}
+
+bool ON_Big5CodePoint::IsStandard(bool bNullIsValid, bool bASCIICodePointIsValid) const
+{
+ if (IsValid(bNullIsValid, bASCIICodePointIsValid))
+ {
+ // 0xa140 to 0xa3bf
+ if (m_big5_code_point < 0xA140)
+ return false;
+ if (m_big5_code_point <= 0xA3BF)
+ return true;
+
+ // 0xa3c0 to 0xa3fe - reserved - not user defined and not standard
+ if (0xA3E1 == m_big5_code_point)
+ return true; // Old Windows Euro currency symbol
+
+ // 0xa440 to 0xc67e
+ if (m_big5_code_point < 0xA440)
+ return false;
+ if (m_big5_code_point <= 0xC67E)
+ return true;
+
+ // 0xc940 to 0xf9d5
+ if (m_big5_code_point < 0xC940)
+ return false;
+ if (m_big5_code_point <= 0xF9D5)
+ return true;
+ }
+
+ return false;
+}
+
+
+bool ON_Big5CodePoint::IsPrivateUse() const
+{
+ if (IsValid(false,false))
+ {
+ // 0x8140 to 0xa0fe
+ if (m_big5_code_point < 0x8140)
+ return false;
+ if (m_big5_code_point < 0xA140)
+ return true;
+
+ // 0xc6a1 to 0xc8fe
+ if (m_big5_code_point < 0xC6A1)
+ return false;
+ if (m_big5_code_point <= 0xC8FE)
+ return true;
+
+ // 0xf9d6 to 0xfefe
+ if (m_big5_code_point < 0xF9D6)
+ return false;
+ if (m_big5_code_point <= 0xFEFE)
+ return true;
+ }
+
+ return false;
+}
+
+unsigned int ON_Big5CodePoint::Big5CodePoint() const
+{
+ return m_big5_code_point;
+}
+
+int ON_Big5CodePoint::Compare(const ON_Big5CodePoint* lhs, const ON_Big5CodePoint* rhs)
+{
+ const unsigned lhs_cp = (nullptr != lhs) ? ((unsigned)lhs->m_big5_code_point) : 0xFFFFFFFFU;
+ const unsigned rhs_cp = (nullptr != rhs) ? ((unsigned)rhs->m_big5_code_point) : 0xFFFFFFFFU;
+ if (lhs_cp < rhs_cp)
+ return -1;
+ if (lhs_cp > rhs_cp)
+ return 1;
+ return 0;
+};
+
+bool operator==(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return 0 == ON_Big5CodePoint::Compare(&lhs, &rhs);
+}
+
+bool operator!=(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return 0 != ON_Big5CodePoint::Compare(&lhs, &rhs);
+}
+
+bool operator<=(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return ON_Big5CodePoint::Compare(&lhs, &rhs) <= 0;
+}
+
+bool operator>=(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return ON_Big5CodePoint::Compare(&lhs, &rhs) >= 0;
+}
+
+bool operator<(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return ON_Big5CodePoint::Compare(&lhs, &rhs) < 0;
+}
+
+bool operator>(ON_Big5CodePoint lhs, ON_Big5CodePoint rhs)
+{
+ return ON_Big5CodePoint::Compare(&lhs, &rhs) > 0;
+}
+
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::Create(unsigned int unicode_code_point)
+{
+ ON_UnicodeShortCodePoint cp;
+ cp.m_unicode_code_point
+ = (0xFFFE == unicode_code_point || ON_IsValidUnicodeCodePoint(unicode_code_point))
+ ? ((ON__UINT16)unicode_code_point)
+ : ((ON__UINT16)0xFFFFU)
+ ;
+ return cp;
+}
+
+bool ON_UnicodeShortCodePoint::IsNull() const
+{
+ return 0 == m_unicode_code_point;
+}
+
+bool ON_UnicodeShortCodePoint::IsASCII(bool bNullIsASCII) const
+{
+ if (0 == m_unicode_code_point)
+ return bNullIsASCII ? true : false;
+
+ return (m_unicode_code_point > 0 && m_unicode_code_point <= 0x007F);
+}
+
+bool ON_UnicodeShortCodePoint::IsValid(bool bNullIsValid, bool bByteOrderMarkIsValid) const
+{
+ if (0 == m_unicode_code_point)
+ return bNullIsValid ? true : false;
+
+ if (0xFFFE == m_unicode_code_point)
+ return bByteOrderMarkIsValid ? true : false;
+
+ return ON_IsValidUnicodeCodePoint(m_unicode_code_point);
+}
+
+bool ON_UnicodeShortCodePoint::IsStandard(bool bNullIsValid) const
+{
+ if (IsValid(bNullIsValid,false))
+ return ON_IsStandardUnicodeCodePoint(m_unicode_code_point);
+
+ return false;
+}
+
+bool ON_UnicodeShortCodePoint::IsPrivateUse() const
+{
+ return ON_IsPrivateUseUnicodeCodePoint(m_unicode_code_point);
+}
+
+unsigned int ON_UnicodeShortCodePoint::UnicodeCodePoint() const
+{
+ return m_unicode_code_point;
+}
+
+bool ON_UnicodeShortCodePoint::IsReplacementCharacter() const
+{
+ return 0xFFFD == m_unicode_code_point;
+}
+
+bool ON_UnicodeShortCodePoint::IsByteOrderMark() const
+{
+ return 0xFFFE == m_unicode_code_point;
+}
+
+
+int ON_UnicodeShortCodePoint::Compare(const ON_UnicodeShortCodePoint* lhs, const ON_UnicodeShortCodePoint* rhs)
+{
+ const unsigned lhs_cp = (nullptr != lhs) ? ((unsigned)lhs->m_unicode_code_point) : 0xFFFFFFFFU;
+ const unsigned rhs_cp = (nullptr != rhs) ? ((unsigned)rhs->m_unicode_code_point) : 0xFFFFFFFFU;
+ if (lhs_cp < rhs_cp)
+ return -1;
+ if (lhs_cp > rhs_cp)
+ return 1;
+ return 0;
+};
+
+bool operator==(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return 0 == ON_UnicodeShortCodePoint::Compare(&lhs, &rhs);
+}
+
+bool operator!=(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return 0 != ON_UnicodeShortCodePoint::Compare(&lhs, &rhs);
+}
+
+bool operator<=(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return ON_UnicodeShortCodePoint::Compare(&lhs, &rhs) <= 0;
+}
+
+bool operator>=(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return ON_UnicodeShortCodePoint::Compare(&lhs, &rhs) >= 0;
+}
+
+bool operator<(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return ON_UnicodeShortCodePoint::Compare(&lhs, &rhs) < 0;
+}
+
+bool operator>(ON_UnicodeShortCodePoint lhs, ON_UnicodeShortCodePoint rhs)
+{
+ return ON_UnicodeShortCodePoint::Compare(&lhs, &rhs) > 0;
+}
+
+const ON_Big5UnicodePair ON_Big5UnicodePair::Create(
+ ON_Big5CodePoint big5_code_point,
+ ON_UnicodeShortCodePoint unicode_code_point
+)
+{
+ ON_Big5UnicodePair p;
+ p.m_big5 = big5_code_point;
+ p.m_unicode = unicode_code_point;
+ return p;
+}
+
+const ON_Big5UnicodePair ON_Big5UnicodePair::Create(
+ unsigned int big5_code_point,
+ unsigned int unicode_code_point
+)
+{
+ return ON_Big5UnicodePair::Create(ON_Big5CodePoint::Create(big5_code_point), ON_UnicodeShortCodePoint::Create(unicode_code_point));
+}
+
+
+bool ON_Big5UnicodePair::IsNull() const
+{
+ return m_big5.IsNull() && m_unicode.IsNull();
+}
+
+bool ON_Big5UnicodePair::IsASCII(bool bNullIsASCII) const
+{
+ return m_big5.IsASCII(bNullIsASCII) && m_big5.Big5CodePoint() == m_unicode.UnicodeCodePoint();
+}
+
+bool ON_Big5UnicodePair::IsValid(bool bNullIsValid, bool bASCIICodePointIsValid) const
+{
+ const unsigned big5_code_point = m_big5.Big5CodePoint();
+ if (0 == big5_code_point)
+ return bNullIsValid ? IsNull() : false;
+
+ if (big5_code_point <= 0x7F)
+ return bASCIICodePointIsValid ? IsASCII(false) : false;
+
+ return m_big5.IsValid(false, false) && m_unicode.IsValid(false,false);
+}
+
+const ON_Big5CodePoint ON_Big5UnicodePair::Big5() const
+{
+ return m_big5;
+}
+
+const ON_UnicodeShortCodePoint ON_Big5UnicodePair::Unicode() const
+{
+ return m_unicode;
+}
+
+unsigned int ON_Big5UnicodePair::Big5CodePoint() const
+{
+ return m_big5.Big5CodePoint();
+}
+
+unsigned int ON_Big5UnicodePair::UnicodeCodePoint() const
+{
+ return m_unicode.UnicodeCodePoint();
+}
+
+bool ON_Big5UnicodePair::IsStandard(bool bNullIsValid, bool bASCIICodePointIsStandard) const
+{
+ const unsigned big5_code_point = m_big5.Big5CodePoint();
+ if (0 == big5_code_point)
+ return bNullIsValid ? IsNull() : false;
+
+ if (big5_code_point <= 0x7F)
+ return bASCIICodePointIsStandard ? IsASCII(false) : false;
+
+ return m_big5.IsStandard(false, false) && m_unicode.IsStandard(false);
+}
+
+bool ON_Big5UnicodePair::IsPrivateUse() const
+{
+ return IsValid(false, false) && (m_big5.IsPrivateUse() || m_unicode.IsPrivateUse());
+}
+
+
+int ON_Big5UnicodePair::CompareBig5CodePoint(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs)
+{
+ const unsigned lhs_cp = (nullptr != lhs) ? lhs->Big5CodePoint() : 0xFFFFFFFFU;
+ const unsigned rhs_cp = (nullptr != rhs) ? rhs->Big5CodePoint() : 0xFFFFFFFFU;
+ if (lhs_cp < rhs_cp)
+ return -1;
+ if (lhs_cp > rhs_cp)
+ return 1;
+ return 0;
+}
+
+int ON_Big5UnicodePair::CompareUnicodeCodePoint(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs)
+{
+ const unsigned lhs_cp = (nullptr != lhs) ? lhs->UnicodeCodePoint() : 0xFFFFFFFFU;
+ const unsigned rhs_cp = (nullptr != rhs) ? rhs->UnicodeCodePoint() : 0xFFFFFFFFU;
+ if (lhs_cp < rhs_cp)
+ return -1;
+ if (lhs_cp > rhs_cp)
+ return 1;
+ return 0;
+}
+
+int ON_Big5UnicodePair::CompareBig5AndUnicodeCodePoints(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs)
+{
+ int rc = ON_Big5UnicodePair::CompareBig5CodePoint(lhs, rhs);
+ if (0 == rc)
+ rc = ON_Big5UnicodePair::CompareUnicodeCodePoint(lhs, rhs);
+ return rc;
+}
+
+int ON_Big5UnicodePair::CompareUnicodeAndBig5CodePoints(const ON_Big5UnicodePair* lhs, const ON_Big5UnicodePair* rhs)
+{
+ int rc = ON_Big5UnicodePair::CompareUnicodeCodePoint(lhs, rhs);
+ if (0 == rc)
+ rc = ON_Big5UnicodePair::CompareBig5CodePoint(lhs, rhs);
+ return rc;
+}
+
+bool operator==(ON_Big5UnicodePair lhs, ON_Big5UnicodePair rhs)
+{
+ return 0 == ON_Big5UnicodePair::CompareBig5AndUnicodeCodePoints(&lhs, &rhs);
+}
+
+bool operator!=(ON_Big5UnicodePair lhs, ON_Big5UnicodePair rhs)
+{
+ return 0 != ON_Big5UnicodePair::CompareBig5AndUnicodeCodePoints(&lhs, &rhs);
+}
+
+const ON_Big5CodePoint ON_Big5CodePoint::CreateFromUnicode(
+ unsigned int unicode_code_point,
+ ON_Big5CodePoint not_avilable
+)
+{
+ if (unicode_code_point > 0xFFFF)
+ return ON_IsValidUnicodeCodePoint(unicode_code_point) ? not_avilable : ON_Big5CodePoint::Error;
+
+ return ON_Big5CodePoint::CreateFromUnicode(ON_UnicodeShortCodePoint::Create(unicode_code_point), not_avilable);
+}
+
+const ON_Big5CodePoint ON_Big5CodePoint::CreateFromUnicode(
+ const ON_UnicodeShortCodePoint& unicode_code_point,
+ ON_Big5CodePoint not_avilable
+)
+{
+ if (unicode_code_point.IsASCII(true))
+ return ON_Big5CodePoint::Create(unicode_code_point.UnicodeCodePoint()); // ASCII code point
+
+ if (false == unicode_code_point.IsValid(false, false))
+ return ON_Big5CodePoint::Error;
+
+ const ON_SimpleArray& unicode_to_big5 = ON_Big5UnicodePair::UnicodeToBig5();
+
+ const ON_Big5UnicodePair key = ON_Big5UnicodePair::Create(ON_Big5CodePoint::Null, unicode_code_point);
+ const int i = unicode_to_big5.BinarySearch(&key, ON_Big5UnicodePair::CompareUnicodeCodePoint);
+ if (i >= 0)
+ {
+ const ON_Big5UnicodePair pair = unicode_to_big5[i];
+ if (unicode_code_point == pair.Unicode() && pair.IsValid(false, false))
+ return pair.Big5();
+ }
+
+ // No BIG5 code point available for this Unicode code point.
+ return not_avilable;
+}
+
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::CreateFromBig5(
+ unsigned int big5_code_point,
+ ON_UnicodeShortCodePoint not_avilable
+)
+{
+ return ON_UnicodeShortCodePoint::CreateFromBig5(ON_Big5CodePoint::Create(big5_code_point), not_avilable);
+}
+
+const ON_UnicodeShortCodePoint ON_UnicodeShortCodePoint::CreateFromBig5(
+ const ON_Big5CodePoint& big5_code_point,
+ ON_UnicodeShortCodePoint not_avilable
+)
+{
+ if (big5_code_point.IsASCII(true))
+ return ON_UnicodeShortCodePoint::Create(big5_code_point.Big5CodePoint()); // ASCII code point
+
+ if (false == big5_code_point.IsValid(false, false))
+ return ON_UnicodeShortCodePoint::Error;
+
+ const ON_SimpleArray& big5_to_unicode = ON_Big5UnicodePair::Big5ToUnicode();
+
+ const ON_Big5UnicodePair key = ON_Big5UnicodePair::Create(big5_code_point, ON_UnicodeShortCodePoint::Null);
+ const int i = big5_to_unicode.BinarySearch(&key, ON_Big5UnicodePair::CompareBig5CodePoint);
+ if (i >= 0)
+ {
+ const ON_Big5UnicodePair pair = big5_to_unicode[i];
+ if (big5_code_point == pair.Big5() && pair.IsValid(false, false))
+ return pair.Unicode();
+ }
+
+ // No Unicode code point available for this BIG5 code point.
+ return not_avilable;
+}
+
+////////////////////////
+
+const ON_SimpleArray< ON_Big5UnicodePair >& ON_Big5UnicodePair::Big5ToUnicode()
+{
+ // The pair[][2] array was created by combining information from
+ // http://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/OTHER/BIG5.TXT, retrieved: May 17, 2021,
+ // and WIndows 10 MultiByteToWideChar() using code page 950.
+ //
+ // pairs[][2] = {...{BIG5 code point, Unicode code point}...}
+ // sorted by BIG5 code point. All Unicode code points must be valid and < 0xFFFF.
+ //
+ // The primary use of this code is to convert legacy BIG5 encoded information into
+ // Unicode encoded information and modificiations to the original sources
+ // (marked with [MODyyymmdd]) were made to make thos conversions as robust as possible.
+ //
+
+ static const ON__UINT16 pairs[][2] = {
+ {0xA140,0x3000}, // IDEOGRAPHIC SPACE
+ {0xA141,0xff0c}, // FULLWIDTH COMMA
+ {0xA142,0x3001}, // IDEOGRAPHIC COMMA
+ {0xA143,0x3002}, // IDEOGRAPHIC FULL STOP
+ {0xA144,0xff0e}, // FULLWIDTH FULL STOP
+ {0xA145,0x2027}, // HYPHENATION POINT [MOD20210517] changed from 0x2022 # BULLET
+ {0xA146,0xff1b}, // FULLWIDTH SEMICOLON
+ {0xA147,0xff1a}, // FULLWIDTH COLON
+ {0xA148,0xff1f}, // FULLWIDTH QUESTION MARK
+ {0xA149,0xff01}, // FULLWIDTH EXCLAMATION MARK
+ {0xA14A,0xfe30}, // PRESENTATION FORM FOR VERTICAL TWO DOT LEADER
+ {0xA14B,0x2026}, // HORIZONTAL ELLIPSIS
+ {0xA14C,0x2025}, // TWO DOT LEADER
+ {0xA14D,0xfe50}, // SMALL COMMA
+ {0xA14E,0xfe51}, // SMALL IDEOGRAPHIC COMMA [MOD20210517] changed from 0xFF64 # HALFWIDTH IDEOGRAPHIC COMMA
+ {0xA14F,0xfe52}, // SMALL FULL STOP
+ {0xA150,0x00b7}, // MIDDLE DOT
+ {0xA151,0xfe54}, // SMALL SEMICOLON
+ {0xA152,0xfe55}, // SMALL COLON
+ {0xA153,0xfe56}, // SMALL QUESTION MARK
+ {0xA154,0xfe57}, // SMALL EXCLAMATION MARK
+ {0xA155,0xff5c}, // FULLWIDTH VERTICAL BAR
+ {0xA156,0x2013}, // EN DASH
+ {0xA157,0xfe31}, // PRESENTATION FORM FOR VERTICAL EM DASH
+ {0xA158,0x2014}, // EM DASH
+ {0xA159,0xfe33}, // PRESENTATION FORM FOR VERTICAL LOW LINE
+ {0xA15A,0x2574}, // BOX DRAWINGS LIGHT LEFT [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA15B,0xfe34}, // PRESENTATION FORM FOR VERTICAL WAVY LOW LINE
+ {0xA15C,0xfe4f}, // WAVY LOW LINE
+ {0xA15D,0xff08}, // FULLWIDTH LEFT PARENTHESIS
+ {0xA15E,0xff09}, // FULLWIDTH RIGHT PARENTHESIS
+ {0xA15F,0xfe35}, // PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS
+ {0xA160,0xfe36}, // PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS
+ {0xA161,0xff5b}, // FULLWIDTH LEFT CURLY BRACKET
+ {0xA162,0xff5d}, // FULLWIDTH RIGHT CURLY BRACKET
+ {0xA163,0xfe37}, // PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
+ {0xA164,0xfe38}, // PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
+ {0xA165,0x3014}, // LEFT TORTOISE SHELL BRACKET
+ {0xA166,0x3015}, // RIGHT TORTOISE SHELL BRACKET
+ {0xA167,0xfe39}, // PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET
+ {0xA168,0xfe3a}, // PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET
+ {0xA169,0x3010}, // LEFT BLACK LENTICULAR BRACKET
+ {0xA16A,0x3011}, // RIGHT BLACK LENTICULAR BRACKET
+ {0xA16B,0xfe3b}, // PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET
+ {0xA16C,0xfe3c}, // PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET
+ {0xA16D,0x300a}, // LEFT DOUBLE ANGLE BRACKET
+ {0xA16E,0x300b}, // RIGHT DOUBLE ANGLE BRACKET
+ {0xA16F,0xfe3d}, // PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET
+ {0xA170,0xfe3e}, // PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET
+ {0xA171,0x3008}, // LEFT ANGLE BRACKET
+ {0xA172,0x3009}, // RIGHT ANGLE BRACKET
+ {0xA173,0xfe3f}, // PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET
+ {0xA174,0xfe40}, // PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET
+ {0xA175,0x300c}, // LEFT CORNER BRACKET
+ {0xA176,0x300d}, // RIGHT CORNER BRACKET
+ {0xA177,0xfe41}, // PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET
+ {0xA178,0xfe42}, // PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET
+ {0xA179,0x300e}, // LEFT WHITE CORNER BRACKET
+ {0xA17A,0x300f}, // RIGHT WHITE CORNER BRACKET
+ {0xA17B,0xfe43}, // PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET
+ {0xA17C,0xfe44}, // PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
+ {0xA17D,0xfe59}, // SMALL LEFT PARENTHESIS
+ {0xA17E,0xfe5a}, // SMALL RIGHT PARENTHESIS
+ {0xA1A1,0xfe5b}, // SMALL LEFT CURLY BRACKET
+ {0xA1A2,0xfe5c}, // SMALL RIGHT CURLY BRACKET
+ {0xA1A3,0xfe5d}, // SMALL LEFT TORTOISE SHELL BRACKET
+ {0xA1A4,0xfe5e}, // SMALL RIGHT TORTOISE SHELL BRACKET
+ {0xA1A5,0x2018}, // LEFT SINGLE QUOTATION MARK
+ {0xA1A6,0x2019}, // RIGHT SINGLE QUOTATION MARK
+ {0xA1A7,0x201c}, // LEFT DOUBLE QUOTATION MARK
+ {0xA1A8,0x201d}, // RIGHT DOUBLE QUOTATION MARK
+ {0xA1A9,0x301d}, // REVERSED DOUBLE PRIME QUOTATION MARK
+ {0xA1AA,0x301e}, // DOUBLE PRIME QUOTATION MARK
+ {0xA1AB,0x2035}, // REVERSED PRIME
+ {0xA1AC,0x2032}, // PRIME
+ {0xA1AD,0xff03}, // FULLWIDTH NUMBER SIGN
+ {0xA1AE,0xff06}, // FULLWIDTH AMPERSAND
+ {0xA1AF,0xff0a}, // FULLWIDTH ASTERISK
+ {0xA1B0,0x203b}, // REFERENCE MARK
+ {0xA1B1,0x00a7}, // SECTION SIGN
+ {0xA1B2,0x3003}, // DITTO MARK
+ {0xA1B3,0x25cb}, // WHITE CIRCLE
+ {0xA1B4,0x25cf}, // BLACK CIRCLE
+ {0xA1B5,0x25b3}, // WHITE UP-POINTING TRIANGLE
+ {0xA1B6,0x25b2}, // BLACK UP-POINTING TRIANGLE
+ {0xA1B7,0x25ce}, // BULLSEYE
+ {0xA1B8,0x2606}, // WHITE STAR
+ {0xA1B9,0x2605}, // BLACK STAR
+ {0xA1BA,0x25c7}, // WHITE DIAMOND
+ {0xA1BB,0x25c6}, // BLACK DIAMOND
+ {0xA1BC,0x25a1}, // WHITE SQUARE
+ {0xA1BD,0x25a0}, // BLACK SQUARE
+ {0xA1BE,0x25bd}, // WHITE DOWN-POINTING TRIANGLE
+ {0xA1BF,0x25bc}, // BLACK DOWN-POINTING TRIANGLE
+ {0xA1C0,0x32a3}, // CIRCLED IDEOGRAPH CORRECT
+ {0xA1C1,0x2105}, // CARE OF
+ {0xA1C2,0x00af}, // MACRON [MOD20210517] changed from 0x203E # OVERLINE
+ {0xA1C3,0xffe3}, // FULLWIDTH MACRON [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA1C4,0xff3f}, // FULLWIDTH LOW LINE
+ {0xA1C5,0x02cd}, // MODIFIER LETTER LOW MACRON [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA1C6,0xfe49}, // DASHED OVERLINE
+ {0xA1C7,0xfe4a}, // CENTRELINE OVERLINE
+ {0xA1C8,0xfe4d}, // DASHED LOW LINE
+ {0xA1C9,0xfe4e}, // CENTRELINE LOW LINE
+ {0xA1CA,0xfe4b}, // WAVY OVERLINE
+ {0xA1CB,0xfe4c}, // DOUBLE WAVY OVERLINE
+ {0xA1CC,0xfe5f}, // SMALL NUMBER SIGN
+ {0xA1CD,0xfe60}, // SMALL AMPERSAND
+ {0xA1CE,0xfe61}, // SMALL ASTERISK
+ {0xA1CF,0xff0b}, // FULLWIDTH PLUS SIGN
+ {0xA1D0,0xff0d}, // FULLWIDTH HYPHEN-MINUS
+ {0xA1D1,0x00d7}, // MULTIPLICATION SIGN
+ {0xA1D2,0x00f7}, // DIVISION SIGN
+ {0xA1D3,0x00b1}, // PLUS-MINUS SIGN
+ {0xA1D4,0x221a}, // SQUARE ROOT
+ {0xA1D5,0xff1c}, // FULLWIDTH LESS-THAN SIGN
+ {0xA1D6,0xff1e}, // FULLWIDTH GREATER-THAN SIGN
+ {0xA1D7,0xff1d}, // FULLWIDTH EQUALS SIGN
+ {0xA1D8,0x2266}, // LESS THAN OVER EQUAL TO
+ {0xA1D9,0x2267}, // GREATER THAN OVER EQUAL TO
+ {0xA1DA,0x2260}, // NOT EQUAL TO
+ {0xA1DB,0x221e}, // INFINITY
+ {0xA1DC,0x2252}, // APPROXIMATELY EQUAL TO OR THE IMAGE OF
+ {0xA1DD,0x2261}, // IDENTICAL TO
+ {0xA1DE,0xfe62}, // SMALL PLUS SIGN
+ {0xA1DF,0xfe63}, // SMALL HYPHEN-MINUS
+ {0xA1E0,0xfe64}, // SMALL LESS-THAN SIGN
+ {0xA1E1,0xfe65}, // SMALL GREATER-THAN SIGN
+ {0xA1E2,0xfe66}, // SMALL EQUALS SIGN
+ {0xA1E3,0xff5e}, // YI SYLLABLE GAP [MOD20210517] changed from 0x223C # TILDE OPERATOR
+ {0xA1E4,0x2229}, // INTERSECTION
+ {0xA1E5,0x222a}, // UNION
+ {0xA1E6,0x22a5}, // UP TACK
+ {0xA1E7,0x2220}, // ANGLE
+ {0xA1E8,0x221f}, // RIGHT ANGLE
+ {0xA1E9,0x22bf}, // RIGHT TRIANGLE
+ {0xA1EA,0x33d2}, // SQUARE LOG
+ {0xA1EB,0x33d1}, // SQUARE LN
+ {0xA1EC,0x222b}, // INTEGRAL
+ {0xA1ED,0x222e}, // CONTOUR INTEGRAL
+ {0xA1EE,0x2235}, // BECAUSE
+ {0xA1EF,0x2234}, // THEREFORE
+ {0xA1F0,0x2640}, // FEMALE SIGN
+ {0xA1F1,0x2642}, // MALE SIGN
+ {0xA1F2,0x2295}, // CIRCLED PLUS [MOD20210517] changed from 0x2641 # EARTH
+ {0xA1F3,0x2299}, // CIRCLED DOT OPERATOR [MOD20210517] changed from 0x2609 # SUN
+ {0xA1F4,0x2191}, // UPWARDS ARROW
+ {0xA1F5,0x2193}, // DOWNWARDS ARROW
+ {0xA1F6,0x2190}, // LEFTWARDS ARROW
+ {0xA1F7,0x2192}, // RIGHTWARDS ARROW
+ {0xA1F8,0x2196}, // NORTH WEST ARROW
+ {0xA1F9,0x2197}, // NORTH EAST ARROW
+ {0xA1FA,0x2199}, // SOUTH WEST ARROW
+ {0xA1FB,0x2198}, // SOUTH EAST ARROW
+ {0xA1FC,0x2225}, // PARALLEL TO
+ {0xA1FD,0x2223}, // DIVIDES
+ {0xA1FE,0xff0f}, // FULLWIDTH SOLIDUS [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA240,0xff3c}, // FULLWIDTH REVERSE SOLIDUS [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA241,0x2215}, // DIVISION SLASH [MOD20210517] changed from 0xFF0F # FULLWIDTH SOLIDUS
+ {0xA242,0xfe68}, // SMALL REVERSE SOLIDUS [MOD20210517] changed from 0xFF3C # FULLWIDTH REVERSE SOLIDUS
+ {0xA243,0xff04}, // FULLWIDTH DOLLAR SIGN
+ {0xA244,0xffe5}, // FULLWIDTH YEN SIGN [MOD20210517] changed from 0x00A5 # YEN SIGN
+ {0xA245,0x3012}, // POSTAL MARK
+ {0xA246,0xffe0}, // FULLWIDTH CENT SIGN [MOD20210517] changed from 0x00A2 # CENT SIGN
+ {0xA247,0xffe1}, // FULLWIDTH POUND SIGN [MOD20210517] changed from 0x00A3 # POUND SIGN
+ {0xA248,0xff05}, // FULLWIDTH PERCENT SIGN
+ {0xA249,0xff20}, // FULLWIDTH COMMERCIAL AT
+ {0xA24A,0x2103}, // DEGREE CELSIUS
+ {0xA24B,0x2109}, // DEGREE FAHRENHEIT
+ {0xA24C,0xfe69}, // SMALL DOLLAR SIGN
+ {0xA24D,0xfe6a}, // SMALL PERCENT SIGN
+ {0xA24E,0xfe6b}, // SMALL COMMERCIAL AT
+ {0xA24F,0x33d5}, // SQUARE MIL
+ {0xA250,0x339c}, // SQUARE MM
+ {0xA251,0x339d}, // SQUARE CM
+ {0xA252,0x339e}, // SQUARE KM
+ {0xA253,0x33ce}, // SQUARE KM CAPITAL
+ {0xA254,0x33a1}, // SQUARE M SQUARED
+ {0xA255,0x338e}, // SQUARE MG
+ {0xA256,0x338f}, // SQUARE KG
+ {0xA257,0x33c4}, // SQUARE CC
+ {0xA258,0x00b0}, // DEGREE SIGN
+ {0xA259,0x5159}, //
+ {0xA25A,0x515b}, //
+ {0xA25B,0x515e}, //
+ {0xA25C,0x515d}, //
+ {0xA25D,0x5161}, //
+ {0xA25E,0x5163}, //
+ {0xA25F,0x55e7}, //
+ {0xA260,0x74e9}, //
+ {0xA261,0x7cce}, //
+ {0xA262,0x2581}, // LOWER ONE EIGHTH BLOCK
+ {0xA263,0x2582}, // LOWER ONE QUARTER BLOCK
+ {0xA264,0x2583}, // LOWER THREE EIGHTHS BLOCK
+ {0xA265,0x2584}, // LOWER HALF BLOCK
+ {0xA266,0x2585}, // LOWER FIVE EIGHTHS BLOCK
+ {0xA267,0x2586}, // LOWER THREE QUARTERS BLOCK
+ {0xA268,0x2587}, // LOWER SEVEN EIGHTHS BLOCK
+ {0xA269,0x2588}, // FULL BLOCK
+ {0xA26A,0x258f}, // LEFT ONE EIGHTH BLOCK
+ {0xA26B,0x258e}, // LEFT ONE QUARTER BLOCK
+ {0xA26C,0x258d}, // LEFT THREE EIGHTHS BLOCK
+ {0xA26D,0x258c}, // LEFT HALF BLOCK
+ {0xA26E,0x258b}, // LEFT FIVE EIGHTHS BLOCK
+ {0xA26F,0x258a}, // LEFT THREE QUARTERS BLOCK
+ {0xA270,0x2589}, // LEFT SEVEN EIGHTHS BLOCK
+ {0xA271,0x253c}, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ {0xA272,0x2534}, // BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ {0xA273,0x252c}, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ {0xA274,0x2524}, // BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ {0xA275,0x251c}, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ {0xA276,0x2594}, // UPPER ONE EIGHTH BLOCK
+ {0xA277,0x2500}, // BOX DRAWINGS LIGHT HORIZONTAL
+ {0xA278,0x2502}, // BOX DRAWINGS LIGHT VERTICAL
+ {0xA279,0x2595}, // RIGHT ONE EIGHTH BLOCK
+ {0xA27A,0x250c}, // BOX DRAWINGS LIGHT DOWN AND RIGHT
+ {0xA27B,0x2510}, // BOX DRAWINGS LIGHT DOWN AND LEFT
+ {0xA27C,0x2514}, // BOX DRAWINGS LIGHT UP AND RIGHT
+ {0xA27D,0x2518}, // BOX DRAWINGS LIGHT UP AND LEFT
+ {0xA27E,0x256d}, // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
+ {0xA2A1,0x256e}, // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
+ {0xA2A2,0x2570}, // BOX DRAWINGS LIGHT ARC UP AND RIGHT
+ {0xA2A3,0x256f}, // BOX DRAWINGS LIGHT ARC UP AND LEFT
+ {0xA2A4,0x2550}, // BOX DRAWINGS DOUBLE HORIZONTAL
+ {0xA2A5,0x255e}, // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ {0xA2A6,0x256a}, // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ {0xA2A7,0x2561}, // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ {0xA2A8,0x25e2}, // BLACK LOWER RIGHT TRIANGLE
+ {0xA2A9,0x25e3}, // BLACK LOWER LEFT TRIANGLE
+ {0xA2AA,0x25e5}, // BLACK UPPER RIGHT TRIANGLE
+ {0xA2AB,0x25e4}, // BLACK UPPER LEFT TRIANGLE
+ {0xA2AC,0x2571}, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
+ {0xA2AD,0x2572}, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
+ {0xA2AE,0x2573}, // BOX DRAWINGS LIGHT DIAGONAL CROSS
+ {0xA2AF,0xff10}, // FULLWIDTH DIGIT ZERO
+ {0xA2B0,0xff11}, // FULLWIDTH DIGIT ONE
+ {0xA2B1,0xff12}, // FULLWIDTH DIGIT TWO
+ {0xA2B2,0xff13}, // FULLWIDTH DIGIT THREE
+ {0xA2B3,0xff14}, // FULLWIDTH DIGIT FOUR
+ {0xA2B4,0xff15}, // FULLWIDTH DIGIT FIVE
+ {0xA2B5,0xff16}, // FULLWIDTH DIGIT SIX
+ {0xA2B6,0xff17}, // FULLWIDTH DIGIT SEVEN
+ {0xA2B7,0xff18}, // FULLWIDTH DIGIT EIGHT
+ {0xA2B8,0xff19}, // FULLWIDTH DIGIT NINE
+ {0xA2B9,0x2160}, // ROMAN NUMERAL ONE
+ {0xA2BA,0x2161}, // ROMAN NUMERAL TWO
+ {0xA2BB,0x2162}, // ROMAN NUMERAL THREE
+ {0xA2BC,0x2163}, // ROMAN NUMERAL FOUR
+ {0xA2BD,0x2164}, // ROMAN NUMERAL FIVE
+ {0xA2BE,0x2165}, // ROMAN NUMERAL SIX
+ {0xA2BF,0x2166}, // ROMAN NUMERAL SEVEN
+ {0xA2C0,0x2167}, // ROMAN NUMERAL EIGHT
+ {0xA2C1,0x2168}, // ROMAN NUMERAL NINE
+ {0xA2C2,0x2169}, // ROMAN NUMERAL TEN
+ {0xA2C3,0x3021}, // HANGZHOU NUMERAL ONE
+ {0xA2C4,0x3022}, // HANGZHOU NUMERAL TWO
+ {0xA2C5,0x3023}, // HANGZHOU NUMERAL THREE
+ {0xA2C6,0x3024}, // HANGZHOU NUMERAL FOUR
+ {0xA2C7,0x3025}, // HANGZHOU NUMERAL FIVE
+ {0xA2C8,0x3026}, // HANGZHOU NUMERAL SIX
+ {0xA2C9,0x3027}, // HANGZHOU NUMERAL SEVEN
+ {0xA2CA,0x3028}, // HANGZHOU NUMERAL EIGHT
+ {0xA2CB,0x3029}, // HANGZHOU NUMERAL NINE
+ {0xA2CC,0x5341}, // [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA2CD,0x5344}, //
+ {0xA2CE,0x5345}, // [MOD20210517] changed from 0xFFFD # *** NO MAPPING ***
+ {0xA2CF,0xff21}, // FULLWIDTH LATIN CAPITAL LETTER A
+ {0xA2D0,0xff22}, // FULLWIDTH LATIN CAPITAL LETTER B
+ {0xA2D1,0xff23}, // FULLWIDTH LATIN CAPITAL LETTER C
+ {0xA2D2,0xff24}, // FULLWIDTH LATIN CAPITAL LETTER D
+ {0xA2D3,0xff25}, // FULLWIDTH LATIN CAPITAL LETTER E
+ {0xA2D4,0xff26}, // FULLWIDTH LATIN CAPITAL LETTER F
+ {0xA2D5,0xff27}, // FULLWIDTH LATIN CAPITAL LETTER G
+ {0xA2D6,0xff28}, // FULLWIDTH LATIN CAPITAL LETTER H
+ {0xA2D7,0xff29}, // FULLWIDTH LATIN CAPITAL LETTER I
+ {0xA2D8,0xff2a}, // FULLWIDTH LATIN CAPITAL LETTER J
+ {0xA2D9,0xff2b}, // FULLWIDTH LATIN CAPITAL LETTER K
+ {0xA2DA,0xff2c}, // FULLWIDTH LATIN CAPITAL LETTER L
+ {0xA2DB,0xff2d}, // FULLWIDTH LATIN CAPITAL LETTER M
+ {0xA2DC,0xff2e}, // FULLWIDTH LATIN CAPITAL LETTER N
+ {0xA2DD,0xff2f}, // FULLWIDTH LATIN CAPITAL LETTER O
+ {0xA2DE,0xff30}, // FULLWIDTH LATIN CAPITAL LETTER P
+ {0xA2DF,0xff31}, // FULLWIDTH LATIN CAPITAL LETTER Q
+ {0xA2E0,0xff32}, // FULLWIDTH LATIN CAPITAL LETTER R
+ {0xA2E1,0xff33}, // FULLWIDTH LATIN CAPITAL LETTER S
+ {0xA2E2,0xff34}, // FULLWIDTH LATIN CAPITAL LETTER T
+ {0xA2E3,0xff35}, // FULLWIDTH LATIN CAPITAL LETTER U
+ {0xA2E4,0xff36}, // FULLWIDTH LATIN CAPITAL LETTER V
+ {0xA2E5,0xff37}, // FULLWIDTH LATIN CAPITAL LETTER W
+ {0xA2E6,0xff38}, // FULLWIDTH LATIN CAPITAL LETTER X
+ {0xA2E7,0xff39}, // FULLWIDTH LATIN CAPITAL LETTER Y
+ {0xA2E8,0xff3a}, // FULLWIDTH LATIN CAPITAL LETTER Z
+ {0xA2E9,0xff41}, // FULLWIDTH LATIN SMALL LETTER A
+ {0xA2EA,0xff42}, // FULLWIDTH LATIN SMALL LETTER B
+ {0xA2EB,0xff43}, // FULLWIDTH LATIN SMALL LETTER C
+ {0xA2EC,0xff44}, // FULLWIDTH LATIN SMALL LETTER D
+ {0xA2ED,0xff45}, // FULLWIDTH LATIN SMALL LETTER E
+ {0xA2EE,0xff46}, // FULLWIDTH LATIN SMALL LETTER F
+ {0xA2EF,0xff47}, // FULLWIDTH LATIN SMALL LETTER G
+ {0xA2F0,0xff48}, // FULLWIDTH LATIN SMALL LETTER H
+ {0xA2F1,0xff49}, // FULLWIDTH LATIN SMALL LETTER I
+ {0xA2F2,0xff4a}, // FULLWIDTH LATIN SMALL LETTER J
+ {0xA2F3,0xff4b}, // FULLWIDTH LATIN SMALL LETTER K
+ {0xA2F4,0xff4c}, // FULLWIDTH LATIN SMALL LETTER L
+ {0xA2F5,0xff4d}, // FULLWIDTH LATIN SMALL LETTER M
+ {0xA2F6,0xff4e}, // FULLWIDTH LATIN SMALL LETTER N
+ {0xA2F7,0xff4f}, // FULLWIDTH LATIN SMALL LETTER O
+ {0xA2F8,0xff50}, // FULLWIDTH LATIN SMALL LETTER P
+ {0xA2F9,0xff51}, // FULLWIDTH LATIN SMALL LETTER Q
+ {0xA2FA,0xff52}, // FULLWIDTH LATIN SMALL LETTER R
+ {0xA2FB,0xff53}, // FULLWIDTH LATIN SMALL LETTER S
+ {0xA2FC,0xff54}, // FULLWIDTH LATIN SMALL LETTER T
+ {0xA2FD,0xff55}, // FULLWIDTH LATIN SMALL LETTER U
+ {0xA2FE,0xff56}, // FULLWIDTH LATIN SMALL LETTER V
+ {0xA340,0xff57}, // FULLWIDTH LATIN SMALL LETTER W
+ {0xA341,0xff58}, // FULLWIDTH LATIN SMALL LETTER X
+ {0xA342,0xff59}, // FULLWIDTH LATIN SMALL LETTER Y
+ {0xA343,0xff5a}, // FULLWIDTH LATIN SMALL LETTER Z
+ {0xA344,0x0391}, // GREEK CAPITAL LETTER ALPHA
+ {0xA345,0x0392}, // GREEK CAPITAL LETTER BETA
+ {0xA346,0x0393}, // GREEK CAPITAL LETTER GAMMA
+ {0xA347,0x0394}, // GREEK CAPITAL LETTER DELTA
+ {0xA348,0x0395}, // GREEK CAPITAL LETTER EPSILON
+ {0xA349,0x0396}, // GREEK CAPITAL LETTER ZETA
+ {0xA34A,0x0397}, // GREEK CAPITAL LETTER ETA
+ {0xA34B,0x0398}, // GREEK CAPITAL LETTER THETA
+ {0xA34C,0x0399}, // GREEK CAPITAL LETTER IOTA
+ {0xA34D,0x039a}, // GREEK CAPITAL LETTER KAPPA
+ {0xA34E,0x039b}, // GREEK CAPITAL LETTER LAMDA
+ {0xA34F,0x039c}, // GREEK CAPITAL LETTER MU
+ {0xA350,0x039d}, // GREEK CAPITAL LETTER NU
+ {0xA351,0x039e}, // GREEK CAPITAL LETTER XI
+ {0xA352,0x039f}, // GREEK CAPITAL LETTER OMICRON
+ {0xA353,0x03a0}, // GREEK CAPITAL LETTER PI
+ {0xA354,0x03a1}, // GREEK CAPITAL LETTER RHO
+ {0xA355,0x03a3}, // GREEK CAPITAL LETTER SIGMA
+ {0xA356,0x03a4}, // GREEK CAPITAL LETTER TAU
+ {0xA357,0x03a5}, // GREEK CAPITAL LETTER UPSILON
+ {0xA358,0x03a6}, // GREEK CAPITAL LETTER PHI
+ {0xA359,0x03a7}, // GREEK CAPITAL LETTER CHI
+ {0xA35A,0x03a8}, // GREEK CAPITAL LETTER PSI
+ {0xA35B,0x03a9}, // GREEK CAPITAL LETTER OMEGA
+ {0xA35C,0x03b1}, // GREEK SMALL LETTER ALPHA
+ {0xA35D,0x03b2}, // GREEK SMALL LETTER BETA
+ {0xA35E,0x03b3}, // GREEK SMALL LETTER GAMMA
+ {0xA35F,0x03b4}, // GREEK SMALL LETTER DELTA
+ {0xA360,0x03b5}, // GREEK SMALL LETTER EPSILON
+ {0xA361,0x03b6}, // GREEK SMALL LETTER ZETA
+ {0xA362,0x03b7}, // GREEK SMALL LETTER ETA
+ {0xA363,0x03b8}, // GREEK SMALL LETTER THETA
+ {0xA364,0x03b9}, // GREEK SMALL LETTER IOTA
+ {0xA365,0x03ba}, // GREEK SMALL LETTER KAPPA
+ {0xA366,0x03bb}, // GREEK SMALL LETTER LAMDA
+ {0xA367,0x03bc}, // GREEK SMALL LETTER MU
+ {0xA368,0x03bd}, // GREEK SMALL LETTER NU
+ {0xA369,0x03be}, // GREEK SMALL LETTER XI
+ {0xA36A,0x03bf}, // GREEK SMALL LETTER OMICRON
+ {0xA36B,0x03c0}, // GREEK SMALL LETTER PI
+ {0xA36C,0x03c1}, // GREEK SMALL LETTER RHO
+ {0xA36D,0x03c3}, // GREEK SMALL LETTER SIGMA
+ {0xA36E,0x03c4}, // GREEK SMALL LETTER TAU
+ {0xA36F,0x03c5}, // GREEK SMALL LETTER UPSILON
+ {0xA370,0x03c6}, // GREEK SMALL LETTER PHI
+ {0xA371,0x03c7}, // GREEK SMALL LETTER CHI
+ {0xA372,0x03c8}, // GREEK SMALL LETTER PSI
+ {0xA373,0x03c9}, // GREEK SMALL LETTER OMEGA
+ {0xA374,0x3105}, // BOPOMOFO LETTER B
+ {0xA375,0x3106}, // BOPOMOFO LETTER P
+ {0xA376,0x3107}, // BOPOMOFO LETTER M
+ {0xA377,0x3108}, // BOPOMOFO LETTER F
+ {0xA378,0x3109}, // BOPOMOFO LETTER D
+ {0xA379,0x310a}, // BOPOMOFO LETTER T
+ {0xA37A,0x310b}, // BOPOMOFO LETTER N
+ {0xA37B,0x310c}, // BOPOMOFO LETTER L
+ {0xA37C,0x310d}, // BOPOMOFO LETTER G
+ {0xA37D,0x310e}, // BOPOMOFO LETTER K
+ {0xA37E,0x310f}, // BOPOMOFO LETTER H
+ {0xA3A1,0x3110}, // BOPOMOFO LETTER J
+ {0xA3A2,0x3111}, // BOPOMOFO LETTER Q
+ {0xA3A3,0x3112}, // BOPOMOFO LETTER X
+ {0xA3A4,0x3113}, // BOPOMOFO LETTER ZH
+ {0xA3A5,0x3114}, // BOPOMOFO LETTER CH
+ {0xA3A6,0x3115}, // BOPOMOFO LETTER SH
+ {0xA3A7,0x3116}, // BOPOMOFO LETTER R
+ {0xA3A8,0x3117}, // BOPOMOFO LETTER Z
+ {0xA3A9,0x3118}, // BOPOMOFO LETTER C
+ {0xA3AA,0x3119}, // BOPOMOFO LETTER S
+ {0xA3AB,0x311a}, // BOPOMOFO LETTER A
+ {0xA3AC,0x311b}, // BOPOMOFO LETTER O
+ {0xA3AD,0x311c}, // BOPOMOFO LETTER E
+ {0xA3AE,0x311d}, // BOPOMOFO LETTER EH
+ {0xA3AF,0x311e}, // BOPOMOFO LETTER AI
+ {0xA3B0,0x311f}, // BOPOMOFO LETTER EI
+ {0xA3B1,0x3120}, // BOPOMOFO LETTER AU
+ {0xA3B2,0x3121}, // BOPOMOFO LETTER OU
+ {0xA3B3,0x3122}, // BOPOMOFO LETTER AN
+ {0xA3B4,0x3123}, // BOPOMOFO LETTER EN
+ {0xA3B5,0x3124}, // BOPOMOFO LETTER ANG
+ {0xA3B6,0x3125}, // BOPOMOFO LETTER ENG
+ {0xA3B7,0x3126}, // BOPOMOFO LETTER ER
+ {0xA3B8,0x3127}, // BOPOMOFO LETTER I
+ {0xA3B9,0x3128}, // BOPOMOFO LETTER U
+ {0xA3BA,0x3129}, // BOPOMOFO LETTER IU
+ {0xA3BB,0x02d9}, // DOT ABOVE (Mandarin Chinese light tone)
+ {0xA3BC,0x02c9}, // MODIFIER LETTER MACRON (Mandarin Chinese first tone)
+ {0xA3BD,0x02ca}, // MODIFIER LETTER ACUTE ACCENT (Mandarin Chinese second tone)
+ {0xA3BE,0x02c7}, // CARON (Mandarin Chinese third tone)
+ {0xA3BF,0x02cb}, // MODIFIER LETTER GRAVE ACCENT (Mandarin Chinese fourth tone)
+ {0xA3E1,0x20ac}, // EURO SIGN [MOD20210517] Old versions of Windows code page 950 addition
+ {0xA440,0x4e00}, //
+ {0xA441,0x4e59}, //
+ {0xA442,0x4e01}, //
+ {0xA443,0x4e03}, //
+ {0xA444,0x4e43}, //
+ {0xA445,0x4e5d}, //
+ {0xA446,0x4e86}, //
+ {0xA447,0x4e8c}, //
+ {0xA448,0x4eba}, //
+ {0xA449,0x513f}, //
+ {0xA44A,0x5165}, //
+ {0xA44B,0x516b}, //
+ {0xA44C,0x51e0}, //
+ {0xA44D,0x5200}, //
+ {0xA44E,0x5201}, //
+ {0xA44F,0x529b}, //
+ {0xA450,0x5315}, //
+ {0xA451,0x5341}, //
+ {0xA452,0x535c}, //
+ {0xA453,0x53c8}, //
+ {0xA454,0x4e09}, //
+ {0xA455,0x4e0b}, //
+ {0xA456,0x4e08}, //
+ {0xA457,0x4e0a}, //
+ {0xA458,0x4e2b}, //
+ {0xA459,0x4e38}, //
+ {0xA45A,0x51e1}, //
+ {0xA45B,0x4e45}, //
+ {0xA45C,0x4e48}, //
+ {0xA45D,0x4e5f}, //
+ {0xA45E,0x4e5e}, //
+ {0xA45F,0x4e8e}, //
+ {0xA460,0x4ea1}, //
+ {0xA461,0x5140}, //
+ {0xA462,0x5203}, //
+ {0xA463,0x52fa}, //
+ {0xA464,0x5343}, //
+ {0xA465,0x53c9}, //
+ {0xA466,0x53e3}, //
+ {0xA467,0x571f}, //
+ {0xA468,0x58eb}, //
+ {0xA469,0x5915}, //
+ {0xA46A,0x5927}, //
+ {0xA46B,0x5973}, //
+ {0xA46C,0x5b50}, //
+ {0xA46D,0x5b51}, //
+ {0xA46E,0x5b53}, //
+ {0xA46F,0x5bf8}, //
+ {0xA470,0x5c0f}, //
+ {0xA471,0x5c22}, //
+ {0xA472,0x5c38}, //
+ {0xA473,0x5c71}, //
+ {0xA474,0x5ddd}, //
+ {0xA475,0x5de5}, //
+ {0xA476,0x5df1}, //
+ {0xA477,0x5df2}, //
+ {0xA478,0x5df3}, //
+ {0xA479,0x5dfe}, //
+ {0xA47A,0x5e72}, //
+ {0xA47B,0x5efe}, //
+ {0xA47C,0x5f0b}, //
+ {0xA47D,0x5f13}, //
+ {0xA47E,0x624d}, //
+ {0xA4A1,0x4e11}, //
+ {0xA4A2,0x4e10}, //
+ {0xA4A3,0x4e0d}, //
+ {0xA4A4,0x4e2d}, //
+ {0xA4A5,0x4e30}, //
+ {0xA4A6,0x4e39}, //
+ {0xA4A7,0x4e4b}, //
+ {0xA4A8,0x5c39}, //
+ {0xA4A9,0x4e88}, //
+ {0xA4AA,0x4e91}, //
+ {0xA4AB,0x4e95}, //
+ {0xA4AC,0x4e92}, //
+ {0xA4AD,0x4e94}, //
+ {0xA4AE,0x4ea2}, //
+ {0xA4AF,0x4ec1}, //
+ {0xA4B0,0x4ec0}, //
+ {0xA4B1,0x4ec3}, //
+ {0xA4B2,0x4ec6}, //
+ {0xA4B3,0x4ec7}, //
+ {0xA4B4,0x4ecd}, //
+ {0xA4B5,0x4eca}, //
+ {0xA4B6,0x4ecb}, //
+ {0xA4B7,0x4ec4}, //
+ {0xA4B8,0x5143}, //
+ {0xA4B9,0x5141}, //
+ {0xA4BA,0x5167}, //
+ {0xA4BB,0x516d}, //
+ {0xA4BC,0x516e}, //
+ {0xA4BD,0x516c}, //
+ {0xA4BE,0x5197}, //
+ {0xA4BF,0x51f6}, //
+ {0xA4C0,0x5206}, //
+ {0xA4C1,0x5207}, //
+ {0xA4C2,0x5208}, //
+ {0xA4C3,0x52fb}, //
+ {0xA4C4,0x52fe}, //
+ {0xA4C5,0x52ff}, //
+ {0xA4C6,0x5316}, //
+ {0xA4C7,0x5339}, //
+ {0xA4C8,0x5348}, //
+ {0xA4C9,0x5347}, //
+ {0xA4CA,0x5345}, //
+ {0xA4CB,0x535e}, //
+ {0xA4CC,0x5384}, //
+ {0xA4CD,0x53cb}, //
+ {0xA4CE,0x53ca}, //
+ {0xA4CF,0x53cd}, //
+ {0xA4D0,0x58ec}, //
+ {0xA4D1,0x5929}, //
+ {0xA4D2,0x592b}, //
+ {0xA4D3,0x592a}, //
+ {0xA4D4,0x592d}, //
+ {0xA4D5,0x5b54}, //
+ {0xA4D6,0x5c11}, //
+ {0xA4D7,0x5c24}, //
+ {0xA4D8,0x5c3a}, //
+ {0xA4D9,0x5c6f}, //
+ {0xA4DA,0x5df4}, //
+ {0xA4DB,0x5e7b}, //
+ {0xA4DC,0x5eff}, //
+ {0xA4DD,0x5f14}, //
+ {0xA4DE,0x5f15}, //
+ {0xA4DF,0x5fc3}, //
+ {0xA4E0,0x6208}, //
+ {0xA4E1,0x6236}, //
+ {0xA4E2,0x624b}, //
+ {0xA4E3,0x624e}, //
+ {0xA4E4,0x652f}, //
+ {0xA4E5,0x6587}, //
+ {0xA4E6,0x6597}, //
+ {0xA4E7,0x65a4}, //
+ {0xA4E8,0x65b9}, //
+ {0xA4E9,0x65e5}, //
+ {0xA4EA,0x66f0}, //
+ {0xA4EB,0x6708}, //
+ {0xA4EC,0x6728}, //
+ {0xA4ED,0x6b20}, //
+ {0xA4EE,0x6b62}, //
+ {0xA4EF,0x6b79}, //
+ {0xA4F0,0x6bcb}, //
+ {0xA4F1,0x6bd4}, //
+ {0xA4F2,0x6bdb}, //
+ {0xA4F3,0x6c0f}, //
+ {0xA4F4,0x6c34}, //
+ {0xA4F5,0x706b}, //
+ {0xA4F6,0x722a}, //
+ {0xA4F7,0x7236}, //
+ {0xA4F8,0x723b}, //
+ {0xA4F9,0x7247}, //
+ {0xA4FA,0x7259}, //
+ {0xA4FB,0x725b}, //
+ {0xA4FC,0x72ac}, //
+ {0xA4FD,0x738b}, //
+ {0xA4FE,0x4e19}, //
+ {0xA540,0x4e16}, //
+ {0xA541,0x4e15}, //
+ {0xA542,0x4e14}, //
+ {0xA543,0x4e18}, //
+ {0xA544,0x4e3b}, //
+ {0xA545,0x4e4d}, //
+ {0xA546,0x4e4f}, //
+ {0xA547,0x4e4e}, //
+ {0xA548,0x4ee5}, //
+ {0xA549,0x4ed8}, //