- When the faces that overlap form a cycle of depth orders, the order
broke the cycle only after drawing every face with nothing to wait
for, so a cap could be drawn before the walls and they painted over
it. A face drawn last now keeps its place: no face moves across it.
- A face with a corner at or past the eye, which the perspective
projection holds just in front of the eye, no longer gets a plane,
so its depth is not measured on a plane its drawing does not match.
- docs/three-d.md states the masked member rule in Groups as the
Fills and strokes section does, and docs/target-app-parity.md cites
the Inflate test by its name.
- Each contour of a graphic is cut only at the faces its own bounds reach,
and the points cut at the faces count toward a limit of 64,000,000.
- The paint copies of a graphic's parts count toward a budget of their own,
one copy for a part's fill and one for its seam stroke when it takes one.
- A part filled with a pattern, or whose gradient has a transparent stop or
point, takes no seam stroke.
- Under several lights an opaque solid part also covers its seams, with its
lit art clipped to a band of the seam stroke's width.
- A line of a graphic that reaches farther than any surface is refused, as a
curve already was.
- A full turn places each segment by its own middle, so the cut at the back
falls on the segment edge nearest half a turn from the middle.
- A group's members are measured for graphics only when it has some.
- docs/three-d.md: Revolve shows no Face control, and the new limits.
Extrude & Bevel, Extrude & Bevel (Classic) and Inflate sorted the fill
walls and the faces of a stroke that leaves gaps (dashed, profiled,
brushed) by one depth each, so a far stroke face could draw over a near
fill wall and the reverse. Two such strokes on one shape drew over each
other the same way, and Inflate drew a plain stroke as if it covered
the fill's edge, which an inflated fill can rise in front of.
- Each flat facet now carries its plane, so its depth can be measured
at any point under it, with orthographic and perspective views.
- Where faces of one shape's fill and strokes overlap in the wrong
order, the overlapping faces are ordered by their depths where they
overlap. A fill wall and its covering stroke keep their order.
- A face of a stroke that leaves gaps that passes through a fill wall,
a covering stroke's face or an earlier such stroke's face is cut
along the line where their depths meet, keeping its curved outline,
within the face limit and the paint allowance.
- Under Inflate, no stroke counts as covering the fill's edge.
- The faces go in one order when the fill and strokes are at full
opacity with Normal blending. A member of a 3D group with an opacity
mask or a blending mode of its own orders its faces inside it when
its own paints qualify. A gradient with transparent stops keeps the
one order with graphics on its cap, and so does Inflate in gradients
under a colored light. A solid color under a colored light, drawn as
one composite per surface, still draws each surface whole.
The Materials tab and threeD.mapGraphic now map graphics onto
Revolve's swept side, unrolled flat: x runs around the axis (the
angle times the profile's distance from the axis at that point) and
y along the profile. A graphic keeps its size at every distance from
the axis, so it reaches farther around where the profile comes nearer
the axis. Each face of the side draws the part that falls on it: a
graphic narrows toward the silhouette, follows the profile along its
length, is shaded with the surface (several lights included) and is
hidden where the side faces away. A part that runs past the end of
the profile reaches the curved rim and stops there. The middle of the
side faces the viewer with the axis on either edge. On a compound
profile or a group the graphics go on the longest contour.
- three-d: with Revolve::map_surface set, side faces carry a
SurfaceCell (corners on the page and on the unrolled side, whether
the outside faces the viewer) and a closed profile off the axis
takes an anchor where its side is cut; without it the faces are as
before. revolve_surface_size gives the side's size for a new
graphic's fit.
- effects: Revolve takes `graphics` (front only); surface_parts cuts
and maps them per face; curves are cut into straight pieces one at
a time, and commands refuse graphics past the point limits;
switching between Extrude, Plane and Revolve keeps the front's
graphics.
- The Materials tab hides Face for Revolve; params text, docs/mcp.md,
docs/three-d.md and the progress docs describe the change.
- i18n: the complete-message catalogs translate the three new
refusals in place of the planar-faces refusal they replace, and ja,
es, zh-hans and zh-hant translate the Materials tab's label.
Extrude & Bevel (both kinds) and Inflate built every fill and stroke
solid of an object, each up to 163,840 faces, before any check, so one
effect on a path with many strokes or a group with many members could
build several times that. The solids are now built in order and their
faces counted together: once they pass 163,840 (VectorCraft's own
limit), no later solid is built, the object draws its source art, and
effect.apply, effect.setParams, Export 3D Object and Expand as
Wireframes report "The 3D surface is too complex; simplify the shape".
A stroke left out for its own limits is not counted.
Export 3D Object now builds Extrude & Bevel (Classic) meshes with the
Classic bevel options, so the exported solid matches the drawn one.
A solid now shares its mapped graphics only with the run of solids just
before it that have the same opacity and blending mode. Before, it
joined any earlier solid with the same settings, so with members A, B
and C where A and C match, A's graphic was drawn after C and over B.
The Materials tab shows no artboard handles for graphics on a hidden
fill or stroke, which draws no solid and no graphics.
docs/three-d.md now says that pattern tiles in a graphic keep their own
colors instead of taking the face's light.
A 3D effect left an image in a clipping group out only when the image's
box missed the clipping path's box. An image in the corner of a round
clip's box, a turned image whose box reaches a clip but whose frame does
not, an image in an even-odd hole, or one inside each of two nested clips
but outside the area they show together was refused with the Image Trace
message, although it draws nothing.
The image's frame, the pixel rectangle as its transform places it, is now
cut to every clipping path around it in turn, each with its own fill rule,
and the image is left out when nothing is left. An image the clips show
any part of, a strip narrower than a hundredth of a point included, is
still refused. One effect's art cuts image frames at most 1,024 times;
past that, or when a cut fails, the art is refused.
- Under a perspective, the shared 400,000-element limit of a 3D effect's graphics now counts
what each path draws once mapped: a straight side stays one element, and only curves count
as their 8 pieces. Two copies of a 30,000-sided polygon now both draw. The symbol check
counts the same way, and without a perspective it counts each curve once.
- The Materials tab's handles on the artboard are hidden while a geometry effect (such as
Transform) comes after the 3D effect of a selected object, since that effect draws the
graphics away from the handles.
- A press on the canvas while its context menu is open only closes the menu, and no longer
grabs a graphic's handle.
- A symbol instance inside a graphic's art shows its changes to a dynamic symbol, and a symbol
with 9-slice guides is placed through them, as the canvas draws the instance. Paths that
cross a guide are cut at it.
The Materials tab of the 3D and Materials panel lists the document's
graphics (symbols). Add as Single Graphic keeps the selected art as a
new symbol (threeD.addGraphic), and clicking a thumbnail maps it onto
the front of each selected object's 3D effect (threeD.mapGraphic) in
one undo step. The tab lists the mapped graphics with Face, X, Y, Scale
and Rotation and Remove Graphic, and a handle on the artboard drags a
graphic over its face.
The graphics are the new `graphics` parameter of threeD.extrude and
threeD.rotate: drawn on Extrude & Bevel's front and back caps and on
the Plane type, turned, foreshortened, twisted and tapered with the
face, shaded with its light and clipped to it. Symbol renames follow
into the graphics, and Select All Unused counts a symbol a graphic uses.
Docs: docs/mcp.md, docs/three-d.md, docs/effects-parity.md.
effect.expandAsWireframes returned "draws no faces" for an object with
Revolve (Classic). It now takes the faces of Revolve (Classic) with that
effect's own options, and Rotate (Classic) is expanded as Rotate is: every
fill and stroke turned as one plane.
Docs: docs/mcp.md, docs/three-d.md.
Effect › 3D and Materials › 3D (Classic) › Rotate (Classic)… turns every
fill and stroke as one flat plane, as Rotate does, and shades it with the
classic No Shading or Diffuse surface.
- `effect.apply` with `threeD.classicRotate`: the Position rotation and
Perspective, `surface` (`none` or `diffuse`), the classic lights,
Ambient Light, Blend Steps and Shading Color. It has no highlight, no
Draw Hidden Faces and no shadow.
- The dialog shows Position on the Rotation tab and the surface and
lights on the Lighting tab, with no Object tab.
- Groups and layers turn about one shared center, and the effect also
goes on a fill or stroke, as Rotate's does.
- `effect.lightingPreset` refuses it, as it refuses the other classic
3D effects.
- Unknown parameters, numbers out of range, bad flags, a surface other
than No Shading or Diffuse and bad shading colors are refused with an
error.
Effect › 3D and Materials › 3D (Classic) › Revolve (Classic)… sweeps the
profile round a vertical axis, as Revolve does, and draws every face with
the Wireframe, No Shading, Diffuse and Plastic surfaces of Extrude & Bevel
(Classic).
- `effect.apply` with `threeD.classicRevolve`: Angle (0 to 360), Cap,
Offset (0 to 1000 pt) from the left or right edge of the profile's
bounds, the Position rotation and Perspective, and the classic lights,
Ambient Light, highlight, Blend Steps and Shading Color.
- Groups and layers revolve about one shared axis, and the effect also
goes on a fill or stroke, as Revolve's does.
- `effect.lightingPreset` refuses it, as it refuses Extrude & Bevel
(Classic).
- Revolve faces carry their view-space normal, and
`three_d::revolve_facets` hands them to the classic surface code.
- Unknown parameters, numbers out of range and bad flags, edges,
surfaces and colors are refused with an error.
Window > 3D and Materials > Quick Actions > Expand as Wireframes, and the
command effect.expandAsWireframes, replace each selected object that has a
visible 3D effect (Extrude & Bevel, Extrude & Bevel (Classic), Revolve,
Inflate or Rotate) with a group of paths: the edges of the faces the effect
draws for the object as it is turned and projected now.
- Each edge appears once. Edges that meet end to end form one path, closed
where it comes back to its start, and curved edges stay curves. Geometry
effects after the 3D effect reshape the edges as they reshape the drawn
surface.
- The paths have a 0.5 pt black stroke with round joins and no fill. The
group keeps the object's id, name, transparency, attributes, slice and
text wrap, and is selected. A layer stays a layer holding the edges.
- It is one undo step. Nothing selected, no visible 3D effect, a solid that
can't be built or is drawn as its source art, or more than 1,000,000 edges
in one object is an error, and the document does not change.
Docs: docs/mcp.md, docs/three-d.md, docs/effects-parity.md,
docs/target-app-parity.md.
Fixes#6354
Revolve, Extrude & Bevel, Inflate and Rotate take a Shadows switch on the
Lighting tab, with Position (Behind Object or Below Object), Distance from
Object (0 to 100%) and Shadow Bounds (10 to 400%), saved in the effect as
shadow, shadowPosition, shadowDistance and shadowBounds.
With shadows on, the first light casts a hard-edged vector shadow of the
solid: every face of every solid of the object projected along the light
onto a plane behind the object (square to the view) or below it (square to
its own vertical axis and turned with it), filled black with the nonzero
rule at 50% times the opacity of the art that casts it, in that art's
blending mode, and drawn behind the solids. Distance moves the plane away
by a share of the object's largest size; Bounds ends the plane at a square
about the foot of the object's center, four times that size across at
100%. No shadow is drawn while Shading is off, when the first light's
intensity is 0, or when it does not reach the plane. A member's opacity
mask and blending mode apply to the part of the shadow it casts. A shadow
is cast from at most 327,680 faces; past that, commands are refused and a
loaded file draws the source path. The canvas, SVG and PDF exports draw the
shadow, and Expand Appearance keeps it as a path. Values out of range are
refused before the document changes.
Docs: docs/three-d.md, docs/mcp.md, docs/effects-parity.md and the
docs/target-app-parity.md evidence list.
Extrude & Bevel (both kinds) and Inflate drew the faces of the fill's
solid and the stroke's solid in one order by each face's center depth.
At a concave corner a long fill wall could sort after a stroke wall in
front of it, so stripes of the fill showed through the stroke.
- Facets now record whether they are side faces (walls, bevels,
Inflate's sides) and whether they are turned away from the viewer.
- With caps on, each fill side face turned toward the viewer is drawn
before every face of the same shape's stroke whose bounds overlap
it, for strokes whose solid hides the fill's whole edge: a closed
shape, a width, no dashes, width profile or brush, and a solid color
or a gradient without transparent stops. Caps, other shapes in a
group, hollow effects and translucent or blended surfaces keep their
order.
Docs: docs/three-d.md (Fills and strokes), docs/target-app-parity.md.
On a group, these effects drew every member's solids in Normal mode: only
an opacity mask on a member or on a group inside the 3D group was applied
to the solids it holds. A member's blending mode, and that of a group
inside the 3D group, now apply the same way, to the solids of the members
it holds together. Group Revolve already did this.
A cap inset by a bevel fitted each run of sampled edges along one curve
as a whole, while the wall next to it fitted each sampled edge by itself.
The two outlines lay up to 0.25 pt apart on an 800 pt circle, which left
a gap or an overlap between the cap and the wall.
An inset outline is now fitted one sampled edge at a time in the cap as
well, so both faces draw the same curves. Caps that are not inset keep
one cubic for each curve of the art.
Test: solid::tests::a_beveled_cap_and_its_walls_share_their_curves.
Docs: docs/three-d.md, docs/target-app-parity.md.
A 3D effect on a group built its solids from the members' paths and
paint and left out each member's opacity mask, and the mask of a group
inside the 3D group. Clipping groups, blends and symbol instances
resolved for the effect lost their masks the same way.
A member, or a group inside the 3D group, with an opacity mask now
gets a scope like one with its own opacity or blending mode, and the
mask goes on the group that holds that member's surfaces. Revolve
applies it with the member's opacity and blending mode; Extrude &
Bevel, Extrude & Bevel (Classic), Inflate and Rotate draw each solid
on its own when a mask is present and put the masked members' solids
in a group with the mask. A resolved clipping group, blend or symbol
instance keeps its mask. A disabled mask is ignored.
Extrude & Bevel (both kinds) and Rotate wrote every face as straight
segments between sample points, so Expand Appearance and the exports
gave a corner at every sample. Sampling now records the curve each
polygon edge follows, and a facet carries an outline of cubics rebuilt
from it: exact without perspective, within 0.001 pt with it, and within
the sampling tolerance for bevel insets. Caps, Rotate's face and the
wall and bevel edges that meet a cap keep curves; interior bevel edges,
edges a twist cuts and edges past 1,048,576 curves stay straight.
Inflate's faces stay polygons.
Docs: docs/three-d.md, docs/target-app-parity.md.
The message for a group member that can't become a solid now names groups
and clipping groups too. docs/three-d.md states that an image left out of a
clipping group also needs no visible fills or strokes of its own.
Inflate's grid clipped every ring of the outline in each cell the
outline crosses and tested every edge for each cell's center, so its
work grew with the outline's points times the cells: a comb of 4,000
teeth (16,002 points) took 1.1 to 1.3 s and one of 16,000 teeth 8.8 to
9.1 s. Inflate now counts that work (the edge index's entries, the edges
its queries look at and the ring points its cells clip) and refuses the
shape with "The 3D surface is too complex; simplify the shape" once it
passes 268,435,456, VectorCraft's own value (about 0.65 s). The index
takes at most 16,777,216 entries (64 MiB). Each row's inside test reads
the sorted crossings of its line, and a cell clips only the rings whose
bounds meet it, so a 65,536-point circle inflates in 0.66 s instead of
1.0 to 1.2 s.
The effects test of a stroke past the limits uses 40,000 corners, so its
outline passes the 65,536 points a solid takes.
Docs: docs/three-d.md, docs/mcp.md.
Each Extrude & Bevel and Inflate solid now takes up to 65,536 outline
points (was 2,048) and 163,840 faces (was 32,768), VectorCraft's own
limits, as Revolve's. Before any point is sampled, the outline's lines
and curves are counted with each curve's tolerance pieces, and the faces
the walls, bevels and caps (Extrude) or the sides (Inflate) need. When
they do not fit, every curve takes fewer pieces, down to 4; a shape that
does not fit at 4 is refused with the existing surface error before any
face is built. The subpath count is capped at the point limit too. The
error texts are unchanged.
A closed path of 2,000 curves now extrudes with a bevel and inflates,
and paths of 5,000 and 10,000 curves build at the default settings.
Docs: docs/three-d.md, docs/mcp.md, docs/target-app-parity.md.
An image in a clipping group whose bounds lie wholly outside the clipping
path's bounds draws nothing, so a 3D effect leaves it out instead of
refusing it. An image with effects, fills or strokes of its own, or one in
a group with effects inside the clipping group, is still refused, since
those can draw it inside the clip.
- Resolving the symbol instances, blends and clipping groups a 3D effect
builds from counts every object it visits or makes and stops past
262,144 with an error. Nested symbol instances that end in hidden art
are refused instead of being expanded at every level. Each object is
visited once; a group is no longer walked again for each level above it.
- A symbol instance, blend or clipping group inside a 3D group keeps its
appearance, so its geometry effects reshape the art it shows. A nested
group's geometry effects now reshape its members inside a 3D group too,
and inside a clipping group they apply before the clip cuts the art.
- Hidden members and guides of a group inside a clipping group build no
solid.
- Tests cover an empty clipping group, a blend set to no steps, an
instance of an empty symbol, a symbol holding a clipping group, the
paint-copy budget across resolved art, and art past the limits drawing
as it does without the effect.
Extrude & Bevel, Extrude & Bevel (Classic), Inflate, Rotate and Revolve
now apply to a clipping group (or a layer with a clipping mask), a blend
or a symbol instance, alone or inside a group or layer. The solid is
built from the art the object shows: the art inside the clipping path
(fills cut to the clip, strokes as their outlines cut to the clip), the
blend's keys and steps, and the symbol's art where the instance places
it. Validation, the canvas, bounds, Expand Appearance, Rasterize and
assets.add3D read the same art from the document.
An image is refused, alone or in a group, with a message to trace it
first with Object > Image Trace > Make. A missing symbol, a symbol that
holds an instance of itself, art nested more than 64 levels deep and
more than 4,096 objects are refused. The refusal messages change, and
their rows change in every catalog.
Docs: docs/three-d.md, docs/revolve.md, docs/mcp.md,
docs/target-app-parity.md.
Type sweeps as its glyph outlines and stays editable. On a group or layer every visible member sweeps about one axis on the edge of the members' bounds together, each in its own paint.
A surface now takes up to 16,384 profile points and 163,840 faces. Curves take fewer samples, down to 4, when a surface passes those limits, and the command reports the failure below that, after the geometry effects before the Revolve and with the cumulative size of its paint copies counted. Mesh export turns the walls of holes toward them. Expand Appearance works on type with Extrude as well as Revolve.
Fixes#3617
The boolean's answer went through orient(), which judges each ring's
side from its first point. The boolean often starts a contour where it
touches another, so a triangle whose tip rests on a square's edge was
dropped, and an even-odd hole touching its outline was filled. The
boolean already runs holes against their outlines, so its rings are now
only turned as a whole.
Other fixes:
- A bowtie drawn as one contour has no area of its own. It was dropped
before the meet check and built nothing, although it fills both lobes.
Sampling now keeps rings with no area, and orient() drops them after
the check. Flat rings, every point on one line, fill nothing and are
still dropped, so a stray flat contour doesn't send separate art
through the boolean.
- An open subpath was closed along its end handles when the profile went
through the boolean, but straight otherwise, as the renderer fills it.
It is now closed straight before the boolean too.
- An empty union was taken as a refusal. Contours that cancel (a square
drawn twice under even-odd) built a square. They now build nothing.
- Contours meet within twice the boolean's own sweep tolerance,
computed by its rule from every anchor and handle of the path it is
given. pathops snaps anchors onto nearby heights before the sweep
joins edges within that tolerance, so contours up to about 1.4 times
it apart can come out as one; within twice it, every contour it
might join goes through it, even far from the origin, and the few it
leaves apart cost a boolean that builds the same shape.
- A one-anchor subpath draws nothing, so it is skipped, as the renderer
and the boolean skip it.
- A profile the boolean refuses reuses the rings already sampled.
Tests:
- touching parts and holes;
- an open subpath with handles, alone and beside crossing bars;
- a flat contour beside separate squares;
- contours joined far from the origin, including by handles alone,
and contours a few millionths apart near it kept apart;
- triangles snapped together and joined at 1.4 times the tolerance,
the same triangles left apart and kept whole, and the factor pinned
between 1.9 and 2.2 times, with the worst case just past twice the
tolerance left apart by pathops;
- a 24-bar grid: too complex for Extrude and Inflate, whole under Rotate;
- the merged rings turned as a whole;
- a one-anchor loop;
- the bowtie and the cancelling squares under all three effects;
- a 3,001-point star that the boolean refuses (TooComplex), built as
drawn.
Docs: the pathops README's Limits section now says only the try_
functions refuse, lists OpenPath and the caught panic, and describes
unite_all's answers, and which calls take a precision. three-d.md and effects-parity.md (the 3D row, its
history and status line) describe the merge and its limits.
The boolean that merges crossing contours cost about as much as the rest
of a plain extrusion, and it ran on every profile. Most art has no
contours that meet, so the profile is now sampled first (its point limit
checked there, before any boolean) and resolved only when two edges
cross, touch, run along each other or fold back, in one contour or
across two. Edges are swept by their left ends, within a pair budget past
which the boolean decides. Touching counts: squares sharing an edge
inflate as one puff, not two with a seam.
On a release build, Extrude of 120 separate contours takes 0.31 ms
instead of 4.18 ms; profiles that do cross cost what they did.
Tests: the meeting cases, squares sharing an edge, the fallback for a
profile the boolean refuses, Inflate's extent and area for the crossing
k, and the coincident circles' area. The hairline test now holds a
crossing pair so that its profile still goes through the boolean.
Docs: the merge is described once for all three effects, linking to a new
Limits section in the pathops README instead of repeating its limits,
and the point limit notes that a merged outline can have more points.
With several objects selected, Shading, Revolve's Cap, Keep visible surfaces
only when expanding and the other checkboxes read a setting the effects hold
with different values as off. A mixed Shading also turned off the Lighting
tab's controls.
- A mixed checkbox shows a dash with its reset dot filled; a click turns it on
for every selected effect, and other changes leave it as each effect has it.
- A mixed Shading leaves the light settings available.
- A mixed menu choice shows its default; a mixed light color fills its reset dot.
- Add Light is unavailable while a setting of a single light is mixed.
The panel opens from Window › 3D and Materials (window.panel {panel:
"threeD"}). Its object types Plane, Extrude, Revolve and Inflate apply
Rotate, Extrude & Bevel, Revolve or Inflate to each selected object
without a 3D effect, and change the 3D effect of each other selected
object in place, all as one undo step. Below them are the effect
dialog's Object, Rotation and Lighting tabs with the same controls.
A changed setting goes to every selected 3D effect that takes it, as
one undo step; a drag previews and records one step on release, and a
change of selection during a drag commits it first, and so does closing
or hiding the panel. A setting the
selected effects hold with different values shows its default with its
reset dot filled. A light list edit reaches only the objects whose list
matches the first object's.
effect.setParams takes threeDType (plane, extrude, revolve, inflate):
the settings both effects take keep their values, rotation angles wrap
into -180..180 outside Revolve, and the rest start at the new effect's
defaults. Another value, or a non-3D effect at index, is refused; the
type an effect already has, with nothing else to change, records no
undo step.
Materials, shadows and ray-traced rendering are not part of this.
Docs: docs/three-d.md, docs/mcp.md, docs/effects-parity.md,
docs/target-app-parity.md.
Fixes#3672
Extrude & Bevel (Classic) ships, so the remaining-work lists in
effects-parity.md, gaps.md (G3), target-app-parity.md and three-d.md
name Revolve (Classic), Rotate (Classic) and Map Art instead of all of
3D Classic.
A 3D effect on a group or layer builds a solid from every path,
compound path and type in it, all turned about one shared axis, as the
public docs describe for groups. Fixes#4506.
Extrude & Bevel and Inflate built one solid from the path, painted with
the object's top fill or, without a fill, its top stroke. A filled and
stroked object lost its stroke, and a stroked open path was extruded as
the area it would enclose if it were closed.
Each visible stroke now builds a solid from its outline in its own
paint, beside the solid the top fill builds from the path, as Rotate
already turns each stroke's outline. When the fill and the strokes are
opaque with normal blending, the faces of all the solids are drawn in
one back-to-front order; otherwise each solid is drawn on its own, in
the order of the appearance. Under a light that is not white, a surface
drawn as one composite or with an opacity mask is also drawn on its
own. In that one order a flat cap turned toward the viewer is drawn
after every other face, untwisted solids included, so the stroke's
front cap covers the fill's walls inside it. The solids share one set
of bounds, and with Perspective one eye sees every inflated solid. A
stroke whose outline is past the 2,048-point limit is left out.
Inflate's cells cut by a hole now pass only the nearby contours to the
boolean. An inflated, outlined 72 pt "VectorCraft" takes about 8 ms
instead of 34 ms, and 66 ms with a 3 pt stroke.
Docs: docs/three-d.md (Fills and strokes), docs/target-app-parity.md.
Add opaque RGB light color and up to ten independently adjustable lights to
Revolve, Extrude & Bevel, Inflate and Rotate. Keep light selection, Add/Delete,
selected-light gizmos and preview transactions on the existing command path.
Preserve legacy white-light Inflate polygons, paint composition and seam joins,
including the 0.05 seam minimum. Use shared smooth normals for colored Inflate,
and batch solid-fill RGB channel paths behind one surface clip. Keep source
paths editable and SVG/PDF output vector artwork.
Rebase onto main f7de3d6a, retaining both sides of progress docs and all 22
catalogs. Shade freeform points once through the shared paint mapper. Document
release measurements and a reproducible CLI fixture, including the remaining
source-gradient cost and the one-light timing tradeoff.
Validation: full cargo xtask ci passed all 10 steps; 7,978 tests passed, zero
failed and 11 ignored. Four legacy white-light PNGs match unchanged main byte
for byte. Three paired release bench/perf repetitions completed without busy
warnings; all six standard perf runs passed every budget. Two-light solid
Inflate improved from 136.2 to 96.8 ms/frame at 1200x1200, default eight threads.
At 180° and 360° of twist, Extrude & Bevel and Inflate drew some wall
strips in the wrong order, and a far strip showed through a near one.
Each strip ran the whole length of an outline edge and was sorted by
the depth of its center, while a twisted wall can cross it far from
that center. The caps were sorted the same way.
Twisted walls and bevels are now drawn in pieces about as long as a
wall slice is wide (fewer near the 32,768-facet limit), and twisted
bevel steps turn at most 6° each, as wall slices do. A cap, or
Inflate's flat back, that faces the viewer is drawn after the walls,
and edge-on pieces of a twisted wall keep their own depth. Pieces that
face the viewer are drawn where their strip as a whole faces away, so
a twisted solid seen from behind shows its walls' corners around the
back cap. Untwisted solids give the same facets as on main.
Over 80 twisted squares, the facet seen over a point lay up to 79.7 pt
behind a nearer facet on main and now lies at most 5.0 pt behind.
"Twisted" in 72 pt type, extruded with a 360° twist, takes 12,311
facets instead of 7,622 and 3.1 ms instead of 1.9 ms.
Docs: docs/three-d.md.
The public 3D and Materials panel options give the Light Settings as
Intensity (0–100 %), Rotation (-180° to 180°), Height (0° to 90°),
Softness (0–100 %) and Ambient Light (0–200 %), with the light moved to
the back or the front of the object. The 3D effects (Revolve included)
had Direction and Elevation (an azimuth and elevation), Intensity and
Ambient (0–100 %).
- Rotation turns the light round the object on the page (our reading:
0° on its right, counterclockwise); Height raises it from beside the
object (0°) to straight in front (90°); Light behind the object puts
it at the back. The light gizmo's ring and line are Rotation and
Height.
- Softness wraps the light round the object: at 0 % it falls off with
the angle as before, at 100 % it reaches nothing only straight away.
- Ambient Light runs to 200 %; faces stay no brighter than the art.
- lightRotation, lightHeight, lightBehind and lightSoftness replace
lightAzimuth and lightElevation. An earlier light is read as the same
direction, so it shades exactly as before (old and new code give the
same colours for the four effects and five lights); effect.setParams
turns a stored one into the new form before applying new values, and
the parameter description names lightAzimuth and lightElevation as the
earlier form, so effect.apply and effect.setParams still take them. The
default light is the earlier default's: Rotation 125.26°, Height 30°.
- The dialog's Lighting tab shows the settings in the documented order;
Softness, Ambient Light and Light behind the object are in every
catalog, and the unused Direction, Elevation and Ambient are gone.
Light colour, presets, several lights and shadows are not built.
The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.
- The eye stands where a sphere about the object's centre, √3 times its
half size (the box around it fits inside), fills that angle of the
view: 0° is none, wider brings the eye nearer, and at 160° it is just
outside the sphere, never inside the object. The documentation doesn't
say how the angle places the eye; docs/three-d.md says this is our
reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
an earlier file or script is read as the angle that shows exactly the
same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
angle given too wins. effect.setParams turns a stored strength into the
angle before applying new values. The parameter description names
`perspective` as the earlier form, so effect.apply and effect.setParams
still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.
# Conflicts:
# docs/target-app-parity.md
The public 3D and Materials panel options list Depth, Twist, Taper and Cap
for every 3D type; Inflate had only Depth.
- Twist (0° to 360°) and Taper (1–100 %) turn and scale the back about the
shape's centre, as Extrude & Bevel's do: a puffed back is turned and
lowered in proportion, and the sides follow in slices.
- Cap: Solid closes the back; Hollow leaves it off, so the inflated front is
a shell open behind. The documentation doesn't say what Cap means for
Inflate; this reading is stated in docs/three-d.md.
- The dialog's Object tab shows them in the documented order (Depth, Twist,
Taper, Cap), then Volume and Both sides.
At 0 % volume a twisted, tapered Inflate has the extrusion's outline from
every side (tested).
A twisted, tapered, hollow Inflate draws on the canvas exactly as it
exports, and expands to faces with their own ids (tested). ROADMAP and the
parity pages list the new options; the 3D score stays at ~25 %.