Skip to content

Ollin 0.16.0

Choose a tag to compare

@eaviles eaviles released this 18 Oct 21:58
· 48 commits to main since this release
0.16.0
22b8ece

Added

  • A cloud a kernel writes, and its shadow. ComputeTexture3D is a box of samples kept on the GPU that a sketch's own compute kernel fills through compute(_:writing:), one thread per sample (PingPongTexture3D pairs two for a simulation that reads its last state, and compute(_:reading:writing:) steps it), and drawVolume draws it as a cloud in place of a Volume. The grid never leaves the GPU, so 128 samples a side can change every frame where a grid filled on the CPU held 60 frames a second to about 32. And a volume now casts its shadow: under castShadows() the solids under a cloud read darker by the light the cloud keeps from each casting light, read off the same light-transmittance grid the cloud shades itself by, so a plume stands in its own shadow on the floor and a ball inside it is shadowed by the smoke above it; a scene that never calls castShadows() draws as before. The live march now takes its step from volumetricQuality: one and a half steps per sample spacing at the default, one at .performance, and two at .detail and on every export, which is what every march took before. Examples/3D/Effects/Volumes fills its plume from a kernel (Plume.metal), 96 by 144 by 96 samples, and the plume shadows the pedestal.
  • A second visit to Mohamed Melehi. Examples/Recreations/MohamedMelehi gains two sketches where his wave meets the sun. Soleil is after the untitled panel of 1975 (cellulose paint on wood, 110 x 95 cm). A sun of white, yellow, ochre and red rings around a lavender core fills the panel. A ribbon of green and pink waves crosses it and parts down its middle: the two halves touch at the sun's center and open toward the corners as a hyperbola, so the sun shows as a pinched core and two arcs of its rings. Volcanique is after Volcanic (1985, shown in London as Volcanique 1 and 2), read beside the untitled panel of 1980. Four tall skies each hold an eclipsed sun, which grows by one ratio from panel to panel until the panel's edge cuts it. Under them stands a mountain of bands: straight gray and orange stripes up one face, and the wave in white and gold down the other. Each band is the region between two depths below the outline, and its two halves meet on the seam where those depths agree. In both, the wave travels because the phase is the clock, and the colors are palettes to edit in the inspector. Both export SVG that gives the rule back. Every edge is the declared wave at the clock's phase, to the file's precision, once the run's tilt (or the ridge and the two slopes) is solved from the file. Neighbors share their edges, written identically, and every band is one outline on unbroken stations from past one side of the panel to past the other. The rings are circles one ring apart on one center, and a cut sun is a true arc closed on its panel's edge, at the radius its ratio sets. In the PNG, every pixel away from an edge is the color the SVG paints there.
  • Typed text. A sketch reads the keyboard the way a text field does, through typedText(): an accent typed with a dead key (Option-E, then E) arrives as "é", the emoji viewer's pick arrives as itself, and Chinese or Japanese typed through an input method arrives once a candidate is chosen, with composingText holding the reading until then so the sketch can draw it underlined. textTyped() fires as text arrives, a held key repeats, Return arrives as "\n", and Delete as a backspace that caption.append(typing: typedText()) reads as taking a character away. caretPosition tells the input method where the line is, so its candidates open under it. key and keyPressed() still report the keys themselves, unchanged. A take records the text and a replay repeats it. Examples/Input/TypedText puts what you type on the canvas beside the last key and the last text it typed.
  • A curve and a row of bars as parameters. @Param var falloff = ResponseCurve([(0, 1), (0.5, 0.8), (1, 0)]) is a small graph in the inspector whose points drag, read by the sketch as a function from 0 to 1 (falloff(t)), for a falloff, a response, or a tone curve; a press on the graph adds a point on the curve, and count: bounds how many it holds. ResponseCurve runs smooth through its points and never overshoots them (monotone cubic interpolation, after Fritsch and Carlson), so it is a useful value outside the inspector too, and .easing hands it to a call that takes an Easing. A list of numbers, @Param(0...1) var accents = [1, 0.2, 0.6, 0.2] or a list of whole numbers, is a row of bars you paint by dragging across them, for a step pattern. Both are written back into the sketch by Save, held by a cue and a timeline key (moving bar by bar and point by point between two of the same length), set for a run with --param falloff=1@0,0.7@0.4,0@1 or --param accents=1,0.2,0.6, served over OSCQuery one address per bar or point, and edited by touch on the phone's remote surface. Examples/Live/Parameters has one of each.
  • Pinch and turn. Two fingers on the trackpad reach a sketch as plain values in draw(): pinchScale is how much the pinch changed the size this frame, as a factor (1 while nothing pinches), and turnAngle is how far the fingers turned, in radians clockwise on the screen (0 while nothing turns), the way rotate turns. So size *= pinchScale and spin += turnAngle keep a shape under the fingers. fingersPinched() and fingersTurned() fire once per event for a response in steps. viewControl() zooms with a pinch about the pointer and cameraControl() dollies with one, a take records and replays both, and the phone showing the sketch (PhoneDevice.show) sends its own two fingers to the same two values. Examples/Input/PinchAndTurn sizes and spins a rose and clicks its petal count with the turn; Input has the details, and Guide Chapter 38 teaches it.
  • Dóra Maurer joins the recreations. Examples/Recreations/DoraMaurer is new, after the Hungarian artist (1937 to 2026) whose Displacements moved eight colored fields across a grid by a rule she stated herself: a surface of 4:5 divided into 10 by 10 units, two planes each divided into four parts, the cold parts displaced one unit a step diagonally and the warm ones vertically and horizontally. Displacements runs that system: eight parts of 5 by 5 units start as contrasting pairs in the corners, a warm part walks the border and a cold one the diagonal, one unit a step, so every part rests in a corner again after five steps and all come home after twenty; the parts are ruled with a diagonal a cell in lanes of their own so coinciding lines bundle, on raw canvas with cells chosen by random numbers painted in full, or on a white sheet. QuasiImage is one such detail enlarged until every line is a band of flat color, the window chosen by random numbers around the middle of the grid, in the system's colors, in the broken colors of a relative quasi-image, or cut away from its white and hung on the wall over its shadow; one seed deals one system in both. Both export SVG that gives the rule back: every part a rectangle 5 by 5 cells apart from its lane, where the rule puts it at that step, one unit from where it was a step before, with its nine diagonals at their own two ends.
  • A soft body from any outline. World.addSoftBody(from:) turns a closed Shape (a lumpy circle, a star, a letter) into a squishy body in the 2D physics world: a ring of particles round each outline that keeps its area, keeps its corners, springs back toward its own shape, and never lets another soft body's outline in. Four settings decide the feel: stiffness (stretch), bend (corners), pressure (area, held by default), and shapeMemory (a disc dropped from its own height squashes by a third at 0, a tenth at the default 0.3, and is rigid at 1), each meaning the same body at any frame rate. A letter's counter stays open, a shape of several pieces is one body, and loose particles with a radius are pushed out when particlesCollide is on. The SoftBody it returns reads its shape, area, center, and velocity, takes push, applyForce, and move(by:), and inflates when its restArea is set. Rigid bodies pass through soft ones and nothing rubs yet. The Physics/Blobs example is rewritten over it: nine lumpy blobs sloshing under a gravity that swings from side to side. Physics
  • Eduardo Mac Entyre and Miguel Ángel Vidal join the recreations. Examples/Recreations/MacEntyreVidal is new, after the two painters who named Arte Generativo in Buenos Aires in 1959 and wrote in their 1960 manifesto that a generator is the line or figure that by its movement engenders a new geometric body. Circunferencias is Mac Entyre's circle: a disc holds families of circles, each one radius and one generator, a ring that sets the centers at one distance from the middle one constant angle apart until they close the turn, so the family passes through the middle and touches the rim as a rosette, or a path that walks the centers along one straight line one constant step apart into a lobe; three looks after the 1965, 1967, and 1970 paintings; over a cycle every ring family turns by one of its own steps and every path family slides one, so the picture comes back to itself. Focos is Vidal's straight line: an octagon with a lit corner between each pair of vertices, four faces ruled with chevrons parallel to their outer edges one step apart as far as the middle, so they meet as a pyramid seen from above, and at each corner and at a second light along each diagonal a fan of lines one constant angle apart, each line running until it leaves the two faces that meet there; three looks; the chevrons advance and the fans sweep by one step a cycle. Both export SVG that gives the rule back: every circle at its family's radius, so a ring family's centers read at one distance one angle apart with a count that closes the turn and a path family's on one line one step apart, and every line at its own two ends, so a fan reads as one point and one angle and a face's chevrons as one spacing with the count that fits.
  • A shape bent into a curved patch. CoonsPatch is a four-sided region whose sides are curves, read like a rectangle with point(u:v:): give four curves and each side follows its curve exactly (Coons's patch), or give four corners for the bilinear map. Contour.warped(from:into:) and the Shape form bend an outline from a rectangle into one, so lettering set in a box lands on a waving banner. The Shapes/WavingBanner example waves a striped banner and its word from a pole. Geometry
  • Packing rectangles. packRectangles(_:in:padding:allowsRotation:) fits rectangles of given sizes into one bin without overlapping, each at its own size, and returns a RectanglePacking with where each one went and which did not fit, for a contact sheet, a collage, or a texture atlas. It is MaxRects with the best short side fit, choosing among every waiting rectangle at each step (Jylänki's tightest variant). The Patterns/PackedPhotos example lays the bundled photos out on a page that changes shape. Packing rectangles
  • Bending outlines. Contour.warped(from:along:) bends an outline so it follows one curve the way it followed another, so lettering set on a straight baseline bends along a wave; pulled(toward:amount:radius:) draws the part of an outline near a point toward it, a smooth bump with an open contour's ends held; smoothed(neighbors:keepingCornersOver:) is the moving average with the corners left where they are; split(atCornersOver:span:) cuts an outline into pieces at its corners; and fitted(tolerance:) traces a dense outline, a mouse stroke of hundreds of points, with a few smooth cubic curves returned as a Path (Schneider's method). Each is on Shape too. Path.cubics reads any path back as cubic Béziers, one per command, so a fit's curves can be counted and their handles drawn. Examples Shapes/WavyLettering and Shapes/TraceToCurves. Geometry
  • A small picture drawn big, cell by cell. imageSampling(.nearest) keeps every pixel a hard square when a picture is drawn larger than it is (pixel art, a low-resolution canvas, a photo dithered to two levels at four canvas pixels a cell), with no enlarged copy made on the CPU; .linear, the default, blends as before, a picture drawn smaller is blended under either, and the choice is drawing state beside tint that withState { } restores. A web page made with --export-web reads the picture the same way. New example Images/PixelArt. Images
  • A still named by its moment. --at S beside --frame N on every one-frame export (--export, --export-exr, the vector files, the sheets, the separations, the spatial file): the frame is S seconds of the sketch's clock at --fps, so --at 2 is --frame 120 at 60 fps and nobody multiplies by the frame rate. Export
  • A tone map that keeps a bright color's hue. toneMap(.neutral), the Khronos PBR Neutral curve: .reinhard and .aces bend each channel on its own, so a bright orange of linear (4, 1, 0.2) came out yellow; the neutral curve compresses the three channels by one factor and holds the hue, with a gentler roll-off than film. The web page's present pass carries it, and the Rendering/ToneMapping example cycles four. HDR

Changed

  • A 3D world that is full says so. Every call that creates a body in a World3D (addBody, addStaticBodies, addCharacter, addVehicle, addRagdoll, addSoftBody, addRope, and addBodies(from:)) throws PhysicsError.full once the world holds the maxBodies it was made for, naming the limit; addBody used to hand back a body the solver never made, which sat at the origin for good, and addCharacter crashed. addBody, addStaticBodies, addCharacter, and addBodies(from:) are now throwing calls, so every 3D sketch gains a try on them (a setup() lets the error out, a click handler writes try?). A ground set past the limit notes the failure instead, since a property has no caller to throw to. Physics in 3D
  • A copy's turn is a Rotation3D. MeshInstance.rotation is a Rotation3D rather than three Euler angles, so a copy turns about any axis exactly and a physics body's own rotation places it without a conversion that lost precision near a quarter turn about y. .aboutY(a) spins a copy about y, and Rotation3D.euler(x:y:z:) is the three angles composed the way rotateX, rotateY, rotateZ would be, which is what rotation: Vector3(a, b, c) meant; SurfaceSample.alignment(spin:) returns a Rotation3D too. Instancing
  • A wide frame's files keep their depth. A .wide or .extended PNG is written at 16 bits a channel under the Display P3 curve. It was 8 bits under a linear profile, which left a dark gradient two codes. A HEIC's picture is written at 10 bits where it was 8, and it comes out a smaller file, within half a percent of the frame. A .wide video's frames and a recording of a wide window stay 8 bits and now take the same dither a standard frame takes, so a slow gradient that came out as 3 flat bands is 1,006 short runs. Color output
  • A see-through still stores its color as drawn. A PNG of a background(.clear) canvas keeps each pixel's color beside its coverage, the form PNG defines, and the dither goes on that color. The bytes used to hold the color already scaled by its coverage, so a fill at alpha 0.1 kept 26 of the 64 grays drawn, where it keeps all 64 now, and a translucent field stored one code everywhere instead of averaging to the shade asked for. OllinApp.image(of:) of such a canvas hands back an image tagged straight alpha (CGImageAlphaInfo.first), where it was tagged premultipliedFirst. Drawn through Core Graphics the two are the same picture; code that reads the bytes itself now reads the color as drawn. A frame grab for Syphon or a recording stays premultiplied, with its dither moved onto the stored value too. Export
  • A simulation can read a filtered layer, a frame late. A filtered(_:) or combined(with:_:) output in a SimField's inputs used to read as zero, since filters resolve after the sims step. It now arrives as it stood at the end of the frame before, kept for the field in a texture of its own, so a kernel can read a measurement of what the sketch drew: a wake pushed by the distance to a moving mass (.distanceField()), as it was a frame ago. It reads as zero on the first frame and after a frame in which the layer did not resolve. Effects
  • A Shape drawn again unchanged costs a copy, and every plain stroke costs less. A shape kept in a property and drawn every frame remembers its fill triangles and its stroke from its second draw on and copies them in each frame's colors, so 195 traced outlines filled and stroked every frame went from 124 ms to 6 ms of drawing on an M2 in a release build. An edit to its points or winding, or a stroke at a new width, join, cap, or scale, works it out again; a dashed, brushed, profiled, fading, or gradient stroke is worked out every time. Apart from that, every solid stroke of one width is laid into the frame in one pass rather than one check a vertex, which takes the same outlines drawn as new shapes every frame from 107 ms to 28 ms. Nothing drawn changes: the frames are identical to the byte.
  • Every blown tube speaks from pressure 0.3. A tube's breath was the pressure times a constant, and where a note starts moves with how much its tube loses, so the clarinet spoke only from about 0.6 at A3, the hollow tube from 0.7, and every tube later on a low note than a high one, which left most of the dial silent. Each note's start is now worked out from its own loss and placed at 0.3, with any reed at any pitch, and full pressure blows what it always did. A sweep written for the old dial swells sooner now. Physical models
  • A Limiter listens ahead, and its sound leaves 5 ms later. It used to take the gain a sample needed on that sample, which flattened every crest over the ceiling (1.2% distortion on a loud sine). It now listens Limiter.lookahead ahead, so the level is already coming down when a peak arrives, the wave keeps its shape, and nothing crosses the ceiling still. The cost is the lookahead itself: what leaves the limiter is 5 ms behind what went in. Synthesis
  • Delay holds its settings to their ranges. time is held to 0...2 seconds (Delay.maxTime, the longest the echo can wait), feedback to 0...0.95, and mix to 0...1, at init, as the other effects' settings are. A time of 3 s used to read back as 3 and echo at 2; it reads back as 2 now. Synthesis
  • The procedural sky has its sun. A .sky environment lights the scene with its own sun now: a directional light from where the sky draws its disc, as bright and as colored as the real sun's beam at that haze and height (the sky model's own solar radiance function, through the eye's color response into the sky's unit), so a white ball facing it is about eight times as bright as its far side on a clear day, a hazy day's shadows are softer, a low sun reddens, and castShadows() throws its shadows. While it lights the scene, the backdrop draws the disc at the sun's true size and the auto-exposure counts the beam beside the sky, so a sunlit white floor sits about where the sky alone put it and the sky reads darker beside it; under clouds the beam keeps the share of the sky the clouds leave clear. Environment.sunlight(_:) decides when: .automatic (the default) unless the sketch places a directional light of its own, which then stands in as the sun, so a sky scene with its own key light or lighting preset keeps its look; .always beside every light; .off for the sky's glow alone. A sketch with a .sky and no directional light changes picture. The ocean's glitter, a lens flare, and caustics follow the sky's sun like any directional light. 3D
  • The air is the scene's light. aerialPerspective() under a .sky takes the sky's horizon as its far light, at the backdrop's own exposure, so far ridges fade into the sky behind them and the veil scales with the sky's intensity; without a sky the first directional light's color and intensity scale it, so twice the light gives twice the glow. The haze's halo still leans the air brighter toward the sun. Atmosphere
  • One drag, read the same way in both worlds at every frame rate. A 2D particle's drag was taken once a step, so the default 0.01 removed 45% of its speed in a second at 60 fps and 70% at 120, and a rigid Body read the same number as a loss per second, sixty times weaker. drag is now the share of speed lost every sixtieth of a second, scaled by the step, and a rigid body reads it the same way on its speed and its spin, so a particle and a body given the same number slow alike and a frame rate changes nothing. Every particle sketch feels as it did at 60 fps; a rigid 2D body at the default slows 45% a second where it slowed 1%, so a rigid sketch that wants its old coasting sets world.drag = 0 (the Physics/Burst, Chain, Joints, and RigidBodies examples do). A .kinematic body is never dragged. World3D gains a drag of its own, the same reading, defaulting to the solver's 5% a second every 3D body already carried, so world.drag = 0 is what lets a thrown ball land on the exact parabola. Physics
  • A body's own restitution wins, and the world's reaches it when changed. The walls of a 2D bounds and the 3D ground carried the world's restitution, and both engines combine a pair by the larger, so a ball given restitution: 0 still bounced off a default floor. They now carry none of their own, so a body bounces off them with its own value. A body that named none follows world.restitution when it changes, in both worlds, and drag reaches every body the same way; Body.restitution is readable and settable in 2D as it was in 3D. A change to gravity wakes every settled body, so a tilt reaches a box asleep on the floor. The engines' quarter-turn-a-step cap on a spin is lifted: an angularVelocity of 100 rad/s reads back as 100. Physics in 3D
  • substeps in both worlds. A step can be cut into pieces, each integrated and relaxed on its own, for the scenes where a joint's loss shows: a bob on a rod in the 2D world gave back 70% of its height on its first swing at one piece a frame and gives back 98% at sixteen, and a bar hinged in the 3D world returns to 92% at one piece and 98% at four. The default of 1 changes nothing. A physics snapshot now carries the world's drag and substeps and which bodies named a bounce of their own (format 7; the previous three formats still read). Physics in 3D
  • CubicBezier is a value of its own. HobbySpline.Segment is now a typealias of the top-level CubicBezier (the same start, two handles, end, point(at:), and direction(at:)), which Path.cubics and Contour.fitted(tolerance:) return too; every existing spelling compiles unchanged. Geometry
  • setup() may throw. open func setup() throws: a loader that cannot read its file lets its FileError out with a bare try, where it used to need try!. A window reports the sentence on stderr and, under OllinLive and the live-coding stage, in the error strip the way a compile error is reported, and holds the sketch rather than drawing it half built; a headless export fails with the sketch's own error. An override may leave throws off, so every existing sketch compiles unchanged. The in-memory recorders that run setup() for a frame now throw with it: OllinApp.svg(of:), pdf(of:), gcode(of:), dxf(of:), drafting(of:), stitching(of:), embroidery(of:), and spatialScene(of:) take a try (a breaking change for a caller of those; the file-writing exports already threw). Sketch
  • Timeline edits undo. ⌘Z while the timeline panel is in front puts the last edit back, from a key placed in the inspector to a track removed from a lane, and ⇧⌘Z brings it back; a drag is one step. Clicking a key moves the playhead onto it, a scrub near a key snaps to it, and the footer names a selected key's value beside its moment and curve.
  • The keyframe diamonds make one column, and come with the panel. Every inspector row's diamond sits at the trailing edge, after its control, where it used to sit before controls of differing widths and zigzag down the list. The diamonds show while the Timeline panel is open; with it closed, only a parameter a track or a rule already drives keeps its mark, so a sketch that never keyframes has a clean inspector.
  • A @Param tunes the 2D view live. viewControl(center:zoom:) applied its arguments on the first call only, so a parameter passed as the zoom changed nothing in the window until resetView(). After the first call they are now the view's home, as the camera rig's framing is: a constant framing changes nothing, a changed one glides the view there while the viewer has not dragged or scrolled it, and once they have, a changed value moves only the home, which resetView() opens on. Drawing
  • A sprayed line's weight counts under .perLine too. A SprayLine or SprayQuad under .perLine(n) draws its weight's share of n: 0.5 draws half the points, rounded and never fewer than one, and n is the most any line draws. Its light is unchanged, so a faint line can ask for fewer points and only fills in with coarser grain, and a weight that changes every frame rebuilds nothing. The weight used to be ignored under .perLine; a sketch that set one there now draws that share of the points, and every sketch at the default weight of 1 draws what it drew. Depth of field

