Solvespace

Форк
0
/
export.cpp 
1268 строк · 43.8 Кб
1
//-----------------------------------------------------------------------------
2
// The 2d vector output stuff that isn't specific to any particular file
3
// format: getting the appropriate lines and curves, performing hidden line
4
// removal, calculating bounding boxes, and so on. Also raster and triangle
5
// mesh output.
6
//
7
// Copyright 2008-2013 Jonathan Westhues.
8
//-----------------------------------------------------------------------------
9
#include "solvespace.h"
10
#include "config.h"
11

12
void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) {
13
    Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
14
    gn = gn.WithMagnitude(1);
15

16
    Group *g = SK.GetGroup(SS.GW.activeGroup);
17
    g->GenerateDisplayItems();
18
    if(g->displayMesh.IsEmpty()) {
19
        Error(_("No solid model present; draw one with extrudes and revolves, "
20
                "or use Export 2d View to export bare lines and curves."));
21
        return;
22
    }
23

24
    // The plane in which the exported section lies; need this because we'll
25
    // reorient from that plane into the xy plane before exporting.
26
    Vector origin, u, v, n;
27
    double d;
28

29
    SS.GW.GroupSelection();
30
    auto const &gs = SS.GW.gs;
31
    if((gs.n == 0 && g->activeWorkplane != Entity::FREE_IN_3D)) {
32
        Entity *wrkpl = SK.GetEntity(g->activeWorkplane);
33
        origin = wrkpl->WorkplaneGetOffset();
34
        n = wrkpl->Normal()->NormalN();
35
        u = wrkpl->Normal()->NormalU();
36
        v = wrkpl->Normal()->NormalV();
37
    } else if(gs.n == 1 && gs.faces == 1) {
38
        Entity *face = SK.GetEntity(gs.entity[0]);
39
        origin = face->FaceGetPointNum();
40
        n = face->FaceGetNormalNum();
41
        if(n.Dot(gn) < 0) n = n.ScaledBy(-1);
42
        u = n.Normal(0);
43
        v = n.Normal(1);
44
    } else if(gs.n == 3 && gs.vectors == 2 && gs.points == 1) {
45
        Vector ut = SK.GetEntity(gs.entity[0])->VectorGetNum(),
46
               vt = SK.GetEntity(gs.entity[1])->VectorGetNum();
47
        ut = ut.WithMagnitude(1);
48
        vt = vt.WithMagnitude(1);
49

50
        if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) {
51
            swap(ut, vt);
52
        }
53
        if(SS.GW.projRight.Dot(ut) < 0) ut = ut.ScaledBy(-1);
54
        if(SS.GW.projUp.   Dot(vt) < 0) vt = vt.ScaledBy(-1);
55

56
        origin = SK.GetEntity(gs.point[0])->PointGetNum();
57
        n = ut.Cross(vt);
58
        u = ut.WithMagnitude(1);
59
        v = (n.Cross(u)).WithMagnitude(1);
60
    } else {
61
        Error(_("Bad selection for export section. Please select:\n\n"
62
                "    * nothing, with an active workplane "
63
                          "(workplane is section plane)\n"
64
                "    * a face (section plane through face)\n"
65
                "    * a point and two line segments "
66
                          "(plane through point and parallel to lines)\n"));
67
        return;
68
    }
69
    SS.GW.ClearSelection();
70

71
    n = n.WithMagnitude(1);
72
    d = origin.Dot(n);
73

74
    SEdgeList el = {};
75
    SBezierList bl = {};
76

77
    // If there's a mesh, then grab the edges from it.
78
    g->runningMesh.MakeEdgesInPlaneInto(&el, n, d);
79

80
    // If there's a shell, then grab the edges and possibly Beziers.
81
    bool export_as_pwl = SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS;
82
    g->runningShell.MakeSectionEdgesInto(n, d, &el, export_as_pwl ? NULL : &bl);
83

84
    // All of these are solid model edges, so use the appropriate style.
85
    SEdge *se;
86
    for(se = el.l.First(); se; se = el.l.NextAfter(se)) {
87
        se->auxA = Style::SOLID_EDGE;
88
    }
89
    SBezier *sb;
90
    for(sb = bl.l.First(); sb; sb = bl.l.NextAfter(sb)) {
91
        sb->auxA = Style::SOLID_EDGE;
92
    }
93

94
    // Remove all overlapping edges/beziers to merge the areas they describe.
95
    el.CullExtraneousEdges(/*both=*/true);
96
    bl.CullIdenticalBeziers(/*both=*/true);
97

98
    // Collect lines and beziers with custom style & export.
99
    for(auto &ent : SK.entity) {
100
        Entity *e = &ent;
101
        if (!e->IsVisible()) continue;
102
        if (e->style.v < Style::FIRST_CUSTOM) continue;
103
        if (!Style::Exportable(e->style.v)) continue;
104
        if (!e->IsInPlane(n,d)) continue;
105
        if (export_as_pwl) {
106
            e->GenerateEdges(&el);
107
        } else {
108
            e->GenerateBezierCurves(&bl);
109
        }
110
    }
111

112
    // Only remove half of the overlapping edges/beziers to support TTF Stick Fonts.
113
    el.CullExtraneousEdges(/*both=*/false);
114
    bl.CullIdenticalBeziers(/*both=*/false);
115

116
    // And write the edges.
117
    VectorFileWriter *out = VectorFileWriter::ForFile(filename);
118
    if(out) {
119
        // parallel projection (no perspective), and no mesh
120
        ExportLinesAndMesh(&el, &bl, NULL,
121
                           u, v, n, origin, 0,
122
                           out);
123
    }
124
    el.Clear();
125
    bl.Clear();
126
}
127

128
// This is an awful temporary hack to replace Constraint::GetEdges until we have proper
129
// export through Canvas.
130
class GetEdgesCanvas : public Canvas {
131
public:
132
    Camera     camera;
133
    SEdgeList *edges;
134

135
    const Camera &GetCamera() const override {
136
        return camera;
137
    }
138

139
    void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override {
140
        edges->AddEdge(a, b, Style::CONSTRAINT);
141
    }
142
    void DrawEdges(const SEdgeList &el, hStroke hcs) override {
143
        for(const SEdge &e : el.l) {
144
            edges->AddEdge(e.a, e.b, Style::CONSTRAINT);
145
        }
146
    }
147
    void DrawVectorText(const std::string &text, double height,
148
                        const Vector &o, const Vector &u, const Vector &v,
149
                        hStroke hcs) override {
150
        auto traceEdge = [&](Vector a, Vector b) { edges->AddEdge(a, b, Style::CONSTRAINT); };
151
        VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera);
152
    }
