Abstract all (ex-OpenGL) drawing operations into a Canvas interface.

This has several desirable consequences:
  * It is now possible to port SolveSpace to a later version of
    OpenGL, such as OpenGLES 2, so that it runs on platforms that
    only have that OpenGL version;
  * The majority of geometry is now rendered without references to
    the camera in C++ code, so a renderer can now submit it to
    the video card once and re-rasterize with a different projection
    matrix every time the projection is changed, avoiding expensive
    reuploads;
  * The DOGD (draw or get distance) interface is now
    a straightforward Canvas implementation;
  * There are no more direct references to SS.GW.(projection)
    in sketch rendering code, which allows rendering to multiple
    viewports;
  * There are no more unnecessary framebuffer flips on CPU on Cocoa
    and GTK;
  * The platform-dependent GL code is now confined to rendergl1.cpp.
  * The Microsoft and Apple headers required by it that are prone to
    identifier conflicts are no longer included globally;
  * The rendergl1.cpp implementation can now be omitted from
    compilation to run SolveSpace headless or with a different
    OpenGL version.

Note these implementation details of Canvas:
  * GetCamera currently always returns a reference to the field
    `Camera camera;`. This is so that a future renderer that caches
    geometry in the video memory can define it as asserting, which
    would provide assurance against code that could accidentally
    put something projection-dependent in the cache;
  * Line and triangle rendering is specified through a level of
    indirection, hStroke and hFill. This is so that a future renderer
    that batches geometry could cheaply group identical styles.
  * DrawPixmap and DrawVectorText accept a (o,u,v) and not a matrix.
    This is so that a future renderer into an output format that
    uses 2d transforms (e.g. SVG) could easily derive those.

Some additional internal changes were required to enable this:
  * Pixmap is now always passed as std::shared_ptr<{const ,}Pixmap>.
    This is so that the renderer could cache uploaded textures
    between API calls, which requires it to capture a (weak)
    reference.
  * The PlatformPathEqual function was properly extracted into
    platform-specific code. This is so that the <windows.h> header
    could be included only where needed (in platform/w32* as well
    as rendergl1.cpp).
  * The SBsp{2,3}::DebugDraw functions were removed. They can be
    rewritten using the Canvas API if they are ever needed.

While no visual changes were originally intended, some minor fixes
happened anyway:
  * The "emphasis" yellow line from top-left corner is now correctly
    rendered much wider.
  * The marquee rectangle is now pixel grid aligned.
  * The hidden entities now do not clobber the depth buffer, removing
    some minor artifacts.
  * The workplane "tab" now scales with the font used to render
    the workplane name.
  * The workplane name font is now taken from the normals style.
  * Workplane and constraint line stipple is insignificantly
    different. This is so that it can reuse the existing stipple
    codepaths; rendering of workplanes and constraints predates
    those.

Some debug functionality was added:
  * In graphics window, an fps counter that becomes red when
    rendering under 60fps is drawn.
pull/33/head
whitequark 2016-05-31 00:55:13 +00:00
parent bd2da7fe3f
commit e7c8c1c8f2
36 changed files with 2941 additions and 2566 deletions

View File

@ -137,6 +137,7 @@ set(solvespace_HEADERS
sketch.h
solvespace.h
ui.h
render/render.h
srf/surface.h)
set(solvespace_SOURCES
@ -156,7 +157,6 @@ set(solvespace_SOURCES
expr.cpp
file.cpp
generate.cpp
glhelper.cpp
graphicswin.cpp
group.cpp
groupmesh.cpp
@ -177,6 +177,8 @@ set(solvespace_SOURCES
undoredo.cpp
util.cpp
view.cpp
render/render.cpp
render/rendergl1.cpp
srf/boolean.cpp
srf/curve.cpp
srf/merge.cpp

View File

@ -423,48 +423,6 @@ void SBsp3::GenerateInPaintOrder(SMesh *m) const {
}
}
void SBsp3::DebugDraw() const {
if(pos) pos->DebugDraw();
Vector norm = tri.Normal();
glNormal3d(norm.x, norm.y, norm.z);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glBegin(GL_TRIANGLES);
ssglVertex3v(tri.a);
ssglVertex3v(tri.b);
ssglVertex3v(tri.c);
glEnd();
glDisable(GL_LIGHTING);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
ssglDepthRangeOffset(2);
glBegin(GL_TRIANGLES);
ssglVertex3v(tri.a);
ssglVertex3v(tri.b);
ssglVertex3v(tri.c);
glEnd();
glDisable(GL_LIGHTING);
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
glPointSize(10);
ssglDepthRangeOffset(2);
glBegin(GL_TRIANGLES);
ssglVertex3v(tri.a);
ssglVertex3v(tri.b);
ssglVertex3v(tri.c);
glEnd();
ssglDepthRangeOffset(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
if(more) more->DebugDraw();
if(neg) neg->DebugDraw();
if(edges) edges->DebugDraw(n, d);
}
/////////////////////////////////
Vector SBsp2::IntersectionWith(Vector a, Vector b) const {
@ -663,19 +621,3 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) {
return;
}
void SBsp2::DebugDraw(Vector n, double d) const {
ssassert(fabs((edge.a).Dot(n) - d) < LENGTH_EPS, "Endpoint too close to BSP node plane");
ssassert(fabs((edge.b).Dot(n) - d) < LENGTH_EPS, "Endpoint too close to BSP node plane");
ssglLineWidth(10);
glBegin(GL_LINES);
ssglVertex3v(edge.a);
ssglVertex3v(edge.b);
glEnd();
if(pos) pos->DebugDraw(n, d);
if(neg) neg->DebugDraw(n, d);
if(more) more->DebugDraw(n, d);
ssglLineWidth(1);
}

View File

@ -302,10 +302,12 @@ void TextWindow::ShowConfiguration() {
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
SS.autosaveInterval, &ScreenChangeAutosaveInterval);
const char *gl_vendor, *gl_renderer, *gl_version;
OpenGl1Renderer::GetIdent(&gl_vendor, &gl_renderer, &gl_version);
Printf(false, "");
Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR));
Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER));
Printf(false, " %Ft version %E%s", glGetString(GL_VERSION));
Printf(false, " %Ftgl vendor %E%s", gl_vendor);
Printf(false, " %Ft renderer %E%s", gl_renderer);
Printf(false, " %Ft version %E%s", gl_version);
}
bool TextWindow::EditControlDoneForConfiguration(const char *s) {

View File

@ -30,40 +30,49 @@ void GraphicsWindow::Selection::Clear() {
emphasized = false;
}
void GraphicsWindow::Selection::Draw() {
Vector refp[2];
refp[0] = refp[1] = Vector::From(0, 0, 0);
void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) {
const Camera &camera = canvas->GetCamera();
std::vector<Vector> refs;
if(entity.v) {
Entity *e = SK.GetEntity(entity);
e->Draw(/*drawAsHidden=*/false);
e->Draw(isHovered ? Entity::DrawAs::HOVERED :
Entity::DrawAs::SELECTED,
canvas);
if(emphasized) {
refp[0] = refp[1] = e->GetReferencePos();
e->GetReferencePoints(&refs);
}
}
if(constraint.v) {
Constraint *c = SK.GetConstraint(constraint);
c->Draw();
if(emphasized) c->GetReferencePos(refp);
c->Draw(isHovered ? Constraint::DrawAs::HOVERED :
Constraint::DrawAs::SELECTED,
canvas);
if(emphasized) {
c->GetReferencePoints(camera, &refs);
}
}
if(emphasized && (constraint.v || entity.v)) {
// We want to emphasize this constraint or entity, by drawing a thick
// line from the top left corner of the screen to the reference point(s)
// of that entity or constraint.
double s = 0.501/SS.GW.scale;
Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s);
topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
topLeft = topLeft.Minus(SS.GW.offset);
Canvas::Stroke strokeEmphasis = {};
strokeEmphasis.layer = Canvas::Layer::FRONT;
strokeEmphasis.color = Style::Color(Style::HOVERED).WithAlpha(50);
strokeEmphasis.width = 40;
Canvas::hStroke hcsEmphasis = canvas->GetStroke(strokeEmphasis);
ssglLineWidth(40);
RgbaColor rgb = Style::Color(Style::HOVERED);
glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), 0.2);
for(int i = 0; i < (refp[0].Equals(refp[1]) ? 1 : 2); i++) {
glBegin(GL_LINES);
ssglVertex3v(topLeft);
ssglVertex3v(refp[i]);
glEnd();
Point2d topLeftScreen;
topLeftScreen.x = -(double)camera.width / 2;
topLeftScreen.y = (double)camera.height / 2;
Vector topLeft = camera.UnProjectPoint(topLeftScreen);
auto it = std::unique(refs.begin(), refs.end(),
[](Vector a, Vector b) { return a.Equals(b); });
refs.erase(it, refs.end());
for(Vector p : refs) {
canvas->DrawLine(topLeft, p, hcsEmphasis);
}
ssglLineWidth(1);
}
}
@ -285,9 +294,31 @@ void GraphicsWindow::GroupSelection() {
}
}
Camera GraphicsWindow::GetCamera() const {
Camera camera = {};
camera.width = (int)width;
camera.height = (int)height;
camera.offset = offset;
camera.projUp = projUp;
camera.projRight = projRight;
camera.scale = scale;
camera.tangent = SS.CameraTangent();
camera.hasPixels = true;
return camera;
}
Lighting GraphicsWindow::GetLighting() const {
Lighting lighting = {};
lighting.backgroundColor = SS.backgroundColor;
lighting.ambientIntensity = SS.ambientIntensity;
lighting.lightIntensity[0] = SS.lightIntensity[0];
lighting.lightIntensity[1] = SS.lightIntensity[1];
lighting.lightDirection[0] = SS.lightDir[0];
lighting.lightDirection[1] = SS.lightDir[1];
return lighting;
}
void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
int i;
double d, dmin = 1e12;
Selection s = {};
// Did the view projection change? If so, invalidate bounding boxes.
@ -304,40 +335,45 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
}
}
ObjectPicker canvas = {};
canvas.camera = GetCamera();
canvas.selRadius = 10.0;
canvas.point = mp;
canvas.maxZIndex = -1;
// Always do the entities; we might be dragging something that should
// be auto-constrained, and we need the hover for that.
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
for(Entity &e : SK.entity) {
if(!e.IsVisible()) continue;
// Don't hover whatever's being dragged.
if(e->h.request().v == pending.point.request().v) {
if(e.h.request().v == pending.point.request().v) {
// The one exception is when we're creating a new cubic; we
// want to be able to hover the first point, because that's
// how we turn it into a periodic spline.
if(!e->IsPoint()) continue;
if(!e->h.isFromRequest()) continue;
Request *r = SK.GetRequest(e->h.request());
if(!e.IsPoint()) continue;
if(!e.h.isFromRequest()) continue;
Request *r = SK.GetRequest(e.h.request());
if(r->type != Request::Type::CUBIC) continue;
if(r->extraPoints < 2) continue;
if(e->h.v != r->h.entity(1).v) continue;
if(e.h.v != r->h.entity(1).v) continue;
}
d = e->GetDistance(mp);
if(d < SELECTION_RADIUS && d < dmin) {
if(canvas.Pick([&]{ e.Draw(Entity::DrawAs::DEFAULT, &canvas); })) {
s = {};
s.entity = e->h;
dmin = d;
s.entity = e.h;
}
}
// The constraints and faces happen only when nothing's in progress.
if(pending.operation == Pending::NONE) {
// Constraints
for(i = 0; i < SK.constraint.n; i++) {
d = SK.constraint.elem[i].GetDistance(mp);
if(d < SELECTION_RADIUS && d < dmin) {
for(Constraint &c : SK.constraint) {
if(!c.IsVisible()) continue;
if(canvas.Pick([&]{ c.Draw(Constraint::DrawAs::DEFAULT, &canvas); })) {
s = {};
s.constraint = SK.constraint.elem[i].h;
dmin = d;
s.constraint = c.h;
}
}
@ -440,147 +476,13 @@ void GraphicsWindow::NormalizeProjectionVectors() {
projRight = projRight.WithMagnitude(1);
}
Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) {
Vector n = projRight.Cross(projUp);
void GraphicsWindow::DrawSnapGrid(Canvas *canvas) {
if(!LockedInWorkplane()) return;
Vector r = (projRight.ScaledBy(rightUpForward.x));
r = r.Plus(projUp.ScaledBy(rightUpForward.y));
r = r.Plus(n.ScaledBy(rightUpForward.z));
return r;
}
void GraphicsWindow::Paint() {
int i;
havePainted = true;
int w, h;
GetGraphicsWindowSize(&w, &h);
width = w; height = h;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000);
double mat[16];
// Last thing before display is to apply the perspective
double clp = SS.CameraTangent()*scale;
MakeMatrix(mat, 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, clp, 1);
glMultMatrixd(mat);
// Before that, we apply the rotation
Vector n = projUp.Cross(projRight);
MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0,
projUp.x, projUp.y, projUp.z, 0,
n.x, n.y, n.z, 0,
0, 0, 0, 1);
glMultMatrixd(mat);
// And before that, the translation
MakeMatrix(mat, 1, 0, 0, offset.x,
0, 1, 0, offset.y,
0, 0, 1, offset.z,
0, 0, 0, 1);
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glShadeModel(GL_SMOOTH);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
// don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards,
// drawn with leaks in the mesh
glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_FILL);
glEnable(GL_DEPTH_TEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glEnable(GL_NORMALIZE);
// At the same depth, we want later lines drawn over earlier.
glDepthFunc(GL_LEQUAL);
if(SS.ActiveGroupsOkay()) {
glClearColor(SS.backgroundColor.redF(),
SS.backgroundColor.greenF(),
SS.backgroundColor.blueF(), 1.0f);
} else {
// Draw a different background whenever we're having solve problems.
RgbaColor rgb = Style::Color(Style::DRAW_ERROR);
glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f);
// And show the text window, which has info to debug it
ForceTextWindowShown();
}
glClear(GL_COLOR_BUFFER_BIT);
glClearDepth(1.0);
glClear(GL_DEPTH_BUFFER_BIT);
if(!SS.bgImage.pixmap.IsEmpty()) {
double mmw = SS.bgImage.pixmap.width / SS.bgImage.scale,
mmh = SS.bgImage.pixmap.height / SS.bgImage.scale;
Vector origin = SS.bgImage.origin;
origin = origin.DotInToCsys(projRight, projUp, n);
// Place the depth of our origin at the point that corresponds to
// w = 1, so that it's unaffected by perspective.
origin.z = (offset.ScaledBy(-1)).Dot(n);
origin = origin.ScaleOutOfCsys(projRight, projUp, n);
// Place the background at the very back of the Z order, though, by
// mucking with the depth range.
glDepthRange(1, 1);
ssglDrawPixmap(SS.bgImage.pixmap,
origin,
origin.Plus(projUp.ScaledBy(mmh)),
origin.Plus(projRight.ScaledBy(mmw).Plus(
projUp. ScaledBy(mmh))),
origin.Plus(projRight.ScaledBy(mmw)));
}
ssglDepthRangeOffset(0);
// Nasty case when we're reloading the linked files; could be that
// we get an error, so a dialog pops up, and a message loop starts, and
// we have to get called to paint ourselves. If the sketch is screwed
// up, then we could trigger an oops trying to draw.
if(!SS.allConsistent) return;
// Let's use two lights, at the user-specified locations
GLfloat f;
glEnable(GL_LIGHT0);
f = (GLfloat)SS.lightIntensity[0];
GLfloat li0[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT0, GL_DIFFUSE, li0);
glLightfv(GL_LIGHT0, GL_SPECULAR, li0);
glEnable(GL_LIGHT1);
f = (GLfloat)SS.lightIntensity[1];
GLfloat li1[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT1, GL_DIFFUSE, li1);
glLightfv(GL_LIGHT1, GL_SPECULAR, li1);
Vector ld;
ld = VectorFromProjs(SS.lightDir[0]);
GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT0, GL_POSITION, ld0);
ld = VectorFromProjs(SS.lightDir[1]);
GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT1, GL_POSITION, ld1);
GLfloat ambient[4] = { (float)SS.ambientIntensity,
(float)SS.ambientIntensity,
(float)SS.ambientIntensity, 1 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
ssglUnlockColor();
if(showSnapGrid && LockedInWorkplane()) {
hEntity he = ActiveWorkplane();
EntityBase *wrkpl = SK.GetEntity(he),
*norm = wrkpl->Normal();
Vector n = projUp.Cross(projRight);
Vector wu, wv, wn, wp;
wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
wu = norm->NormalU();
@ -609,7 +511,7 @@ void GraphicsWindow::Paint() {
wn, wn.Dot(wp),
tp, tp.Plus(n),
&parallel);
if(parallel) goto nogrid;
if(parallel) return;
tpp = tpp.Minus(wp);
double uu = tpp.Dot(wu),
@ -628,142 +530,149 @@ void GraphicsWindow::Paint() {
j0 = (int)(vmin / g);
j1 = (int)(vmax / g);
if(i0 > i1 || i1 - i0 > 400) goto nogrid;
if(j0 > j1 || j1 - j0 > 400) goto nogrid;
if(i0 > i1 || i1 - i0 > 400) return;
if(j0 > j1 || j1 - j0 > 400) return;
Canvas::Stroke stroke = {};
stroke.layer = Canvas::Layer::BACK;
stroke.color = Style::Color(Style::DATUM).WithAlpha(75);
Canvas::hStroke hcs = canvas->GetStroke(stroke);
ssglLineWidth(1);
ssglColorRGBa(Style::Color(Style::DATUM), 0.3);
glBegin(GL_LINES);
for(i = i0 + 1; i < i1; i++) {
ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)));
ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)));
canvas->DrawLine(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)),
wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)),
hcs);
}
for(j = j0 + 1; j < j1; j++) {
ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)));
ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)));
}
glEnd();
// Clear the depth buffer, so that the grid is at the very back of
// the Z order.
glClear(GL_DEPTH_BUFFER_BIT);
nogrid:;
canvas->DrawLine(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)),
wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)),
hcs);
}
}
void GraphicsWindow::DrawPersistent(Canvas *canvas) {
// Draw the active group; this does stuff like the mesh and edges.
(SK.GetGroup(activeGroup))->Draw();
SK.GetGroup(activeGroup)->Draw(canvas);
// Now draw the entities.
for(Entity &e : SK.entity) {
if(SS.GW.showHdnLines) {
ssglDepthRangeOffset(2);
glDepthFunc(GL_GREATER);
Entity::DrawAll(/*drawAsHidden=*/true);
glDepthFunc(GL_LEQUAL);
e.Draw(Entity::DrawAs::HIDDEN, canvas);
}
e.Draw(Entity::DrawAs::DEFAULT, canvas);
}
ssglDepthRangeOffset(0);
Entity::DrawAll(/*drawAsHidden=*/false);
// Draw filled paths in all groups, when those filled paths were requested
// specially by assigning a style with a fill color, or when the filled
// paths are just being filled by default. This should go last, to make
// the transparency work.
for(i = 0; i < SK.groupOrder.n; i++) {
Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
for(hGroup hg : SK.groupOrder) {
Group *g = SK.GetGroup(hg);
if(!(g->IsVisible())) continue;
g->DrawFilledPaths();
g->DrawFilledPaths(canvas);
}
}
void GraphicsWindow::Draw(Canvas *canvas) {
const Camera &camera = canvas->GetCamera();
if(SS.bgImage.pixmap) {
double mmw = SS.bgImage.pixmap->width / SS.bgImage.scale,
mmh = SS.bgImage.pixmap->height / SS.bgImage.scale;
Vector n = camera.projUp.Cross(camera.projRight);
Vector origin = SS.bgImage.origin;
origin = origin.DotInToCsys(camera.projRight, camera.projUp, n);
// Place the depth of our origin at the point that corresponds to
// w = 1, so that it's unaffected by perspective.
origin.z = (offset.ScaledBy(-1)).Dot(n);
origin = origin.ScaleOutOfCsys(camera.projRight, camera.projUp, n);
// Place the background at the very back of the Z order.
Canvas::Fill fillBackground = {};
fillBackground.layer = Canvas::Layer::BACK;
Canvas::hFill hcfBackground = canvas->GetFill(fillBackground);
canvas->DrawPixmap(SS.bgImage.pixmap,
origin, projRight.ScaledBy(mmw), projUp.ScaledBy(mmh),
{ 0.0, 1.0 }, { 1.0, 0.0 },
hcfBackground);
}
// Nasty case when we're reloading the linked files; could be that
// we get an error, so a dialog pops up, and a message loop starts, and
// we have to get called to paint ourselves. If the sketch is screwed
// up, then we could trigger an oops trying to draw.
if(!SS.allConsistent) return;
if(showSnapGrid) DrawSnapGrid(canvas);
// Draw all the things that don't change when we rotate.
DrawPersistent(canvas);
// Draw the polygon errors.
if(SS.checkClosedContour) {
SK.GetGroup(activeGroup)->DrawPolyError(canvas);
}
glDisable(GL_DEPTH_TEST);
// Draw the constraints
for(i = 0; i < SK.constraint.n; i++) {
SK.constraint.elem[i].Draw();
for(Constraint &c : SK.constraint) {
c.Draw(Constraint::DrawAs::DEFAULT, canvas);
}
// Draw the "pending" constraint, i.e. a constraint that would be
// placed on a line that is almost horizontal or vertical
// placed on a line that is almost horizontal or vertical.
if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT &&
SS.GW.pending.hasSuggestion) {
Constraint c = {};
c.group = SS.GW.activeGroup;
c.workplane = SS.GW.ActiveWorkplane();
c.type = SS.GW.pending.suggestion;
c.ptA = Entity::NO_ENTITY;
c.ptB = Entity::NO_ENTITY;
c.entityA = SS.GW.pending.request.entity(0);
c.entityB = Entity::NO_ENTITY;
c.other = false;
c.other2 = false;
// Only draw.
c.Draw();
c.Draw(Constraint::DrawAs::DEFAULT, canvas);
}
Canvas::Stroke strokeAnalyze = {};
strokeAnalyze.layer = Canvas::Layer::FRONT;
strokeAnalyze.color = Style::Color(Style::ANALYZE);
strokeAnalyze.width = Style::Width(Style::ANALYZE);
Canvas::hStroke hcsAnalyze = canvas->GetStroke(strokeAnalyze);
// Draw the traced path, if one exists
ssglLineWidth(Style::Width(Style::ANALYZE));
ssglColorRGB(Style::Color(Style::ANALYZE));
SContour *sc = &(SS.traced.path);
glBegin(GL_LINE_STRIP);
for(i = 0; i < sc->l.n; i++) {
ssglVertex3v(sc->l.elem[i].p);
}
glEnd();
SEdgeList tracedEdges = {};
SS.traced.path.MakeEdgesInto(&tracedEdges);
canvas->DrawEdges(tracedEdges, hcsAnalyze);
tracedEdges.Clear();
Canvas::Stroke strokeError = {};
strokeError.layer = Canvas::Layer::FRONT;
strokeError.color = Style::Color(Style::DRAW_ERROR);
strokeError.width = 12;
Canvas::hStroke hcsError = canvas->GetStroke(strokeError);
// And the naked edges, if the user did Analyze -> Show Naked Edges.
ssglDrawEdges(&(SS.nakedEdges), /*endpointsToo=*/true, { Style::DRAW_ERROR });
canvas->DrawEdges(SS.nakedEdges, hcsError);
// Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST);
ssglLockColorTo(Style::Color(Style::HOVERED));
hover.Draw();
hover.Draw(/*isHovered=*/true, canvas);
SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::HOVERED, canvas);
// And finally draw the selection, same mechanism.
ssglLockColorTo(Style::Color(Style::SELECTED));
for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
s->Draw();
s->Draw(/*isHovered=*/false, canvas);
}
SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::SELECTED, canvas);
ssglUnlockColor();
// If a marquee selection is in progress, then draw the selection
// rectangle, as an outline and a transparent fill.
if(pending.operation == Pending::DRAGGING_MARQUEE) {
Point2d begin = ProjectPoint(orig.marqueePoint);
double xmin = min(orig.mouse.x, begin.x),
xmax = max(orig.mouse.x, begin.x),
ymin = min(orig.mouse.y, begin.y),
ymax = max(orig.mouse.y, begin.y);
Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)),
tr = UnProjectPoint(Point2d::From(xmax, ymin)),
br = UnProjectPoint(Point2d::From(xmax, ymax)),
bl = UnProjectPoint(Point2d::From(xmin, ymax));
ssglLineWidth((GLfloat)1.3);
ssglColorRGB(Style::Color(Style::HOVERED));
glBegin(GL_LINE_LOOP);
ssglVertex3v(tl);
ssglVertex3v(tr);
ssglVertex3v(br);
ssglVertex3v(bl);
glEnd();
ssglColorRGBa(Style::Color(Style::HOVERED), 0.10);
glBegin(GL_QUADS);
ssglVertex3v(tl);
ssglVertex3v(tr);
ssglVertex3v(br);
ssglVertex3v(bl);
glEnd();
}
Canvas::Stroke strokeDatum = {};
strokeDatum.layer = Canvas::Layer::FRONT;
strokeDatum.color = Style::Color(Style::DATUM);
strokeDatum.width = 1;
Canvas::hStroke hcsDatum = canvas->GetStroke(strokeDatum);
// An extra line, used to indicate the origin when rotating within the
// plane of the monitor.
if(SS.extraLine.draw) {
ssglLineWidth(1);
ssglLockColorTo(Style::Color(Style::DATUM));
glBegin(GL_LINES);
ssglVertex3v(SS.extraLine.ptA);
ssglVertex3v(SS.extraLine.ptB);
glEnd();
canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum);
}
// A note to indicate the origin in the just-exported file.
@ -774,38 +683,106 @@ nogrid:;
u = SS.justExportedInfo.u,
v = SS.justExportedInfo.v;
} else {
p = SS.GW.offset.ScaledBy(-1);
u = SS.GW.projRight;
v = SS.GW.projUp;
p = camera.offset.ScaledBy(-1);
u = camera.projRight;
v = camera.projUp;
}
ssglColorRGB(Style::Color(Style::DATUM));
ssglWriteText("previewing exported geometry; press Esc to return",
Style::DefaultTextHeight(),
p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)),
u, v, NULL, NULL);
canvas->DrawVectorText("previewing exported geometry; press Esc to return",
Style::DefaultTextHeight() / camera.scale,
p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v,
hcsDatum);
if(SS.justExportedInfo.showOrigin) {
ssglLineWidth(1.5);
glBegin(GL_LINES);
ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(u.WithMagnitude(30/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
ssglVertex3v(p.Plus(v.WithMagnitude(30/scale)));
glEnd();
ssglWriteText("(x, y) = (0, 0) for file just exported",
Style::DefaultTextHeight(),
Vector um = p.Plus(u.WithMagnitude(-15/scale)),
up = p.Plus(u.WithMagnitude(30/scale)),
vm = p.Plus(v.WithMagnitude(-15/scale)),
vp = p.Plus(v.WithMagnitude(30/scale));
canvas->DrawLine(um, up, hcsDatum);
canvas->DrawLine(vm, vp, hcsDatum);
canvas->DrawVectorText("(x, y) = (0, 0) for file just exported",
Style::DefaultTextHeight() / camera.scale,
p.Plus(u.ScaledBy(40/scale)).Plus(
v.ScaledBy(-(Style::DefaultTextHeight())/scale)),
u, v, NULL, NULL);
v.ScaledBy(-(Style::DefaultTextHeight())/scale)), u, v,
hcsDatum);
}
}
}
void GraphicsWindow::Paint() {
havePainted = true;
auto renderStartTime = std::chrono::high_resolution_clock::now();
int w, h;
GetGraphicsWindowSize(&w, &h);
width = w;
height = h;
Camera camera = GetCamera();
OpenGl1Renderer canvas = {};
canvas.camera = camera;
canvas.lighting = GetLighting();
if(!SS.ActiveGroupsOkay()) {
// Draw a different background whenever we're having solve problems.
RgbaColor bgColor = Style::Color(Style::DRAW_ERROR);
bgColor = RgbaColor::FromFloat(0.4f*bgColor.redF(),
0.4f*bgColor.greenF(),
0.4f*bgColor.blueF());
canvas.lighting.backgroundColor = bgColor;
// And show the text window, which has info to debug it
ForceTextWindowShown();
}
canvas.BeginFrame();
canvas.UpdateProjection();
Draw(&canvas);
canvas.camera.LoadIdentity();
canvas.UpdateProjection();
UiCanvas uiCanvas = {};
uiCanvas.canvas = &canvas;
// If a marquee selection is in progress, then draw the selection
// rectangle, as an outline and a transparent fill.
if(pending.operation == Pending::DRAGGING_MARQUEE) {
Point2d begin = ProjectPoint(orig.marqueePoint);
uiCanvas.DrawRect((int)orig.mouse.x, (int)begin.x,
(int)orig.mouse.y, (int)begin.y,
/*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25),
/*outlineColor=*/Style::Color(Style::HOVERED));
}
// And finally the toolbar.
if(SS.showToolbar) {
ToolbarDraw();
canvas.camera.offset = {};
canvas.camera.offset.x = -(double)canvas.camera.width / 2.0;
canvas.camera.offset.y = -(double)canvas.camera.height / 2.0;
canvas.UpdateProjection();
ToolbarDraw(&uiCanvas);
}
}
// If we display UI elements, also display an fps counter.
if(SS.showToolbar) {
canvas.EndFrame();
auto renderEndTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> renderTime = renderEndTime - renderStartTime;
RgbaColor renderTimeColor;
if(1000 / renderTime.count() < 60) {
// We aim for a steady 60fps; draw the counter in red when we're slower.
renderTimeColor = { 255, 0, 0, 255 };
} else {
renderTimeColor = { 255, 255, 255, 255 };
}
uiCanvas.DrawBitmapText(ssprintf("rendered in %ld ms (%ld 1/s)",
(long)renderTime.count(),
(long)(1000/renderTime.count())),
5, 5, renderTimeColor);
}
canvas.EndFrame();
}

