NAME
Graphics::Penplotter::GcodeXY::Geometry3D - Role::Tiny role adding 3-D geometry to GcodeXY
VERSION
v0.4.0
SYNOPSIS
$g->gsave(); # saves both 2-D and 3-D state
$g->initmatrix3(); # reset 3-D CTM
$g->translate3(50, 50, 0); # move 3-D origin
$g->rotate3(axis => [0,0,1], deg => 45); # spin around Z
$g->scale3(10); # uniform scale
my $m = $g->sphere(0, 0, 0, 1, 12, 24); # UV sphere mesh
my $s = $g->flatten_to_2d($m); # project to 2-D edge list
$g->draw_polylines($s); # draw via host pen hooks
$g->grestore();
$g->output('myplot.gcode');
DESCRIPTION
This Role::Tiny role grafts a full 3-D geometry pipeline onto Graphics::Penplotter::GcodeXY. It is careful not to shadow any of the host class's own methods:
gsave/grestoreare extended viaaftermodifiers so the 3-D Current Transformation Matrix (CTM3) stack stays in sync with the host's 2-D stack without overriding anything.All 3-D variants of 2-D methods that would otherwise conflict are renamed with a
3suffix:translate3,scale3,rotate3,moveto3,line3,lineR3,initmatrix3,translateC3,currentpoint3.All 3-D private state lives in
$self->{_g3_*}hash slots.
The coordinate system is right-handed (+Z out of the screen) and the internal representation uses 4x4 homogeneous matrices in row-major order.
REQUIRED METHODS
The consuming class must provide:
penup pendown _genfastmove _genslowmove stroke _croak gsave grestore
METHODS
CTM and transforms
initmatrix3()-
Reset the 3-D CTM to identity.
translate3($tx, $ty [, $tz])-
Pre-multiply the 3-D CTM by a translation.
translateC3()-
Move the 3-D origin to the current 3-D position, then reset the position to (0,0,0).
scale3($sx [, $sy [, $sz]])-
Pre-multiply by a scale matrix. If
$sy/$szare omitted they default to$sx(uniform scale). rotate3(axis => [$ax,$ay,$az], deg => $angle)-
Pre-multiply by a rotation around an arbitrary axis.
rotate3_euler($rx, $ry, $rz [, $order])-
Pre-multiply by a sequence of axis-aligned rotations.
$orderis a three-character string such as'XYZ'(default). compose_matrix($aref, $bref)-
Multiply two 4x4 matrices; returns a new matrix ref. Neither input is modified.
invert_matrix($mref)-
Invert a 4x4 matrix (Gauss-Jordan with partial pivoting). Returns a matrix ref, or
undefif the matrix is singular.
3-D current point
currentpoint3()-
Return the current 3-D position as a list
($x, $y, $z). currentpoint3($x, $y, $z)-
Set the current 3-D position.
Point transformation
transform_point($pt_ref)-
Transform a point (arrayref
[$x,$y,$z]) through the current CTM3. Returns($tx, $ty, $tz). transform_points($pts_ref)-
Transform an arrayref of points; returns an arrayref of
[$tx,$ty,$tz].
3-D drawing primitives
moveto3($x, $y [, $z])-
Lift the pen, fast-move to the projected 2-D position, lower pen.
movetoR3($dx, $dy [, $dz])-
Relative
moveto3from the current 3-D position. line3($x1,$y1,$z1 [, $x2,$y2,$z2])-
Six-arg form: move to start, draw to end. Three-arg form: draw from the current position.
lineR3($dx, $dy [, $dz])-
Relative line from the current 3-D position.
polygon3(x1,y1,z1, ...)-
Move to the first triple, draw through the remaining triples.
polygon3C(x1,y1,z1, ...)-
Like
polygon3but automatically closes back to the first point. polygon3R(dx1,dy1,dz1, ...)-
Like
polygon3but each triple is relative to the preceding point.
Wireframe solid drawing (draw directly, no mesh returned)
box3($x1,$y1,$z1, $x2,$y2,$z2)-
Draw a wireframe axis-aligned box between two opposite corners.
cube($cx,$cy,$cz,$side)-
Draw a wireframe cube centred at
(cx,cy,cz). axis_gizmo($cx,$cy,$cz [, $len [, $cone_r [, $cone_h]]])-
Draw three labelled axis arrows (X, Y, Z) as wireframe lines with small arrow cones.
$lenis the total axis length (default 1). The cone radius and height default to 5% and 15% of$lenrespectively.
Mesh-returning solid primitives
All of the following return a mesh structure { verts => \@v, faces => \@f } which can be passed to flatten_to_2d, hidden_line_remove, mesh_to_obj, etc.
mesh($verts_ref, $faces_ref)-
Low-level constructor. Build a mesh from existing arrays.
prism($cx,$cy,$cz, $w,$h,$d)-
Axis-aligned rectangular prism (box) centred at
(cx,cy,cz), with dimensionsw(X),h(Y),d(Z). A cube isprismwithw == h == d. Returns a closed 12-face triangulated mesh. sphere($cx,$cy,$cz, $r [, $lat [, $lon]])-
UV-sphere mesh.
$latand$loncontrol the tessellation density (defaults 12 and 24). icosphere($cx,$cy,$cz, $r [, $subdivisions])-
Icosphere mesh built by repeated midpoint subdivision of a regular icosahedron.
$subdivisionsdefaults to 2 (320 faces). Produces a more uniform tessellation thansphere. cylinder($base_ref, $top_ref, $r [, $seg])-
Cylinder mesh.
$base_refand$top_refare[$x,$y,$z]centre points. Side walls only; no end caps. frustum($cx,$cy,$cz, $r_bot,$r_top,$height [, $seg])-
General truncated cone (frustum) centred at
(cx,cy,cz). Both end caps are included. When$r_top == 0this is a cone; when$r_bot == $r_topit is a closed cylinder. cone($cx,$cy,$cz, $r,$height [, $seg])-
Convenience wrapper:
frustumwithr_top = 0. capsule($cx,$cy,$cz, $r,$height [, $seg_r [, $seg_h]])-
Cylinder with hemispherical end caps.
$heightis the length of the cylindrical body (not counting the caps).$seg_ris the number of radial segments (default 16);$seg_his the number of latitudinal segments per hemisphere (default 8). plane($cx,$cy,$cz, $w,$h [, $segs_w [, $segs_h]])-
Flat rectangular mesh in the XY plane, centred at
(cx,cy,cz). Dimensions$wx$h; subdivided into$segs_wx$segs_hquads. Useful for floors, billboards, and UI surfaces. torus($cx,$cy,$cz, $R,$r [, $maj_seg [, $min_seg]])-
Torus mesh in the XY plane.
$Ris the major radius (centre of tube to centre of torus);$ris the minor radius (tube radius). Defaults: 24 major segments, 12 minor segments. disk($cx,$cy,$cz, $r [, $seg])-
Flat circular disk mesh in the XY plane. Fan-triangulated from the centre. Vertex 0 is the centre; vertices
1..$segare the rim. pyramid($cx,$cy,$cz, $r,$height [, $sides])-
Regular-polygon-base pyramid.
(cx,cy,cz)is the base centre;$ris the base circumradius;$heightis the height in +Z.$sidesdefaults to 4 (square pyramid). The base cap is included.
Quaternions
quat_from_axis_angle($axis_ref, $deg)-
Return a unit quaternion
[$w,$x,$y,$z]. quat_to_matrix($q)-
Convert a quaternion to a 4x4 rotation matrix.
quat_slerp($q1, $q2, $t)-
Spherical linear interpolation (0 <= t <= 1).
Mesh utilities
bbox3($mesh_or_pts)-
Returns
([$minx,$miny,$minz], [$maxx,$maxy,$maxz]). compute_normals($mesh)-
Compute face and averaged vertex normals in-place; returns
$mesh.
Visibility
backface_cull($mesh [, view_dir => \@dir])-
Return an arrayref of the indices (into
$mesh->{faces}) of faces whose outward normal has a negative dot product with the view direction, i.e. faces that are pointing toward the camera and therefore visible.The view direction defaults, in order of preference, to:
The
fwdvector stored by the most recentset_camera()call, if one has been made.[0, 0, -1](looking along the negative Z axis) if no camera has been set.
Pass
view_dir => \@vto override both defaults with an explicit unit vector pointing from the scene toward the camera. occlusion_clip($mesh [, res => N])-
Z-buffer rasterisation; returns arrayref of
[[p1,p2],...]edge segments. -
Back-face cull then occlusion clip; returns edge segments.
2-D output
flatten_to_2d($mesh_or_polylines)-
Project mesh edges or pass-through polylines; returns
[[$p1,$p2],...]. draw_polylines($segs_ref)-
Emit segments via the host's pen hooks; calls
stroke()at the end. project_to_svg($obj [, %opts])-
Return an SVG string of the projected edges.
Mesh I/O
mesh_to_obj($mesh [, $name])-
Serialise to ASCII OBJ string.
mesh_from_obj($str)-
Parse an ASCII OBJ string; returns a mesh.
mesh_to_stl($mesh [, $name])-
Serialise to ASCII STL string.
mesh_from_stl($str)-
Parse an ASCII STL string; returns a mesh (vertices are de-duplicated).
Camera
The three camera methods together provide a gluLookAt-style workflow for positioning the viewer in 3-D space. Typical usage:
$g->set_camera(
eye => [5, 5, 10], # camera position in world space
center => [0, 0, 0], # point to look at
up => [0, 1, 0], # world up hint
);
$g->camera_to_ctm(); # bake view matrix into the 3-D CTM
my $m = $g->sphere(0, 0, 0, 1);
my $v = $g->backface_cull($m); # uses stored fwd automatically
$g->draw_polylines($g->flatten_to_2d(
{ verts => $m->{verts},
faces => [ @{$m->{faces}}[@$v] ] }
));
Camera state is saved and restored by gsave() / grestore() alongside the 3-D CTM and current point.
set_camera(eye => \@e, center => \@c [, up => \@u])-
Position the camera using a gluLookAt-style interface.
eye(required) is an arrayref[$ex,$ey,$ez]giving the camera position in world space.center(required) is an arrayref[$cx,$cy,$cz]giving the point in world space the camera looks at. Must differ fromeye; croaks with"same point"otherwise.up(optional, default[0,1,0]) is an arrayref[$ux,$uy,$uz]giving a world-space hint for the upward direction. Must not be parallel to the view direction (center - eye); croaks with"parallel"if it is.The method builds an orthonormal right-handed camera basis:
forward = normalise(center - eye) right = normalise(forward x up_hint) up = right x forward # reorthogonalisedand assembles a standard 4x4 world-to-camera view matrix from those three basis vectors and the eye position. The result is stored internally and can be retrieved with
get_camera().After the call,
backface_cull()picks up the storedfwdvector automatically unless an explicitview_diris supplied. get_camera()-
Return the camera record set by the most recent
set_camera()call, orundefifset_camera()has not yet been called (or if the record was cleared bygrestore()).The returned hashref contains:
eye-
Arrayref
[$ex,$ey,$ez]- the eye position as supplied. center-
Arrayref
[$cx,$cy,$cz]- the look-at point as supplied. up-
Arrayref
[$ux,$uy,$uz]- the reorthogonalised up vector (not necessarily the same as the hint passed in). fwd-
Arrayref
[$fx,$fy,$fz]- unit forward vector pointing fromeyetowardcenter. Used automatically bybackface_cull(). view-
4x4 arrayref-of-arrayrefs - the world-to-camera view matrix in row-major order. The first three rows encode the camera basis (right, up, -forward); the translation is in the rightmost column.
camera_to_ctm()-
Pre-multiply the view matrix stored by
set_camera()into the 3-D CTM via the same_g3_premul4path used bytranslate3,rotate3, etc.After this call every subsequent
transform_point(),moveto3(),line3(),flatten_to_2d(), etc. automatically includes the camera transform; no further action is needed to get correct projected coordinates.Croaks with
"no camera"if called beforeset_camera().This method is additive: calling it more than once will compound the camera transform. If you need to reposition the camera, call
initmatrix3()first (or usegsave()/grestore()). set_perspective(fov => $deg [, aspect => $r, near => $n, far => $f])-
Build and store a symmetric perspective projection matrix (equivalent to OpenGL's
gluPerspective). Does not modify the CTM; callperspective_to_ctm()afterwards to apply it.fov(optional, default45)-
Vertical field of view in degrees. Must be in (0, 180).
aspect(optional, default1.0)-
Viewport width / height ratio.
near(optional, default0.1)-
Distance to the near clipping plane. Must be > 0.
far(optional, default100)-
Distance to the far clipping plane. Must be >
near.
The resulting 4x4 matrix (row-major, column-vector convention) has
-1in position [3][2], which causestransform_point()to computetw = -z. The existing perspective divide (triggered whenevertw != 0andtw != 1) then gives correctly foreshortened X and Y coordinates. Z is discarded byflatten_to_2d(), which only retains X and Y.Projection state is saved and restored by
gsave()/grestore(). set_frustum(left => $l, right => $r, bottom => $b, top => $t, near => $n, far => $f)-
Build and store an asymmetric (off-axis) perspective projection matrix (equivalent to OpenGL's
glFrustum). All six named arguments are required;nearandfardefault to0.1and100respectively.left,right,bottom,topare the X/Y extents of the view volume at the near plane. Settingleft = -rightandbottom = -topreproduces a symmetric frustum identical toset_perspective().Use this method for off-centre viewports, stereo rendering, or anamorphic projections. As with
set_perspective(), callperspective_to_ctm()afterwards to apply it. perspective_to_ctm()-
Pre-multiply the projection matrix stored by
set_perspective()orset_frustum()into the 3-D CTM (CTM := P x CTM).After this call every subsequent
transform_point()call applies the full view + projection pipeline, andflatten_to_2d()yields perspective-correct 2-D coordinates.Croaks if neither
set_perspective()norset_frustum()has been called.Typical full workflow:
$g->initmatrix3(); $g->set_camera(eye => [5,5,10], center => [0,0,0]); $g->camera_to_ctm(); $g->set_perspective(fov => 45, aspect => 1.0, near => 0.1, far => 100); $g->perspective_to_ctm(); # All drawing calls now produce perspective-foreshortened output. get_projection()-
Return the 4x4 projection matrix stored by the most recent
set_perspective()orset_frustum()call, orundefif none has been set. The matrix is an arrayref of four arrayrefs (row-major).
Numeric configuration
set_tolerance($eps),get_tolerance()-
Set/get the floating-point equality tolerance (default 1e-9).
set_units($units)-
Store a units tag (e.g.
'mm'); no automatic scaling is applied. set_coordinate_convention(handedness => ..., euler_order => ...)-
Store convention tags for downstream use.
IMPLEMENTATION NOTES
State storage
All 3-D state lives in $self->{_g3_*} to avoid collisions with the host:
_g3_CTM 4x4 arrayref-of-arrayrefs (current 3-D transform)
_g3_gstate arrayref of save-state records
_g3_posx/y/z 3-D current point
_g3_camera camera record (eye/center/up/fwd/view) set by set_camera()
_g3_tolerance floating-point epsilon
_g3_units units tag
_g3_handedness coordinate convention
_g3_euler_order default Euler axis order
Why method names have a 3 suffix
Role::Tiny does not override methods that already exist in the consuming class. Because Graphics::Penplotter::GcodeXY already defines translate, scale, rotate, moveto, line, lineR, gsave, grestore, and initmatrix, any role method with the same name would be silently discarded. The 3 suffix makes the 3-D variants unambiguous. gsave/grestore are augmented instead via after modifiers.
Mesh representation
All solid primitives that return a mesh use the structure:
{ verts => \@v, faces => \@f }
where @v is an array of [$x,$y,$z] position arrayrefs and @f is an array of [$i0,$i1,$i2] triangle index arrayrefs. Winding order is counter-clockwise when viewed from the outside (right-hand normal pointing outward).
AUTHOR
Albert Koelmans
LICENSE
Same terms as Perl itself.