153

154
    void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
155
                  hFill hcf) override {
156
        // Do nothing
157
    }
158

159
    bool DrawBeziers(const SBezierList &bl, hStroke hcs) override {
160
        ssassert(false, "Not implemented");
161
    }
162
    void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override {
163
        ssassert(false, "Not implemented");
164
    }
165
    void DrawPoint(const Vector &o, hStroke hcs) override {
166
        ssassert(false, "Not implemented");
167
    }
168
    void DrawPolygon(const SPolygon &p, hFill hcf) override {
169
        ssassert(false, "Not implemented");
170
    }
171
    void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) override {
172
        ssassert(false, "Not implemented");
173
    }
174
    void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override {
175
        ssassert(false, "Not implemented");
176
    }
177
    void DrawPixmap(std::shared_ptr<const Pixmap> pm,
178
                            const Vector &o, const Vector &u, const Vector &v,
179
                            const Point2d &ta, const Point2d &tb, hFill hcf) override {
180
        ssassert(false, "Not implemented");
181
    }
182
    void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {
183
        ssassert(false, "Not implemented");
184
    }
185
};
186

187
void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe) {
188
    SEdgeList edges = {};
189
    SBezierList beziers = {};
190

191
    VectorFileWriter *out = VectorFileWriter::ForFile(filename);
192
    if(!out) return;
193

194
    SS.exportMode = true;
195
    GenerateAll(Generate::ALL);
196

197
    SMesh *sm = NULL;
198
    if(SS.GW.showShaded || SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::VISIBLE) {
199
        Group *g = SK.GetGroup(SS.GW.activeGroup);
200
        g->GenerateDisplayItems();
201
        sm = &(g->displayMesh);
202
    }
203
    if(sm && sm->IsEmpty()) {
204
        sm = NULL;
205
    }
206

207
    for(auto &entity : SK.entity) {
208
        Entity *e = &entity;
209
        if(!e->IsVisible()) continue;
210

211
        if(SS.exportPwlCurves || sm || fabs(SS.exportOffset) > LENGTH_EPS)
212
        {
213
            // We will be doing hidden line removal, which we can't do on
214
            // exact curves; so we need things broken down to pwls. Same
215
            // problem with cutter radius compensation.
216
            e->GenerateEdges(&edges);
217
        } else {
218
            e->GenerateBezierCurves(&beziers);
219
        }
220
    }
221

222
    if(SS.GW.showEdges || SS.GW.showOutlines) {
223
        Group *g = SK.GetGroup(SS.GW.activeGroup);
224
        g->GenerateDisplayItems();
225
        if(SS.GW.showEdges) {
226
            g->displayOutlines.ListTaggedInto(&edges, Style::SOLID_EDGE);
227
        }
228
    }
229

230
    if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW ) {
231
        if(!out->OutputConstraints(&SK.constraint)) {
232
            GetEdgesCanvas canvas = {};
233
            canvas.camera = SS.GW.GetCamera();
234
            canvas.edges  = &edges;
235

236
            // The output format cannot represent constraints directly,
237
            // so convert them to edges.
238
            for(Constraint &c : SK.constraint) {
239
                c.Draw(Constraint::DrawAs::DEFAULT, &canvas);
240
            }
241

242
            canvas.Clear();
243
        }
244
    }
245

246
    if(exportWireframe) {
247
        Vector u = Vector::From(1.0, 0.0, 0.0),
248
               v = Vector::From(0.0, 1.0, 0.0),
249
               n = Vector::From(0.0, 0.0, 1.0),
250
               origin = Vector::From(0.0, 0.0, 0.0);
251
        double cameraTan = 0.0,
252
               scale = 1.0;
253

254
        out->SetModelviewProjection(u, v, n, origin, cameraTan, scale);
255

256
        ExportWireframeCurves(&edges, &beziers, out);
257
    } else {
258
        Vector u = SS.GW.projRight,
259
               v = SS.GW.projUp,
260
               n = u.Cross(v),
261
               origin = SS.GW.offset.ScaledBy(-1);
262

263
        out->SetModelviewProjection(u, v, n, origin,
264
                                    SS.CameraTangent()*SS.GW.scale, SS.exportScale);
265

266
        ExportLinesAndMesh(&edges, &beziers, sm,
267
                           u, v, n, origin, SS.CameraTangent()*SS.GW.scale,
268
                           out);
269

270
        if(!out->HasCanvasSize()) {
271
            // These file formats don't have a canvas size, so they just
272
            // get exported in the raw coordinate system. So indicate what
273
            // that was on-screen.
274
            SS.justExportedInfo.showOrigin = true;
275
            SS.justExportedInfo.pt = origin;
276
            SS.justExportedInfo.u = u;
277
            SS.justExportedInfo.v = v;
278
        } else {
279
            SS.justExportedInfo.showOrigin = false;
280
        }
281

282
        SS.justExportedInfo.draw = true;
283
        GW.Invalidate();
284
    }
285

286
    edges.Clear();
287
    beziers.Clear();
288
}
289

290
void SolveSpaceUI::ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl,
291
                           VectorFileWriter *out)
292
{
293
    SBezierLoopSetSet sblss = {};
294
    SEdge *se;
295
    for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
296
        SBezier sb = SBezier::From(
297
                                (se->a).ScaledBy(1.0 / SS.exportScale),
298
                                (se->b).ScaledBy(1.0 / SS.exportScale));
299
        sblss.AddOpenPath(&sb);
300
    }
301

302
    sbl->ScaleSelfBy(1.0/SS.exportScale);
303
    SBezier *sb;
304
    for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
305
        sblss.AddOpenPath(sb);
306
    }
307

308
    out->OutputLinesAndMesh(&sblss, NULL);
309
    sblss.Clear();
310
}
311

312
void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
313
                                      Vector u, Vector v, Vector n,
314
                                      Vector origin, double cameraTan,
315
                                      VectorFileWriter *out)