Fixed

  • A still 2D line stays crisp under temporal anti-aliasing. The live window's resolve ran its 3 by 3 reconstruction over every pixel, so a one-pixel 2D line drawn into a 3D canvas read 85, 219, 111 across three columns with the camera still, where the plain frame reads 0, 255, 0. The reconstruction now runs only where the frame has depth, and a pixel of pure backdrop keeps its own sample, so 2D over a background color passes through exactly as drawn. 3D
  • The sky turns with the camera. Every pixel at the far depth, an environment's backdrop included, wrote zero motion, so a turning camera smeared its sky under temporalAntialiasing(), left it unstreaked under motionBlur(), and warped it under temporalUpscaling() and frameInterpolation(). The backdrop is a direction, not a point: under an environment it now moves with the camera's turn and never with its travel, so a pan streaks the sky by exactly what it streaks a floor at the horizon. Over a plain background(_:) the far plane still holds still, so 2D drawn over it never softens or streaks. The resolve's history resample is also held to the pixels under it, so a bright disc the camera turns past (the sky's sun, a glowing lamp) no longer rings into a halo and a pit. 3D
  • A mover's edge streaks with it. The withMotion pass drew one sample a pixel while the picture is multisampled, so a pixel a mover half covered took the background's motion and its share of the mover stayed sharp under motionBlur(), about 1% of a 24-pixel box's light on its leading side. The pass is multisampled at the frame's own count and resolved by the nearest sample, so a pixel the mover covers at all carries its motion, and the streak matches the subframe reference with the box's edges on whole pixels or halfway across them. 3D
  • A sprayed line in focus keeps its light. A frame's pass of light into an Accumulator was half float, so an in-focus line, which piles every point onto a few pixels, lost most of them once those pixels were bright: a dot in focus kept half its light at 1,000 points a pass and a twentieth at 16,000. A pass of light particles alone (every LineSpray, and drawParticles(_:style: .light) alone in a withAccumulator block) is now single precision and single-sample, and a point under a pixel shares its light among the four pixels around it, so a dot keeps all of it at any count, in focus or not, wherever it falls. That pass takes a quarter of the memory it took and drew four million points about 10% faster. A dense spray's in-focus lines come out brighter, at the light they were given. Anything else drawn into an accumulator is drawn as before. Depth of field
  • A line with no light costs nothing in a spray. A SprayLine dark at both ends, or a SprayQuad with no light, gets no points at all, so a scene that pads its list with dark pieces costs what its lit pieces cost, and under .byLength the lit ones share the whole pass. A spray whose pieces all go dark now develops dark; it used to keep showing its last picture. Depth of field
  • A gain map keeps a highlight's edge. A HEIC rebuilt a highlight's edge, and the three pixels around it, up to 38% off, because the file keeps a map's color at half resolution and a per-channel gain is that color. The map is now written at four times the frame's size, so the edge rebuilds within about 2% of the rendered frame. It drops to twice the size for a frame past about 1.5 million pixels, where a highlight's one-pixel rim can stay 4% off. Color output
  • A black ground separates to black alone. Once the inks already chosen print solid black, nothing laid over them shows, and the separation broke that tie toward solid ink, so a black background in separated(into: [.black, .fluorescentPink]) laid solid pink under the black, in either ink order. A tie now goes to less ink. Print separations
  • An absolute proof shows the paper, and the paper is the stock's. An absolute SoftProof of white came out cyan (195, 210, 226), and the simulated paper came out far yellower than the press profile's own stock, (238, 220, 185) against (224, 223, 216). The sRGB profile records the screen's daylight white, and the color engine's absolute intent carried that white across as a color. The proof now reads the canvas profile's white as the white the two profiles meet at, which is what the format's current version writes for every display profile, so white proofs as the stock and the stock as its profile says. An .absolute proof now shows the paper whether or not simulatesPaper is set, as its page always said. Relative and perceptual proofs are unchanged. Print color
  • A video started before an export plays in it. An export makes the sketch before it takes the clock, so a VideoPlayer told to play() in a property's initializer held its first frame for the whole export, and a tracker reading it was handed nothing. The play now carries over to the export's clock. Examples/Vision/OpticalFlow was caught by it: its published picture showed the film's first frame with no motion field at all, and it is rendered again.
  • ObjectTracking follows something with no camera. With no camera it used to track a still photograph, where nothing moves. It now plays the bundled dance film and takes its first lock from a FaceTracker box, asking the detector again only once the tracker's confidence says the patch is lost. The Vision reference also listed a stop() the tracker does not have; the call is stopTracking().
  • A white surface sends back the light that reaches it, from every side. The matte color under a physically based gloss kept one minus the Fresnel term, which does not match what the gloss beside it reflects, so a white plate under an even environment kept 90% of it at a low angle at roughness 0.5 and 103% at roughness 0.2, and under lamps it sent back up to 135% of what fell on it at roughness 0.2. The color now keeps one minus what the gloss reflects toward the eye, under the environment, every lamp and panel, a caustic, the bounce, and the path-traced export, so a white plate returns the light within half a percent at every angle. Floors seen low and glancing read a little darker and glossier; head on, almost nothing moves. A panel's diffuse now takes the same weight, where it took none. 3D
  • A panel's traced shadow is the share of its light the blocker stops. Its visibility rays counted every point of the panel alike, so the floor under a blocker's edge kept 0.213 of its light where the share that arrives is 0.091. Each ray now counts by the light its point sends the surface, so the near part of a panel weighs more than its far part. 3D
  • A pane of index 1 changes nothing in the path-traced export. A ball seen through clear glass of the air's own index read 8 to 12% brighter than without it. Two things counted light twice: a continuation that crossed a pane gave up its pairing with the environment's draw, and the light strategies of a surface seen past one priced a widened lobe the continuation did not. A thin pane's crossing now carries the pairing through, the widened lobe is kept for the scene's lights alone, and a shadow ray through a pane loses what the pane reflects, as a path through it does. Path-traced export
  • A picture shows in a traced mirror and through traced glass. A mesh drawn with a picture (Mesh.textured, from a loaded image or a layer's image) reached a ray-traced reflection or the traced view through glass as its plain fill, so a picture on a table under a glass drop read as a white slab where the raster view showed the picture. A traced hit now reads the surface's picture, at full size; its other maps (normal, roughness, occlusion, glow) still show only on the surface itself. 3D
  • A plucked string rings for its decay at every pitch. The loop's loss filter takes a share of the note itself, and that share was not counted, so a high note rang a fraction of the time it was given: the steel string's 3.6 s was 0.64 s at MIDI 93, nylon's 1.8 s was 0.42 s. The loop's gain now allows for it, and near the top, where the filter alone would take more than the decay allows, the filter is eased (Jaffe and Smith's decay stretching). Physical models
  • A lifted bow lets the string ring. At pressure 0 the bow sat on the string and muted it within a tenth of a second, whatever decay said. The bow now leaves the string over the bottom tenth of pressure, where nothing sounds yet, so a string the bow leaves rings for its decay, and the loop's loss is taken once a period, which had made that ring half as long. Physical models
  • A blown tube holds no offset. What a tube sent out sat below zero by up to 0.17 at every pressure under full, which ate headroom in a mix. The standing part is taken out of what it radiates. Physical models
  • The four built-in rooms are four rooms. Reverb(.room), .hall, .plate, and .cathedral all sounded as the same medium hall, live and in an export alike: on macOS 27 the call that loaded a room named it and left every setting at the default. Each now loads its own settings, so a hall and a cathedral have the long tails they are named for. Synthesis
  • A distortion at mix 0 passes the sound unchanged. The unit still colored what it passed at mix 0 (a bell by 7% of its height); it now steps aside, as a flat equalizer does. Synthesis
  • An export no longer writes a frame the GPU threw away. When another program's work faults the GPU, the GPU can discard a frame that was only waiting its turn; the export printed one line and wrote the frame anyway, half drawn (a long render kept a frame of green blocks that way). A frame whose picture builds on nothing from the frame before is now drawn again, up to twice, and the file holds the frame it would have held. One that does build on the frame before (a noClear() pile, a persistent layer, a simulation, a moment of a --subframes average) stops the export with an unrendered error carrying the GPU's own reason, since the discarded work may have left part of it behind. Export
  • withMotion keeps its drawing state inside the block. It saves and restores the transform and style the way withState does, so withMotion { translate(x, y, 0); drawMesh(box) } in a loop places each box from the origin. The block left them in place, so each translate added to the last one and a 10 by 10 grid drew 2 of its 100 boxes, the rest marched off screen with nothing said; a block that already holds a withState inside draws as before. 3D
  • A dense pile of 3D bodies no longer stops the sketch. A World3D set aside one touching pair and one contact constraint a body, and the physics library's debug checks were compiled into every build, so a pile denser than that (a box packed among others touches up to 26 neighbors) tripped an assert and ended the process. The world now holds sixteen touching pairs a body, the checks are off, and a step that still drops contacts says so in World3D.lastError instead, cleared by the next step that keeps them all. maxBodies reads back what the world was made with. Physics in 3D
  • A floor gathers the whole sky. The environment's diffuse light read the middle of its up face, the direction every floor faces, through the environment's coarsest level (the whole face averaged with the ground), so floors under a sky or a loaded map with any structure came out dim: 3 to 8% under a clear procedural sky and 14% under a hazy one. Each sample of the bake now reads the level its own footprint asks for, and a floor gathers the sky's own level irradiance within 2% on a clear day. 3D
  • Two volumes over one box composite in call order. Volumes draw farthest first by their boxes' centers, and nothing defined the order when two centers were the same distance from the eye, so an absorbing medium drawn over the same box as a glow could land under it and dim nothing. The tie now keeps call order, the later drawVolume on top: a dust layer drawn after a glow dims the glow, and drawn before it dims only what lies behind both. A medium that only glows adds its light in any order, so only an absorbing one needs placing, and the reference says so. Volumes
  • The timeline's authoring loop works as written. A parameter changed while the clock is held now shows at once (the picture follows the inspector, and the track takes the value back on the next scrub unless it is keyed); the diamond on a key re-keys a changed value, where it removed the key and lost the value; an edit made with the panel closed lands where the clock stands rather than where the playhead was when the panel closed; the ruler keeps the playhead in view when the clock runs past the last key and holds still while a key is dragged; the loop region and its button show the moment an Option-drag sets them under a held clock; and the panel takes its first click as a drag rather than as the click that brings it to the front.
  • A glowing surface lights its neighbors. It the probe trace reads a hit's emission (Mesh.glowing, an emissive color) and adds it to the bounce, so a glowing slab lights the floor under it as the path-traced export does, where before the field read 0 there. 3D
  • A metal bounces its own color. Under globalIllumination() a metal bounces through its own lobe, not as the plaster its base color would make: a copper wall throws copper onto what faces it, and a chrome one throws the lamp back along its reflection. The probe trace shades a physically based hit through the same microfacet lobes the renderer and the path tracer use (its body over pi, its lobe beside it), so a physically based wall bounces the light it is lit with and no more, where it bounced about three times its share before; the standard finish is unchanged. 3D
  • An export's bounce reaches its depth in a light room. It does so because the headless probe field runs a burn-in before it averages, so the deep bounces that are most of a light room's light are no longer weighed down by the shallow first updates. A white room at albedo 0.8 read 0.77 of a 64-deep path trace; its bounce against a darker room's now matches the trace's own ratio within 3%. Exports and snapshots with the bounce on render brighter in light rooms. 3D