File diff suppressed because it is too large Load Diff

View File

@ -17,109 +17,6 @@ std::string Entity::DescriptionString() const {
}
}
void Entity::LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat, int data) {
if(dogd.drawing) {
// Draw lines from active group in front of those from previous
ssglDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 3);
// Narrow lines are drawn as lines, but fat lines must be drawn as
// filled polygons, to get the line join style right.
ssglStippledLine(a, b, dogd.lineWidth, dogd.stippleType, dogd.stippleScale, maybeFat);
ssglDepthRangeOffset(0);
} else {
Point2d ap = SS.GW.ProjectPoint(a);
Point2d bp = SS.GW.ProjectPoint(b);
double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
// A little bit easier to select in the active group
if(group.v == SS.GW.activeGroup.v) d -= 1;
if(d < dogd.dmin) {
dogd.dmin = d;
dogd.data = data;
}
}
}
void Entity::DrawAll(bool drawAsHidden) {
// This handles points as a special case, because I seem to be able
// to get a huge speedup that way, by consolidating stuff to gl.
int i;
if(SS.GW.showPoints) {
double s = 3.5/SS.GW.scale;
Vector r = SS.GW.projRight.ScaledBy(s);
Vector d = SS.GW.projUp.ScaledBy(s);
ssglColorRGB(Style::Color(Style::DATUM));
ssglDepthRangeOffset(6);
glBegin(GL_QUADS);
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(!e->IsPoint()) continue;
if(!(SK.GetGroup(e->group)->IsVisible())) continue;
if(e->forceHidden) continue;
Vector v = e->PointGetNum();
// If we're analyzing the sketch to show the degrees of freedom,
// then we draw big colored squares over the points that are
// free to move.
bool free = false;
if(e->type == Type::POINT_IN_3D) {
Param *px = SK.GetParam(e->param[0]),
*py = SK.GetParam(e->param[1]),
*pz = SK.GetParam(e->param[2]);
free = (px->free) || (py->free) || (pz->free);
} else if(e->type == Type::POINT_IN_2D) {
Param *pu = SK.GetParam(e->param[0]),
*pv = SK.GetParam(e->param[1]);
free = (pu->free) || (pv->free);
}
if(free) {
Vector re = r.ScaledBy(2.5), de = d.ScaledBy(2.5);
ssglColorRGB(Style::Color(Style::ANALYZE));
ssglVertex3v(v.Plus (re).Plus (de));
ssglVertex3v(v.Plus (re).Minus(de));
ssglVertex3v(v.Minus(re).Minus(de));
ssglVertex3v(v.Minus(re).Plus (de));
ssglColorRGB(Style::Color(Style::DATUM));
}
ssglVertex3v(v.Plus (r).Plus (d));
ssglVertex3v(v.Plus (r).Minus(d));
ssglVertex3v(v.Minus(r).Minus(d));
ssglVertex3v(v.Minus(r).Plus (d));
}
glEnd();
ssglDepthRangeOffset(0);
}
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(e->IsPoint()) {
continue; // already handled
}
e->Draw(drawAsHidden);
}
}
void Entity::Draw(bool drawAsHidden) {
hStyle hs = Style::ForEntity(h);
dogd.lineWidth = Style::Width(hs);
if(drawAsHidden) {
dogd.stippleType = Style::PatternType({ Style::HIDDEN_EDGE });
dogd.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE });
} else {
dogd.stippleType = Style::PatternType(hs);
dogd.stippleScale = Style::StippleScaleMm(hs);
}
ssglLineWidth((float)dogd.lineWidth);
ssglColorRGB(Style::Color(hs));
dogd.drawing = true;
DrawOrGetDistance();
}
void Entity::GenerateEdges(SEdgeList *el) {
SBezierList *sbl = GetOrGenerateBezierCurves();
@ -185,17 +82,7 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
return screenBBox;
}
double Entity::GetDistance(Point2d mp) {
dogd.drawing = false;
dogd.mp = mp;
dogd.dmin = 1e12;
DrawOrGetDistance();
return dogd.dmin;
}
Vector Entity::GetReferencePos() {
void Entity::GetReferencePoints(std::vector<Vector> *refs) {
switch(type) {
case Type::POINT_N_COPY:
case Type::POINT_N_TRANS:
@ -203,7 +90,8 @@ Vector Entity::GetReferencePos() {
case Type::POINT_N_ROT_AA:
case Type::POINT_IN_3D:
case Type::POINT_IN_2D:
return PointGetNum();
refs->push_back(PointGetNum());
break;
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
@ -216,12 +104,14 @@ Vector Entity::GetReferencePos() {
case Type::CUBIC:
case Type::CUBIC_PERIODIC:
case Type::TTF_TEXT:
return SK.GetEntity(point[0])->PointGetNum();
refs->push_back(SK.GetEntity(point[0])->PointGetNum());
break;
case Type::LINE_SEGMENT: {
Vector a = SK.GetEntity(point[0])->PointGetNum(),
b = SK.GetEntity(point[1])->PointGetNum();
return b.Plus(a.Minus(b).ScaledBy(0.5));
refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5)));
break;
}
case Type::DISTANCE:
@ -233,7 +123,17 @@ Vector Entity::GetReferencePos() {
case Type::FACE_N_ROT_AA:
break;
}
ssassert(false, "Unexpected entity type");
}
int Entity::GetPositionOfPoint(const Camera &camera, Point2d p) {
ObjectPicker canvas = {};
canvas.camera = camera;
canvas.point = p;
canvas.minDistance = 1e12;
Draw(DrawAs::DEFAULT, &canvas);
return canvas.position;
}
bool Entity::IsStylable() const {
@ -252,8 +152,7 @@ bool Entity::IsVisible() const {
}
if(!(g->IsVisible())) return false;
// Don't check if points are hidden; this gets called only for
// selected or hovered points, and those should always be shown.
if(IsPoint() && !SS.GW.showPoints) return false;
if(IsNormal() && !SS.GW.showNormals) return false;
if(!SS.GW.showWorkplanes) {
@ -296,10 +195,6 @@ void Entity::CalculateNumerical(bool forExport) {
}
}
bool Entity::PointIsFromReferences() const {
return h.request().IsFromReferences();
}
//-----------------------------------------------------------------------------
// Compute a cubic, second derivative continuous, interpolating spline. Same
// routine for periodic splines (in a loop) or open splines (with specified
@ -544,17 +439,61 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
}
}
void Entity::DrawOrGetDistance() {
// If we're about to perform hit testing on an entity, consider
// whether the pointer is inside its bounding box first.
if(!dogd.drawing && !IsNormal()) {
bool hasBBox;
BBox box = GetOrGenerateScreenBBox(&hasBBox);
if(hasBBox && !box.Contains(dogd.mp, SELECTION_RADIUS))
return;
void Entity::Draw(DrawAs how, Canvas *canvas) {
if(!IsVisible()) return;
int zIndex;
if(how == DrawAs::HIDDEN) {
zIndex = 2;
} else if(group.v != SS.GW.activeGroup.v) {
zIndex = 3;
} else {
zIndex = 4;
}
if(!IsVisible()) return;
hStyle hs;
if(IsPoint()) {
hs.v = Style::DATUM;
} else if(IsNormal() || type == Type::WORKPLANE) {
hs.v = Style::NORMALS;
} else {
hs = Style::ForEntity(h);
}
Canvas::Stroke stroke = {};
stroke.zIndex = zIndex;
stroke.color = Style::Color(hs);
stroke.width = Style::Width(hs);
stroke.stipplePattern = Style::PatternType(hs);
stroke.stippleScale = Style::StippleScaleMm(hs);
switch(how) {
case DrawAs::DEFAULT:
stroke.layer = Canvas::Layer::NORMAL;
break;
case DrawAs::HIDDEN:
stroke.layer = Canvas::Layer::OCCLUDED;
stroke.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE });
stroke.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE });
break;
case DrawAs::HOVERED:
stroke.layer = Canvas::Layer::FRONT;
stroke.color = Style::Color(Style::HOVERED);
break;
case DrawAs::SELECTED:
stroke.layer = Canvas::Layer::FRONT;
stroke.color = Style::Color(Style::SELECTED);
break;
}
Canvas::hStroke hcs = canvas->GetStroke(stroke);
Canvas::Fill fill = {};
fill.layer = stroke.layer;
fill.zIndex = IsPoint() ? zIndex + 1 : 0;
fill.color = stroke.color;
Canvas::hFill hcf = canvas->GetFill(fill);
switch(type) {
case Type::POINT_N_COPY:
@ -563,26 +502,33 @@ void Entity::DrawOrGetDistance() {
case Type::POINT_N_ROT_AA:
case Type::POINT_IN_3D:
case Type::POINT_IN_2D: {
Vector v = PointGetNum();
if(how == DrawAs::HIDDEN) return;
if(dogd.drawing) {
double s = 3.5;
Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale);
Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale);
// If we're analyzing the sketch to show the degrees of freedom,
// then we draw big colored squares over the points that are
// free to move.
bool free = false;
if(type == Type::POINT_IN_3D) {
Param *px = SK.GetParam(param[0]),
*py = SK.GetParam(param[1]),
*pz = SK.GetParam(param[2]);
ssglColorRGB(Style::Color(Style::DATUM));
ssglDepthRangeOffset(6);
glBegin(GL_QUADS);
ssglVertex3v(v.Plus (r).Plus (d));
ssglVertex3v(v.Plus (r).Minus(d));
ssglVertex3v(v.Minus(r).Minus(d));
ssglVertex3v(v.Minus(r).Plus (d));
glEnd();
ssglDepthRangeOffset(0);
} else {
Point2d pp = SS.GW.ProjectPoint(v);
dogd.dmin = pp.DistanceTo(dogd.mp) - 6;
free = px->free || py->free || pz->free;
} else if(type == Type::POINT_IN_2D) {
Param *pu = SK.GetParam(param[0]),
*pv = SK.GetParam(param[1]);
free = pu->free || pv->free;
}
if(free) {
Canvas::Fill fillAnalyze = fill;
fillAnalyze.color = Style::Color(Style::ANALYZE);
Canvas::hFill hcfAnalyze = canvas->GetFill(fillAnalyze);
canvas->DrawPoint(PointGetNum(), 7.0, hcfAnalyze);
}
canvas->DrawPoint(PointGetNum(), 3.5, hcf);
return;
}
@ -591,67 +537,59 @@ void Entity::DrawOrGetDistance() {
case Type::NORMAL_N_ROT_AA:
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D: {
int i;
for(i = 0; i < 2; i++) {
if(i == 0 && !SS.GW.showNormals) {
// When the normals are hidden, we will continue to show
// the coordinate axes at the bottom left corner, but
// not at the origin.
continue;
const Camera &camera = canvas->GetCamera();
if(how == DrawAs::HIDDEN) return;
for(int i = 0; i < 2; i++) {
bool asReference = (i == 1);
if(asReference) {
if(!h.request().IsFromReferences()) continue;
} else {
if(!SK.GetGroup(group)->IsVisible() || !SS.GW.showNormals) continue;
}
hRequest hr = h.request();
stroke.layer = (asReference) ? Canvas::Layer::FRONT : Canvas::Layer::NORMAL;
if(how == DrawAs::DEFAULT) {
// Always draw the x, y, and z axes in red, green, and blue;
// brighter for the ones at the bottom left of the screen,
// dimmer for the ones at the model origin.
int f = (i == 0 ? 100 : 255);
hRequest hr = h.request();
uint8_t luma = (asReference) ? 255 : 100;
if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
if(dogd.drawing)
ssglColorRGB(RGBi(0, 0, f));
stroke.color = RgbaColor::From(0, 0, luma);
} else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
if(dogd.drawing)
ssglColorRGB(RGBi(f, 0, 0));
stroke.color = RgbaColor::From(luma, 0, 0);
} else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
if(dogd.drawing)
ssglColorRGB(RGBi(0, f, 0));
} else {
if(dogd.drawing)
ssglColorRGB(Style::Color(Style::NORMALS));
if(i > 0) break;
stroke.color = RgbaColor::From(0, luma, 0);
}
}
hcs = canvas->GetStroke(stroke);
Quaternion q = NormalGetNum();
Vector tail;
if(i == 0) {
tail = SK.GetEntity(point[0])->PointGetNum();
if(dogd.drawing)
ssglLineWidth(1);
} else {
if(asReference) {
// Draw an extra copy of the x, y, and z axes, that's
// always in the corner of the view and at the front.
// So those are always available, perhaps useful.
double s = SS.GW.scale;
double h = 60 - SS.GW.height/2;
double w = 60 - SS.GW.width/2;
tail = SS.GW.projRight.ScaledBy(w/s).Plus(
SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset);
if(dogd.drawing) {
ssglDepthRangeLockToFront(true);
ssglLineWidth(2);
stroke.width = 2;
double s = camera.scale;
double h = 60 - camera.height / 2.0;
double w = 60 - camera.width / 2.0;
tail = camera.projRight.ScaledBy(w/s).Plus(
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
} else {
tail = SK.GetEntity(point[0])->PointGetNum();
}
}
Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale);
Vector v = (q.RotationN()).WithMagnitude(50.0 / camera.scale);
Vector tip = tail.Plus(v);
LineDrawOrGetDistance(tail, tip);
canvas->DrawLine(tail, tip, hcs);
v = v.WithMagnitude(12/SS.GW.scale);
v = v.WithMagnitude(12.0 / camera.scale);
Vector axis = q.RotationV();
LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6)));
LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6)));
canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, 0.6)), hcs);
canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, -0.6)), hcs);
}
if(dogd.drawing)
ssglDepthRangeLockToFront(false);
return;
}
@ -661,13 +599,14 @@ void Entity::DrawOrGetDistance() {
return;
case Type::WORKPLANE: {
Vector p;
p = SK.GetEntity(point[0])->PointGetNum();
const Camera &camera = canvas->GetCamera();
Vector p = SK.GetEntity(point[0])->PointGetNum();
Vector u = Normal()->NormalU();
Vector v = Normal()->NormalV();
double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale;
double s = (std::min(camera.width, camera.height)) * 0.45 / camera.scale;
Vector us = u.ScaledBy(s);
Vector vs = v.ScaledBy(s);
@ -677,41 +616,28 @@ void Entity::DrawOrGetDistance() {
Vector mm = p.Minus(us).Minus(vs), mm2 = mm;
Vector mp = p.Minus(us).Plus (vs);
if(dogd.drawing) {
ssglLineWidth(1);
ssglColorRGB(Style::Color(Style::NORMALS));
glEnable(GL_LINE_STIPPLE);
glLineStipple(3, 0x1111);
}
Canvas::Stroke strokeBorder = stroke;
strokeBorder.zIndex -= 3;
strokeBorder.stipplePattern = StipplePattern::SHORT_DASH;
strokeBorder.stippleScale = 8.0 / camera.scale;
Canvas::hStroke hcsBorder = canvas->GetStroke(strokeBorder);
double textHeight = Style::TextHeight(hs) / camera.scale;
if(!h.isFromRequest()) {
mm = mm.Plus(v.ScaledBy(70/SS.GW.scale));
mm2 = mm2.Plus(u.ScaledBy(70/SS.GW.scale));
LineDrawOrGetDistance(mm2, mm);
mm = mm.Plus(v.ScaledBy(textHeight * 4.7));
mm2 = mm2.Plus(u.ScaledBy(textHeight * 4.7));
canvas->DrawLine(mm2, mm, hcsBorder);
}
LineDrawOrGetDistance(pp, pm);
LineDrawOrGetDistance(pm, mm2);
LineDrawOrGetDistance(mp, mm);
LineDrawOrGetDistance(pp, mp);
canvas->DrawLine(pp, pm, hcsBorder);
canvas->DrawLine(mm2, pm, hcsBorder);
canvas->DrawLine(mm, mp, hcsBorder);
canvas->DrawLine(pp, mp, hcsBorder);
if(dogd.drawing)
glDisable(GL_LINE_STIPPLE);
std::string str = DescriptionString().substr(5);
double th = Style::DefaultTextHeight();
if(dogd.drawing) {
Vector o = mm2.Plus(u.ScaledBy(3/SS.GW.scale)).Plus(
v.ScaledBy(3/SS.GW.scale));
ssglWriteText(str, th, o, u, v, NULL, NULL);
} else {
Vector pos = mm2.Plus(u.ScaledBy(ssglStrWidth(str, th)/2)).Plus(
v.ScaledBy(ssglStrCapHeight(th)/2));
Point2d pp = SS.GW.ProjectPoint(pos);
dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10);
// If a line lies in a plane, then select the line, not
// the plane.
dogd.dmin += 3;
}
Vector o = mm2.Plus(u.ScaledBy(3.0 / camera.scale)).Plus(
v.ScaledBy(3.0 / camera.scale));
std::string shortDesc = DescriptionString().substr(5);
canvas->DrawVectorText(shortDesc, textHeight, o, u, v, hcs);
return;
}
@ -721,13 +647,10 @@ void Entity::DrawOrGetDistance() {
case Type::CUBIC:
case Type::CUBIC_PERIODIC:
case Type::TTF_TEXT: {
// Nothing but the curves; generate the rational polynomial curves for
// everything, then piecewise linearize them, and display those.
SEdgeList *sel = GetOrGenerateEdges();
dogd.data = -1;
for(int i = 0; i < sel->l.n; i++) {
SEdge *se = &(sel->l.elem[i]);
LineDrawOrGetDistance(se->a, se->b, /*maybeFat=*/true, se->auxB);
// Generate the rational polynomial curves, then piecewise linearize
// them, and display those.
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) {
canvas->DrawEdges(*GetOrGenerateEdges(), hcs);
}
return;
}
@ -742,4 +665,3 @@ void Entity::DrawOrGetDistance() {
}
ssassert(false, "Unexpected entity type");
}

View File

@ -135,6 +135,7 @@ public:
double Dot(Point2d p) const;
double DistanceTo(const Point2d &p) const;
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const;
double DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const;
double Angle() const;
double AngleTo(const Point2d &p) const;
double Magnitude() const;
@ -459,6 +460,8 @@ public:
float blueF() const { return (float)blue / 255.0f; }
float alphaF() const { return (float)alpha / 255.0f; }
bool IsEmpty() const { return alpha == 0; }
bool Equals(RgbaColor c) const {
return
c.red == red &&
@ -467,6 +470,12 @@ public:
c.alpha == alpha;
}
RgbaColor WithAlpha(uint8_t newAlpha) const {
RgbaColor color = *this;
color.alpha = newAlpha;
return color;
}
uint32_t ToPackedIntBGRA() const {
return
blue |

View File

@ -10,7 +10,6 @@
#ifndef WIN32
#include <platform/gloffscreen.h>
#endif
#include <png.h>
void SolveSpaceUI::ExportSectionTo(const std::string &filename) {
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
@ -110,6 +109,66 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) {
bl.Clear();
}
// This is an awful temporary hack to replace Constraint::GetEdges until we have proper
// export through Canvas.
class GetEdgesCanvas : public Canvas {
public:
Camera camera;
SEdgeList *edges;
const Camera &GetCamera() const override {
return camera;
}
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override {
edges->AddEdge(a, b, Style::CONSTRAINT);
}
void DrawEdges(const SEdgeList &el, hStroke hcs) override {
for(const SEdge &e : el.l) {
edges->AddEdge(e.a, e.b, Style::CONSTRAINT);
}
}
void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) override {
auto traceEdge = [&](Vector a, Vector b) { edges->AddEdge(a, b, Style::CONSTRAINT); };
VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
}
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override {
// Do nothing
}
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override {
ssassert(false, "Not implemented");
}
void DrawOutlines(const SOutlineList &ol, hStroke hcs) override {
ssassert(false, "Not implemented");
}
void DrawPoint(const Vector &o, double d, hFill hcf) override {
ssassert(false, "Not implemented");
}
void DrawPolygon(const SPolygon &p, hFill hcf) override {
ssassert(false, "Not implemented");
}
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {},
hStroke hcsTriangles = {}) override {
ssassert(false, "Not implemented");
}
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override {
ssassert(false, "Not implemented");
}
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override {
ssassert(false, "Not implemented");
}
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {
ssassert(false, "Not implemented");
}
};
void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe) {
int i;
SEdgeList edges = {};
@ -159,11 +218,14 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exp
if(SS.GW.showConstraints) {
if(!out->OutputConstraints(&SK.constraint)) {
GetEdgesCanvas canvas = {};
canvas.camera = SS.GW.GetCamera();
canvas.edges = &edges;
// The output format cannot represent constraints directly,
// so convert them to edges.
Constraint *c;
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
c->GetEdges(&edges);
for(Constraint &c : SK.constraint) {
c.Draw(Constraint::DrawAs::DEFAULT, &canvas);
}
}
}
@ -1021,62 +1083,26 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename,
// rendering the view in the usual way and then copying the pixels.
//-----------------------------------------------------------------------------
void SolveSpaceUI::ExportAsPngTo(const std::string &filename) {
int w = (int)SS.GW.width, h = (int)SS.GW.height;
// No guarantee that the back buffer contains anything valid right now,
// so repaint the scene. And hide the toolbar too.
bool prevShowToolbar = SS.showToolbar;
SS.showToolbar = false;
#ifndef WIN32
std::unique_ptr<GLOffscreen> gloffscreen(new GLOffscreen);
gloffscreen->begin(w, h);
gloffscreen->begin((int)SS.GW.width, (int)SS.GW.height);
#endif
SS.GW.Paint();
SS.showToolbar = prevShowToolbar;
// Somewhat hacky way to invoke glReadPixels without dragging in all OpenGL headers.
OpenGl1Renderer canvas = {};
canvas.camera = SS.GW.GetCamera();
std::shared_ptr<Pixmap> screenshot = canvas.ReadFrame();
FILE *f = ssfopen(filename, "wb");
if(!f) goto err;
png_struct *png_ptr; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if(!png_ptr) goto err;
png_info *info_ptr; info_ptr = png_create_info_struct(png_ptr);
if(!png_ptr) goto err;
if(setjmp(png_jmpbuf(png_ptr))) goto err;
png_init_io(png_ptr, f);
// glReadPixels wants to align things on 4-boundaries, and there's 3
// bytes per pixel. As long as the row width is divisible by 4, all
// works out.
w &= ~3; h &= ~3;
png_set_IHDR(png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
// Get the pixel data from the framebuffer
uint8_t *pixels; pixels = (uint8_t *)AllocTemporary(3*w*h);
uint8_t **rowptrs; rowptrs = (uint8_t **)AllocTemporary(h*sizeof(uint8_t *));
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
int y;
for(y = 0; y < h; y++) {
// gl puts the origin at lower left, but png puts it top left
rowptrs[y] = pixels + ((h - 1) - y)*(3*w);
if(!f || !screenshot->WritePng(f, /*flip=*/FLIP_FRAMEBUFFER)) {
Error("Couldn't write to '%s'", filename.c_str());
}
png_write_image(png_ptr, rowptrs);
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(f);
return;
err:
Error("Error writing PNG file '%s'", filename.c_str());
if(f) fclose(f);
return;
}

View File

@ -684,21 +684,6 @@ static std::string Join(const std::vector<std::string> &parts, const std::string
return result;
}
static bool PlatformPathEqual(const std::string &a, const std::string &b)
{
// Case-sensitivity is actually per-volume on both Windows and OS X,
// but it is extremely tedious to implement and test for little benefit.
#if defined(WIN32)
std::wstring wa = Widen(a), wb = Widen(b);
return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/
[](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); });
#elif defined(__APPLE__)
return !strcasecmp(a.c_str(), b.c_str());
#else
return a == b;
#endif
}
static std::string MakePathRelative(const std::string &base, const std::string &path)
{
std::vector<std::string> baseParts = Split(base, PATH_SEP),
@ -708,7 +693,7 @@ static std::string MakePathRelative(const std::string &base, const std::string &
size_t common;
for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) {
if(!PlatformPathEqual(baseParts[common], pathParts[common]))
if(!PathEqual(baseParts[common], pathParts[common]))
break;
}

View File

@ -1,835 +0,0 @@
//-----------------------------------------------------------------------------
// Helper functions that ultimately draw stuff with gl.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
namespace SolveSpace {
static bool ColorLocked;
static bool DepthOffsetLocked;
void ssglLineWidth(GLfloat width) {
// Intel GPUs with Mesa on *nix render thin lines poorly.
static bool workaroundChecked, workaroundEnabled;
if(!workaroundChecked) {
// ssglLineWidth can be called before GL is initialized
if(glGetString(GL_VENDOR)) {
workaroundChecked = true;
if(!strcmp((char*)glGetString(GL_VENDOR), "Intel Open Source Technology Center"))
workaroundEnabled = true;
}
}
if(workaroundEnabled && width < 1.6f)
width = 1.6f;
glLineWidth(width);
}
static void LineDrawCallback(void *fndata, Vector a, Vector b)
{
ssglLineWidth(1);
glBegin(GL_LINES);
ssglVertex3v(a);
ssglVertex3v(b);
glEnd();
}
void ssglVertex3v(Vector u)
{
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
}
void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone)
{
if(lone) glBegin(GL_QUADS);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
if(lone) glEnd();
}
void ssglAxisAlignedLineLoop(double l, double r, double t, double b)
{
glBegin(GL_LINE_LOOP);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
glEnd();
}
static void FatLineEndcap(Vector p, Vector u, Vector v)
{
// A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10
static const double Circle[11][2] = {
{ 0.0000, 1.0000 },
{ -0.3090, 0.9511 },
{ -0.5878, 0.8090 },
{ -0.8090, 0.5878 },
{ -0.9511, 0.3090 },
{ -1.0000, 0.0000 },
{ -0.9511, -0.3090 },
{ -0.8090, -0.5878 },
{ -0.5878, -0.8090 },
{ -0.3090, -0.9511 },
{ 0.0000, -1.0000 },
};
glBegin(GL_TRIANGLE_FAN);
for(int i = 0; i <= 10; i++) {
double c = Circle[i][0], s = Circle[i][1];
ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
}
glEnd();
}
void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat) {
if(!maybeFat || pixelWidth <= 3.0) {
glBegin(GL_LINES);
ssglVertex3v(a);
ssglVertex3v(b);
glEnd();
} else {
ssglFatLine(a, b, pixelWidth / SS.GW.scale);
}
}
void ssglPoint(Vector p, double pixelSize)
{
if(/*!maybeFat || */pixelSize <= 3.0) {
glBegin(GL_LINES);
Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
ssglVertex3v(p.Minus(u));
ssglVertex3v(p.Plus(u));
glEnd();
} else {
Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
Vector v = SS.GW.projUp.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
FatLineEndcap(p, u, v);
FatLineEndcap(p, u.ScaledBy(-1.0), v);
}
}
void ssglStippledLine(Vector a, Vector b, double width,
StipplePattern stippleType, double stippleScale, bool maybeFat)
{
const char *stipplePattern;
switch(stippleType) {
case StipplePattern::CONTINUOUS: ssglLine(a, b, width, maybeFat); return;
case StipplePattern::SHORT_DASH: stipplePattern = "- "; break;
case StipplePattern::DASH: stipplePattern = "- "; break;
case StipplePattern::LONG_DASH: stipplePattern = "_ "; break;
case StipplePattern::DASH_DOT: stipplePattern = "-."; break;
case StipplePattern::DASH_DOT_DOT: stipplePattern = "-.."; break;
case StipplePattern::DOT: stipplePattern = "."; break;
case StipplePattern::FREEHAND: stipplePattern = "~"; break;
case StipplePattern::ZIGZAG: stipplePattern = "~__"; break;
}
ssglStippledLine(a, b, width, stipplePattern, stippleScale, maybeFat);
}
void ssglStippledLine(Vector a, Vector b, double width,
const char *stipplePattern, double stippleScale, bool maybeFat)
{
ssassert(stipplePattern != NULL, "Unexpected stipple pattern");
Vector dir = b.Minus(a);
double len = dir.Magnitude();
dir = dir.WithMagnitude(1.0);
const char *si = stipplePattern;
double end = len;
double ss = stippleScale / 2.0;
do {
double start = end;
switch(*si) {
case ' ':
end -= 1.0 * ss;
break;
case '-':
start = max(start - 0.5 * ss, 0.0);
end = max(start - 2.0 * ss, 0.0);
if(start == end) break;
ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat);
end = max(end - 0.5 * ss, 0.0);
break;
case '_':
end = max(end - 4.0 * ss, 0.0);
ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat);
break;
case '.':
end = max(end - 0.5 * ss, 0.0);
if(end == 0.0) break;
ssglPoint(a.Plus(dir.ScaledBy(end)), width);
end = max(end - 0.5 * ss, 0.0);
break;
case '~': {
Vector ab = b.Minus(a);
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
double pws = 2.0 * width / SS.GW.scale;
end = max(end - 0.5 * ss, 0.0);
Vector aa = a.Plus(dir.ScaledBy(start));
Vector bb = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
ssglLine(aa, bb, width, maybeFat);
if(end == 0.0) break;
start = end;
end = max(end - 1.0 * ss, 0.0);
aa = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws))
.Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss));
ssglLine(bb, aa, width, maybeFat);
if(end == 0.0) break;
start = end;
end = max(end - 0.5 * ss, 0.0);
bb = a.Plus(dir.ScaledBy(end))
.Minus(abn.ScaledBy(pws))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
ssglLine(aa, bb, width, maybeFat);
break;
}
default: ssassert(false, "Unexpected stipple pattern element");
}
if(*(++si) == 0) si = stipplePattern;
} while(end > 0.0);
}
void ssglFatLine(Vector a, Vector b, double width)
{
if(a.EqualsExactly(b)) return;
// The half-width of the line we're drawing.
double hw = width / 2;
Vector ab = b.Minus(a);
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
// So now abn is normal to the projection of ab into the screen, so the
// line will always have constant thickness as the view is rotated.
abn = abn.WithMagnitude(hw);
ab = gn.Cross(abn);
ab = ab. WithMagnitude(hw);
// The body of a line is a quad
glBegin(GL_QUADS);
ssglVertex3v(a.Minus(abn));
ssglVertex3v(b.Minus(abn));
ssglVertex3v(b.Plus (abn));
ssglVertex3v(a.Plus (abn));
glEnd();
// And the line has two semi-circular end caps.
FatLineEndcap(a, ab, abn);
FatLineEndcap(b, ab.ScaledBy(-1), abn);
}
void ssglLockColorTo(RgbaColor rgb)
{
ColorLocked = false;
glColor3d(rgb.redF(), rgb.greenF(), rgb.blueF());
ColorLocked = true;
}
void ssglUnlockColor()
{
ColorLocked = false;
}
void ssglColorRGB(RgbaColor rgb)
{
// Is there a bug in some graphics drivers where this is not equivalent
// to glColor3d? There seems to be...
ssglColorRGBa(rgb, 1.0);
}
void ssglColorRGBa(RgbaColor rgb, double a)
{
if(!ColorLocked) glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), a);
}
static void Stipple(bool forSelection)
{
static bool Init;
const int BYTES = (32*32)/8;
static GLubyte HoverMask[BYTES];
static GLubyte SelMask[BYTES];
if(!Init) {
int x, y;
for(x = 0; x < 32; x++) {
for(y = 0; y < 32; y++) {
int i = y*4 + x/8, b = x % 8;
int ym = y % 4, xm = x % 4;
for(int k = 0; k < 2; k++) {
if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) {
(k == 0 ? SelMask : HoverMask)[i] |= (0x80 >> b);
}
ym = (ym + 2) % 4; xm = (xm + 2) % 4;
}
}
}
Init = true;
}
glEnable(GL_POLYGON_STIPPLE);
if(forSelection) {
glPolygonStipple(SelMask);
} else {
glPolygonStipple(HoverMask);
}
}
static void StippleTriangle(STriangle *tr, bool forSelection, RgbaColor rgb)
{
glEnd();
glDisable(GL_LIGHTING);
ssglColorRGB(rgb);
Stipple(forSelection);
glBegin(GL_TRIANGLES);
ssglVertex3v(tr->a);
ssglVertex3v(tr->b);
ssglVertex3v(tr->c);
glEnd();
glEnable(GL_LIGHTING);
glDisable(GL_POLYGON_STIPPLE);
glBegin(GL_TRIANGLES);
}
void ssglFillMesh(bool useSpecColor, RgbaColor specColor,
SMesh *m, uint32_t h, uint32_t s1, uint32_t s2)
{
RgbaColor rgbHovered = Style::Color(Style::HOVERED),
rgbSelected = Style::Color(Style::SELECTED);
glEnable(GL_NORMALIZE);
bool hasMaterial = false;
RgbaColor prevColor;
glBegin(GL_TRIANGLES);
for(int i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
RgbaColor color;
if(useSpecColor) {
color = specColor;
} else {
color = tr->meta.color;
}
if(!hasMaterial || !color.Equals(prevColor)) {
GLfloat mpf[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() };
glEnd();
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
prevColor = color;
hasMaterial = true;
glBegin(GL_TRIANGLES);
}
if(tr->an.EqualsExactly(Vector::From(0, 0, 0))) {
// Compute the normal from the vertices
Vector n = tr->Normal();
glNormal3d(n.x, n.y, n.z);
ssglVertex3v(tr->a);
ssglVertex3v(tr->b);
ssglVertex3v(tr->c);
} else {
// Use the exact normals that are specified
glNormal3d((tr->an).x, (tr->an).y, (tr->an).z);
ssglVertex3v(tr->a);
glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z);
ssglVertex3v(tr->b);
glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z);
ssglVertex3v(tr->c);
}
if((s1 != 0 && tr->meta.face == s1) ||
(s2 != 0 && tr->meta.face == s2))
{
StippleTriangle(tr, /*forSelection=*/true, rgbSelected);
}
if(h != 0 && tr->meta.face == h) {
StippleTriangle(tr, /*forSelection=*/false, rgbHovered);
}
}
glEnd();
}
static void SSGL_CALLBACK Vertex(Vector *p)
{
ssglVertex3v(*p);
}
void ssglFillPolygon(SPolygon *p)
{
GLUtesselator *gt = gluNewTess();
gluTessCallback(gt, GLU_TESS_BEGIN, (ssglCallbackFptr *)glBegin);
gluTessCallback(gt, GLU_TESS_END, (ssglCallbackFptr *)glEnd);
gluTessCallback(gt, GLU_TESS_VERTEX, (ssglCallbackFptr *)Vertex);
ssglTesselatePolygon(gt, p);
gluDeleteTess(gt);
}
static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4],
float weight[4], void **outData)
{
Vector *n = (Vector *)AllocTemporary(sizeof(Vector));
n->x = coords[0];
n->y = coords[1];
n->z = coords[2];
*outData = n;
}
void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p)
{
int i, j;
gluTessCallback(gt, GLU_TESS_COMBINE, (ssglCallbackFptr *)Combine);
gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
Vector normal = p->normal;
glNormal3d(normal.x, normal.y, normal.z);
gluTessNormal(gt, normal.x, normal.y, normal.z);
gluTessBeginPolygon(gt, NULL);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
gluTessBeginContour(gt);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
double ap[3];
ap[0] = sp->p.x;
ap[1] = sp->p.y;
ap[2] = sp->p.z;
gluTessVertex(gt, ap, &(sp->p));
}
gluTessEndContour(gt);
}
gluTessEndPolygon(gt);
}
void ssglDebugPolygon(SPolygon *p)
{
int i, j;
ssglLineWidth(2);
glPointSize(7);
glDisable(GL_DEPTH_TEST);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
Vector a = (sc->l.elem[j]).p;
Vector b = (sc->l.elem[j+1]).p;
ssglLockColorTo(RGBi(0, 0, 255));
Vector d = (a.Minus(b)).WithMagnitude(-0);
glBegin(GL_LINES);
ssglVertex3v(a.Plus(d));
ssglVertex3v(b.Minus(d));
glEnd();
ssglLockColorTo(RGBi(255, 0, 0));
glBegin(GL_POINTS);
ssglVertex3v(a.Plus(d));
ssglVertex3v(b.Minus(d));
glEnd();
}
}
}
void ssglDrawEdges(SEdgeList *el, bool endpointsToo, hStyle hs)
{
double lineWidth = Style::Width(hs);
StipplePattern stippleType = Style::PatternType(hs);
double stippleScale = Style::StippleScaleMm(hs);
ssglLineWidth(float(lineWidth));
ssglColorRGB(Style::Color(hs));
SEdge *se;
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
ssglStippledLine(se->a, se->b, lineWidth, stippleType, stippleScale,
/*maybeFat=*/true);
}
if(endpointsToo) {
glPointSize(12);
glBegin(GL_POINTS);
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
ssglVertex3v(se->a);
ssglVertex3v(se->b);
}
glEnd();
}
}
void ssglDrawOutlines(SOutlineList *sol, Vector projDir, hStyle hs)
{
double lineWidth = Style::Width(hs);
StipplePattern stippleType = Style::PatternType(hs);
double stippleScale = Style::StippleScaleMm(hs);
ssglLineWidth((float)lineWidth);
ssglColorRGB(Style::Color(hs));
for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) {
if(!so->IsVisible(projDir)) continue;
ssglStippledLine(so->a, so->b, lineWidth, stippleType, stippleScale,
/*maybeFat=*/true);
}
}
void ssglDebugMesh(SMesh *m)
{
int i;
ssglLineWidth(1);
glPointSize(7);
ssglDepthRangeOffset(1);
ssglUnlockColor();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
ssglColorRGBa(RGBi(0, 255, 0), 1.0);
glBegin(GL_TRIANGLES);
for(i = 0; i < m->l.n; i++) {
STriangle *t = &(m->l.elem[i]);
if(t->tag) continue;
ssglVertex3v(t->a);
ssglVertex3v(t->b);
ssglVertex3v(t->c);
}
glEnd();
ssglDepthRangeOffset(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
void ssglMarkPolygonNormal(SPolygon *p)
{
Vector tail = Vector::From(0, 0, 0);
int i, j, cnt = 0;
// Choose some reasonable center point.
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
tail = tail.Plus(sp->p);
cnt++;
}
}
if(cnt == 0) return;
tail = tail.ScaledBy(1.0/cnt);
Vector gn = SS.GW.projRight.Cross(SS.GW.projUp);
Vector tip = tail.Plus((p->normal).WithMagnitude(40/SS.GW.scale));
Vector arrow = (p->normal).WithMagnitude(15/SS.GW.scale);
glColor3d(1, 1, 0);
glBegin(GL_LINES);
ssglVertex3v(tail);
ssglVertex3v(tip);
ssglVertex3v(tip);
ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6)));
ssglVertex3v(tip);
ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6)));
glEnd();
glEnable(GL_LIGHTING);
}
void ssglDepthRangeOffset(int units)
{
if(!DepthOffsetLocked) {
// The size of this step depends on the resolution of the Z buffer; for
// a 16-bit buffer, this should be fine.
double d = units/60000.0;
glDepthRange(0.1-d, 1-d);
}
}
void ssglDepthRangeLockToFront(bool yes)
{
if(yes) {
DepthOffsetLocked = true;
glDepthRange(0, 0);
} else {
DepthOffsetLocked = false;
ssglDepthRangeOffset(0);
}
}
void ssglDrawPixmap(const Pixmap &pixmap, Vector a, Vector b, Vector c, Vector d) {
glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
int format = pixmap.hasAlpha ? GL_RGBA : GL_RGB;
glTexImage2D(GL_TEXTURE_2D, 0, format, pixmap.width, pixmap.height, 0,
format, GL_UNSIGNED_BYTE, &pixmap.data[0]);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0.0, 0.0);
glVertex3d(CO(a));
glTexCoord2d(0.0, 1.0);
glVertex3d(CO(b));
glTexCoord2d(1.0, 1.0);
glVertex3d(CO(c));
glTexCoord2d(1.0, 0.0);
glVertex3d(CO(d));
glEnd();
glDisable(GL_TEXTURE_2D);
}
void ssglDrawPixmap(const Pixmap &pixmap, Point2d o, bool flip) {
double w = (double)pixmap.width, h = (double)pixmap.height;
if(!flip) {
Vector a = { o.x, o.y, 0.0 },
b = { o.x, o.y + h, 0.0 },
c = { o.x + w, o.y + h, 0.0 },
d = { o.x + w, o.y, 0.0 };
ssglDrawPixmap(pixmap, a, b, c, d);
} else {
Vector a = { o.x, o.y + h, 0.0 },
b = { o.x, o.y, 0.0 },
c = { o.x + w, o.y, 0.0 },
d = { o.x + w, o.y + h, 0.0 };
ssglDrawPixmap(pixmap, a, b, c, d);
}
}
//-----------------------------------------------------------------------------
// Bitmap font rendering
//-----------------------------------------------------------------------------
static BitmapFont BuiltinBitmapFont;
static void LoadBitmapFont() {
if(!BuiltinBitmapFont.IsEmpty()) return;
BuiltinBitmapFont = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz"));
BuiltinBitmapFont.AddGlyph(0xE000, LoadPNG("fonts/private/0-check-false.png"));
BuiltinBitmapFont.AddGlyph(0xE001, LoadPNG("fonts/private/1-check-true.png"));
BuiltinBitmapFont.AddGlyph(0xE002, LoadPNG("fonts/private/2-radio-false.png"));
BuiltinBitmapFont.AddGlyph(0xE003, LoadPNG("fonts/private/3-radio-true.png"));
BuiltinBitmapFont.AddGlyph(0xE004, LoadPNG("fonts/private/4-stipple-dot.png"));
BuiltinBitmapFont.AddGlyph(0xE005, LoadPNG("fonts/private/5-stipple-dash-long.png"));
BuiltinBitmapFont.AddGlyph(0xE006, LoadPNG("fonts/private/6-stipple-dash.png"));
BuiltinBitmapFont.AddGlyph(0xE007, LoadPNG("fonts/private/7-stipple-zigzag.png"));
// Unifont doesn't have a glyph for U+0020.
BuiltinBitmapFont.AddGlyph(0x20,
Pixmap({ 8, 16, 8*3, /*hasAlpha=*/false, std::vector<uint8_t>(8*16*3) }));
}
void ssglInitializeBitmapFont()
{
LoadBitmapFont();
glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
BitmapFont::TEXTURE_DIM, BitmapFont::TEXTURE_DIM,
0, GL_ALPHA, GL_UNSIGNED_BYTE, &BuiltinBitmapFont.texture[0]);
}
int ssglBitmapCharWidth(char32_t codepoint) {
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// These are special-cased because checkboxes predate support for 2 cell wide
// characters; and so all Printf() calls pad them with spaces.
return 1;
}
LoadBitmapFont();
return BuiltinBitmapFont.GetGlyph(codepoint).advanceCells;
}
double ssglBitmapCharQuad(char32_t codepoint, double x, double y)
{
double s0, t0, s1, t1;
size_t w, h;
if(BuiltinBitmapFont.LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h)) {
// LocateGlyph modified the texture, reload it.
glEnd();
ssglInitializeBitmapFont();
glBegin(GL_QUADS);
}
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// Special character, like a checkbox or a radio button
x -= 3;
}
glTexCoord2d(s0, t0);
glVertex2d(x, y - h);
glTexCoord2d(s0, t1);
glVertex2d(x, y);
glTexCoord2d(s1, t1);
glVertex2d(x + w, y);
glTexCoord2d(s1, t0);
glVertex2d(x + w, y - h);
return w;
}
void ssglBitmapText(const std::string &str, Vector p)
{
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
for(char32_t codepoint : ReadUTF8(str)) {
p.x += ssglBitmapCharQuad(codepoint, p.x, p.y);
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
//-----------------------------------------------------------------------------
// Bitmap font rendering
//-----------------------------------------------------------------------------
static VectorFont BuiltinVectorFont;
static void LoadVectorFont() {
if(!BuiltinVectorFont.IsEmpty()) return;
BuiltinVectorFont = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz"));
}
// Internally and in the UI, the vector font is sized using cap height.
#define FONT_SCALE(h) ((h)/(double)BuiltinVectorFont.capHeight)
double ssglStrCapHeight(double h)
{
return BuiltinVectorFont.capHeight *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrFontSize(double h)
{
return (BuiltinVectorFont.ascender - BuiltinVectorFont.descender) *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrWidth(const std::string &str, double h)
{
LoadVectorFont();
double width = 0;
for(char32_t codepoint : ReadUTF8(str)) {
width += BuiltinVectorFont.GetGlyph(codepoint).advanceWidth;
}
return width * FONT_SCALE(h) / SS.GW.scale;
}
static Vector PixelAlign(Vector v) {
v = SS.GW.ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
v = SS.GW.UnProjectPoint3(v);
return v;
}
static double DrawCharacter(const VectorFont::Glyph &glyph, Vector t, Vector o, Vector u, Vector v,
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
double advanceWidth = glyph.advanceWidth;
double actualWidth, offsetX;
if(gridFit) {
o.x += glyph.leftSideBearing;
offsetX = glyph.leftSideBearing;
actualWidth = glyph.boundingWidth;
if(actualWidth == 0) {
// Dot, "i", etc.
actualWidth = 1;
}
} else {
offsetX = 0;
actualWidth = advanceWidth;
}
Vector tt = t;
tt = tt.Plus(u.ScaledBy(o.x * scale));
tt = tt.Plus(v.ScaledBy(o.y * scale));
Vector tu = tt;
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
Vector tv = tt;
tv = tv.Plus(v.ScaledBy(BuiltinVectorFont.capHeight * scale));
if(gridFit) {
tt = PixelAlign(tt);
tu = PixelAlign(tu);
tv = PixelAlign(tv);
}
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
tv = tv.Minus(tt).ScaledBy(1.0 / BuiltinVectorFont.capHeight);
for(const VectorFont::Contour &contour : glyph.contours) {
Vector prevp;
bool penUp = true;
for(const Point2d &pt : contour.points) {
Vector p = tt;
p = p.Plus(tu.ScaledBy(pt.x - offsetX));
p = p.Plus(tv.ScaledBy(pt.y));
if(!penUp) fn(fndata, prevp, p);
prevp = p;
penUp = false;
}
}
return advanceWidth;
}
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
if(!fn) fn = LineDrawCallback;
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
// Perform grid-fitting only when the text is parallel to the view plane.
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
double scale = FONT_SCALE(h) / SS.GW.scale;
Vector o = {};
for(char32_t codepoint : ReadUTF8(str)) {
o.x += DrawCharacter(BuiltinVectorFont.GetGlyph(codepoint),
t, o, u, v, scale, fn, fndata, gridFit);
}
}
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double fh = ssglStrCapHeight(h);
double fw = ssglStrWidth(str, h);
t = t.Plus(u.ScaledBy(-fw/2));
t = t.Plus(v.ScaledBy(-fh/2));
ssglWriteText(str, h, t, u, v, fn, fndata);
}
};

View File

@ -910,9 +910,9 @@ void GraphicsWindow::MenuEdit(Command id) {
SS.MarkGroupDirty(ep->group);
} else if(s->constraint.v) {
Constraint *c = SK.GetConstraint(s->constraint);
Vector refp[2];
c->GetReferencePos(refp);
c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refp[0]).Minus(refp[0]));
std::vector<Vector> refs;
c->GetReferencePoints(SS.GW.GetCamera(), &refs);
c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refs[0]).Minus(refs[0]));
}
}
// Regenerate, with these points marked as dragged so that they

View File

@ -452,83 +452,137 @@ bool Group::IsMeshGroup() {
}
}
void Group::DrawDisplayItems(Group::Type t) {
RgbaColor specColor;
bool useSpecColor;
if(t == Type::DRAWING_3D || t == Type::DRAWING_WORKPLANE) {
// force the color to something dim
specColor = Style::Color(Style::DIM_SOLID);
useSpecColor = true;
} else {
useSpecColor = false; // use the model color
void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
if(!(SS.GW.showShaded || SS.GW.showHdnLines)) return;
switch(how) {
case DrawMeshAs::DEFAULT: {
// Force the shade color to something dim to not distract from
// the sketch.
Canvas::Fill fillFront = {};
if(!SS.GW.showShaded) {
fillFront.layer = Canvas::Layer::DEPTH_ONLY;
}
if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) {
fillFront.color = Style::Color(Style::DIM_SOLID);
}
Canvas::hFill hcfFront = canvas->GetFill(fillFront);
// The back faces are drawn in red; should never seem them, since we
// draw closed shells, so that's a debugging aid.
GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0f };
glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb);
// When we fill the mesh, we need to know which triangles are selected
// or hovered, in order to draw them differently.
uint32_t mh = 0, ms1 = 0, ms2 = 0;
hEntity he = SS.GW.hover.entity;
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
mh = he.v;
}
SS.GW.GroupSelection();
if(gs.faces > 0) ms1 = gs.face[0].v;
if(gs.faces > 1) ms2 = gs.face[1].v;
if(SS.GW.showShaded || SS.GW.showHdnLines) {
Canvas::hFill hcfBack = {};
if(SS.drawBackFaces && !displayMesh.isTransparent) {
// For debugging, draw the backs of the triangles in red, so that we
// notice when a shell is open
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
} else {
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
Canvas::Fill fillBack = {};
fillBack.layer = fillFront.layer;
fillBack.color = RgbaColor::FromFloat(1.0f, 0.1f, 0.1f);
hcfBack = canvas->GetFill(fillBack);
}
// Draw mesh edges, for debugging.
Canvas::hStroke hcsTriangle = {};
if(SS.GW.showMesh) {
Canvas::Stroke strokeTriangle = {};
strokeTriangle.zIndex = 1;
strokeTriangle.color = RgbaColor::FromFloat(0.0f, 1.0f, 0.0f);
strokeTriangle.width = 1;
hcsTriangle = canvas->GetStroke(strokeTriangle);
}
// Draw the shaded solid into the depth buffer for hidden line removal,
// and if we're actually going to display it, to the color buffer too.
glEnable(GL_LIGHTING);
if(!SS.GW.showShaded) glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
ssglFillMesh(useSpecColor, specColor, &displayMesh, mh, ms1, ms2);
if(!SS.GW.showShaded) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_LIGHTING);
canvas->DrawMesh(displayMesh, hcfFront, hcfBack, hcsTriangle);
break;
}
if(SS.GW.showEdges) {
Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp);
case DrawMeshAs::HOVERED: {
Canvas::Fill fill = {};
fill.color = Style::Color(Style::HOVERED);
fill.pattern = Canvas::FillPattern::CHECKERED_A;
fill.zIndex = 2;
Canvas::hFill hcf = canvas->GetFill(fill);
glDepthMask(GL_FALSE);
if(SS.GW.showHdnLines) {
ssglDepthRangeOffset(0);
glDepthFunc(GL_GREATER);
ssglDrawEdges(&displayEdges, /*endpointsToo=*/false, { Style::HIDDEN_EDGE });
ssglDrawOutlines(&displayOutlines, projDir, { Style::HIDDEN_EDGE });
glDepthFunc(GL_LEQUAL);
std::vector<uint32_t> faces;
hEntity he = SS.GW.hover.entity;
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
faces.push_back(he.v);
}
ssglDepthRangeOffset(2);
ssglDrawEdges(&displayEdges, /*endpointsToo=*/false, { Style::SOLID_EDGE });
if(SS.GW.showOutlines) {
ssglDrawOutlines(&displayOutlines, projDir, { Style::OUTLINE });
} else {
ssglDrawOutlines(&displayOutlines, projDir, { Style::SOLID_EDGE });
}
glDepthMask(GL_TRUE);
canvas->DrawFaces(displayMesh, faces, hcf);
break;
}
if(SS.GW.showMesh) ssglDebugMesh(&displayMesh);
case DrawMeshAs::SELECTED: {
Canvas::Fill fill = {};
fill.color = Style::Color(Style::SELECTED);
fill.pattern = Canvas::FillPattern::CHECKERED_B;
fill.zIndex = 1;
Canvas::hFill hcf = canvas->GetFill(fill);
std::vector<uint32_t> faces;
SS.GW.GroupSelection();
if(gs.faces > 0) faces.push_back(gs.face[0].v);
if(gs.faces > 1) faces.push_back(gs.face[1].v);
canvas->DrawFaces(displayMesh, faces, hcf);
break;
}
}
}
void Group::Draw() {
void Group::Draw(Canvas *canvas) {
// Everything here gets drawn whether or not the group is hidden; we
// can control this stuff independently, with show/hide solids, edges,
// mesh, etc.
GenerateDisplayItems();
DrawDisplayItems(type);
DrawMesh(DrawMeshAs::DEFAULT, canvas);
if(!SS.checkClosedContour) return;
if(SS.GW.showEdges) {
Canvas::Stroke strokeEdge = {};
strokeEdge.zIndex = 1;
strokeEdge.color = Style::Color(Style::SOLID_EDGE);
strokeEdge.width = Style::Width(Style::SOLID_EDGE);
Canvas::hStroke hcsEdge = canvas->GetStroke(strokeEdge);
canvas->DrawEdges(displayEdges, hcsEdge);
if(SS.GW.showHdnLines) {
Canvas::Stroke strokeHidden = strokeEdge;
strokeHidden.layer = Canvas::Layer::OCCLUDED;
strokeHidden.width = Style::Width(Style::HIDDEN_EDGE);
strokeHidden.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE });
strokeHidden.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE });
Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden);
canvas->DrawEdges(displayEdges, hcsHidden);
canvas->DrawOutlines(displayOutlines, hcsHidden);
}
if(SS.GW.showOutlines) {
Canvas::Stroke strokeOutline = strokeEdge;
strokeOutline.color = Style::Color(Style::OUTLINE);
strokeOutline.width = Style::Width(Style::OUTLINE);
Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline);
canvas->DrawOutlines(displayOutlines, hcsOutline);
} else {
canvas->DrawOutlines(displayOutlines, hcsEdge);
}
}
}
void Group::DrawPolyError(Canvas *canvas) {
const Camera &camera = canvas->GetCamera();
Canvas::Stroke strokeUnclosed = {};
strokeUnclosed.color = Style::Color(Style::DRAW_ERROR).WithAlpha(50);
strokeUnclosed.width = Style::Width(Style::DRAW_ERROR);
Canvas::hStroke hcsUnclosed = canvas->GetStroke(strokeUnclosed);
Canvas::Stroke strokeError = {};
strokeError.layer = Canvas::Layer::FRONT;
strokeError.color = Style::Color(Style::DRAW_ERROR);
Canvas::hStroke hcsError = canvas->GetStroke(strokeError);
double textHeight = Style::DefaultTextHeight() / camera.scale;
// And finally show the polygons too, and any errors if it's not possible
// to assemble the lines into closed polygons.
@ -536,28 +590,16 @@ void Group::Draw() {
// Report this error only in sketch-in-workplane groups; otherwise
// it's just a nuisance.
if(type == Type::DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
ssglColorRGBa(Style::Color(Style::DRAW_ERROR), 0.2);
ssglLineWidth (Style::Width(Style::DRAW_ERROR));
glBegin(GL_LINES);
ssglVertex3v(polyError.notClosedAt.a);
ssglVertex3v(polyError.notClosedAt.b);
glEnd();
ssglColorRGB(Style::Color(Style::DRAW_ERROR));
ssglWriteText("not closed contour, or not all same style!",
Style::DefaultTextHeight(),
polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
canvas->DrawVectorText("not closed contour, or not all same style!", textHeight,
polyError.notClosedAt.b, camera.projRight, camera.projUp,
hcsError);
canvas->DrawLine(polyError.notClosedAt.a, polyError.notClosedAt.b, hcsUnclosed);
}
} else if(polyError.how == PolyError::NOT_COPLANAR ||
polyError.how == PolyError::SELF_INTERSECTING ||
polyError.how == PolyError::ZERO_LEN_EDGE)
{
polyError.how == PolyError::ZERO_LEN_EDGE) {
// These errors occur at points, not lines
if(type == Type::DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
ssglColorRGB(Style::Color(Style::DRAW_ERROR));
const char *msg;
if(polyError.how == PolyError::NOT_COPLANAR) {
msg = "points not all coplanar!";
@ -566,52 +608,41 @@ void Group::Draw() {
} else {
msg = "zero-length edge!";
}
ssglWriteText(msg, Style::DefaultTextHeight(),
polyError.errorPointAt, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
canvas->DrawVectorText(msg, textHeight,
polyError.errorPointAt, camera.projRight, camera.projUp,
hcsError);
}
} else {
// The contours will get filled in DrawFilledPaths.
}
}
void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) {
SPolygon sp = {};
sbls->MakePwlInto(&sp);
ssglDepthRangeOffset(1);
ssglFillPolygon(&sp);
ssglDepthRangeOffset(0);
sp.Clear();
}
void Group::DrawFilledPaths(Canvas *canvas) {
for(const SBezierLoopSet &sbls : bezierLoops.l) {
if(sbls.l.n == 0 || sbls.l.elem[0].l.n == 0) continue;
void Group::DrawFilledPaths() {
SBezierLoopSet *sbls;
SBezierLoopSetSet *sblss = &bezierLoops;
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
if(sbls->l.n == 0 || sbls->l.elem[0].l.n == 0) continue;
// In an assembled loop, all the styles should be the same; so doesn't
// matter which one we grab.
SBezier *sb = &(sbls->l.elem[0].l.elem[0]);
hStyle hs = { (uint32_t)sb->auxA };
Style *s = Style::Get(hs);
SBezier *sb = &(sbls.l.elem[0].l.elem[0]);
Style *s = Style::Get({ (uint32_t)sb->auxA });
Canvas::Fill fill = {};
fill.zIndex = 1;
if(s->filled) {
// This is a filled loop, where the user specified a fill color.
ssglColorRGBa(s->fillColor, 1);
FillLoopSetAsPolygon(sbls);
} else {
if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour &&
polyError.how == PolyError::GOOD)
{
fill.color = s->color;
} else if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour &&
polyError.how == PolyError::GOOD) {
// If this is the active group, and we are supposed to check
// for closed contours, and we do indeed have a closed and
// non-intersecting contour, then fill it dimly.
ssglColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5);
ssglDepthRangeOffset(1);
FillLoopSetAsPolygon(sbls);
ssglDepthRangeOffset(0);
}
}
fill.color = Style::Color(Style::CONTOUR_FILL).WithAlpha(127);
} else continue;
Canvas::hFill hcf = canvas->GetFill(fill);
SPolygon sp = {};
sbls.MakePwlInto(&sp);
canvas->DrawPolygon(sp, hcf);
}
}

