0025442: Visualization, TKOpenGl - prevent inclusion of system header glxext.h
[occt.git] / src / OpenGl / OpenGl_SceneGeometry.cxx
CommitLineData
e276548b 1// Created on: 2013-08-27
2// Created by: Denis BOGOLEPOV
3// Copyright (c) 2013 OPEN CASCADE SAS
4//
973c2be1 5// This file is part of Open CASCADE Technology software library.
e276548b 6//
d5f74e42 7// This library is free software; you can redistribute it and/or modify it under
8// the terms of the GNU Lesser General Public License version 2.1 as published
973c2be1 9// by the Free Software Foundation, with special exception defined in the file
10// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11// distribution for complete text of the license and disclaimer of any warranty.
e276548b 12//
973c2be1 13// Alternatively, this file may be used under the terms of Open CASCADE
14// commercial license or contractual agreement.
e276548b 15
265d4508 16#ifdef HAVE_TBB
be8d29f5 17 // On Windows, function TryEnterCriticalSection has appeared in Windows NT
18 // and is surrounded by #ifdef in MS VC++ 7.1 headers.
b7cd4ba7 19 // Thus to use it we need to define appropriate macro saying that we will
be8d29f5 20 // run on Windows NT 4.0 at least
b7cd4ba7 21 #if defined(_WIN32) && !defined(_WIN32_WINNT)
be8d29f5 22 #define _WIN32_WINNT 0x0501
23 #endif
24
265d4508 25 #include <tbb/tbb.h>
26#endif
e276548b 27
265d4508 28#include <OpenGl_SceneGeometry.hxx>
e276548b 29
25ef750e 30#include <OpenGl_ArbTexBindless.hxx>
b7cd4ba7 31#include <OpenGl_PrimitiveArray.hxx>
32#include <OpenGl_Structure.hxx>
25ef750e 33#include <OSD_Timer.hxx>
34#include <Standard_Assert.hxx>
b7cd4ba7 35
265d4508 36//! Use this macro to output BVH profiling info
25ef750e 37// #define RAY_TRACE_PRINT_INFO
e276548b 38
265d4508 39namespace
40{
e276548b 41 //! Useful constant for null floating-point 4D vector.
265d4508 42 static const BVH_Vec4f ZERO_VEC_4F;
25ef750e 43}
e276548b 44
45// =======================================================================
64c759f8 46// function : OpenGl_RaytraceMaterial
e276548b 47// purpose : Creates new default material
48// =======================================================================
49OpenGl_RaytraceMaterial::OpenGl_RaytraceMaterial()
265d4508 50: Ambient (ZERO_VEC_4F),
51 Diffuse (ZERO_VEC_4F),
52 Specular (ZERO_VEC_4F),
53 Emission (ZERO_VEC_4F),
54 Reflection (ZERO_VEC_4F),
55 Refraction (ZERO_VEC_4F),
56 Transparency (ZERO_VEC_4F)
e276548b 57{ }
58
59// =======================================================================
64c759f8 60// function : OpenGl_RaytraceMaterial
e276548b 61// purpose : Creates new material with specified properties
62// =======================================================================
265d4508 63OpenGl_RaytraceMaterial::OpenGl_RaytraceMaterial (const BVH_Vec4f& theAmbient,
64 const BVH_Vec4f& theDiffuse,
65 const BVH_Vec4f& theSpecular)
e276548b 66: Ambient (theAmbient),
67 Diffuse (theDiffuse),
68 Specular (theSpecular),
265d4508 69 Emission (ZERO_VEC_4F),
70 Reflection (ZERO_VEC_4F),
71 Refraction (ZERO_VEC_4F),
72 Transparency (ZERO_VEC_4F)
e276548b 73{
74 //
75}
76
77// =======================================================================
64c759f8 78// function : OpenGl_RaytraceMaterial
e276548b 79// purpose : Creates new material with specified properties
80// =======================================================================
265d4508 81OpenGl_RaytraceMaterial::OpenGl_RaytraceMaterial (const BVH_Vec4f& theAmbient,
82 const BVH_Vec4f& theDiffuse,
83 const BVH_Vec4f& theSpecular,
84 const BVH_Vec4f& theEmission,
85 const BVH_Vec4f& theTranspar)
e276548b 86: Ambient (theAmbient),
87 Diffuse (theDiffuse),
88 Specular (theSpecular),
89 Emission (theEmission),
265d4508 90 Reflection (ZERO_VEC_4F),
91 Refraction (ZERO_VEC_4F),
e276548b 92 Transparency (theTranspar)
93{
94 //
95}
96
97// =======================================================================
64c759f8 98// function : OpenGl_RaytraceMaterial
e276548b 99// purpose : Creates new material with specified properties
100// =======================================================================
265d4508 101OpenGl_RaytraceMaterial::OpenGl_RaytraceMaterial (const BVH_Vec4f& theAmbient,
102 const BVH_Vec4f& theDiffuse,
103 const BVH_Vec4f& theSpecular,
104 const BVH_Vec4f& theEmission,
105 const BVH_Vec4f& theTranspar,
106 const BVH_Vec4f& theReflection,
107 const BVH_Vec4f& theRefraction)
e276548b 108: Ambient (theAmbient),
109 Diffuse (theDiffuse),
110 Specular (theSpecular),
111 Emission (theEmission),
112 Reflection (theReflection),
113 Refraction (theRefraction),
114 Transparency (theTranspar)
115{
116 //
117}
118
119// =======================================================================
120// function : OpenGl_LightSource
121// purpose : Creates new light source
122// =======================================================================
265d4508 123OpenGl_RaytraceLight::OpenGl_RaytraceLight (const BVH_Vec4f& theDiffuse,
124 const BVH_Vec4f& thePosition)
e276548b 125: Diffuse (theDiffuse),
126 Position (thePosition)
127{
128 //
129}
130
131// =======================================================================
50d0e1ce 132// function : Center
133// purpose : Returns centroid position along the given axis
134// =======================================================================
135Standard_ShortReal OpenGl_TriangleSet::Center (
136 const Standard_Integer theIndex, const Standard_Integer theAxis) const
137{
138 // Note: Experiments show that the use of the box centroid (instead
139 // of the triangle centroid) increases render performance up to 12%
140
141 const BVH_Vec4i& aTriangle = Elements[theIndex];
142
143 const Standard_ShortReal aVertex0 =
144 BVH::VecComp<Standard_ShortReal, 3>::Get (Vertices[aTriangle.x()], theAxis);
145 const Standard_ShortReal aVertex1 =
146 BVH::VecComp<Standard_ShortReal, 3>::Get (Vertices[aTriangle.y()], theAxis);
147 const Standard_ShortReal aVertex2 =
148 BVH::VecComp<Standard_ShortReal, 3>::Get (Vertices[aTriangle.z()], theAxis);
149
150 return (Min (Min (aVertex0, aVertex1), aVertex2) +
151 Max (Max (aVertex0, aVertex1), aVertex2)) * 0.5f;
152}
153
154// =======================================================================
25ef750e 155// function : Box
156// purpose : Returns AABB of primitive set
157// =======================================================================
158OpenGl_TriangleSet::BVH_BoxNt OpenGl_TriangleSet::Box() const
159{
160 const BVH_Transform<Standard_ShortReal, 4>* aTransform =
161 dynamic_cast<const BVH_Transform<Standard_ShortReal, 4>* > (Properties().operator->());
162
163 BVH_BoxNt aBox = BVH_PrimitiveSet<Standard_ShortReal, 3>::Box();
164
165 if (aTransform != NULL)
166 {
167 BVH_BoxNt aTransformedBox;
168
169 for (Standard_Integer aX = 0; aX <= 1; ++aX)
170 {
171 for (Standard_Integer aY = 0; aY <= 1; ++aY)
172 {
173 for (Standard_Integer aZ = 0; aZ <= 1; ++aZ)
174 {
175 BVH_Vec4f aCorner = aTransform->Transform() * BVH_Vec4f (
176 aX == 0 ? aBox.CornerMin().x() : aBox.CornerMax().x(),
177 aY == 0 ? aBox.CornerMin().y() : aBox.CornerMax().y(),
178 aZ == 0 ? aBox.CornerMin().z() : aBox.CornerMax().z(),
179 1.f);
180
181 aTransformedBox.Add (reinterpret_cast<BVH_Vec3f&> (aCorner));
182 }
183 }
184 }
185
186 return aTransformedBox;
187 }
188
189 return aBox;
190}
191
192// =======================================================================
e276548b 193// function : Clear
265d4508 194// purpose : Clears ray-tracing geometry
e276548b 195// =======================================================================
265d4508 196void OpenGl_RaytraceGeometry::Clear()
e276548b 197{
25ef750e 198 BVH_Geometry<Standard_ShortReal, 3>::BVH_Geometry::Clear();
e276548b 199
265d4508 200 std::vector<OpenGl_RaytraceLight,
201 NCollection_StdAllocator<OpenGl_RaytraceLight> > anEmptySources;
e276548b 202
265d4508 203 Sources.swap (anEmptySources);
e276548b 204
205 std::vector<OpenGl_RaytraceMaterial,
265d4508 206 NCollection_StdAllocator<OpenGl_RaytraceMaterial> > anEmptyMaterials;
e276548b 207
208 Materials.swap (anEmptyMaterials);
209}
210
265d4508 211#ifdef HAVE_TBB
e276548b 212
265d4508 213struct OpenGL_BVHParallelBuilder
e276548b 214{
25ef750e 215 BVH_ObjectSet<Standard_ShortReal, 3>* Set;
e276548b 216
25ef750e 217 OpenGL_BVHParallelBuilder (BVH_ObjectSet<Standard_ShortReal, 3>* theSet)
265d4508 218 : Set (theSet)
e276548b 219 {
265d4508 220 //
e276548b 221 }
e276548b 222
265d4508 223 void operator() (const tbb::blocked_range<size_t>& theRange) const
224 {
225 for (size_t anObjectIdx = theRange.begin(); anObjectIdx != theRange.end(); ++anObjectIdx)
226 {
227 OpenGl_TriangleSet* aTriangleSet = dynamic_cast<OpenGl_TriangleSet*> (
228 Set->Objects().ChangeValue (static_cast<Standard_Integer> (anObjectIdx)).operator->());
5322131b 229
265d4508 230 if (aTriangleSet != NULL)
231 {
232 aTriangleSet->BVH();
233 }
234 }
235 }
236};
e276548b 237
e276548b 238#endif
239
240// =======================================================================
265d4508 241// function : ProcessAcceleration
242// purpose : Performs post-processing of high-level BVH
e276548b 243// =======================================================================
265d4508 244Standard_Boolean OpenGl_RaytraceGeometry::ProcessAcceleration()
e276548b 245{
25ef750e 246#ifdef RAY_TRACE_PRINT_INFO
265d4508 247 OSD_Timer aTimer;
e276548b 248#endif
5322131b 249
265d4508 250 MarkDirty(); // force BVH rebuilding
e276548b 251
25ef750e 252#ifdef RAY_TRACE_PRINT_INFO
265d4508 253 aTimer.Reset();
254 aTimer.Start();
e276548b 255#endif
256
265d4508 257#ifdef HAVE_TBB
258 // If Intel TBB is available, perform the preliminary
259 // construction of bottom-level scene BVHs
260 tbb::parallel_for (tbb::blocked_range<size_t> (0, Size()),
261 OpenGL_BVHParallelBuilder (this));
262#endif
5322131b 263
fc73a202 264 myBottomLevelTreeDepth = 0;
265
265d4508 266 for (Standard_Integer anObjectIdx = 0; anObjectIdx < Size(); ++anObjectIdx)
e276548b 267 {
265d4508 268 OpenGl_TriangleSet* aTriangleSet = dynamic_cast<OpenGl_TriangleSet*> (
269 myObjects.ChangeValue (anObjectIdx).operator->());
270
271 Standard_ASSERT_RETURN (aTriangleSet != NULL,
272 "Error! Failed to get triangulation of OpenGL element", Standard_False);
5322131b 273
265d4508 274 Standard_ASSERT_RETURN (!aTriangleSet->BVH().IsNull(),
275 "Error! Failed to update bottom-level BVH of OpenGL element", Standard_False);
fc73a202 276
277 myBottomLevelTreeDepth = Max (myBottomLevelTreeDepth, aTriangleSet->BVH()->Depth());
e276548b 278 }
279
25ef750e 280#ifdef RAY_TRACE_PRINT_INFO
265d4508 281 aTimer.Stop();
e276548b 282
265d4508 283 std::cout << "Updating bottom-level BVHs (sec): " <<
284 aTimer.ElapsedTime() << std::endl;
285#endif
e276548b 286
25ef750e 287#ifdef RAY_TRACE_PRINT_INFO
265d4508 288 aTimer.Reset();
289 aTimer.Start();
290#endif
e276548b 291
25ef750e 292 NCollection_Handle<BVH_Tree<Standard_ShortReal, 3> > aBVH = BVH();
e276548b 293
25ef750e 294#ifdef RAY_TRACE_PRINT_INFO
265d4508 295 aTimer.Stop();
e276548b 296
265d4508 297 std::cout << "Updating high-level BVH (sec): " <<
298 aTimer.ElapsedTime() << std::endl;
e276548b 299#endif
300
265d4508 301 Standard_ASSERT_RETURN (!aBVH.IsNull(),
302 "Error! Failed to update high-level BVH of ray-tracing scene", Standard_False);
e276548b 303
fc73a202 304 myHighLevelTreeDepth = aBVH->Depth();
305
265d4508 306 Standard_Integer aVerticesOffset = 0;
307 Standard_Integer aElementsOffset = 0;
308 Standard_Integer aBVHNodesOffset = 0;
e276548b 309
265d4508 310 for (Standard_Integer aNodeIdx = 0; aNodeIdx < aBVH->Length(); ++aNodeIdx)
e276548b 311 {
265d4508 312 if (!aBVH->IsOuter (aNodeIdx))
e276548b 313 continue;
314
265d4508 315 Standard_ASSERT_RETURN (aBVH->BegPrimitive (aNodeIdx) == aBVH->EndPrimitive (aNodeIdx),
316 "Error! Invalid leaf node in high-level BVH (contains several objects)", Standard_False);
e276548b 317
265d4508 318 Standard_Integer anObjectIdx = aBVH->BegPrimitive (aNodeIdx);
e276548b 319
265d4508 320 Standard_ASSERT_RETURN (anObjectIdx < myObjects.Size(),
321 "Error! Invalid leaf node in high-level BVH (contains out-of-range object)", Standard_False);
e276548b 322
265d4508 323 OpenGl_TriangleSet* aTriangleSet = dynamic_cast<OpenGl_TriangleSet*> (
324 myObjects.ChangeValue (anObjectIdx).operator->());
e276548b 325
265d4508 326 // Note: We overwrite node info record to store parameters
327 // of bottom-level BVH and triangulation of OpenGL element
e276548b 328
265d4508 329 aBVH->NodeInfoBuffer().at (aNodeIdx) = BVH_Vec4i (
330 anObjectIdx + 1 /* to keep leaf flag */, aBVHNodesOffset, aVerticesOffset, aElementsOffset);
e276548b 331
d5f74e42 332 aVerticesOffset += (int)aTriangleSet->Vertices.size();
333 aElementsOffset += (int)aTriangleSet->Elements.size();
265d4508 334 aBVHNodesOffset += aTriangleSet->BVH()->Length();
e276548b 335 }
e276548b 336
265d4508 337 return Standard_True;
338}
e276548b 339
265d4508 340// =======================================================================
341// function : AccelerationOffset
342// purpose : Returns offset of bottom-level BVH for given leaf node
343// =======================================================================
344Standard_Integer OpenGl_RaytraceGeometry::AccelerationOffset (Standard_Integer theNodeIdx)
345{
25ef750e 346 const NCollection_Handle<BVH_Tree<Standard_ShortReal, 3> >& aBVH = BVH();
e276548b 347
265d4508 348 if (theNodeIdx >= aBVH->Length() || !aBVH->IsOuter (theNodeIdx))
349 return INVALID_OFFSET;
e276548b 350
265d4508 351 return aBVH->NodeInfoBuffer().at (theNodeIdx).y();
e276548b 352}
353
354// =======================================================================
265d4508 355// function : VerticesOffset
356// purpose : Returns offset of triangulation vertices for given leaf node
e276548b 357// =======================================================================
265d4508 358Standard_Integer OpenGl_RaytraceGeometry::VerticesOffset (Standard_Integer theNodeIdx)
e276548b 359{
25ef750e 360 const NCollection_Handle<BVH_Tree<Standard_ShortReal, 3> >& aBVH = BVH();
e276548b 361
265d4508 362 if (theNodeIdx >= aBVH->Length() || !aBVH->IsOuter (theNodeIdx))
363 return INVALID_OFFSET;
e276548b 364
265d4508 365 return aBVH->NodeInfoBuffer().at (theNodeIdx).z();
366}
e276548b 367
265d4508 368// =======================================================================
369// function : ElementsOffset
370// purpose : Returns offset of triangulation elements for given leaf node
371// =======================================================================
372Standard_Integer OpenGl_RaytraceGeometry::ElementsOffset (Standard_Integer theNodeIdx)
373{
25ef750e 374 const NCollection_Handle<BVH_Tree<Standard_ShortReal, 3> >& aBVH = BVH();
e276548b 375
265d4508 376 if (theNodeIdx >= aBVH->Length() || !aBVH->IsOuter (theNodeIdx))
377 return INVALID_OFFSET;
e276548b 378
265d4508 379 return aBVH->NodeInfoBuffer().at (theNodeIdx).w();
e276548b 380}
381
382// =======================================================================
265d4508 383// function : TriangleSet
384// purpose : Returns triangulation data for given leaf node
e276548b 385// =======================================================================
265d4508 386OpenGl_TriangleSet* OpenGl_RaytraceGeometry::TriangleSet (Standard_Integer theNodeIdx)
e276548b 387{
25ef750e 388 const NCollection_Handle<BVH_Tree<Standard_ShortReal, 3> >& aBVH = BVH();
e276548b 389
265d4508 390 if (theNodeIdx >= aBVH->Length() || !aBVH->IsOuter (theNodeIdx))
391 return NULL;
e276548b 392
265d4508 393 if (aBVH->NodeInfoBuffer().at (theNodeIdx).x() > myObjects.Size())
394 return NULL;
5322131b 395
265d4508 396 return dynamic_cast<OpenGl_TriangleSet*> (myObjects.ChangeValue (
397 aBVH->NodeInfoBuffer().at (theNodeIdx).x() - 1).operator->());
e276548b 398}
399
25ef750e 400// =======================================================================
401// function : AcquireTextures
402// purpose : Makes the OpenGL texture handles resident
403// =======================================================================
404Standard_Boolean OpenGl_RaytraceGeometry::AcquireTextures (const Handle(OpenGl_Context)& theContext) const
405{
406 if (theContext->arbTexBindless == NULL)
407 {
408 return Standard_True;
409 }
410
411#if !defined(GL_ES_VERSION_2_0)
412 for (Standard_Integer anIdx = 0; anIdx < myTextures.Size(); ++anIdx)
413 {
414 theContext->arbTexBindless->glMakeTextureHandleResidentARB (myTextureHandles[anIdx]);
415
416 if (glGetError() != GL_NO_ERROR)
417 {
418#ifdef RAY_TRACE_PRINT_INFO
419 std::cout << "Error: Failed to make OpenGL texture resident" << std::endl;
420#endif
421 return Standard_False;
422 }
423 }
424#endif
425
426 return Standard_True;
427}
428
429// =======================================================================
430// function : ReleaseTextures
431// purpose : Makes the OpenGL texture handles non-resident
432// =======================================================================
433Standard_Boolean OpenGl_RaytraceGeometry::ReleaseTextures (const Handle(OpenGl_Context)& theContext) const
434{
435 if (theContext->arbTexBindless == NULL)
436 {
437 return Standard_True;
438 }
439
440#if !defined(GL_ES_VERSION_2_0)
441 for (Standard_Integer anIdx = 0; anIdx < myTextures.Size(); ++anIdx)
442 {
443 theContext->arbTexBindless->glMakeTextureHandleNonResidentARB (myTextureHandles[anIdx]);
444
445 if (glGetError() != GL_NO_ERROR)
446 {
447#ifdef RAY_TRACE_PRINT_INFO
448 std::cout << "Error: Failed to make OpenGL texture non-resident" << std::endl;
449#endif
450 return Standard_False;
451 }
452 }
453#endif
454
455 return Standard_True;
456}
457
458// =======================================================================
459// function : AddTexture
460// purpose : Adds new OpenGL texture to the scene and returns its index
461// =======================================================================
462Standard_Integer OpenGl_RaytraceGeometry::AddTexture (const Handle(OpenGl_Texture)& theTexture)
463{
464 NCollection_Vector<Handle (OpenGl_Texture)>::iterator anIter =
465 std::find (myTextures.begin(), myTextures.end(), theTexture);
466
467 if (anIter == myTextures.end())
468 {
469 if (myTextures.Size() >= MAX_TEX_NUMBER)
470 {
471 return -1;
472 }
473
474 myTextures.Append (theTexture);
475 }
476
477 return static_cast<Standard_Integer> (anIter - myTextures.begin());
478}
479
480// =======================================================================
481// function : UpdateTextureHandles
482// purpose : Updates unique 64-bit texture handles to use in shaders
483// =======================================================================
484Standard_Boolean OpenGl_RaytraceGeometry::UpdateTextureHandles (const Handle(OpenGl_Context)& theContext)
485{
486 if (theContext->arbTexBindless == NULL)
487 {
488 return Standard_False;
489 }
490
491 myTextureHandles.clear();
492
493#if !defined(GL_ES_VERSION_2_0)
494 for (Standard_Integer anIdx = 0; anIdx < myTextures.Size(); ++anIdx)
495 {
496 const GLuint64 aHandle = theContext->arbTexBindless->glGetTextureHandleARB (
497 myTextures.Value (anIdx)->TextureId());
498
499 if (glGetError() != GL_NO_ERROR)
500 {
501#ifdef RAY_TRACE_PRINT_INFO
502 std::cout << "Error: Failed to get 64-bit handle of OpenGL texture" << std::endl;
503#endif
504 return Standard_False;
505 }
506
507 myTextureHandles.push_back (aHandle);
508 }
509#endif
510
511 return Standard_True;
512}
513
e276548b 514namespace OpenGl_Raytrace
515{
516 // =======================================================================
517 // function : IsRaytracedElement
518 // purpose : Checks to see if the element contains ray-trace geometry
519 // =======================================================================
520 Standard_Boolean IsRaytracedElement (const OpenGl_ElementNode* theNode)
521 {
25ef750e 522 OpenGl_PrimitiveArray* anArray = dynamic_cast<OpenGl_PrimitiveArray*> (theNode->elem);
5322131b 523 return anArray != NULL
871fa103 524 && anArray->DrawMode() >= GL_TRIANGLES;
e276548b 525 }
526
527 // =======================================================================
a89742cf 528 // function : IsRaytracedElement
529 // purpose : Checks to see if the element contains ray-trace geometry
530 // =======================================================================
531 Standard_Boolean IsRaytracedElement (const OpenGl_Element* theElement)
532 {
533 const OpenGl_PrimitiveArray* anArray = dynamic_cast<const OpenGl_PrimitiveArray*> (theElement);
534 return anArray != NULL
535 && anArray->DrawMode() >= GL_TRIANGLES;
536 }
537
538 // =======================================================================
e276548b 539 // function : IsRaytracedGroup
540 // purpose : Checks to see if the group contains ray-trace geometry
541 // =======================================================================
542 Standard_Boolean IsRaytracedGroup (const OpenGl_Group *theGroup)
543 {
544 const OpenGl_ElementNode* aNode;
545 for (aNode = theGroup->FirstNode(); aNode != NULL; aNode = aNode->next)
546 {
547 if (IsRaytracedElement (aNode))
548 {
549 return Standard_True;
550 }
551 }
552 return Standard_False;
553 }
554
555 // =======================================================================
556 // function : IsRaytracedStructure
557 // purpose : Checks to see if the structure contains ray-trace geometry
558 // =======================================================================
b64d84be 559 Standard_Boolean IsRaytracedStructure (const OpenGl_Structure* theStructure)
e276548b 560 {
b64d84be 561 for (OpenGl_Structure::GroupIterator aGroupIter (theStructure->DrawGroups());
562 aGroupIter.More(); aGroupIter.Next())
e276548b 563 {
b64d84be 564 if (aGroupIter.Value()->IsRaytracable())
e276548b 565 return Standard_True;
566 }
567 for (OpenGl_ListOfStructure::Iterator anIts (theStructure->ConnectedStructures());
568 anIts.More(); anIts.Next())
569 {
570 if (IsRaytracedStructure (anIts.Value()))
571 return Standard_True;
572 }
573 return Standard_False;
574 }
575}