* prc: rebuild compressed tessellations whose normals are recalculated A TESS_3D_Compressed (WD 7.8.9.7) was read to its end but never rebuilt, so prc-render drew nothing for it. Rebuild the form whose normals are recalculated, with three edge statuses per triangle, into a Mesh grouped by face and styled by each face's line attribute. The walk (tess/compressed.rs) takes a seed, then one apex per triangle, from a reference or the next point in the frame of the edge it continues across (WD 7.8.9.2), with the WD 12.3 Length, Unitize and MakeOrthoRep arithmetic bit for bit. Arrays that do not fit, or an edge shared by more than two triangles, leave the tessellation Compressed. Every triangle, style and face range is charged to the element allowance first. Stored-normals meshes, the one-status-per-triangle form, the planar bend check, the best-fit search and partial salvage are later PRs. Adapted from pdfcer (MIT, same author) at 0a249a73. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * prc: charge rebuilt compressed meshes after they fit; model_render test The rebuilt triangles, their styles and the face ranges are now charged to the element allowance once the arrays fit and before the face ranges are made. Arrays the walk cannot fit stay Compressed and spend nothing, so they no longer fail the file over a face count they never use. Tests: - a_rebuilt_compressed_mesh_is_charged_its_triangles_and_faces pins the exact charge (4 triangles x 3 elements + 2 faces); - arrays_that_do_not_fit_spend_no_triangles_or_faces; - rebuilt_triangles_are_grouped_by_face_with_their_styles, a direct by_face test with out-of-order faces, empty faces and styles; - model_render's a_compressed_prc_model_is_rebuilt_and_drawn, end to end from a PDF, cited in misc.3d-view; its hex fixture is the prc-render builder's output, kept so by the_automation_fixture_is_the_compressed_triangle. The walk's working tables are documented as bounded by the triangle count rather than charged. The module docs now say the arithmetic matches the WD pseudocode as read and that the traversal rules are inferred, not WD text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * prc: charge compressed styles and face ranges before making them A rebuilt compressed mesh's per-triangle styles were built before the budget was charged, and also when the normals were stored and the mesh was read past. The face attributes are now kept as read, and the styles are built only for a rebuilt mesh, after the charge. Each face range is a Range<usize>, two budget elements on 64-bit targets, so it is charged at its size, not as one element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * prc: bound the compressed rebuild walk by the allowance left The walk's vertices, triangles, edge counts, edge log and stack were allocated before anything was charged, and could hold several times the decoded arrays. The walk now gets the elements the reader has left and stops once its arrays, by length, would hold more; the tessellation then stays compressed, as when its arrays do not fit the walk. A face whose line attribute is 0 but whose behaviour is not now styles its triangles, so the behaviour bits are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * prc: say the rebuild walk is bounded by the allowance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * prc: cap the rebuild walk's first reservation by its limit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: KenM76 <KenM76> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
pdfcraft-prc-render
A headless CPU renderer for PRC 3D models read by pdfcraft-prc: placed meshes in world space, a fitted camera, a
depth-buffered triangle fill and headlight shading, into a straight-RGBA image.
- Layer: L3. Depends on
pdfcraft-prconly. - Licence: MIT OR Apache-2.0.
#![forbid(unsafe_code)]. Never panics on input: image size, triangle count and every index are checked, and damage returnsRenderError.
API
assemble(&PrcFile) -> Result<Vec<WorldMesh>, RenderError> // every placement's mesh moved into the world and coloured
place(&TriangleMesh, &Matrix, colour, triangle_colours) -> WorldMesh
Camera::fit(&Bounds, direction, up, perspective, aspect) / Camera::fit_meshes(&[WorldMesh], ...)
render(&[WorldMesh], &Camera, &RenderOptions) -> Result<Image, RenderError>
nav::Orbit::named(NamedView, perspective, Upright) / nav::Orbit::looking(direction, up, perspective)
orbit.turn(yaw, pitch) / pan_by([right, up]) / zoom_at(&Bounds, reach, aspect, factor, [right, up]) / anchor_at(&Camera, point) / centre_on / refit
orbit.camera(&Bounds, nav::reach(&[WorldMesh], &Bounds), aspect) -> Result<Camera, RenderError>
-
navis a viewer's orbit control without the GUI: the fitted view holds the sphere round the model's centre that holds every drawn vertex, so turning or reshaping the picture never changes the model's size. Onceanchor_atpicks a model point the view turns about it, and the point keeps its place on the picture. Pitch stays 0.01 rad short of the up axis, zoom within 0.05–50 of the fitted view, pan within 1,000 model radii; non-finite input is ignored. -
With no product tree, each tessellated mesh is drawn where it lies, in the default colour.
-
A triangle's own colour wins over its mesh's, which wins over
RenderOptions::colour. -
Opaque triangles are drawn first, then translucent ones blended over them; alpha 0 is not drawn.
-
Lighting::Headlight(default) shades by the angle to the view direction;Lighting::Unlitdraws flat colour. -
Perspective triangles are clipped at a near plane 1/10,000 of the eye-to-target distance from the eye; orthographic views are not clipped.
-
MAX_RENDER_PIXELS(2^24, about 16 MP) bounds the image: at most 64 MB of RGBA plus 128 MB of depth.MAX_ASSEMBLED_TRIANGLESandMAX_ASSEMBLED_VERTICES(2^24 each) bound oneassemblecall.MAX_RENDER_WORK(2^31) bounds the pixel centres onerendertests, summed over each triangle's clipped bounding box; past it the render returnsRenderError::TooMuchWork. That is still seconds of CPU at the top, so callers on a UI thread should render small or off-thread. -
Not yet: shadows, ambient occlusion, specular light, scene lights, textures, wires and markups.