View File

@ -2,11 +2,6 @@
#include "libdxfrw.h"
#include "libdwgr.h"
#ifdef WIN32
// Conflicts with DRW::TEXT.
# undef TEXT
#endif
namespace SolveSpace {
static std::string ToUpper(std::string str) {

View File

@ -583,8 +583,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
Request *r = SK.GetRequest(gs.entity[0].request());
if(r->type == Request::Type::CUBIC || r->type == Request::Type::CUBIC_PERIODIC) {
Entity *e = SK.GetEntity(gs.entity[0]);
e->GetDistance(Point2d::From(x, y));
addAfterPoint = e->dogd.data;
addAfterPoint = e->GetPositionOfPoint(GetCamera(), Point2d::From(x, y));
ssassert(addAfterPoint != -1, "Expected a nearest bezier point to be located");
// Skip derivative point.
if(r->type == Request::Type::CUBIC) addAfterPoint++;
@ -1253,7 +1252,7 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {
return;
}
Vector p3 = c->GetLabelPos();
Vector p3 = c->GetLabelPos(GetCamera());
Point2d p2 = ProjectPoint(p3);
std::string editValue;
@ -1300,7 +1299,7 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {
hStyle hs = c->disp.style;
if(hs.v == 0) hs.v = Style::CONSTRAINT;
ShowGraphicsEditControl((int)p2.x, (int)p2.y,
(int)(ssglStrFontSize(Style::TextHeight(hs)) * scale),
(int)(VectorFont::Builtin()->GetHeight(Style::TextHeight(hs))),
editMinWidthChar, editValue);
}
}

View File

@ -19,12 +19,6 @@
using SolveSpace::dbp;
#define GL_CHECK() \
do { \
int err = (int)glGetError(); \
if(err) dbp("%s:%d: glGetError() == 0x%X", __FILE__, __LINE__, err); \
} while (0)
/* Settings */
namespace SolveSpace {
@ -116,6 +110,8 @@ void SolveSpace::ScheduleLater() {
/* OpenGL view */
const bool SolveSpace::FLIP_FRAMEBUFFER = false;
@interface GLViewWithEditor : NSView
- (void)drawGL;
@ -200,9 +196,8 @@ CONVERT(Rect)
offscreen->begin(width, height);
[self drawGL];
GL_CHECK();
uint8_t *pixels = offscreen->end(![self isFlipped]);
uint8_t *pixels = offscreen->end();
CGDataProviderRef provider = CGDataProviderCreateWithData(
NULL, pixels, width * height * 4, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();

View File

@ -12,7 +12,7 @@
#include "gloffscreen.h"
#include "solvespace.h"
GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _height(0) {
GLOffscreen::GLOffscreen() : _pixels(NULL), _width(0), _height(0) {
#ifndef __APPLE__
ssassert(glewInit() == GLEW_OK, "Unexpected GLEW failure");
#endif
@ -26,7 +26,6 @@ GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _heigh
GLOffscreen::~GLOffscreen() {
delete[] _pixels;
delete[] _pixels_inv;
glDeleteRenderbuffersEXT(1, &_depth_renderbuffer);
glDeleteRenderbuffersEXT(1, &_color_renderbuffer);
glDeleteFramebuffersEXT(1, &_framebuffer);
@ -35,10 +34,7 @@ GLOffscreen::~GLOffscreen() {
bool GLOffscreen::begin(int width, int height) {
if(_width != width || _height != height) {
delete[] _pixels;
delete[] _pixels_inv;
_pixels = new uint32_t[width * height * 4];
_pixels_inv = new uint32_t[width * height * 4];
_pixels = new uint8_t[width * height * 4];
_width = width;
_height = height;
@ -63,22 +59,16 @@ bool GLOffscreen::begin(int width, int height) {
return false;
}
uint8_t *GLOffscreen::end(bool flip) {
uint8_t *GLOffscreen::end() {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glReadPixels(0, 0, _width, _height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels_inv);
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels);
#else
glReadPixels(0, 0, _width, _height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels_inv);
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels);
#endif
if(flip) {
/* in OpenGL coordinates, bottom is zero Y */
for(int i = 0; i < _height; i++)
memcpy(&_pixels[_width * i], &_pixels_inv[_width * (_height - i - 1)], _width * 4);
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return (uint8_t*) (flip ? _pixels : _pixels_inv);
return _pixels;
}

View File

@ -24,12 +24,12 @@ public:
the pixel format is ARGB32 with top row at index 0 if
flip is true and bottom row at index 0 if flip is false.
the returned array is valid until the next call to begin() */
uint8_t *end(bool flip = true);
uint8_t *end();
private:
unsigned int _framebuffer;
unsigned int _color_renderbuffer, _depth_renderbuffer;
uint32_t *_pixels, *_pixels_inv;
uint8_t *_pixels;
int _width, _height;
};

View File

@ -224,12 +224,7 @@ void ScheduleLater() {
/* GL wrapper */
#define GL_CHECK() \
do { \
int err = (int)glGetError(); \
if(err) dbp("%s:%d: glGetError() == 0x%X %s", \
__FILE__, __LINE__, err, gluErrorString(err)); \
} while (0)
const bool FLIP_FRAMEBUFFER = true;
class GlWidget : public Gtk::DrawingArea {
public:
@ -327,8 +322,6 @@ protected:
"Cannot allocate offscreen rendering buffer");
on_gl_draw();
glFlush();
GL_CHECK();
Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(
_offscreen->end(), Cairo::FORMAT_RGB24,
@ -1631,10 +1624,11 @@ int main(int argc, char** argv) {
CnfLoad();
SolveSpace::Pixmap icon = LoadPNG("freedesktop/solvespace-48x48.png");
Glib::RefPtr<Gdk::Pixbuf> icon_gdk =
Gdk::Pixbuf::create_from_data(&icon.data[0], Gdk::COLORSPACE_RGB,
icon.hasAlpha, 8, icon.width, icon.height, icon.stride);
auto icon = LoadPng("freedesktop/solvespace-48x48.png");
auto icon_gdk =
Gdk::Pixbuf::create_from_data(&icon->data[0], Gdk::COLORSPACE_RGB,
icon->format == SolveSpace::Pixmap::Format::RGBA, 8,
icon->width, icon->height, icon->stride);
TW.reset(new TextWindowGtk);
GW.reset(new GraphicsWindowGtk);

View File

@ -9,6 +9,9 @@
//-----------------------------------------------------------------------------
#include <time.h>
#include <execinfo.h>
#ifdef __APPLE__
# include <strings.h> // for strcasecmp
#endif
#include "solvespace.h"
@ -51,6 +54,17 @@ void assert_failure(const char *file, unsigned line, const char *function,
abort();
}
bool PathEqual(const std::string &a, const std::string &b)
{
#if defined(__APPLE__)
// Case-sensitivity is actually per-volume on OS X,
// but it is tedious to implement and test for little benefit.
return !strcasecmp(a.c_str(), b.c_str());
#else
return a == b;
#endif
}
FILE *ssfopen(const std::string &filename, const char *mode)
{
ssassert(filename.length() == strlen(filename.c_str()),

View File

@ -6,14 +6,16 @@
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include <time.h>
#include "config.h"
#include "solvespace.h"
// Include after solvespace.h to avoid identifier clashes.
#include <windows.h>
#include <shellapi.h>
#include <commctrl.h>
#include <commdlg.h>
#include "solvespace.h"
#include "config.h"
#ifdef HAVE_SPACEWARE
# include <si.h>
# include <siapp.h>
@ -725,6 +727,8 @@ void SolveSpace::ShowTextWindow(bool visible)
ShowWindow(TextWnd, visible ? SW_SHOWNOACTIVATE : SW_HIDE);
}
const bool SolveSpace::FLIP_FRAMEBUFFER = false;
static void CreateGlContext(HWND hwnd, HGLRC *glrc)
{
HDC hdc = GetDC(hwnd);
@ -1378,13 +1382,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
ShowWindow(TextWnd, SW_SHOWNOACTIVATE);
ShowWindow(GraphicsWnd, SW_SHOW);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers(GetDC(GraphicsWnd));
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers(GetDC(GraphicsWnd));
// Create the heaps for all dynamic memory (AllocTemporary, MemAlloc)
InitHeaps();

View File

@ -8,6 +8,9 @@
//-----------------------------------------------------------------------------
#include "solvespace.h"
// Include after solvespace.h to avoid identifier clashes.
#include <windows.h>
namespace SolveSpace {
static HANDLE PermHeap, TempHeap;
@ -78,6 +81,16 @@ std::wstring Widen(const std::string &in)
return out;
}
bool PathEqual(const std::string &a, const std::string &b)
{
// Case-sensitivity is actually per-volume on Windows,
// but it is tedious to implement and test for little benefit.
std::wstring wa = Widen(a), wb = Widen(b);
return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/
[](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); });
}
FILE *ssfopen(const std::string &filename, const char *mode)
{
// Prepend \\?\ UNC prefix unless already an UNC path.

View File

@ -207,8 +207,6 @@ public:
static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge,
Vector nnp, Vector out);
static SBsp2 *Alloc();
void DebugDraw(Vector n, double d) const;
};
class SBsp3 {
@ -240,8 +238,6 @@ public:
void InsertInPlane(bool pos2, STriangle *tr, SMesh *m);
void GenerateInPaintOrder(SMesh *m) const;
void DebugDraw() const;
};
class SMesh {

353
src/render/render.cpp Normal file
View File

@ -0,0 +1,353 @@
//-----------------------------------------------------------------------------
// Backend-agnostic rendering interface, and various backends we use.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "solvespace.h"
namespace SolveSpace {
//-----------------------------------------------------------------------------
// Camera transformations.
//-----------------------------------------------------------------------------
Point2d Camera::ProjectPoint(Vector p) const {
Vector p3 = ProjectPoint3(p);
Point2d p2 = { p3.x, p3.y };
return p2;
}
Vector Camera::ProjectPoint3(Vector p) const {
double w;
Vector r = ProjectPoint4(p, &w);
return r.ScaledBy(scale/w);
}
Vector Camera::ProjectPoint4(Vector p, double *w) const {
p = p.Plus(offset);
Vector r;
r.x = p.Dot(projRight);
r.y = p.Dot(projUp);
r.z = p.Dot(projUp.Cross(projRight));
*w = 1 + r.z*tangent*scale;
return r;
}
Vector Camera::UnProjectPoint(Point2d p) const {
Vector orig = offset.ScaledBy(-1);
// Note that we're ignoring the effects of perspective. Since our returned
// point has the same component normal to the screen as the offset, it
// will have z = 0 after the rotation is applied, thus w = 1. So this is
// correct.
orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
projUp. ScaledBy(p.y / scale));
return orig;
}
Vector Camera::UnProjectPoint3(Vector p) const {
p.z = p.z / (scale - p.z * tangent * scale);
double w = 1 + p.z * tangent * scale;
p.x *= w / scale;
p.y *= w / scale;
Vector orig = offset.ScaledBy(-1);
orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
projUp. ScaledBy(p.y).Plus(
projRight.Cross(projUp). ScaledBy(p.z)));
return orig;
}
Vector Camera::VectorFromProjs(Vector rightUpForward) const {
Vector n = projRight.Cross(projUp);
Vector r = (projRight.ScaledBy(rightUpForward.x));
r = r.Plus(projUp.ScaledBy(rightUpForward.y));
r = r.Plus(n.ScaledBy(rightUpForward.z));
return r;
}
Vector Camera::AlignToPixelGrid(Vector v) const {
if(!hasPixels) return v;
v = ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
return UnProjectPoint3(v);
}
void Camera::LoadIdentity() {
offset = { 0.0, 0.0, 0.0 };
projRight = { 1.0, 0.0, 0.0 };
projUp = { 0.0, 1.0, 0.0 };
scale = 1.0;
tangent = 0.0;
}
void Camera::NormalizeProjectionVectors() {
if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0);
}
Vector norm = projRight.Cross(projUp);
// If projRight and projUp somehow ended up parallel, then pick an
// arbitrary projUp normal to projRight.
if(norm.Magnitude() < LENGTH_EPS) {
norm = projRight.Normal(0);
}
projUp = norm.Cross(projRight);
projUp = projUp.WithMagnitude(1);
projRight = projRight.WithMagnitude(1);
}
//-----------------------------------------------------------------------------
// Stroke and fill caching.
//-----------------------------------------------------------------------------
bool Canvas::Stroke::Equals(const Stroke &other) const {
return (layer == other.layer &&
zIndex == other.zIndex &&
color.Equals(other.color) &&
width == other.width &&
stipplePattern == other.stipplePattern &&
stippleScale == other.stippleScale);
}
bool Canvas::Fill::Equals(const Fill &other) const {
return (layer == other.layer &&
zIndex == other.zIndex &&
color.Equals(other.color) &&
pattern == other.pattern);
}
Canvas::hStroke Canvas::GetStroke(const Stroke &stroke) {
for(const Stroke &s : strokes) {
if(s.Equals(stroke)) return s.h;
}
Stroke strokeCopy = stroke;
return strokes.AddAndAssignId(&strokeCopy);
}
Canvas::hFill Canvas::GetFill(const Fill &fill) {
for(const Fill &f : fills) {
if(f.Equals(fill)) return f.h;
}
Fill fillCopy = fill;
return fills.AddAndAssignId(&fillCopy);
}
//-----------------------------------------------------------------------------
// A wrapper around Canvas that simplifies drawing UI in screen coordinates
//-----------------------------------------------------------------------------
void UiCanvas::DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width) {
Vector va = { (double)x1 + 0.5, (double)Flip(y1) + 0.5, 0.0 },
vb = { (double)x2 + 0.5, (double)Flip(y2) + 0.5, 0.0 };
Canvas::Stroke stroke = {};
stroke.layer = Canvas::Layer::FRONT;
stroke.width = (double)width;
stroke.color = color;
Canvas::hStroke hcs = canvas->GetStroke(stroke);
canvas->DrawLine(va, vb, hcs);
}
void UiCanvas::DrawRect(int l, int r, int t, int b,
RgbaColor fillColor, RgbaColor outlineColor) {
Vector va = { (double)l + 0.5, (double)Flip(b) + 0.5, 0.0 },
vb = { (double)l + 0.5, (double)Flip(t) + 0.5, 0.0 },
vc = { (double)r + 0.5, (double)Flip(t) + 0.5, 0.0 },
vd = { (double)r + 0.5, (double)Flip(b) + 0.5, 0.0 };
if(!fillColor.IsEmpty()) {
Canvas::Fill fill = {};
fill.layer = Canvas::Layer::FRONT;
fill.color = fillColor;
Canvas::hFill hcf = canvas->GetFill(fill);
canvas->DrawQuad(va, vb, vc, vd, hcf);
}
if(!outlineColor.IsEmpty()) {
Canvas::Stroke stroke = {};
stroke.layer = Canvas::Layer::FRONT;
stroke.width = 1.0;
stroke.color = outlineColor;
Canvas::hStroke hcs = canvas->GetStroke(stroke);
canvas->DrawLine(va, vb, hcs);
canvas->DrawLine(vb, vc, hcs);
canvas->DrawLine(vc, vd, hcs);
canvas->DrawLine(vd, va, hcs);
}
}
void UiCanvas::DrawPixmap(std::shared_ptr<const Pixmap> pm, int x, int y) {
Canvas::Fill fill = {};
fill.layer = Canvas::Layer::FRONT;
fill.color = { 0, 0, 0, 255 };
Canvas::hFill hcf = canvas->GetFill(fill);
canvas->DrawPixmap(pm,
{ (double)x, (double)(flip ? Flip(y) - pm->height : y), 0.0 },
{ (double)pm->width, 0.0, 0.0 },
{ 0.0, (double)pm->height, 0.0 },
{ 0.0, 1.0 },
{ 1.0, 0.0 },
hcf);
}
void UiCanvas::DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color) {
BitmapFont *font = BitmapFont::Builtin();
Canvas::Fill fill = {};
fill.layer = Canvas::Layer::FRONT;
fill.color = color;
Canvas::hFill hcf = canvas->GetFill(fill);
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// Special character, like a checkbox or a radio button
x -= 3;
}
double s0, t0, s1, t1;
size_t w, h;
if(font->LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h)) {
// LocateGlyph modified the texture, reload it.
canvas->InvalidatePixmap(font->texture);
}
canvas->DrawPixmap(font->texture,
{ (double)x, (double)Flip(y), 0.0 },
{ (double)w, 0.0, 0.0 },
{ 0.0, (double) h, 0.0 },
{ s0, t1 },
{ s1, t0 },
hcf);
}
void UiCanvas::DrawBitmapText(const std::string &str, int x, int y, RgbaColor color) {
BitmapFont *font = BitmapFont::Builtin();
for(char32_t codepoint : ReadUTF8(str)) {
DrawBitmapChar(codepoint, x, y, color);
x += font->GetWidth(codepoint) * 8;
}
}
//-----------------------------------------------------------------------------
// A canvas that performs picking against drawn geometry.
//-----------------------------------------------------------------------------
void ObjectPicker::DoCompare(double distance, int zIndex, int comparePosition) {
if(distance > selRadius) return;
if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) {
minDistance = distance;
maxZIndex = zIndex;
position = comparePosition;
}
}
void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
int zIndex, int comparePosition) {
Point2d corners[4] = {
camera.ProjectPoint(a),
camera.ProjectPoint(b),
camera.ProjectPoint(c),
camera.ProjectPoint(d)
};
double minNegative = VERY_NEGATIVE,
maxPositive = VERY_POSITIVE;
for(int i = 0; i < 4; i++) {
Point2d ap = corners[i],
bp = corners[(i + 1) % 4];
double distance = point.DistanceToLineSigned(ap, bp.Minus(ap), /*asSegment=*/true);
if(distance < 0) minNegative = std::max(minNegative, distance);
if(distance > 0) maxPositive = std::min(maxPositive, distance);
}
bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE);
if(insideQuad) {
DoCompare(0.0, zIndex, comparePosition);
} else {
double distance = std::min(fabs(minNegative), fabs(maxPositive));
DoCompare(distance, zIndex, comparePosition);
}
}
void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
Stroke *stroke = strokes.FindById(hcs);
Point2d ap = camera.ProjectPoint(a);
Point2d bp = camera.ProjectPoint(b);
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
DoCompare(distance - stroke->width / 2.0, stroke->zIndex);
}
void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
Stroke *stroke = strokes.FindById(hcs);
for(const SEdge &e : el.l) {
Point2d ap = camera.ProjectPoint(e.a);
Point2d bp = camera.ProjectPoint(e.b);
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
DoCompare(distance - stroke->width / 2.0, stroke->zIndex, e.auxB);
}
}
void ObjectPicker::DrawOutlines(const SOutlineList &ol, hStroke hcs) {
ssassert(false, "Not implemented");
}
void ObjectPicker::DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) {
Stroke *stroke = strokes.FindById(hcs);
double w = VectorFont::Builtin()-> GetWidth(height, text),
h = VectorFont::Builtin()->GetHeight(height);
DoQuad(o,
o.Plus(v.ScaledBy(h)),
o.Plus(u.ScaledBy(w)).Plus(v.ScaledBy(h)),
o.Plus(u.ScaledBy(w)),
stroke->zIndex);
}
void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) {
Fill *fill = fills.FindById(hcf);
DoQuad(a, b, c, d, fill->zIndex);
}
void ObjectPicker::DrawPoint(const Vector &o, double s, hFill hcf) {
Fill *fill = fills.FindById(hcf);
double distance = point.DistanceTo(camera.ProjectPoint(o)) - s / 2;
DoCompare(distance, fill->zIndex);
}
void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) {
ssassert(false, "Not implemented");
}
void ObjectPicker::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) {
ssassert(false, "Not implemented");
}
void ObjectPicker::DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) {
ssassert(false, "Not implemented");
}
void ObjectPicker::DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, Canvas::hFill hcf) {
ssassert(false, "Not implemented");
}
bool ObjectPicker::Pick(std::function<void()> drawFn) {
minDistance = VERY_POSITIVE;
drawFn();
return minDistance < selRadius;
}
}

