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
parent
bd2da7fe3f
commit
e7c8c1c8f2
|
@ -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
|
||||
|
|
58
src/bsp.cpp
58
src/bsp.cpp
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
649
src/draw.cpp
649
src/draw.cpp
|
@ -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,107 +476,128 @@ 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;
|
||||
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();
|
||||
wv = norm->NormalV();
|
||||
wn = norm->NormalN();
|
||||
|
||||
double g = SS.gridSpacing;
|
||||
|
||||
double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
|
||||
vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
|
||||
int a;
|
||||
for(a = 0; a < 4; a++) {
|
||||
// Ideally, we would just do +/- half the width and height; but
|
||||
// allow some extra slop for rounding.
|
||||
Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g),
|
||||
vert = projUp. ScaledBy((0.6*height)/scale + 2*g);
|
||||
if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
|
||||
if(a == 1 || a == 3) vert = vert. ScaledBy(-1);
|
||||
Vector tp = horiz.Plus(vert).Minus(offset);
|
||||
|
||||
// Project the point into our grid plane, normal to the screen
|
||||
// (not to the grid plane). If the plane is on edge then this is
|
||||
// impossible so don't try to draw the grid.
|
||||
bool parallel;
|
||||
Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
|
||||
wn, wn.Dot(wp),
|
||||
tp, tp.Plus(n),
|
||||
¶llel);
|
||||
if(parallel) return;
|
||||
|
||||
tpp = tpp.Minus(wp);
|
||||
double uu = tpp.Dot(wu),
|
||||
vv = tpp.Dot(wv);
|
||||
|
||||
umin = min(uu, umin);
|
||||
umax = max(uu, umax);
|
||||
vmin = min(vv, vmin);
|
||||
vmax = max(vv, vmax);
|
||||
}
|
||||
|
||||
int i, j, i0, i1, j0, j1;
|
||||
|
||||
i0 = (int)(umin / g);
|
||||
i1 = (int)(umax / g);
|
||||
j0 = (int)(vmin / g);
|
||||
j1 = (int)(vmax / g);
|
||||
|
||||
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);
|
||||
|
||||
for(i = i0 + 1; i < i1; i++) {
|
||||
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++) {
|
||||
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::Paint() {
|
||||
int i;
|
||||
havePainted = true;
|
||||
void GraphicsWindow::DrawPersistent(Canvas *canvas) {
|
||||
// Draw the active group; this does stuff like the mesh and edges.
|
||||
SK.GetGroup(activeGroup)->Draw(canvas);
|
||||
|
||||
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();
|
||||
// Now draw the entities.
|
||||
for(Entity &e : SK.entity) {
|
||||
if(SS.GW.showHdnLines) {
|
||||
e.Draw(Entity::DrawAs::HIDDEN, canvas);
|
||||
}
|
||||
e.Draw(Entity::DrawAs::DEFAULT, canvas);
|
||||
}
|
||||
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;
|
||||
// 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(hGroup hg : SK.groupOrder) {
|
||||
Group *g = SK.GetGroup(hg);
|
||||
if(!(g->IsVisible())) continue;
|
||||
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(projRight, projUp, n);
|
||||
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(projRight, projUp, n);
|
||||
origin = origin.ScaleOutOfCsys(camera.projRight, camera.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)));
|
||||
// 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);
|
||||
}
|
||||
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
|
||||
|
@ -548,222 +605,74 @@ void GraphicsWindow::Paint() {
|
|||
// 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);
|
||||
if(showSnapGrid) DrawSnapGrid(canvas);
|
||||
|
||||
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);
|
||||
// Draw all the things that don't change when we rotate.
|
||||
DrawPersistent(canvas);
|
||||
|
||||
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 wu, wv, wn, wp;
|
||||
wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
|
||||
wu = norm->NormalU();
|
||||
wv = norm->NormalV();
|
||||
wn = norm->NormalN();
|
||||
|
||||
double g = SS.gridSpacing;
|
||||
|
||||
double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
|
||||
vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
|
||||
int a;
|
||||
for(a = 0; a < 4; a++) {
|
||||
// Ideally, we would just do +/- half the width and height; but
|
||||
// allow some extra slop for rounding.
|
||||
Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g),
|
||||
vert = projUp. ScaledBy((0.6*height)/scale + 2*g);
|
||||
if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
|
||||
if(a == 1 || a == 3) vert = vert. ScaledBy(-1);
|
||||
Vector tp = horiz.Plus(vert).Minus(offset);
|
||||
|
||||
// Project the point into our grid plane, normal to the screen
|
||||
// (not to the grid plane). If the plane is on edge then this is
|
||||
// impossible so don't try to draw the grid.
|
||||
bool parallel;
|
||||
Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
|
||||
wn, wn.Dot(wp),
|
||||
tp, tp.Plus(n),
|
||||
¶llel);
|
||||
if(parallel) goto nogrid;
|
||||
|
||||
tpp = tpp.Minus(wp);
|
||||
double uu = tpp.Dot(wu),
|
||||
vv = tpp.Dot(wv);
|
||||
|
||||
umin = min(uu, umin);
|
||||
umax = max(uu, umax);
|
||||
vmin = min(vv, vmin);
|
||||
vmax = max(vv, vmax);
|
||||
}
|
||||
|
||||
int i, j, i0, i1, j0, j1;
|
||||
|
||||
i0 = (int)(umin / g);
|
||||
i1 = (int)(umax / g);
|
||||
j0 = (int)(vmin / g);
|
||||
j1 = (int)(vmax / g);
|
||||
|
||||
if(i0 > i1 || i1 - i0 > 400) goto nogrid;
|
||||
if(j0 > j1 || j1 - j0 > 400) goto nogrid;
|
||||
|
||||
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)));
|
||||
}
|
||||
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:;
|
||||
// Draw the polygon errors.
|
||||
if(SS.checkClosedContour) {
|
||||
SK.GetGroup(activeGroup)->DrawPolyError(canvas);
|
||||
}
|
||||
|
||||
// Draw the active group; this does stuff like the mesh and edges.
|
||||
(SK.GetGroup(activeGroup))->Draw();
|
||||
|
||||
// Now draw the entities.
|
||||
if(SS.GW.showHdnLines) {
|
||||
ssglDepthRangeOffset(2);
|
||||
glDepthFunc(GL_GREATER);
|
||||
Entity::DrawAll(/*drawAsHidden=*/true);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
}
|
||||
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]);
|
||||
if(!(g->IsVisible())) continue;
|
||||
g->DrawFilledPaths();
|
||||
}
|
||||
|
||||
|
||||
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(),
|
||||
p.Plus(u.ScaledBy(40/scale)).Plus(
|
||||
v.ScaledBy(-(Style::DefaultTextHeight())/scale)),
|
||||
u, v, NULL, NULL);
|
||||
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,
|
||||
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
|
@ -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();
|
||||
// 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);
|
||||
if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
|
||||
if(dogd.drawing)
|
||||
ssglColorRGB(RGBi(0, 0, f));
|
||||
} else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
|
||||
if(dogd.drawing)
|
||||
ssglColorRGB(RGBi(f, 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.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.
|
||||
hRequest hr = h.request();
|
||||
uint8_t luma = (asReference) ? 255 : 100;
|
||||
if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
|
||||
stroke.color = RgbaColor::From(0, 0, luma);
|
||||
} else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
|
||||
stroke.color = RgbaColor::From(luma, 0, 0);
|
||||
} else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
122
src/export.cpp
122
src/export.cpp
|
@ -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;
|
||||
}
|
||||
|
|
17
src/file.cpp
17
src/file.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
835
src/glhelper.cpp
835
src/glhelper.cpp
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
// 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);
|
||||
void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
|
||||
if(!(SS.GW.showShaded || SS.GW.showHdnLines)) return;
|
||||
|
||||
// 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;
|
||||
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);
|
||||
|
||||
if(SS.GW.showShaded || SS.GW.showHdnLines) {
|
||||
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);
|
||||
// The back faces are drawn in red; should never seem them, since we
|
||||
// draw closed shells, so that's a debugging aid.
|
||||
Canvas::hFill hcfBack = {};
|
||||
if(SS.drawBackFaces && !displayMesh.isTransparent) {
|
||||
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.
|
||||
canvas->DrawMesh(displayMesh, hcfFront, hcfBack, hcsTriangle);
|
||||
break;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
|
||||
if(SS.GW.showEdges) {
|
||||
Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp);
|
||||
|
||||
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);
|
||||
}
|
||||
canvas->DrawFaces(displayMesh, faces, hcf);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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 = 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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -26,7 +26,7 @@ enum class BspClass : uint32_t {
|
|||
NEG = 101,
|
||||
COPLANAR = 200
|
||||
};
|
||||
|
||||
|
||||
enum class EdgeKind : uint32_t {
|
||||
NAKED_OR_SELF_INTER = 100,
|
||||
SELF_INTER = 200,
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
323
src/resource.cpp
323
src/resource.cpp
|
@ -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) {
|
||||
return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]);
|
||||
} else {
|
||||
return RgbaColor::From(pixel[0], pixel[1], pixel[2]);
|
||||
switch(format) {
|
||||
case Format::RGBA:
|
||||
return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
119
src/sketch.h
119
src/sketch.h
|
@ -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,
|
||||
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);
|
||||
|
||||
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(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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
324
src/textwin.cpp
324
src/textwin.cpp
|
@ -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);
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
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++;
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],
|
||||
fgColorTable[fg*3+1],
|
||||
fgColorTable[fg*3+2]);
|
||||
int yp = y + CHAR_HEIGHT;
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
|
|
34
src/ui.h
34
src/ui.h
|
@ -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,
|
||||
|
|
|
@ -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++) {
|
||||
|
|
17
src/util.cpp
17
src/util.cpp
|
@ -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 };
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue