text: /* $NoKeywords: $ */
  /*
  //
  // Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
  // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
  // McNeel & Associates.
  //
  // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
  // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
  // MERCHANTABILITY ARE HEREBY DISCLAIMED.
  //				
  // For complete openNURBS copyright information see <http://www.opennurbs.org>.
  //
  ////////////////////////////////////////////////////////////////
  */
  #include "opennurbs.h"
  
  #if !defined(ON_COMPILING_OPENNURBS)
  // This check is included in all opennurbs source .c and .cpp files to insure
  // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
  // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
  // and the opennurbs .h files alter what is declared and how it is declared.
  #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
  #endif
  
  class ON_OBSOLETE_V5_HatchExtra : public ON_UserData
  {
    //March 23, 2008 - LW
    //Adding ON_OBSOLETE_V5_HatchExtra class to support movable base point for hatches
    //This should be combined with the ON_Hatch class next time that is possible
    // Don't put this extension class in a header file or export it.
  
    ON_OBJECT_DECLARE(ON_OBSOLETE_V5_HatchExtra);
  
  public:
    ON_OBSOLETE_V5_HatchExtra();
    ~ON_OBSOLETE_V5_HatchExtra() = default;
    ON_OBSOLETE_V5_HatchExtra(const ON_OBSOLETE_V5_HatchExtra&) = default;
    ON_OBSOLETE_V5_HatchExtra& operator=(const ON_OBSOLETE_V5_HatchExtra&) = default;
  
  public:
    static ON_OBSOLETE_V5_HatchExtra* HatchExtension(const ON_Hatch* pHatch);
  
    // override virtual ON_Object::Dump function
    void Dump( ON_TextLog& text_log ) const override;
  
    // override virtual ON_Object::SizeOf function
    unsigned int SizeOf() const override;
  
    // override virtual ON_Object::Write function
    bool Write(ON_BinaryArchive& binary_archive) const override;
  
    // override virtual ON_Object::Read function
    bool Read(ON_BinaryArchive& binary_archive) override;
  
    // override virtual ON_UserData::GetDescription function
    bool GetDescription( ON_wString& description ) override;
  
    // override virtual ON_UserData::Archive function
    bool Archive() const override; 
  
    bool WriteToArchive(
      const class ON_BinaryArchive& archive,
      const class ON_Object* parent_object
      ) const override;
  
    bool DeleteAfterWrite(
      const class ON_BinaryArchive& archive,
      const class ON_Object* parent_object
    ) const override;
  
    bool DeleteAfterRead(
      const class ON_BinaryArchive& archive,
      class ON_Object* parent_object
    ) const override;
  
    ON_2dPoint m_basepoint = ON_2dPoint::Origin;
  };
  
  ON_OBJECT_IMPLEMENT(ON_OBSOLETE_V5_HatchExtra,ON_UserData,"3FF7007C-3D04-463f-84E3-132ACEB91062");
  
  ON_OBSOLETE_V5_HatchExtra* ON_OBSOLETE_V5_HatchExtra::HatchExtension(const ON_Hatch* pHatch)
  {
    ON_OBSOLETE_V5_HatchExtra* pExtra = nullptr;
    if(pHatch)
    {
      pExtra = ON_OBSOLETE_V5_HatchExtra::Cast(pHatch->GetUserData(ON_CLASS_ID(ON_OBSOLETE_V5_HatchExtra)));
      if(pExtra == nullptr)
      {
        pExtra = new ON_OBSOLETE_V5_HatchExtra;
        if(pExtra)
        {
          if(!const_cast<ON_Hatch*>(pHatch)->AttachUserData(pExtra))
          {
            delete pExtra;
            pExtra = nullptr;
          }
        }  
      }
    }
    return pExtra;
  }
  
  ON_OBSOLETE_V5_HatchExtra::ON_OBSOLETE_V5_HatchExtra()
  {
    m_userdata_uuid = ON_CLASS_ID(ON_OBSOLETE_V5_HatchExtra);
    m_application_uuid = ON_opennurbs5_id; // opennurbs.dll reads/writes this userdata
                                           // The id must be the version 5 id because
                                           // V6 SaveAs V5 needs to work, but SaveAs
                                           // V4 should not write this userdata.   
    m_userdata_copycount = 0;
  }
  
  void ON_OBSOLETE_V5_HatchExtra::Dump(ON_TextLog& text_log) const
  {
  }
  
  unsigned int ON_OBSOLETE_V5_HatchExtra::SizeOf() const
  {
    unsigned int sz = ON_UserData::SizeOf();
    sz += sizeof(*this)-sizeof(ON_UserData);
    return sz;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::Write(ON_BinaryArchive& archive) const
  {
    bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0);
  
    if(rc) rc = archive.WriteUuid( ON_nil_uuid);
    if(rc) rc = archive.WritePoint(m_basepoint);
  
    if(!archive.EndWrite3dmChunk())
      rc = false;
  
    return rc;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::Read(ON_BinaryArchive& archive)
  {
    int major_version = 0;
    int minor_version = 0;
    bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version);
  
    if(major_version != 1)
        rc = false;
  
    m_basepoint.Set(0.0,0.0);
    ON_UUID ignored_id = ON_nil_uuid;
    if(rc) rc = archive.ReadUuid(ignored_id);
    if(rc) rc = archive.ReadPoint(m_basepoint);
  
    if(!archive.EndRead3dmChunk())
      rc = false;
  
    return rc;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::GetDescription( ON_wString& description)
  {
    description = L"OBSOLETE V5 ON_Hatch basepoint userdata.";
    return true;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::Archive() const
  {
    return true;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::WriteToArchive(
    const class ON_BinaryArchive& archive,
    const class ON_Object* parent_object
    ) const
  {
    return (50 == archive.Archive3dmVersion() && nullptr != ON_Hatch::Cast(parent_object));
  }
  
  
  bool ON_OBSOLETE_V5_HatchExtra::DeleteAfterWrite(
    const class ON_BinaryArchive& archive,
    const class ON_Object* parent_object
  ) const
  {
    return true;
  }
  
  bool ON_OBSOLETE_V5_HatchExtra::DeleteAfterRead(
    const class ON_BinaryArchive& archive,
    class ON_Object* parent_object
  ) const
  {
    for (;;)
    {
      if (false == m_basepoint.IsValid())
        break;
      ON_Hatch* hatch = ON_Hatch::Cast(parent_object);
      if (nullptr == hatch)
        break;
      ON_2dPoint p = hatch->BasePoint2d();
      if (p.x != m_basepoint.x || p.y != m_basepoint.y)
      {
        hatch->SetBasePoint(m_basepoint);
      }
      break;
    }
    return true;
  }
  
  /////////////////////////////////////////////////////////////////
  //  class ON_HatchLine
  /////////////////////////////////////////////////////////////////
  
  ON_HatchLine::ON_HatchLine(
    double angle_in_radians
    )
    : m_angle_radians( angle_in_radians)
  {}
  
  ON_HatchLine::ON_HatchLine(
    double angle_in_radians, 
    ON_2dPoint base, 
    ON_2dVector offset,
    const ON_SimpleArray<double>& dashes
    )
    : m_angle_radians( angle_in_radians)
    , m_base( base)
    , m_offset( offset)
    , m_dashes( dashes)
  {}
  
  bool ON_HatchLine::operator==(const ON_HatchLine& src) const
  {
    return(
      m_angle_radians == src.m_angle_radians
      && m_base == src.m_base
      && m_offset == src.m_offset
      && m_dashes == src.m_dashes
      );
  }
  
  bool ON_HatchLine::operator!=(const ON_HatchLine& src) const
  {
    return(
      m_angle_radians != src.m_angle_radians
      || m_base != src.m_base
      || m_offset != src.m_offset
      || m_dashes != src.m_dashes
      );
  }
  
  bool ON_HatchLine::IsValid( ON_TextLog* text_log) const
  {
    bool rc = m_angle_radians >= 0.0;
    if( !rc)
    {
      if( text_log)
        text_log->Print( "Angle ( %lf) must be >= 0.0\n", m_angle_radians);
      return false;
    }
    rc = m_angle_radians < ON_PI * 2.0;
    if( !rc)
    {
      if( text_log)
        text_log->Print( "Angle ( %lf) must be < 2*Pi.\n", m_angle_radians);
      return false;
    }
    rc = m_base.IsValid();
    if( !rc)
    {
      if( text_log)
        text_log->Print( "Base is not a valid point.\n");
      return false;
    }
    rc = m_offset.IsValid();
    if( !rc)
    {
      if( text_log)
        text_log->Print( "Offset is not a valid vector.\n");
      return false;
    }
    return true;
  }
  
  void ON_HatchLine::Dump( ON_TextLog& dump) const
  {
    dump.Print( "ON_HatchLine: angle = %lf radians ( %lf degrees) ", 
      AngleRadians(), AngleDegrees());
    dump.Print( " base = ");
    dump.Print( m_base);
    dump.Print( " offset = ");
    dump.Print( m_offset);
    int count = m_dashes.Count();
    dump.Print( "\nDash count = %d: ", count);
    for( int i = 0; i < count; i++)
    {
      dump.Print( "%lf", Dash( i));
      if( i < count-1)
        dump.Print( ", ");
    }
    dump.Print( "\n");
  }
  
  
  bool ON_HatchLine::Write(ON_BinaryArchive& archive) const
  {
    if (archive.Archive3dmVersion() < 60)
      return WriteV5(archive);
  
    const int major_version = 1;
    const int minor_version = 0;
    if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, major_version, minor_version))
      return false;
    bool rc = false;
    for (;;)
    {
      if (!archive.WriteDouble(m_angle_radians))
        break;
      if (!archive.WritePoint(m_base))
        break;
      if (!archive.WriteVector(m_offset))
        break;
      if (!archive.WriteArray(m_dashes))
        break;
      rc = true;
      break;
    }
    if (!archive.EndWrite3dmChunk())
      rc = false;
  
    return rc;
  }
  
  static bool Internal_UseHatchReadV5(
    ON_BinaryArchive& archive
    )
  {
    if (archive.Archive3dmVersion() < 60)
      return true;
    if (archive.Archive3dmVersion() > 60)
      return false;
    
    // There are 4 possible Feb 26 version numbers depending on the build system
    // archive.Archive3dmVersion() % 4 = (0,1,2,3 identifies the build system = OPENNURBS_VERSION_BRANCH)
    const unsigned int Feb_26_2016_opennurbs_version_number0 = 2348833956;
    const unsigned int Feb_26_2016_opennurbs_version_number3 = 2348833956+3; 
  
    const unsigned int archive_opennurbs_version_number = archive.ArchiveOpenNURBSVersion();
  
    if (archive_opennurbs_version_number < Feb_26_2016_opennurbs_version_number0)
      return true; // "old" opennurbs hatch code
  
    if (  archive_opennurbs_version_number >= Feb_26_2016_opennurbs_version_number0
      && archive_opennurbs_version_number <= Feb_26_2016_opennurbs_version_number3
      )
    {
      // new hatch IO code pushed to master branch on Feb 26, 2016.
      // THis clunky test has to be used on files writting with the Feb 26, 2016 version of opennurbs source code.
      ON__UINT32 tcode = 0;
      ON__INT64 big_value = 0;
      archive.PeekAt3dmBigChunkType(&tcode, &big_value);
      if (TCODE_ANONYMOUS_CHUNK == tcode)
        return false; // use "new" IO
      return true; // use "old" IO
    }
  
    // 6.0 or later file created with executable using opennurbs code on Feb 27, 2016 or later
    return false;
  }
  
  bool ON_HatchLine::Read(ON_BinaryArchive& archive)
  {
    if ( Internal_UseHatchReadV5(archive) )
      return ReadV5(archive);
  
    *this = ON_HatchLine::SolidHorizontal;
    int major_version = 0;
    int minor_version = 0;
    if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version))
      return false;
    bool rc = false;
    for (;;)
    {
      if (1 != major_version)
        break;
      if (!archive.ReadDouble(&m_angle_radians))
        break;
      if (!archive.ReadPoint(m_base))
        break;
      if (!archive.ReadVector(m_offset))
        break;
      if (!archive.ReadArray(m_dashes))
        break;
      rc = true;
      break;
    }
    if (!archive.EndRead3dmChunk())
      rc = false;
  
    return rc;
  }
  
  
  bool ON_HatchLine::WriteV5( ON_BinaryArchive& ar) const
  {
    // Incorrectly designed chunk IO - never change version numbers.
    bool rc = ar.Write3dmChunkVersion(1,1);
  
    if (rc) rc = ar.WriteDouble( m_angle_radians);
    if (rc) rc = ar.WritePoint( m_base);
    if (rc) rc = ar.WriteVector( m_offset);
    if (rc) rc = ar.WriteArray( m_dashes);
  
    return rc;
  }
  
  bool ON_HatchLine::ReadV5( ON_BinaryArchive& ar)
  {
    *this = ON_HatchLine::SolidHorizontal;
    // Incorrectly designed chunk IO - never change version numbers.
    int major_version = 0;
    int minor_version = 0;
    bool rc = ar.Read3dmChunkVersion( &major_version, &minor_version);
    if (rc)
      rc = (1 == major_version);
    if ( rc )
    {
      if ( rc) rc = ar.ReadDouble( &m_angle_radians);
      if ( rc) rc = ar.ReadPoint( m_base);
      if ( rc) rc = ar.ReadVector( m_offset);
      if ( rc) rc = ar.ReadArray( m_dashes);
    }
    return rc;
  }
  
  
  
  int ON_HatchLine::Compare(
    const ON_HatchLine& a,
    const ON_HatchLine& b
    )
  {
    int rc = ON_CompareDouble(a.m_angle_radians, b.m_angle_radians);
    if (rc) return rc;
    rc = ON_2dPoint::Compare(a.m_base, b.m_base);
    if (rc) return rc;
    rc = ON_2dVector::Compare(a.m_offset, b.m_offset);
    if (rc) return rc;
    size_t acount = (size_t)a.m_dashes.UnsignedCount();
    size_t bcount = (size_t)b.m_dashes.UnsignedCount();
    if (acount < bcount)
      return -1;
    if (acount > bcount)
      return 1;
    return ON_CompareDoubleArray(acount, a.m_dashes.Array(), b.m_dashes.Array());
  }
  
  // ON_HatchLine Interface
  double ON_HatchLine::AngleRadians() const
  {
    return m_angle_radians;
  }
  
  double ON_HatchLine::AngleDegrees() const
  {
    if (0.0 <= m_angle_radians && m_angle_radians < 2.0*ON_PI)
    {
      double angle_degrees = m_angle_radians*(180.0/ON_PI);
      if (angle_degrees >= 360.0)
        angle_degrees = 0.0;
    }
    return m_angle_radians; // UNSET VALUE OR NaN
  }
  
  static double DealWithUnsetAngles(
    double a
    )
  {
    if (((double)ON_UNSET_FLOAT) == a)
      return ON_UNSET_VALUE;
    
    if (((double)ON_UNSET_POSITIVE_FLOAT) == a)
      return ON_UNSET_POSITIVE_VALUE;
    
    if (a < ON_UNSET_VALUE)
      return ON_UNSET_VALUE;
    
    if (a > ON_UNSET_POSITIVE_VALUE)
      return ON_UNSET_POSITIVE_VALUE;
  
    return a;
  }
  
  void ON_HatchLine::SetAngleRadians( double angle_radians)
  {
    angle_radians = DealWithUnsetAngles(angle_radians);
    
    if (angle_radians > ON_UNSET_VALUE && angle_radians < ON_UNSET_POSITIVE_VALUE)
    {
      const double twopi = ON_PI * 2.0;
      // clamp between [0  2pi)
      while (angle_radians < 0.0)
        angle_radians += twopi;
      while (angle_radians > twopi)
        angle_radians -= twopi;
  
      const double zero_angle_tol = ON_ZERO_TOLERANCE*twopi;
      if (
        fabs(angle_radians) <= zero_angle_tol 
        || fabs(angle_radians - twopi) <= zero_angle_tol 
        )
        angle_radians = 0.0;
    }
    m_angle_radians = angle_radians;
  }
  
  
  void ON_HatchLine::SetAngleDegrees(double angle_degrees)
  {
    angle_degrees = DealWithUnsetAngles(angle_degrees);
  
    if (angle_degrees > ON_UNSET_VALUE && angle_degrees < ON_UNSET_POSITIVE_VALUE)
      SetAngleRadians(angle_degrees*(ON_PI/180.0));
    else
      m_angle_radians = angle_degrees;
  }
  
  ON_2dPoint ON_HatchLine::Base() const
  {
    return m_base;
  }
  
  void ON_HatchLine::SetBase( const ON_2dPoint& base)
  {
    m_base = base;
  }
  
  ON_2dVector ON_HatchLine::Offset() const
  {
    return m_offset;
  }
  
  void ON_HatchLine::SetOffset( const ON_2dVector& offset)
  {
    m_offset = offset;
  }
  
  int ON_HatchLine::DashCount() const
  {
    return m_dashes.Count();
  }
  
  double ON_HatchLine::Dash( int index) const
  {
    if( index >= 0 && index < m_dashes.Count())
      return m_dashes[index];
    return 0.0;
  }
  
  void ON_HatchLine::AppendDash( double dash)
  {
  //  if( fabs( dash) > ON_SQRT_EPSILON)
      m_dashes.Append( dash);
  }
  
  void ON_HatchLine::SetDashes( const ON_SimpleArray<double>& dashes)
  {
    m_dashes = dashes;
  }
  
  const ON_SimpleArray<double>& ON_HatchLine::Dashes() const
  {
    return m_dashes;
  }
  
  void ON_HatchLine::GetLineData( double& angle, 
                                  ON_2dPoint& base, 
                                  ON_2dVector& offset, 
                                  ON_SimpleArray<double>& dashes) const
  {
    angle = m_angle_radians;
    base = m_base;
    offset = m_offset;  dashes = m_dashes;
  }
  
  double ON_HatchLine::GetPatternLength() const
  {
    int i;
    double length = 0.0;
    for( i = 0; i < m_dashes.Count(); i++)
      length += fabs( m_dashes[i]);
  
    return length;
  }
  
  
  //  class ON_HatchPattern
  /////////////////////////////////////////////////////////////////
  ON_OBJECT_IMPLEMENT( ON_HatchPattern, ON_ModelComponent, "064E7C91-35F6-4734-A446-79FF7CD659E1" );
  
  ON_HatchPattern::HatchFillType ON_HatchPattern::HatchFillTypeFromUnsigned(
    unsigned hatch_fill_type_as_unsigned
    )
  {
    switch (hatch_fill_type_as_unsigned)
    {
      ON_ENUM_FROM_UNSIGNED_CASE(ON_HatchPattern::HatchFillType::Solid);
      ON_ENUM_FROM_UNSIGNED_CASE(ON_HatchPattern::HatchFillType::Lines);
      ON_ENUM_FROM_UNSIGNED_CASE(ON_HatchPattern::HatchFillType::Gradient);
    }
    ON_ERROR("Invalid hatch_fill_type_as_unsigned value.");
    return ON_HatchPattern::HatchFillType::Solid;
  }
  
  int ON_HatchPattern::Compare(
    const ON_HatchPattern& a,
    const ON_HatchPattern& b
    )
  {
    int rc = ON_ModelComponent::CompareNameAndId(a, b);
    if (0 == rc)
      rc = CompareAppearance(a, b);
    if (0 == rc)
      rc = ON_wString::CompareOrdinal(a.m_description, b.m_description, false);
    return rc;
  }
  
  int ON_HatchPattern::CompareAppearance( 
    const ON_HatchPattern& a,
    const ON_HatchPattern& b
    )
  {
    const unsigned int atype = static_cast<unsigned int>(a.FillType());
    const unsigned int btype = static_cast<unsigned int>(b.FillType());
    if (atype < btype)
      return -1;
    if (atype > btype)
      return 1;
    
    if (ON_HatchPattern::HatchFillType::Lines != a.FillType())
      return 0;
  
    const unsigned acount = a.m_lines.UnsignedCount();
    const unsigned bcount = b.m_lines.UnsignedCount();
    if (acount < bcount)
      return -1;
    if (acount > bcount)
      return 1;
  
    for (unsigned int i = 0; i < acount; i++)
    {
      int rc = ON_HatchLine::Compare(a.m_lines[i], b.m_lines[i]);
      if (0 != rc)
        return rc;
    }
  
    return 0;
  }
  
  const ON_HatchPattern* ON_HatchPattern::FromModelComponentRef(
    const class ON_ModelComponentReference& model_component_reference,
    const ON_HatchPattern* none_return_value
    )
  {
    const ON_HatchPattern* hatch_pattern = ON_HatchPattern::Cast(model_component_reference.ModelComponent());
    return (nullptr != hatch_pattern) ? hatch_pattern : none_return_value;
  }
  
  ON_HatchPattern::ON_HatchPattern() ON_NOEXCEPT
    : ON_ModelComponent(ON_ModelComponent::Type::HatchPattern)
  {}
  
  ON_HatchPattern::ON_HatchPattern(const ON_HatchPattern& src)
    : ON_ModelComponent(ON_ModelComponent::Type::HatchPattern, src)
    , m_type(src.m_type)
    , m_description(src.m_description)
    , m_lines(src.m_lines)
  {}
  
  bool ON_HatchPattern::IsValid( ON_TextLog* text_log) const
  {
    ON_HatchPattern::HatchFillType type = FillType();
    bool rc = true;
    if( type != ON_HatchPattern::HatchFillTypeFromUnsigned(static_cast<unsigned int>(type)))
    {
      if( text_log)
        text_log->Print( "Type field not set correctly.\n");
      rc = false;
    }
    if( type == ON_HatchPattern::HatchFillType::Lines)
    {
      int count = m_lines.Count();
      if( count < 1)
      {
        if( text_log)
          text_log->Print( "Line type patetern with no lines.\n");
        return false;
      }
      for( int i = 0; i < count; i++)
      {
        if( !m_lines[i].IsValid())
        {
          if( text_log)
            text_log->Print( "Line[%d] is not valid.\n", i);
          return false;
        }
      }
      return true;
    }
    return rc;
  }
  
  void ON_HatchPattern::Dump( ON_TextLog& dump) const
  {
    ON_ModelComponent::Dump(dump);
  
    switch( m_type)
    {
    case ON_HatchPattern::HatchFillType::Solid:
      dump.Print( "fill type: Solid");
      break;
    case ON_HatchPattern::HatchFillType::Lines:
      dump.Print( "fill type: Lines");
      break;
    case ON_HatchPattern::HatchFillType::Gradient:
      dump.Print( "fill type: Gradient");
      break;
    }
    dump.Print( "\n");
  
    const wchar_t* wsDescription =  static_cast< const wchar_t* >(m_description);
    if ( 0 == wsDescription )
      wsDescription = L"";
    dump.Print( "Description: %ls\n", wsDescription);
  
    if( m_type == ON_HatchPattern::HatchFillType::Lines)
    {
      int count = m_lines.Count();
      dump.Print( "Line count = %d\n", count);
      for( int i = 0; i < count; i++)
      {
        m_lines[i].Dump( dump);
      }
      dump.Print( "\n");
    }
  }
  
  bool ON_HatchPattern::Write(ON_BinaryArchive& archive ) const
  {
    if (archive.Archive3dmVersion() < 60)
      return WriteV5(archive);
  
    const int major_version = 1;
    const int minor_version = 0;
    if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, major_version, minor_version))
      return false;
    bool rc = false;
    for (;;)
    {
      unsigned int attributes_filter
        = ON_ModelComponent::Attributes::IdAttribute
        | ON_ModelComponent::Attributes::IndexAttribute
        | ON_ModelComponent::Attributes::NameAttribute;
      if (!WriteModelComponentAttributes(archive, attributes_filter))
        break;
  
      if (!archive.WriteInt(static_cast<unsigned int>(m_type)))
        break;
  
      if (!archive.WriteString(m_description))
        break;
  
      if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, 0))
        break;
      {
        const unsigned int count = (m_type == ON_HatchPattern::HatchFillType::Lines ? m_lines.UnsignedCount() : 0);
        bool lines_rc = archive.WriteInt(count);
        for (unsigned int i = 0; i < count && lines_rc; i++)
          lines_rc = m_lines[i].Write(archive);
        if (!archive.EndWrite3dmChunk())
          lines_rc = false;
        if (false == lines_rc)
          break;
      }
  
      rc = true;
      break;
    }
    if (!archive.EndWrite3dmChunk())
      rc = false;
    return rc;
  }
  
  bool ON_HatchPattern::Read(ON_BinaryArchive& archive)
  {
    if ( Internal_UseHatchReadV5(archive) )
      return ReadV5(archive);
  
    int major_version = 0;
    int minor_version = 0;
    if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version))
      return false;
    bool rc = false;
    for (;;)
    {
      if (!ReadModelComponentAttributes(archive))
        break;
  
      unsigned int type_as_unsigned = 0;
      if (!archive.ReadInt(&type_as_unsigned))
        break;
      m_type = ON_HatchPattern::HatchFillTypeFromUnsigned(type_as_unsigned);
  
      if (!archive.ReadString(m_description))
        break;
  
      unsigned int tcode = 0;
      ON__INT64 value = 0;
      if (!archive.BeginRead3dmBigChunk(&tcode, &value))
        break;
      {
        bool lines_rc = (TCODE_ANONYMOUS_CHUNK == tcode && value > 0);
        unsigned int count = 0;
        lines_rc = archive.ReadInt(&count);
        m_lines.Reserve(count);
        for (unsigned int i = 0; i < count && lines_rc; i++)
          lines_rc = m_lines.AppendNew().Read(archive);
        if (!archive.EndRead3dmChunk())
          lines_rc = false;
        if (false == lines_rc)
          break;
      }
  
      rc = true;
      break;
    }
    if (!archive.EndRead3dmChunk())
      rc = false;
    return rc;
  }
  
  bool ON_HatchPattern::WriteV5( ON_BinaryArchive& ar) const
  {
    bool rc = ar.Write3dmChunkVersion(1,2);
  
    if (rc) rc = ar.Write3dmReferencedComponentIndex( *this );
    if (rc) rc = ar.WriteInt( static_cast<unsigned int>(m_type));
    ON_wString name = Name();
    if (name.IsEmpty() && ModelComponentStatus().IsDeleted())
      name = DeletedName();
    if (rc) rc = ar.WriteString(name );
    if (rc) rc = ar.WriteString( m_description);
    if( rc)
    {
      if( m_type == ON_HatchPattern::HatchFillType::Lines)
      {
        int i, count = m_lines.Count();
        if ( count < 0 )
          count = 0;
        rc = ar.WriteInt( count );
        for( i = 0; i < count && rc; i++)
          rc = m_lines[i].Write( ar);
      }
    }
    // version 1.2 field
    if (rc) rc = ar.WriteUuid(Id());
  
    return rc;
  }
  
  bool ON_HatchPattern::ReadV5( ON_BinaryArchive& ar)
  {
    *this = ON_HatchPattern::Unset;
  
    int major_version = 0;
    int minor_version = 0;
    bool rc = ar.Read3dmChunkVersion( &major_version, &minor_version);
    if (rc && 1 != major_version)
      rc = false;
    if ( rc ) 
    {
      int hatchpattern_index = Index();
      if (rc) rc = ar.ReadInt(&hatchpattern_index);
      if (rc) SetIndex(hatchpattern_index);
  
      int fill_type_as_unsigned = 0;
      if( rc) rc = ar.ReadInt( &fill_type_as_unsigned);
      if( rc)  m_type = ON_HatchPattern::HatchFillTypeFromUnsigned(fill_type_as_unsigned);
      ON_wString hatchpattern_name;
      if( rc) rc = ar.ReadString( hatchpattern_name);
      if (rc) SetName(hatchpattern_name);
      if( rc) rc = ar.ReadString( m_description);
      if( rc)
      {
        if( m_type == ON_HatchPattern::HatchFillType::Lines)
        {
          m_lines.Empty();
          int count = 0;
          rc = ar.ReadInt( &count);
          if( rc && count > 0 ) 
          {
            m_lines.SetCapacity( count);
            for(int li = 0; rc && li < count; li++)
            {
              ON_HatchLine& line = m_lines.AppendNew();
              rc = line.Read( ar);
            }
          }
        }
      }
      if ( minor_version >= 2 )
      {
        ON_UUID hatchpattern_id = ON_nil_uuid;
        rc = ar.ReadUuid(hatchpattern_id);
        if (rc) SetId(hatchpattern_id);
      }
    }
    if (rc && IdIsNil())
      SetId(); // modern hatch patterns require a uniquie id.
    return rc;
  }
  
  ON_HatchPattern::ON_HatchPattern::HatchFillType ON_HatchPattern::FillType() const
  {
    return m_type;
  }
  
  void ON_HatchPattern::SetFillType( ON_HatchPattern::HatchFillType type)
  {
    if ( m_type != type && type == ON_HatchPattern::HatchFillTypeFromUnsigned(static_cast<unsigned int>(type)) )
    {
      if (ON_HatchPattern::HatchFillType::Lines != type)
        m_lines.Destroy();
      m_type = type;
      IncrementContentVersionNumber();
    }
  }
  
  void ON_HatchPattern::SetDescription( const wchar_t* pDescription)
  {
    ON_wString s(pDescription);
    s.TrimLeftAndRight();
    if (s != m_description)
    {
      m_description = s;
      IncrementContentVersionNumber();
    }
  }
  
  
  const ON_wString& ON_HatchPattern::Description() const
  {
    return m_description;
  }
  
  //  Line HatchPattern functions
  
  int ON_HatchPattern::HatchLineCount() const
  {
    return m_lines.Count();
  }
  
  int ON_HatchPattern::AddHatchLine( const ON_HatchLine& line)
  {
    m_lines.Append( line);
    IncrementContentVersionNumber();
    return m_lines.Count()-1;
  }
  
  const ON_HatchLine* ON_HatchPattern::HatchLine( int index) const
  {
    if( index >= 0 && index < m_lines.Count())
      return &m_lines[index];
    else
      return nullptr;
  }
  
  bool ON_HatchPattern::RemoveHatchLine( int index)
  {
    if( index >= 0 && index < m_lines.Count())
    {
      m_lines.Remove( index);
      IncrementContentVersionNumber();
      return true;
    }
    return false;
  }
  
  void ON_HatchPattern::RemoveAllHatchLines()
  {
    if ( m_lines.UnsignedCount() > 0 )
      IncrementContentVersionNumber();
    m_lines.Empty();
  }
  
  int ON_HatchPattern::SetHatchLines( const ON_ClassArray<ON_HatchLine>& lines)
  {
    return SetHatchLines(
      lines.UnsignedCount(),
      lines.Array()
      );
  }
  
  int ON_HatchPattern::SetHatchLines(
    size_t count,
    const ON_HatchLine* lines
    )
  {
  
    if (count > 0 && nullptr != lines )
    {
      m_lines.SetCount(0);
      m_lines.Append((int)count, lines);
      IncrementContentVersionNumber();
      SetFillType(ON_HatchPattern::HatchFillType::Lines);
    }
    else
    {
      if (0 != m_lines.UnsignedCount())
        IncrementContentVersionNumber();
      m_lines.Destroy();
      if ( ON_HatchPattern::HatchFillType::Lines == FillType())
        SetFillType(ON_HatchPattern::HatchFillType::Solid);
    }
    return m_lines.Count();
  }
  
  const ON_ClassArray<ON_HatchLine>& ON_HatchPattern::HatchLines() const
  {
    return m_lines;
  }
  
  
  //  class ON_HatchLoop
  /////////////////////////////////////////////////////////////////
  
  #if defined(OPENNURBS_EXPORTS)
  
  // When the Microsoft CRT(s) is/are used, this is the best
  // way to prevent crashes that happen when a hatch loop is
  // allocated with new in one DLL and deallocated with
  // delete in another DLL.
  
  void* ON_HatchLoop::operator new(size_t sz)
  {
    // ON_HatchLoop new
    return onmalloc(sz);
  }
  
  void ON_HatchLoop::operator delete(void* p)
  {
    // ON_HatchLoop delete
    onfree(p);
  }
  
  void* ON_HatchLoop::operator new[] (size_t sz)
  {
    // ON_HatchLoop array new
    return onmalloc(sz);
  }
  
  void ON_HatchLoop::operator delete[] (void* p)
  {
    // ON_HatchLoop array delete
    onfree(p);
  }
  
  void* ON_HatchLoop::operator new(size_t, void* p)
  {
    // ON_HatchLoop placement new
    return p;
  }
  
  void ON_HatchLoop::operator delete(void*, void*)
  {
    // ON_HatchLoop placement delete
    return;
  }
  
  #endif
  
  
  ON_HatchLoop::ON_HatchLoop()
  : m_type( ON_HatchLoop::ltOuter), m_p2dCurve( nullptr)
  {
  }
  
  ON_HatchLoop::ON_HatchLoop( ON_Curve* pCurve2d, eLoopType type)
  : m_type( type), m_p2dCurve( pCurve2d)
  {
  }
  
  ON_HatchLoop::ON_HatchLoop( const ON_HatchLoop& src)
  : m_type( src.m_type), m_p2dCurve( nullptr)
  { 
    if( src.m_p2dCurve)
      m_p2dCurve = src.m_p2dCurve->DuplicateCurve();
  }
  
  ON_HatchLoop::~ON_HatchLoop()
  {
    delete m_p2dCurve;
  }
  
  ON_HatchLoop& ON_HatchLoop::operator=( const ON_HatchLoop& src)
  {
    if( this != &src)
    {
      if( m_p2dCurve)
        delete m_p2dCurve;
      m_p2dCurve = src.m_p2dCurve->DuplicateCurve();
  
      m_type = src.m_type;
    }
    return *this;
  }
  
  bool ON_HatchLoop::IsValid( ON_TextLog* text_log) const
  {
    bool rc = m_p2dCurve != nullptr;
    if( !rc)
    {
      if( text_log)
        text_log->Print( "2d loop curve is nullptr\n");
    }
    if( rc)
    {
      rc = m_p2dCurve->IsValid( text_log);
      if( !rc)
      {
        if( text_log)
          text_log->Print( "Loop curve is not valid\n");
      }
    }
  
    if(rc)
    {
    }
  
    if( rc)
    {
      ON_BoundingBox box;
      m_p2dCurve->GetBoundingBox( box);
      rc = ( box.Max().z == box.Min().z && box.Max().z == 0.0);
      if( !rc)
      {
        if( text_log)
          text_log->Print( "2d loop curve has non-zero z coordinates\n");
      }
    }
  
    if( rc && m_type != ltOuter && m_type != ltInner)
    {
      if( text_log)
        text_log->Print( "Loop type is invalid.\n");
      rc = false;
    }
  
    return rc;
  }
  
  void ON_HatchLoop::Dump( ON_TextLog& dump) const
  {
    if( m_type == ltOuter)
      dump.Print( "Outer hatch loop\n");
    if( m_type == ltInner)
      dump.Print( "Inner hatch loop\n");
  
    if ( 0 == m_p2dCurve )
    {
      dump.Print( "2d curve: null pointer\n");
    }
    else
    {
      dump.Print( "2d curve:\n");
      m_p2dCurve->Dump(dump);
    }
  
  }
  
  bool ON_HatchLoop::Write( ON_BinaryArchive& ar) const
  {
    bool rc = ar.Write3dmChunkVersion(1,1);
    if( rc) rc = ar.WriteInt( m_type);
    if( rc) rc = ar.WriteObject( m_p2dCurve);
    return rc;
  }
  
  bool ON_HatchLoop::Read( ON_BinaryArchive& ar)
  {
    m_type = ltOuter;
    delete m_p2dCurve;
    m_p2dCurve = nullptr;
    int major_version = 0;
    int minor_version = 0;
    bool rc = ar.Read3dmChunkVersion( &major_version, &minor_version);
    if ( major_version == 1 ) 
    {
      int type = 0;
      if( rc) rc = ar.ReadInt( &type);
      if( rc) 
      {
        switch( type)
        {
        case ltOuter:  m_type = ltOuter; break;
        case ltInner:  m_type = ltInner; break;
        default: rc = false; break;
        }
      }
      if( rc)
      {
        ON_Object* pObj = nullptr;
        rc = ar.ReadObject( &pObj);
        if( pObj)
        {
          m_p2dCurve = ON_Curve::Cast( pObj);
          if( !m_p2dCurve) // read something, but it wasn't right
          {
            rc = false;
            delete pObj;
          }
        }
      }
    }
    return rc;
  }
  
  const ON_Curve* ON_HatchLoop::Curve() const
  {
    return m_p2dCurve;
  }
  
  bool ON_HatchLoop::SetCurve( const ON_Curve& curve)
  {
    ON_Curve* pC = curve.DuplicateCurve();
    if( pC)
    {
      if(pC->Dimension() == 3 && !pC->ChangeDimension(2))
        return false;
  
      if( m_p2dCurve)
        delete m_p2dCurve;
      m_p2dCurve = pC;
    }
    return true;
  }
  ON_HatchLoop::eLoopType ON_HatchLoop::Type() const
  {
    return m_type;
  }
  
  void ON_HatchLoop::SetType( eLoopType type)
  {
    m_type = type;
  }
  
  //  class ON_Hatch
  /////////////////////////////////////////////////////////////////
  ON_OBJECT_IMPLEMENT( ON_Hatch, ON_Geometry, "0559733B-5332-49d1-A936-0532AC76ADE5");
  
  ON_Hatch::ON_Hatch( const ON_Hatch& src)
    : ON_Geometry(src)
  {
    Internal_CopyFrom(src);
  }
  
  ON_Hatch& ON_Hatch::operator=( const ON_Hatch& src)
  {
    if( this != &src)
    {
      Internal_Destroy();
      ON_Geometry::operator =(src);
      Internal_CopyFrom(src);
    }
    return *this;
  }
  
  ON_Hatch::~ON_Hatch()
  {
    Internal_Destroy();
  }
  
  void ON_Hatch::Internal_Destroy()
  {
    const int count = m_loops.Count();
    for ( int i = 0; i < count; i++ )
    {
      ON_HatchLoop* pL = m_loops[i];
      if ( pL )
      {
        m_loops[i] = nullptr;
        delete pL;
      }
    }
    m_loops.Destroy();
  }
  
  void ON_Hatch::Internal_CopyFrom(const ON_Hatch& src)
  {
    m_plane = src.m_plane;
    m_pattern_scale = src.m_pattern_scale;
    m_pattern_rotation = src.m_pattern_rotation;
    m_basepoint = src.m_basepoint;
    const int count = src.m_loops.Count();
    m_loops.SetCount(0);
    m_loops.Reserve(count);
    for( int i = 0; i < count; i++)
    {
      ON_HatchLoop* pL = new ON_HatchLoop( *src.m_loops[i]);
      m_loops.Append( pL);
    }
    m_pattern_index = src.m_pattern_index;
  }
  
  ON_Hatch* ON_Hatch::DuplicateHatch() const
  {
    return Duplicate();
  }
  
  
  ON_Hatch* ON_Hatch::HatchFromBrep(
    ON_Hatch* use_this_hatch,
    const ON_Brep* brep,
    int face_index,
    int pattern_index,
    double pattern_rotation_radians,
    double pattern_scale,
    ON_3dPoint basepoint)
  {
    ON_Hatch* newhatch = nullptr;
    if (nullptr != use_this_hatch)
      use_this_hatch->Internal_Destroy();
  
    if (nullptr == brep)
      return nullptr;
  
    if (face_index < 0 || face_index >= brep->m_F.Count())
      face_index = 0;
  
    const ON_BrepFace* face = brep->Face(face_index);
    if (nullptr == face)
      return nullptr;
  
    ON_Plane plane;
    if (!face->IsPlanar(&plane))
      return nullptr;
  
    if (nullptr == use_this_hatch)
      newhatch = new ON_Hatch();
    else
    {
      newhatch = use_this_hatch;
      newhatch->Internal_Destroy();
    }
  
    if (0 > pattern_index)
      pattern_index = 0;
  
    if (pattern_scale < ON_SQRT_EPSILON)
      pattern_scale = 1.0;
  
    if (ON_3dPoint::UnsetPoint == basepoint)
      basepoint = ON_3dPoint::Origin;
  
    newhatch->SetPlane(plane);
    newhatch->SetPatternIndex(pattern_index);
    newhatch->SetPatternRotation(pattern_rotation_radians);
    newhatch->SetPatternScale(pattern_scale);
    newhatch->SetBasePoint(basepoint);
  
    bool rc = false;
    int loopcount = face->LoopCount();
    for (int li = 0; li < loopcount; li++)
    {
      ON_Curve* loopcurve = nullptr;
      ON_SimpleArray< ON_Curve* > edgecurves;
      ON_BrepLoop* loop = face->Loop(li);
      if (nullptr != loop)
      {
        int trimcount = loop->TrimCount();
        for (int ti = 0; ti < trimcount; ti++)
        {
          ON_BrepTrim* trim = loop->Trim(ti);
          if (nullptr != trim)
          {
            const ON_Curve* edgecurve = trim->EdgeCurveOf();
            if (nullptr != edgecurve)
            {
              ON_Curve* ec = edgecurve->Duplicate();
              if (trim->m_bRev3d)
                ec->Reverse();
              edgecurves.Append(ec);
            }
          }
        }
  
        int edgecount = edgecurves.Count();
        if (edgecount == 1)
        {
          loopcurve = edgecurves[0];
        }
        else if (edgecount > 1)
        {
          ON_PolyCurve* pc = new ON_PolyCurve(edgecount);
          if (nullptr != pc)
          {
            for (int ei = 0; ei < edgecount; ei++)
            {
              ON_Curve* ec = edgecurves[ei];
              if (nullptr != ec)
                pc->AppendAndMatch(ec);
            }
            loopcurve = pc;
          }
        }
        if (nullptr != loopcurve)
        {
          ON_Xform xf;
          xf.ChangeBasis(ON_xy_plane, plane);
          loopcurve->Transform(xf);
          ON_HatchLoop* hloop = new ON_HatchLoop();
          hloop->SetCurve(*loopcurve);
          if(loop->m_type == ON_BrepLoop::TYPE::outer)
          {
            hloop->SetType(ON_HatchLoop::eLoopType::ltOuter);
            rc = true;
          }
          else
            hloop->SetType(ON_HatchLoop::eLoopType::ltInner);
          newhatch->AddLoop(hloop);
        }
      }
    }
    if (!rc)
    {
      if (nullptr != newhatch)
      {
        delete newhatch;
        newhatch = nullptr;
      }
    }
  
    return newhatch;
  }
  
  
  bool ON_Hatch::IsValid( ON_TextLog* text_log) const
  {
    bool rc = m_plane.IsValid();
    if( !rc)
    {
      if( text_log)
        text_log->Print( "Plane is not valid\n");
      return false;
    }
    // 18 June 2012 - Lowell - Added loop self-intersection and 
    // intersecting other loops tests
    int count = m_loops.Count();
    for(int i = 0; i < count; i++)
    {
      if(m_loops[i] == 0)
      {
        if( text_log)
          text_log->Print( "Loop[%d] is nullptr\n", i);
        return false;
      }
      if(rc)
        rc = m_loops[i]->IsValid( text_log);
      if( !rc)
      {
        if( text_log)
          text_log->Print( "Loop[%d] is not valid\n", i);
        return false;
      }
    }
    
  
    return true;
  }
  
  void ON_Hatch::Dump( ON_TextLog& dump) const
  {
    dump.Print("Hatch: Pattern index: %d\n", PatternIndex());
    dump.Print("Pattern rotation: %g\n", PatternRotation());
    dump.Print("Pattern scale: %g\n", PatternScale());
    ON_3dPoint p = this->BasePoint();
    dump.Print("Base point: %g, %g, %g\n", p.x, p.y, p.z);
    dump.Print("Plane origin: %g, %g, %g\n", m_plane.origin.x, m_plane.origin.y, m_plane.origin.z);
    dump.Print("Plane x axis: %g, %g, %g\n", m_plane.xaxis.x, m_plane.xaxis.y, m_plane.xaxis.z);
    dump.Print("Plane y axis: %g, %g, %g\n", m_plane.yaxis.x, m_plane.yaxis.y, m_plane.yaxis.z);
    dump.Print("Plane z axis: %g, %g, %g\n", m_plane.zaxis.x, m_plane.zaxis.y, m_plane.zaxis.z);
    int count = m_loops.Count();
    dump.Print("Loop count = %d\n", count);
    for( int i = 0; i < count; i++)
      m_loops[i]->Dump( dump);
  }
  
  bool ON_Hatch::Write( ON_BinaryArchive& ar) const
  {
    // Added basepoint to 1.2;
    const int minor_version = (ar.Archive3dmVersion() >= 60) ? 2 : 1;
    bool rc = ar.Write3dmChunkVersion(1,minor_version);
    if (rc) rc = ar.WritePlane( m_plane);
    if (rc) rc = ar.WriteDouble( m_pattern_scale);
    if (rc) rc = ar.WriteDouble( m_pattern_rotation);
    if (rc) rc = ar.Write3dmReferencedComponentIndex( ON_ModelComponent::Type::HatchPattern, m_pattern_index);
    if (rc)
    {
      int i, count = m_loops.Count();
      if( count < 0 )
        count = 0;
      rc = ar.WriteInt( count);
      for( i = 0; i < count && rc; i++)
        rc = m_loops[i]->Write( ar);
    }
    const ON_2dPoint basepoint = BasePoint2d();
    if (minor_version >= 2)
    {
      // m_basepoint = a 2d (x,y) point.
      if (rc) rc = ar.WritePoint(basepoint);
    }
    else if (
      basepoint.IsValid() 
      && false == basepoint.IsZero()
      && 50 == ar.Archive3dmVersion()
      )
    {
      // add temporary V5 user data cache
      ON_OBSOLETE_V5_HatchExtra* v5_ud = ON_OBSOLETE_V5_HatchExtra::HatchExtension(this);
      if( nullptr!= v5_ud)
        v5_ud->m_basepoint = basepoint;
    }
    return rc;
  }
  
  bool ON_Hatch::Read( ON_BinaryArchive& ar)
  {
    m_plane.CreateFromNormal( ON_3dPoint::Origin, ON_3dVector::ZAxis);
    m_pattern_scale = 1.0;
    m_pattern_rotation = 0.0;
    m_pattern_index = -1;
    m_loops.Empty();
    int major_version = 0;
    int minor_version = 0;
    bool rc = ar.Read3dmChunkVersion( &major_version, &minor_version);
    if ( rc && major_version == 1 ) 
    {
      if( rc) rc = ar.ReadPlane( m_plane);
      if( rc) rc = ar.ReadDouble( &m_pattern_scale);
      if( rc) rc = ar.ReadDouble( &m_pattern_rotation);
      if( rc) rc = ar.Read3dmReferencedComponentIndex(ON_ModelComponent::Type::HatchPattern,&m_pattern_index);
      if( rc)
      {
        m_loops.Empty();
        int i, count = 0;
        rc = ar.ReadInt( &count);
        if( rc && count > 0)
        {
          m_loops.SetCapacity( count );
          for( i = 0; rc && i < count; i++)
          {
            ON_HatchLoop*& pLoop = m_loops.AppendNew();
            pLoop = new ON_HatchLoop;
            if( pLoop)
              rc = pLoop->Read( ar);
            else
              rc = false;
          }
        }
      }
      if (minor_version >= 2)
      {
        ON_2dPoint basepoint = BasePoint2d();
        if (rc) rc = ar.ReadPoint(basepoint);
        if (rc)
          SetBasePoint(basepoint);
      }
    }
    return rc;
  }
  
  ON::object_type ON_Hatch::ObjectType() const
  {
    return ON::hatch_object;
  }
  
  int ON_Hatch::Dimension() const
  {
    return 3;
  }
  
  // Copy the 2d curve, make it 3d, and transform it 
  // to the 3d plane position
  ON_Curve* ON_Hatch::LoopCurve3d( int index) const
  {
    int count = m_loops.Count();
    ON_Curve* pC = nullptr;
  
    if( index >= 0 && index < count)
    {
      if( m_loops[index]->Curve())
      {
        pC = m_loops[index]->Curve()->DuplicateCurve();
        if( pC)
        {
          pC->ChangeDimension( 3);
  
          ON_Xform xf;
          xf.Rotation( ON_xy_plane, m_plane);
  
          pC->Transform( xf);
        }
      }
    }
    return pC;
  }
  
  
  int ON_Hatch::PatternIndex() const
  {
    return m_pattern_index;
  }
  
  void ON_Hatch::SetPatternIndex( int index)
  {
    m_pattern_index = index;
  }
  
  
  bool ON_Hatch::GetBBox( double* bmin, double* bmax, bool bGrowBox) const
  {
    int i;
    int count = m_loops.Count();
    bool rc = true;
    ON_Curve* pC;
    for( i = 0; rc && i < count; i++)
    {
      pC = LoopCurve3d( i);
      if( pC)
      {
        rc = pC->GetBBox( bmin, bmax, i?true:bGrowBox);
        delete pC;
      }
    }
    return rc;
  }
  
  bool ON_Hatch::GetTightBoundingBox( ON_BoundingBox& tight_bbox, bool bGrowBox, const ON_Xform* xform) const
  {
    int i;
    int count = m_loops.Count();
    ON_CurveArray curves(count);
    for( i = 0; i < count; i++)
    {
      curves.Append( LoopCurve3d(i) );
    }
    return curves.GetTightBoundingBox(tight_bbox,bGrowBox,xform);
  }
  
  static double Angle3d(const ON_3dVector& axis, ON_3dVector& from, const ON_3dVector& to)
  {
    ON_3dVector x = from, a = to;
    x.Unitize();
    a.Unitize();
  
    ON_3dVector y = ON_CrossProduct(axis, from);
    y.Unitize();
  
    double cosa = x * a;
  
    if(cosa > 1.0 - ON_SQRT_EPSILON)
      return 0.0;
    if(cosa < ON_SQRT_EPSILON - 1.0)
      return ON_PI;
  
    double sina = a * y;
  
    return atan2(sina, cosa);
  }
  
  
  #define ARBBOUND  0.015625
  void arbaxis(const ON_3dVector& givenaxis, ON_3dVector& newaxis)
  {
    if(fabs(givenaxis[0]) < ARBBOUND && fabs(givenaxis[1]) < ARBBOUND) // near world z
      newaxis = ON_CrossProduct(ON_3dVector::YAxis, givenaxis);
    else
      newaxis = ON_CrossProduct(ON_3dVector::ZAxis, givenaxis);
  
    newaxis.Unitize();
  }
  
  double arbaxisRotation(const ON_Plane& plane)
  {
    // get arbaxis frame and angle of rotation from it
    ON_3dVector arbXaxis;
    arbaxis(plane.zaxis, arbXaxis);
    return Angle3d(plane.zaxis, arbXaxis, plane.xaxis);
  }
  
  // 20 June 2012 - Lowell - rr44706, 68320
  // This will find A, the arbaxis direction for the hatch plane
  // and rotate the hatch plane by -A and rotate the hatch boundaries
  // by A and add A to the hatch rotation.
  // The picture will be the same after that, but the way the
  // angle is represented will match the way AutoCAD does it
  // so hatches can be round-tripped with acad files.
  // In addition, after several hatches are rotated by different amounts
  // the hatch angles can be set to look the same by setting them all
  // to the same pattern rotation
   
  static void UnrotateHatch(ON_Hatch* hatch)
  {
    double a = arbaxisRotation(hatch->Plane());
    ON_Plane& plane = *(ON_Plane*)(&hatch->Plane());
    if(fabs(a) > ON_ZERO_TOLERANCE)
    {
      plane.Rotate(-a, plane.zaxis);
      for(int i = 0; i < hatch->LoopCount(); i++)
      {
        ON_Curve* pC = (ON_Curve*)hatch->Loop(i)->Curve();
        pC->Rotate(a, ON_3dVector::ZAxis, ON_3dPoint::Origin);
      }
      //hatch->SetPatternRotation(hatch->PatternRotation()+a);
    }
    // Project world origin to hatch plane and set hatch plane origin to the result
    // Translate hatch 2d curves to get back to the right position
    ON_3dPoint P;
    plane.ClosestPointTo(ON_3dPoint::Origin, &P.x, &P.y);
  
    if(fabs(P.x) > ON_ZERO_TOLERANCE ||fabs(P.y) > ON_ZERO_TOLERANCE ||fabs(P.z) > ON_ZERO_TOLERANCE)
    {
      const ON_3dVector V(-P.x, -P.y, 0.0);
      for(int i = 0; i < hatch->LoopCount(); i++)
      {
        ON_Curve* pC = (ON_Curve*)hatch->Loop(i)->Curve();
        pC->Translate(V);
      }
      P = plane.PointAt(P.x, P.y);
      plane.origin = P;
    }
  }
  
  bool ON_Hatch::Transform( const ON_Xform& xform)
  {
    if( fabs( fabs( xform.Determinant()) - 1.0) > 1.0e-4)
    {
      // xform has a scale component
      ON_Plane tmp( m_plane);
      tmp.Transform( xform);
      ON_Xform A, B, T;
      A.Rotation( ON_xy_plane, m_plane);
      B.Rotation( tmp, ON_xy_plane);
      T = B * xform * A;
  
      // kill translation and z-scaling
      T[0][2] = T[0][3] = 0.0;
      T[1][2] = T[1][3] = 0.0;
      T[2][0] = T[2][1] = 0.0; T[2][2] = 1.0; T[2][3] = 0.0; 
      T[3][0] = T[3][1] = T[3][2] = 0.0; T[3][3] = 1.0;
  
      for( int i = 0; i < LoopCount(); i++)
        m_loops[i]->m_p2dCurve->Transform( T);
    }
    int rc = m_plane.Transform( xform);
  
    UnrotateHatch(this);
  
    TransformUserData(xform);
  
    return rc;
  }
  
  ON_Brep* ON_Hatch::BrepForm(ON_Brep* brep) const
  {
    if (brep)
      brep->Destroy();
  
    ON_Brep* newbrep = brep ? brep : ON_Brep::New();
  
    if (0 == newbrep)
    {
      ON_ERROR("Unable to get allocate brep.");
      return 0;
    }
    brep = newbrep;
  
    ON_Plane plane = Plane();
    ON_PlaneSurface* srf = new ON_PlaneSurface(plane);
    int srf_i = -1, face_i = -1;
  
    int loopcount = LoopCount();
    for (int i = 0; i < loopcount; i++)
    {
      const ON_HatchLoop* loop = Loop(i);
      const ON_Curve* loopcurve = loop->Curve();
      ON_Curve* edgecurve = this->LoopCurve3d(i);
      ON_SimpleArray< ON_Curve* > bounds;
      bounds.Append(edgecurve);
  
      if (i == 0)
      {
        ON_BoundingBox bbox;
        loopcurve->GetBBox(&bbox.m_min.x, &bbox.m_max.x, false);
        srf->SetExtents(0, ON_Interval(bbox.m_min.x - 1.0, bbox.m_max.x + 1.0), true);
        srf->SetExtents(1, ON_Interval(bbox.m_min.y - 1.0, bbox.m_max.y + 1.0), true);
        srf_i = brep->AddSurface(srf);
        ON_BrepFace& face = brep->NewFace(srf_i);
        face_i = face.m_face_index;
        brep->NewPlanarFaceLoop(face_i, ON_BrepLoop::TYPE::outer, bounds, false);
      }
      else
        brep->NewPlanarFaceLoop(face_i, ON_BrepLoop::TYPE::inner, bounds, false);
    }
    brep->SetTolerancesBoxesAndFlags();
  
    return brep;
  }
  
  
  bool ON_Hatch::Create( const ON_Plane& plane,
                         const ON_SimpleArray<const ON_Curve*> loops, 
                         int pattern_index, 
                         double pattern_rotation, 
                         double pattern_scale)
  {
    if( loops.Count() < 1)
      return false;
    if( pattern_index < 0)
      return false;
    SetPlane( plane);
    for( int i = 0; i < loops.Count(); i++)
    {
      ON_HatchLoop* pLoop = new ON_HatchLoop;
      pLoop->SetCurve( *loops[i]);
      pLoop->SetType( i?ON_HatchLoop::ltInner:ON_HatchLoop::ltOuter);
      AddLoop( pLoop);
    }
    SetPatternIndex( pattern_index);
    SetPatternRotation( pattern_rotation);
    SetPatternScale( pattern_scale);
    return true;
  }
  
  const ON_Plane& ON_Hatch::Plane() const
  {
    return m_plane;
  }
  
  void ON_Hatch::SetPlane( const ON_Plane& plane)
  {
    m_plane = plane;
  }
  
  double ON_Hatch::PatternRotation() const
  {
    return m_pattern_rotation;
  }
  
  void ON_Hatch::SetPatternRotation( double rotation)
  {
    m_pattern_rotation = rotation;
  }
  
  double ON_Hatch::PatternScale() const
  {
    return m_pattern_scale;
  }
  
  void ON_Hatch::SetPatternScale( double scale)
  {
    if( scale > 0.001) // Changed May 13, 2009 - Lowell - rr39185
      m_pattern_scale = scale;
  }
  
  int ON_Hatch::LoopCount() const
  {
    return m_loops.Count();
  }
  
  void ON_Hatch::AddLoop( ON_HatchLoop* pLoop)
  {
    m_loops.Append( pLoop);
  }
  
  bool ON_Hatch::InsertLoop( int index, ON_HatchLoop* loop)
  {
    if( index >= 0 && index <= m_loops.Count()) // 26 June 2012 - Lowell - Changed ndex < to ndex <= 
    {
      m_loops.Insert(index, loop);
  	return true;
    }
  
    return false;
  }
  
  bool ON_Hatch::RemoveLoop( int index)
  {
    if( index >= 0 && index < m_loops.Count())
    {
      delete m_loops[index];
      m_loops.Remove(index);
      return true;
    }
    
    return false;
  }
  
  
  bool ON_Hatch::ReplaceLoops(ON_SimpleArray<const ON_Curve*>& loop_curves)
  {
    if(loop_curves.Count() < 1)
      return false;
  
    bool rc = true;
    ON_Xform xf;
    bool flat = false;
    ON_SimpleArray<ON_HatchLoop*> loops;
  
    for(int i = 0; i < loop_curves.Count(); i++)
    {
      if(loop_curves[i] == 0)
      {
        rc = false;
        break;
      }
      ON_Curve* p2d = loop_curves[i]->Duplicate();
      if(p2d == 0)
      {
        rc = false;
        break;
      }
      if(p2d->Dimension() == 3)
      {
        if(!flat)
        {
          ON_Xform planexf, flatxf;
          ON_Plane hplane(ON_xy_plane);
          hplane.origin = m_plane.origin;
          planexf.Rotation(m_plane, hplane);
          flatxf.PlanarProjection(hplane);
          xf = flatxf * planexf;
          flat = true;
        }
        if(!p2d->Transform(xf) ||
           !p2d->ChangeDimension(2))
        {
          delete p2d;
          rc = false;
          break;
        }
      }
      ON_HatchLoop* loop = new ON_HatchLoop(p2d,loops.Count()?ON_HatchLoop::ltInner:ON_HatchLoop::ltOuter);
      if(loop)
        loops.Append(loop);
      else
        delete p2d;
    }
    if(!rc)
    {
      for(int i = 0; i < loops.Count(); i++)
        delete loops[i];
  
      loops.Empty();
    }
  
    if(loops.Count() < 1)
      return false;
  
    for(int i = 0; i < m_loops.Count(); i++)
      delete m_loops[i];
    m_loops.Empty();
    for(int i = 0; i < loops.Count(); i++)
      m_loops.Append(loops[i]);
    return true;
  }
  
  const ON_HatchLoop* ON_Hatch::Loop( int index) const
  {
    if( index >= 0 && index < m_loops.Count())
      return m_loops[index];  
    return nullptr;
  }
  
  // Basepoint functions added March 23, 2008 -LW
  void ON_Hatch::SetBasePoint(ON_2dPoint basepoint)
  {
    m_basepoint = basepoint;
  }
  
  void ON_Hatch::SetBasePoint(ON_3dPoint point)
  {
    m_basepoint = point;
  }
  
  ON_3dPoint ON_Hatch::BasePoint() const
  {
    const ON_3dPoint point(m_basepoint);
    return point;
  }
  
  ON_2dPoint ON_Hatch::BasePoint2d() const
  {
    return m_basepoint;
  }
  