254
src/render/render.h Normal file
View File

@ -0,0 +1,254 @@
//-----------------------------------------------------------------------------
// Backend-agnostic rendering interface, and various backends we use.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_RENDER_H
#define SOLVESPACE_RENDER_H
enum class StipplePattern : uint32_t;
// A mapping from 3d sketch coordinates to 2d screen coordinates, using
// an axonometric projection.
class Camera {
public:
size_t width, height;
Vector offset;
Vector projRight;
Vector projUp;
double scale;
double tangent;
bool hasPixels;
bool IsPerspective() const { return tangent != 0.0; }
Point2d ProjectPoint(Vector p) const;
Vector ProjectPoint3(Vector p) const;
Vector ProjectPoint4(Vector p, double *w) const;
Vector UnProjectPoint(Point2d p) const;
Vector UnProjectPoint3(Vector p) const;
Vector VectorFromProjs(Vector rightUpForward) const;
Vector AlignToPixelGrid(Vector v) const;
void LoadIdentity();
void NormalizeProjectionVectors();
};
// A description of scene lighting.
class Lighting {
public:
RgbaColor backgroundColor;
double ambientIntensity;
double lightIntensity[2];
Vector lightDirection[2];
};
// An interface for populating a drawing area with geometry.
class Canvas {
public:
// Stroke and fill styles are addressed with handles to be able to quickly
// group geometry into indexed draw calls.
class hStroke {
public:
uint32_t v;
};
class hFill {
public:
uint32_t v;
};
// The layer of a geometry describes how it occludes other geometry.
// Within a layer, geometry with higher z-index occludes geometry with lower z-index,
// or geometry drawn earlier if z-indexes match.
enum class Layer {
NORMAL, // Occluded by geometry with lower Z coordinate
OCCLUDED, // Only drawn over geometry with lower Z coordinate
DEPTH_ONLY, // Like NORMAL, but only affects future occlusion, not color
BACK, // Always drawn below all other geometry
FRONT, // Always drawn above all other geometry
};
class Stroke {
public:
hStroke h;
Layer layer;
int zIndex;
RgbaColor color;
double width;
StipplePattern stipplePattern;
double stippleScale;
bool Equals(const Stroke &other) const;
};
enum class FillPattern {
SOLID, CHECKERED_A, CHECKERED_B
};
class Fill {
public:
hFill h;
Layer layer;
int zIndex;
RgbaColor color;
FillPattern pattern;
bool Equals(const Fill &other) const;
};
IdList<Stroke, hStroke> strokes;
IdList<Fill, hFill> fills;
Canvas() : strokes(), fills() {};
hStroke GetStroke(const Stroke &stroke);
hFill GetFill(const Fill &fill);
virtual const Camera &GetCamera() const = 0;
virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0;
virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0;
virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0;
virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs) = 0;
virtual void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) = 0;
virtual void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) = 0;
virtual void DrawPoint(const Vector &o, double d, hFill hcf) = 0;
virtual void DrawPolygon(const SPolygon &p, hFill hcf) = 0;
virtual void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {},
hStroke hcsTriangles = {}) = 0;
virtual void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) = 0;
virtual void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) = 0;
virtual void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) = 0;
};
// A wrapper around Canvas that simplifies drawing UI in screen coordinates.
class UiCanvas {
public:
Canvas *canvas;
bool flip;
UiCanvas() : canvas(), flip() {};
void DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width = 1);
void DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor);
void DrawPixmap(std::shared_ptr<const Pixmap> pm, int x, int y);
void DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color);
void DrawBitmapText(const std::string &str, int x, int y, RgbaColor color);
int Flip(int y) const { return flip ? (int)canvas->GetCamera().height - y : y; }
};
// A canvas that performs picking against drawn geometry.
class ObjectPicker : public Canvas {
public:
Camera camera;
// Configuration.
Point2d point;
double selRadius;
// Picking state.
double minDistance;
int maxZIndex;
uint32_t position;
ObjectPicker() : camera(), point(), selRadius(),
minDistance(), maxZIndex(), position() {}
const Camera &GetCamera() const override { return camera; }
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override;
void DrawEdges(const SEdgeList &el, hStroke hcs) override;
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; }
void DrawOutlines(const SOutlineList &ol, hStroke hcs) override;
void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) override;
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override;
void DrawPoint(const Vector &o, double s, hFill hcf) override;
void DrawPolygon(const SPolygon &p, hFill hcf) override;
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) override;
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {}
void DoCompare(double distance, int zIndex, int comparePosition = 0);
void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
int zIndex, int comparePosition = 0);
bool Pick(std::function<void()> drawFn);
};
// A canvas that uses the core OpenGL profile, for desktop systems.
class OpenGl1Renderer : public Canvas {
public:
Camera camera;
Lighting lighting;
// Cached OpenGL state.
struct {
bool drawing;
unsigned mode; // GLenum, but we don't include GL.h globally
hStroke hcs;
Stroke *stroke;
hFill hcf;
Fill *fill;
std::weak_ptr<const Pixmap> texture;
} current;
OpenGl1Renderer() : camera(), lighting(), current() {}
const Camera &GetCamera() const override { return camera; }
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override;
void DrawEdges(const SEdgeList &el, hStroke hcs) override;
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; }
void DrawOutlines(const SOutlineList &ol, hStroke hcs) override;
void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) override;
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override;
void DrawPoint(const Vector &o, double s, hFill hcf) override;
void DrawPolygon(const SPolygon &p, hFill hcf) override;
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) override;
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override;
void SelectPrimitive(unsigned mode);
void UnSelectPrimitive();
Stroke *SelectStroke(hStroke hcs);
Fill *SelectFill(hFill hcf);
void SelectTexture(std::shared_ptr<const Pixmap> pm);
void DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v);
void DoFatLine(const Vector &a, const Vector &b, double width);
void DoLine(const Vector &a, const Vector &b, hStroke hcs);
void DoPoint(Vector p, double radius);
void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs);
void UpdateProjection(bool flip = FLIP_FRAMEBUFFER);
void BeginFrame();
void EndFrame();
std::shared_ptr<Pixmap> ReadFrame();
static void GetIdent(const char **vendor, const char **renderer, const char **version);
};
#endif

720
src/render/rendergl1.cpp Normal file
View File