316
{
317
    double s = 1.0 / SS.exportScale;
318

319
    // Project into the export plane; so when we're done, z doesn't matter,
320
    // and x and y are what goes in the DXF.
321
    for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
322
        // project into the specified csys, and apply export scale
323
        (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
324
        (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
325
    }
326

327
    if(sbl) {
328
        for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
329
            *b = b->InPerspective(u, v, n, origin, cameraTan);
330
            int i;
331
            for(i = 0; i <= b->deg; i++) {
332
                b->ctrl[i] = (b->ctrl[i]).ScaledBy(s);
333
            }
334
        }
335
    }
336

337
    // If cutter radius compensation is requested, then perform it now
338
    if(fabs(SS.exportOffset) > LENGTH_EPS) {
339
        // assemble those edges into a polygon, and clear the edge list
340
        SPolygon sp = {};
341
        sel->AssemblePolygon(&sp, NULL);
342
        sel->Clear();
343

344
        SPolygon compd = {};
345
        sp.normal = Vector::From(0, 0, -1);
346
        sp.FixContourDirections();
347
        sp.OffsetInto(&compd, SS.exportOffset*s);
348
        sp.Clear();
349

350
        compd.MakeEdgesInto(sel);
351
        compd.Clear();
352
    }
353

354
    // Now the triangle mesh; project, then build a BSP to perform
355
    // occlusion testing and generated the shaded surfaces.
356
    SMesh smp = {};
357
    if(sm) {
358
        Vector l0 = (SS.lightDir[0]).WithMagnitude(1),
359
               l1 = (SS.lightDir[1]).WithMagnitude(1);
360
        STriangle *tr;
361
        for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
362
            STriangle tt = *tr;
363
            tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
364
            tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
365
            tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
366

367
            // And calculate lighting for the triangle
368
            Vector n = tt.Normal().WithMagnitude(1);
369
            double lighting = min(1.0, SS.ambientIntensity +
370
                                  max(0.0, (SS.lightIntensity[0])*(n.Dot(l0))) +
371
                                  max(0.0, (SS.lightIntensity[1])*(n.Dot(l1))));
372
            double r = min(1.0, tt.meta.color.redF()   * lighting),
373
                   g = min(1.0, tt.meta.color.greenF() * lighting),
374
                   b = min(1.0, tt.meta.color.blueF()  * lighting);
375
            tt.meta.color = RGBf(r, g, b);
376
            smp.AddTriangle(&tt);
377
        }
378
    }
379

380
    SMesh sms = {};
381

382
    // We need the mesh for occlusion testing, but if we don't/can't export it,
383
    // don't generate it.
384
    if(SS.GW.showShaded && out->CanOutputMesh()) {
385
        // Use the BSP routines to generate the split triangles in paint order.
386
        SBsp3 *bsp = SBsp3::FromMesh(&smp);
387
        if(bsp) bsp->GenerateInPaintOrder(&sms);
388
        // And cull the back-facing triangles
389
        STriangle *tr;
390
        sms.l.ClearTags();
391
        for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) {
392
            Vector n = tr->Normal();
393
            if(n.z < 0) {
394
                tr->tag = 1;
395
            }
396
        }
397
        sms.l.RemoveTagged();
398
    }
399

400
    // And now we perform hidden line removal if requested
401
    SEdgeList hlrd = {};
402
    if(sm) {
403
        SKdNode *root = SKdNode::From(&smp);
404

405
        // Generate the edges where a curved surface turns from front-facing
406
        // to back-facing.
407
        if(SS.GW.showEdges || SS.GW.showOutlines) {
408
            root->MakeCertainEdgesInto(sel, EdgeKind::TURNING,
409
                                       /*coplanarIsInter=*/false, NULL, NULL,
410
                                       GW.showOutlines ? Style::OUTLINE : Style::SOLID_EDGE);
411
        }
412

413
        root->ClearTags();
414
        int cnt = 1234;
415

416
        SEdge *se;
417
        for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
418
            if(se->auxA == Style::CONSTRAINT) {
419
                // Constraints should not get hidden line removed; they're
420
                // always on top.
421
                hlrd.AddEdge(se->a, se->b, se->auxA);
422
                continue;
423
            }
424

425
            SEdgeList edges = {};
426
            // Split the original edge against the mesh
427
            edges.AddEdge(se->a, se->b, se->auxA);
428
            root->OcclusionTestLine(*se, &edges, cnt);
429
            if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::STIPPLED) {
430
                for(SEdge &se : edges.l) {
431
                    if(se.tag == 1) {
432
                        se.auxA = Style::HIDDEN_EDGE;
433
                    }
434
                }
435
            } else if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::INVISIBLE) {
436
                edges.l.RemoveTagged();
437
            }
438

439
            // the occlusion test splits unnecessarily; so fix those
440
            edges.MergeCollinearSegments(se->a, se->b);
441
            cnt++;
442
            // And add the results to our output
443
            SEdge *sen;
444
            for(sen = edges.l.First(); sen; sen = edges.l.NextAfter(sen)) {
445
                hlrd.AddEdge(sen->a, sen->b, sen->auxA);
446
            }
447
            edges.Clear();
448
        }
449

450
        sel = &hlrd;
451
    }
452

453
    // Clean up: remove overlapping line segments and
454
    // segments with zero-length projections.
455
    sel->l.ClearTags();
