{
    "archive_path": "archive/1790805942.390334",
    "base_url": "www.mattkeeter.com/projects/halfspace",
    "basename": "",
    "bookmarked_date": "2026-09-30 22:05",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/www.mattkeeter.com/projects/halfspace",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=www.mattkeeter.com",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": "www.mattkeeter.com/projects/halfspace/index.html"
    },
    "domain": "www.mattkeeter.com",
    "extension": "",
    "hash": "V889FPG248GPVGPB3WWD",
    "history": {
        "archive_org": [],
        "dom": [],
        "favicon": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.google.com/s2/favicons?domain=www.mattkeeter.com"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T22:06:08.878161+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:08.331116+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.mattkeeter.com/projects/halfspace/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T22:06:09.266988+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:08.886801+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [],
        "media": [],
        "mercury": [
            {
                "cmd": [
                    "/app/node_modules/@postlight/parser/cli.js",
                    "https://www.mattkeeter.com/projects/halfspace/"
                ],
                "cmd_version": "1.0.0",
                "end_ts": "2026-09-30T22:06:15.600124+00:00",
                "index_texts": null,
                "output": "mercury",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:12.442485+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/app/node_modules/readability-extractor/readability-extractor",
                    "/tmp/tmpbagddgac",
                    "https://www.mattkeeter.com/projects/halfspace/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-09-30T22:06:12.433962+00:00",
                "index_texts": [
                    "Introduction\nHalfspace is an experimental IDE for\nsolid modeling with distance fields.\n\n\nTry the demo\n\n(The demo is best experienced on a computer; mobile Safari has some WebGPU\nissues, and pan / tilt / zoom interactions are not yet designed for multitouch)\nHalfspace is a showcase app for the Fidget kernel, which is used\nfor rasterization and meshing.  Within the GUI, images are rasterized in\nreal(ish)-time; models can be exported as either images or triangle meshes:\n\nSince it's 2026, let me note at the outset that this is not vibe-coded.\nI've been working on it since\nApril 2025\nand am writing the code using my human brain, for\nvarious reasons\n(and Fidget dates back to\n2022).\nNow, the rest of this writeup assumes some knowledge of implicit surfaces;\nplease see many previous\nwriteups for details for more\nbackground info (or just keep reading, you'll be fine).\nWhy?\nI've spent a bunch of time writing implicit kernels, slightly less time writing\nGUIs on those kernels, and even less time actually modeling with those tools.\nIn practice, I don't actually need to do much solid modeling in my daily life,\nso most of the stuff that I create is a demo or an example of how to use a\nparticular kernel.\nStill, I've noticed a particular tension when working with implicit surfaces.\nWorking with low-level implicit surfaces is a bit like writing assembly: it's\nlow-level, powerful, and annoying.  If all you're given is x, y, z\nvariables \u2013 and it's your responsibility to combine them into all the shapes of\nyour dreams \u2013 that can be a painful experience.\nWhen faced with the pain of writing assembly, most people build abstractions on\ntop of it: high-level languages and libraries that compile down to a low-level\nrepresentation.  The equivalent here is a standard library of shapes and\ntransformations: sphere, box, translate, scale, etc.\n\nThere's also a less common approach to the pain of assembly: making assembly\nitself less painful to write.\nMy favorite project along those lines is\nKartik Agaram's Mu,\nwhich wraps emulation, tracing, and time-travel debugging around a subset of x86\nassembly language.\nHalfspace takes both paths.  It includes a (small but growing) standard library,\nbut also makes it easy to build up models incrementally: a complex model can\nbe split into smaller pieces, which can be parameterized and visualized\nindividually.\n\nGiven that justification, let's unpack the description a bit farther.\nSolid modeling\nFirst off, \"solid modeling\" means that we're focusing on objects with a\ndefinitive inside and outside; you should be able to pick any point in space and\nsay whether it's inside or outside the model.\nThis seems obvious, but there's plenty of modeling that doesn't care about that\nproperty: pull up any\nvideo game model viewer\nand you'll see plenty of infinitely-thin textured walls, built from a single\nfan of triangles.\nSince my background is in\nCAD/CAM software\n(with an emphasis on 3D printing), I want models that can be physically\nrealized.\n\nThere are a bunch of ways to do solid modeling.  In most CAD software, a\nboundary-representation\ngeometry kernel\nis responsible for stitching a bunch of individual surfaces together into a\nsolid body.  This is a tremendously hard problem \u2013 for example, the intersection\nof two NURBS\nsurfaces may not have a closed-form solution!\nDating back to my Master's thesis, I've been working on geometry kernels based\non implicit surfaces.  These have the advantage that they can conceivably be\nwritten and fully understood by a single person or small team, so they're a good\nfit for personal-scale fabrication software.\nThis continues in Halfspace: it's a GUI wrapped around the\nFidget geometry kernel.  Models can be designed using some\ncombination of pre-defined primitives and hand-written scripts, and exported as\neither images or triangle meshes.\nAn IDE for distance fields\nWe could use the Fidget kernel purely at the constructive solid geometry (CSG)\nlayer, building shapes (spheres, cubes, cylinders, etc) and combining them with\nlogical operations (union, intersection, difference).  Halfspace instead make\nthe decision to put the underlying distance fields in the foreground.\nLet me give you an example of why this matters.  Here are two distance fields\nfor a sawtooth wave, which have the same signs at every point in space, but\ndifferent values:\n\n\nIn this visualization, the sign (which defines inside versus outside) is\nshown by color (blue versus orange), with the boundary of the shape shown in\nwhite (corresponding to a value of 0). The field values are shown by the\nfainter lines, which are spaced at regular intervals (like a topographic map).\nDespite having identical signs everywhere, the first field is very poorly behaved.\nLook at the vertical edge of the sawtooth: there's a transition from inside\n(blue) to outside (orange) without a crossing through zero.\nThis is a C0 or\n\"jump\" discontinuity,\nand it's bad news!  Fidget uses automatic differentiation to compute normals, so\nthe normals across this boundary don't point in the correct direction (compare\nthe field lines between top and bottom images). In 3D, where we use normals for\nshading, this produces incorrect shading in the cabin's shingles:\n\n(If this looks familiar, it's because it's extracted from\nan earlier blog post)\nPutting distance fields front-and-center makes it easy to diagnose these kind of\nissues.  In fact, it suggests a further improvement on the sawtooth field: we\ncan tweak the gradient so that it's 1 everywhere, instead of being bunched up on\nthe diagonals.  Here's a before / after comparison:\n\n\nHaving uniform gradients makes various algorithms better-behaved; Fidget doesn't\nrequire it for correctness, but it may (for example) improve mesh quality.\nHalfspace is experimental and cross-platform\nRight now, it would be a very bold decision to use Halfspace in any\nload-bearing capacity.  In the Fidget writeup, here's one of\nthe project goals:\n\nFinding the \"right\" APIs for implicit kernels, with the possibility of making\nsubstantial compatibility breaks\n\nHalfspace is similar; it doesn't present APIs to end-users, but I'm flexing my\nsoftware architecture skills by building a substantial cross-platform\napplication, and I'm willing to aggresively iterate and break things as we go.\nSpeaking of cross-platform, I'm making my life harder by targeting both the web\nand native platforms.  In the era of supply-chain attacks, being able to share a\nweb link \u2013 instead of asking someone to compile and run your code \u2013 is great for\nonboarding and casual usage.\nTo that end, I've been collecting the \"Halfspace stack\": a set of libraries and\npatterns which let me ship a combined native + web application with a minimum of\npain.  Right now, here are the core pieces:\n\nRust for the application (and all dependencies)\n\nManually calling wasm-bindgen et cetera to build for the web\n\n\negui for the GUI\n\negui_dock for the core\nwindow-and-tab abstraction\n\n\nwgpu for both rendering the UI and GPU compute (!)\nRhai for scripting\nRayon and\nwasm-bindgen-rayon,\nused for two purposes:\n\nSpeeding up parallel algorithms by distributing work over many workers; this\nis a \"typical\" usage of the library\nA thread pool for short-lived background tasks; this is a more unusual\nusage, but we can't spawn threads on the web.\n\n\n...and a long tail of other libraries and shenanigans\n\nweb-time for cross-platform\ntime support\nA homebrew worker pool for off-thread (async) GPU rendering\n\n\n\nThis all deserves a dedicated writeup, and I have complaints about every single\nlayer of the stack, but overall, it's incredible that everything Just Works\u2122.\nDriving Fidget improvements\nAnother goal of Halfspace is to drive improvements in the Fidget kernel, by\nusing it in a non-trivial application.\nThe biggest victory on this front has been ongoing work on\nfidget-wgpu.  This was\nmotivated by performance on the web: the native build was pleasantly fast, but\ndoing rasterization on the CPU was awfully slow (\"non-interactive speeds\") when\nrunning through a layer of WebAssembly.\nAfter a lot of work on the Fidget side, both rasterization and\npost-processing (e.g. shading) can be run purely on the GPU, without any\nroundtrips to the CPU.\n\nThis was both a performance and architectural win:\n\nWe now have native rendering speed on both native and web targets\nRendering logic is no longer spread between Fidget (on the CPU) and Halfspace\n(with a mix of CPU and GPU code):\n\nFidget implements the canonical rendering logic\nHalfspace has thin shaders which draw an RGBA texture\n\n\n\n(The 2D rendering pipeline is also now fully GPU-accelerated, although it has\nslightly fancier shaders in Halfspace, for reasons)\nWrapping up\nHalfspace is a thing that exists!\n\nYou should try it out, and should probably not use it for critical applications!\nIf you encounter problems, please file an issue or open a discussion on Github.\nLike most of my work, I plan to keep working on it until I have run out of\nthings to learn from the project.\nAlso like most of my work, it's\nopen-source\nunder the MPLv2 license."
                ],
                "output": "readability",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:10.820935+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.mattkeeter.com/projects/halfspace/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T22:06:10.814334+00:00",
                "index_texts": null,
                "output": "Halfspace",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:10.804037+00:00",
                "status": "succeeded"
            }
        ],
        "wget": [
            {
                "cmd": [
                    "wget",
                    "--no-verbose",
                    "--adjust-extension",
                    "--convert-links",
                    "--force-directories",
                    "--span-hosts",
                    "--no-parent",
                    "-e",
                    "robots=off",
                    "--reject=js,mjs,woff,woff2,ttf,otf,eot,mp4,m4v,mov,webm,ogv,mkv,avi,flv",
                    "--timeout=120",
                    "--restrict-file-names=windows",
                    "--page-requisites",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) wget/GNU Wget 1.21.3",
                    "--compression=auto",
                    "--timestamping",
                    "https://www.mattkeeter.com/projects/halfspace/"
                ],
                "cmd_version": "GNU Wget 1.21.3",
                "end_ts": "2026-09-30T22:06:10.796384+00:00",
                "index_texts": null,
                "output": "www.mattkeeter.com/projects/halfspace/index.html",
                "pwd": "/data/archive/1790805942.390334",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T22:06:09.276034+00:00",
                "status": "succeeded"
            }
        ]
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": null,
        "dom": null,
        "favicon": "favicon.ico",
        "git": null,
        "media": null,
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Halfspace",
        "warc": null,
        "wget": "www.mattkeeter.com/projects/halfspace/index.html"
    },
    "link_dir": "/data/archive/1790805942.390334",
    "newest_archive_date": "2026-09-30T22:06:12.442485+00:00",
    "num_failures": 0,
    "num_outputs": 6,
    "oldest_archive_date": "2026-09-30T22:06:08.331116+00:00",
    "path": "/projects/halfspace/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_id": "61f2e1e9-2c27-48e1-acb4-3864a6e4cd8d",
    "sources": [
        "/data/sources/1790805942-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1790805942.390334",
    "title": "Halfspace",
    "updated": "2026-09-30T22:06:08.330620+00:00",
    "updated_date": "2026-09-30 22:06",
    "url": "https://www.mattkeeter.com/projects/halfspace/"
}