]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/entitylib.h
fixed eol-style
[xonotic/netradiant.git] / libs / entitylib.h
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #if !defined (INCLUDED_ENTITYLIB_H)
23 #define INCLUDED_ENTITYLIB_H
24
25 #include "ireference.h"
26 #include "debugging/debugging.h"
27
28 #include "ientity.h"
29 #include "irender.h"
30 #include "igl.h"
31 #include "selectable.h"
32
33 #include "generic/callback.h"
34 #include "math/vector.h"
35 #include "math/aabb.h"
36 #include "undolib.h"
37 #include "string/string.h"
38 #include "generic/referencecounted.h"
39 #include "scenelib.h"
40 #include "container/container.h"
41 #include "eclasslib.h"
42
43 #include <list>
44 #include <set>
45
46 inline void arrow_draw(const Vector3& origin, const Vector3& direction)
47 {
48   Vector3 up(0, 0, 1);
49   Vector3 left(-direction[1], direction[0], 0);
50
51         Vector3 endpoint(vector3_added(origin, vector3_scaled(direction, 32.0)));
52
53   Vector3 tip1(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(up, -4.0)));
54         Vector3 tip2(vector3_added(tip1, vector3_scaled(up, 8.0)));
55   Vector3 tip3(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(left, -4.0)));
56         Vector3 tip4(vector3_added(tip3, vector3_scaled(left, 8.0)));
57
58   glBegin (GL_LINES);
59
60   glVertex3fv(vector3_to_array(origin));
61   glVertex3fv(vector3_to_array(endpoint));
62
63   glVertex3fv(vector3_to_array(endpoint));
64   glVertex3fv(vector3_to_array(tip1));
65
66   glVertex3fv(vector3_to_array(endpoint));
67   glVertex3fv(vector3_to_array(tip2));
68
69   glVertex3fv(vector3_to_array(endpoint));
70   glVertex3fv(vector3_to_array(tip3));
71
72   glVertex3fv(vector3_to_array(endpoint));
73   glVertex3fv(vector3_to_array(tip4));
74
75   glVertex3fv(vector3_to_array(tip1));
76   glVertex3fv(vector3_to_array(tip3));
77
78   glVertex3fv(vector3_to_array(tip3));
79   glVertex3fv(vector3_to_array(tip2));
80
81   glVertex3fv(vector3_to_array(tip2));
82   glVertex3fv(vector3_to_array(tip4));
83
84   glVertex3fv(vector3_to_array(tip4));
85   glVertex3fv(vector3_to_array(tip1));
86
87   glEnd();
88 }
89
90 class SelectionIntersection;
91
92 inline void aabb_testselect(const AABB& aabb, SelectionTest& test, SelectionIntersection& best)
93 {
94   const IndexPointer::index_type indices[24] = {
95     2, 1, 5, 6,
96     1, 0, 4, 5,
97     0, 1, 2, 3,
98     3, 7, 4, 0,
99     3, 2, 6, 7,
100     7, 6, 5, 4,
101   };
102
103   Vector3 points[8];
104   aabb_corners(aabb, points);
105   test.TestQuads(VertexPointer(reinterpret_cast<VertexPointer::pointer>(points), sizeof(Vector3)), IndexPointer(indices, 24), best);
106 }
107
108 inline void aabb_draw_wire(const Vector3 points[8])
109 {
110   typedef std::size_t index_t;
111   index_t indices[24] = {
112     0, 1, 1, 2, 2, 3, 3, 0,
113     4, 5, 5, 6, 6, 7, 7, 4,
114     0, 4, 1, 5, 2, 6, 3, 7,
115   };
116 #if 1
117   glVertexPointer(3, GL_FLOAT, 0, points);
118   glDrawElements(GL_LINES, sizeof(indices)/sizeof(index_t), GL_UNSIGNED_INT, indices);
119 #else
120   glBegin(GL_LINES);
121   for(std::size_t i = 0; i < sizeof(indices)/sizeof(index_t); ++i)
122   {
123     glVertex3fv(points[indices[i]]);
124   }
125   glEnd();
126 #endif
127 }
128
129 inline void aabb_draw_flatshade(const Vector3 points[8])
130 {
131   glBegin(GL_QUADS);
132
133   glNormal3fv(vector3_to_array(aabb_normals[0]));
134   glVertex3fv(vector3_to_array(points[2]));
135   glVertex3fv(vector3_to_array(points[1]));
136   glVertex3fv(vector3_to_array(points[5]));
137   glVertex3fv(vector3_to_array(points[6]));
138
139   glNormal3fv(vector3_to_array(aabb_normals[1]));
140   glVertex3fv(vector3_to_array(points[1]));
141   glVertex3fv(vector3_to_array(points[0]));
142   glVertex3fv(vector3_to_array(points[4]));
143   glVertex3fv(vector3_to_array(points[5]));
144
145   glNormal3fv(vector3_to_array(aabb_normals[2]));
146   glVertex3fv(vector3_to_array(points[0]));
147   glVertex3fv(vector3_to_array(points[1]));
148   glVertex3fv(vector3_to_array(points[2]));
149   glVertex3fv(vector3_to_array(points[3]));
150
151   glNormal3fv(vector3_to_array(aabb_normals[3]));
152   glVertex3fv(vector3_to_array(points[0]));
153   glVertex3fv(vector3_to_array(points[3]));
154   glVertex3fv(vector3_to_array(points[7]));
155   glVertex3fv(vector3_to_array(points[4]));
156
157   glNormal3fv(vector3_to_array(aabb_normals[4]));
158   glVertex3fv(vector3_to_array(points[3]));
159   glVertex3fv(vector3_to_array(points[2]));
160   glVertex3fv(vector3_to_array(points[6]));
161   glVertex3fv(vector3_to_array(points[7]));
162
163   glNormal3fv(vector3_to_array(aabb_normals[5]));
164   glVertex3fv(vector3_to_array(points[7]));
165   glVertex3fv(vector3_to_array(points[6]));
166   glVertex3fv(vector3_to_array(points[5]));
167   glVertex3fv(vector3_to_array(points[4]));
168
169   glEnd();
170 }
171
172 inline void aabb_draw_wire(const AABB& aabb)
173 {
174   Vector3 points[8];
175         aabb_corners(aabb, points);
176   aabb_draw_wire(points);
177 }
178
179 inline void aabb_draw_flatshade(const AABB& aabb)
180 {
181   Vector3 points[8];
182         aabb_corners(aabb, points);
183   aabb_draw_flatshade(points);
184 }
185
186 inline void aabb_draw_textured(const AABB& aabb)
187 {
188   Vector3 points[8];
189         aabb_corners(aabb, points);
190
191   glBegin(GL_QUADS);
192
193   glNormal3fv(vector3_to_array(aabb_normals[0]));
194   glTexCoord2fv(aabb_texcoord_topleft);
195   glVertex3fv(vector3_to_array(points[2]));
196   glTexCoord2fv(aabb_texcoord_topright);
197   glVertex3fv(vector3_to_array(points[1]));
198   glTexCoord2fv(aabb_texcoord_botright);
199   glVertex3fv(vector3_to_array(points[5]));
200   glTexCoord2fv(aabb_texcoord_botleft);
201   glVertex3fv(vector3_to_array(points[6]));
202
203   glNormal3fv(vector3_to_array(aabb_normals[1]));
204   glTexCoord2fv(aabb_texcoord_topleft);
205   glVertex3fv(vector3_to_array(points[1]));
206   glTexCoord2fv(aabb_texcoord_topright);
207   glVertex3fv(vector3_to_array(points[0]));
208   glTexCoord2fv(aabb_texcoord_botright);
209   glVertex3fv(vector3_to_array(points[4]));
210   glTexCoord2fv(aabb_texcoord_botleft);
211   glVertex3fv(vector3_to_array(points[5]));
212
213   glNormal3fv(vector3_to_array(aabb_normals[2]));
214   glTexCoord2fv(aabb_texcoord_topleft);
215   glVertex3fv(vector3_to_array(points[0]));
216   glTexCoord2fv(aabb_texcoord_topright);
217   glVertex3fv(vector3_to_array(points[1]));
218   glTexCoord2fv(aabb_texcoord_botright);
219   glVertex3fv(vector3_to_array(points[2]));
220   glTexCoord2fv(aabb_texcoord_botleft);
221   glVertex3fv(vector3_to_array(points[3]));
222
223   glNormal3fv(vector3_to_array(aabb_normals[3]));
224   glTexCoord2fv(aabb_texcoord_topleft);
225   glVertex3fv(vector3_to_array(points[0]));
226   glTexCoord2fv(aabb_texcoord_topright);
227   glVertex3fv(vector3_to_array(points[3]));
228   glTexCoord2fv(aabb_texcoord_botright);
229   glVertex3fv(vector3_to_array(points[7]));
230   glTexCoord2fv(aabb_texcoord_botleft);
231   glVertex3fv(vector3_to_array(points[4]));
232
233   glNormal3fv(vector3_to_array(aabb_normals[4]));
234   glTexCoord2fv(aabb_texcoord_topleft);
235   glVertex3fv(vector3_to_array(points[3]));
236   glTexCoord2fv(aabb_texcoord_topright);
237   glVertex3fv(vector3_to_array(points[2]));
238   glTexCoord2fv(aabb_texcoord_botright);
239   glVertex3fv(vector3_to_array(points[6]));
240   glTexCoord2fv(aabb_texcoord_botleft);
241   glVertex3fv(vector3_to_array(points[7]));
242
243   glNormal3fv(vector3_to_array(aabb_normals[5]));
244   glTexCoord2fv(aabb_texcoord_topleft);
245   glVertex3fv(vector3_to_array(points[7]));
246   glTexCoord2fv(aabb_texcoord_topright);
247   glVertex3fv(vector3_to_array(points[6]));
248   glTexCoord2fv(aabb_texcoord_botright);
249   glVertex3fv(vector3_to_array(points[5]));
250   glTexCoord2fv(aabb_texcoord_botleft);
251   glVertex3fv(vector3_to_array(points[4]));
252
253   glEnd();
254 }
255
256 inline void aabb_draw_solid(const AABB& aabb, RenderStateFlags state)
257 {
258   if(state & RENDER_TEXTURE)
259   {
260     aabb_draw_textured(aabb);
261   }
262   else
263   {
264     aabb_draw_flatshade(aabb);
265   }
266 }
267
268 inline void aabb_draw(const AABB& aabb, RenderStateFlags state)
269 {
270   if(state & RENDER_FILL)
271   {
272     aabb_draw_solid(aabb, state);
273   }
274   else
275   {
276     aabb_draw_wire(aabb);
277   }
278 }
279
280 class RenderableSolidAABB : public OpenGLRenderable
281 {
282   const AABB& m_aabb;
283 public:
284   RenderableSolidAABB(const AABB& aabb) : m_aabb(aabb)
285   {
286   }
287   void render(RenderStateFlags state) const
288   {
289     aabb_draw_solid(m_aabb, state);
290   }
291 };
292
293 class RenderableWireframeAABB : public OpenGLRenderable
294 {
295   const AABB& m_aabb;
296 public:
297   RenderableWireframeAABB(const AABB& aabb) : m_aabb(aabb)
298   {
299   }
300   void render(RenderStateFlags state) const
301   {
302     aabb_draw_wire(m_aabb);
303   }
304 };
305
306
307 typedef Callback1<const char*> KeyObserver;
308
309 /// \brief A key/value pair of strings.
310 ///
311 /// - Notifies observers when value changes - value changes to "" on destruction.
312 /// - Provides undo support through the global undo system.
313 class KeyValue
314 {
315   typedef UnsortedSet<KeyObserver> KeyObservers;
316
317   std::size_t m_refcount;
318   KeyObservers m_observers;
319   CopiedString m_string;
320   const char* m_empty;
321   ObservedUndoableObject<CopiedString> m_undo;
322   static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged;
323 public:
324
325   KeyValue(const char* string, const char* empty)
326     : m_refcount(0), m_string(string), m_empty(empty), m_undo(m_string, UndoImportCaller(*this))
327   {
328     notify();
329   }
330   ~KeyValue()
331   {
332     ASSERT_MESSAGE(m_observers.empty(), "KeyValue::~KeyValue: observers still attached");
333   }
334
335   static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func)
336   {
337     m_entityKeyValueChanged = func;
338   }
339
340   void IncRef()
341   {
342     ++m_refcount;
343   }
344   void DecRef()
345   {
346     if(--m_refcount == 0)
347     {
348       delete this;
349     }
350   }
351
352   void instanceAttach(MapFile* map)
353   {
354     m_undo.instanceAttach(map);
355   }
356   void instanceDetach(MapFile* map)
357   {
358     m_undo.instanceDetach(map);
359   }
360
361   void attach(const KeyObserver& observer)
362   {
363     (*m_observers.insert(observer))(c_str());
364   }
365   void detach(const KeyObserver& observer)
366   {
367     observer(m_empty);
368     m_observers.erase(observer);
369   }
370   const char* c_str() const
371   {
372     if(string_empty(m_string.c_str()))
373     {
374       return m_empty;
375     }
376     return m_string.c_str();
377   }
378   void assign(const char* other)
379   {
380     if(!string_equal(m_string.c_str(), other))
381     {
382       m_undo.save();
383       m_string = other;
384       notify();
385     }
386   }
387
388   void notify()
389   {
390     m_entityKeyValueChanged();
391     KeyObservers::reverse_iterator i = m_observers.rbegin();
392     while(i != m_observers.rend())
393     {
394       (*i++)(c_str());
395     }
396   }
397
398   void importState(const CopiedString& string)
399   {
400     m_string = string;
401
402     notify();
403   }
404   typedef MemberCaller1<KeyValue, const CopiedString&, &KeyValue::importState> UndoImportCaller;
405 };
406
407 /// \brief An unsorted list of key/value pairs.
408 ///
409 /// - Notifies observers when a pair is inserted or removed.
410 /// - Provides undo support through the global undo system.
411 /// - New keys are appended to the end of the list.
412 class EntityKeyValues : public Entity
413 {
414 public:
415   typedef KeyValue Value;
416
417   class Observer
418   {
419   public:
420     virtual void insert(const char* key, Value& value) = 0;
421     virtual void erase(const char* key, Value& value) = 0;
422   };
423
424 private:
425   static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged;
426   static Counter* m_counter;
427
428   EntityClass* m_eclass;
429
430   typedef SmartPointer<KeyValue> KeyValuePtr;
431   typedef UnsortedMap<CopiedString, KeyValuePtr > KeyValues;
432   KeyValues m_keyValues;
433
434   typedef UnsortedSet<Observer*> Observers;
435   Observers m_observers;
436
437   ObservedUndoableObject<KeyValues> m_undo;
438   bool m_instanced;
439
440   bool m_observerMutex;
441
442   void notifyInsert(const char* key, Value& value)
443   {
444     m_observerMutex = true;
445     for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i)
446     {
447       (*i)->insert(key, value);
448     }
449     m_observerMutex = false;
450   }
451   void notifyErase(const char* key, Value& value)
452   {
453     m_observerMutex = true;
454     for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i)
455     {
456       (*i)->erase(key, value);
457     }
458     m_observerMutex = false;
459   }
460   void forEachKeyValue_notifyInsert()
461   {
462     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
463     {
464       notifyInsert((*i).first.c_str(), *(*i).second);
465     }
466   }
467   void forEachKeyValue_notifyErase()
468   {
469     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
470     {
471       notifyErase((*i).first.c_str(), *(*i).second);
472     }
473   }
474
475   void insert(const char* key, const KeyValuePtr& keyValue)
476   {
477     KeyValues::iterator i = m_keyValues.insert(KeyValues::value_type(key, keyValue));
478     notifyInsert(key, *(*i).second);
479
480     if(m_instanced)
481     {
482       (*i).second->instanceAttach(m_undo.map());
483     }
484   }
485
486   void insert(const char* key, const char* value)
487   {
488     KeyValues::iterator i = m_keyValues.find(key);
489     if(i != m_keyValues.end())
490     {
491       (*i).second->assign(value);
492     }
493     else
494     {
495       m_undo.save();
496       insert(key, KeyValuePtr(new KeyValue(value, EntityClass_valueForKey(*m_eclass, key))));
497     }
498   }
499
500   void erase(KeyValues::iterator i)
501   {
502     if(m_instanced)
503     {
504       (*i).second->instanceDetach(m_undo.map());
505     }
506
507     CopiedString key((*i).first);
508     KeyValuePtr value((*i).second);
509     m_keyValues.erase(i);
510     notifyErase(key.c_str(), *value);
511   }
512
513   void erase(const char* key)
514   {
515     KeyValues::iterator i = m_keyValues.find(key);
516     if(i != m_keyValues.end())
517     {
518       m_undo.save();
519       erase(i);
520     }
521   }
522
523 public:
524   bool m_isContainer;
525
526   EntityKeyValues(EntityClass* eclass) :
527     m_eclass(eclass),
528     m_undo(m_keyValues, UndoImportCaller(*this)),
529     m_instanced(false),
530     m_observerMutex(false),
531     m_isContainer(!eclass->fixedsize)
532   {
533   }
534   EntityKeyValues(const EntityKeyValues& other) :
535     Entity(other),
536     m_eclass(&other.getEntityClass()),
537     m_undo(m_keyValues, UndoImportCaller(*this)),
538     m_instanced(false),
539     m_observerMutex(false),
540     m_isContainer(other.m_isContainer)
541   {
542     for(KeyValues::const_iterator i = other.m_keyValues.begin(); i != other.m_keyValues.end(); ++i)
543     {
544       insert((*i).first.c_str(), (*i).second->c_str());
545     }
546   }
547   ~EntityKeyValues()
548   {
549     ASSERT_MESSAGE(m_observers.empty(), "EntityKeyValues::~EntityKeyValues: observers still attached");
550   }
551
552   static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func)
553   {
554     m_entityKeyValueChanged = func;
555     KeyValue::setKeyValueChangedFunc(func);
556   }
557   static void setCounter(Counter* counter)
558   {
559     m_counter = counter;
560   }
561
562   void importState(const KeyValues& keyValues)
563   {
564     for(KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end();)
565     {
566       erase(i++);
567     }
568
569     for(KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i)
570     {
571       insert((*i).first.c_str(), (*i).second);
572     }
573
574     m_entityKeyValueChanged();
575   }
576   typedef MemberCaller1<EntityKeyValues, const KeyValues&, &EntityKeyValues::importState> UndoImportCaller;
577
578   void attach(Observer& observer)
579   {
580     ASSERT_MESSAGE(!m_observerMutex, "observer cannot be attached during iteration");
581     m_observers.insert(&observer);
582     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
583     {
584       observer.insert((*i).first.c_str(), *(*i).second);
585     }
586   }
587   void detach(Observer& observer)
588   {
589     ASSERT_MESSAGE(!m_observerMutex, "observer cannot be detached during iteration");
590     m_observers.erase(&observer);
591     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
592     {
593       observer.erase((*i).first.c_str(), *(*i).second);
594     }
595   }
596
597   void forEachKeyValue_instanceAttach(MapFile* map)
598   {
599     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
600     {
601       (*i).second->instanceAttach(map);
602     }
603   }
604   void forEachKeyValue_instanceDetach(MapFile* map)
605   {
606     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
607     {
608       (*i).second->instanceDetach(map);
609     }
610   }
611
612   void instanceAttach(MapFile* map)
613   {
614     if(m_counter != 0)
615     {
616       m_counter->increment();
617     }
618
619     m_instanced = true;
620     forEachKeyValue_instanceAttach(map);
621     m_undo.instanceAttach(map);
622   }
623   void instanceDetach(MapFile* map)
624   {
625     if(m_counter != 0)
626     {
627       m_counter->decrement();
628     }
629
630     m_undo.instanceDetach(map);
631     forEachKeyValue_instanceDetach(map);
632     m_instanced = false;
633   }
634
635   // entity
636   EntityClass& getEntityClass() const
637   {
638     return *m_eclass;
639   }
640   void forEachKeyValue(Visitor& visitor) const
641   {
642     for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i)
643     {
644       visitor.visit((*i).first.c_str(), (*i).second->c_str());
645     }
646   }
647   void setKeyValue(const char* key, const char* value)
648   {
649     if(value[0] == '\0'
650       /*|| string_equal(EntityClass_valueForKey(*m_eclass, key), value)*/) // don't delete values equal to default
651     {
652       erase(key);
653     }
654     else
655     {
656       insert(key, value);
657     }
658     m_entityKeyValueChanged();
659   }
660   const char* getKeyValue(const char* key) const
661   {
662     KeyValues::const_iterator i = m_keyValues.find(key);
663     if(i != m_keyValues.end())
664     {
665       return (*i).second->c_str();
666     }
667
668     return EntityClass_valueForKey(*m_eclass, key);
669   }
670
671   bool isContainer() const
672   {
673     return m_isContainer;
674   }
675 };
676
677 /// \brief A Resource reference with a controlled lifetime.
678 /// \brief The resource is released when the ResourceReference is destroyed.
679 class ResourceReference
680 {
681   CopiedString m_name;
682   Resource* m_resource;
683 public:
684   ResourceReference(const char* name)
685     : m_name(name)
686   {
687     capture();
688   }
689   ResourceReference(const ResourceReference& other)
690     : m_name(other.m_name)
691   {
692     capture();
693   }
694   ResourceReference& operator=(const ResourceReference& other)
695   {
696     ResourceReference tmp(other);
697     tmp.swap(*this);
698     return *this;
699   }
700   ~ResourceReference()
701   {
702     release();
703   }
704
705   void capture()
706   {
707     m_resource = GlobalReferenceCache().capture(m_name.c_str());
708   }
709   void release()
710   {
711     GlobalReferenceCache().release(m_name.c_str());
712   }
713
714   const char* getName() const
715   {
716     return m_name.c_str();
717   }
718   void setName(const char* name)
719   {
720     ResourceReference tmp(name);
721     tmp.swap(*this);
722   }
723
724   void swap(ResourceReference& other)
725   {
726     std::swap(m_resource, other.m_resource);
727     std::swap(m_name, other.m_name);
728   }
729
730   void attach(ModuleObserver& observer)
731   {
732     m_resource->attach(observer);
733   }
734   void detach(ModuleObserver& observer)
735   {
736     m_resource->detach(observer);
737   }
738
739   Resource* get()
740   {
741     return m_resource;
742   }
743 };
744
745 namespace std
746 {
747   /// \brief Swaps the values of \p self and \p other.
748   /// Overloads std::swap.
749   inline void swap(ResourceReference& self, ResourceReference& other)
750   {
751     self.swap(other);
752   }
753 }
754
755 #endif