// // Copyright (c) 1993-2022 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 . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #include "opennurbs_internal_defines.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 static const wchar_t* XMLPath_Sun(void) { return ON_RDK_DOCUMENT ON_XML_SLASH ON_RDK_SETTINGS ON_XML_SLASH ON_RDK_SUN; } static double Sin(double deg) { return sin(ON_RadiansFromDegrees(deg)); } static double Cos(double deg) { return cos(ON_RadiansFromDegrees(deg)); } static double Tan(double deg) { return tan(ON_RadiansFromDegrees(deg)); } static double ArcSin(double sine) { return ON_DegreesFromRadians(asin(sine)); } static double ArcTan2(double dy, double dx) { return ON_DegreesFromRadians(atan2(dy, dx)); } static double WorldToCompass(double d) { return 90.0 - d; } static double TwilightZone(void) { return 3.0; } static constexpr double c2 = 14388.0; static constexpr double monitor_white = 5000.0; static bool IsLeapYear(int year) { // The year can only be a leap year if it is divisible by 4. if (0 != (year % 4)) return false; // If the year is a century it is only a leap year if it is divisible by 400. if ((0 == (year % 100)) && (0 != (year % 400))) return false; return true; } static double Planck(double lambda, double temp) { static const double E = 2.7182818284590452354; return (pow(double(lambda), -5.0) / (pow(E, c2 / (lambda * temp)) - 1.0)); } static ON_4fColor ColorTemperature(double temperature) { // Use a variant of Planck's equation to get values for the three CIE wavelengths. double temp = monitor_white; double er = Planck(0.60, temp); double eg = Planck(0.56, temp); double eb = Planck(0.44, temp); double es = 1.0 / std::max(er, std::max(eg, eb)); const double r_white = er * es; const double g_white = eg * es; const double b_white = eb * es; temp = temperature; er = Planck(0.60, temp); eg = Planck(0.56, temp); eb = Planck(0.44, temp); es = 1.0 / std::max(er,std::max(eg, eb)); const double r = er * es / r_white; const double g = eg * es / g_white; const double b = eb * es / b_white; es = 1.0 / std::max(r, std::max(g, b)); const float rr = float(pow(r * es, 0.15)); const float gr = float(pow(g * es, 0.15)); const float br = float(pow(b * es, 0.15)); return ON_4fColor(rr, gr, br, 1.0f); } template static ON__UINT32 UpdateCRC(ON__UINT32 crc, T x) { return ON_CRC32(crc, sizeof(x), &x); } inline static double Int(double x) { return (x < 0.0) ? ceil(x) : floor(x); } inline static double Frac(double x) { return x - Int(x); } inline static double Unwind(double dDegrees) { dDegrees = Frac(dDegrees / 360.0) * 360.0; if (dDegrees < 0.0) dDegrees += 360.0; return dDegrees; } static bool IsVectorEqual(const ON_3dVector& v1, const ON_3dVector& v2) { return IsDoubleEqual(v1.x, v2.x) && IsDoubleEqual(v1.y, v2.y) && IsDoubleEqual(v1.z, v2.z); } static ON_3dVector PerpendicularVectorOnXYPlane(const ON_3dVector& vec) { return ON_2dVector(vec.x, vec.y).IsTiny() ? ON_3dVector(vec.z, 0.0, -vec.x) : ON_3dVector(-vec.y, vec.x, 0.0); } static double AngleFromVectors(const ON_3dVector& v1, const ON_3dVector& v2, ON_3dVector normal) { if (IsVectorEqual(v1, v2)) return 0.0; normal.Unitize(); const double numerator = v1 * v2; const double denominator = v1.Length() * v2.Length(); auto cross = ON_CrossProduct(v1, v2); cross.Unitize(); if (IsVectorEqual(cross, ON_3dVector::ZeroVector)) { if (IsDoubleEqual(numerator, +1.0)) return 0.0; if (IsDoubleEqual(numerator, -1.0)) return ON_PI; } double division = numerator / denominator; if (division > 1.0) division = 1.0; else if (division < -1.0) division = -1.0; if (IsDoubleEqual(division, -1.0)) return ON_PI; double angle = acos(division); // Check if cross is parallel or anti-parallel to normal vector. If anti-parallel then angle = 360 - angle. const double dot = cross * normal; if (IsDoubleEqual(dot, -1.0)) angle = (ON_PI * 2.0) - angle; return angle; } // Reference: Jean Meeus - 'Astronomical Algorithms', second edition. class ON_SunEngine::CImpl final { public: void UpdateIfModified(void); double JulianDay(void) const { return _local_julian_day - (_local_tz_hours + (_local_daylight_mins / 60.0)) / 24.0; } public: double _azimuth = 0.0; double _altitude = 0.0; double _latitude = 0.0; double _longitude = 0.0; double _local_julian_day = 0.0; double _local_tz_hours = 0.0; int _local_daylight_mins = 0; double _cache_right_ascension = 0.0; double _cache_sin_declination = 0.0; double _cache_cos_declination = 0.0; double _cache_tan_declination = 0.0; double _cache_sin_latitude = Sin(0.0); double _cache_cos_latitude = Cos(0.0); double _cache_greenwich_sidereal_time = 0.0; bool _modified = true; bool _julian_date_changed = true; Accuracy _accuracy = Accuracy::Minimum; // The obliquity of the ecliptic (the tilt of the earth's axis) is usually considered to be about 23.5 // degrees, but it actually changes very slowly with the passing centuries. These rough values are used // when the accuracy is set to minimum. const double _rough_cos_obliquity = 0.91747714052291862; const double _rough_sin_obliquity = 0.39778850739794974; }; void ON_SunEngine::CImpl::UpdateIfModified(void) { if (!_modified) return; if (_julian_date_changed) { const auto dJulianDayUT = JulianDay(); const auto dJulianDayUT2000 = dJulianDayUT - 2451545.0; const auto dJulianCenturies = dJulianDayUT2000 / 36525.0; const auto dJulianCenturies2 = dJulianCenturies * dJulianCenturies; const auto dJulianCenturies3 = dJulianCenturies * dJulianCenturies2; // Calculate the sun's true and apparent ecliptic longitude. const auto dL0 = 280.46646 + 36000.76983 * dJulianCenturies + 0.0003032 * dJulianCenturies2; auto dC = 0.0; if ((Accuracy::Maximum == _accuracy)) { const auto dMeanAnomaly = 357.52911 + 35999.05029 * dJulianCenturies - 0.0001537 * dJulianCenturies2; dC = Sin(dMeanAnomaly * 3.0) * 0.000289 + Sin(dMeanAnomaly * 2.0) * (0.019993 - 0.000101 * dJulianCenturies) + Sin(dMeanAnomaly ) * (1.914602 - 0.004817 * dJulianCenturies - 0.000014 * dJulianCenturies2); } const auto dTrueLongitude = Unwind(dL0 + dC); auto dApparentLongitude = dTrueLongitude - 0.00569; auto dSinObliquity = _rough_sin_obliquity; auto dCosObliquity = _rough_cos_obliquity; if ((Accuracy::Maximum == _accuracy)) { // Include the effect of the moon. const auto dOmega = 125.04 - 1934.136 * dJulianCenturies; dApparentLongitude -= 0.00478 * Sin(dOmega); // Calculate the obliquity of the ecliptic (the tilt of the earth's axis). const auto dObliquity = 23.439291111 - (46.81500 * dJulianCenturies - 0.000590 * dJulianCenturies2 + 0.001813 * dJulianCenturies3) / 3600.0 + 0.002560 * Cos(dOmega); dSinObliquity = Sin(dObliquity); dCosObliquity = Cos(dObliquity); } // Calculate the sun's equatorial coordinates (right ascension and declination). const auto dSinApparentLongitude = Sin(dApparentLongitude); const auto dCosApparentLongitude = Cos(dApparentLongitude); const auto dDeclination = ArcSin(dSinApparentLongitude * dSinObliquity); _cache_right_ascension = Unwind(ArcTan2(dSinApparentLongitude * dCosObliquity, dCosApparentLongitude)); _cache_sin_declination = Sin(dDeclination); _cache_cos_declination = Cos(dDeclination); _cache_tan_declination = Tan(dDeclination); // Declination is between -24 and +24 so no problem with Tan(90). // Calculate the sidereal time at Greenwich, expressed in degrees. const auto dTheta0 = 280.46061837 + (360.98564736629 * dJulianDayUT2000) + (0.000387933 * dJulianCenturies2) - (dJulianCenturies3 / 38710000.0); _cache_greenwich_sidereal_time = Unwind(dTheta0); _julian_date_changed = false; } // Calculate the sun's local hour angle, expressed in degrees. const auto dLocalHourAngle = _cache_greenwich_sidereal_time + _longitude - _cache_right_ascension; // Calculate the sun's horizontal coordinates (azimuth and altitude). const auto dSinLocalHourAngle = Sin(dLocalHourAngle); const auto dCosLocalHourAngle = Cos(dLocalHourAngle); _azimuth = Unwind(180.0 + ArcTan2(dSinLocalHourAngle, dCosLocalHourAngle * _cache_sin_latitude - _cache_tan_declination * _cache_cos_latitude)); _altitude = ArcSin(_cache_sin_latitude * _cache_sin_declination + _cache_cos_latitude * _cache_cos_declination * dCosLocalHourAngle); _modified = false; } ON_SunEngine::ON_SunEngine(Accuracy a) { _impl = new CImpl; _impl->_accuracy = a; } ON_SunEngine::~ON_SunEngine() { delete _impl; _impl = nullptr; } ON_SunEngine::ON_SunEngine(const ON_SunEngine& e) { _impl = new CImpl; operator = (e); } const ON_SunEngine& ON_SunEngine::operator = (const ON_SunEngine& e) { _impl->_accuracy = e._impl->_accuracy; _impl->_azimuth = e._impl->_azimuth; _impl->_altitude = e._impl->_altitude; _impl->_latitude = e._impl->_latitude; _impl->_longitude = e._impl->_longitude; _impl->_local_julian_day = e._impl->_local_julian_day; _impl->_local_tz_hours = e._impl->_local_tz_hours; _impl->_local_daylight_mins = e._impl->_local_daylight_mins; _impl->_cache_right_ascension = e._impl->_cache_right_ascension; _impl->_cache_sin_declination = e._impl->_cache_sin_declination; _impl->_cache_cos_declination = e._impl->_cache_cos_declination; _impl->_cache_tan_declination = e._impl->_cache_tan_declination; _impl->_cache_sin_latitude = e._impl->_cache_sin_latitude; _impl->_cache_cos_latitude = e._impl->_cache_cos_latitude; _impl->_cache_greenwich_sidereal_time = e._impl->_cache_greenwich_sidereal_time; _impl->_modified = e._impl->_modified; _impl->_julian_date_changed = e._impl->_julian_date_changed; return *this; } bool ON_SunEngine::operator == (const ON_SunEngine& e) { if (_impl->_azimuth != e._impl->_azimuth) return false; if (_impl->_altitude != e._impl->_altitude) return false; if (_impl->_latitude != e._impl->_latitude) return false; if (_impl->_longitude != e._impl->_longitude) return false; if (_impl->_local_julian_day != e._impl->_local_julian_day) return false; if (_impl->_local_tz_hours != e._impl->_local_tz_hours) return false; if (_impl->_local_daylight_mins != e._impl->_local_daylight_mins) return false; return true; } bool ON_SunEngine::operator != (const ON_SunEngine& e) { return !(operator == (e)); } int ON_SunEngine::DaysInMonth(int month, int year) // Static. { month = std::max(1, std::min(12, month)); if ((2 == month) && IsLeapYear(year)) return 29; static const int tab[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return tab[month]; } double ON_SunEngine::Latitude(void) const { return _impl->_latitude; } bool ON_SunEngine::SetLatitude(double lat) { if ((lat < -90.0) || (lat > 90.0)) return false; if (_impl->_latitude != lat) { _impl->_latitude = lat; _impl->_cache_sin_latitude = Sin(lat); _impl->_cache_cos_latitude = Cos(lat); _impl->_modified = true; } return true; } double ON_SunEngine::Longitude(void) const { return _impl->_longitude; } bool ON_SunEngine::SetLongitude(double dLong) { if ((dLong < -180.0) || (dLong > 180.0)) return false; if (_impl->_longitude != dLong) { _impl->_longitude = dLong; _impl->_modified = true; } return true; } bool ON_SunEngine::SetTimeZoneHours(double dHours) { if ((dHours < -12.0) || (dHours > 13.0)) return false; const double dLocalTimeZoneHours = dHours; if (_impl->_local_tz_hours != dLocalTimeZoneHours) { _impl->_local_tz_hours = dLocalTimeZoneHours; _impl->_modified = true; _impl->_julian_date_changed = true; } return true; } bool ON_SunEngine::SetDaylightSavingMinutes(int iMinutes) { if ((iMinutes < 0) || (iMinutes > 120)) return false; const int iLocalDaylightMinutes = iMinutes; if (_impl->_local_daylight_mins != iLocalDaylightMinutes) { _impl->_local_daylight_mins = iLocalDaylightMinutes; _impl->_modified = true; _impl->_julian_date_changed = true; } return true; } bool ON_SunEngine::SetLocalDateTime(int iYear, int iMonth, int iDay, double dHours) { if ((iYear < 1800) || (iYear > 2199) || (iMonth < 1) || (iMonth > 12)) return false; if ((iDay < 1) || (iDay > DaysInMonth(iMonth, iYear))) return false; if ((dHours < 0.0) || (dHours > 24.0)) return false; if (iMonth < 3) { iMonth += 12; iYear--; } const int a = iYear / 100; const int b = 2 - a + (a / 4); const int iJulianDay = (36525 * (iYear + 4716)) / 100 + (306 * (iMonth + 1)) / 10 + iDay + b - 1524; const double dJulianDay = iJulianDay + dHours / 24.0 - 0.5; return SetLocalJulianDay(dJulianDay); } void ON_SunEngine::LocalDateTime(int& iYear, int& iMonth, int& iDay, double& dHours) const { const double jd = _impl->_local_julian_day + 0.5; int b = (int)Int(jd); const int a = (b * 100 - 186721625) / 3652425; b += 1 + a - (a / 4) + 1524; const int c = (b * 100 - 12210) / 36525; const int d = (365 * c) + (c / 4); const int e = (10000 * (b - d)) / 306001; iDay = (int)(b - d - ((306001 * e) / 10000)); iMonth = (int)((e < 14) ? e - 1 : e - 13); iYear = (int)((iMonth > 2) ? c - 4716 : c - 4715); dHours = Frac(jd) * 24.0 + 1e-8; } bool ON_SunEngine::SetLocalJulianDay(double dLocalJulianDay) { if ((dLocalJulianDay < 2378496.5) || (dLocalJulianDay > 2524593.499999999)) return false; if (_impl->_local_julian_day != dLocalJulianDay) { _impl->_local_julian_day = dLocalJulianDay; _impl->_julian_date_changed = true; _impl->_modified = true; } return true; } double ON_SunEngine::Azimuth(void) const { _impl->UpdateIfModified(); return _impl->_azimuth; } double ON_SunEngine::Altitude(void) const { _impl->UpdateIfModified(); return _impl->_altitude; } double ON_SunEngine::JulianDay(void) const { return _impl->JulianDay(); } double ON_SunEngine::LocalJulianDay(void) const { return _impl->_local_julian_day; } double ON_SunEngine::TimeZoneHours(void) const { return _impl->_local_tz_hours; } int ON_SunEngine::DaylightSavingMinutes(void) const { return _impl->_local_daylight_mins; } void ON_SunEngine::ConvertHorizonCoordsToSolarVector(double dAzimuth, double dAltitude, double* dVector) // Static. { dVector[0] = -Cos(dAltitude) * Sin(dAzimuth); dVector[1] = -Cos(dAltitude) * Cos(dAzimuth); dVector[2] = -Sin(dAltitude); } void ON_SunEngine::ConvertSolarVectorToHorizonCoords(const double* dVector, double& dAzimuth, double& dAltitude) // Static. { dAltitude = ArcSin(-dVector[2]); const double dY = dVector[0] / -Cos(dAltitude); const double dX = dVector[1] / -Cos(dAltitude); dAzimuth = ArcTan2(dY, dX); if (dAzimuth < 0.0) dAzimuth += 360.0; } class ON_Sun::CImpl : public ON_InternalXMLImpl { public: CImpl() { } CImpl(ON_XMLNode& n) : ON_InternalXMLImpl(&n) { } bool EnableAllowed(void) const; void SetEnableAllowed(bool allowed); bool EnableOn(void) const; void SetEnableOn(bool on); bool ManualControlAllowed(void) const; void SetManualControlAllowed(bool allowed); bool ManualControlOn(void) const; void SetManualControlOn(bool manual); double Azimuth(void) const; void SetAzimuth(double azi); double Altitude(void) const; void SetAltitude(double alt); double TimeZone(void) const; void SetTimeZone(double hours); double North(void) const; void SetNorth(double); double Latitude(void) const; void SetLatitude(double); double Longitude(void) const; void SetLongitude(double); bool DaylightSavingOn(void) const; void SetDaylightSavingOn(bool on); int DaylightSavingMinutes(void) const; void SetDaylightSavingMinutes(int minutes); void LocalDateTime(int& year, int& month, int& day, double& hours) const; bool SetLocalDateTime(int year, int month, int day, double hours); double Intensity(void) const; void SetIntensity(double d); double ShadowIntensity(void) const; void SetShadowIntensity(double d); bool IsUsingManualControl(void) const; private: void UpdateAziAlt(void) const; void GetEarthAnchorPlane(ON_3dVector& anchor_north, ON_Plane& plane) const; public: mutable double _azimuth = 0.0; mutable double _altitude = 0.0; mutable bool _calc_dirty = true; ON_EarthAnchorPoint* _earth_anchor_point = nullptr; ON_SunEngine::Accuracy _accuracy = ON_SunEngine::Accuracy::Minimum; }; bool ON_Sun::CImpl::IsUsingManualControl(void) const { // If manual control has been set on even if it's not 'allowed', consider it on anyway. // This makes it easier for clients that set manual control on without knowing they are supposed // to also allow it. I'm not even sure why it wouldn't be allowed nowadays. TODO: Ask Andy. return ManualControlOn(); } void ON_Sun::CImpl::GetEarthAnchorPlane(ON_3dVector& anchor_north, ON_Plane& plane) const { ON_3dVector anchor_east = _earth_anchor_point->ModelEast(); anchor_north = _earth_anchor_point->ModelNorth(); // Keeps original vector length if feasible. double east_len = anchor_east .LengthAndUnitize(); double north_len = anchor_north.LengthAndUnitize(); // Compute normal of the plane P that contains m_model_east & m_model_north. plane.zaxis = ON_CrossProduct(anchor_east, anchor_north); if (plane.zaxis.IsTiny()) { // Recompute a valid North and East vector. if (!anchor_north.IsTiny()) { // North and east are equal or east is tiny. anchor_east = -PerpendicularVectorOnXYPlane(anchor_north); east_len = north_len; } else { if (!anchor_east.IsTiny()) { // North and east are equal or north is tiny. anchor_north = PerpendicularVectorOnXYPlane(anchor_east); north_len = east_len; } else { // Both are identity. east_len = north_len = 1.0; anchor_east = ON_3dVector::XAxis; anchor_north = ON_3dVector::YAxis; } } plane.zaxis = ON_CrossProduct(anchor_east, anchor_north); } plane.xaxis = PerpendicularVectorOnXYPlane(plane.zaxis); plane.yaxis = ON_CrossProduct(plane.zaxis, plane.xaxis); // Restores original vector length if were valid. plane.xaxis *= east_len; plane.yaxis *= north_len; plane.zaxis *= east_len * north_len; plane.origin = _earth_anchor_point->ModelPoint(); plane.UpdateEquation(); } void ON_Sun::CImpl::UpdateAziAlt(void) const { if (_calc_dirty) { ON_SunEngine engine(_accuracy); engine.SetLatitude (Latitude()); engine.SetLongitude(Longitude()); engine.SetTimeZoneHours(TimeZone()); const int dsm = DaylightSavingOn() ? DaylightSavingMinutes() : 0; engine.SetDaylightSavingMinutes(dsm); int y = 0, m = 0, d = 0; double h = 0.0; LocalDateTime(y, m, d, h); engine.SetLocalDateTime(y, m, d, h); _azimuth = engine.Azimuth(); _altitude = engine.Altitude(); _calc_dirty = false; } } bool ON_Sun::CImpl::EnableAllowed(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_ENABLE_ALLOWED, false); } void ON_Sun::CImpl::SetEnableAllowed(bool allowed) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_ENABLE_ALLOWED, allowed); } bool ON_Sun::CImpl::EnableOn(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_ENABLE_ON, false); } void ON_Sun::CImpl::SetEnableOn(bool on) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_ENABLE_ON, on); } bool ON_Sun::CImpl::ManualControlAllowed(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_MANUAL_CONTROL_ALLOWED, false); } void ON_Sun::CImpl::SetManualControlAllowed(bool allowed) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_MANUAL_CONTROL_ALLOWED, allowed); } bool ON_Sun::CImpl::ManualControlOn(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_MANUAL_CONTROL_ON, false); } void ON_Sun::CImpl::SetManualControlOn(bool manual) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_MANUAL_CONTROL_ON, manual); } double ON_Sun::CImpl::Azimuth(void) const { if (IsUsingManualControl()) return GetParameter(XMLPath_Sun(), ON_RDK_SUN_AZIMUTH, 0.0).AsDouble(); UpdateAziAlt(); return _azimuth; } void ON_Sun::CImpl::SetAzimuth(double azimuth) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_AZIMUTH, azimuth); } double ON_Sun::CImpl::Altitude(void) const { if (IsUsingManualControl()) return GetParameter(XMLPath_Sun(), ON_RDK_SUN_ALTITUDE, 0.0).AsDouble(); UpdateAziAlt(); return _altitude; } void ON_Sun::CImpl::SetAltitude(double altitude) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_ALTITUDE, altitude); } double ON_Sun::CImpl::TimeZone(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_TIMEZONE, 0.0); } void ON_Sun::CImpl::SetTimeZone(double hours) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_TIMEZONE, hours); _calc_dirty = true; } bool ON_Sun::CImpl::DaylightSavingOn(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_DAYLIGHT_SAVING_ON, false); } void ON_Sun::CImpl::SetDaylightSavingOn(bool on) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_DAYLIGHT_SAVING_ON, on); _calc_dirty = true; } int ON_Sun::CImpl::DaylightSavingMinutes(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_DAYLIGHT_SAVING_MINUTES, 0); } void ON_Sun::CImpl::SetDaylightSavingMinutes(int minutes) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_DAYLIGHT_SAVING_MINUTES, minutes); _calc_dirty = true; } void ON_Sun::CImpl::LocalDateTime(int& year, int& month, int& day, double& hours) const { const wchar_t* s = XMLPath_Sun(); year = GetParameter(s, ON_RDK_SUN_DATE_YEAR, 2000); month = GetParameter(s, ON_RDK_SUN_DATE_MONTH, 1); day = GetParameter(s, ON_RDK_SUN_DATE_DAY, 1); hours = GetParameter(s, ON_RDK_SUN_TIME_HOURS, 0.0); } bool ON_Sun::CImpl::SetLocalDateTime(int year, int month, int day, double hours) { year = std::max(MinYear(), std::min(MaxYear(), year)); month = std::max(1, std::min(12, month)); day = std::max(1, std::min(ON_SunEngine::DaysInMonth(month, year), day)); const wchar_t* s = XMLPath_Sun(); SetParameter(s, ON_RDK_SUN_DATE_YEAR, year); SetParameter(s, ON_RDK_SUN_DATE_MONTH, month); SetParameter(s, ON_RDK_SUN_DATE_DAY, day); SetParameter(s, ON_RDK_SUN_TIME_HOURS, hours); _calc_dirty = true; return true; } double ON_Sun::CImpl::Intensity(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_INTENSITY, 1.0); } void ON_Sun::CImpl::SetIntensity(double d) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_INTENSITY, std::max(0.0, d)); } double ON_Sun::CImpl::ShadowIntensity(void) const { return GetParameter(XMLPath_Sun(), ON_RDK_SUN_SHADOW_INTENSITY, 1.0); } void ON_Sun::CImpl::SetShadowIntensity(double d) { SetParameter(XMLPath_Sun(), ON_RDK_SUN_SHADOW_INTENSITY, std::max(0.0, std::min(1.0, d))); } double ON_Sun::CImpl::North(void) const { if (nullptr != _earth_anchor_point) { // Calculate north angle from earth anchor point. ON_Plane plane; ON_3dVector true_north; GetEarthAnchorPlane(true_north, plane); return ON_DegreesFromRadians(AngleFromVectors(plane.xaxis, true_north, plane.zaxis)); } // No earth anchor point is set; just return the value in the XML. return GetParameter(XMLPath_Sun(), ON_RDK_SUN_NORTH, 90.0).AsDouble(); } void ON_Sun::CImpl::SetNorth(double north) { if (nullptr != _earth_anchor_point) { // Store the north in the earth anchor point. This is more complicated than just setting one value. ON_Plane plane; ON_3dVector true_north; GetEarthAnchorPlane(true_north, plane); // 29 January 2013 - Kike: Don't use ON_Plane::Rotate without origin. // This function doesn't keep the vector length if the axis is the plane's z axis. plane.Rotate(ON_RadiansFromDegrees(north - 90.0), plane.zaxis, plane.origin); if (!IsVectorEqual(_earth_anchor_point->ModelEast(), plane.xaxis) || !IsVectorEqual(_earth_anchor_point->ModelNorth(), plane.yaxis)) { _earth_anchor_point->SetModelEast (plane.xaxis); _earth_anchor_point->SetModelNorth(plane.yaxis); } // Make sure this value never appears in the XML. This will clean old documents that were // incorrectly saving this value in the XML as well as the earth anchor point. RemoveParameter(XMLPath_Sun(), ON_RDK_SUN_NORTH); } else { // No earth anchor point is set; store the value in the XML. SetParameter(XMLPath_Sun(), ON_RDK_SUN_NORTH, north); } _calc_dirty = true; } double ON_Sun::CImpl::Latitude(void) const { if (nullptr != _earth_anchor_point) { // Retrieve the latitude from the earth anchor point. const double unset_latitude = 0.0; return _earth_anchor_point->Latitude(unset_latitude); } // No earth anchor point is set; just return the value in the XML. return GetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LATITUDE, 0.0).AsDouble(); } void ON_Sun::CImpl::SetLatitude(double lat) { if (nullptr != _earth_anchor_point) { // Store the latitude in the earth anchor point. _earth_anchor_point->SetLatitude(lat); // Make sure this value never appears in the XML. This will clean old documents that were // incorrectly saving this value in the XML as well as the earth anchor point. RemoveParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LATITUDE); } else { // No earth anchor point is set; store the value in the XML. SetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LATITUDE, lat); } _calc_dirty = true; } double ON_Sun::CImpl::Longitude(void) const { if (nullptr != _earth_anchor_point) { // Retrieve the longitude from the earth anchor point. const double unset_longitude = 0.0; return _earth_anchor_point->Longitude(unset_longitude); } // No earth anchor point is set; just return the value in the XML. return GetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LONGITUDE, 0.0).AsDouble(); } void ON_Sun::CImpl::SetLongitude(double lon) { if (nullptr != _earth_anchor_point) { // Store the longitude in the earth anchor point. _earth_anchor_point->SetLongitude(lon); // Make sure this value never appears in the XML. This will clean old documents that were // incorrectly saving this value in the XML as well as the earth anchor point. RemoveParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LONGITUDE); } else { // No earth anchor point is set; store the value in the XML. SetParameter(XMLPath_Sun(), ON_RDK_SUN_OBSERVER_LONGITUDE, lon); } _calc_dirty = true; } ON_Sun::ON_Sun() { _impl = new CImpl; } ON_Sun::ON_Sun(ON_EarthAnchorPoint& eap) { _impl = new CImpl; _impl->_earth_anchor_point = &eap; } ON_Sun::ON_Sun(ON_XMLNode& model_node) { _impl = new CImpl(model_node); } ON_Sun::ON_Sun(const ON_Sun& sun) { _impl = new CImpl; operator = (sun); } ON_Sun::~ON_Sun() { delete _impl; _impl = nullptr; } int ON_Sun::MinYear(void) { return 1971; } int ON_Sun::MaxYear(void) { return 2199; } const ON_Sun& ON_Sun::operator = (const ON_Sun& sun) { if (this != &sun) { // When copying the object, we need to directly copy the underlying XML. So we can't allow // virtual overrides to execute because they might hide the real values we want to copy. ON_Sun::SetEnableOn (sun.ON_Sun::EnableOn()); ON_Sun::SetEnableAllowed (sun.ON_Sun::EnableAllowed()); ON_Sun::SetEnableOn (sun.ON_Sun::EnableOn()); ON_Sun::SetManualControlAllowed (sun.ON_Sun::ManualControlAllowed()); ON_Sun::SetManualControlOn (sun.ON_Sun::ManualControlOn()); ON_Sun::SetNorth (sun.ON_Sun::North()); ON_Sun::SetDaylightSavingOn (sun.ON_Sun::DaylightSavingOn()); ON_Sun::SetDaylightSavingMinutes(sun.ON_Sun::DaylightSavingMinutes()); ON_Sun::SetAzimuth (sun.ON_Sun::Azimuth()); ON_Sun::SetAltitude (sun.ON_Sun::Altitude()); ON_Sun::SetLatitude (sun.ON_Sun::Latitude()); ON_Sun::SetLongitude (sun.ON_Sun::Longitude()); ON_Sun::SetTimeZone (sun.ON_Sun::TimeZone()); ON_Sun::SetIntensity (sun.ON_Sun::Intensity()); ON_Sun::SetShadowIntensity (sun.ON_Sun::ShadowIntensity()); int y = 0, m = 0, d = 0; double h = 0.0; sun.ON_Sun::LocalDateTime(y, m, d, h); ON_Sun::SetLocalDateTime(y, m, d, h); _impl->_azimuth = sun._impl->_azimuth; _impl->_altitude = sun._impl->_altitude; _impl->_calc_dirty = sun._impl->_calc_dirty; } return *this; } bool ON_Sun::operator == (const ON_Sun& sun) const { // When checking equality, we need to directly check the underlying storage. So we can't allow // virtual overrides to execute because they might hide the real values we want to check. if (ON_Sun::EnableAllowed() != sun.ON_Sun::EnableAllowed()) return false; if (ON_Sun::EnableOn() != sun.ON_Sun::EnableOn()) return false; if (ON_Sun::ManualControlAllowed() != sun.ON_Sun::ManualControlAllowed()) return false; if (ON_Sun::ManualControlOn() != sun.ON_Sun::ManualControlOn()) return false; if (ON_Sun::DaylightSavingOn() != sun.ON_Sun::DaylightSavingOn()) return false; if (ON_Sun::DaylightSavingMinutes() != sun.ON_Sun::DaylightSavingMinutes()) return false; if (!IsDoubleEqual(ON_Sun::North() , sun.ON_Sun::North())) return false; if (!IsDoubleEqual(ON_Sun::Latitude() , sun.ON_Sun::Latitude())) return false; if (!IsDoubleEqual(ON_Sun::Longitude() , sun.ON_Sun::Longitude())) return false; if (!IsDoubleEqual(ON_Sun::TimeZone() , sun.ON_Sun::TimeZone())) return false; if (!IsDoubleEqual(ON_Sun::Intensity() , sun.ON_Sun::Intensity())) return false; if (!IsDoubleEqual(ON_Sun::ShadowIntensity(), sun.ON_Sun::ShadowIntensity())) return false; int y1, m1, d1, y2, m2, d2; double h1, h2; ON_Sun::LocalDateTime(y1, m1, d1, h1); sun.ON_Sun::LocalDateTime(y2, m2, d2, h2); if ((y1 != y2) || (m1 != m2) || (d1 != d2) || !IsDoubleEqual(h1, h2)) return false; if (_impl->IsUsingManualControl()) { if (!IsDoubleEqual(ON_Sun::Azimuth() , sun.ON_Sun::Azimuth())) return false; if (!IsDoubleEqual(ON_Sun::Altitude(), sun.ON_Sun::Altitude())) return false; } return true; } bool ON_Sun::operator != (const ON_Sun& sun) const { return !(operator == (sun)); } bool ON_Sun::EnableAllowed(void) const { return _impl->EnableAllowed(); } bool ON_Sun::EnableOn(void) const { return _impl->EnableOn(); } bool ON_Sun::ManualControlAllowed(void) const { return _impl->ManualControlAllowed(); } bool ON_Sun::ManualControlOn(void) const { return _impl->ManualControlOn(); } double ON_Sun::Azimuth(void) const { return _impl->Azimuth(); } double ON_Sun::Altitude(void) const { return _impl->Altitude(); } double ON_Sun::TimeZone(void) const { return _impl->TimeZone(); } double ON_Sun::North(void) const { return _impl->North(); } double ON_Sun::Latitude(void) const { return _impl->Latitude(); } double ON_Sun::Longitude(void) const { return _impl->Longitude(); } bool ON_Sun::DaylightSavingOn(void) const { return _impl->DaylightSavingOn(); } int ON_Sun::DaylightSavingMinutes(void) const { return _impl->DaylightSavingMinutes(); } void ON_Sun::LocalDateTime(int& year, int& month, int& day, double& hours) const { _impl->LocalDateTime(year, month, day, hours); } void ON_Sun::UTCDateTime(int& year, int& month, int& day, double& hours) const { // Get the local date and time. LocalDateTime(year, month, day, hours); // Set the local time, timezone and daylight into a helper 'sun'. ON_SunEngine engine(_impl->_accuracy); engine.SetLocalDateTime(year, month, day, hours); engine.SetTimeZoneHours(TimeZone()); engine.SetDaylightSavingMinutes(DaylightSavingOn() ? DaylightSavingMinutes() : 0); // Get the UTC JD and set it as 'local', then get the 'local' (Greenwich) time. engine.SetLocalJulianDay(engine.JulianDay()); engine.LocalDateTime(year, month, day, hours); } double ON_Sun::Intensity(void) const { return _impl->Intensity(); } double ON_Sun::ShadowIntensity(void) const { return _impl->ShadowIntensity(); } void ON_Sun::SetEnableAllowed(bool allowed) { _impl->SetEnableAllowed(allowed); } void ON_Sun::SetEnableOn(bool on) { _impl->SetEnableOn(on); } void ON_Sun::SetManualControlAllowed(bool allowed) { _impl->SetManualControlAllowed(allowed); } void ON_Sun::SetManualControlOn(bool manual) { _impl->SetManualControlOn(manual); } void ON_Sun::SetAzimuth(double azi) { _impl->SetAzimuth(azi); } void ON_Sun::SetAltitude(double alt) { _impl->SetAltitude(alt); } void ON_Sun::SetTimeZone(double hours) { _impl->SetTimeZone(hours); } void ON_Sun::SetNorth(double north) { _impl->SetNorth(north); } void ON_Sun::SetLatitude(double latitude) { _impl->SetLatitude(latitude); } void ON_Sun::SetLongitude(double longitude) { _impl->SetLongitude(longitude); } void ON_Sun::SetDaylightSavingOn(bool on) { return _impl->SetDaylightSavingOn(on); } void ON_Sun::SetDaylightSavingMinutes(int minutes) { return _impl->SetDaylightSavingMinutes(minutes); } bool ON_Sun::SetLocalDateTime(int year, int month, int day, double hours) { return _impl->SetLocalDateTime(year, month, day, hours); } bool ON_Sun::SetUTCDateTime(int year, int month, int day, double hours) { // Set the UTC date and time into a helper 'sun' assuming 'local' means 'Greenwich' and get the UTC JD. ON_SunEngine engine(_impl->_accuracy); engine.SetLocalDateTime(year, month, day, hours); // Convert the UTC JD to local JD by adding the timezone and daylight offset, // and use the helper 'sun' to get the local date and time. const int dsm = DaylightSavingOn() ? DaylightSavingMinutes() : 0; engine.SetLocalJulianDay(engine.JulianDay() + (TimeZone() + (dsm / 60.0)) / 24.0); engine.LocalDateTime(year, month, day, hours); // Set the local date and time. return SetLocalDateTime(year, month, day, hours); } void ON_Sun::SetIntensity(double d) { _impl->SetIntensity(d); } void ON_Sun::SetShadowIntensity(double d) { _impl->SetShadowIntensity(d); } ON__UINT32 ON_Sun::DataCRC(ON__UINT32 crc) const { // We need to calculate the CRC of the underlying storage, so we can't allow virtual overrides to // execute because they might hide the real values we want to use. crc = UpdateCRC(crc, ON_Sun::EnableAllowed()); crc = UpdateCRC(crc, ON_Sun::EnableOn()); crc = UpdateCRC(crc, ON_Sun::ManualControlAllowed()); crc = UpdateCRC(crc, ON_Sun::ManualControlOn()); crc = UpdateCRC(crc, ON_Sun::DaylightSavingOn()); crc = UpdateCRC(crc, ON_Sun::DaylightSavingMinutes()); crc = UpdateCRC(crc, Integerize(ON_Sun::Azimuth())); crc = UpdateCRC(crc, Integerize(ON_Sun::Altitude())); crc = UpdateCRC(crc, Integerize(ON_Sun::North())); crc = UpdateCRC(crc, Integerize(ON_Sun::Latitude())); crc = UpdateCRC(crc, Integerize(ON_Sun::Longitude())); crc = UpdateCRC(crc, Integerize(ON_Sun::TimeZone())); crc = UpdateCRC(crc, Integerize(ON_Sun::ShadowIntensity())); crc = UpdateCRC(crc, Integerize(ON_Sun::Intensity())); int y = 0, m = 0, d = 0; double h = 0.0; ON_Sun::LocalDateTime(y, m, d, h); crc = UpdateCRC(crc, y); crc = UpdateCRC(crc, m); crc = UpdateCRC(crc, d); crc = UpdateCRC(crc, Integerize(h)); return crc; } #define SUN_ASSERT(x) ON_ASSERT(x); if (!(x)) return false; bool ON_Sun::IsValid(void) const { // Returns false if SUN_ASSERT fails. double hours = 0.0; int year = 0, month = 0, day = 0; LocalDateTime(year, month, day, hours); SUN_ASSERT(year >= MinYear()); SUN_ASSERT(year <= MaxYear()); SUN_ASSERT(month >= 1); SUN_ASSERT(month <= 12); SUN_ASSERT(day >= 1); SUN_ASSERT(day <= ON_SunEngine::DaysInMonth(month, year)); SUN_ASSERT(hours >= 0.0); SUN_ASSERT(hours <= 24.0); SUN_ASSERT(Azimuth() >= 0.0); SUN_ASSERT(Azimuth() <= 360.0) SUN_ASSERT(Altitude() >= -90.0); SUN_ASSERT(Altitude() <= +90.0); SUN_ASSERT(North() >= 0.0); SUN_ASSERT(North() <= 360.0) SUN_ASSERT(Latitude() >= -90.0); SUN_ASSERT(Latitude() <= +90.0); SUN_ASSERT(Longitude() >= -180.0); SUN_ASSERT(Longitude() <= +180.0); SUN_ASSERT(TimeZone() >= -12.0); SUN_ASSERT(TimeZone() <= +13.0); SUN_ASSERT(DaylightSavingMinutes() >= 0); SUN_ASSERT(DaylightSavingMinutes() <= 120); SUN_ASSERT(Intensity() >= 0.0); SUN_ASSERT(ShadowIntensity() >= 0.0); SUN_ASSERT(ShadowIntensity() <= 1.0); return true; } void ON_Sun::LoadFromXMLNode(const ON_XMLNode& node) { ON_XMLParameters p(node); const auto y = p.GetParam(ON_RDK_SUN_DATE_YEAR, 2000).AsInteger(); const auto m = p.GetParam(ON_RDK_SUN_DATE_MONTH, 1).AsInteger(); const auto d = p.GetParam(ON_RDK_SUN_DATE_DAY, 1).AsInteger(); const auto h = p.GetParam(ON_RDK_SUN_TIME_HOURS, 12.0).AsDouble(); SetLocalDateTime(y, m, d, h); SetEnableAllowed (p.GetParam(ON_RDK_SUN_ENABLE_ALLOWED, false)); SetEnableOn (p.GetParam(ON_RDK_SUN_ENABLE_ON, false)); SetManualControlAllowed (p.GetParam(ON_RDK_SUN_MANUAL_CONTROL_ALLOWED, false)); SetManualControlOn (p.GetParam(ON_RDK_SUN_MANUAL_CONTROL_ON, false)); SetNorth (p.GetParam(ON_RDK_SUN_NORTH, 90.0)); SetAzimuth (p.GetParam(ON_RDK_SUN_AZIMUTH, 0.0)); SetAltitude (p.GetParam(ON_RDK_SUN_ALTITUDE, 0.0)); SetDaylightSavingOn (p.GetParam(ON_RDK_SUN_DAYLIGHT_SAVING_ON, false)); SetDaylightSavingMinutes(p.GetParam(ON_RDK_SUN_DAYLIGHT_SAVING_MINUTES, 0)); SetLatitude (p.GetParam(ON_RDK_SUN_OBSERVER_LATITUDE, 0.0)); SetLongitude (p.GetParam(ON_RDK_SUN_OBSERVER_LONGITUDE, 0.0)); SetTimeZone (p.GetParam(ON_RDK_SUN_OBSERVER_TIMEZONE, 0)); SetShadowIntensity (p.GetParam(ON_RDK_SUN_SHADOW_INTENSITY, 1.0)); SetIntensity (p.GetParam(ON_RDK_SUN_INTENSITY, 1.0)); } void ON_Sun::SaveToXMLNode(ON_XMLNode& node) const { ON_XMLParameters p(node); int y = 0, m = 0, d = 0; double h = 0.0; LocalDateTime(y, m, d, h); p.SetParam(ON_RDK_SUN_DATE_YEAR , y); p.SetParam(ON_RDK_SUN_DATE_MONTH, m); p.SetParam(ON_RDK_SUN_DATE_DAY , d); p.SetParam(ON_RDK_SUN_TIME_HOURS, h); p.SetParam(ON_RDK_SUN_ENABLE_ALLOWED , EnableAllowed()); p.SetParam(ON_RDK_SUN_ENABLE_ON , EnableOn()); p.SetParam(ON_RDK_SUN_MANUAL_CONTROL_ALLOWED , ManualControlAllowed()); p.SetParam(ON_RDK_SUN_MANUAL_CONTROL_ON , ManualControlOn()); p.SetParam(ON_RDK_SUN_NORTH , North()); p.SetParam(ON_RDK_SUN_AZIMUTH , Azimuth()); p.SetParam(ON_RDK_SUN_ALTITUDE , Altitude()); p.SetParam(ON_RDK_SUN_DAYLIGHT_SAVING_ON , DaylightSavingOn()); p.SetParam(ON_RDK_SUN_DAYLIGHT_SAVING_MINUTES, DaylightSavingMinutes()); p.SetParam(ON_RDK_SUN_OBSERVER_LATITUDE , Latitude()); p.SetParam(ON_RDK_SUN_OBSERVER_LONGITUDE , Longitude()); p.SetParam(ON_RDK_SUN_OBSERVER_TIMEZONE , TimeZone()); p.SetParam(ON_RDK_SUN_SHADOW_INTENSITY , ShadowIntensity()); p.SetParam(ON_RDK_SUN_INTENSITY , Intensity()); } ON_4fColor ON_Sun::SunColorFromAltitude(double altitude) // Static. { ON_4fColor colDark(0, 0, 64, 255); if (altitude < -TwilightZone()) return colDark; const double value = (30.0 * pow(std::max(0.0, altitude), 1.5)) + 500.0; auto temperature = std::min(5300.0, value); ON_4fColor col = ColorTemperature(temperature); if (altitude < 0.0) { const double dBlend = -altitude / TwilightZone(); col.BlendTo(float(dBlend), colDark); } return col; } bool ON_Sun::IsValidDateTime(int y, int m, int d, int h, int n, int s) // Static. { if ((h < 0) || (n < 0) || (s < 0)) return false; if ((h > 23) || (n > 59) || (s > 59)) return false; if ((y < ON_Sun::MinYear()) || (m < 1) || (d < 1)) return false; if ((y > ON_Sun::MaxYear()) || (m > 12) || (d > ON_SunEngine::DaysInMonth(m, y))) return false; return true; } static ON_UUID uuidFeatureSun = { 0x62ee2cf6, 0xb855, 0x4549, { 0xa2, 0x77, 0xe2, 0xbb, 0xf6, 0x09, 0xf3, 0x28 } }; ON_Light ON_Sun::Light(void) const { ON_Light light; light.Enable(false); light.SetStyle(ON::world_directional_light); light.SetLocation(ON_3dPoint(0.0, 0.0, 0.0)); light.SetIntensity(Intensity()); light.SetShadowIntensity(ShadowIntensity()); if (IsValid()) { const auto vec = CalculateVectorFromAzimuthAndAltitude(); light.SetDirection(vec); const bool on = EnableOn(); light.Enable(on); const auto col = SunColorFromAltitude(Altitude()); light.SetDiffuse(col); } light.m_light_id = uuidFeatureSun; return light; } ON_3dVector ON_Sun::CalculateVectorFromAzimuthAndAltitude(void) const { const double azimuth = Azimuth() + WorldToCompass(North()); double d[3] = { 0 }; ON_SunEngine::ConvertHorizonCoordsToSolarVector(azimuth, Altitude(), d); return ON_3dVector(d); } void ON_Sun::SetAzimuthAndAltitudeFromVector(const ON_3dVector& v) { ON_3dVector vec = v; vec.Unitize(); double azimuth = 0.0, altitude = 0.0; const double d[3] = { vec.x, vec.y, vec.z }; ON_SunEngine::ConvertSolarVectorToHorizonCoords(d, azimuth, altitude); azimuth -= WorldToCompass(North()); SetAzimuth(azimuth); SetAltitude(altitude); } void ON_Sun::SetXMLNode(ON_XMLNode& node) const { _impl->SetModelNode(node); } void ON_Sun::UseEarthAnchorPoint(ON_EarthAnchorPoint& eap) { _impl->_earth_anchor_point = &eap; } ON_SunEngine::Accuracy ON_Sun::Accuracy(void) const { return _impl->_accuracy; } void ON_Sun::SetAccuracy(ON_SunEngine::Accuracy acc) { _impl->_accuracy = acc; } void* ON_Sun::EVF(const wchar_t* func, void* data) { return nullptr; } void ON_Sun::OnInternalXmlChanged(const ON_Sun* sun) { if (nullptr != sun) { // The XML has been bulk-overwritten from other XML by the client code. We must now copy the // north, latitude and longitude values from the other sun into this one in case one or the // other sun is using the earth anchor point for those values instead of the XML. SetNorth(sun->North()); SetLatitude(sun->Latitude()); SetLongitude(sun->Longitude()); } } static const int SunVersion = 1; bool ON_Sun::WriteToArchive(ON_BinaryArchive& archive) const { if (!archive.WriteInt(SunVersion)) return false; ON_XMLRootNode root; SaveToXMLNode(root); auto length_wide = root.WriteToStream(nullptr, 0); if (length_wide <= 0) return false; ON_wString sXML; sXML.ReserveArray(size_t(length_wide + 1)); length_wide = root.WriteToStream(sXML.Array(), length_wide); if (length_wide <= 0) return false; const auto* wsz = static_cast(sXML); const auto length_utf8 = ON_ConvertWideCharToUTF8(false, wsz, -1, nullptr, 0, nullptr, 0, 0, nullptr); auto utf8 = std::unique_ptr(new char[length_utf8]); auto* pUTF8 = utf8.get(); ON_ConvertWideCharToUTF8(false, wsz, -1, pUTF8, length_utf8, nullptr, 0, 0, nullptr); // Write the length of the UTF8 buffer to the archive. if (!archive.WriteInt(length_utf8)) return false; // Write the UTF8 buffer to the archive. if (!archive.WriteChar(length_utf8, pUTF8)) return false; return true; } bool ON_Sun::ReadFromArchive(ON_BinaryArchive& archive) { int version = 0; if (!archive.ReadInt(&version)) return false; if (SunVersion != version) return false; int length_utf8 = 0; if (!archive.ReadInt(&length_utf8)) return false; auto utf8 = std::unique_ptr(new char[size_t(length_utf8)+1]); auto* pUTF8 = utf8.get(); if (!archive.ReadChar(length_utf8, pUTF8)) return false; pUTF8[length_utf8] = 0; // Terminator. const auto length_wide = ON_ConvertUTF8ToWideChar(false, pUTF8, -1, nullptr, 0, nullptr, 0, 0, nullptr); auto wide = std::unique_ptr(new wchar_t[length_wide + 1]); auto* pWide = wide.get(); ON_ConvertUTF8ToWideChar(false, pUTF8, -1, pWide, length_wide + 1, nullptr, 0, 0, nullptr); ON_XMLRootNode root; root.ReadFromStream(pWide); LoadFromXMLNode(root); return true; }