PreviewCanvas.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. package Slic3r::GUI::PreviewCanvas;
  2. use strict;
  3. use warnings;
  4. use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
  5. # must load OpenGL *before* Wx::GLCanvas
  6. use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
  7. use base qw(Wx::GLCanvas Class::Accessor);
  8. use Math::Trig qw(asin);
  9. use List::Util qw(reduce min max first);
  10. use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scale unscale);
  11. use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
  12. use Wx::GLCanvas qw(:all);
  13. __PACKAGE__->mk_accessors( qw(_quat _dirty init
  14. enable_picking
  15. enable_moving
  16. on_hover
  17. on_select
  18. on_double_click
  19. on_right_click
  20. on_move
  21. volumes
  22. print
  23. _sphi _stheta
  24. cutting_plane_z
  25. cut_lines_vertices
  26. bed_shape
  27. bed_triangles
  28. bed_grid_lines
  29. origin
  30. _mouse_pos
  31. _hover_volume_idx
  32. _drag_volume_idx
  33. _drag_start_pos
  34. _drag_start_xy
  35. _camera_target
  36. _zoom
  37. ) );
  38. use constant TRACKBALLSIZE => 0.8;
  39. use constant TURNTABLE_MODE => 1;
  40. use constant GROUND_Z => 0.02;
  41. use constant SELECTED_COLOR => [0,1,0,1];
  42. use constant HOVER_COLOR => [0.8,0.8,0,1];
  43. use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
  44. # make OpenGL::Array thread-safe
  45. {
  46. no warnings 'redefine';
  47. *OpenGL::Array::CLONE_SKIP = sub { 1 };
  48. }
  49. sub new {
  50. my ($class, $parent) = @_;
  51. # we request a depth buffer explicitely because it looks like it's not created by
  52. # default on Linux, causing transparency issues
  53. my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
  54. [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0]);
  55. $self->_quat((0, 0, 0, 1));
  56. $self->_stheta(45);
  57. $self->_sphi(45);
  58. $self->_zoom(1);
  59. # 3D point in model space
  60. $self->_camera_target(Slic3r::Pointf3->new(0,0,0));
  61. $self->reset_objects;
  62. EVT_PAINT($self, sub {
  63. my $dc = Wx::PaintDC->new($self);
  64. $self->Render($dc);
  65. });
  66. EVT_SIZE($self, sub { $self->_dirty(1) });
  67. EVT_IDLE($self, sub {
  68. return unless $self->_dirty;
  69. return if !$self->IsShownOnScreen;
  70. $self->Resize( $self->GetSizeWH );
  71. $self->Refresh;
  72. });
  73. EVT_MOUSEWHEEL($self, sub {
  74. my ($self, $e) = @_;
  75. # Calculate the zoom delta and apply it to the current zoom factor
  76. my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
  77. $zoom = max(min($zoom, 4), -4);
  78. $zoom /= 10;
  79. $self->_zoom($self->_zoom * (1-$zoom));
  80. # In order to zoom around the mouse point we need to translate
  81. # the camera target
  82. my $size = Slic3r::Pointf->new($self->GetSizeWH);
  83. my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
  84. $self->_camera_target->translate(
  85. # ($pos - $size/2) represents the vector from the viewport center
  86. # to the mouse point. By multiplying it by $zoom we get the new,
  87. # transformed, length of such vector.
  88. # Since we want that point to stay fixed, we move our camera target
  89. # in the opposite direction by the delta of the length of such vector
  90. # ($zoom - 1). We then scale everything by 1/$self->_zoom since
  91. # $self->_camera_target is expressed in terms of model units.
  92. -($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
  93. -($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
  94. 0,
  95. ) if 0;
  96. $self->_dirty(1);
  97. $self->Refresh;
  98. });
  99. EVT_MOUSE_EVENTS($self, \&mouse_event);
  100. return $self;
  101. }
  102. sub mouse_event {
  103. my ($self, $e) = @_;
  104. my $pos = Slic3r::Pointf->new($e->GetPositionXY);
  105. if ($e->LeftDClick) {
  106. $self->on_double_click->()
  107. if $self->on_double_click;
  108. } elsif ($e->LeftDown || $e->RightDown) {
  109. # If user pressed left or right button we first check whether this happened
  110. # on a volume or not.
  111. my $volume_idx = $self->_hover_volume_idx // -1;
  112. # select volume in this 3D canvas
  113. if ($self->enable_picking) {
  114. $self->deselect_volumes;
  115. $self->select_volume($volume_idx);
  116. $self->Refresh;
  117. }
  118. # propagate event through callback
  119. $self->on_select->($volume_idx)
  120. if $self->on_select;
  121. if ($volume_idx != -1) {
  122. if ($e->LeftDown && $self->enable_moving) {
  123. $self->_drag_volume_idx($volume_idx);
  124. $self->_drag_start_pos($self->mouse_to_3d(@$pos));
  125. } elsif ($e->RightDown) {
  126. # if right clicking on volume, propagate event through callback
  127. $self->on_right_click->($e->GetPosition)
  128. if $self->on_right_click;
  129. }
  130. }
  131. } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
  132. # get volume being dragged
  133. my $volume = $self->volumes->[$self->_drag_volume_idx];
  134. # get new position at the same Z of the initial click point
  135. my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
  136. my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
  137. # calculate the translation vector
  138. my $vector = $self->_drag_start_pos->vector_to($cur_pos);
  139. # apply new temporary volume origin and ignore Z
  140. $volume->origin->translate($vector->x, $vector->y, 0); #,,
  141. $self->_drag_start_pos($cur_pos);
  142. $self->Refresh;
  143. } elsif ($e->Dragging && !defined $self->_hover_volume_idx) {
  144. if ($e->LeftIsDown) {
  145. # if dragging over blank area with left button, rotate
  146. if (defined $self->_drag_start_pos) {
  147. my $orig = $self->_drag_start_pos;
  148. if (TURNTABLE_MODE) {
  149. $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
  150. $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
  151. $self->_stheta(150) if $self->_stheta > 150;
  152. $self->_stheta(0) if $self->_stheta < 0;
  153. } else {
  154. my $size = $self->GetClientSize;
  155. my @quat = trackball(
  156. $orig->x / ($size->width / 2) - 1,
  157. 1 - $orig->y / ($size->height / 2), #/
  158. $pos->x / ($size->width / 2) - 1,
  159. 1 - $pos->y / ($size->height / 2), #/
  160. );
  161. $self->_quat(mulquats($self->_quat, \@quat));
  162. }
  163. $self->Refresh;
  164. }
  165. $self->_drag_start_pos($pos);
  166. } elsif ($e->RightIsDown) {
  167. # if dragging over blank area with right button, translate
  168. if (defined $self->_drag_start_xy) {
  169. # get point in model space at Z = 0
  170. my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0);
  171. my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0);
  172. $self->_camera_target->translate(
  173. @{$orig->vector_to($cur_pos)->negative},
  174. );
  175. $self->Refresh;
  176. }
  177. $self->_drag_start_xy($pos);
  178. }
  179. } elsif ($e->LeftUp || $e->RightUp) {
  180. if ($self->on_move && defined $self->_drag_volume_idx) {
  181. $self->on_move->($self->_drag_volume_idx);
  182. }
  183. $self->_drag_volume_idx(undef);
  184. $self->_drag_start_pos(undef);
  185. $self->_drag_start_xy(undef);
  186. } elsif ($e->Moving) {
  187. $self->_mouse_pos($pos);
  188. $self->Refresh;
  189. } else {
  190. $e->Skip();
  191. }
  192. }
  193. sub reset_objects {
  194. my ($self) = @_;
  195. $self->volumes([]);
  196. $self->_dirty(1);
  197. }
  198. sub zoom_to_bounding_box {
  199. my ($self, $bb) = @_;
  200. # calculate the zoom factor needed to adjust viewport to
  201. # bounding box
  202. my $max_size = max(@{$bb->size}) * 2;
  203. my $min_viewport_size = min($self->GetSizeWH);
  204. $self->_zoom($min_viewport_size / $max_size);
  205. # center view around bounding box center
  206. $self->_camera_target($bb->center);
  207. }
  208. sub zoom_to_bed {
  209. my ($self) = @_;
  210. if ($self->bed_shape) {
  211. $self->zoom_to_bounding_box($self->bed_bounding_box);
  212. }
  213. }
  214. sub zoom_to_volume {
  215. my ($self, $volume_idx) = @_;
  216. my $volume = $self->volumes->[$volume_idx];
  217. my $bb = $volume->bounding_box;
  218. $self->zoom_to_bounding_box($bb);
  219. }
  220. sub zoom_to_volumes {
  221. my ($self) = @_;
  222. $self->zoom_to_bounding_box($self->volumes_bounding_box);
  223. }
  224. sub volumes_bounding_box {
  225. my ($self) = @_;
  226. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  227. $bb->merge($_->bounding_box) for @{$self->volumes};
  228. return $bb;
  229. }
  230. sub bed_bounding_box {
  231. my ($self) = @_;
  232. my $bb = Slic3r::Geometry::BoundingBoxf3->new;
  233. $bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
  234. return $bb;
  235. }
  236. sub max_bounding_box {
  237. my ($self) = @_;
  238. my $bb = $self->bed_bounding_box;
  239. $bb->merge($self->volumes_bounding_box);
  240. return $bb;
  241. }
  242. sub set_auto_bed_shape {
  243. my ($self, $bed_shape) = @_;
  244. # draw a default square bed around object center
  245. my $max_size = max(@{ $self->volumes_bounding_box->size });
  246. my $center = $self->volumes_bounding_box->center;
  247. $self->set_bed_shape([
  248. [ $center->x - $max_size, $center->y - $max_size ], #--
  249. [ $center->x + $max_size, $center->y - $max_size ], #--
  250. [ $center->x + $max_size, $center->y + $max_size ], #++
  251. [ $center->x - $max_size, $center->y + $max_size ], #++
  252. ]);
  253. $self->origin(Slic3r::Pointf->new(@$center[X,Y]));
  254. }
  255. sub set_bed_shape {
  256. my ($self, $bed_shape) = @_;
  257. $self->bed_shape($bed_shape);
  258. # triangulate bed
  259. my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
  260. my $bed_bb = $expolygon->bounding_box;
  261. {
  262. my @points = ();
  263. foreach my $triangle (@{ $expolygon->triangulate }) {
  264. push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #))
  265. }
  266. $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
  267. }
  268. {
  269. my @lines = ();
  270. for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
  271. push @lines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
  272. }
  273. for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
  274. push @lines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
  275. }
  276. @lines = @{intersection_pl(\@lines, [ @$expolygon ])};
  277. my @points = ();
  278. foreach my $polyline (@lines) {
  279. push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #))
  280. }
  281. $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
  282. }
  283. $self->origin(Slic3r::Pointf->new(0,0));
  284. }
  285. sub load_object {
  286. my ($self, $object, $all_instances) = @_;
  287. my $z_min = $object->raw_bounding_box->z_min;
  288. # color mesh(es) by material
  289. my @materials = ();
  290. # sort volumes: non-modifiers first
  291. my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
  292. my @volumes_idx = ();
  293. foreach my $volume (@volumes) {
  294. my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
  295. foreach my $instance_idx (@instance_idxs) {
  296. my $instance = $object->instances->[$instance_idx];
  297. my $mesh = $volume->mesh->clone;
  298. $instance->transform_mesh($mesh);
  299. my $material_id = $volume->material_id // '_';
  300. my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;
  301. if (!defined $color_idx) {
  302. push @materials, $material_id;
  303. $color_idx = $#materials;
  304. }
  305. my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
  306. push @$color, $volume->modifier ? 0.5 : 1;
  307. push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
  308. instance_idx => $instance_idx,
  309. mesh => $mesh,
  310. color => $color,
  311. origin => Slic3r::Pointf3->new(0,0,-$z_min),
  312. );
  313. push @volumes_idx, $#{$self->volumes};
  314. {
  315. my $vertices = $mesh->vertices;
  316. my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
  317. $v->verts(OpenGL::Array->new_list(GL_FLOAT, @verts));
  318. }
  319. {
  320. my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
  321. $v->norms(OpenGL::Array->new_list(GL_FLOAT, @norms));
  322. }
  323. }
  324. }
  325. return @volumes_idx;
  326. }
  327. sub deselect_volumes {
  328. my ($self) = @_;
  329. $_->selected(0) for @{$self->volumes};
  330. }
  331. sub select_volume {
  332. my ($self, $volume_idx) = @_;
  333. $self->volumes->[$volume_idx]->selected(1)
  334. if $volume_idx != -1;
  335. }
  336. sub SetCuttingPlane {
  337. my ($self, $z) = @_;
  338. $self->cutting_plane_z($z);
  339. # perform cut and cache section lines
  340. my @verts = ();
  341. foreach my $volume (@{$self->volumes}) {
  342. foreach my $volume (@{$self->volumes}) {
  343. my $expolygons = $volume->mesh->slice([ $z + $volume->origin->z ])->[0];
  344. $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
  345. foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
  346. push @verts, (
  347. unscale($line->a->x), unscale($line->a->y), $z, #))
  348. unscale($line->b->x), unscale($line->b->y), $z, #))
  349. );
  350. }
  351. }
  352. }
  353. $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
  354. }
  355. # Given an axis and angle, compute quaternion.
  356. sub axis_to_quat {
  357. my ($ax, $phi) = @_;
  358. my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
  359. my @q = map { $_ * (1 / $lena) } @$ax;
  360. @q = map { $_ * sin($phi / 2.0) } @q;
  361. $q[$#q + 1] = cos($phi / 2.0);
  362. return @q;
  363. }
  364. # Project a point on the virtual trackball.
  365. # If it is inside the sphere, map it to the sphere, if it outside map it
  366. # to a hyperbola.
  367. sub project_to_sphere {
  368. my ($r, $x, $y) = @_;
  369. my $d = sqrt($x * $x + $y * $y);
  370. if ($d < $r * 0.70710678118654752440) { # Inside sphere
  371. return sqrt($r * $r - $d * $d);
  372. } else { # On hyperbola
  373. my $t = $r / 1.41421356237309504880;
  374. return $t * $t / $d;
  375. }
  376. }
  377. sub cross {
  378. my ($v1, $v2) = @_;
  379. return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
  380. @$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
  381. @$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
  382. }
  383. # Simulate a track-ball. Project the points onto the virtual trackball,
  384. # then figure out the axis of rotation, which is the cross product of
  385. # P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
  386. # deformed trackball-- is a trackball in the center, but is deformed
  387. # into a hyperbolic sheet of rotation away from the center.
  388. # It is assumed that the arguments to this routine are in the range
  389. # (-1.0 ... 1.0).
  390. sub trackball {
  391. my ($p1x, $p1y, $p2x, $p2y) = @_;
  392. if ($p1x == $p2x && $p1y == $p2y) {
  393. # zero rotation
  394. return (0.0, 0.0, 0.0, 1.0);
  395. }
  396. # First, figure out z-coordinates for projection of P1 and P2 to
  397. # deformed sphere
  398. my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
  399. my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
  400. # axis of rotation (cross product of P1 and P2)
  401. my @a = cross(\@p2, \@p1);
  402. # Figure out how much to rotate around that axis.
  403. my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
  404. my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
  405. # Avoid problems with out-of-control values...
  406. $t = 1.0 if ($t > 1.0);
  407. $t = -1.0 if ($t < -1.0);
  408. my $phi = 2.0 * asin($t);
  409. return axis_to_quat(\@a, $phi);
  410. }
  411. # Build a rotation matrix, given a quaternion rotation.
  412. sub quat_to_rotmatrix {
  413. my ($q) = @_;
  414. my @m = ();
  415. $m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
  416. $m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
  417. $m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
  418. $m[3] = 0.0;
  419. $m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
  420. $m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
  421. $m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
  422. $m[7] = 0.0;
  423. $m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
  424. $m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
  425. $m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
  426. $m[11] = 0.0;
  427. $m[12] = 0.0;
  428. $m[13] = 0.0;
  429. $m[14] = 0.0;
  430. $m[15] = 1.0;
  431. return @m;
  432. }
  433. sub mulquats {
  434. my ($q1, $rq) = @_;
  435. return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
  436. @$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
  437. @$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
  438. @$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
  439. }
  440. sub mouse_to_3d {
  441. my ($self, $x, $y, $z) = @_;
  442. my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
  443. my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
  444. my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
  445. $y = $viewport[3] - $y;
  446. $z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
  447. my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
  448. return Slic3r::Pointf3->new(@projected);
  449. }
  450. sub mouse_ray {
  451. my ($self, $x, $y) = @_;
  452. return Slic3r::Linef3->new(
  453. $self->mouse_to_3d($x, $y, 0),
  454. $self->mouse_to_3d($x, $y, 1),
  455. );
  456. }
  457. sub GetContext {
  458. my ($self) = @_;
  459. if (Wx::wxVERSION >= 2.009) {
  460. return $self->{context} ||= Wx::GLContext->new($self);
  461. } else {
  462. return $self->SUPER::GetContext;
  463. }
  464. }
  465. sub SetCurrent {
  466. my ($self, $context) = @_;
  467. if (Wx::wxVERSION >= 2.009) {
  468. return $self->SUPER::SetCurrent($context);
  469. } else {
  470. return $self->SUPER::SetCurrent;
  471. }
  472. }
  473. sub Resize {
  474. my ($self, $x, $y) = @_;
  475. return unless $self->GetContext;
  476. $self->_dirty(0);
  477. $self->SetCurrent($self->GetContext);
  478. glViewport(0, 0, $x, $y);
  479. $x /= $self->_zoom;
  480. $y /= $self->_zoom;
  481. glMatrixMode(GL_PROJECTION);
  482. glLoadIdentity();
  483. glOrtho(
  484. -$x/2, $x/2, -$y/2, $y/2,
  485. -200, 10 * max(@{ $self->max_bounding_box->size }),
  486. );
  487. glMatrixMode(GL_MODELVIEW);
  488. }
  489. sub InitGL {
  490. my $self = shift;
  491. return if $self->init;
  492. return unless $self->GetContext;
  493. $self->init(1);
  494. glClearColor(0, 0, 0, 1);
  495. glColor3f(1, 0, 0);
  496. glEnable(GL_DEPTH_TEST);
  497. glClearDepth(1.0);
  498. glDepthFunc(GL_LEQUAL);
  499. glEnable(GL_CULL_FACE);
  500. glEnable(GL_BLEND);
  501. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  502. # Set antialiasing/multisampling
  503. glDisable(GL_LINE_SMOOTH);
  504. glDisable(GL_POLYGON_SMOOTH);
  505. glEnable(GL_MULTISAMPLE);
  506. # ambient lighting
  507. glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
  508. glEnable(GL_LIGHTING);
  509. glEnable(GL_LIGHT0);
  510. glEnable(GL_LIGHT1);
  511. glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0);
  512. glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
  513. glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.8, 0.8, 0.8, 1);
  514. glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0);
  515. glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
  516. glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 1, 1, 1, 1);
  517. # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
  518. glShadeModel(GL_SMOOTH);
  519. glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
  520. glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
  521. glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
  522. glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
  523. # A handy trick -- have surface material mirror the color.
  524. glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  525. glEnable(GL_COLOR_MATERIAL);
  526. glEnable(GL_MULTISAMPLE);
  527. }
  528. sub Render {
  529. my ($self, $dc) = @_;
  530. # prevent calling SetCurrent() when window is not shown yet
  531. return unless $self->IsShownOnScreen;
  532. return unless my $context = $self->GetContext;
  533. $self->SetCurrent($context);
  534. $self->InitGL;
  535. glClearColor(1, 1, 1, 1);
  536. glClearDepth(1);
  537. glDepthFunc(GL_LESS);
  538. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  539. glMatrixMode(GL_MODELVIEW);
  540. glLoadIdentity();
  541. if (TURNTABLE_MODE) {
  542. glRotatef(-$self->_stheta, 1, 0, 0); # pitch
  543. glRotatef($self->_sphi, 0, 0, 1); # yaw
  544. } else {
  545. my @rotmat = quat_to_rotmatrix($self->quat);
  546. glMultMatrixd_p(@rotmat[0..15]);
  547. }
  548. glTranslatef(@{ $self->_camera_target->negative });
  549. if ($self->enable_picking) {
  550. glDisable(GL_LIGHTING);
  551. $self->draw_volumes(1);
  552. glFlush();
  553. glFinish();
  554. if (my $pos = $self->_mouse_pos) {
  555. my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
  556. my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
  557. $self->_hover_volume_idx(undef);
  558. $_->hover(0) for @{$self->volumes};
  559. if ($volume_idx <= $#{$self->volumes}) {
  560. $self->_hover_volume_idx($volume_idx);
  561. $self->volumes->[$volume_idx]->hover(1);
  562. $self->on_hover->($volume_idx) if $self->on_hover;
  563. }
  564. }
  565. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  566. glFlush();
  567. glFinish();
  568. glEnable(GL_LIGHTING);
  569. }
  570. # draw objects
  571. $self->draw_volumes;
  572. # draw ground and axes
  573. glDisable(GL_LIGHTING);
  574. my $z0 = 0;
  575. {
  576. # draw ground
  577. my $ground_z = GROUND_Z;
  578. if ($self->bed_triangles) {
  579. glEnable(GL_BLEND);
  580. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  581. glEnableClientState(GL_VERTEX_ARRAY);
  582. glColor4f(0.6, 0.7, 0.5, 0.3);
  583. glNormal3d(0,0,1);
  584. glVertexPointer_p(3, $self->bed_triangles);
  585. glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
  586. glDisableClientState(GL_VERTEX_ARRAY);
  587. glDisable(GL_BLEND);
  588. # draw grid
  589. glTranslatef(0, 0, 0.02);
  590. glLineWidth(3);
  591. glColor3f(0.95, 0.95, 0.95);
  592. glEnableClientState(GL_VERTEX_ARRAY);
  593. glVertexPointer_p(3, $self->bed_grid_lines);
  594. glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
  595. glDisableClientState(GL_VERTEX_ARRAY);
  596. }
  597. my $volumes_bb = $self->volumes_bounding_box;
  598. {
  599. # draw axes
  600. $ground_z += 0.02;
  601. my $origin = $self->origin;
  602. my $axis_len = max(
  603. 0.3 * max(@{ $self->bed_bounding_box->size }),
  604. 2 * max(@{ $volumes_bb->size }),
  605. );
  606. glLineWidth(2);
  607. glBegin(GL_LINES);
  608. # draw line for x axis
  609. glColor3f(1, 0, 0);
  610. glVertex3f(@$origin, $ground_z);
  611. glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
  612. # draw line for y axis
  613. glColor3f(0, 1, 0);
  614. glVertex3f(@$origin, $ground_z);
  615. glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
  616. # draw line for Z axis
  617. glColor3f(0, 0, 1);
  618. glVertex3f(@$origin, $ground_z);
  619. glVertex3f(@$origin, $ground_z+$axis_len);
  620. glEnd();
  621. }
  622. # draw cutting plane
  623. if (defined $self->cutting_plane_z) {
  624. my $plane_z = $z0 + $self->cutting_plane_z;
  625. my $bb = $volumes_bb;
  626. glDisable(GL_CULL_FACE);
  627. glEnable(GL_BLEND);
  628. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  629. glBegin(GL_QUADS);
  630. glColor4f(0.8, 0.8, 0.8, 0.5);
  631. glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
  632. glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
  633. glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
  634. glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
  635. glEnd();
  636. glEnable(GL_CULL_FACE);
  637. glDisable(GL_BLEND);
  638. }
  639. }
  640. glEnable(GL_LIGHTING);
  641. glFlush();
  642. $self->SwapBuffers();
  643. }
  644. sub draw_volumes {
  645. my ($self, $fakecolor) = @_;
  646. glEnable(GL_BLEND);
  647. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  648. if (defined($self->print) && !$fakecolor) {
  649. my $tess = gluNewTess();
  650. gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
  651. gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
  652. gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
  653. gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
  654. gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
  655. gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
  656. foreach my $object (@{$self->print->objects}) {
  657. foreach my $layer (@{$object->layers}) {
  658. my $gap = 0;
  659. my $top_z = $layer->print_z;
  660. my $bottom_z = $layer->print_z - $layer->height + $gap;
  661. foreach my $copy (@{ $object->_shifted_copies }) {
  662. glPushMatrix();
  663. glTranslatef(map unscale($_), @$copy, 0);
  664. foreach my $slice (@{$layer->slices}) {
  665. glColor3f(@{COLORS->[0]});
  666. gluTessBeginPolygon($tess);
  667. glNormal3f(0,0,1);
  668. foreach my $polygon (@$slice) {
  669. gluTessBeginContour($tess);
  670. gluTessVertex_p($tess, (map unscale($_), @$_), $layer->print_z) for @$polygon;
  671. gluTessEndContour($tess);
  672. }
  673. gluTessEndPolygon($tess);
  674. foreach my $polygon (@$slice) {
  675. foreach my $line (@{$polygon->lines}) {
  676. if (0) {
  677. glLineWidth(1);
  678. glColor3f(0,0,0);
  679. glBegin(GL_LINES);
  680. glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
  681. glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
  682. glEnd();
  683. }
  684. glLineWidth(0);
  685. glColor3f(@{COLORS->[0]});
  686. glBegin(GL_QUADS);
  687. glNormal3f((map $_/$line->length, @{$line->normal}), 0);
  688. glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
  689. glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
  690. glVertex3f((map unscale($_), @{$line->b}), $top_z);
  691. glVertex3f((map unscale($_), @{$line->a}), $top_z);
  692. glEnd();
  693. }
  694. }
  695. }
  696. glPopMatrix(); # copy
  697. }
  698. }
  699. }
  700. gluDeleteTess($tess);
  701. return;
  702. }
  703. glEnableClientState(GL_VERTEX_ARRAY);
  704. glEnableClientState(GL_NORMAL_ARRAY);
  705. foreach my $volume_idx (0..$#{$self->volumes}) {
  706. my $volume = $self->volumes->[$volume_idx];
  707. glPushMatrix();
  708. glTranslatef(@{$volume->origin});
  709. glVertexPointer_p(3, $volume->verts);
  710. glCullFace(GL_BACK);
  711. glNormalPointer_p($volume->norms);
  712. if ($fakecolor) {
  713. my $r = ($volume_idx & 0x000000FF) >> 0;
  714. my $g = ($volume_idx & 0x0000FF00) >> 8;
  715. my $b = ($volume_idx & 0x00FF0000) >> 16;
  716. glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
  717. } elsif ($volume->selected) {
  718. glColor4f(@{ &SELECTED_COLOR });
  719. } elsif ($volume->hover) {
  720. glColor4f(@{ &HOVER_COLOR });
  721. } else {
  722. glColor4f(@{ $volume->color });
  723. }
  724. glDrawArrays(GL_TRIANGLES, 0, $volume->verts->elements / 3);
  725. glPopMatrix();
  726. }
  727. glDisableClientState(GL_NORMAL_ARRAY);
  728. glDisable(GL_BLEND);
  729. if (defined $self->cutting_plane_z) {
  730. glLineWidth(2);
  731. glColor3f(0, 0, 0);
  732. glVertexPointer_p(3, $self->cut_lines_vertices);
  733. glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
  734. }
  735. glDisableClientState(GL_VERTEX_ARRAY);
  736. }
  737. package Slic3r::GUI::PreviewCanvas::Volume;
  738. use Moo;
  739. has 'mesh' => (is => 'ro', required => 1);
  740. has 'color' => (is => 'ro', required => 1);
  741. has 'instance_idx' => (is => 'ro', default => sub { 0 });
  742. has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
  743. has 'verts' => (is => 'rw');
  744. has 'norms' => (is => 'rw');
  745. has 'selected' => (is => 'rw', default => sub { 0 });
  746. has 'hover' => (is => 'rw', default => sub { 0 });
  747. sub bounding_box {
  748. my ($self) = @_;
  749. my $bb = $self->mesh->bounding_box;
  750. $bb->translate(@{$self->origin});
  751. return $bb;
  752. }
  753. 1;