From 1a845c34329389ca48e1d9671996858a5652a77b Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 17 Mar 2009 08:33:46 -0800 Subject: [PATCH] Add hidden line and surface removal, and vector shaded surface export. So I calculate lighting for each triangle in the mesh, make a BSP, and then traverse it in-order and output those as SVG or EPS. And I test edges against the mesh, removing those portions of the edge that overlap a triangle in front of them (using the kd-tree to accelerate). [git-p4: depot-paths = "//depot/solvespace/": change = 1931] --- bsp.cpp | 24 +++++ draw.cpp | 4 +- dsc.h | 2 + export.cpp | 250 ++++++++++++++++++++++++++++++++++++++----------- mesh.cpp | 134 ++++++++++++++++++++++++++ polygon.h | 5 + solvespace.cpp | 1 + solvespace.h | 15 ++- ui.h | 10 +- util.cpp | 13 +++ 10 files changed, 394 insertions(+), 64 deletions(-) diff --git a/bsp.cpp b/bsp.cpp index 08f0aadd..2fda8b7d 100644 --- a/bsp.cpp +++ b/bsp.cpp @@ -390,6 +390,30 @@ SBsp3 *SBsp3::Insert(STriangle *tr, SMesh *instead) { return this; } +void SBsp3::GenerateInPaintOrder(SMesh *m) { + if(!this) return; + + // Doesn't matter which branch we take if the normal has zero z + // component, so don't need a separate case for that. + if(n.z < 0) { + pos->GenerateInPaintOrder(m); + } else { + neg->GenerateInPaintOrder(m); + } + + SBsp3 *flip = this; + while(flip) { + m->AddTriangle(&(flip->tri)); + flip = flip->more; + } + + if(n.z < 0) { + neg->GenerateInPaintOrder(m); + } else { + pos->GenerateInPaintOrder(m); + } +} + void SBsp3::DebugDraw(void) { if(!this) return; diff --git a/draw.cpp b/draw.cpp index f14b69b4..dc9a5529 100644 --- a/draw.cpp +++ b/draw.cpp @@ -992,7 +992,9 @@ void GraphicsWindow::Paint(int w, int h) { glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0); } - GLfloat ambient[4] = { 0.4f, 0.4f, 0.4f, 1.0f }; + GLfloat ambient[4] = { (float)SS.ambientIntensity, + (float)SS.ambientIntensity, + (float)SS.ambientIntensity, 1 }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); glxUnlockColor(); diff --git a/dsc.h b/dsc.h index 68dde2df..84d92222 100644 --- a/dsc.h +++ b/dsc.h @@ -88,6 +88,8 @@ public: static bool BoundingBoxIntersectsLine(Vector amax, Vector amin, Vector p0, Vector p1, bool segment); bool OutsideAndNotOn(Vector maxv, Vector minv); + Vector InPerspective(Vector u, Vector v, Vector n, + Vector origin, double cameraTan); Point2d Project2d(Vector u, Vector v); Point2d ProjectXy(void); }; diff --git a/export.cpp b/export.cpp index 931cbe67..3517d931 100644 --- a/export.cpp +++ b/export.cpp @@ -2,9 +2,6 @@ #include void SolveSpace::ExportSectionTo(char *filename) { - SPolygon sp; - ZERO(&sp); - Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); @@ -89,17 +86,17 @@ void SolveSpace::ExportSectionTo(char *filename) { SEdgeList el; ZERO(&el); root->MakeNakedEdgesInto(&el); - // Assemble those edges into a polygon, and clear the edge list - el.AssemblePolygon(&sp, NULL); - el.Clear(); m.Clear(); - // And write the polygon. + // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { - ExportPolygon(&sp, u, v, n, origin, out); + // parallel projection (no perspective), and no mesh + ExportLinesAndMesh(&el, NULL, + u, v, n, origin, 0, + out); } - sp.Clear(); + el.Clear(); } void SolveSpace::ExportViewTo(char *filename) { @@ -113,9 +110,10 @@ void SolveSpace::ExportViewTo(char *filename) { e->GenerateEdges(&edges); } - SPolygon sp; - ZERO(&sp); - edges.AssemblePolygon(&sp, NULL); + SMesh *sm = NULL; + if(SS.GW.showShaded) { + sm = &((SS.GetGroup(SS.GW.activeGroup))->runningMesh); + } Vector u = SS.GW.projRight, v = SS.GW.projUp, @@ -124,45 +122,124 @@ void SolveSpace::ExportViewTo(char *filename) { VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { - ExportPolygon(&sp, u, v, n, origin, out); + ExportLinesAndMesh(&edges, sm, + u, v, n, origin, SS.cameraTangent*SS.GW.scale, + out); } edges.Clear(); - sp.Clear(); } -void SolveSpace::ExportPolygon(SPolygon *sp, - Vector u, Vector v, Vector n, Vector origin, - VectorFileWriter *out) +void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, + Vector u, Vector v, Vector n, + Vector origin, double cameraTan, + VectorFileWriter *out) { - int i, j; + double s = 1.0 / SS.exportScale; + // Project into the export plane; so when we're done, z doesn't matter, // and x and y are what goes in the DXF. - for(i = 0; i < sp->l.n; i++) { - SContour *sc = &(sp->l.elem[i]); - for(j = 0; j < sc->l.n; j++) { - Vector *p = &(sc->l.elem[j].p); - *p = p->Minus(origin); - *p = p->DotInToCsys(u, v, n); - // and apply the export scale factor - double s = SS.exportScale; - *p = p->ScaledBy(1.0/s); + SEdge *e; + for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + // project into the specified csys, and apply export scale + (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); + (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); + } + + // If cutter radius compensation is requested, then perform it now + if(fabs(SS.exportOffset) > LENGTH_EPS) { + // assemble those edges into a polygon, and clear the edge list + SPolygon sp; + ZERO(&sp); + sel->AssemblePolygon(&sp, NULL); + sel->Clear(); + + SPolygon compd; + ZERO(&compd); + sp.normal = Vector::From(0, 0, -1); + sp.FixContourDirections(); + sp.OffsetInto(&compd, SS.exportOffset); + sp.Clear(); + + compd.MakeEdgesInto(sel); + compd.Clear(); + } + + // Now the triangle mesh; project, then build a BSP to perform + // occlusion testing and generated the shaded surfaces. + SMesh smp; + ZERO(&smp); + if(sm) { + Vector l0 = (SS.lightDir[0]).WithMagnitude(1), + l1 = (SS.lightDir[1]).WithMagnitude(1); + STriangle *tr; + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + STriangle tt = *tr; + tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); + tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); + tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); + + // And calculate lighting for the triangle + Vector n = tt.Normal().WithMagnitude(1); + double lighting = SS.ambientIntensity + + max(0, (SS.lightIntensity[0])*(n.Dot(l0))) + + max(0, (SS.lightIntensity[1])*(n.Dot(l1))); + double r = min(1, REDf (tt.meta.color)*lighting), + g = min(1, GREENf(tt.meta.color)*lighting), + b = min(1, BLUEf (tt.meta.color)*lighting); + tt.meta.color = RGBf(r, g, b); + smp.AddTriangle(&tt); } } - // If cutter radius compensation is requested, then perform it now. - if(fabs(SS.exportOffset) > LENGTH_EPS) { - SPolygon compd; - ZERO(&compd); - sp->normal = Vector::From(0, 0, -1); - sp->FixContourDirections(); - sp->OffsetInto(&compd, SS.exportOffset); - sp->Clear(); - *sp = compd; + // Use the BSP routines to generate the split triangles in paint order. + SBsp3 *bsp = SBsp3::FromMesh(&smp); + SMesh sms; + ZERO(&sms); + bsp->GenerateInPaintOrder(&sms); + // And cull the back-facing triangles + STriangle *tr; + sms.l.ClearTags(); + for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) { + Vector n = tr->Normal(); + if(n.z < 0) { + tr->tag = 1; + } + } + sms.l.RemoveTagged(); + + // And now we perform hidden line removal if requested + SEdgeList hlrd; + ZERO(&hlrd); + if(sm && !SS.GW.showHdnLines) { + SKdNode *root = SKdNode::From(&smp); + root->ClearTags(); + int cnt = 1234; + + SEdge *se; + for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + SEdgeList out; + ZERO(&out); + // Split the original edge against the mesh + out.AddEdge(se->a, se->b); + root->OcclusionTestLine(*se, &out, cnt); + cnt++; + // And add the results to our output + SEdge *sen; + for(sen = out.l.First(); sen; sen = out.l.NextAfter(sen)) { + hlrd.AddEdge(sen->a, sen->b); + } + out.Clear(); + } + + sel = &hlrd; } - // Now begin the entities, which are just line segments reproduced from - // our piecewise linear curves. - out->OutputPolygon(sp); + // Now write the lines and triangles to the output file + out->Output(sel, &sms); + + smp.Clear(); + sms.Clear(); + hlrd.Clear(); } bool VectorFileWriter::StringEndsIn(char *str, char *ending) { @@ -207,28 +284,36 @@ VectorFileWriter *VectorFileWriter::ForFile(char *filename) { return ret; } -void VectorFileWriter::OutputPolygon(SPolygon *sp) { - int i, j; +void VectorFileWriter::Output(SEdgeList *sel, SMesh *sm) { + STriangle *tr; + SEdge *e; // First calculate the bounding box. ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE); - for(i = 0; i < sp->l.n; i++) { - SContour *sc = &(sp->l.elem[i]); - for(j = 0; j < sc->l.n; j++) { - (sc->l.elem[j].p).MakeMaxMin(&ptMax, &ptMin); + if(sel) { + for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + (e->a).MakeMaxMin(&ptMax, &ptMin); + (e->b).MakeMaxMin(&ptMax, &ptMin); + } + } + if(sm) { + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + (tr->a).MakeMaxMin(&ptMax, &ptMin); + (tr->b).MakeMaxMin(&ptMax, &ptMin); + (tr->c).MakeMaxMin(&ptMax, &ptMin); } } StartFile(); - for(i = 0; i < sp->l.n; i++) { - SContour *sc = &(sp->l.elem[i]); - - for(j = 1; j < sc->l.n; j++) { - Vector p0 = sc->l.elem[j-1].p, - p1 = sc->l.elem[j].p; - - LineSegment(p0.x, p0.y, p1.x, p1.y); + if(sm) { + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + Triangle(tr); + } + } + if(sel) { + for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + LineSegment(e->a.x, e->a.y, e->b.x, e->b.y); } } FinishAndCloseFile(); @@ -303,6 +388,8 @@ void DxfFileWriter::LineSegment(double x0, double y0, double x1, double y1) { x0, y0, 0.0, x1, y1, 0.0); } +void DxfFileWriter::Triangle(STriangle *tr) { +} void DxfFileWriter::FinishAndCloseFile(void) { fprintf(f, @@ -351,6 +438,37 @@ void EpsFileWriter::LineSegment(double x0, double y0, double x1, double y1) { MmToPoints(x0 - ptMin.x), MmToPoints(y0 - ptMin.y), MmToPoints(x1 - ptMin.x), MmToPoints(y1 - ptMin.y)); } +void EpsFileWriter::Triangle(STriangle *tr) { + fprintf(f, +"%.3f %.3f %.3f setrgbcolor\r\n" +"newpath\r\n" +" %.3f %.3f moveto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" closepath\r\n" +"fill\r\n", + REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color), + MmToPoints(tr->a.x - ptMin.x), MmToPoints(tr->a.y - ptMin.y), + MmToPoints(tr->b.x - ptMin.x), MmToPoints(tr->b.y - ptMin.y), + MmToPoints(tr->c.x - ptMin.x), MmToPoints(tr->c.y - ptMin.y)); + + // same issue with cracks, stroke it to avoid them + double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; + fprintf(f, +"%.3f %.3f %.3f setrgbcolor\r\n" +"%.3f setlinewidth\r\n" +"newpath\r\n" +" %.3f %.3f moveto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" closepath\r\n" +"stroke\r\n", + REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color), + MmToPoints(sw), + MmToPoints(tr->a.x - ptMin.x), MmToPoints(tr->a.y - ptMin.y), + MmToPoints(tr->b.x - ptMin.x), MmToPoints(tr->b.y - ptMin.y), + MmToPoints(tr->c.x - ptMin.x), MmToPoints(tr->c.y - ptMin.y)); +} void EpsFileWriter::FinishAndCloseFile(void) { fprintf(f, @@ -379,11 +497,28 @@ void SvgFileWriter::StartFile(void) { } void SvgFileWriter::LineSegment(double x0, double y0, double x1, double y1) { + // SVG uses a coordinate system with the origin at top left, +y down fprintf(f, -"\r\n", - (x0 - ptMin.x), (y0 - ptMin.y), - (x1 - ptMin.x), (y1 - ptMin.y)); + (x0 - ptMin.x), (ptMax.y - y0), + (x1 - ptMin.x), (ptMax.y - y1)); +} +void SvgFileWriter::Triangle(STriangle *tr) { + // crispEdges turns of anti-aliasing, which tends to cause hairline + // cracks between triangles; but there still is some cracking, so + // specify a stroke width too, hope for around a pixel + double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; + fprintf(f, +"\r\n", + (tr->a.x - ptMin.x), (ptMax.y - tr->a.y), + (tr->b.x - ptMin.x), (ptMax.y - tr->b.y), + (tr->c.x - ptMin.x), (ptMax.y - tr->c.y), + RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color), + sw, + RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color)); } void SvgFileWriter::FinishAndCloseFile(void) { @@ -407,6 +542,9 @@ void HpglFileWriter::LineSegment(double x0, double y0, double x1, double y1) { fprintf(f, "PU%d,%d;\r\n", (int)MmToHpglUnits(x0), (int)MmToHpglUnits(y0)); fprintf(f, "PD%d,%d;\r\n", (int)MmToHpglUnits(x1), (int)MmToHpglUnits(y1)); } +void HpglFileWriter::Triangle(STriangle *tr) { + // HPGL does not support filled triangles +} void HpglFileWriter::FinishAndCloseFile(void) { fclose(f); diff --git a/mesh.cpp b/mesh.cpp index f8772b54..181fd9b7 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -535,6 +535,140 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt, bool *inter) { } } +void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) { + SEdgeList seln; + ZERO(&seln); + + Vector tn = tr->Normal().WithMagnitude(1); + double td = tn.Dot(tr->a); + + // Consider front-facing triangles only + if(tn.z > LENGTH_EPS) { + // If the edge crosses our triangle's plane, then split into above + // and below parts. + SEdge *se; + for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + double da = (se->a).Dot(tn) - td, + db = (se->b).Dot(tn) - td; + if((da < -LENGTH_EPS && db > LENGTH_EPS) || + (db < -LENGTH_EPS && da > LENGTH_EPS)) + { + Vector m = Vector::AtIntersectionOfPlaneAndLine( + tn, td, + se->a, se->b, NULL); + seln.AddEdge(m, se->b); + se->b = m; + } + } + for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { + sel->AddEdge(se->a, se->b); + } + seln.Clear(); + + for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + Vector pt = ((se->a).Plus(se->b)).ScaledBy(0.5); + double dt = pt.Dot(tn) - td; + if(pt.Dot(tn) - td > -LENGTH_EPS) { + // Edge is in front of or on our plane (remember, tn.z > 0) + // so it is exempt from further splitting + se->auxA = 1; + } else { + // Edge is behind our plane, needs further splitting + se->auxA = 0; + } + } + + // Considering only the (x, y) coordinates, split the edge against our + // triangle. + Point2d a = (tr->a).ProjectXy(), + b = (tr->b).ProjectXy(), + c = (tr->c).ProjectXy(); + + Point2d n[3] = { (b.Minus(a)).Normal().WithMagnitude(1), + (c.Minus(b)).Normal().WithMagnitude(1), + (a.Minus(c)).Normal().WithMagnitude(1) }; + + double d[3] = { n[0].Dot(b), + n[1].Dot(c), + n[2].Dot(a) }; + + // Split all of the edges where they intersect the triangle edges + int i; + for(i = 0; i < 3; i++) { + for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + if(se->auxA) continue; + + Point2d ap = (se->a).ProjectXy(), + bp = (se->b).ProjectXy(); + double da = n[i].Dot(ap) - d[i], + db = n[i].Dot(bp) - d[i]; + if((da < -LENGTH_EPS && db > LENGTH_EPS) || + (db < -LENGTH_EPS && da > LENGTH_EPS)) + { + double dab = (db - da); + Vector spl = ((se->a).ScaledBy( db/dab)).Plus( + (se->b).ScaledBy(-da/dab)); + seln.AddEdge(spl, se->b); + se->b = spl; + } + } + for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { + sel->AddEdge(se->a, se->b, 0); + } + seln.Clear(); + } + + for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + if(se->auxA) { + // Lies above or on the triangle plane, so triangle doesn't + // occlude it. + se->tag = 0; + } else { + // Test the segment to see if it lies outside the triangle + // (i.e., outside wrt at least one edge), and keep it only + // then. + Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy(); + se->tag = 1; + for(i = 0; i < 3; i++) { + if(n[i].Dot(pt) - d[i] > -LENGTH_EPS) se->tag = 0; + } + } + } + sel->l.RemoveTagged(); + } +} + +void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) { + if(gt && lt) { + double ac = (orig.a).Element(which), + bc = (orig.b).Element(which); + // We can ignore triangles that are separated in x or y, but triangles + // that are separated in z may still contribute + if(ac < c + KDTREE_EPS || + bc < c + KDTREE_EPS || + which == 2) + { + lt->OcclusionTestLine(orig, sel, cnt); + } + if(ac > c - KDTREE_EPS || + bc > c - KDTREE_EPS || + which == 2) + { + gt->OcclusionTestLine(orig, sel, cnt); + } + } else { + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + STriangle *tr = ll->tri; + + if(tr->tag == cnt) continue; + + SplitLinesAgainstTriangle(sel, tr); + tr->tag = cnt; + } + } +} + void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool *inter, bool *leaky) { if(inter) *inter = false; if(leaky) *leaky = false; diff --git a/polygon.h b/polygon.h index bd22149c..f8165151 100644 --- a/polygon.h +++ b/polygon.h @@ -159,6 +159,8 @@ public: void InsertInPlane(bool pos2, STriangle *tr, SMesh *m); + void GenerateInPaintOrder(SMesh *m); + void DebugDraw(void); }; @@ -222,6 +224,9 @@ public: void FindEdgeOn(Vector a, Vector b, int *n, int cnt, bool *inter); void MakeNakedEdgesInto(SEdgeList *sel, bool *inter=NULL, bool *leaky=NULL); + + void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt); + void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr); }; #endif diff --git a/solvespace.cpp b/solvespace.cpp index a1f4f721..a5ed8f90 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -30,6 +30,7 @@ void SolveSpace::Init(char *cmdLine) { // Light intensities lightIntensity[0] = CnfThawFloat(1.0f, "LightIntensity_0"); lightIntensity[1] = CnfThawFloat(0.5f, "LightIntensity_1"); + ambientIntensity = 0.3; // no setting for that yet // Light positions lightDir[0].x = CnfThawFloat(-1.0f, "LightDir_0_Right" ); lightDir[0].y = CnfThawFloat( 1.0f, "LightDir_0_Up" ); diff --git a/solvespace.h b/solvespace.h index d377c55e..81033706 100644 --- a/solvespace.h +++ b/solvespace.h @@ -346,15 +346,17 @@ public: static bool StringEndsIn(char *str, char *ending); static VectorFileWriter *ForFile(char *file); - void OutputPolygon(SPolygon *sp); + void Output(SEdgeList *sel, SMesh *sm); virtual void LineSegment(double x0, double y0, double x1, double y1) = 0; + virtual void Triangle(STriangle *tr) = 0; virtual void StartFile(void) = 0; virtual void FinishAndCloseFile(void) = 0; }; class DxfFileWriter : public VectorFileWriter { public: void LineSegment(double x0, double y0, double x1, double y1); + void Triangle(STriangle *tr); void StartFile(void); void FinishAndCloseFile(void); }; @@ -362,12 +364,14 @@ class EpsFileWriter : public VectorFileWriter { public: static double MmToPoints(double mm); void LineSegment(double x0, double y0, double x1, double y1); + void Triangle(STriangle *tr); void StartFile(void); void FinishAndCloseFile(void); }; class SvgFileWriter : public VectorFileWriter { public: void LineSegment(double x0, double y0, double x1, double y1); + void Triangle(STriangle *tr); void StartFile(void); void FinishAndCloseFile(void); }; @@ -375,6 +379,7 @@ class HpglFileWriter : public VectorFileWriter { public: static double MmToHpglUnits(double mm); void LineSegment(double x0, double y0, double x1, double y1); + void Triangle(STriangle *tr); void StartFile(void); void FinishAndCloseFile(void); }; @@ -430,6 +435,7 @@ public: int modelColor[MODEL_COLORS]; Vector lightDir[2]; double lightIntensity[2]; + double ambientIntensity; double chordTol; int maxSegments; double cameraTangent; @@ -494,9 +500,10 @@ public: void ExportMeshTo(char *file); void ExportViewTo(char *file); void ExportSectionTo(char *file); - void ExportPolygon(SPolygon *sp, - Vector u, Vector v, Vector n, Vector origin, - VectorFileWriter *out); + void ExportLinesAndMesh(SEdgeList *sel, SMesh *sm, + Vector u, Vector v, Vector n, Vector origin, + double cameraTan, + VectorFileWriter *out); static void MenuAnalyze(int id); struct { diff --git a/ui.h b/ui.h index 9f78f5b7..ed970019 100644 --- a/ui.h +++ b/ui.h @@ -10,9 +10,13 @@ public: #ifndef RGB #define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) #endif -#define REDf(v) ((((v) >> 0) & 0xff) / 255.0f) -#define GREENf(v) ((((v) >> 8) & 0xff) / 255.0f) -#define BLUEf(v) ((((v) >> 16) & 0xff) / 255.0f) +#define RGBf(r, g, b) RGB((int)(255*(r)), (int)(255*(g)), (int)(255*(b))) +#define RED(v) (((v) >> 0) & 0xff) +#define GREEN(v) (((v) >> 8) & 0xff) +#define BLUE(v) (((v) >> 16) & 0xff) +#define REDf(v) (RED (v) / 255.0f) +#define GREENf(v) (GREEN(v) / 255.0f) +#define BLUEf(v) (BLUE (v) / 255.0f) typedef struct { char c; int color; diff --git a/util.cpp b/util.cpp index d42f9d4d..82f9c0ea 100644 --- a/util.cpp +++ b/util.cpp @@ -430,6 +430,19 @@ Vector Vector::ScaleOutOfCsys(Vector u, Vector v, Vector n) { return r; } +Vector Vector::InPerspective(Vector u, Vector v, Vector n, + Vector origin, double cameraTan) +{ + Vector r = this->Minus(origin); + r = r.DotInToCsys(u, v, n); + // yes, minus; we are assuming a csys where u cross v equals n, backwards + // from the display stuff + double w = (1 - r.z*cameraTan); + r = r.ScaledBy(1/w); + + return r; +} + double Vector::DistanceToLine(Vector p0, Vector dp) { double m = dp.Magnitude(); return ((this->Minus(p0)).Cross(dp)).Magnitude() / m;