456
    for(int i = 0; i < sel->l.n; ++i) {
457
        SEdge *sei = &sel->l[i];
458
        hStyle hsi = { (uint32_t)sei->auxA };
459
        Style *si = Style::Get(hsi);
460
        if(sei->tag != 0) continue;
461

462
        // Remove segments with zero length projections.
463
        Vector ai = sei->a;
464
        ai.z = 0.0;
465
        Vector bi = sei->b;
466
        bi.z = 0.0;
467
        Vector di = bi.Minus(ai);
468
        if(fabs(di.x) < LENGTH_EPS && fabs(di.y) < LENGTH_EPS) {
469
            sei->tag = 1;
470
            continue;
471
        }
472

473
        for(int j = i + 1; j < sel->l.n; ++j) {
474
            SEdge *sej = &sel->l[j];
475
            if(sej->tag != 0) continue;
476

477
            Vector *pAj = &sej->a;
478
            Vector *pBj = &sej->b;
479

480
            // Remove segments with zero length projections.
481
            Vector aj = sej->a;
482
            aj.z = 0.0;
483
            Vector bj = sej->b;
484
            bj.z = 0.0;
485
            Vector dj = bj.Minus(aj);
486
            if(fabs(dj.x) < LENGTH_EPS && fabs(dj.y) < LENGTH_EPS) {
487
                sej->tag = 1;
488
                continue;
489
            }
490

491
            // Skip non-collinear segments.
492
            const double eps = 1e-6;
493
            if(aj.DistanceToLine(ai, di) > eps) continue;
494
            if(bj.DistanceToLine(ai, di) > eps) continue;
495

496
            double ta = aj.Minus(ai).Dot(di) / di.Dot(di);
497
            double tb = bj.Minus(ai).Dot(di) / di.Dot(di);
498
            if(ta > tb) {
499
                std::swap(pAj, pBj);
500
                std::swap(ta, tb);
501
            }
502

503
            hStyle hsj = { (uint32_t)sej->auxA };
504
            Style *sj = Style::Get(hsj);
505

506
            bool canRemoveI = sej->auxA == sei->auxA || si->zIndex < sj->zIndex;
507
            bool canRemoveJ = sej->auxA == sei->auxA || sj->zIndex < si->zIndex;
508

509
            if(canRemoveJ) {
510
                // j-segment inside i-segment
511
                if(ta > 0.0 - eps && tb < 1.0 + eps) {
512
                    sej->tag = 1;
513
                    continue;
514
                }
515

516
                // cut segment
517
                bool aInside = ta > 0.0 - eps && ta < 1.0 + eps;
518
                if(tb > 1.0 - eps && aInside) {
519
                    *pAj = sei->b;
520
                    continue;
521
                }
522

523
                // cut segment
524
                bool bInside = tb > 0.0 - eps && tb < 1.0 + eps;
525
                if(ta < 0.0 - eps && bInside) {
526
                    *pBj = sei->a;
527
                    continue;
528
                }
529

530
                // split segment
531
                if(ta < 0.0 - eps && tb > 1.0 + eps) {
532
                    sel->AddEdge(sei->b, *pBj, sej->auxA, sej->auxB);
533
                    *pBj = sei->a;
534
                    continue;
535
                }
536
            }
537

538
            if(canRemoveI) {
539
                // j-segment inside i-segment
540
                if(ta < 0.0 + eps && tb > 1.0 - eps) {
541
                    sei->tag = 1;
542
                    break;
543
                }
544

545
                // cut segment
546
                bool aInside = ta > 0.0 + eps && ta < 1.0 - eps;
547
                if(tb > 1.0 - eps && aInside) {
548
                    sei->b = *pAj;
549
                    i--;
550
                    break;
551
                }
552

553
                // cut segment
554
                bool bInside = tb > 0.0 + eps && tb < 1.0 - eps;
555
                if(ta < 0.0 + eps && bInside) {
556
                    sei->a = *pBj;
557
                    i--;
558
                    break;
559
                }
560

561
                // split segment
562
                if(ta > 0.0 + eps && tb < 1.0 - eps) {
563
                    sel->AddEdge(*pBj, sei->b, sei->auxA, sei->auxB);
564
                    sei->b = *pAj;
565
                    i--;
566
                    break;
567
                }
568
            }
569
        }
570
    }
571
    sel->l.RemoveTagged();
572

573
    // We kept the line segments and Beziers separate until now; but put them
574
    // all together, and also project everything into the xy plane, since not
575
    // all export targets ignore the z component of the points.
576
    ssassert(sbl != nullptr, "Adding line segments to beziers assumes bezier list is non-null.");
577
    for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
578
        SBezier sb = SBezier::From(e->a, e->b);
579
        sb.auxA = e->auxA;
580
        sbl->l.Add(&sb);
581
    }
582
    for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
583
        for(int i = 0; i <= b->deg; i++) {
584
            b->ctrl[i].z = 0;
585
        }
586
    }
587

588
    // If possible, then we will assemble these output curves into loops. They
589
    // will then get exported as closed paths.
590
    SBezierLoopSetSet sblss = {};
591
    SBezierLoopSet leftovers = {};
592
    SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0),
593
                                       Vector::From(1, 0, 0),
594
                                       Vector::From(0, 1, 0));
595
    SPolygon spxyz = {};
596
    bool allClosed;
597
    SEdge notClosedAt;
598
    sbl->l.ClearTags();
599
    sblss.FindOuterFacesFrom(sbl, &spxyz, &srf,
600
                             SS.ExportChordTolMm(),
601
                             &allClosed, &notClosedAt,
602
                             NULL, NULL,
603
                             &leftovers);
604
    sblss.l.Add(&leftovers);
605

606
    // Now write the lines and triangles to the output file
607
    out->OutputLinesAndMesh(&sblss, &sms);
608

609
    spxyz.Clear();
610
    sblss.Clear();
611
    smp.Clear();
612
    sms.Clear();
613
    hlrd.Clear();
614
}
615

616
double VectorFileWriter::MmToPts(double mm) {
617
    // 72 points in an inch
618
    return (mm/25.4)*72;
619
}
620

621
VectorFileWriter *VectorFileWriter::ForFile(const Platform::Path &filename) {
622
    VectorFileWriter *ret;
623
    bool needOpen = true;
624
    if(filename.HasExtension("dxf")) {
625
        static DxfFileWriter DxfWriter;
626
        ret = &DxfWriter;
627
        needOpen = false;
628
    } else if(filename.HasExtension("ps") || filename.HasExtension("eps")) {
629
        static EpsFileWriter EpsWriter;
630
        ret = &EpsWriter;
631
    } else if(filename.HasExtension("pdf")) {
632
        static PdfFileWriter PdfWriter;
633
        ret = &PdfWriter;
634
    } else if(filename.HasExtension("svg")) {
635
        static SvgFileWriter SvgWriter;
636
        ret = &SvgWriter;
637
    } else if(filename.HasExtension("plt") || filename.HasExtension("hpgl")) {
638
        static HpglFileWriter HpglWriter;
639
        ret = &HpglWriter;
640
    } else if(filename.HasExtension("step") || filename.HasExtension("stp")) {
641
        static Step2dFileWriter Step2dWriter;
642
        ret = &Step2dWriter;
643
    } else if(filename.HasExtension("txt") || filename.HasExtension("ngc")) {
644
        static GCodeFileWriter GCodeWriter;
645
        ret = &GCodeWriter;
646
    } else {
647
        Error("Can't identify output file type from file extension of "
648
        "filename '%s'; try "
649
        ".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, .ngc, "
650
        ".eps, or .ps.",
651
            filename.raw.c_str());
652
        return NULL;
653
    }
654
    ret->filename = filename;
655
    if(!needOpen) return ret;
656

657
    FILE *f = OpenFile(filename, "wb");
658
    if(!f) {
659
        Error("Couldn't write to '%s'", filename.raw.c_str());
660
        return NULL;
661
    }
662
    ret->f = f;
663
    return ret;
664
}
665

