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]
solver
Jonathan Westhues 2009-03-17 08:33:46 -08:00
parent ed9f448398
commit 1a845c3432
10 changed files with 394 additions and 64 deletions

24
bsp.cpp
View File

@ -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;

View File

@ -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();

2
dsc.h
View File

@ -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);
};

View File

@ -2,9 +2,6 @@
#include <png.h>
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,
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 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();
*sp = compd;
sp.normal = Vector::From(0, 0, -1);
sp.FixContourDirections();
sp.OffsetInto(&compd, SS.exportOffset);
sp.Clear();
compd.MakeEdgesInto(sel);
compd.Clear();
}
// Now begin the entities, which are just line segments reproduced from
// our piecewise linear curves.
out->OutputPolygon(sp);
// 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);
}
}
// 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 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,
"<polyline points='%.3f %.3f, %.3f %.3f' "
"<polyline points='%.3f,%.3f %.3f,%.3f' "
"stroke-width='1' stroke='black' style='fill: none;' />\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,
"<polygon points='%.3f,%.3f %.3f,%.3f %.3f,%.3f' "
"stroke='#%02x%02x%02x' stroke-width='%.3f' "
"style='fill:#%02x%02x%02x' shape-rendering='crispEdges'/>\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);

134
mesh.cpp
View File

@ -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;

View File

@ -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

View File

@ -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" );

View File

@ -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,8 +500,9 @@ public:
void ExportMeshTo(char *file);
void ExportViewTo(char *file);
void ExportSectionTo(char *file);
void ExportPolygon(SPolygon *sp,
void ExportLinesAndMesh(SEdgeList *sel, SMesh *sm,
Vector u, Vector v, Vector n, Vector origin,
double cameraTan,
VectorFileWriter *out);
static void MenuAnalyze(int id);

10
ui.h
View File

@ -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;

View File

@ -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;