Skip to content

Ollin 0.14.0

Latest

Choose a tag to compare

@eaviles eaviles released this 07 Oct 02:21
· 14 commits to main since this release
0.14.0
ca63964

Added

  • Kazuya Sakai joins the recreations. Examples/Recreations/KazuyaSakai is new, after the painter who was art director of Octavio Paz's Plural and among the first to show geometric abstraction in Mexico. Ondulaciones is the series of 1975 and 1976: a ribbon of hard-edge parallel bands comes into the panel straight, wraps disc after disc in concentric arcs, turning one way then the other, and runs off another side, each band the one centerline moved across by its own width so neighbors share their edge point for point, in three looks read from Filles de Kilimanjaro III (Miles Davis), the 1975 screenprint and the purple canvases, the route dealt by the seed and laid down and taken back over the cycle. RightOff is the painting of 1973 where the circle came into his fields: a score of horizontal bands under navy suns ruled with white rings, crescent hills, and two red lobes with a valley between them, the rings traveling outward on the clock. Both export as outlines, every band and hill a closed path and every disc and ring a circle.

  • The lit and sounding tiers are pinned by physical invariants. Thirty-two tests state what holds whatever the implementation (a lossless surface returns the furnace's field, a shadow only takes, fog follows Beer-Lambert, a tone curve is monotone, a plucked string only loses energy, a sound placed to the left is the mirror of one placed to the right) and read it off a render or rendered samples against a twin or an analytic value; each one that holds was verified red under a planted defect. Four measured gaps stand as known-issue pins and name the next deep reviews: the caustic deposits 1.5 times the light its shadow took, the motion blur gains a quarter of a mover's light at a full shutter, the color-vision simulation's dichromat ends are not projections (the tritan one farthest off), and a proof through the generic CMYK profile moves a saturated color by up to 11 levels when proofed again.

  • The regions a drawing encloses. regions(enclosedBy:) reads a set of outlines as drawn, cuts every line where it meets another or itself, and hands back every patch the lines wall off as its own Shape: two overlapping circles give three, a grid of lines its cells, a page of scribbles every patch a pen could color without crossing a line, and the bare page ringed by several circles is a patch like any other. A region inside another is a region of its own and a hole in the one around it, a stroke whose end hangs loose walls nothing off, and the regions come back in reading order. Circle.contour(segments:) and Rectangle.contour turn the two values into outlines for it, or for a Shape or a boolean. New example: Examples/Shapes/ColoringBook. Geometry

  • Points in the order of a Hilbert curve. hilbertOrder(of:) and hilbertSorted(_:) sort points by their place along a space-filling curve laid over their bounds, so one line through a stipple comes from a single sort and stays local, and a large set is put in order in one pass; hilbertIndex(of:in:level:) is the place of one point on a curve over any rectangle, and level sets how fine the curve is. New example: Examples/Images/HilbertLine. Hilbert order

  • A traced frame on a time budget. --path-traced --pt-seconds 10 gives each frame ten seconds in place of a count: the tracer sizes its last dispatch to the time left, so a frame ends within about a sample's worth of its budget, and the count follows the scene under the cap (a named count is still the most a pixel traces; the bare flag's cap under a budget is the detail tier's 4096). The frame the clock leaves is a fixed render of the count it reached, byte for byte, and the recipe records the budget and that count ("seconds":10,"reachedSamples":50), so a frame reproduces with the count in place of the budget, under --pt-noise too. --pt-min is a floor the clock may not cut under, the first frame of a --pt-reuse run gets the history's worth of time, and a sequence or video export ends with one line that says the counts the frames reached and the mean time a frame took. On the example scene at 1080 by 1080, ten seconds traced 50 samples where a fixed 128 took 29.5 s, and the same ten seconds at a path depth of 2 traced 93. In code, PathTracing.secondsPerFrame. Path-traced export

  • A traced sequence carries its earlier frames. --path-traced 32 --pt-reuse 8 carries up to eight frames' worth of samples into each frame of a sequence or video export: every pixel is followed back to where it was in the frame before, by the motion the sketch declared for its movers or the camera's own through the depth, the earlier frame's samples are taken there only where they belong to the same surface, and the carried light is trusted only as far as the frame's own grain allows, so a surface a mover uncovers starts over and a reflection sliding over a polished ball is followed rather than smeared. A sequence at 32 samples a frame then shows the grain of 256, and that grain holds still from frame to frame: on a field of two thousand polished beads sweeping under a lens, 32 with 8 carried ran in 42 percent of the time of 128 alone and its frames sat 20.6 dB apart where 128's sat 19.9, each frame's own fidelity between the two counts. The first frame of a run traces the history's worth itself, so the sequence starts settled. A frame is then a function of the frames before it: the same command renders the same sequence byte for byte, a still rendered alone has no history, and the recipe records the frames reused and the mean count a pixel held ("reuse":8,"carriedSamples":231.5). In code, PathTracing.reusedFrames. Path-traced export

  • A bound on what a second bounce may carry. --path-traced 256 --pt-clamp 4 holds what light that has bounced twice or more may add to a sample at four times white, so a polished scene's fireflies, a lamp caught off one polished surface onto another every few hundred samples that no count smooths, are gone at a few hundred samples and the per-pixel stop can settle it. What one bounce shows, a lamp in a mirror, a window on a glossy floor, is never touched, so a scene with nothing to bounce light between renders the same bytes. It is a bias and the frame says what it cost: the recipe records the bound and the share of the frame's light it dropped ("clamp":4,"clampDropped":0.0083), and a sequence or video export ends with the mean over the run. A share of several percent beside grain that did not move means the grain was one-bounce light, which the bound leaves alone, and the scene wants its count instead. In code, PathTracing.maxBounceLight. Path-traced export

  • A traced pixel stops once it has settled. --path-traced 512 --pt-noise 0.01 lets each pixel end early once the grain it is left with falls under the figure (the standard error of its brightness against the square root of that brightness, about one level in 255 at 0.01), so the sample count becomes the most a pixel may trace and the samples go where the grain is: the black margins and a sharp, evenly lit face end early, a blurred or glossy region runs to the cap. Every pixel takes a minimum first (the square root of the count, --pt-min N to raise it), the open pixels are checked every eight samples, and a pixel still sampling keeps its eight neighbors sampling. On the example scene at 1080 by 1080, a cap of 512 at 0.04 reached a mean of 217 samples a pixel for 29.2 dB against a 2048-sample render, where a fixed 224 read 26.7 and a fixed render given the same time read 28.9; the time follows the samples only loosely, since the pixels that settle first are the cheap ones. A scene that is grain from edge to edge settles only its margins, and says so in the mean. The frame stays a pure function of its index, and a traced frame's recipe now names what it was traced with and, under the stop, the mean count reached ("pathTraced":{"samples":512,"depth":8,"noise":0.01,"minSamples":24,"meanSamples":131.4}); a sequence or video export ends with one line that says the mean over the run. In code, PathTracing.noiseThreshold and minSamplesPerPixel. Path-traced export

  • Small highlights under the lens are drawn as discs of their own. depthOfField() and the .defocus layer filter take a small bright source out of the gather and draw its disc directly, scattered rather than gathered, so a point of light opens into an even disc at every quality where before it came out grainy at .performance and a one-pixel lamp was lost almost whole; a sharp object in front cuts the disc at its own edge. The look of every blurred highlight moves, so a snapshot or a video made with either will differ. 3D

  • A traced export no longer blurs twice. A sketch that calls depthOfField() and exports with --path-traced had the canvas pass run over the traced frame, which had come through the lens already; the pass now stands down under a traced export. Path-traced export

  • The overlay. withOverlay { } draws its block over the finished frame, after the temporal anti-aliasing, the depth of field, the motion blur, the lens flare, and the postProcess filters, so a caption or a readout stays sharp and untinted whatever the scene under it does. The block lands over everything else whatever the call order, a blend mode in it reads the finished picture, a clip inside it clips the overlay alone, and over an accumulating canvas (noClear()) it is drawn each frame without joining the pile. A vector export writes it last, and so does the web page. Effects

  • Depth of field on the canvas. depthOfField() blurs the finished 3D frame by its own depth through the camera's lens: Camera3D.aperture, focusDistance, and apertureBlades, the settings the path-traced export reads. A point spreads by the thin lens's own size, so the blur grows quickly toward the camera and levels off far away, a highlight opens into an even disc the shape of the iris, a subject in focus keeps its edge, and a blurred foreground turns into a veil over what stands behind it. focusRange holds a band sharp, maxBlur caps the blur, and quality sets how smooth it is. It runs after temporal anti-aliasing and before motion blur, so all three work together, and an export carries it byte for byte. noDepthOfField() turns it off. 3D What a blurred foreground hides is read from a second drawing of the scene behind it (a depth peel against the frame's own depth, drawn only when something stands in front of the focus and only over the pixels it covers), so a ball's halo over the horizon carries the floor the ball hides rather than the backdrop beside it; measured against an accumulation-buffer lens, the green cast there fell by three quarters and the whole frame's error by a fifth. A raymarched field joins that second drawing (its march starts behind the first surface), and a plane, a strip, or an open tube seen from its back joins it too, where before only closed shapes' outsides did.

  • Instanced copies that move. drawMesh(_:instances:) inside withMotion follows every copy from where it was last frame to where it is now, so temporal anti-aliasing keeps each copy's edges and motion blur streaks each copy along its own path, in one draw. Copies are matched by their place in the list; a frame whose copy count changed keeps the camera's motion alone for those copies. Copies placed by a compute buffer and a MeshField's keep the camera's motion, with a note. Instancing

  • Lines in 3D. drawLine and drawPolyline take Vector3 points and draw a line through the camera with the 2D stroke's weight, joins, caps, and soft edge, and drawPolyline(_:colors:closed:) gives every point its own color. Every point keeps its depth, so a solid in front hides the line and the line hides what lies behind it, whichever is drawn first. The weight is in canvas points, the same at every distance, unless strokeWeight(0.05, in: .world) asks for the scene's units, which thin the line as it recedes. drawAxes(length:) and drawGrid(size:divisions:) are built from the same lines and export with the picture. An SVG export carries the line as the 2D polyline the camera made of it. New example: Examples/3D/Geometry/FieldLines. Lines in 3D

  • Drawing part of an image. drawImage takes a source rectangle in the image's own pixels, so one cell of a sprite sheet, a tile of a tile set, or a frame of a film strip draws with no cropping first: drawImage(sheet, x, y, width, height, sourceX, sourceY, sourceWidth, sourceHeight), or drawImage(sheet, in: box, source: cell). The draw reads only inside the part, so a cell drawn at its own size or larger looks exactly like the same cell cropped out first, and a magnified sprite never picks up the border of the one beside it. A web page of the sketch does the same. Images

  • A precise pile. noClear(precision: .float32) keeps the canvas in single precision, so a long exposure of very faint marks keeps adding where a half-float canvas levels off: a pile that takes eight marks of 0.00005 a frame from 0.25 reaches 0.37 after three hundred frames, where half float stays at 0.25. Accumulation

  • A feedback layer, an accumulator, or a simulation field as the base of a combine. Feedback, Accumulator, and SimField take combined(with:_:) beside filtered(_:), so trails can be masked or displaced by another layer without a filter that changes nothing first. Effects

  • Saloua Raouda Choucair joins the recreations. Examples/Recreations/SalouaRaoudaChoucair is new, after her modular sculptures. Poem cuts a block into verses by a rule, every corner of every joint on a grid and some verses with windows through them, after the wooden Poem, the stone Poem and Infinite Structure, and Poem of Nine Verses; the verses are lifted apart until each clears the one below, turned and slid, and set back, lit in the round, and --export-usdz writes them as solids that make the block again. Duals cuts a block once into two parts with the shape booleans, along the stepped teeth of the Repetitive Dual, a keyed slab, or a staircase; they slide apart until a line can pass between them, lean away, and lock, lit in the round or flat as a plan that --export-svg writes.

  • Sweeps and strips. Curve3D is a path through space, open or closed, kept as points or threaded smoothly through a few with Curve3D(curveThrough:closed:segments:). It walks by length (point(at:), frame(at:)) and carries a frame at every point that turns with the path and never twists about it, so rotate(frame.rotation) faces a rider the way the path runs. Mesh.sweep(_:along:scale:twist:capped:) (and drawSweep) carries a Shape along it, sized and turned by functions of the fraction along, with hard edges kept at sharp corners, walls facing out whichever way the outlines run, capped ends, and uvs. Mesh.strip(between:and:colors:closed:) (and drawStrip) is the ribbon between two lines, one color a rung, with normals from the strip itself. Pace walks a curve given as a function at one speed (Pace(byLengthOf:period:)), or the strip between two such curves at one rate of area (Pace(byAreaBetween:and:period:)), and keeps counting periods so a loop never jumps. Sweeps and strips

Changed

  • A traced pixel reads a curved polished surface at its footprint. A pixel's rays fan out over a cone of directions on a curved mirror, and the path tracer now carries that cone with each path (the pixel's angle, the lens, and every surface's curvature under it) and reads the environment at the matching level, beside the sampled lobe's own footprint, while the light list is shaded through a lobe widened by the normals under the footprint, the live view's specular anti-aliasing; the tracer's copy of the environment weights its mip chain by latitude so a coarse read keeps the room's mean. A field of small polished beads mirroring a lit room, grain from edge to edge at any count before, renders at 128 samples with a quarter of the per-sample spread; a flat mirror reads as before, and nothing is set. Path-traced export

  • A scene nests four times deeper, and nothing a file nests reaches the stack. A glTF or USD node tree reads to 256 levels, from 64, and every walk of a loaded scene (its bounds, cameras, and lights, the posing and skinning, an animation, the import, the USD writer, and the draw) keeps the nodes still to visit on a list rather than on the call stack, as both readers already built their trees. The limit is a choice checked against the one cost that stays per level, the runtime's own release of a nested value when a scene is dropped, so a scene at the limit reads, poses, writes out, and is dropped on half a background thread's stack with room to spare. A value in a USD file (a dictionary in a dictionary, a tuple in an array) keeps a limit of its own, 64 levels. Scenes

  • .defocus is the canvas lens's gather. The layer filter runs the same five passes depthOfField() runs, fed by its own depth map and its ramp (focus, range, maxBlur unchanged): each sample lands as a disc of light of its own size, and the layers are sorted by distance, so a near highlight keeps its light and opens into an even disc (it kept 27 percent and peaked at six times its mean), a thin line in focus keeps its weight (it kept 4 percent), a near object keeps all its light (half before), and an in-focus silhouette has no rim (10 percent before). A drawn depth map's anti-aliased rim joins its nearer side instead of being dilated away, which is what used to consume thin lines. The blur also grows with distance all the way to the map's ends now, reaching maxBlur at 0 and 1 (a scene's near and far planes); before it reached maxBlur one range past the sharp band, so a narrow band blurred every other depth by the same amount, a masked blur rather than a lens. The blade count, irisAngle, and catsEye carry over. Behind the focus both passes now composite a nearer surface over what it hides by its cover instead of averaging the two by density, so a sharp object over a much blurrier background keeps its light (98 percent measured, 93 before). The look of every defocused layer moves, so a snapshot or a video made with it will differ. 3D

  • The frame recipe names the framework's commit. Beside git, the sketch package's commit, an exported frame's recipe carries ollin, the framework's own, each -dirty when its tree is, so a frame made from a path dependency can be tied to the framework that drew it. Export

  • A filter over a single-precision layer keeps single precision. A filter or a combine over a layer made with precision: .float32 used to write a half-float layer, so a distance field measured past 1,024 pixels came back rounded to whole pixels and its far contours were ragged, and a blur after it rounded again. Precision now follows the layer through the whole chain. A half-float layer's chain is unchanged. Effects, Measured distance fields

  • An export holds a sketch that stopped its loop, the way the window does. After noLoop(), every export used to call draw() for every frame up to the one asked for, so a draw that rolled the sketch's dice exported a picture the window never showed, and a slow still took one draw per frame (the Watercolor example, about four seconds a draw, took half an hour to reach eight seconds). Now the frame the loop stopped on is the picture from then on: a still at any later frame is that frame, drawn once, and a video, a GIF, or a PNG sequence writes it for every frame after the stop. A web page of a sketch stopped in setup() is one frame, held still. A replayed take still draws every frame it holds. Sketch

  • A closed contour's walk wraps. Contour.point(at:), tangent(at:), normal(at:), and piece(from:to:) used to clamp a fraction to 0...1 on every contour, so a clock passed to a closed one stopped at the start after one lap. On a closed contour they now wrap it, the way a closed Curve3D does: point(at: 1.25) is a quarter of the way round again, and piece(from: 1.9, to: 2.1) is piece(from: 0.9, to: 0.1). An open contour still clamps, and fractions inside 0...1 read exactly as before. Geometry

  • A tube's rings ride the new frames. Mesh.tube(along:), and the helix and torus knot built on it, now place their rings on Curve3D's frames. They are worked out by double reflection, which is accurate to the fourth order where the old transport was second order (on a helix at 64 points a turn, its twist was 0.006 radians off after one turn, and now it is 0.000002). A closed tube spreads its leftover turn by length rather than by point. On the built-in helix and torus knot the rings turn by under two degrees about the path, and the first ring starts where it always did. 3D

Fixed

  • A tap no longer overflows its thread after minutes of use. A video's soundtrack tap, a camera's or the screen's frame tap, a listening hub's tap, a custom effect's process, and a parameter's show rule were each kept as the bare state of a lock, which re-wrapped the closure in two thunk frames on every read, in debug and release builds alike; a tap read once per audio block or camera frame overflowed its thread's 512 KB stack within minutes (the video tap at about twenty, the screen tap sooner). Each is kept in a struct now, and the consumer sits one call deep on the three-hundredth block as on the first. Video

  • A blurred ball's edge follows the lens. depthOfField() and .defocus spread each sample of a surface by its own blur, and on a ball in front of the focus the rim is nearer than the middle and so blurred wider, which spread the ball's edge wider than a lens does: a band of too much ball outside the silhouette (a teal halo over a floor, a brighter halo over black) and too little inside. Each sample's weight now carries the Jacobian of the blur's slope across its surface, and a surface's share of its own pixel is read as that density, so to the gather a ball is what it is to a lens, a disc at its center's depth. Measured against the lens on a ball in front of the focus, the cover outside the rim went from a thirtieth over to within a hundredth, and inside from a twentieth under to within a hundredth; a flat surface, a bar, and a disc render as before. The look of a blurred curved surface moves a little, so a snapshot or a video made with either will differ. 3D

  • A number the solver cannot hold no longer stops the sketch. A body's position, velocity, or spin set to a value that is not a number, an infinity, or a magnitude past a billion units, a force or an impulse made of one, a world's gravity, ground, or unit scale, a character's walk, a joint's drive, a vehicle's controls, a soft body's pressure, or a body added at such a place used to stop a debug build inside the solver one step later and poison a release build's contacts. Each is now refused where it crosses into the solver, in 2D and 3D: a property keeps what it had, a force does nothing, a creation argument falls back to its default, and the world prints one note naming the call. The calls that already throw for what they cannot build throw for the number too. 3D physics, 2D physics

  • The barrel keeps the light. A .defocus with a catsEye above zero let a blurred disc carry only the clipped opening's share of its light, so a near disc kept its whole color inside its silhouette and covered the backdrop by that share outside, a step at the silhouette and a halo too faint, growing toward the frame's edges (on the Effects/Defocus example's largest disc, a fifth of its brightness across four pixels). The gather and the sprite draw now divide by the area of the opening as the barrel leaves it, so the clipped opening changes a disc's shape and never its light, as the reference said. A catsEye of 0 is byte for byte what it was; a picture made with one above it moves. 3D

  • The traced lens focuses where focusDistance says. The path tracer focused focusDistance past the near plane rather than from the eye, and placed its opening on the near plane, which widened the blur by s / (s - near); the focus is now measured from the eye and the opening sits at the eye, as the canvas lens and the thin-lens formula have it. Every traced frame with an aperture changes. Path-traced export

  • A mover behind instanced copies no longer streaks through them. The motion pass that temporal anti-aliasing and motion blur read drew only plain meshes in front of a mover, so a withMotion mover behind a field of instanced copies wrote its motion through them. Every instanced draw now hides what stands behind it there. Instancing

  • An EXR's coverage stays at one. Additive marks sum their alpha along with their light, so an opaque canvas lit by two added coats wrote an alpha of 3 into --export-exr, which a compositor reads as a pixel covered three times over. The alpha channel now stops at 1; the light itself stays as it summed. Export

  • A sketch that reads a detector exports the same picture every run. A tracker's reading used to land whenever its analysis finished, so which frame showed it followed how fast the export ran: seven examples exported a different picture each time, and in most of the rest the detector never answered at all (DepthCloud stayed on its loading notice, BodyPose found nobody). During an export, every frame a source offers is now analyzed before the frame is drawn, each tracker's model is loaded before its first frame, and a tracker made during the export reads the newest frame at once. A still feed publishes on the export's clock, and a playing VideoPlayer hands each new frame to its trackers, where before they read nothing in an export. --settle no longer has to wait out a model. One detector is still not exact: Vision's trajectory request fits its arcs a few millionths differently from run to run, so a long TrajectoryTracker export can find a different arc. Vision