1 #include "canvas3d_base.hpp"
2 #include "import_step/import.hpp"
3 #include "board/board_layers.hpp"
4 #include "board/board.hpp"
5 #include "canvas/gl_util.hpp"
6 #include "util/util.hpp"
7 #include "util/min_max_accumulator.hpp"
8 #include <glm/glm.hpp>
9 #include <glm/gtc/matrix_transform.hpp>
10 #include <glm/gtc/type_ptr.hpp>
11 #include <glm/gtx/rotate_vector.hpp>
12 #include <glibmm/miscutils.h>
13 #include "pool/pool_manager.hpp"
14 #include "pool/ipool.hpp"
15 #include "logger/log_util.hpp"
16
17 namespace horizon {
18
Canvas3DBase()19 Canvas3DBase::Canvas3DBase()
20 : center(0), cover_renderer(*this), wall_renderer(*this), face_renderer(*this), background_renderer(*this),
21 point_renderer(*this)
22 {
23 }
24
get_layer_color(int layer) const25 Color Canvas3DBase::get_layer_color(int layer) const
26 {
27 if (layer == 20000 || BoardLayers::is_copper(layer)) { // pth or cu
28 if (use_layer_colors && appearance.layer_colors.count(layer)) {
29 return appearance.layer_colors.at(layer);
30 }
31 return {1, .8, 0};
32 }
33
34 if (layer == BoardLayers::TOP_MASK || layer == BoardLayers::BOTTOM_MASK)
35 return solder_mask_color;
36
37 if (layer == BoardLayers::TOP_PASTE || layer == BoardLayers::BOTTOM_PASTE)
38 return {.7, .7, .7};
39
40 if (layer == BoardLayers::TOP_SILKSCREEN || layer == BoardLayers::BOTTOM_SILKSCREEN)
41 return silkscreen_color;
42
43 if (layer == BoardLayers::L_OUTLINE || layer >= 10000)
44 return substrate_color;
45 return {1, 0, 0};
46 }
47
get_layer_offset(int layer) const48 float Canvas3DBase::get_layer_offset(int layer) const
49 {
50 if (layer == 20000)
51 return get_layer_offset(BoardLayers::TOP_COPPER);
52
53 else
54 return ca.get_layer(layer).offset + ca.get_layer(layer).explode_mul * explode;
55 }
56
get_layer_thickness(int layer) const57 float Canvas3DBase::get_layer_thickness(int layer) const
58 {
59 if (layer == BoardLayers::L_OUTLINE && explode == 0) {
60 return ca.get_layer(BoardLayers::BOTTOM_COPPER).offset + ca.get_layer(BoardLayers::BOTTOM_COPPER).thickness;
61 }
62 else if (layer == 20000) {
63 return -(get_layer_offset(BoardLayers::TOP_COPPER) - get_layer_offset(BoardLayers::BOTTOM_COPPER));
64 }
65 else {
66 return ca.get_layer(layer).thickness;
67 }
68 }
69
layer_is_visible(int layer) const70 bool Canvas3DBase::layer_is_visible(int layer) const
71 {
72 if (layer == 20000) // pth holes
73 return show_copper;
74
75 if (layer == BoardLayers::TOP_MASK || layer == BoardLayers::BOTTOM_MASK)
76 return show_solder_mask;
77
78 if (layer == BoardLayers::TOP_PASTE || layer == BoardLayers::BOTTOM_PASTE)
79 return show_solder_paste;
80
81 if (layer == BoardLayers::TOP_SILKSCREEN || layer == BoardLayers::BOTTOM_SILKSCREEN)
82 return show_silkscreen;
83
84 if (layer == BoardLayers::L_OUTLINE || layer >= 10000) {
85 if (show_substrate) {
86 if (layer == BoardLayers::L_OUTLINE)
87 return true;
88 else {
89 return explode > 0;
90 }
91 }
92 else {
93 return false;
94 }
95 }
96
97 if (layer < BoardLayers::TOP_COPPER && layer > BoardLayers::BOTTOM_COPPER)
98 return (show_substrate == false || explode > 0) && show_copper;
99
100 if (BoardLayers::is_copper(layer))
101 return show_copper;
102
103 return true;
104 }
105
a_realize()106 void Canvas3DBase::a_realize()
107 {
108 cover_renderer.realize();
109 wall_renderer.realize();
110 face_renderer.realize();
111 background_renderer.realize();
112 point_renderer.realize();
113 glEnable(GL_DEPTH_TEST);
114 glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
115
116 GLint fb;
117 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb); // save fb
118
119 glGenRenderbuffers(1, &renderbuffer);
120 glGenRenderbuffers(1, &depthrenderbuffer);
121 glGenRenderbuffers(1, &pickrenderbuffer);
122 glGenRenderbuffers(1, &pickrenderbuffer_downsampled);
123
124 resize_buffers();
125
126 GL_CHECK_ERROR
127
128 glGenFramebuffers(1, &fbo_downsampled);
129 glBindFramebuffer(GL_FRAMEBUFFER, fbo_downsampled);
130 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, pickrenderbuffer_downsampled);
131
132 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
133 // Gtk::MessageDialog md("Error setting up framebuffer, will now exit", false /* use_markup */,
134 // Gtk::MESSAGE_ERROR,
135 // Gtk::BUTTONS_OK);
136 // md.run();
137 abort();
138 }
139
140 GL_CHECK_ERROR
141
142 glGenFramebuffers(1, &fbo);
143 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
144
145 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
146 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, pickrenderbuffer);
147 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);
148
149 GL_CHECK_ERROR
150
151 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
152 // Gtk::MessageDialog md("Error setting up framebuffer, will now exit", false /* use_markup */,
153 // Gtk::MESSAGE_ERROR,
154 // Gtk::BUTTONS_OK);
155 // md.run();
156 abort();
157 }
158 glBindFramebuffer(GL_FRAMEBUFFER, fb);
159
160 GL_CHECK_ERROR
161 }
162
163
resize_buffers()164 void Canvas3DBase::resize_buffers()
165 {
166 GLint rb;
167 GLint samples = gl_clamp_samples(num_samples);
168 glGetIntegerv(GL_RENDERBUFFER_BINDING, &rb); // save rb
169 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
170 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width * a_get_scale_factor(),
171 height * a_get_scale_factor());
172 glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);
173 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_COMPONENT, width * a_get_scale_factor(),
174 height * a_get_scale_factor());
175 glBindRenderbuffer(GL_RENDERBUFFER, pickrenderbuffer);
176 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_R16UI, width * a_get_scale_factor(),
177 height * a_get_scale_factor());
178
179 glBindRenderbuffer(GL_RENDERBUFFER, pickrenderbuffer_downsampled);
180 glRenderbufferStorage(GL_RENDERBUFFER, GL_R16UI, width * a_get_scale_factor(), height * a_get_scale_factor());
181
182 glBindRenderbuffer(GL_RENDERBUFFER, rb);
183 }
184
push()185 void Canvas3DBase::push()
186 {
187 cover_renderer.push();
188 wall_renderer.push();
189 face_renderer.push();
190 point_renderer.push();
191 }
192
get_magic_number() const193 float Canvas3DBase::get_magic_number() const
194 {
195 return tan(0.5 * glm::radians(cam_fov));
196 }
197
fix_cam_elevation(float cam_elevation)198 static float fix_cam_elevation(float cam_elevation)
199 {
200 while (cam_elevation >= 360)
201 cam_elevation -= 360;
202 while (cam_elevation < 0)
203 cam_elevation += 360;
204 if (cam_elevation > 180)
205 cam_elevation -= 360;
206 return cam_elevation;
207 }
208
set_cam_elevation(const float & ele)209 void Canvas3DBase::set_cam_elevation(const float &ele)
210 {
211 cam_elevation = fix_cam_elevation(ele);
212 redraw();
213 invalidate_pick();
214 s_signal_view_changed.emit();
215 }
216
set_cam_azimuth(const float & az)217 void Canvas3DBase::set_cam_azimuth(const float &az)
218 {
219 cam_azimuth = az;
220 while (cam_azimuth < 0)
221 cam_azimuth += 360;
222
223 while (cam_azimuth > 360)
224 cam_azimuth -= 360;
225 redraw();
226 invalidate_pick();
227 s_signal_view_changed.emit();
228 }
229
render(RenderBackground mode)230 void Canvas3DBase::render(RenderBackground mode)
231 {
232
233 GLint fb;
234 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb); // save fb
235
236 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
237
238 glClearColor(0, 0, 0, 0);
239 glClearDepth(10);
240 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
241 GL_CHECK_ERROR
242 {
243 const std::array<GLenum, 2> bufs = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
244 glDrawBuffers(bufs.size(), bufs.data());
245 }
246
247 if (mode == RenderBackground::YES) {
248 glDisable(GL_DEPTH_TEST);
249 background_renderer.render();
250 glEnable(GL_DEPTH_TEST);
251 }
252
253 float r = cam_distance;
254 float phi = glm::radians(cam_azimuth);
255 float theta = glm::radians(90 - cam_elevation);
256 auto cam_offset = glm::vec3(r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta));
257 auto cam_pos = cam_offset + glm::vec3(center, 0);
258
259 glm::vec3 right(sin(phi - 3.14f / 2.0f), cos(phi - 3.14f / 2.0f), 0);
260
261 viewmat = glm::lookAt(cam_pos, glm::vec3(center, 0), glm::vec3(0, 0, std::abs(cam_elevation) < 90 ? 1 : -1));
262
263 float cam_dist_min = std::max(std::abs(cam_pos.z) - (10 + explode * (brd->get_n_inner_layers() * 2 + 3)), 1.0f);
264 float cam_dist_max = 0;
265
266 float zmin = -10 - explode * (brd->get_n_inner_layers() * 2 + 3 + package_height_max);
267 float zmax = 10 + explode * 2 + package_height_max;
268 std::array<glm::vec3, 8> bbs = {
269 glm::vec3(bbox.first.x, bbox.first.y, zmin), glm::vec3(bbox.first.x, bbox.second.y, zmin),
270 glm::vec3(bbox.second.x, bbox.first.y, zmin), glm::vec3(bbox.second.x, bbox.second.y, zmin),
271 glm::vec3(bbox.first.x, bbox.first.y, zmax), glm::vec3(bbox.first.x, bbox.second.y, zmax),
272 glm::vec3(bbox.second.x, bbox.first.y, zmax), glm::vec3(bbox.second.x, bbox.second.y, zmax)};
273
274 for (const auto &bb : bbs) {
275 float dist = glm::length(bb - cam_pos);
276 cam_dist_max = std::max(dist, cam_dist_max);
277 cam_dist_min = std::min(dist, cam_dist_min);
278 }
279 float m = get_magic_number() / height * cam_distance;
280 float d = cam_dist_max * 2;
281 if (projection == Projection::PERSP) {
282 projmat = glm::perspective(glm::radians(cam_fov), (float)width / height, cam_dist_min / 2, cam_dist_max * 2);
283 }
284 else {
285 projmat = glm::ortho(-width * m, width * m, -height * m, height * m, -d, d);
286 }
287
288 cam_normal = glm::normalize(cam_offset);
289 wall_renderer.render();
290
291 if (show_models)
292 face_renderer.render();
293
294 if (show_points)
295 point_renderer.render();
296
297 cover_renderer.render();
298
299 GL_CHECK_ERROR
300
301 if (pick_state == PickState::QUEUED) {
302 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_downsampled);
303 glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
304 glDrawBuffer(GL_COLOR_ATTACHMENT0);
305 glReadBuffer(GL_COLOR_ATTACHMENT1);
306 glBlitFramebuffer(0, 0, width * a_get_scale_factor(), height * a_get_scale_factor(), 0, 0,
307 width * a_get_scale_factor(), height * a_get_scale_factor(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
308
309 glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_downsampled);
310 glReadBuffer(GL_COLOR_ATTACHMENT0);
311 pick_buf.resize(width * a_get_scale_factor() * height * a_get_scale_factor());
312 GL_CHECK_ERROR
313
314 glPixelStorei(GL_PACK_ALIGNMENT, 2);
315 glReadPixels(0, 0, width * a_get_scale_factor(), height * a_get_scale_factor(), GL_RED_INTEGER,
316 GL_UNSIGNED_SHORT, pick_buf.data());
317
318 GL_CHECK_ERROR
319 pick_state = PickState::CURRENT;
320 s_signal_pick_ready.emit();
321 }
322
323 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);
324 glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
325 glDrawBuffer(fb ? GL_COLOR_ATTACHMENT0 : GL_FRONT);
326 glReadBuffer(GL_COLOR_ATTACHMENT0);
327 glBlitFramebuffer(0, 0, width * a_get_scale_factor(), height * a_get_scale_factor(), 0, 0,
328 width * a_get_scale_factor(), height * a_get_scale_factor(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
329
330 glBindFramebuffer(GL_FRAMEBUFFER, fb);
331 GL_CHECK_ERROR
332 glFlush();
333 }
334
view_all()335 void Canvas3DBase::view_all()
336 {
337 if (!brd)
338 return;
339
340 const auto &vertices = ca.get_layer(BoardLayers::L_OUTLINE).walls;
341 MinMaxAccumulator<float> acc_x, acc_y;
342
343 for (const auto &it : vertices) {
344 acc_x.accumulate(it.x);
345 acc_y.accumulate(it.y);
346 }
347
348 float xmin = acc_x.get_min();
349 float xmax = acc_x.get_max();
350 float ymin = acc_y.get_min();
351 float ymax = acc_y.get_max();
352
353 float board_width = (xmax - xmin) / 1e6;
354 float board_height = (ymax - ymin) / 1e6;
355
356 if (board_height < 1 || board_width < 1)
357 return;
358
359 set_center({(xmin + xmax) / 2e6, (ymin + ymax) / 2e6});
360
361
362 set_cam_distance(std::max(board_width / width, board_height / height) / (2 * get_magic_number() / height) * 1.1);
363 set_cam_azimuth(270);
364 set_cam_elevation(89.99);
365 }
366
prepare()367 void Canvas3DBase::prepare()
368 {
369 bbox.first = glm::vec3();
370 bbox.second = glm::vec3();
371 for (const auto &it : ca.get_patches()) {
372 for (const auto &path : it.second) {
373 for (const auto &p : path) {
374 glm::vec3 q(p.X / 1e6, p.Y / 1e6, 0);
375 bbox.first = glm::min(bbox.first, q);
376 bbox.second = glm::max(bbox.second, q);
377 }
378 }
379 }
380 }
381
a_get_scale_factor() const382 int Canvas3DBase::a_get_scale_factor() const
383 {
384 return 1;
385 }
386
prepare_packages()387 void Canvas3DBase::prepare_packages()
388 {
389 if (!brd)
390 return;
391 package_infos.clear();
392 package_transforms.clear();
393 std::map<std::pair<std::string, bool>, std::set<const BoardPackage *>> pkg_map;
394 for (const auto &it : brd->packages) {
395 auto model = it.second.package.get_model(it.second.model);
396 if (model)
397 pkg_map[{model->filename, it.second.component ? it.second.component->nopopulate : false}].insert(
398 &it.second);
399 }
400
401 unsigned int pick_base = 2; // 0: background, 1: PCB, 2+: models
402 for (const auto &it_pkg : pkg_map) {
403 size_t size_before = package_transforms.size();
404 std::vector<UUID> pkgs;
405 for (const auto &it_brd_pkg : it_pkg.second) {
406 pkgs.push_back(it_brd_pkg->uuid);
407 const auto &pl = it_brd_pkg->placement;
408 const auto &pkg = it_brd_pkg->package;
409 package_transforms.emplace_back(pl.shift.x / 1e6, pl.shift.y / 1e6, pl.get_angle(), it_brd_pkg->flip,
410 packages_highlight.count(it_brd_pkg->uuid));
411 auto &tr = package_transforms.back();
412 const auto model = pkg.get_model(it_brd_pkg->model);
413 if (model) {
414 tr.model_x = model->x / 1e6;
415 tr.model_y = model->y / 1e6;
416 tr.model_z = model->z / 1e6;
417 tr.model_roll = model->roll;
418 tr.model_pitch = model->pitch;
419 tr.model_yaw = model->yaw;
420 }
421 }
422 size_t size_after = package_transforms.size();
423 package_infos[it_pkg.first] = {size_before, size_after - size_before, pick_base, pkgs};
424 pick_base += pkgs.size();
425 }
426 point_pick_base = pick_base;
427 }
428
import_step(const std::string & filename_rel,const std::string & filename_abs)429 STEPImporter::Faces Canvas3DBase::import_step(const std::string &filename_rel, const std::string &filename_abs)
430 {
431 const auto result = STEPImporter::import(filename_abs);
432 return result.faces;
433 }
434
load_3d_model(const std::string & filename,const std::string & filename_abs)435 void Canvas3DBase::load_3d_model(const std::string &filename, const std::string &filename_abs)
436 {
437 if (models.count(filename))
438 return;
439
440 const auto faces = import_step(filename, filename_abs);
441 {
442 std::lock_guard<std::mutex> lock(models_loading_mutex);
443 // canvas->face_vertex_buffer.reserve(faces.size());
444 size_t vertex_offset = face_vertex_buffer.size();
445 size_t first_index = face_index_buffer.size();
446 for (const auto &face : faces) {
447 for (size_t i = 0; i < face.vertices.size(); i++) {
448 const auto &v = face.vertices.at(i);
449 const auto &n = face.normals.at(i);
450 face_vertex_buffer.emplace_back(v.x, v.y, v.z, n.x, n.y, n.z, face.color.r * 255, face.color.g * 255,
451 face.color.b * 255);
452 }
453
454 for (const auto &tri : face.triangle_indices) {
455 size_t a, b, c;
456 std::tie(a, b, c) = tri;
457 face_index_buffer.push_back(a + vertex_offset);
458 face_index_buffer.push_back(b + vertex_offset);
459 face_index_buffer.push_back(c + vertex_offset);
460 }
461 vertex_offset += face.vertices.size();
462 }
463 size_t last_index = face_index_buffer.size();
464 models.emplace(std::piecewise_construct, std::forward_as_tuple(filename),
465 std::forward_as_tuple(first_index, last_index - first_index));
466 }
467 }
468
model_is_loaded(const std::string & filename)469 bool Canvas3DBase::model_is_loaded(const std::string &filename)
470 {
471 std::lock_guard<std::mutex> lock(models_loading_mutex);
472 return models.count(filename);
473 }
474
set_point_transform(const glm::dmat4 & mat)475 void Canvas3DBase::set_point_transform(const glm::dmat4 &mat)
476 {
477 point_mat = mat;
478 invalidate_pick();
479 redraw();
480 }
481
set_points(const std::vector<Point3D> & pts)482 void Canvas3DBase::set_points(const std::vector<Point3D> &pts)
483 {
484 points = pts;
485 }
486
get_model_filenames(IPool & pool)487 std::map<std::string, std::string> Canvas3DBase::get_model_filenames(IPool &pool)
488 {
489 std::map<std::string, std::string> model_filenames; // first: relative, second: absolute
490 for (const auto &it : brd->packages) {
491 auto model = it.second.package.get_model(it.second.model);
492 if (model) {
493 std::string model_filename;
494 const Package *pool_package = nullptr;
495
496 UUID this_pool_uuid = pool.get_pool_info().uuid;
497 UUID pkg_pool_uuid;
498 try {
499 pool_package = pool.get_package(it.second.package.uuid, &pkg_pool_uuid);
500 }
501 catch (const std::runtime_error &e) {
502 // it's okay
503 }
504 if (it.second.pool_package == pool_package) {
505 // package is from pool, ask pool for model filename (might come from cache)
506 model_filename = pool.get_model_filename(it.second.package.uuid, model->uuid);
507 }
508 else {
509 // package is not from pool (from package editor, use filename relative to current pool)
510 if (pkg_pool_uuid && pkg_pool_uuid != this_pool_uuid) { // pkg is open in RO mode from included pool
511 model_filename = pool.get_model_filename(it.second.package.uuid, model->uuid);
512 }
513 else { // really editing package
514 model_filename = Glib::build_filename(pool.get_base_path(), model->filename);
515 }
516 }
517 if (model_filename.size())
518 model_filenames[model->filename] = model_filename;
519 }
520 }
521 return model_filenames;
522 }
523
clear_3d_models()524 void Canvas3DBase::clear_3d_models()
525 {
526 face_vertex_buffer.clear();
527 face_index_buffer.clear();
528 models.clear();
529 }
530
update_max_package_height()531 void Canvas3DBase::update_max_package_height()
532 {
533 package_height_max = 0;
534 for (const auto &it : face_vertex_buffer) {
535 package_height_max = std::max(it.z, package_height_max);
536 }
537 }
538
invalidate_pick()539 void Canvas3DBase::invalidate_pick()
540 {
541 pick_state = PickState::INVALID;
542 }
543
queue_pick()544 void Canvas3DBase::queue_pick()
545 {
546 if (pick_state == PickState::INVALID) {
547 pick_state = PickState::QUEUED;
548 redraw();
549 }
550 else if (pick_state == PickState::CURRENT) {
551 s_signal_pick_ready.emit();
552 }
553 }
554
pick_package_or_point(unsigned int x,unsigned int y) const555 std::variant<UUID, glm::dvec3> Canvas3DBase::pick_package_or_point(unsigned int x, unsigned int y) const
556 {
557 if (pick_state != PickState::CURRENT) {
558 Logger::log_warning("can't with non-current pick state");
559 return {};
560 }
561 x *= a_get_scale_factor();
562 y *= a_get_scale_factor();
563 const int idx = ((height * a_get_scale_factor()) - y - 1) * width * a_get_scale_factor() + x;
564 const auto pick = pick_buf.at(idx);
565 if (pick >= point_pick_base) {
566 const auto &pt = points.at(pick - point_pick_base);
567 const auto p4 = point_mat * glm::dvec4(pt.x, pt.y, pt.z, 1);
568 return glm::dvec3(p4.x, p4.y, p4.z);
569 }
570 for (const auto &[k, v] : package_infos) {
571 if (pick >= v.pick_base && pick < (v.pick_base + v.n_packages)) {
572 return v.pkg.at(pick - v.pick_base);
573 }
574 }
575 return {};
576 }
577
acc(float & l,float & h,float v)578 static void acc(float &l, float &h, float v)
579 {
580 l = std::min(l, v);
581 h = std::max(h, v);
582 }
583
get_model_bbox(const std::string & filename) const584 Canvas3DBase::BBox Canvas3DBase::get_model_bbox(const std::string &filename) const
585 {
586 Canvas3DBase::BBox bb;
587 bool first = true;
588 if (models.count(filename)) {
589 const auto &m = models.at(filename);
590 for (size_t i = m.face_index_offset; i < m.face_index_offset + m.count; i++) {
591 const auto idx = face_index_buffer.at(i);
592 const auto &v = face_vertex_buffer.at(idx);
593 if (first) {
594 bb.xh = v.x;
595 bb.xl = v.x;
596 bb.yh = v.y;
597 bb.yl = v.y;
598 bb.zh = v.z;
599 bb.zl = v.z;
600 }
601 else {
602 acc(bb.xl, bb.xh, v.x);
603 acc(bb.yl, bb.yh, v.y);
604 acc(bb.zl, bb.zh, v.z);
605 }
606 first = false;
607 }
608 }
609 else {
610 bb = {0};
611 }
612 return bb;
613 }
614
615 } // namespace horizon
616