Euphoria
dump.cc
Go to the documentation of this file.
1 #include "core/dump.h"
2 
3 #include <functional>
4 #include <fstream>
5 
6 
7 #include "assert/assert.h"
8 #include "base/plane.h"
9 #include "base/ray.h"
10 
11 
13 {
14  std::string to_html_or_none_string(const std::optional<Rgbi>& c)
15  {
16  if(!c)
17  {
18  return "none";
19  }
20  else
21  {
22  return to_html_rgb(*c);
23  }
24  }
25 
26  namespace strokes
27  {
28  std::vector<int> create_dash(int size)
29  {
30  return {size, size};
31  }
32  }
33 
34  Poly& Poly::set_stroke(const std::vector<int>& new_stroke)
35  {
36  stroke = new_stroke;
37  return *this;
38  }
39 
40  Poly& Poly::fill(const Rgbi& a_fill_color)
41  {
42  fill_color = a_fill_color;
43  return *this;
44  }
45 
47  {
48  is_closed = true;
49  return *this;
50  }
51 
52  Text::Text(const vec2f& p, const std::string& t, const Rgbi& c)
53  : point(p), label(t), color(c) {}
54 
55  Circle&
57  {
58  line_color = lc;
59  return *this;
60  }
61  Circle::Circle(const vec2f& p, float r, std::optional<Rgbi> fill)
62  : point(p), radius(r), fill_color(fill) {}
63 
64  Item::Item(const dump2d::Poly& p) : poly(std::make_shared<dump2d::Poly>(p)) {}
65  Item::Item(const dump2d::Text& p) : text(std::make_shared<dump2d::Text>(p)) {}
66  Item::Item(const dump2d::Group& g) : group(std::make_shared<dump2d::Group>(g)) {}
67  Item::Item(const dump2d::Circle& c) : circle(std::make_shared<dump2d::Circle>(c)) {}
68 
69  const Poly* as_poly(const Item* item) { return item->poly? item->poly.get() : nullptr; }
70  const Text* as_text(const Item* item) { return item->text? item->text.get() : nullptr; }
71  const Group* as_group(const Item* item) { return item->group? item->group.get() : nullptr; }
72  const Circle* as_circle(const Item* item) { return item->circle? item->circle.get() : nullptr; }
73 
74  Group& Group::add(const Item& item)
75  {
76  items.emplace_back(item);
77  return *this;
78  }
79 
80  namespace detail
81  {
82  struct Writer
83  {
84  std::ofstream file;
85 
86  std::function<float (float)> px;
87  std::function<float (float)> py;
88 
89  float scale = 1.0f;
90 
91  explicit Writer(const std::string& path)
92  : file(path.c_str()) {}
93  };
94 
95  // todo(Gustav): merge with MinMax in core
96  struct MinMaxer
97  {
100 
101  MinMaxer& include(const vec2f& point, float extra=0)
102  {
103  min.x = std::min(min.x, point.x - extra);
104  min.y = std::min(min.y, point.y - extra);
105 
106  max.x = std::max(max.x, point.x + extra);
107  max.y = std::max(max.y, point.y + extra);
108 
109  return *this;
110  }
111 
112  MinMaxer& operator<<(const vec2f& point)
113  {
114  return include(point);
115  }
116  };
117 
118  void write_poly(Writer* writer, const Poly* poly)
119  {
120  writer->file << "<polyline points=\"";
121  {
122  bool first = true;
123 
124  auto write_point = [&writer](const vec2f& p)
125  {
126  writer->file << writer->px(p.x) << ","
127  << writer->py(p.y);
128  };
129 
130  for(const auto p: poly->points)
131  {
132  if(first) {first = false;}
133  else {writer->file << " ";}
134 
135  write_point(p);
136  }
137 
138  if(poly->is_closed && !poly->points.empty())
139  {
140  writer->file << " ";
141  write_point(poly->points[0]);
142  }
143  }
144 
145  writer->file << "\" style=\"fill:";
146  writer->file << to_html_or_none_string(poly->fill_color);
147  writer->file << ";stroke:";
148  writer->file << to_html_rgb(poly->stroke_color);
149  if(poly->stroke_width <= 0.0f)
150  {
151  writer->file << ";stroke-width:0\"";
152  }
153  else
154  {
155  writer->file << ";stroke-width:1\"";
156  if(!poly->stroke.empty())
157  {
158  writer->file << " stroke-dasharray=\"";
159  {bool first = true;
160  for(const auto d: poly->stroke)
161  {
162  if(first) { first = false; }
163  else { writer->file << ","; }
164  writer->file << d;
165  }}
166  writer->file << "\"";
167  }
168  }
169  writer->file << "/>\n";
170  }
171 
172  void write_text(Writer* writer, const Text* text)
173  {
174  writer->file << "<text x=\"" << writer->px(text->point.x)
175  << "\" y=\"" << writer->py(text->point.y) << "\" fill=\"" << to_html_rgb(text->color) << "\">" << text->label << "</text>\n";
176  }
177 
178  void write_circle(Writer* writer, const Circle* circle)
179  {
180  writer->file
181  << "<circle cx=\"" << writer->px(circle->point.x) << "\" cy=\"" << writer->py(circle->point.y)
182  << "\" r=\"" << circle->radius*writer->scale << "\" stroke=\"" << to_html_or_none_string(circle->line_color)
183  << "\" fill=\"" << to_html_or_none_string(circle->fill_color) << "\""
184  << "/>\n";
185  }
186 
187  void write_item(Writer* writer, const Item& item)
188  {
189  const auto* poly = as_poly(&item);
190  const auto* text = as_text(&item);
191  const auto* group = as_group(&item);
192  const auto* circle = as_circle(&item);
193  if(poly != nullptr)
194  {
195  write_poly(writer, poly);
196  }
197  else if(text != nullptr)
198  {
199  write_text(writer, text);
200  }
201  else if(group != nullptr)
202  {
203  writer->file << "<g>\n";
204  for(const auto& i: group->items)
205  {
206  write_item(writer, i);
207  }
208  writer->file << "</g>\n";
209  }
210  else if(circle != nullptr)
211  {
212  write_circle(writer, circle);
213  }
214  else
215  {
216  DIE("unhandled type");
217  }
218  }
219 
220  void find_min_max(MinMaxer* mm, const Item& item)
221  {
222  const auto* poly = as_poly(&item);
223  const auto* text = as_text(&item);
224  const auto* group = as_group(&item);
225  const auto* circle = as_circle(&item);
226 
227  if(poly != nullptr)
228  {
229  for(const auto& point: poly->points)
230  {
231  *mm << point;
232  }
233  }
234  else if(text != nullptr)
235  {
236  *mm << text->point;
237  }
238  else if(circle != nullptr)
239  {
240  mm->include(circle->point, circle->radius);
241  }
242  else if(group != nullptr)
243  {
244  for(const auto& i: group->items)
245  {
246  find_min_max(mm, i);
247  }
248  }
249  else
250  {
251  DIE("unhandled type");
252  }
253  }
254  }
255 
257  {
258  add_axis_when_writing = true;
259  return *this;
260  }
261 
263  {
264  grid_x = xy;
265  grid_y = xy;
266  return *this;
267  }
268 
270  {
271  point_size = size;
272  return *this;
273  }
274 
275  Dumper& Dumper::add(const Item& item)
276  {
277  items.emplace_back(item);
278  return *this;
279  }
280 
281  // calculate total area size and offset so that x+offset will never be lower than 0
282  std::pair<vec2f,vec2f> Dumper::calc_size_and_offset() const
283  {
284  detail::MinMaxer minmax;
285 
286  for(const auto& item: items)
287  {
288  detail::find_min_max(&minmax, item);
289  }
290 
291  const auto& min = minmax.min;
292  const auto& max = minmax.max;
293 
294  const auto offset = vec2f{-std::min(min.x,0.0f), -std::min(min.y, 0.0f)};
295  const auto size = vec2f{max.x - min.x, max.y-min.y};
296 
297  return {size, offset};
298  }
299 
300  void Dumper::write(const std::string& path, int width, int height, int space) const
301  {
302  // reference: https://www.w3schools.com/graphics/svg_path.asp
303  // todo(Gustav): improve api
304  // todo(Gustav): interactive zoom & pan grid view (in canvas)
305  // todo(Gustav): list of polys and view and hover over points
306  // todo(Gustav): hide polys
307  // todo(Gustav): attach string->string properties
308  // todo(Gustav): option to replace string with polylines so we can measure and layout text
309 
310  const auto r = calc_size_and_offset();
311  const auto size = r.first;
312  const auto offset = r.second;
313  const auto scale = std::min(static_cast<float>(width-space*2) / size.x, static_cast<float>(height-space*2) / size.y);
314 
315  const auto dx = offset.x * scale;
316  const auto dy = offset.y * scale;
317  const auto px = [=](float x) -> float { return static_cast<float>(space) + dx + x * scale; };
318  const auto py = [=](float y) -> float { return static_cast<float>(height) - (static_cast<float>(space) + dy + y * scale); };
319 
320  detail::Writer writer(path);
321  writer.px = px;
322  writer.py = py;
323  writer.scale = scale;
324 
325  writer.file << "<html style=\"height: 100%\">\n";
326  writer.file << "<body style=\"background-color:" << to_html_rgb(NamedColor::dark_gray) << "; height: 100%\">\n";
327 
328  writer.file << "<div style=\"display: grid; grid-template-columns: 1fr auto 1fr; grid-template-rows: 1fr auto 1fr; width:100%; height:100%\">\n";
329  writer.file << "<div style=\"grid-row-start: 2; grid-column-start: 2;\">\n";
330 
331  writer.file << "<svg width=\"" << width << "\" height=\"" << height << "\">\n";
332  writer.file << "<rect width=\"" << width << "\" height=\"" << height << "\""
333  " style=\"fill:" << to_html_rgb(canvas_color) << ";stroke-width:0\" />\n";
334 
335  for(const auto& item: items)
336  {
337  detail::write_item(&writer, item);
338  }
339 
340  if(point_size > 0)
341  {
342  const auto c = NamedColor::black;
343 
344  for(const auto& item: items)
345  {
346  const auto* poly = as_poly(&item);
347  if(poly != nullptr)
348  {
349  int index = 0;
350  for(const auto p: poly->points)
351  {
352  writer.file << "<circle cx=\"" << px(p.x) << "\" cy=\"" << py(p.y) << "\" r=\"" << point_size << "\" stroke=\"none\" fill=\"" << to_html_rgb(c) << "\""
353  << " title=\""
354  // << "index: " << index
355  << index << "(" << p.x << " " << p.y << ")"
356  << "\"/>\n";
357  index += 1;
358  }
359  }
360  }
361  }
362 
363  if(point_text)
364  {
365  const auto c = NamedColor::black;
366 
367  for(const auto& item: items)
368  {
369  const auto* poly = as_poly(&item);
370  if(poly != nullptr)
371  {
372  int index = 0;
373  for(const auto p: poly->points)
374  {
375  writer.file << "<text x=\"" << px(p.x) << "\" y=\"" << py(p.y) << "\" fill=\"" << to_html_rgb(c) << "\">"
376  << index << "(" << p.x << " " << p.y << ")"
377  << "</text>";
378  index += 1;
379  }
380  }
381  }
382  }
383 
384  auto vline = [&](float x, const Rgbi& c) { writer.file << "<line x1=\"" << px(x) << "\" y1=\"0\"" " x2=\"" << px(x) << "\" y2=\"" << height << "\" style=\"stroke:" << to_html_rgb(c) << ";stroke-width:1\" />\n"; };
385  auto hline = [&](float y, const Rgbi& c) { writer.file << "<line x1=\"0\"" " y1=\"" << py(y) << "\" x2=\"" << width << "\" y2=\"" << py(y) << "\" style=\"stroke:" << to_html_rgb(c) << ";stroke-width:1\" />\n"; };
386 
387  const auto grid_color = NamedColor::light_gray;
388 
389  if(grid_x > 0 )
390  {
391  for(auto x = grid_x; px(x) < static_cast<float>(width); x += grid_x) { vline(x, grid_color); }
392  for(auto x = -grid_x; px(x) > 0; x -= grid_x) { vline(x, grid_color); }
393  }
394 
395  if(grid_y > 0 )
396  {
397  for(auto y = grid_y; px(y) < static_cast<float>(height); y += grid_y) { hline(y, grid_color); }
398  for(auto y = -grid_y; px(y) > 0; y -= grid_y) { hline(y, grid_color); }
399  }
400 
402  {
403  const auto axis_color = NamedColor::black;
404  hline(0, axis_color);
405  vline(0, axis_color);
406  }
407 
408  writer.file << "</svg>\n";
409 
410  writer.file << "</div>\n";
411  writer.file << "</div>\n";
412 
413  writer.file << "</body>\n";
414  writer.file << "</html>\n";
415  }
416 }
417 
418 namespace eu::core::dump3d
419 {
420 
421  Dumper::Dumper(const std::string& path)
422  : file(path.c_str())
423  {
424  file <<
425  R"html(<html>
426  <head>
427  <title>My first three.js app</title>
428  <style>
429  body { margin: 0; }
430  canvas { width: 100%; height: 100% }
431  </style>
432  </head>
433  <body>
434  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js"></script>
435  <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
436 
437  <script>
438  var scene = new THREE.Scene();
439  scene.background = new THREE.Color( 0x0096ff );
440  var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
441 
442  // https://threejs.org/examples/#webgl_shadowmesh
443 
444  var light = new THREE.AmbientLight(0xffffff);
445  scene.add( light );
446  // scene.add( new THREE.AxisHelper( 20 ) );
447 
448  var renderer = new THREE.WebGLRenderer();
449  renderer.setSize( window.innerWidth, window.innerHeight );
450  document.body.appendChild( renderer.domElement );
451  function onWindowResize() {
452  camera.aspect = window.innerWidth / window.innerHeight;
453  camera.updateProjectionMatrix();
454  renderer.setSize( window.innerWidth, window.innerHeight );
455  }
456  window.addEventListener( 'resize', onWindowResize, false );
457 
458  var add_geom = function(geom, c)
459  {
460  var material = new THREE.MeshPhongMaterial( {
461  color: c,
462  // shading: THREE.FlatShading,
463  polygonOffset: true,
464  polygonOffsetFactor: 1,
465  polygonOffsetUnits: 1
466  } );
467 
468  var mesh = new THREE.Mesh( geom, material );
469  scene.add( mesh );
470  return mesh;
471  }
472 
473  var add_wire = function(geom, c)
474  {
475  mesh = add_geom(geom, c);
476  var geo = new THREE.EdgesGeometry( mesh.geometry ); // or WireframeGeometry
477  var mat = new THREE.LineBasicMaterial( { color: 0xffffff, linewidth: 2 } );
478  var wireframe = new THREE.LineSegments( geo, mat );
479  mesh.add( wireframe );
480  }
481 
482  // add_wire(new THREE.BoxGeometry( 1, 1, 1 ));
483 )html"
484  ;
485  }
486 
487  namespace
488  {
489  constexpr auto s = " ";
490  }
491 
492  Dumper::~Dumper()
493  {
494  file <<
495  R"html(
496  camera.position.z = 5;
497  new THREE.OrbitControls( camera, renderer.domElement );
498 
499  var animate = function () {
500  requestAnimationFrame( animate );
501  renderer.render( scene, camera );
502  };
503 
504  animate();
505  </script>
506  </body>
507 </html>)html"
508  ;
509  }
510 
511 
512  void
513  Dumper::add_sphere(const vec3f& p, float radius, const Rgbi& color)
514  {
515  file << s << "add_geom(new THREE.SphereGeometry(" << radius << "), " << to_js_hex_color(color) << ")\n"
516  << s << " .position.set("<<p.x<<", "<<p.y<<", "<<p.z<<");\n";
517  }
518 
519 
520  void
521  Dumper::add_lines(const std::vector<vec3f>& points, const Rgbi& color)
522  {
523  file
524  << s << "(function() {\n"
525  << s << " var material = new THREE.LineBasicMaterial( { color: " << to_js_hex_color(color) << " } );\n"
526  << s << " var geometry = new THREE.Geometry();\n"
527  ;
528  for(auto p: points)
529  {
530  file
531  << s << " geometry.vertices.push(new THREE.Vector3( "
532  << p.x <<", " << p.y << ", " << p.z << ") );\n"
533  ;
534  }
535  file
536  << s << " var line = new THREE.Line( geometry, material );\n"
537  << s << " scene.add( line );\n"
538  << s << "})();\n"
539  ;
540  }
541 
542 
543  std::string
544  to_three_vector_source(const vec3f& v)
545  {
546  return fmt::format("new THREE.Vector3({}, {}, {})", v.x, v.y, v.z);
547  }
548 
549 
550  void
551  Dumper::add_plane(const Plane& plane, const Rgbi& color)
552  {
553  constexpr auto size = 5;
554  file
555  << s << "scene.add( new THREE.PlaneHelper( new THREE.Plane( "
556  << to_three_vector_source(plane.normal) << ", " << plane.distance
557  << "), " << size << ", " << to_js_hex_color(color) << ") );\n"
558  ;
559  }
560 
561 
562  void
563  Dumper::add_arrow(const Ray3f& ray, const Rgbi& color)
564  {
565  file
566  << s << "scene.add(new THREE.ArrowHelper("
567  << to_three_vector_source(ray.dir.get_normalized()) << ", "
568  << to_three_vector_source(ray.from) << ", "
569  << ray.dir.get_length() << ", "
570  << to_js_hex_color(color) << ") );\n";
571  }
572 
573 
574  void
576  {
577  constexpr auto size = 2;
578  file
579  << s << "scene.add(new THREE.AxesHelper("<< size <<") );\n";
580  }
581 
582 
583  void
585  {
586  constexpr auto size = 10;
587  constexpr auto divisions = 10;
588 
589  file
590  << s << "scene.add(new THREE.GridHelper("<< size <<", " << divisions<<") );\n";
591  }
592 }
#define DIE(message)
Definition: assert.h:67
void write_poly(Writer *writer, const Poly *poly)
Definition: dump.cc:118
void find_min_max(MinMaxer *mm, const Item &item)
Definition: dump.cc:220
void write_text(Writer *writer, const Text *text)
Definition: dump.cc:172
void write_circle(Writer *writer, const Circle *circle)
Definition: dump.cc:178
void write_item(Writer *writer, const Item &item)
Definition: dump.cc:187
std::vector< int > create_dash(int size)
Definition: dump.cc:28
std::string to_html_or_none_string(const std::optional< Rgbi > &c)
Definition: dump.cc:14
const Text * as_text(const Item *item)
Definition: dump.cc:70
const Poly * as_poly(const Item *item)
Definition: dump.cc:69
const Group * as_group(const Item *item)
Definition: dump.cc:71
const Circle * as_circle(const Item *item)
Definition: dump.cc:72
constexpr vec2f zero2f
Definition: vec2.h:68
std::string to_html_rgb(const Rgbi &c)
Definition: rgb.cc:427
size2f min(const size2f lhs, const size2f rhs)
Definition: size2.cc:140
std::string to_js_hex_color(const Rgbi &c)
Definition: rgb.cc:422
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
TPoints * points
Definition: polybezier.cc:41
Definition: rgb.h:26
std::optional< Rgbi > line_color
Definition: dump.h:56
Circle(const vec2f &p, float r, std::optional< Rgbi > fill=std::nullopt)
Definition: dump.cc:61
Circle & set_line_color(const Rgbi &lc)
Definition: dump.cc:56
std::optional< Rgbi > fill_color
Definition: dump.h:57
Dumper & add(const Item &item)
Definition: dump.cc:275
std::vector< Item > items
Definition: dump.h:105
void write(const std::string &path, int width=1280, int height=1024, int space=6) const
Definition: dump.cc:300
bool add_axis_when_writing
Definition: dump.h:106
Dumper & add_grid(float xy)
Definition: dump.cc:262
Dumper & add_axis()
Definition: dump.cc:256
std::pair< vec2f, vec2f > calc_size_and_offset() const
calculate total area size and offset so that x+offset will never be lower than 0
Definition: dump.cc:282
Dumper & enable_points_rendering(int size=3)
Definition: dump.cc:269
Group & add(const Item &item)
Definition: dump.cc:74
std::vector< Item > items
Definition: dump.h:97
std::shared_ptr< dump2d::Poly > poly
Definition: dump.h:69
std::shared_ptr< dump2d::Group > group
Definition: dump.h:71
std::shared_ptr< dump2d::Text > text
Definition: dump.h:70
Item(const dump2d::Poly &p)
Definition: dump.cc:64
std::shared_ptr< dump2d::Circle > circle
Definition: dump.h:72
Poly & close()
Definition: dump.cc:46
std::optional< Rgbi > fill_color
Definition: dump.h:30
std::vector< vec2f > points
Definition: dump.h:35
Poly & fill(const Rgbi &fill_color)
Definition: dump.cc:40
Poly & set_stroke(const std::vector< int > &new_stroke)
Definition: dump.cc:34
std::vector< int > stroke
Definition: dump.h:34
float stroke_width
Definition: dump.h:32
Text(const vec2f &p, const std::string &t, const Rgbi &c=NamedColor::black)
Definition: dump.cc:52
MinMaxer & include(const vec2f &point, float extra=0)
Definition: dump.cc:101
MinMaxer & operator<<(const vec2f &point)
Definition: dump.cc:112
std::function< float(float)> py
Definition: dump.cc:87
std::function< float(float)> px
Definition: dump.cc:86
Writer(const std::string &path)
Definition: dump.cc:91
Dumper(const std::string &path)
Definition: vec2.h:33
float x
Definition: vec2.h:34
float y
Definition: vec2.h:35