@ -0,0 +1,720 @@
//-----------------------------------------------------------------------------
// OpenGL 1 based rendering interface.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "config.h"
#include "solvespace.h"
#ifdef WIN32
// Include after solvespace.h to avoid identifier clashes.
# include <windows.h> // required by GL headers
#endif
#ifdef __APPLE__
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
#else
# include <GL/gl.h>
# include <GL/glu.h>
#endif
namespace SolveSpace {
//-----------------------------------------------------------------------------
// Thin wrappers around OpenGL functions to fix bugs, adapt them to our
// data structures, etc.
//-----------------------------------------------------------------------------
static inline void ssglNormal3v(Vector n) {
glNormal3d(n.x, n.y, n.z);
}
static inline void ssglVertex3v(Vector v) {
glVertex3d(v.x, v.y, v.z);
}
void ssglLineWidth(double width) {
// Intel GPUs with Mesa on *nix render thin lines poorly.
static bool workaroundChecked, workaroundEnabled;
if(!workaroundChecked) {
// ssglLineWidth can be called before GL is initialized
if(glGetString(GL_VENDOR)) {
workaroundChecked = true;
if(!strcmp((char*)glGetString(GL_VENDOR), "Intel Open Source Technology Center"))
workaroundEnabled = true;
}
}
if(workaroundEnabled && width < 1.6)
width = 1.6;
glLineWidth((GLfloat)width);
}
static inline void ssglColorRGBA(RgbaColor color) {
glColor4d(color.redF(), color.greenF(), color.blueF(), color.alphaF());
}
static inline void ssglMaterialRGBA(GLenum side, RgbaColor color) {
GLfloat mpb[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() };
glMaterialfv(side, GL_AMBIENT_AND_DIFFUSE, mpb);
}
static void ssglDepthRange(Canvas::Layer layer, int zIndex) {
switch(layer) {
case Canvas::Layer::NORMAL:
case Canvas::Layer::FRONT:
case Canvas::Layer::BACK:
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
break;
case Canvas::Layer::DEPTH_ONLY:
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
break;
case Canvas::Layer::OCCLUDED:
glDepthFunc(GL_GREATER);
glDepthMask(GL_FALSE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
break;
}
switch(layer) {
case Canvas::Layer::FRONT:
glDepthRange(0.0, 0.0);
break;
case Canvas::Layer::BACK:
glDepthRange(1.0, 1.0);
break;
case Canvas::Layer::NORMAL:
case Canvas::Layer::DEPTH_ONLY:
case Canvas::Layer::OCCLUDED:
// The size of this step depends on the resolution of the Z buffer; for
// a 16-bit buffer, this should be fine.
double offset = 1.0 / (65535 * 0.8) * zIndex;
glDepthRange(0.1 - offset, 1.0 - offset);
break;
}
}
static void ssglFillPattern(Canvas::FillPattern pattern) {
static bool Init;
static GLubyte MaskA[(32*32)/8];
static GLubyte MaskB[(32*32)/8];
if(!Init) {
int x, y;
for(x = 0; x < 32; x++) {
for(y = 0; y < 32; y++) {
int i = y*4 + x/8, b = x % 8;
int ym = y % 4, xm = x % 4;
for(int k = 0; k < 2; k++) {
if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) {
(k == 0 ? MaskB : MaskA)[i] |= (0x80 >> b);
}
ym = (ym + 2) % 4; xm = (xm + 2) % 4;
}
}
}
Init = true;
}
switch(pattern) {
case Canvas::FillPattern::SOLID:
glDisable(GL_POLYGON_STIPPLE);
break;
case Canvas::FillPattern::CHECKERED_A:
glEnable(GL_POLYGON_STIPPLE);
glPolygonStipple(MaskA);
break;
case Canvas::FillPattern::CHECKERED_B:
glEnable(GL_POLYGON_STIPPLE);
glPolygonStipple(MaskB);
break;
}
}
//-----------------------------------------------------------------------------
// A simple OpenGL state tracker to group consecutive draw calls.
//-----------------------------------------------------------------------------
void OpenGl1Renderer::SelectPrimitive(GLenum mode) {
if(current.drawing && current.mode == mode) {
return;
} else if(current.drawing) {
glEnd();
}
glBegin(mode);
current.drawing = true;
current.mode = mode;
}
void OpenGl1Renderer::UnSelectPrimitive() {
if(!current.drawing) return;
glEnd();
current.drawing = false;
}
Canvas::Stroke *OpenGl1Renderer::SelectStroke(hStroke hcs) {
if(current.hcs.v == hcs.v) return current.stroke;
Stroke *stroke = strokes.FindById(hcs);
UnSelectPrimitive();
ssglColorRGBA(stroke->color);
ssglDepthRange(stroke->layer, stroke->zIndex);
ssglLineWidth(stroke->width);
// Fat lines and points are quads affected by glPolygonStipple, so make sure
// they are displayed correctly.
ssglFillPattern(FillPattern::SOLID);
glDisable(GL_TEXTURE_2D);
current.hcs = hcs;
current.stroke = stroke;
current.hcf = {};
current.fill = NULL;
current.texture.reset();
return stroke;
}
Canvas::Fill *OpenGl1Renderer::SelectFill(hFill hcf) {
if(current.hcf.v == hcf.v) return current.fill;
Fill *fill = fills.FindById(hcf);
UnSelectPrimitive();
ssglColorRGBA(fill->color);
ssglDepthRange(fill->layer, fill->zIndex);
ssglFillPattern(fill->pattern);
glDisable(GL_TEXTURE_2D);
current.hcs = {};
current.stroke = NULL;
current.hcf = hcf;
current.fill = fill;
current.texture.reset();
return fill;
}
void OpenGl1Renderer::SelectTexture(std::shared_ptr<const Pixmap> pm) {
if(current.texture.lock() == pm) return;
glBindTexture(GL_TEXTURE_2D, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
GLenum format;
switch(pm->format) {
case Pixmap::Format::RGBA: format = GL_RGBA; break;
case Pixmap::Format::RGB: format = GL_RGB; break;
case Pixmap::Format::A: format = GL_ALPHA; break;
}
glTexImage2D(GL_TEXTURE_2D, 0, format, pm->width, pm->height, 0,
format, GL_UNSIGNED_BYTE, &pm->data[0]);
glEnable(GL_TEXTURE_2D);
current.texture = pm;
}
//-----------------------------------------------------------------------------
// OpenGL's GL_LINES mode does not work on lines thicker than about 3 px,
// so we have to draw thicker lines using triangles.
//-----------------------------------------------------------------------------
void OpenGl1Renderer::DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v) {
// A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10
static const double Circle[11][2] = {
{ 0.0000, 1.0000 },
{ -0.3090, 0.9511 },
{ -0.5878, 0.8090 },
{ -0.8090, 0.5878 },
{ -0.9511, 0.3090 },
{ -1.0000, 0.0000 },
{ -0.9511, -0.3090 },
{ -0.8090, -0.5878 },
{ -0.5878, -0.8090 },
{ -0.3090, -0.9511 },
{ 0.0000, -1.0000 },
};
SelectPrimitive(GL_TRIANGLE_FAN);
for(auto pc : Circle) {
double c = pc[0], s = pc[1];
ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
}
UnSelectPrimitive();
}
void OpenGl1Renderer::DoFatLine(const Vector &a, const Vector &b, double width) {
// The half-width of the line we're drawing.
double hw = width / 2;
Vector ab = b.Minus(a);
Vector gn = (camera.projRight).Cross(camera.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
// So now abn is normal to the projection of ab into the screen, so the
// line will always have constant thickness as the view is rotated.
abn = abn.WithMagnitude(hw);
ab = gn.Cross(abn);
ab = ab. WithMagnitude(hw);
// The body of a line is a quad
SelectPrimitive(GL_QUADS);
ssglVertex3v(a.Plus (abn));
ssglVertex3v(b.Plus (abn));
ssglVertex3v(b.Minus(abn));
ssglVertex3v(a.Minus(abn));
// And the line has two semi-circular end caps.
DoFatLineEndcap(a, ab, abn);
DoFatLineEndcap(b, ab.ScaledBy(-1), abn);
}
void OpenGl1Renderer::DoLine(const Vector &a, const Vector &b, hStroke hcs) {
if(a.Equals(b)) return;
Stroke *stroke = SelectStroke(hcs);
if(stroke->width <= 3.0) {
SelectPrimitive(GL_LINES);
ssglVertex3v(a);
ssglVertex3v(b);
} else {
DoFatLine(a, b, stroke->width / camera.scale);
}
}
void OpenGl1Renderer::DoPoint(Vector p, double d) {
if(d <= 3.0) {
Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale);
SelectPrimitive(GL_LINES);
ssglVertex3v(p.Minus(u));
ssglVertex3v(p.Plus(u));
} else {
Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale);
Vector v = camera.projUp.WithMagnitude(d / 2.0 / camera.scale);
DoFatLineEndcap(p, u, v);
DoFatLineEndcap(p, u.ScaledBy(-1.0), v);
}
}
void OpenGl1Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs) {
Stroke *stroke = SelectStroke(hcs);
const char *patternSeq;
switch(stroke->stipplePattern) {
case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return;
case StipplePattern::SHORT_DASH: patternSeq = "- "; break;
case StipplePattern::DASH: patternSeq = "- "; break;
case StipplePattern::LONG_DASH: patternSeq = "_ "; break;
case StipplePattern::DASH_DOT: patternSeq = "-."; break;
case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break;
case StipplePattern::DOT: patternSeq = "."; break;
case StipplePattern::FREEHAND: patternSeq = "~"; break;
case StipplePattern::ZIGZAG: patternSeq = "~__"; break;
}
Vector dir = b.Minus(a);
double len = dir.Magnitude();
dir = dir.WithMagnitude(1.0);
const char *si = patternSeq;
double end = len;
double ss = stroke->stippleScale / 2.0;
do {
double start = end;
switch(*si) {
case ' ':
end -= 1.0 * ss;
break;
case '-':
start = max(start - 0.5 * ss, 0.0);
end = max(start - 2.0 * ss, 0.0);
if(start == end) break;
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
end = max(end - 0.5 * ss, 0.0);
break;
case '_':
end = max(end - 4.0 * ss, 0.0);
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
break;
case '.':
end = max(end - 0.5 * ss, 0.0);
if(end == 0.0) break;
DoPoint(a.Plus(dir.ScaledBy(end)), stroke->width);
end = max(end - 0.5 * ss, 0.0);
break;
case '~': {
Vector ab = b.Minus(a);
Vector gn = (camera.projRight).Cross(camera.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
double pws = 2.0 * stroke->width / camera.scale;
end = max(end - 0.5 * ss, 0.0);
Vector aa = a.Plus(dir.ScaledBy(start));
Vector bb = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
DoLine(aa, bb, hcs);
if(end == 0.0) break;
start = end;
end = max(end - 1.0 * ss, 0.0);
aa = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws))
.Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss));
DoLine(bb, aa, hcs);
if(end == 0.0) break;
start = end;
end = max(end - 0.5 * ss, 0.0);
bb = a.Plus(dir.ScaledBy(end))
.Minus(abn.ScaledBy(pws))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
DoLine(aa, bb, hcs);
break;
}
default: ssassert(false, "Unexpected stipple pattern element");
}
if(*(++si) == 0) si = patternSeq;
} while(end > 0.0);
}
//-----------------------------------------------------------------------------
// A canvas implemented using OpenGL 2 immediate mode.
//-----------------------------------------------------------------------------
void OpenGl1Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
DoStippledLine(a, b, hcs);
}
void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) {
for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) {
DoStippledLine(e->a, e->b, hcs);
}
}
void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs) {
Vector projDir = camera.projRight.Cross(camera.projUp);
for(const SOutline *o = ol.l.First(); o; o = ol.l.NextAfter(o)) {
if(!o->IsVisible(projDir)) continue;
DoStippledLine(o->a, o->b, hcs);
}
}
void OpenGl1Renderer::DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) {
auto traceEdge = [&](Vector a, Vector b) { DoStippledLine(a, b, hcs); };
VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
}
void OpenGl1Renderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) {
SelectFill(hcf);
SelectPrimitive(GL_QUADS);
ssglVertex3v(a);
ssglVertex3v(b);
ssglVertex3v(c);
ssglVertex3v(d);
}
void OpenGl1Renderer::DrawPoint(const Vector &o, double s, hFill hcf) {
Vector r = camera.projRight.ScaledBy(s/camera.scale);
Vector u = camera.projUp.ScaledBy(s/camera.scale);
Vector a = o.Plus (r).Plus (u),
b = o.Plus (r).Minus(u),
c = o.Minus(r).Minus(u),
d = o.Minus(r).Plus (u);
DrawQuad(a, b, c, d, hcf);
}
#ifdef WIN32
#define SSGL_CALLBACK CALLBACK
#else
#define SSGL_CALLBACK
#endif
typedef void(SSGL_CALLBACK *GLUCallback)();
static void SSGL_CALLBACK Vertex(Vector *p) {
ssglVertex3v(*p);
}
static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4],
float weight[4], void **outData) {
Vector *n = (Vector *)AllocTemporary(sizeof(Vector));
n->x = coords[0];
n->y = coords[1];
n->z = coords[2];
*outData = n;
}
void OpenGl1Renderer::DrawPolygon(const SPolygon &p, hFill hcf) {
UnSelectPrimitive();
SelectFill(hcf);
GLUtesselator *gt = gluNewTess();
gluTessCallback(gt, GLU_TESS_BEGIN, (GLUCallback) glBegin);
gluTessCallback(gt, GLU_TESS_VERTEX, (GLUCallback) Vertex);
gluTessCallback(gt, GLU_TESS_END, (GLUCallback) glEnd);
gluTessCallback(gt, GLU_TESS_COMBINE, (GLUCallback) Combine);
gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
ssglNormal3v(p.normal);
gluTessNormal(gt, p.normal.x, p.normal.y, p.normal.z);
gluTessBeginPolygon(gt, NULL);
for(const SContour &sc : p.l) {
gluTessBeginContour(gt);
for(const SPoint &sp : sc.l) {
double ap[3] = { sp.p.x, sp.p.y, sp.p.z };
gluTessVertex(gt, ap, (GLvoid *) &sp.p);
}
gluTessEndContour(gt);
}
gluTessEndPolygon(gt);
gluDeleteTess(gt);
}
void OpenGl1Renderer::DrawMesh(const SMesh &m,
hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) {
UnSelectPrimitive();
Fill *frontFill = SelectFill(hcfFront);
ssglMaterialRGBA(GL_FRONT, frontFill->color);
if(hcfBack.v != 0) {
Fill *backFill = fills.FindById(hcfBack);
ssassert(frontFill->layer == backFill->layer &&
frontFill->zIndex == backFill->zIndex,
"frontFill and backFill should belong to the same depth range");
ssassert(frontFill->pattern == backFill->pattern,
"frontFill and backFill should have the same pattern");
ssglMaterialRGBA(GL_BACK, backFill->color);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
} else {
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
}
RgbaColor frontColor = {};
glEnable(GL_LIGHTING);
glBegin(GL_TRIANGLES);
for(const STriangle &tr : m.l) {
if(frontFill->color.IsEmpty()) {
if(frontColor.IsEmpty() || !frontColor.Equals(tr.meta.color)) {
frontColor = tr.meta.color;
ssglMaterialRGBA(GL_FRONT, frontColor);
}
}
if(tr.an.EqualsExactly(Vector::From(0, 0, 0))) {
// Compute the normal from the vertices
ssglNormal3v(tr.Normal());
ssglVertex3v(tr.a);
ssglVertex3v(tr.b);
ssglVertex3v(tr.c);
} else {
// Use the exact normals that are specified
ssglNormal3v(tr.an);
ssglVertex3v(tr.a);
ssglNormal3v(tr.bn);
ssglVertex3v(tr.b);
ssglNormal3v(tr.cn);
ssglVertex3v(tr.c);
}
}
glEnd();
glDisable(GL_LIGHTING);
if(hcsTriangles.v != 0) {
Stroke *triangleStroke = SelectStroke(hcsTriangles);
ssassert(triangleStroke->width == 1 &&
triangleStroke->stipplePattern == StipplePattern::CONTINUOUS &&
triangleStroke->stippleScale == 0.0,
"Triangle stroke must match predefined OpenGL parameters");
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_TRIANGLES);
for(const STriangle &tr : m.l) {
ssglVertex3v(tr.a);
ssglVertex3v(tr.b);
ssglVertex3v(tr.c);
}
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
void OpenGl1Renderer::DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) {
SelectFill(hcf);
SelectPrimitive(GL_TRIANGLES);
size_t facesSize = faces.size();
for(const STriangle &tr : m.l) {
uint32_t face = tr.meta.face;
for(size_t j = 0; j < facesSize; j++) {
if(faces[j] != face) continue;
ssglVertex3v(tr.a);
ssglVertex3v(tr.b);
ssglVertex3v(tr.c);
break;
}
}
}
void OpenGl1Renderer::DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) {
UnSelectPrimitive();
SelectFill(hcf);
SelectTexture(pm);
SelectPrimitive(GL_QUADS);
glTexCoord2d(ta.x, ta.y);
ssglVertex3v(o);
glTexCoord2d(ta.x, tb.y);
ssglVertex3v(o.Plus(v));
glTexCoord2d(tb.x, tb.y);
ssglVertex3v(o.Plus(u).Plus(v));
glTexCoord2d(tb.x, ta.y);
ssglVertex3v(o.Plus(u));
}
void OpenGl1Renderer::InvalidatePixmap(std::shared_ptr<const Pixmap> pm) {
if(current.texture.lock() == pm) {
current.texture.reset();
}
}
void OpenGl1Renderer::UpdateProjection(bool flip) {
UnSelectPrimitive();
glViewport(0, 0, camera.width, camera.height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScaled(camera.scale * 2.0 / camera.width,
camera.scale * 2.0 / camera.height,
camera.scale * 1.0 / 30000);
double mat[16];
// Last thing before display is to apply the perspective
double clp = camera.tangent * camera.scale;
double sy = flip ? -1.0 : 1.0;
MakeMatrix(mat, 1, 0, 0, 0,
0, sy, 0, 0,
0, 0, 1, 0,
0, 0, clp, 1);
glMultMatrixd(mat);
// Before that, we apply the rotation
Vector projRight = camera.projRight,
projUp = camera.projUp,
n = camera.projUp.Cross(camera.projRight);
MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0,
projUp.x, projUp.y, projUp.z, 0,
n.x, n.y, n.z, 0,
0, 0, 0, 1);
glMultMatrixd(mat);
// And before that, the translation
Vector offset = camera.offset;
MakeMatrix(mat, 1, 0, 0, offset.x,
0, 1, 0, offset.y,
0, 0, 1, offset.z,
0, 0, 0, 1);
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void OpenGl1Renderer::BeginFrame() {
glEnable(GL_NORMALIZE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
// don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards,
// drawn with leaks in the mesh
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
if(EXACT(lighting.lightIntensity[0] != 0.0)) {
glEnable(GL_LIGHT0);
GLfloat f = (GLfloat)lighting.lightIntensity[0];
GLfloat li0[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT0, GL_DIFFUSE, li0);
glLightfv(GL_LIGHT0, GL_SPECULAR, li0);
Vector ld = camera.VectorFromProjs(lighting.lightDirection[0]);
GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT0, GL_POSITION, ld0);
}
if(EXACT(lighting.lightIntensity[1] != 0.0)) {
glEnable(GL_LIGHT1);
GLfloat f = (GLfloat)lighting.lightIntensity[1];
GLfloat li0[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT1, GL_DIFFUSE, li0);
glLightfv(GL_LIGHT1, GL_SPECULAR, li0);
Vector ld = camera.VectorFromProjs(lighting.lightDirection[1]);
GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT1, GL_POSITION, ld0);
}
if(EXACT(lighting.ambientIntensity != 0.0)) {
GLfloat ambient[4] = { (float)lighting.ambientIntensity,
(float)lighting.ambientIntensity,
(float)lighting.ambientIntensity, 1 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
}
glClearColor(lighting.backgroundColor.redF(), lighting.backgroundColor.greenF(),
lighting.backgroundColor.blueF(), lighting.backgroundColor.alphaF());
glClear(GL_COLOR_BUFFER_BIT);
glClearDepth(1.0);
glClear(GL_DEPTH_BUFFER_BIT);
}
void OpenGl1Renderer::EndFrame() {
UnSelectPrimitive();
glFlush();
GLenum error = glGetError();
if(error != GL_NO_ERROR) {
dbp("glGetError() == 0x%X %s", error, gluErrorString(error));
}
}
std::shared_ptr<Pixmap> OpenGl1Renderer::ReadFrame() {
std::shared_ptr<Pixmap> pixmap =
Pixmap::Create(Pixmap::Format::RGB, (size_t)camera.width, (size_t)camera.height);
glReadPixels(0, 0, camera.width, camera.height, GL_RGB, GL_UNSIGNED_BYTE, &pixmap->data[0]);
return pixmap;
}
void OpenGl1Renderer::GetIdent(const char **vendor, const char **renderer, const char **version) {
*vendor = (const char *)glGetString(GL_VENDOR);
*renderer = (const char *)glGetString(GL_RENDERER);
*version = (const char *)glGetString(GL_VERSION);
}
}

View File

