/* $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=developer,1=windows build system,2 = mac build system, 3=unused)
  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);

  //ON_3dVector x = m_plane.xaxis;
  //x.Transform(xform);
  //double scale = x.Length() * PatternScale();
  //SetPatternScale(scale);

  UnrotateHatch(this);

  TransformUserData(xform);

  return rc;
}

bool ON_Hatch::ScalePattern(ON_Xform xform)
{
  ON_3dVector v = m_plane.xaxis;
  v.Transform(xform);

  double l = v.Length();
  if (v.Unitize())
  {
    m_pattern_scale *= l;
    return true;
  }
  return false;
}

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_plane.ClosestPointTo(point, &m_basepoint.x, &m_basepoint.y);
}

ON_3dPoint ON_Hatch::BasePoint() const
{
  return m_plane.PointAt(m_basepoint.x, m_basepoint.y);
}

ON_2dPoint ON_Hatch::BasePoint2d() const
{
  return m_basepoint;
}