666
void VectorFileWriter::SetModelviewProjection(const Vector &u, const Vector &v, const Vector &n,
667
                                              const Vector &origin, double cameraTan,
668
                                              double scale) {
669
    this->u = u;
670
    this->v = v;
671
    this->n = n;
672
    this->origin = origin;
673
    this->cameraTan = cameraTan;
674
    this->scale = scale;
675
}
676

677
Vector VectorFileWriter::Transform(Vector &pos) const {
678
    return pos.InPerspective(u, v, n, origin, cameraTan).ScaledBy(1.0 / scale);
679
}
680

681
void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) {
682
    STriangle *tr;
683
    SBezier *b;
684

685
    // First calculate the bounding box.
686
    ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
687
    ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
688
    if(sm) {
689
        for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
690
            (tr->a).MakeMaxMin(&ptMax, &ptMin);
691
            (tr->b).MakeMaxMin(&ptMax, &ptMin);
692
            (tr->c).MakeMaxMin(&ptMax, &ptMin);
693
        }
694
    }
695
    if(sblss) {
696
        SBezierLoopSet *sbls;
697
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
698
            SBezierLoop *sbl;
699
            for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
700
                for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
701
                    for(int i = 0; i <= b->deg; i++) {
702
                        (b->ctrl[i]).MakeMaxMin(&ptMax, &ptMin);
703
                    }
704
                }
705
            }
706
        }
707
    }
708

709
    // And now we compute the canvas size.
710
    double s = 1.0 / SS.exportScale;
711
    if(SS.exportCanvasSizeAuto) {
712
        // It's based on the calculated bounding box; we grow it along each
713
        // boundary by the specified amount.
714
        ptMin.x -= s*SS.exportMargin.left;
715
        ptMax.x += s*SS.exportMargin.right;
716
        ptMin.y -= s*SS.exportMargin.bottom;
717
        ptMax.y += s*SS.exportMargin.top;
718
    } else {
719
        ptMin.x = (s*SS.exportCanvas.dx);
720
        ptMin.y = (s*SS.exportCanvas.dy);
721
        ptMax.x = ptMin.x + (s*SS.exportCanvas.width);
722
        ptMax.y = ptMin.y + (s*SS.exportCanvas.height);
723
    }
724

725
    StartFile();
726
    if(SS.exportBackgroundColor) {
727
        Background(SS.backgroundColor);
728
    }
729
    if(sm && SS.exportShadedTriangles) {
730
        for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
731
            Triangle(tr);
732
        }
733
    }
734
    if(sblss) {
735
        SBezierLoopSet *sbls;
736
        for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
737
            for(SBezierLoop *sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
738
                b = sbl->l.First();
739
                if(!b || !Style::Exportable(b->auxA)) continue;
740

741
                hStyle hs = { (uint32_t)b->auxA };
742
                Style *stl = Style::Get(hs);
743
                double lineWidth   = Style::WidthMm(b->auxA)*s;
744
                RgbaColor strokeRgb = Style::Color(hs, /*forExport=*/true);
745
                RgbaColor fillRgb   = Style::FillColor(hs, /*forExport=*/true);
746

747
                StartPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs);
748
                for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
749
                    Bezier(b);
750
                }
751
                FinishPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs);
752
            }
753
        }
754
    }
755
    FinishAndCloseFile();
756
}
757

758
void VectorFileWriter::BezierAsPwl(SBezier *sb) {
759
    List<Vector> lv = {};
760
    sb->MakePwlInto(&lv, SS.ExportChordTolMm());
761

762
    for(int i = 1; i < lv.n; i++) {
763
        SBezier sb = SBezier::From(lv[i-1], lv[i]);
764
        Bezier(&sb);
765
    }
766
    lv.Clear();
767
}
768

769
void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) {
770
    Vector t0 = sb->TangentAt(0), t1 = sb->TangentAt(1);
771
    // The curve is correct, and the first derivatives are correct, at the
772
    // endpoints.
773
    SBezier bnr = SBezier::From(
774
                        sb->Start(),
775
                        sb->Start().Plus(t0.ScaledBy(1.0/3)),
776
                        sb->Finish().Minus(t1.ScaledBy(1.0/3)),
777
                        sb->Finish());
778

779
    double tol = SS.ExportChordTolMm();
780
    // Arbitrary choice, but make it a little finer than pwl tolerance since
781
    // it should be easier to achieve that with the smooth curves.
782
    tol /= 2;
783

784
    bool closeEnough = true;
785
    int i;
786
    for(i = 1; i <= 3; i++) {
787
        double t = i/4.0;
788
        Vector p0 = sb->PointAt(t),
789
               pn = bnr.PointAt(t);
790
        double d = (p0.Minus(pn)).Magnitude();
791
        if(d > tol) {
792
            closeEnough = false;
793
        }
794
    }
795

796
    if(closeEnough || depth > 3) {
797
        Bezier(&bnr);
798
    } else {
799
        SBezier bef, aft;
800
        sb->SplitAt(0.5, &bef, &aft);
801
        BezierAsNonrationalCubic(&bef, depth+1);
802
        BezierAsNonrationalCubic(&aft, depth+1);
803
    }
804
}
805

