Changes since version 1.3.0¶
New Features¶
- SDF viewer implementation plan (container entry) — initial branch
changelog for the signed-distance-function viewer; planning structure lives in
dev/todos/viz-sdf-viewer/(README overview plus phase plans, including the Phase 6a first-vertical-slice milestone for a Line + Sphere example with manual user confirmation). This entry records implementation changes as they land on the branch. - SDF primitive + combinator GLSL library (Phase 1) — added the base
inigo-quilez signed-distance primitives (
sdSphere,sdEllipsoid,sdBox,sdRoundBox,sdPlane,sdSegment,sdCapsule,sdCylinder,sdCappedCylinder,sdCone,sdCappedCone,sdTorus), Boolean/smooth combinators (opUnion,opSubtract,opIntersect,opSmoothUnion,opSmoothSubtract,opSmoothIntersect), and a shared constant/rotation header underpy/pytanga/viz/templates/sdf/shaders/. - WebGL2 raymarcher core (Phase 2) — added the SDF viewer entry module
(
sdf_viewer.js) with a WebGL2 gate (in-page error banner, no WebGL1 fallback), a fullscreen-quadShaderMaterialray-marcher, gradient normals, IQ-style shading with soft shadows/fog, and araymarch.glslfragment body. The raymarcher reuses the standard viewer'sview_mode.jscamera andcontrols.jsOrbitControls so the default/custom camera matches the non-SDF viewer 1:1; camera position/matrices/near/far are plumbed in as uniforms. Structural shader-assembly smoke tests live inpy/tests/viz/sdf/test_raymarch_shader.py. (The hardcoded-sphere visual smoke check and real GLSL compile are deferred to the Phase 6/6a browser slice — no localglslangValidator/glslc/Node-threeis available.) - Analytic entity SDF serializer (Phase 4) — added the Python SDF
primitive/combinator descriptor model (
sdf/primitives.py) and the entity → SDF-tree serializer (sdf/serializer.py) for the six supported entities (Point/Line/Plane/Sphere/Circle/PointPair), plus the frontendscene-builder.jsandobjects/*GLSL emitters that dispatch a serialized tree to a single distance expression. Infinite lines/planes carry an explicit finitebound; unsupported kinds raiseTypeError. Unit tests inpy/tests/viz/sdf/test_primitives.pyandtest_sdf_serializer.py. - Composed global SDF + material table (Phase 5) — added the per-object
composer (
templates/sdf/composer.js) that folds all object distances into onevec2 map(vec3 p)returning(distance, materialId), and the material table (templates/sdf/material-table.js) packing per-object color/opacity into a uniform array with amaterialColor(matId)GLSL sampler. The raymarch body now resolves the hit material for lighting (injectedmap+materialColorcontract). Signed combine modes / smooth-blend / AABB pruning are deferred to Phase 11 (CSG booleans). A headless Node smoke test (dev/src/sdf_composer_smoke.mjs) exercises the composed map. SdfVisualizerfacade + HTML bootstrap (Phase 6) — added theSdfVisualizerfacade (sdf/visualizer.py) mirroring the standard viewer'sadd/show/waitAPI, thesdf_viewer.htmlentry page, and a WebSocket + camera-parity wiring insdf_viewer.js. The sharedVizServergains an opt-inentry_pageparameter (default unchanged) so the SDF viewer servessdf_viewer.htmlwhile the standard viewer still servesviewer.html. Camera parity is verified against the standard viewer inpy/tests/viz/sdf/test_visualizer.py.- First vertical slice example (Phase 6a) — added
entities.py(py/examples/viz/sdf/entities.py) drawing a Line + Sphere through the analytic path, as the gate for manual user confirmation in the browser before any CSG work continues. - Primitive object library +
Composedobjects (Phase 06b) — exposed the SDF primitive library as first-class, directly addable objects (sphere,box,cylinder,capped_cylinder,cone,capped_cone,torus,ellipsoid,round_box,capsule,segment,plane) via named constructors inpytanga.viz.sdf, and addedComposedto bundle constituents into a single object with one material and a per-constituent combine mode (union/intersection/subtract).SdfNodegains acombinefield and agroup()helper;SdfVisualizer.addand the serializer acceptSdfNode/Composeddirectly; the frontendgroupfold and the previously-unwired primitive emitters were added. Tests inpy/tests/viz/sdf/test_composed.pyandtest_primitives.py. - Entity/operator draw-style SDF mapping (Phase 06c) — mapped every entity
and operator kind to a basic or composed SDF tree with
style=support (Point→sphere, crosshair point→3-axis crosshair,Line→segment,Sphere→sphere,Rotor→sector disc + rim ring + axis arrow,Translator/Direction→arrow,Dilator→rings,Motor→disc+arrow,GeneralRotor→sector disc+ring+axis at origin, etc.). Added thecomposed.pyexample (sphere with a bored-out cylinder plus torus/box/rotor) and thearrowhead.pydiagnostic. - Configurable SDF lighting —
SdfVisualizernow acceptsDirectionalLightobjects throughadd(), has anadd_default_lightflag (mirroring the standard viewer'sadd_default_axes/add_default_grid), and exposesset_ambient_light()(plus anambientproperty) to tune the ambient term. The raymarcher shades with a uniform-driven set of up to 8 directional lights plus a vec3 ambient; the previous hardcoded single light is now the built-in default. - SDF object/light updates + animation loop —
SdfVisualizergainedupdate_entity()/update_light()(replace an object or light by id),flush(), andsleep_ms()so objects and lights can be modified afteradd()and animated.DirectionalLight.directionnow normalizes on assignment. Added thelight_animation.pyexample (a light orbiting a sphere). - SDF viewer identity + version check parity — the SDF frontend now sends a
viewer_name(from the?viewer=URL param) in itsreadymessage and compares the server-injected frontend build hash against thebrowser_idmessage'sfrontend_version, showing the standard "Reload now" version- mismatch banner on a stale cached copy (matching the standard viewer). -
Shader-drawn grid overlays (
SdfOverlay/Grid) —SdfVisualizergainedadd_default_grid(a default ground grid) and acceptsGridobjects throughadd(): an arbitrary-plane, infinite grid drawn as a depth-compositedfract()-based overlay rather than a raymarched volume (the grid draws over objects behind its plane and is hidden behind objects in front). A newSdfOverlaybase class and a frontendoverlays/factory.jsdispatch (mirroring the standard viewer'srenderers/factory.js) are the seam for future overlay kinds (axes, crosshair, …). -
Shader-drawn coordinate axes overlays (
SdfOverlay/Axes) —SdfVisualizergainedadd_default_axes(three default X/Y/Z axis lines from the origin) and acceptsAxesobjects throughadd(): three infinite red/green/blue lines drawn as depth-composited,fwidth()-AA'd overlays that extend only along their positive directions (like the standard viewer'sAxesHelper), hidden behind objects in front and drawn over objects behind. -
CSG boolean combine modes + smooth variants (Phase 11) — the per-object
combine/polarityfold (union/intersection/subtract), and smooth variants (smooth_union/smooth_intersection/smooth_subtractwith a per-objectsmoothness) fold via the Phase 1vec2smooth combinators, blending the material id by the blend factor. Unit tests inpy/tests/viz/sdf/test_combine.py. -
SDF viewer examples + docs — added
booleans.py(per-objectcombine=/polarity=), a headless smoke test (dev/src/test_viz_sdf.py), and adocs/py/viz/sdf-viewer.mdguide (linked from the viz index and mkdocs nav). -
Per-object SDF objects in the standard viewer (viz-sdf-objects) — added an
SdfStylemarker style that opts an entity into ray-marched SDF rendering inside the standard viewer (emitted askind:"sdf"on the wire), mixed with the normal vertex/mesh pipeline. The Python serializer emits the tree in object-local space with a conservative AABBboundand a placementtransform(sdf/bounds.py+serialize_entity_local), so SDF objects animate/parent/interact exactly like meshes; per-object CSG works viaComposed.Visualizer.add(..., style=SdfStyle(...))acceptsComposedand bareSdfNodedrawables alongside entities. - SDF proxy renderer (frontend) — added
createSdfProxy()(a BoxGeometry sized to the object'sboundplus aShaderMaterialwhose fragment shader marches the single-object SDF in local space and writesgl_FragDepth, so the standard depth buffer occludes it against meshes and other SDF proxies). The directional-light model is factored intorenderers/sdf/lighting.js(one source of truth for both viewers), and the HTML export bundles the SDF renderer with its GLSL inlined. On WebGL1 SDF objects are skipped with a single yellow warning banner. Cross-object CSG and mutual shadows are deferred. Tests inpy/tests/viz/sdf/(test_bounds.py,test_standard_serializer_sdf.py,test_proxy_shader.py) +dev/src/sdf_proxy_smoke.mjs; examplepy/examples/viz/sdf/objects.py. - SDF object groups (
SdfGroup) — added a grouped SDF drawable whose members are folded into one ray-marched solid (cross-object CSG viaunion/intersection/subtract), each with an independent runtime transform uploaded as shader uniforms so members can be animated separately without recompiling. The proxy bounding box is the union of the members' AABBs and resizes dynamically as they move;update_sdf_group_member()updates a member and pushes a content patch. Tests inpy/tests/viz/sdf/test_sdf_group.py; examplepy/examples/viz/sdf/group.py. Point/Direction/Rotoraccepted by SDF constructors — every SDF object creation function (sphere,box,capped_cylinder,cone,torus,ellipsoid,plane,bound_box, …) now accepts aPoint/Directionforpositionand aRotor(or an(axis, angle)pair) forrotation, coercing them to the wire transform. Tests inpy/tests/viz/sdf/test_primitives.py.- SDF node ids +
viz.new()refs — every SDF constructor andComposedaccepts an optionalid, and anSdfGroupmember can be addressed by that id or by index inupdate_sdf_group_member/set_member_transform.viz.new(…)returns aVizObjectRefwhoseentityis the group and whoseset_member_transform(…)marks the node dirty. Tests inpy/tests/viz/sdf/test_sdf_group.py+test_primitives.py; examplepy/examples/viz/sdf/group.pyupdated. -
Shared transform/SDF argument types (
Vec3/Rotation/Triple) — addedpy/pytanga/viz/_types.pywith shared aliases (Vec3,Triple,Rotation,TransformRotation) plus_as_vec3/_as_eulercoercion (deduplicating the per-module_as_vec3helpers).set_transform/set_member_transform/update_sdf_group_member/Transformnow acceptPoint/Rotorfor position/rotation (aRotoris converted to Euler internally) and a 3-tuple for scale, instead ofAny. Tests inpy/tests/viz/test_object_ref.py+py/tests/viz/sdf/test_sdf_group.py. -
Per-entity SDF styles — added
SdfSphereStyle/SdfLineStyle/SdfCircleStyle/SdfPointStyle/SdfCylinderStyle/SdfPlaneStyle(each carrying its entity-specific SDF knob such asthickness/tube_radius/size) plus aSDF_STYLE_BY_KINDregistry. Tests inpy/tests/viz/sdf/test_sdf_styles.py. SdfObject+EComposeoperator model — added a unified, composable SDF object layer: anSdfElementbase with Python CSG operators (+/|= union,-= subtract,&= intersection,^= xor, unary-x/~xpolarity), theEComposeStrEnum, a binaryCombinenode, andSdfObjectwrapping a geometry entity (incl.Cylinder) with an id + per-entity style, converted at construction via_entity_to_sdf.viz.add/viz.newaccept these elements directly (noSdfStylemarker required). Tests inpy/tests/viz/sdf/test_ecompose_operators.py+test_sdf_object.py.- Per-object materials —
Composed/SdfGroupnow emit a per-membermaterialsarray (one color/opacity per member), and the proxy shader switches tovec2 map()returning(distance, materialIndex)backed by auMaterialuniform table, so each member shades with its own color/opacity; addedopXorto the combinators. Tests inpy/tests/viz/sdf/test_sdf_object_serialization.py+dev/src/sdf_proxy_smoke.mjs; examplepy/examples/viz/sdf/object_model.py.
Bug Fixes¶
- Fixed inverted rotations in the SDF viewer —
transform.jspassed-angleto IQ'srotationAxisAngle, which already negates the angle internally, so every rotation was applied with the wrong sign (cone apex pointed −Z, rotor sector handedness mirrored). Passing+anglenow yields the intended inverse/local-space rotation. - Fixed patchy, direction-inconsistent SDF lighting — the gradient normal
used a finite-difference step of
0.5773(the tetrahedral vertex coefficient1/√3) rather than a small epsilon, making normals patchy, lighting appear to come from different directions per object, and producing unmotivated soft shadows. The step is now0.001; the remaining subtractive-CSG soft-shadow penumbra is documented as a known limitation. - SDF viewer browser connect/reconnect parity — the SDF viewer's frontend
and backend now mirror the standard viewer's behaviour: the frontend
auto-reconnects (2s interval, 60s window, connect watchdog, single-flight
guard, reconnect button, visibility wake-up), and
SdfVisualizergainedreuse_existing,open_browser(wait_for_browser=…),wait_for_browser(), and the interactive connect prompt, so a script re-run reuses the already-open tab instead of opening a new one.