@ -48,12 +48,12 @@ std::string LoadStringFromGzip(const std::string &name) {
return result;
}
Pixmap LoadPNG(const std::string &name) {
std::shared_ptr<Pixmap> LoadPng(const std::string &name) {
size_t size;
const void *data = LoadResource(name, &size);
Pixmap pixmap = Pixmap::FromPNG(static_cast<const uint8_t *>(data), size);
ssassert(!pixmap.IsEmpty(), "Cannot load pixmap");
std::shared_ptr<Pixmap> pixmap = Pixmap::FromPng(static_cast<const uint8_t *>(data), size);
ssassert(pixmap != nullptr, "Cannot load pixmap");
return pixmap;
}
@ -62,44 +62,59 @@ Pixmap LoadPNG(const std::string &name) {
// Pixmap manipulation
//-----------------------------------------------------------------------------
void Pixmap::Clear() {
*this = {};
size_t Pixmap::GetBytesPerPixel() const {
switch(format) {
case Format::RGBA: return 4;
case Format::RGB: return 3;
case Format::A: return 1;
}
ssassert(false, "Unexpected pixmap format");
}
RgbaColor Pixmap::GetPixel(size_t x, size_t y) const {
const uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()];
if(hasAlpha) {
switch(format) {
case Format::RGBA:
return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]);
} else {
return RgbaColor::From(pixel[0], pixel[1], pixel[2]);
case Format::RGB:
return RgbaColor::From(pixel[0], pixel[1], pixel[2], 255);
case Format::A:
return RgbaColor::From( 255, 255, 255, pixel[0]);
}
ssassert(false, "Unexpected resource format");
}
static Pixmap ReadPNGIntoPixmap(png_struct *png_ptr, png_info *info_ptr) {
static std::shared_ptr<Pixmap> ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr) {
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL);
Pixmap pixmap = {};
pixmap.width = png_get_image_width(png_ptr, info_ptr);
pixmap.height = png_get_image_height(png_ptr, info_ptr);
pixmap.hasAlpha = (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0;
size_t stride = pixmap.width * pixmap.GetBytesPerPixel();
if(stride % 4 != 0) stride += 4 - stride % 4;
pixmap.stride = stride;
pixmap.data = std::vector<uint8_t>(pixmap.stride * pixmap.height);
uint8_t **rows = png_get_rows(png_ptr, info_ptr);
for(size_t y = 0; y < pixmap.height; y++) {
memcpy(&pixmap.data[pixmap.stride * y], rows[y],
pixmap.width * pixmap.GetBytesPerPixel());
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
pixmap->width = png_get_image_width(png_ptr, info_ptr);
pixmap->height = png_get_image_height(png_ptr, info_ptr);
if((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0) {
pixmap->format = Pixmap::Format::RGBA;
} else {
pixmap->format = Pixmap::Format::RGB;
}
size_t stride = pixmap->width * pixmap->GetBytesPerPixel();
if(stride % 4 != 0) stride += 4 - stride % 4;
pixmap->stride = stride;
pixmap->data = std::vector<uint8_t>(pixmap->stride * pixmap->height);
uint8_t **rows = png_get_rows(png_ptr, info_ptr);
for(size_t y = 0; y < pixmap->height; y++) {
memcpy(&pixmap->data[pixmap->stride * y], rows[y],
pixmap->width * pixmap->GetBytesPerPixel());
}
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return pixmap;
}
Pixmap Pixmap::FromPNG(const uint8_t *data, size_t size) {
Pixmap pixmap = {};
std::shared_ptr<Pixmap> Pixmap::FromPng(const uint8_t *data, size_t size) {
struct Slice { const uint8_t *data; size_t size; };
Slice dataSlice = { data, size };
png_struct *png_ptr = NULL;
@ -124,16 +139,14 @@ Pixmap Pixmap::FromPNG(const uint8_t *data, size_t size) {
}
});
pixmap = ReadPNGIntoPixmap(png_ptr, info_ptr);
return ReadPngIntoPixmap(png_ptr, info_ptr);
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return pixmap;
return nullptr;
}
Pixmap Pixmap::FromPNG(FILE *f) {
Pixmap pixmap = {};
std::shared_ptr<Pixmap> Pixmap::ReadPng(FILE *f) {
png_struct *png_ptr = NULL;
png_info *info_ptr = NULL;
@ -151,10 +164,66 @@ Pixmap Pixmap::FromPNG(FILE *f) {
png_init_io(png_ptr, f);
png_set_sig_bytes(png_ptr, sizeof(header));
pixmap = ReadPNGIntoPixmap(png_ptr, info_ptr);
return ReadPngIntoPixmap(png_ptr, info_ptr);
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return nullptr;
}
bool Pixmap::WritePng(FILE *f, bool flip) {
int colorType;
switch(format) {
case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; break;
case Format::RGB: colorType = PNG_COLOR_TYPE_RGB; break;
case Format::A: ssassert(false, "Unexpected pixmap format");
}
std::vector<uint8_t *> rows;
for(size_t y = 0; y < height; y++) {
if(flip) {
rows.push_back(&data[stride * y]);
} else {
rows.push_back(&data[stride * (height - y)]);
}
}
png_struct *png_ptr = NULL;
png_info *info_ptr = NULL;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if(!png_ptr) goto exit;
info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr) goto exit;
if(setjmp(png_jmpbuf(png_ptr))) goto exit;
png_init_io(png_ptr, f);
png_set_IHDR(png_ptr, info_ptr, width, height, 8,
colorType, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
png_write_image(png_ptr, &rows[0]);
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
return true;
exit:
png_destroy_write_struct(&png_ptr, &info_ptr);
return false;
}
std::shared_ptr<Pixmap> Pixmap::Create(Format format, size_t width, size_t height) {
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
pixmap->format = format;
pixmap->width = width;
pixmap->height = height;
// Align to fulfill OpenGL texture requirements.
size_t stride = pixmap->width * pixmap->GetBytesPerPixel();
if(stride % 4 != 0) stride += 4 - stride % 4;
pixmap->stride = stride;
pixmap->data = std::vector<uint8_t>(pixmap->stride * pixmap->height);
return pixmap;
}
@ -246,39 +315,41 @@ public:
// Bitmap font manipulation
//-----------------------------------------------------------------------------
static const size_t CHARS_PER_ROW = BitmapFont::TEXTURE_DIM / 16;
static uint8_t *BitmapFontTextureRow(uint8_t *texture, uint16_t position, size_t y) {
static uint8_t *BitmapFontTextureRow(std::shared_ptr<Pixmap> texture,
uint16_t position, size_t y) {
// position = 0;
size_t col = position % CHARS_PER_ROW,
row = position / CHARS_PER_ROW;
return &texture[BitmapFont::TEXTURE_DIM * (16 * row + y) + 16 * col];
size_t col = position % (texture->width / 16),
row = position / (texture->width / 16);
return &texture->data[texture->stride * (16 * row + y) + 16 * col];
}
BitmapFont BitmapFont::From(std::string &&unifontData) {
BitmapFont font = {};
font.unifontData = std::move(unifontData);
font.texture = std::vector<uint8_t>(TEXTURE_DIM * TEXTURE_DIM);
font.texture = Pixmap::Create(Pixmap::Format::A, 1024, 1024);
return font;
}
void BitmapFont::AddGlyph(char32_t codepoint, const Pixmap &pixmap) {
ssassert((pixmap.width == 8 || pixmap.width == 16) && pixmap.height == 16,
"Unexpected glyph dimensions");
void BitmapFont::AddGlyph(char32_t codepoint, std::shared_ptr<const Pixmap> pixmap) {
ssassert((pixmap->width == 8 || pixmap->width == 16) && pixmap->height == 16,
"Unexpected pixmap dimensions");
ssassert(pixmap->format == Pixmap::Format::RGB,
"Unexpected pixmap format");
ssassert(glyphs.find(codepoint) == glyphs.end(),
"Glyph with this codepoint already exists");
ssassert(nextPosition != 0xffff,
"Too many glyphs for current texture size");
BitmapFont::Glyph glyph = {};
glyph.advanceCells = pixmap.width / 8;
glyph.advanceCells = pixmap->width / 8;
glyph.position = nextPosition++;
glyphs.emplace(codepoint, std::move(glyph));
for(size_t y = 0; y < pixmap.height; y++) {
uint8_t *row = BitmapFontTextureRow(&texture[0], glyph.position, y);
for(size_t x = 0; x < pixmap.width; x++) {
if(pixmap.GetPixel(x, y).ToPackedInt() != 0) {
for(size_t y = 0; y < pixmap->height; y++) {
uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y);
for(size_t x = 0; x < pixmap->width; x++) {
if((pixmap->GetPixel(x, y).ToPackedInt() & 0xffffff) != 0) {
row[x] = 255;
}
}
@ -346,7 +417,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
// Fill in the texture (one texture byte per glyph bit).
for(size_t y = 0; y < 16; y++) {
uint8_t *row = BitmapFontTextureRow(&texture[0], glyph.position, y);
uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y);
for(size_t x = 0; x < 16; x++) {
if(glyphBits[y] & (1 << (15 - x))) {
row[x] = 255;
@ -370,13 +441,49 @@ bool BitmapFont::LocateGlyph(char32_t codepoint,
const Glyph &glyph = GetGlyph(codepoint);
*w = glyph.advanceCells * 8;
*h = 16;
*s0 = (16.0 * (glyph.position % CHARS_PER_ROW)) / TEXTURE_DIM;
*s1 = *s0 + (double)(*w) / TEXTURE_DIM;
*t0 = (16.0 * (glyph.position / CHARS_PER_ROW)) / TEXTURE_DIM;
*t1 = *t0 + (double)(*h) / TEXTURE_DIM;
*s0 = (16.0 * (glyph.position % (texture->width / 16))) / texture->width;
*s1 = *s0 + (double)(*w) / texture->width;
*t0 = (16.0 * (glyph.position / (texture->width / 16))) / texture->height;
*t1 = *t0 + (double)(*h) / texture->height;
return textureUpdated;
}
size_t BitmapFont::GetWidth(char32_t codepoint) {
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// These are special-cased because checkboxes predate support for 2 cell wide
// characters; and so all Printf() calls pad them with spaces.
return 1;
}
return GetGlyph(codepoint).advanceCells;
}
size_t BitmapFont::GetWidth(const std::string &str) {
size_t width = 0;
for(char32_t codepoint : ReadUTF8(str)) {
width += GetWidth(codepoint);
}
return width;
}
BitmapFont *BitmapFont::Builtin() {
static BitmapFont Font;
if(Font.IsEmpty()) {
Font = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz"));
// Unifont doesn't have a glyph for U+0020.
Font.AddGlyph(0x0020, Pixmap::Create(Pixmap::Format::RGB, 8, 16));
Font.AddGlyph(0xE000, LoadPng("fonts/private/0-check-false.png"));
Font.AddGlyph(0xE001, LoadPng("fonts/private/1-check-true.png"));
Font.AddGlyph(0xE002, LoadPng("fonts/private/2-radio-false.png"));
Font.AddGlyph(0xE003, LoadPng("fonts/private/3-radio-true.png"));
Font.AddGlyph(0xE004, LoadPng("fonts/private/4-stipple-dot.png"));
Font.AddGlyph(0xE005, LoadPng("fonts/private/5-stipple-dash-long.png"));
Font.AddGlyph(0xE006, LoadPng("fonts/private/6-stipple-dash.png"));
Font.AddGlyph(0xE007, LoadPng("fonts/private/7-stipple-zigzag.png"));
}
return &Font;
}
//-----------------------------------------------------------------------------
// Vector font manipulation
//-----------------------------------------------------------------------------
@ -476,6 +583,7 @@ VectorFont VectorFont::From(std::string &&lffData) {
GetGlyphBBox(font.GetGlyph('h'), nullptr, nullptr, nullptr, &font.ascender);
GetGlyphBBox(font.GetGlyph('p'), nullptr, nullptr, &font.descender, nullptr);
ssassert(!font.IsEmpty(), "Expected to load a font");
return font;
}
@ -574,4 +682,115 @@ const VectorFont::Glyph &VectorFont::GetGlyph(char32_t codepoint) {
return GetGlyph(0xfffd);
}
VectorFont *VectorFont::Builtin() {
static VectorFont Font;
if(Font.IsEmpty()) {
Font = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz"));
}
return &Font;
}
double VectorFont::GetCapHeight(double forCapHeight) const {
ssassert(!IsEmpty(), "Expected a loaded font");
return forCapHeight;
}
double VectorFont::GetHeight(double forCapHeight) const {
ssassert(!IsEmpty(), "Expected a loaded font");
return (ascender - descender) * (forCapHeight / capHeight);
}
double VectorFont::GetWidth(double forCapHeight, const std::string &str) {
ssassert(!IsEmpty(), "Expected a loaded font");
double width = 0;
for(char32_t codepoint : ReadUTF8(str)) {
width += GetGlyph(codepoint).advanceWidth;
}
width -= rightSideBearing;
return width * (forCapHeight / capHeight);
}
Vector VectorFont::GetExtents(double forCapHeight, const std::string &str) {
Vector ex = {};
ex.x = GetWidth(forCapHeight, str);
ex.y = GetHeight(forCapHeight);
return ex;
}
void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge) {
ssassert(!IsEmpty(), "Expected a loaded font");
double scale = (forCapHeight / capHeight);
u = u.ScaledBy(scale);
v = v.ScaledBy(scale);
for(char32_t codepoint : ReadUTF8(str)) {
const Glyph &glyph = GetGlyph(codepoint);
for(const VectorFont::Contour &contour : glyph.contours) {
Vector prevp;
bool penUp = true;
for(const Point2d &pt : contour.points) {
Vector p = o.Plus(u.ScaledBy(pt.x))
.Plus(v.ScaledBy(pt.y));
if(!penUp) traceEdge(prevp, p);
prevp = p;
penUp = false;
}
}
o = o.Plus(u.ScaledBy(glyph.advanceWidth));
}
}
void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge, const Camera &camera) {
ssassert(!IsEmpty(), "Expected a loaded font");
// Perform grid-fitting only when the text is parallel to the view plane.
if(camera.hasPixels && !(u.WithMagnitude(1).Equals(camera.projRight) &&
v.WithMagnitude(1).Equals(camera.projUp))) {
return Trace(forCapHeight, o, u, v, str, traceEdge);
}
double scale = forCapHeight / capHeight;
u = u.ScaledBy(scale);
v = v.ScaledBy(scale);
for(char32_t codepoint : ReadUTF8(str)) {
const Glyph &glyph = GetGlyph(codepoint);
double actualWidth = std::max(1.0, glyph.boundingWidth);
// Align (o+lsb), (o+lsb+u) and (o+lsb+v) to pixel grid.
Vector ao = o.Plus(u.ScaledBy(glyph.leftSideBearing));
Vector au = ao.Plus(u.ScaledBy(actualWidth));
Vector av = ao.Plus(v.ScaledBy(capHeight));
ao = camera.AlignToPixelGrid(ao);
au = camera.AlignToPixelGrid(au);
av = camera.AlignToPixelGrid(av);
au = au.Minus(ao).ScaledBy(1.0 / actualWidth);
av = av.Minus(ao).ScaledBy(1.0 / capHeight);
for(const VectorFont::Contour &contour : glyph.contours) {
Vector prevp;
bool penUp = true;
for(const Point2d &pt : contour.points) {
Vector p = ao.Plus(au.ScaledBy(pt.x - glyph.leftSideBearing))
.Plus(av.ScaledBy(pt.y));
if(!penUp) traceEdge(prevp, p);
prevp = p;
penUp = false;
}
}
o = o.Plus(u.ScaledBy(glyph.advanceWidth));
}
}
}

View File

@ -7,8 +7,10 @@
#ifndef __RESOURCE_H
#define __RESOURCE_H
class Camera;
class Point2d;
class Pixmap;
class Vector;
// Only the following function is platform-specific.
// It returns a pointer to resource contents that is aligned to at least
@ -18,24 +20,26 @@ const void *LoadResource(const std::string &name, size_t *size);
std::string LoadString(const std::string &name);
std::string LoadStringFromGzip(const std::string &name);
Pixmap LoadPNG(const std::string &name);
std::shared_ptr<Pixmap> LoadPng(const std::string &name);
class Pixmap {
public:
enum class Format { RGBA, RGB, A };
Format format;
size_t width;
size_t height;
size_t stride;
bool hasAlpha;
std::vector<uint8_t> data;
static Pixmap FromPNG(const uint8_t *data, size_t size);
static Pixmap FromPNG(FILE *f);
static std::shared_ptr<Pixmap> Create(Format format, size_t width, size_t height);
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size);
bool IsEmpty() const { return width == 0 && height == 0; }
size_t GetBytesPerPixel() const { return hasAlpha ? 4 : 3; }
static std::shared_ptr<Pixmap> ReadPng(FILE *f);
bool WritePng(FILE *f, bool flip = false);
size_t GetBytesPerPixel() const;
RgbaColor GetPixel(size_t x, size_t y) const;
void Clear();
};
class BitmapFont {
@ -45,21 +49,23 @@ public:
uint16_t position;
};
static const size_t TEXTURE_DIM = 1024;
std::string unifontData;
std::map<char32_t, Glyph> glyphs;
std::vector<uint8_t> texture;
std::shared_ptr<Pixmap> texture;
uint16_t nextPosition;
static BitmapFont From(std::string &&unifontData);
static BitmapFont *Builtin();
bool IsEmpty() const { return unifontData.empty(); }
const Glyph &GetGlyph(char32_t codepoint);
bool LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1,
size_t *advanceWidth, size_t *boundingHeight);
void AddGlyph(char32_t codepoint, const Pixmap &pixmap);
void AddGlyph(char32_t codepoint, std::shared_ptr<const Pixmap> pixmap);
size_t GetWidth(char32_t codepoint);
size_t GetWidth(const std::string &str);
};
class VectorFont {
@ -83,10 +89,20 @@ public:
double descender;
static VectorFont From(std::string &&lffData);
static VectorFont *Builtin();
bool IsEmpty() const { return lffData.empty(); }
const Glyph &GetGlyph(char32_t codepoint);
double GetCapHeight(double forCapHeight) const;
double GetHeight(double forCapHeight) const;
double GetWidth(double forCapHeight, const std::string &str);
Vector GetExtents(double forCapHeight, const std::string &str);
void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge);
void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge, const Camera &camera);
};
#endif

View File

@ -8,11 +8,6 @@
#ifndef __SKETCH_H
#define __SKETCH_H
#ifdef WIN32
// winuser.h confilct (DIFFERENCE)
#undef DIFFERENCE
#endif
class hGroup;
class hRequest;
class hEntity;
@ -270,16 +265,17 @@ public:
Group *PreviousGroup();
Group *RunningMeshGroup();
bool IsMeshGroup();
void GenerateShellAndMesh();
template<class T> void GenerateForStepAndRepeat(T *steps, T *outs);
template<class T> void GenerateForBoolean(T *a, T *b, T *o, Group::CombineAs how);
void GenerateDisplayItems();
void DrawDisplayItems(Group::Type t);
void Draw();
RgbaColor GetLoopSetFillColor(SBezierLoopSet *sbls,
bool *allSame, Vector *errorAt);
void FillLoopSetAsPolygon(SBezierLoopSet *sbls);
void DrawFilledPaths();
enum class DrawMeshAs { DEFAULT, HOVERED, SELECTED };
void DrawMesh(DrawMeshAs how, Canvas *canvas);
void Draw(Canvas *canvas);
void DrawPolyError(Canvas *canvas);
void DrawFilledPaths(Canvas *canvas);
SPolygon GetPolygon();
@ -482,8 +478,7 @@ public:
// POD members with indeterminate value.
Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(),
actDistance(), actVisible(), style(), construction(),
beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid(),
dogd() {};
beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid() {};
// A linked entity that was hidden in the source file ends up hidden
// here too.
@ -507,42 +502,26 @@ public:
BBox screenBBox;
bool screenBBoxValid;
// Routines to draw and hit-test the representation of the entity
// on-screen.
struct {
bool drawing;
Point2d mp;
double dmin;
double lineWidth;
double stippleScale;
StipplePattern stippleType;
int data;
} dogd; // state for drawing or getting distance (for hit testing)
void LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat=false, int userData = -1);
void DrawOrGetDistance();
bool IsStylable() const;
bool IsVisible() const;
bool PointIsFromReferences() const;
enum class DrawAs { DEFAULT, HIDDEN, HOVERED, SELECTED };
void Draw(DrawAs how, Canvas *canvas);
void GetReferencePoints(std::vector<Vector> *refs);
int GetPositionOfPoint(const Camera &camera, Point2d p);
void ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const;
void GenerateBezierCurves(SBezierList *sbl) const;
void GenerateEdges(SEdgeList *el);
static void DrawAll(bool drawAsHidden);
void Draw(bool drawAsHidden);
double GetDistance(Point2d mp);
Vector GetReferencePos();
SBezierList *GetOrGenerateBezierCurves();
SEdgeList *GetOrGenerateEdges();
BBox GetOrGenerateScreenBBox(bool *hasBBox);
void CalculateNumerical(bool forExport);
std::string DescriptionString() const;
SBezierList *GetOrGenerateBezierCurves();
SEdgeList *GetOrGenerateEdges();
BBox GetOrGenerateScreenBBox(bool *hasBBox);
void Clear() {
beziers.l.Clear();
edges.l.Clear();
@ -668,7 +647,7 @@ public:
class Constraint : public ConstraintBase {
public:
// See Entity::Entity().
Constraint() : ConstraintBase({}), disp(), dogd() {}
Constraint() : ConstraintBase({}), disp() {}
// These define how the constraint is drawn on-screen.
struct {
@ -676,39 +655,41 @@ public:
hStyle style;
} disp;
// State for drawing or getting distance (for hit testing).
struct {
bool drawing;
Point2d mp;
double dmin;
SEdgeList *sel;
} dogd;
double GetDistance(Point2d mp);
Vector GetLabelPos();
void GetReferencePos(Vector *refps);
void Draw();
void GetEdges(SEdgeList *sel);
bool IsVisible() const;
bool IsStylable() const;
hStyle GetStyle() const;
bool HasLabel() const;
void LineDrawOrGetDistance(Vector a, Vector b);
bool IsVisible() const;
void DrawOrGetDistance(Vector *labelPos, Vector *refps);
std::string Label() const;
bool DoLineExtend(Vector p0, Vector p1, Vector pt, double salient);
void DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db,
enum class DrawAs { DEFAULT, HOVERED, SELECTED };
void Draw(DrawAs how, Canvas *canvas);
Vector GetLabelPos(const Camera &camera);
void GetReferencePoints(const Camera &camera, std::vector<Vector> *refs);
void DoLayout(DrawAs how, Canvas *canvas,
Vector *labelPos, std::vector<Vector> *refs);
bool DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
Vector p0, Vector p1, Vector pt, double salient);
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
Vector a0, Vector da, Vector b0, Vector db,
Vector offset, Vector *ref, bool trim);
void DoArrow(Vector p, Vector dir, Vector n, double width, double angle, double da);
void DoLineWithArrows(Vector ref, Vector a, Vector b, bool onlyOneExt);
int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend, Vector gr, Vector gu, double swidth, double sheight);
int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend = true);
void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu);
void StippledLine(Vector a, Vector b);
void DoProjectedPoint(Vector *p);
void DoEqualLenTicks(Vector a, Vector b, Vector gn, Vector *refp);
void DoEqualRadiusTicks(hEntity he, Vector *refp);
void DoArrow(Canvas *canvas, Canvas::hStroke hcs,
Vector p, Vector dir, Vector n, double width, double angle, double da);
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool onlyOneExt);
int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool extend,
Vector gr, Vector gu, double swidth, double sheight);
int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool extend = true);
void DoLabel(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector *labelPos, Vector gr, Vector gu);
void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs,
Vector *p);
void DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs,
Vector a, Vector b, Vector gn, Vector *refp);
void DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
hEntity he, Vector *refp);
std::string DescriptionString() const;
@ -831,9 +812,9 @@ public:
static Style *Get(hStyle hs);
static RgbaColor Color(hStyle hs, bool forExport=false);
static RgbaColor FillColor(hStyle hs, bool forExport=false);
static float Width(hStyle hs);
static double Width(hStyle hs);
static RgbaColor Color(int hs, bool forExport=false);
static float Width(int hs);
static double Width(int hs);
static double WidthMm(int hs);
static double TextHeight(hStyle hs);
static double DefaultTextHeight();

View File

@ -17,25 +17,16 @@
#include <math.h>
#include <limits.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <locale>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <chrono>
#ifdef WIN32
# include <windows.h> // required by GL headers
#endif
#ifdef __APPLE__
# include <strings.h> // for strcasecmp in file.cpp
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
#else
# include <GL/gl.h>
# include <GL/glu.h>
#endif
// We declare these in advance instead of simply using FT_Library
// (defined as typedef FT_LibraryRec_* FT_Library) because including
@ -153,6 +144,9 @@ enum class ContextCommand : uint32_t;
#define PATH_SEP "/"
#endif
extern const bool FLIP_FRAMEBUFFER;
bool PathEqual(const std::string &a, const std::string &b);
FILE *ssfopen(const std::string &filename, const char *mode);
void ssremove(const std::string &filename);
@ -304,6 +298,7 @@ class SSurface;
#include "dsc.h"
#include "polygon.h"
#include "srf/surface.h"
#include "render/render.h"
class Entity;
class hEntity;
@ -346,56 +341,6 @@ public:
utf8_iterator end() const { return utf8_iterator(&str[str.length()]); }
};
void ssglLineWidth(GLfloat width);
void ssglVertex3v(Vector u);
void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone = true);
void ssglAxisAlignedLineLoop(double l, double r, double t, double b);
#ifdef WIN32
# define SSGL_CALLBACK __stdcall
#else
# define SSGL_CALLBACK
#endif
extern "C" { typedef void SSGL_CALLBACK ssglCallbackFptr(); }
void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p);
void ssglFillPolygon(SPolygon *p);
void ssglFillMesh(bool useSpecColor, RgbaColor color,
SMesh *m, uint32_t h, uint32_t s1, uint32_t s2);
void ssglDebugPolygon(SPolygon *p);
void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs);
void ssglDrawOutlines(SOutlineList *l, Vector projDir, hStyle hs);
void ssglDebugMesh(SMesh *m);
void ssglMarkPolygonNormal(SPolygon *p);
typedef void ssglLineFn(void *data, Vector a, Vector b);
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata);
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata);
double ssglStrCapHeight(double h);
double ssglStrFontSize(double h);
double ssglStrWidth(const std::string &str, double h);
void ssglLockColorTo(RgbaColor rgb);
void ssglStippledLine(Vector a, Vector b, double width,
StipplePattern stippleType, double stippleScale, bool maybeFat);
void ssglStippledLine(Vector a, Vector b, double width,
const char *stipplePattern, double stippleScale, bool maybeFat);
void ssglFatLine(Vector a, Vector b, double width);
void ssglUnlockColor();
void ssglColorRGB(RgbaColor rgb);
void ssglColorRGBa(RgbaColor rgb, double a);
void ssglDepthRangeOffset(int units);
void ssglDepthRangeLockToFront(bool yes);
void ssglDrawPixmap(const Pixmap &pixmap, Vector a, Vector b, Vector c, Vector d);
void ssglDrawPixmap(const Pixmap &pixmap, Point2d o, bool flip = false);
void ssglInitializeBitmapFont();
void ssglBitmapText(const std::string &str, Vector p);
double ssglBitmapCharQuad(char32_t chr, double x, double y);
int ssglBitmapCharWidth(char32_t chr);
#define TEXTURE_BACKGROUND_IMG 10
#define TEXTURE_DRAW_PIXELS 20
#define TEXTURE_COLOR_PICKER_2D 30
#define TEXTURE_COLOR_PICKER_1D 40
#define TEXTURE_BITMAP_FONT 50
#define arraylen(x) (sizeof((x))/sizeof((x)[0]))
#define PI (3.1415926535897931)
@ -878,7 +823,7 @@ public:
Vector ptB;
} extraLine;
struct {
Pixmap pixmap;
std::shared_ptr<Pixmap> pixmap;
double scale; // pixels per mm
Vector origin;
} bgImage;

View File

@ -10,10 +10,6 @@
#ifndef __SURFACE_H
#define __SURFACE_H
#ifdef WIN32
#undef DIFFERENCE
#endif
// Utility functions, Bernstein polynomials of order 1-3 and their derivatives.
double Bernstein(int k, int deg, double t);
double BernsteinDerivative(int k, int deg, double t);

View File

@ -6,9 +6,6 @@
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#include <png.h>
#define DEFAULT_TEXT_HEIGHT 11.5
const Style::Default Style::Defaults[] = {
{ { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4 },
@ -88,8 +85,8 @@ void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) {
s->color = (factory) ? d->color : CnfThawColor(d->color, CnfColor(d->cnfPrefix));
s->width = (factory) ? d->width : CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix));
s->widthAs = UnitsAs::PIXELS;
s->textHeight = (factory) ? DEFAULT_TEXT_HEIGHT
: CnfThawFloat(DEFAULT_TEXT_HEIGHT, CnfTextHeight(d->cnfPrefix));
s->textHeight = (factory) ? 11.5
: CnfThawFloat(11.5, CnfTextHeight(d->cnfPrefix));
s->textHeightAs = UnitsAs::PIXELS;
s->textOrigin = TextOrigin::NONE;
s->textAngle = 0;
@ -110,7 +107,7 @@ void Style::LoadFactoryDefaults() {
FillDefaultStyle(s, d, /*factory=*/true);
}
SS.backgroundColor = RGBi(0, 0, 0);
SS.bgImage.pixmap.Clear();
SS.bgImage.pixmap = nullptr;
}
void Style::FreezeDefaultStyles() {
@ -200,7 +197,7 @@ RgbaColor Style::Color(int s, bool forExport) {
hStyle hs = { (uint32_t)s };
return Color(hs, forExport);
}
float Style::Width(int s) {
double Style::Width(int s) {
hStyle hs = { (uint32_t)s };
return Width(hs);
}
@ -250,16 +247,13 @@ RgbaColor Style::FillColor(hStyle h, bool forExport) {
//-----------------------------------------------------------------------------
// Return the width associated with our style in pixels..
//-----------------------------------------------------------------------------
float Style::Width(hStyle h) {
double r = 1.0;
double Style::Width(hStyle h) {
Style *s = Get(h);
if(s->widthAs == UnitsAs::MM) {
r = s->width * SS.GW.scale;
} else if(s->widthAs == UnitsAs::PIXELS) {
r = s->width;
switch(s->widthAs) {
case UnitsAs::MM: return s->width * SS.GW.scale;
case UnitsAs::PIXELS: return s->width;
}
// This returns a float because ssglLineWidth expects a float, avoid casts.
return (float)r;
ssassert(false, "Unexpected units");
}
//-----------------------------------------------------------------------------
@ -273,13 +267,13 @@ double Style::WidthMm(int hs) {
//-----------------------------------------------------------------------------
// Return the associated text height, in pixels.
//-----------------------------------------------------------------------------
double Style::TextHeight(hStyle hs) {
Style *s = Get(hs);
if(s->textHeightAs == UnitsAs::MM) {
return s->textHeight * SS.GW.scale;
} else /* s->textHeightAs == UNITS_AS_PIXELS */ {
return s->textHeight;
double Style::TextHeight(hStyle h) {
Style *s = Get(h);
switch(s->textHeightAs) {
case UnitsAs::MM: return s->textHeight * SS.GW.scale;
case UnitsAs::PIXELS: return s->textHeight;
}
ssassert(false, "Unexpected units");
}
double Style::DefaultTextHeight() {
@ -369,29 +363,20 @@ void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) {
SS.TW.edit.meaning = Edit::BACKGROUND_COLOR;
}
static int RoundUpToPowerOfTwo(int v)
{
int i;
for(i = 0; i < 31; i++) {
int vt = (1 << i);
if(vt >= v) {
return vt;
}
}
return 0;
}
void TextWindow::ScreenBackgroundImage(int link, uint32_t v) {
SS.bgImage.pixmap.Clear();
SS.bgImage.pixmap = nullptr;
if(link == 'l') {
std::string bgImageFile;
if(GetOpenFile(&bgImageFile, "", PngFileFilter)) {
FILE *f = ssfopen(bgImageFile, "rb");
if(f) {
SS.bgImage.pixmap = Pixmap::FromPNG(f);
SS.bgImage.pixmap = Pixmap::ReadPng(f);
SS.bgImage.scale = SS.GW.scale;
SS.bgImage.origin = SS.GW.offset.ScaledBy(-1);
fclose(f);
} else {
Error("Error reading PNG file '%s'", bgImageFile.c_str());
}
}
}
@ -432,9 +417,9 @@ void TextWindow::ShowListOfStyles() {
Printf(false, "");
Printf(false, "%Ft background bitmap image%E");
if(!SS.bgImage.pixmap.IsEmpty()) {
if(SS.bgImage.pixmap) {
Printf(false, "%Ba %Ftwidth:%E %dpx %Ftheight:%E %dpx",
SS.bgImage.pixmap.width, SS.bgImage.pixmap.height);
SS.bgImage.pixmap->width, SS.bgImage.pixmap->height);
Printf(false, " %Ftscale:%E %# px/%s %Fl%Ll%f%D[change]%E",
SS.bgImage.scale*SS.MmPerUnit(),

View File

@ -254,7 +254,7 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
}
for(utf8_iterator it(buf); *it; ++it) {
for(int i = 0; i < ssglBitmapCharWidth(*it); i++) {
for(size_t i = 0; i < BitmapFont::Builtin()->GetWidth(*it); i++) {
if(c >= MAX_COLS) goto done;
text[r][c] = (i == 0) ? *it : ' ';
meta[r][c].fg = fg;
@ -344,7 +344,8 @@ void TextWindow::TimerCallback()
InvalidateText();
}
void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, double my)
void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow how,
double mx, double my)
{
int width, height;
GetTextWindowSize(&width, &height);
@ -353,10 +354,9 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou
y -= scrollPos*(LINE_HEIGHT/2);
if(how == PAINT) {
double grey = 30.0/255;
double top = y - 28, bot = y + 4;
glColor4d(grey, grey, grey, 1.0);
ssglAxisAlignedQuad(0, width, top, bot);
int top = y - 28, bot = y + 4;
uiCanvas->DrawRect(0, width, top, bot,
/*fillColor=*/{ 30, 30, 30, 255 }, /*outlineColor=*/{});
}
HideShowIcon *oldHovered = hoveredIcon;
@ -369,38 +369,29 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou
if(hsi->var == &SPACER) {
// Draw a darker-grey spacer in between the groups of icons.
if(how == PAINT) {
int l = x, r = l + 4,
t = y, b = t - 24;
glColor4d(0.17, 0.17, 0.17, 1);
ssglAxisAlignedQuad(l, r, t, b);
uiCanvas->DrawRect(x, x + 4, y, y - 24,
/*fillColor=*/{ 45, 45, 45, 255 }, /*outlineColor=*/{});
}
x += 12;
continue;
}
if(hsi->icon.IsEmpty()) {
hsi->icon = LoadPNG(ssprintf("icons/text-window/%s.png", hsi->iconName));
if(hsi->icon == nullptr) {
hsi->icon = LoadPng(ssprintf("icons/text-window/%s.png", hsi->iconName));
}
if(how == PAINT) {
glColor4d(0, 0, 0, 1.0);
Point2d o = { (double)x, (double)(y - 24) };
ssglDrawPixmap(hsi->icon, o);
uiCanvas->DrawPixmap(hsi->icon, x, y - 24);
if(hsi == hoveredIcon) {
glColor4d(1, 1, 0, 0.3);
ssglAxisAlignedQuad(x - 2, x + 26, y + 2, y - 26);
uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26,
/*fillColor=*/{ 255, 255, 0, 75 }, /*outlineColor=*/{});
}
if(!*(hsi->var)) {
glColor4d(1, 0, 0, 0.6);
glLineWidth(2);
RgbaColor color = { 255, 0, 0, 150 };
int s = 0, f = 24;
glBegin(GL_LINES);
glVertex2d(x+s, y-s);
glVertex2d(x+f, y-f);
glVertex2d(x+s, y-f);
glVertex2d(x+f, y-s);
glEnd();
uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2);
uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2);
}
} else {
if(mx > x - 2 && mx < x + 26 &&
@ -443,22 +434,17 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou
tooltippedIcon->tip);
}
double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT;
int ox = (int)oldMousePos.x, oy = (int)oldMousePos.y - LINE_HEIGHT;
ox += 3;
oy -= 3;
int tw = (str.length() + 1) * (CHAR_WIDTH - 1);
ox = min(ox, (double) (width - 25) - tw);
oy = max(oy, 5.0);
ox = min(ox, (width - 25) - tw);
oy = max(oy, 5);
ssglInitializeBitmapFont();
glLineWidth(1);
glColor4d(1.0, 1.0, 0.6, 1.0);
ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT);
glColor4d(0.0, 0.0, 0.0, 1.0);
ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+LINE_HEIGHT);
glColor4d(0, 0, 0, 1);
ssglBitmapText(str, Vector::From(ox+5, oy-3+LINE_HEIGHT, 0));
uiCanvas->DrawRect(ox, ox+tw, oy, oy+LINE_HEIGHT,
/*fillColor=*/{ 255, 255, 150, 255 },
/*outlineColor=*/{ 0, 0, 0, 255 });
uiCanvas->DrawBitmapText(str, ox+5, oy-3+LINE_HEIGHT, { 0, 0, 0, 255 });
} else {
if(!hoveredIcon ||
(hoveredIcon != tooltippedIcon))
@ -504,44 +490,36 @@ Vector TextWindow::HsvToRgb(Vector hsv) {
return rgb;
}
uint8_t *TextWindow::HsvPattern2d() {
static uint8_t Texture[256*256*3];
static bool Init;
if(!Init) {
int i, j, p;
p = 0;
for(i = 0; i < 256; i++) {
for(j = 0; j < 256; j++) {
Vector hsv = Vector::From(6.0*i/255.0, 1.0*j/255.0, 1);
std::shared_ptr<Pixmap> TextWindow::HsvPattern2d(int w, int h) {
std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);
for(size_t j = 0; j < pixmap->height; j++) {
size_t p = pixmap->stride * j;
for(size_t i = 0; i < pixmap->width; i++) {
Vector hsv = Vector::From(6.0*i/(pixmap->width-1), 1.0*j/(pixmap->height-1), 1);
Vector rgb = HsvToRgb(hsv);
rgb = rgb.ScaledBy(255);
Texture[p++] = (uint8_t)rgb.x;
Texture[p++] = (uint8_t)rgb.y;
Texture[p++] = (uint8_t)rgb.z;
pixmap->data[p++] = (uint8_t)rgb.x;
pixmap->data[p++] = (uint8_t)rgb.y;
pixmap->data[p++] = (uint8_t)rgb.z;
}
}
Init = true;
}
return Texture;
return pixmap;
}
uint8_t *TextWindow::HsvPattern1d(double h, double s) {
static uint8_t Texture[256*4];
int i, p;
p = 0;
for(i = 0; i < 256; i++) {
Vector hsv = Vector::From(6*h, s, 1.0*(255 - i)/255.0);
std::shared_ptr<Pixmap> TextWindow::HsvPattern1d(double hue, double sat, int w, int h) {
std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);
for(size_t i = 0; i < pixmap->height; i++) {
size_t p = i * pixmap->stride;
for(size_t j = 0; j < pixmap->width; j++) {
Vector hsv = Vector::From(6*hue, sat, 1.0*(pixmap->width - 1 - j)/pixmap->width);
Vector rgb = HsvToRgb(hsv);
rgb = rgb.ScaledBy(255);
Texture[p++] = (uint8_t)rgb.x;
Texture[p++] = (uint8_t)rgb.y;
Texture[p++] = (uint8_t)rgb.z;
// Needs a padding byte, to make things four-aligned
p++;
pixmap->data[p++] = (uint8_t)rgb.x;
pixmap->data[p++] = (uint8_t)rgb.y;
pixmap->data[p++] = (uint8_t)rgb.z;
}
return Texture;
}
return pixmap;
}
void TextWindow::ColorPickerDone() {
@ -549,7 +527,7 @@ void TextWindow::ColorPickerDone() {
EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()).c_str());
}
bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how, bool leftDown,
double x, double y)
{
bool mousePointerAsHand = false;
@ -597,10 +575,12 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
int bw = 6;
if(how == PAINT) {
glColor4d(0.2, 0.2, 0.2, 1);
ssglAxisAlignedQuad(px, pxm+bw, py, pym+bw);
glColor4d(0.0, 0.0, 0.0, 1);
ssglAxisAlignedQuad(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2));
uiCanvas->DrawRect(px, pxm+bw, py, pym+bw,
/*fillColor=*/{ 50, 50, 50, 255 },
/*outlineColor=*/{});
uiCanvas->DrawRect(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2),
/*fillColor=*/{ 0, 0, 0, 255 },
/*outlineColor=*/{});
} else {
if(x < px || x > pxm+(bw/2) ||
y < py || y > pym+(bw/2))
@ -639,8 +619,9 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j;
if(how == PAINT) {
glColor4d(CO(rgb), 1);
ssglAxisAlignedQuad(sx, sx+SIZE, sy, sy+SIZE);
uiCanvas->DrawRect(sx, sx+SIZE, sy, sy+SIZE,
/*fillColor=*/RGBf(rgb.x, rgb.y, rgb.z),
/*outlineColor=*/{});
} else if(how == CLICK) {
if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {
editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
@ -659,8 +640,9 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
hxm = hx + PITCH*7 + SIZE;
hym = hy + PITCH*2 + SIZE;
if(how == PAINT) {
ssglColorRGB(editControl.colorPicker.rgb);
ssglAxisAlignedQuad(hx, hxm, hy, hym);
uiCanvas->DrawRect(hx, hxm, hy, hym,
/*fillColor=*/editControl.colorPicker.rgb,
/*outlineColor=*/{});
} else if(how == CLICK) {
if(x >= hx && x <= hxm && y >= hy && y <= hym) {
ColorPickerDone();
@ -677,41 +659,13 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
hym = hy + PITCH*1 + SIZE;
// The one-dimensional thing to pick the color's value
if(how == PAINT) {
glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_1D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
uiCanvas->DrawPixmap(HsvPattern1d(editControl.colorPicker.h,
editControl.colorPicker.s,
hxm-hx, hym-hy),
hx, hy);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 256, 0,
GL_RGB, GL_UNSIGNED_BYTE,
HsvPattern1d(editControl.colorPicker.h,
editControl.colorPicker.s));
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0, 0);
glVertex2d(hx, hy);
glTexCoord2d(1, 0);
glVertex2d(hx, hym);
glTexCoord2d(1, 1);
glVertex2d(hxm, hym);
glTexCoord2d(0, 1);
glVertex2d(hxm, hy);
glEnd();
glDisable(GL_TEXTURE_2D);
double cx = hx+(hxm-hx)*(1 - editControl.colorPicker.v);
glColor4d(0, 0, 0, 1);
glLineWidth(1);
glBegin(GL_LINES);
glVertex2d(cx, hy);
glVertex2d(cx, hym);
glEnd();
int cx = hx+(int)((hxm-hx)*(1.0 - editControl.colorPicker.v));
uiCanvas->DrawLine(cx, hy, cx, hym, { 0, 0, 0, 255 });
} else if(how == CLICK ||
(how == HOVER && leftDown && editControl.colorPicker.picker1dActive))
{
@ -734,42 +688,12 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown,
hym = hy + PITCH*6 + SIZE;
// Two-dimensional thing to pick a color by hue and saturation
if(how == PAINT) {
glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
uiCanvas->DrawPixmap(HsvPattern2d(hxm-hx, hym-hy), hx, hy);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0,
GL_RGB, GL_UNSIGNED_BYTE, HsvPattern2d());
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0, 0);
glVertex2d(hx, hy);
glTexCoord2d(1, 0);
glVertex2d(hx, hym);
glTexCoord2d(1, 1);
glVertex2d(hxm, hym);
glTexCoord2d(0, 1);
glVertex2d(hxm, hy);
glEnd();
glDisable(GL_TEXTURE_2D);
glColor4d(1, 1, 1, 1);
glLineWidth(1);
double cx = hx+(hxm-hx)*editControl.colorPicker.h,
cy = hy+(hym-hy)*editControl.colorPicker.s;
glBegin(GL_LINES);
glVertex2d(cx - 5, cy);
glVertex2d(cx + 4, cy);
glVertex2d(cx, cy - 5);
glVertex2d(cx, cy + 4);
glEnd();
int cx = hx+(int)((hxm-hx)*editControl.colorPicker.h),
cy = hy+(int)((hym-hy)*editControl.colorPicker.s);
uiCanvas->DrawLine(cx - 5, cy, cx + 4, cy, { 255, 255, 255, 255 });
uiCanvas->DrawLine(cx, cy - 5, cx, cy + 4, { 255, 255, 255, 255 });
} else if(how == CLICK ||
(how == HOVER && leftDown && editControl.colorPicker.picker2dActive))
{
@ -797,22 +721,23 @@ void TextWindow::Paint() {
int width, height;
GetTextWindowSize(&width, &height);
// We would like things pixel-exact, to avoid shimmering.
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 1, 1);
Camera camera = {};
camera.width = width;
camera.height = height;
camera.LoadIdentity();
camera.offset.x = -(double)camera.width / 2.0;
camera.offset.y = -(double)camera.height / 2.0;
glTranslated(-1, 1, 0);
glScaled(2.0/width, -2.0/height, 1);
// Make things round consistently, avoiding exact integer boundary
glTranslated(-0.1, -0.1, 0);
OpenGl1Renderer canvas = {};
canvas.camera = camera;
canvas.BeginFrame();
canvas.UpdateProjection();
halfRows = height / (LINE_HEIGHT/2);
UiCanvas uiCanvas = {};
uiCanvas.canvas = &canvas;
uiCanvas.flip = true;
halfRows = camera.height / (LINE_HEIGHT/2);
int bottom = top[rows-1] + 2;
scrollPos = min(scrollPos, bottom - halfRows);
@ -821,21 +746,9 @@ void TextWindow::Paint() {
// Let's set up the scroll bar first
MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows);
// Create the bitmap font that we're going to use.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
// Now paint the window.
int r, c, a;
for(a = 0; a < 2; a++) {
if(a == 0) {
glBegin(GL_QUADS);
} else if(a == 1) {
glEnable(GL_TEXTURE_2D);
ssglInitializeBitmapFont();
glBegin(GL_QUADS);
}
for(r = 0; r < rows; r++) {
int ltop = top[r];
if(ltop < (scrollPos-1)) continue;
@ -847,31 +760,33 @@ void TextWindow::Paint() {
int fg = meta[r][c].fg;
int bg = meta[r][c].bg;
RgbaColor bgRgb = meta[r][c].bgRgb;
// On the first pass, all the background quads; on the next
// pass, all the foreground (i.e., font) quads.
if(a == 0) {
int bh = LINE_HEIGHT, adj = -2;
RgbaColor bgRgb = meta[r][c].bgRgb;
int bh = LINE_HEIGHT, adj = 0;
if(bg == 'z') {
glColor3f(bgRgb.redF(), bgRgb.greenF(), bgRgb.blueF());
bh = CHAR_HEIGHT;
adj += 2;
} else {
glColor3fv(&(bgColorTable[bg*3]));
bgRgb = RgbaColor::FromFloat(bgColorTable[bg*3+0],
bgColorTable[bg*3+1],
bgColorTable[bg*3+2]);
}
if(bg != 'd') {
// Move the quad down a bit, so that the descenders
// still have the correct background.
y += adj;
ssglAxisAlignedQuad(x, x + CHAR_WIDTH, y, y + bh, /*lone=*/false);
y -= adj;
uiCanvas.DrawRect(x, x + CHAR_WIDTH, y + adj, y + adj + bh,
/*fillColor=*/bgRgb, /*outlineColor=*/{});
}
} else if(a == 1) {
glColor3fv(&(fgColorTable[fg*3]));
RgbaColor fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],
fgColorTable[fg*3+1],
fgColorTable[fg*3+2]);
if(text[r][c] != ' ') {
ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT);
uiCanvas.DrawBitmapChar(text[r][c], x, y + CHAR_HEIGHT, fgRgb);
}
// If this is a link and it's hovered, then draw the
@ -903,29 +818,20 @@ void TextWindow::Paint() {
cs++;
}
glEnd();
// Always use the color of the rightmost character
// in the link, so that underline is consistent color
fg = meta[r][cf-1].fg;
glColor3fv(&(fgColorTable[fg*3]));
glDisable(GL_TEXTURE_2D);
glLineWidth(1);
glBegin(GL_LINES);
fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],
fgColorTable[fg*3+1],
fgColorTable[fg*3+2]);
int yp = y + CHAR_HEIGHT;
glVertex2d(LEFT_MARGIN + cs*CHAR_WIDTH, yp);
glVertex2d(LEFT_MARGIN + cf*CHAR_WIDTH, yp);
glEnd();
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
uiCanvas.DrawLine(LEFT_MARGIN + cs*CHAR_WIDTH, yp,
LEFT_MARGIN + cf*CHAR_WIDTH, yp,
fgRgb);
}
}
}
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
// The line to indicate the column of radio buttons that indicates the
@ -938,24 +844,24 @@ void TextWindow::Paint() {
int x = 29, y = 70 + LINE_HEIGHT;
y -= scrollPos*(LINE_HEIGHT/2);
glLineWidth(1);
glColor3fv(&(fgColorTable['t'*3]));
glBegin(GL_LINES);
glVertex2d(x, y);
glVertex2d(x, y+40);
glEnd();
RgbaColor color = RgbaColor::FromFloat(fgColorTable['t'*3+0],
fgColorTable['t'*3+1],
fgColorTable['t'*3+2]);
uiCanvas.DrawLine(x, y, x, y+40, color);
}
// The header has some icons that are drawn separately from the text
DrawOrHitTestIcons(PAINT, 0, 0);
DrawOrHitTestIcons(&uiCanvas, PAINT, 0, 0);
// And we may show a color picker for certain editable fields
DrawOrHitTestColorPicker(PAINT, false, 0, 0);
DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0);
canvas.EndFrame();
}
void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {
if(TextEditControlIsVisible() || GraphicsEditControlIsVisible()) {
if(DrawOrHitTestColorPicker(leftClick ? CLICK : HOVER, leftDown, x, y))
if(DrawOrHitTestColorPicker(NULL, leftClick ? CLICK : HOVER, leftDown, x, y))
{
return;
}
@ -969,7 +875,7 @@ void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {
return;
}
DrawOrHitTestIcons(leftClick ? CLICK : HOVER, x, y);
DrawOrHitTestIcons(NULL, leftClick ? CLICK : HOVER, x, y);
GraphicsWindow::Selection ps = SS.GW.hover;
SS.GW.hover.Clear();

View File

@ -11,7 +11,7 @@ struct ToolIcon {
std::string name;
Command command;
std::string tooltip;
Pixmap pixmap;
std::shared_ptr<Pixmap> pixmap;
};
static ToolIcon Toolbar[] = {
{ "line", Command::LINE_SEGMENT, "Sketch line segment", {} },
@ -53,8 +53,8 @@ static ToolIcon Toolbar[] = {
{ "ontoworkplane", Command::ONTO_WORKPLANE, "Align view to active workplane", {} },
};
void GraphicsWindow::ToolbarDraw() {
ToolbarDrawOrHitTest(0, 0, /*paint=*/true, NULL);
void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) {
ToolbarDrawOrHitTest(0, 0, canvas, NULL);
}
bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
@ -62,7 +62,7 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
y += ((int)height/2);
Command nh = Command::NONE;
bool withinToolbar = ToolbarDrawOrHitTest(x, y, /*paint=*/false, &nh);
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &nh);
if(!withinToolbar) nh = Command::NONE;
if(nh != toolbarTooltipped) {
@ -89,7 +89,7 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
y += ((int)height/2);
Command nh = Command::NONE;
bool withinToolbar = ToolbarDrawOrHitTest(x, y, /*paint=*/false, &nh);
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &nh);
// They might have clicked within the toolbar, but not on a button.
if(withinToolbar && nh != Command::NONE) {
for(int i = 0; SS.GW.menu[i].level >= 0; i++) {
@ -103,7 +103,7 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
}
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
bool paint, Command *menuHit)
UiCanvas *canvas, Command *menuHit)
{
int i;
int x = 17, y = (int)(height - 52);
@ -115,23 +115,15 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
bool withinToolbar =
(mx >= aleft && mx <= aright && my <= atop && my >= abot);
if(!paint && !withinToolbar) {
if(!canvas && !withinToolbar) {
// This gets called every MouseMove event, so return quickly.
return false;
}
if(paint) {
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glTranslated(-1, -1, 0);
glScaled(2.0/width, 2.0/height, 0);
glDisable(GL_LIGHTING);
double c = 30.0/255;
glColor4d(c, c, c, 1.0);
ssglAxisAlignedQuad(aleft, aright, atop, abot);
if(canvas) {
canvas->DrawRect(aleft, aright, atop, abot,
/*fillColor=*/{ 30, 30, 30, 255 },
/*outlineColor=*/{});
}
struct {
@ -149,37 +141,34 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
}
y -= 16;
if(paint) {
if(canvas) {
// Draw a separator bar in a slightly different color.
int divw = 30, divh = 2;
glColor4d(0.17, 0.17, 0.17, 1);
x += 16;
y += 24;
ssglAxisAlignedQuad(x+divw, x-divw, y+divh, y-divh);
x -= 16;
y -= 24;
canvas->DrawRect(x+16+divw, x+16-divw, y+24+divh, y+24-divh,
/*fillColor=*/{ 45, 45, 45, 255 },
/*outlineColor=*/{});
}
continue;
}
if(icon.pixmap.IsEmpty()) {
icon.pixmap = LoadPNG("icons/graphics-window/" + icon.name + ".png");
if(icon.pixmap == nullptr) {
icon.pixmap = LoadPng("icons/graphics-window/" + icon.name + ".png");
}
if(paint) {
glColor4d(0, 0, 0, 1.0);
Point2d o = { (double)(x - icon.pixmap.width / 2),
(double)(y - icon.pixmap.height / 2) };
ssglDrawPixmap(icon.pixmap, o, /*flip=*/true);
if(canvas) {
canvas->DrawPixmap(icon.pixmap,
x - icon.pixmap->width / 2,
y - icon.pixmap->height / 2);
if(toolbarHovered == icon.command ||
(pending.operation == Pending::COMMAND &&
pending.command == icon.command)) {
// Highlight the hovered or pending item.
glColor4d(1, 1, 0, 0.3);
int boxhw = 15;
ssglAxisAlignedQuad(x+boxhw, x-boxhw, y+boxhw, y-boxhw);
canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw,
/*fillColor=*/{ 255, 255, 0, 75 },
/*outlineColor=*/{});
}
if(toolbarTooltipped == icon.command) {
@ -208,10 +197,9 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
}
}
if(paint) {
if(canvas) {
// Do this last so that nothing can draw over it.
if(toolTip.show) {
ssglInitializeBitmapFont();
std::string str = toolTip.str;
for(i = 0; SS.GW.menu[i].level >= 0; i++) {
@ -224,24 +212,15 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
}
}
int tw = str.length() * (SS.TW.CHAR_WIDTH - 1) + 10,
int tw = BitmapFont::Builtin()->GetWidth(str) * 8 + 10,
th = SS.TW.LINE_HEIGHT + 2;
double ox = toolbarMouseX + 3, oy = toolbarMouseY + 3;
glLineWidth(1);
glColor4d(1.0, 1.0, 0.6, 1.0);
ssglAxisAlignedQuad(ox, ox+tw, oy, oy+th);
glColor4d(0.0, 0.0, 0.0, 1.0);
ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+th);
glColor4d(0, 0, 0, 1);
glPushMatrix();
glTranslated(ox+5, oy+3, 0);
glScaled(1, -1, 1);
ssglBitmapText(str, Vector::From(0, 0, 0));
glPopMatrix();
int ox = toolbarMouseX + 3, oy = toolbarMouseY + 3;
canvas->DrawRect(ox, ox+tw, oy, oy+th,
/*fillColor=*/{ 255, 255, 150, 255 },
/*outlineColor=*/{ 0, 0, 0, 255 });
canvas->DrawBitmapText(str, ox+5, oy+4, { 0, 0, 0, 255 });
}
ssglDepthRangeLockToFront(false);
}
return withinToolbar;

View File

@ -8,11 +8,6 @@
#ifndef __UI_H
#define __UI_H
#ifdef WIN32
// winnt.h
#undef DELETE
#endif
// This table describes the top-level menus in the graphics winodw.
enum class Command : uint32_t {
NONE = 0,
@ -202,11 +197,13 @@ public:
bool *var;
const char *iconName;
const char *tip;
Pixmap icon;
std::shared_ptr<Pixmap> icon;
} HideShowIcon;
static HideShowIcon hideShowIcons[];
static bool SPACER;
void Draw(Canvas *canvas);
// These are called by the platform-specific code.
void Paint();
void MouseEvent(bool isClick, bool leftDown, double x, double y);
@ -219,16 +216,18 @@ public:
HOVER = 1,
CLICK = 2
};
void DrawOrHitTestIcons(DrawOrHitHow how, double mx, double my);
void DrawOrHitTestIcons(UiCanvas *canvas, DrawOrHitHow how,
double mx, double my);
void TimerCallback();
Point2d oldMousePos;
HideShowIcon *hoveredIcon, *tooltippedIcon;
Vector HsvToRgb(Vector hsv);
uint8_t *HsvPattern2d();
uint8_t *HsvPattern1d(double h, double s);
std::shared_ptr<Pixmap> HsvPattern2d(int w, int h);
std::shared_ptr<Pixmap> HsvPattern1d(double hue, double sat, int w, int h);
void ColorPickerDone();
bool DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, double x, double y);
bool DrawOrHitTestColorPicker(UiCanvas *canvas, DrawOrHitHow how,
bool leftDown, double x, double y);
void Init();
void MakeColorTable(const Color *in, float *out);
@ -467,8 +466,6 @@ public:
void EditControlDone(const char *s);
};
#define SELECTION_RADIUS 10.0
class GraphicsWindow {
public:
void Init();
@ -546,6 +543,9 @@ public:
bool active;
} context;
Camera GetCamera() const;
Lighting GetLighting() const;
void NormalizeProjectionVectors();
Point2d ProjectPoint(Vector p);
Vector ProjectPoint3(Vector p);
@ -649,7 +649,7 @@ public:
hConstraint constraint;
bool emphasized;
void Draw();
void Draw(bool isHovered, Canvas *canvas);
void Clear();
bool IsEmpty();
@ -702,8 +702,8 @@ public:
int64_t contextMenuCancelTime;
// The toolbar, in toolbar.cpp
bool ToolbarDrawOrHitTest(int x, int y, bool paint, Command *menuHit);
void ToolbarDraw();
bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, Command *menuHit);
void ToolbarDraw(UiCanvas *canvas);
bool ToolbarMouseMoved(int x, int y);
bool ToolbarMouseDown(int x, int y);
static void TimerCallback();
@ -726,6 +726,7 @@ public:
void ToggleBool(bool *v);
bool showSnapGrid;
void DrawSnapGrid(Canvas *canvas);
void AddPointToDraggedList(hEntity hp);
void StartDraggingByEntity(hEntity he);
@ -733,6 +734,9 @@ public:
void UpdateDraggedNum(Vector *pos, double mx, double my);
void UpdateDraggedPoint(hEntity hp, double mx, double my);
void DrawPersistent(Canvas *canvas);
void Draw(Canvas *canvas);
// These are called by the platform-specific code.
void Paint();
void MouseMoved(double x, double y, bool leftDown, bool middleDown,

View File

@ -83,7 +83,6 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) {
for(i = 0; i < SK.constraint.n; i++) {
Constraint *src = &(SK.constraint.elem[i]);
Constraint dest = *src;
dest.dogd = {};
ut->constraint.Add(&dest);
}
for(i = 0; i < SK.param.n; i++) {

View File

@ -1019,6 +1019,23 @@ double Point2d::DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegm
return DistanceTo(closest);
}
double Point2d::DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const {
double m = dp.x*dp.x + dp.y*dp.y;
if(m < LENGTH_EPS*LENGTH_EPS) return VERY_POSITIVE;
Point2d n = dp.Normal().WithMagnitude(1.0);
double dist = n.Dot(*this) - n.Dot(p0);
if(asSegment) {
// Let our line be p = p0 + t*dp, for a scalar t from 0 to 1
double t = (dp.x*(x - p0.x) + dp.y*(y - p0.y))/m;
double sign = (dist > 0.0) ? 1.0 : -1.0;
if(t < 0.0) return DistanceTo(p0) * sign;
if(t > 1.0) return DistanceTo(p0.Plus(dp)) * sign;
}
return dist;
}
Point2d Point2d::Normal() const {
return { y, -x };
}