806
//-----------------------------------------------------------------------------
807
// Export a triangle mesh, in the requested format.
808
//-----------------------------------------------------------------------------
809
void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) {
810
    SS.exportMode = true;
811
    GenerateAll(Generate::ALL);
812

813
    Group *g = SK.GetGroup(SS.GW.activeGroup);
814
    g->GenerateDisplayItems();
815

816
    SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
817
    if(m->IsEmpty()) {
818
        Error(_("Active group mesh is empty; nothing to export."));
819
        return;
820
    }
821

822
    FILE *f = OpenFile(filename, "wb");
823
    if(!f) {
824
        Error("Couldn't write to '%s'", filename.raw.c_str());
825
        return;
826
    }
827
    ShowNakedEdges(/*reportOnlyWhenNotOkay=*/true);
828
    if(filename.HasExtension("stl")) {
829
        ExportMeshAsStlTo(f, m);
830
    } else if(filename.HasExtension("obj")) {
831
        Platform::Path mtlFilename = filename.WithExtension("mtl");
832
        FILE *fMtl = OpenFile(mtlFilename, "wb");
833
        if(!fMtl) {
834
            Error("Couldn't write to '%s'", filename.raw.c_str());
835
            return;
836
        }
837

838
        fprintf(f, "mtllib %s\n", mtlFilename.FileName().c_str());
839
        ExportMeshAsObjTo(f, fMtl, m);
840

841
        fclose(fMtl);
842
    } else if(filename.HasExtension("js") ||
843
              filename.HasExtension("html")) {
844
        SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
845
        ExportMeshAsThreeJsTo(f, filename, m, e);
846
    } else if(filename.HasExtension("wrl")) {
847
        ExportMeshAsVrmlTo(f, filename, m);
848
    } else {
849
        Error("Can't identify output file type from file extension of "
850
              "filename '%s'; try .stl, .obj, .js, .html.", filename.raw.c_str());
851
    }
852

853
    fclose(f);
854

855
    SS.justExportedInfo.showOrigin = false;
856
    SS.justExportedInfo.draw = true;
857
    GW.Invalidate();
858
}
859

860
//-----------------------------------------------------------------------------
861
// Export the mesh as an STL file; it should always be vertex-to-vertex and
862
// not self-intersecting, so not much to do.
863
//-----------------------------------------------------------------------------
864
void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
865
    char str[80] = {};
866
    strcpy(str, "STL exported mesh");
867
    fwrite(str, 1, 80, f);
868

869
    uint32_t n = sm->l.n;
870
    fwrite(&n, 4, 1, f);
871

872
    double s = SS.exportScale;
873
    int i;
874
    for(i = 0; i < sm->l.n; i++) {
875
        STriangle *tr = &(sm->l[i]);
876
        Vector n = tr->Normal().WithMagnitude(1);
877
        float w;
878
        w = (float)n.x;           fwrite(&w, 4, 1, f);
879
        w = (float)n.y;           fwrite(&w, 4, 1, f);
880
        w = (float)n.z;           fwrite(&w, 4, 1, f);
881
        w = (float)((tr->a.x)/s); fwrite(&w, 4, 1, f);
882
        w = (float)((tr->a.y)/s); fwrite(&w, 4, 1, f);
883
        w = (float)((tr->a.z)/s); fwrite(&w, 4, 1, f);
884
        w = (float)((tr->b.x)/s); fwrite(&w, 4, 1, f);
885
        w = (float)((tr->b.y)/s); fwrite(&w, 4, 1, f);
886
        w = (float)((tr->b.z)/s); fwrite(&w, 4, 1, f);
887
        w = (float)((tr->c.x)/s); fwrite(&w, 4, 1, f);
888
        w = (float)((tr->c.y)/s); fwrite(&w, 4, 1, f);
889
        w = (float)((tr->c.z)/s); fwrite(&w, 4, 1, f);
890
        fputc(0, f);
891
        fputc(0, f);
892
    }
893
}
894

895
//-----------------------------------------------------------------------------
896
// Export the mesh as Wavefront OBJ format. This requires us to reduce all the
897
// identical vertices to the same identifier, so do that first.
898
//-----------------------------------------------------------------------------
899
void SolveSpaceUI::ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm) {
900
    std::map<RgbaColor, std::string, RgbaColorCompare> colors;
901
    for(const STriangle &t : sm->l) {
902
        RgbaColor color = t.meta.color;
903
        if(colors.find(color) == colors.end()) {
904
            std::string id = ssprintf("h%02x%02x%02x",
905
                                      color.red,
906
                                      color.green,
907
                                      color.blue);
908
            colors.emplace(color, id);
909
        }
910
        for(int i = 0; i < 3; i++) {
911
            fprintf(fObj, "v %.10f %.10f %.10f\n",
912
                    CO(t.vertices[i].ScaledBy(1 / SS.exportScale)));
913
        }
914
    }
915

916
    for(auto &it : colors) {
917
        fprintf(fMtl, "newmtl %s\n",
918
                it.second.c_str());
919
        fprintf(fMtl, "Kd %.3f %.3f %.3f\n",
920
                it.first.redF(), it.first.greenF(), it.first.blueF());
921
    }
922

923
    for(const STriangle &t : sm->l) {
924
        for(int i = 0; i < 3; i++) {
925
            Vector n = t.normals[i].WithMagnitude(1.0);
926
            fprintf(fObj, "vn %.10f %.10f %.10f\n",
927
                    CO(n));
928
        }
929
    }
930

931
    RgbaColor currentColor = {};
932
    for(int i = 0; i < sm->l.n; i++) {
933
        const STriangle &t = sm->l[i];
934
        if(!currentColor.Equals(t.meta.color)) {
935
            currentColor = t.meta.color;
936
            fprintf(fObj, "usemtl %s\n", colors[currentColor].c_str());
937
        }
938

939
        fprintf(fObj, "f %d//%d %d//%d %d//%d\n",
940
                i * 3 + 1, i * 3 + 1,
941
                i * 3 + 2, i * 3 + 2,
942
                i * 3 + 3, i * 3 + 3);
943
    }
944
}
945

946
//-----------------------------------------------------------------------------
947
// Export the mesh as a JavaScript script, which is compatible with Three.js.
948
//-----------------------------------------------------------------------------
949
void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
950
                                         SMesh *sm, SOutlineList *sol)
