+/////////////////////////////////////////////////////////////////////////////
+// Name: dxfrenderer.cpp
+// Purpose: DXF reader and renderer
+// Author: Sandro Sigala
+// Modified by:
+// Created: 2005-11-10
+// RCS-ID: $Id$
+// Copyright: (c) Sandro Sigala
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// For compilers that support precompilation, includes "wx/wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+#pragma hdrstop
+#endif
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#include "wx/wfstream.h"
+#include "wx/txtstrm.h"
+
+#if !wxUSE_GLCANVAS
+ #error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#ifdef __WXMAC__
+# ifdef __DARWIN__
+# include <OpenGL/glu.h>
+# else
+# include <glu.h>
+# endif
+#else
+# include <GL/glu.h>
+#endif
+
+#include "dxfrenderer.h"
+
+#include <wx/listimpl.cpp>
+WX_DEFINE_LIST(DXFEntityList);
+WX_DEFINE_LIST(DXFLayerList);
+
+// Conversion table from AutoCAD ACI colours to RGB values
+static const struct { unsigned char r, g, b; } aci_to_rgb[256] = {
+/* 0 */ 255, 255, 255,
+/* 1 */ 255, 0, 0,
+/* 2 */ 255, 255, 0,
+/* 3 */ 0, 255, 0,
+/* 4 */ 0, 255, 255,
+/* 5 */ 0, 0, 255,
+/* 6 */ 255, 0, 255,
+/* 7 */ 255, 255, 255,
+/* 8 */ 128, 128, 128,
+/* 9 */ 192, 192, 192,
+/* 10 */ 255, 0, 0,
+/* 11 */ 255, 127, 127,
+/* 12 */ 204, 0, 0,
+/* 13 */ 204, 102, 102,
+/* 14 */ 153, 0, 0,
+/* 15 */ 153, 76, 76,
+/* 16 */ 127, 0, 0,
+/* 17 */ 127, 63, 63,
+/* 18 */ 76, 0, 0,
+/* 19 */ 76, 38, 38,
+/* 20 */ 255, 63, 0,
+/* 21 */ 255, 159, 127,
+/* 22 */ 204, 51, 0,
+/* 23 */ 204, 127, 102,
+/* 24 */ 153, 38, 0,
+/* 25 */ 153, 95, 76,
+/* 26 */ 127, 31, 0,
+/* 27 */ 127, 79, 63,
+/* 28 */ 76, 19, 0,
+/* 29 */ 76, 47, 38,
+/* 30 */ 255, 127, 0,
+/* 31 */ 255, 191, 127,
+/* 32 */ 204, 102, 0,
+/* 33 */ 204, 153, 102,
+/* 34 */ 153, 76, 0,
+/* 35 */ 153, 114, 76,
+/* 36 */ 127, 63, 0,
+/* 37 */ 127, 95, 63,
+/* 38 */ 76, 38, 0,
+/* 39 */ 76, 57, 38,
+/* 40 */ 255, 191, 0,
+/* 41 */ 255, 223, 127,
+/* 42 */ 204, 153, 0,
+/* 43 */ 204, 178, 102,
+/* 44 */ 153, 114, 0,
+/* 45 */ 153, 133, 76,
+/* 46 */ 127, 95, 0,
+/* 47 */ 127, 111, 63,
+/* 48 */ 76, 57, 0,
+/* 49 */ 76, 66, 38,
+/* 50 */ 255, 255, 0,
+/* 51 */ 255, 255, 127,
+/* 52 */ 204, 204, 0,
+/* 53 */ 204, 204, 102,
+/* 54 */ 153, 153, 0,
+/* 55 */ 153, 153, 76,
+/* 56 */ 127, 127, 0,
+/* 57 */ 127, 127, 63,
+/* 58 */ 76, 76, 0,
+/* 59 */ 76, 76, 38,
+/* 60 */ 191, 255, 0,
+/* 61 */ 223, 255, 127,
+/* 62 */ 153, 204, 0,
+/* 63 */ 178, 204, 102,
+/* 64 */ 114, 153, 0,
+/* 65 */ 133, 153, 76,
+/* 66 */ 95, 127, 0,
+/* 67 */ 111, 127, 63,
+/* 68 */ 57, 76, 0,
+/* 69 */ 66, 76, 38,
+/* 70 */ 127, 255, 0,
+/* 71 */ 191, 255, 127,
+/* 72 */ 102, 204, 0,
+/* 73 */ 153, 204, 102,
+/* 74 */ 76, 153, 0,
+/* 75 */ 114, 153, 76,
+/* 76 */ 63, 127, 0,
+/* 77 */ 95, 127, 63,
+/* 78 */ 38, 76, 0,
+/* 79 */ 57, 76, 38,
+/* 80 */ 63, 255, 0,
+/* 81 */ 159, 255, 127,
+/* 82 */ 51, 204, 0,
+/* 83 */ 127, 204, 102,
+/* 84 */ 38, 153, 0,
+/* 85 */ 95, 153, 76,
+/* 86 */ 31, 127, 0,
+/* 87 */ 79, 127, 63,
+/* 88 */ 19, 76, 0,
+/* 89 */ 47, 76, 38,
+/* 90 */ 0, 255, 0,
+/* 91 */ 127, 255, 127,
+/* 92 */ 0, 204, 0,
+/* 93 */ 102, 204, 102,
+/* 94 */ 0, 153, 0,
+/* 95 */ 76, 153, 76,
+/* 96 */ 0, 127, 0,
+/* 97 */ 63, 127, 63,
+/* 98 */ 0, 76, 0,
+/* 99 */ 38, 76, 38,
+/* 100 */ 0, 255, 63,
+/* 101 */ 127, 255, 159,
+/* 102 */ 0, 204, 51,
+/* 103 */ 102, 204, 127,
+/* 104 */ 0, 153, 38,
+/* 105 */ 76, 153, 95,
+/* 106 */ 0, 127, 31,
+/* 107 */ 63, 127, 79,
+/* 108 */ 0, 76, 19,
+/* 109 */ 38, 76, 47,
+/* 110 */ 0, 255, 127,
+/* 111 */ 127, 255, 191,
+/* 112 */ 0, 204, 102,
+/* 113 */ 102, 204, 153,
+/* 114 */ 0, 153, 76,
+/* 115 */ 76, 153, 114,
+/* 116 */ 0, 127, 63,
+/* 117 */ 63, 127, 95,
+/* 118 */ 0, 76, 38,
+/* 119 */ 38, 76, 57,
+/* 120 */ 0, 255, 191,
+/* 121 */ 127, 255, 223,
+/* 122 */ 0, 204, 153,
+/* 123 */ 102, 204, 178,
+/* 124 */ 0, 153, 114,
+/* 125 */ 76, 153, 133,
+/* 126 */ 0, 127, 95,
+/* 127 */ 63, 127, 111,
+/* 128 */ 0, 76, 57,
+/* 129 */ 38, 76, 66,
+/* 130 */ 0, 255, 255,
+/* 131 */ 127, 255, 255,
+/* 132 */ 0, 204, 204,
+/* 133 */ 102, 204, 204,
+/* 134 */ 0, 153, 153,
+/* 135 */ 76, 153, 153,
+/* 136 */ 0, 127, 127,
+/* 137 */ 63, 127, 127,
+/* 138 */ 0, 76, 76,
+/* 139 */ 38, 76, 76,
+/* 140 */ 0, 191, 255,
+/* 141 */ 127, 223, 255,
+/* 142 */ 0, 153, 204,
+/* 143 */ 102, 178, 204,
+/* 144 */ 0, 114, 153,
+/* 145 */ 76, 133, 153,
+/* 146 */ 0, 95, 127,
+/* 147 */ 63, 111, 127,
+/* 148 */ 0, 57, 76,
+/* 149 */ 38, 66, 76,
+/* 150 */ 0, 127, 255,
+/* 151 */ 127, 191, 255,
+/* 152 */ 0, 102, 204,
+/* 153 */ 102, 153, 204,
+/* 154 */ 0, 76, 153,
+/* 155 */ 76, 114, 153,
+/* 156 */ 0, 63, 127,
+/* 157 */ 63, 95, 127,
+/* 158 */ 0, 38, 76,
+/* 159 */ 38, 57, 76,
+/* 160 */ 0, 63, 255,
+/* 161 */ 127, 159, 255,
+/* 162 */ 0, 51, 204,
+/* 163 */ 102, 127, 204,
+/* 164 */ 0, 38, 153,
+/* 165 */ 76, 95, 153,
+/* 166 */ 0, 31, 127,
+/* 167 */ 63, 79, 127,
+/* 168 */ 0, 19, 76,
+/* 169 */ 38, 47, 76,
+/* 170 */ 0, 0, 255,
+/* 171 */ 127, 127, 255,
+/* 172 */ 0, 0, 204,
+/* 173 */ 102, 102, 204,
+/* 174 */ 0, 0, 153,
+/* 175 */ 76, 76, 153,
+/* 176 */ 0, 0, 127,
+/* 177 */ 63, 63, 127,
+/* 178 */ 0, 0, 76,
+/* 179 */ 38, 38, 76,
+/* 180 */ 63, 0, 255,
+/* 181 */ 159, 127, 255,
+/* 182 */ 51, 0, 204,
+/* 183 */ 127, 102, 204,
+/* 184 */ 38, 0, 153,
+/* 185 */ 95, 76, 153,
+/* 186 */ 31, 0, 127,
+/* 187 */ 79, 63, 127,
+/* 188 */ 19, 0, 76,
+/* 189 */ 47, 38, 76,
+/* 190 */ 127, 0, 255,
+/* 191 */ 191, 127, 255,
+/* 192 */ 102, 0, 204,
+/* 193 */ 153, 102, 204,
+/* 194 */ 76, 0, 153,
+/* 195 */ 114, 76, 153,
+/* 196 */ 63, 0, 127,
+/* 197 */ 95, 63, 127,
+/* 198 */ 38, 0, 76,
+/* 199 */ 57, 38, 76,
+/* 200 */ 191, 0, 255,
+/* 201 */ 223, 127, 255,
+/* 202 */ 153, 0, 204,
+/* 203 */ 178, 102, 204,
+/* 204 */ 114, 0, 153,
+/* 205 */ 133, 76, 153,
+/* 206 */ 95, 0, 127,
+/* 207 */ 111, 63, 127,
+/* 208 */ 57, 0, 76,
+/* 209 */ 66, 38, 76,
+/* 210 */ 255, 0, 255,
+/* 211 */ 255, 127, 255,
+/* 212 */ 204, 0, 204,
+/* 213 */ 204, 102, 204,
+/* 214 */ 153, 0, 153,
+/* 215 */ 153, 76, 153,
+/* 216 */ 127, 0, 127,
+/* 217 */ 127, 63, 127,
+/* 218 */ 76, 0, 76,
+/* 219 */ 76, 38, 76,
+/* 220 */ 255, 0, 191,
+/* 221 */ 255, 127, 223,
+/* 222 */ 204, 0, 153,
+/* 223 */ 204, 102, 178,
+/* 224 */ 153, 0, 114,
+/* 225 */ 153, 76, 133,
+/* 226 */ 127, 0, 95,
+/* 227 */ 127, 63, 111,
+/* 228 */ 76, 0, 57,
+/* 229 */ 76, 38, 66,
+/* 230 */ 255, 0, 127,
+/* 231 */ 255, 127, 191,
+/* 232 */ 204, 0, 102,
+/* 233 */ 204, 102, 153,
+/* 234 */ 153, 0, 76,
+/* 235 */ 153, 76, 114,
+/* 236 */ 127, 0, 63,
+/* 237 */ 127, 63, 95,
+/* 238 */ 76, 0, 38,
+/* 239 */ 76, 38, 57,
+/* 240 */ 255, 0, 63,
+/* 241 */ 255, 127, 159,
+/* 242 */ 204, 0, 51,
+/* 243 */ 204, 102, 127,
+/* 244 */ 153, 0, 38,
+/* 245 */ 153, 76, 95,
+/* 246 */ 127, 0, 31,
+/* 247 */ 127, 63, 79,
+/* 248 */ 76, 0, 19,
+/* 249 */ 76, 38, 47,
+/* 250 */ 51, 51, 51,
+/* 251 */ 91, 91, 91,
+/* 252 */ 132, 132, 132,
+/* 253 */ 173, 173, 173,
+/* 254 */ 214, 214, 214,
+/* 255 */ 255, 255, 255,
+};
+
+inline DXFVector Cross(const DXFVector& v1, const DXFVector& v2)
+{
+ return DXFVector(v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x);
+}
+
+void DXFFace::CalculateNormal()
+{
+ DXFVector v01, v02;
+ v01.x = v0.x - v1.x;
+ v01.y = v0.y - v1.y;
+ v01.z = v0.z - v1.z;
+ v02.x = v0.x - v2.x;
+ v02.y = v0.y - v2.y;
+ v02.z = v0.z - v2.z;
+ n = Cross(v01, v02);
+ float mod = sqrt(n.x*n.x + n.y*n.y + n.z*n.z);
+ n.x /= mod;
+ n.y /= mod;
+ n.z /= mod;
+}
+
+// convert an AutoCAD ACI colour to wxWidgets RGB colour
+inline wxColour ACIColourToRGB(int col)
+{
+ wxASSERT(col >= 0 && col <= 255);
+ return wxColour(aci_to_rgb[col].r, aci_to_rgb[col].g, aci_to_rgb[col].b);
+}
+
+// DXFReader constructor
+DXFRenderer::DXFRenderer()
+{
+ m_loaded = false;
+}
+
+// DXFReader destructor
+DXFRenderer::~DXFRenderer()
+{
+ Clear();
+}
+
+// deallocate all the dynamic data
+void DXFRenderer::Clear()
+{
+ m_loaded = false;
+ {
+ for (DXFLayerList::Node *node = m_layers.GetFirst(); node; node = node->GetNext())
+ {
+ DXFLayer *current = node->GetData();
+ delete current;
+ }
+ }
+ m_layers.Clear();
+ {
+ for (DXFEntityList::Node *node = m_entities.GetFirst(); node; node = node->GetNext())
+ {
+ DXFEntity *current = node->GetData();
+ delete current;
+ }
+ m_entities.Clear();
+ }
+}
+
+int DXFRenderer::GetLayerColour(const wxString& layer) const
+{
+ for (DXFLayerList::Node *node = m_layers.GetFirst(); node; node = node->GetNext())
+ {
+ DXFLayer *current = node->GetData();
+ if (current->name == layer)
+ return current->colour;
+ }
+ return 7; // white
+}
+
+// read two sequential lines
+inline void GetLines(wxTextInputStream& text, wxString& line1, wxString& line2)
+{
+ line1 = text.ReadLine().Trim().Trim(false);
+ line2 = text.ReadLine().Trim().Trim(false);
+}
+
+// parse header section: just skip everything
+bool DXFRenderer::ParseHeader(wxInputStream& stream)
+{
+ wxTextInputStream text(stream);
+ wxString line1, line2;
+ while (stream.CanRead())
+ {
+ GetLines(text, line1, line2);
+ if (line1 == wxT("0") && line2 == wxT("ENDSEC"))
+ return true;
+ }
+ return false;
+}
+
+// parse tables section: save layers name and colour
+bool DXFRenderer::ParseTables(wxInputStream& stream)
+{
+ wxTextInputStream text(stream);
+ wxString line1, line2;
+ bool inlayer=false;
+ DXFLayer layer;
+ while (stream.CanRead())
+ {
+ GetLines(text, line1, line2);
+ if (line1 == wxT("0") && inlayer)
+ {
+ // flush layer
+ if (!layer.name.IsEmpty() && layer.colour != -1)
+ {
+ DXFLayer *p = new DXFLayer;
+ p->name = layer.name;
+ p->colour = layer.colour;
+ m_layers.Append(p);
+ }
+ layer = DXFLayer();
+ inlayer = false;
+ }
+ if (line1 == wxT("0") && line2 == wxT("ENDSEC"))
+ return true;
+ else if (line1 == wxT("0") && line2 == wxT("LAYER"))
+ inlayer = true;
+ else if (inlayer)
+ {
+ if (line1 == wxT("2")) // layer name
+ layer.name = line2;
+ else if (line1 == wxT("62")) // ACI colour
+ {
+ long l;
+ line2.ToLong(&l);
+ layer.colour = l;
+ }
+ }
+ }
+ return false;
+}
+
+// parse entities section: save 3DFACE and LINE entities
+bool DXFRenderer::ParseEntities(wxInputStream& stream)
+{
+ wxTextInputStream text(stream);
+ wxString line1, line2;
+ int state = 0; // 0: none, 1: 3DFACE, 2: LINE
+ DXFVector v[4];
+ int colour = -1;
+ wxString layer;
+ while (stream.CanRead())
+ {
+ GetLines(text, line1, line2);
+ if (line1 == wxT("0") && state > 0)
+ {
+ // flush entity
+ if (state == 1) // 3DFACE
+ {
+ DXFFace *p = new DXFFace;
+ p->v0 = v[0];
+ p->v1 = v[1];
+ p->v2 = v[2];
+ p->v3 = v[3];
+ p->CalculateNormal();
+ if (colour != -1)
+ p->colour = colour;
+ else
+ p->colour = GetLayerColour(layer);
+ m_entities.Append(p);
+ colour = -1; layer = wxEmptyString;
+ v[0] = v[1] = v[2] = v[3] = DXFVector();
+ state = 0;
+ }
+ else if (state == 2) // LINE
+ {
+ DXFLine *p = new DXFLine;
+ p->v0 = v[0];
+ p->v1 = v[1];
+ if (colour != -1)
+ p->colour = colour;
+ else
+ p->colour = GetLayerColour(layer);
+ m_entities.Append(p);
+ colour = -1; layer = wxEmptyString;
+ v[0] = v[1] = v[2] = v[3] = DXFVector();
+ state = 0;
+ }
+ }
+ if (line1 == wxT("0") && line2 == wxT("ENDSEC"))
+ return true;
+ else if (line1 == wxT("0") && line2 == wxT("3DFACE"))
+ state = 1;
+ else if (line1 == wxT("0") && line2 == wxT("LINE"))
+ state = 2;
+ else if (state > 0)
+ {
+ double d;
+ line2.ToDouble(&d);
+ if (line1 == wxT("10"))
+ v[0].x = d;
+ else if (line1 == wxT("20"))
+ v[0].y = d;
+ else if (line1 == wxT("30"))
+ v[0].z = d;
+ else if (line1 == wxT("11"))
+ v[1].x = d;
+ else if (line1 == wxT("21"))
+ v[1].y = d;
+ else if (line1 == wxT("31"))
+ v[1].z = d;
+ else if (line1 == wxT("12"))
+ v[2].x = d;
+ else if (line1 == wxT("22"))
+ v[2].y = d;
+ else if (line1 == wxT("32"))
+ v[2].z = d;
+ else if (line1 == wxT("13"))
+ v[3].x = d;
+ else if (line1 == wxT("23"))
+ v[3].y = d;
+ else if (line1 == wxT("33"))
+ v[3].z = d;
+ else if (line1 == wxT("8")) // layer
+ layer = line2;
+ else if (line1 == wxT("62")) // colour
+ {
+ long l;
+ line2.ToLong(&l);
+ colour = l;
+ }
+ }
+ }
+ return false;
+}
+
+// parse and load a DXF file
+// currently pretty limited, but knows enought do handle 3DFACEs and LINEs
+bool DXFRenderer::Load(wxInputStream& stream)
+{
+ Clear();
+ wxTextInputStream text(stream);
+
+ wxString line1, line2;
+ while (stream.CanRead())
+ {
+ GetLines(text, line1, line2);
+ if (line1 == wxT("999")) // comment
+ continue;
+ else if (line1 == wxT("0") && line2 == wxT("SECTION"))
+ {
+ GetLines(text, line1, line2);
+ if (line1 == wxT("2"))
+ {
+ if (line2 == wxT("HEADER"))
+ {
+ if (!ParseHeader(stream))
+ return false;
+ }
+ else if (line2 == wxT("TABLES"))
+ {
+ if (!ParseTables(stream))
+ return false;
+ }
+ else if (line2 == wxT("ENTITIES"))
+ {
+ if (!ParseEntities(stream))
+ return false;
+ }
+ }
+ }
+ }
+
+ NormalizeEntities();
+ m_loaded = true;
+ return true;
+}
+
+inline float mymin(float x, float y) { return x < y ? x : y; }
+inline float mymax(float x, float y) { return x > y ? x : y; }
+
+// Scale object boundings to [-5,5]
+void DXFRenderer::NormalizeEntities()
+{
+ // calculate current min and max boundings of object
+ DXFVector minv(10e20f, 10e20f, 10e20f);
+ DXFVector maxv(-10e20f, -10e20f, -10e20f);
+ for (DXFEntityList::Node *node = m_entities.GetFirst(); node; node = node->GetNext())
+ {
+ DXFEntity *p = node->GetData();
+ if (p->type == DXFEntity::Line)
+ {
+ DXFLine *line = (DXFLine *)p;
+ const DXFVector *v[2] = { &line->v0, &line->v1 };
+ for (int i = 0; i < 2; ++i)
+ {
+ minv.x = mymin(v[i]->x, minv.x);
+ minv.y = mymin(v[i]->y, minv.y);
+ minv.z = mymin(v[i]->z, minv.z);
+ maxv.x = mymax(v[i]->x, maxv.x);
+ maxv.y = mymax(v[i]->y, maxv.y);
+ maxv.z = mymax(v[i]->z, maxv.z);
+ }
+ } else if (p->type == DXFEntity::Face)
+ {
+ DXFFace *face = (DXFFace *)p;
+ const DXFVector *v[4] = { &face->v0, &face->v1, &face->v2, &face->v3 };
+ for (int i = 0; i < 4; ++i)
+ {
+ minv.x = mymin(v[i]->x, minv.x);
+ minv.y = mymin(v[i]->y, minv.y);
+ minv.z = mymin(v[i]->z, minv.z);
+ maxv.x = mymax(v[i]->x, maxv.x);
+ maxv.y = mymax(v[i]->y, maxv.y);
+ maxv.z = mymax(v[i]->z, maxv.z);
+ }
+ }
+ }
+
+ // rescale object down to [-5,5]
+ DXFVector span(maxv.x - minv.x, maxv.y - minv.y, maxv.z - minv.z);
+ float factor = mymin(mymin(10.0f / span.x, 10.0f / span.y), 10.0f / span.z);
+ for (DXFEntityList::Node *node2 = m_entities.GetFirst(); node2; node2 = node2->GetNext())
+ {
+ DXFEntity *p = node2->GetData();
+ if (p->type == DXFEntity::Line)
+ {
+ DXFLine *line = (DXFLine *)p;
+ DXFVector *v[2] = { &line->v0, &line->v1 };
+ for (int i = 0; i < 2; ++i)
+ {
+ v[i]->x -= minv.x + span.x/2; v[i]->x *= factor;
+ v[i]->y -= minv.y + span.y/2; v[i]->y *= factor;
+ v[i]->z -= minv.z + span.z/2; v[i]->z *= factor;
+ }
+ } else if (p->type == DXFEntity::Face)
+ {
+ DXFFace *face = (DXFFace *)p;
+ DXFVector *v[4] = { &face->v0, &face->v1, &face->v2, &face->v3 };
+ for (int i = 0; i < 4; ++i)
+ {
+ v[i]->x -= minv.x + span.x/2; v[i]->x *= factor;
+ v[i]->y -= minv.y + span.y/2; v[i]->y *= factor;
+ v[i]->z -= minv.z + span.z/2; v[i]->z *= factor;
+ }
+ }
+ }
+}
+
+// OpenGL renderer for DXF entities
+void DXFRenderer::Render() const
+{
+ if (!m_loaded)
+ return;
+
+ for (DXFEntityList::Node *node = m_entities.GetFirst(); node; node = node->GetNext())
+ {
+ DXFEntity *p = node->GetData();
+ wxColour c = ACIColourToRGB(p->colour);
+ if (p->type == DXFEntity::Line)
+ {
+ DXFLine *line = (DXFLine *)p;
+ glBegin(GL_LINES);
+ glColor3f(c.Red()/255.0,c.Green()/255.0,c.Blue()/255.0);
+ glVertex3f(line->v0.x, line->v0.y, line->v0.z);
+ glVertex3f(line->v1.x, line->v1.y, line->v1.z);
+ glEnd();
+ }
+ else if (p->type == DXFEntity::Face)
+ {
+ DXFFace *face = (DXFFace *)p;
+ glBegin(GL_TRIANGLES);
+ glColor3f(c.Red()/255.0,c.Green()/255.0,c.Blue()/255.0);
+ glNormal3f(face->n.x, face->n.y, face->n.z);
+ glVertex3f(face->v0.x, face->v0.y, face->v0.z);
+ glVertex3f(face->v1.x, face->v1.y, face->v1.z);
+ glVertex3f(face->v2.x, face->v2.y, face->v2.z);
+ glEnd();
+ }
+ }
+}