951
{
952
    SPointList spl = {};
953
    STriangle *tr;
954
    Vector bndl, bndh;
955

956
    const std::string THREE_FN("three-r111.min.js");
957
    const std::string HAMMER_FN("hammer-2.0.8.js");
958
    const std::string CONTROLS_FN("SolveSpaceControls.js");
959

960
    const char htmlbegin[] = R"(
961
<!DOCTYPE html>
962
<html lang="en">
963
  <head>
964
    <meta charset="utf-8"></meta>
965
    <title>Three.js Solvespace Mesh</title>
966
    <script id="%s">%s</script>
967
    <script id="%s">%s</script>
968
    <script id="%s">%s</script>
969
    <style type="text/css">
970
    body { margin: 0; overflow: hidden; }
971
    </style>
972
  </head>
973
  <body>
974
    <script>
975
)";
976
    const char htmlend[] = R"(
977
    document.body.appendChild(solvespace(solvespace_model_%s, {
978
        scale: %g,
979
        offset: new THREE.Vector3(%g, %g, %g),
980
        projUp: new THREE.Vector3(%g, %g, %g),
981
        projRight: new THREE.Vector3(%g, %g, %g)
982
    }));
983
    </script>
984
  </body>
985
</html>
986
)";
987

988
    // A default three.js viewer with OrthographicTrackballControls is
989
    // generated as a comment preceding the data.
990

991
    // x bounds should be the range of x or y, whichever
992
    // is larger, before aspect ratio correction is applied.
993
    // y bounds should be the range of x or y, whichever is
994
    // larger. No aspect ratio correction is applied.
995
    // Near plane should be 1.
996
    // Camera's z-position should be the range of z + 1 or the larger of
997
    // the x or y bounds, whichever is larger.
998
    // Far plane should be at least twice as much as the camera's
999
    // z-position.
1000
    // Edge projection bias should be about 1/500 of the far plane's distance.
1001
    // Further corrections will be applied to the z-position and far plane in
1002
    // the default viewer, but the defaults are fine for a model which
1003
    // only rotates about the world origin.
1004

1005
    sm->GetBounding(&bndh, &bndl);
1006
    double largerBoundXY = max((bndh.x - bndl.x), (bndh.y - bndl.y));
1007
    double largerBoundZ = max(largerBoundXY, (bndh.z - bndl.z + 1));
1008

1009
    std::string basename = filename.FileStem();
1010
    for(size_t i = 0; i < basename.length(); i++) {
1011
        if(!(isalnum(basename[i]) || ((unsigned)basename[i] >= 0x80))) {
1012
            basename[i] = '_';
1013
        }
1014
    }
1015

1016
    if(filename.HasExtension("html")) {
1017
        fprintf(f, htmlbegin,
1018
                THREE_FN.c_str(),
1019
                LoadStringFromGzip("threejs/" + THREE_FN + ".gz").c_str(),
1020
                HAMMER_FN.c_str(),
1021
                LoadStringFromGzip("threejs/" + HAMMER_FN + ".gz").c_str(),
1022
                CONTROLS_FN.c_str(),
1023
                LoadString("threejs/" + CONTROLS_FN).c_str());
1024
    }
1025

1026
    fprintf(f, "var solvespace_model_%s = {\n"
1027
               "  bounds: {\n"
1028
               "    x: %f, y: %f, near: %f, far: %f, z: %f, edgeBias: %f\n"
1029
               "  },\n",
1030
            basename.c_str(),
1031
            largerBoundXY,
1032
            largerBoundXY,
1033
            1.0,
1034
            largerBoundZ * 2,
1035
            largerBoundZ,
1036
            largerBoundZ / 250);
1037

1038
    // Output lighting information.
1039
    fputs("  lights: {\n"
1040
          "    d: [\n", f);
1041

1042
    // Directional.
1043
    int lightCount;
1044
    for(lightCount = 0; lightCount < 2; lightCount++) {
1045
        fprintf(f, "      {\n"
1046
                   "        intensity: %f, direction: [%f, %f, %f]\n"
1047
                   "      },\n",
1048
                SS.lightIntensity[lightCount],
1049
                CO(SS.lightDir[lightCount]));
1050
    }
1051

1052
    // Global Ambience.
1053
    fprintf(f, "    ],\n"
1054
               "    a: %f\n", SS.ambientIntensity);
1055

1056
    for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
1057
        spl.IncrementTagFor(tr->a);
1058
        spl.IncrementTagFor(tr->b);
1059
        spl.IncrementTagFor(tr->c);
1060
    }
1061

1062
    // Output all the vertices.
1063
    SPoint *sp;
1064
    fputs("  },\n"
1065
          "  points: [\n", f);
1066
    for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) {
1067
        fprintf(f, "    [%f, %f, %f],\n",
1068
                sp->p.x / SS.exportScale,
1069
                sp->p.y / SS.exportScale,
1070
                sp->p.z / SS.exportScale);
1071
    }
1072

1073
    fputs("  ],\n"
1074
          "  faces: [\n", f);
1075
    // And now all the triangular faces, in terms of those vertices.
1076
    // This time we count from zero.
1077
    for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
1078
        fprintf(f, "    [%d, %d, %d],\n",
1079
                spl.IndexForPoint(tr->a),
1080
                spl.IndexForPoint(tr->b),
1081
                spl.IndexForPoint(tr->c));
1082
    }
1083

1084
    // Output face normals.
1085
    fputs("  ],\n"
1086
          "  normals: [\n", f);
1087
    for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
1088
        fprintf(f, "    [[%f, %f, %f], [%f, %f, %f], [%f, %f, %f]],\n",
1089
                CO(tr->an), CO(tr->bn), CO(tr->cn));
1090
    }
1091

1092
    fputs("  ],\n"
1093
          "  colors: [\n", f);
1094
    // Output triangle colors.
1095
    for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
1096
        fprintf(f, "    0x%x,\n", tr->meta.color.ToARGB32());
1097
    }
1098

1099
    fputs("  ],\n"
1100
          "  edges: [\n", f);
1101
    // Output edges. Assume user's model colors do not obscure white edges.
1102
    for(const SOutline &so : sol->l) {
1103
        if(so.tag == 0) continue;
1104
        fprintf(f, "    [[%f, %f, %f], [%f, %f, %f]],\n",
1105
                so.a.x / SS.exportScale,
1106
                so.a.y / SS.exportScale,
1107
                so.a.z / SS.exportScale,
1108
                so.b.x / SS.exportScale,
1109
                so.b.y / SS.exportScale,
1110
                so.b.z / SS.exportScale);
1111
    }
1112

1113
    fputs("  ]\n};\n", f);
1114

1115
    if(filename.HasExtension("html")) {
1116
        fprintf(f, htmlend,
1117
                basename.c_str(),
1118
                SS.GW.scale,
1119
                CO(SS.GW.offset),
1120
                CO(SS.GW.projUp),
1121
                CO(SS.GW.projRight));
1122
    }
1123

1124
    spl.Clear();
1125
}
1126

1127
//-----------------------------------------------------------------------------
1128
// Export the mesh as a VRML text file / WRL.
1129
//-----------------------------------------------------------------------------
1130
void SolveSpaceUI::ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, SMesh *sm) {
1131
    struct STriangleSpan {
1132
        STriangle *first, *past_last;
1133

1134
        STriangle *begin() const { return first; }
1135
        STriangle *end() const { return past_last; }
1136
    };
1137

1138

1139
    std::string basename = filename.FileStem();
1140
    for(auto & c : basename) {
1141
        if(!(isalnum(c) || ((unsigned)c >= 0x80))) {
1142
            c = '_';
1143
        }
1144
    }
1145

1146
    fprintf(f, "#VRML V2.0 utf8\n"
1147
               "#Exported from SolveSpace %s\n"
1148
               "\n"
1149
               "DEF %s Transform {\n"
1150
               "  children [",
1151
            PACKAGE_VERSION,
1152
            basename.c_str());
1153

1154

1155
    std::map<std::uint8_t, std::vector<STriangleSpan>> opacities;
1156
    STriangle *start          = sm->l.begin();
1157
    std::uint8_t last_opacity = start->meta.color.alpha;
1158
    for(auto & tr : sm->l) {
1159
        if(tr.meta.color.alpha != last_opacity) {
1160
            opacities[last_opacity].push_back(STriangleSpan{start, &tr});
1161
            start = &tr;
1162
            last_opacity = start->meta.color.alpha;
1163
        }
1164
    }
1165
    opacities[last_opacity].push_back(STriangleSpan{start, sm->l.end()});
1166

1167
    for(auto && op : opacities) {
1168
        fprintf(f, "\n"
1169
                   "    Shape {\n"
1170
                   "      appearance Appearance {\n"
1171
                   "        material DEF %s_material_%u Material {\n"
1172
                   "          diffuseColor %f %f %f\n"
1173
                   "          ambientIntensity %f\n"
1174
                   "          transparency %f\n"
1175
                   "        }\n"
1176
                   "      }\n"
1177
                   "      geometry IndexedFaceSet {\n"
1178
                   "        colorPerVertex TRUE\n"
1179
                   "        coord Coordinate { point [\n",
1180
                basename.c_str(),
1181
                (unsigned)op.first,
1182
                SS.ambientIntensity,
1183
                SS.ambientIntensity,
1184
                SS.ambientIntensity,
1185
                SS.ambientIntensity,
1186
                1.f - ((float)op.first / 255.0f));
1187

1188
        SPointList spl = {};
1189

1190
        for(const auto & sp : op.second) {
1191
            for(const auto & tr : sp) {
1192
                spl.IncrementTagFor(tr.a);
1193
                spl.IncrementTagFor(tr.b);
1194
                spl.IncrementTagFor(tr.c);
1195
            }
1196
        }
1197

1198
        // Output all the vertices.
1199
        for(auto sp : spl.l) {
1200
            fprintf(f, "          %f %f %f,\n",
1201
                    sp.p.x / SS.exportScale,
1202
                    sp.p.y / SS.exportScale,
1203
                    sp.p.z / SS.exportScale);
1204
        }
1205

1206
        fputs("        ] }\n"
1207
              "        coordIndex [\n", f);
1208
        // And now all the triangular faces, in terms of those vertices.
1209
        for(const auto & sp : op.second) {
1210
            for(const auto & tr : sp) {
1211
                fprintf(f, "          %d, %d, %d, -1,\n",
1212
                        spl.IndexForPoint(tr.a),
1213
                        spl.IndexForPoint(tr.b),
1214
                        spl.IndexForPoint(tr.c));
1215
            }
1216
        }
1217

1218
        fputs("        ]\n"
1219
              "        color Color { color [\n", f);
1220
        // Output triangle colors.
1221
        std::vector<int> triangle_colour_ids;
1222
        std::vector<RgbaColor> colours_present;
1223
        for(const auto & sp : op.second) {
1224
            for(const auto & tr : sp) {
1225
                const auto colour_itr = std::find_if(colours_present.begin(), colours_present.end(),
1226
                                                     [&](const RgbaColor & c) {
1227
                                                         return c.Equals(tr.meta.color);
1228
                                                     });
1229
                if(colour_itr == colours_present.end()) {
1230
                    fprintf(f, "          %.10f %.10f %.10f,\n",
1231
                            tr.meta.color.redF(),
1232
                            tr.meta.color.greenF(),
1233
                            tr.meta.color.blueF());
1234
                    triangle_colour_ids.push_back(colours_present.size());
1235
                    colours_present.insert(colours_present.end(), tr.meta.color);
1236
                } else {
1237
                    triangle_colour_ids.push_back(colour_itr - colours_present.begin());
1238
                }
1239
            }
1240
        }
1241

1242
        fputs("        ] }\n"
1243
              "        colorIndex [\n", f);
1244

1245
        for(auto colour_idx : triangle_colour_ids) {
1246
            fprintf(f, "          %d, %d, %d, -1,\n", colour_idx, colour_idx, colour_idx);
1247
        }
1248

1249
        fputs("        ]\n"
1250
              "      }\n"
1251
              "    }\n", f);
1252

1253
        spl.Clear();
1254
    }
1255

1256
    fputs("  ]\n"
1257
          "}\n", f);
1258
}
1259

1260
//-----------------------------------------------------------------------------
1261
// Export a view of the model as an image; we just take a screenshot, by
1262
// rendering the view in the usual way and then copying the pixels.
1263
//-----------------------------------------------------------------------------
1264
void SolveSpaceUI::ExportAsPngTo(const Platform::Path &filename) {
1265
    screenshotFile = filename;
1266
    // The rest of the work is done in the next redraw.
1267
    GW.Invalidate();
1268
}
1269

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.