https://github.com/ianthehenry/janet-module/blob/master/init...
This is a very very barebones version of this, but it’s not too hard to construct environment tables dynamically
HN user
blog: https://ianthehenry.com
3d lisp art playground: https://bauble.studio
book i wrote: https://janet.guide
2d lisp art playground: https://toodle.studio
https://github.com/ianthehenry/janet-module/blob/master/init...
This is a very very barebones version of this, but it’s not too hard to construct environment tables dynamically
The `import` macro extends the current environment with prefixed symbols from another environment. But the environment is a first-class object that you can hold and manipulate and use in arbitrary ways — `require` is the lower level primitive that `import` is built on.
And a Blue Planet II video of a brine pool, stronger content warning for much more horrifying death: https://www.youtube.com/watch?v=ZwuVpNYrKPY
The next chapter is about SDFs, but it is not available yet.
Bonsai is a frontend framework that models data as an explicit graph, separate from any “component hierarchy” or DOM structure. State can live anywhere in the application graph, whether or not it’s associated with DOM nodes, and you eliminate all of the “move this state to the parent to share it” ceremony by just having “components” return their views and “outputs” at the same time (if they want).
It’s pretty great and hard to imagine going back to a React-like component hierarchy.
I'm working on adding something like https://graphtoy.com/ to my lisp-based 3D art tool https://bauble.studio/. It's really useful to visualize functions like this, especially when writing animation curves that vary over time.
It's easy to add it as a plain overlay over the screen if you're graphing a function, but I really want it to be able to plot arbitrary expressions with free variables where it just infers the axes, so you can just see values overload in the orthographic view (press alt-q to see that). That way you can just write something like (ss p.x 0 10 | graph) on any expression and visualize it as you go. I haven't quite figured out how to make it seamless though...
If this kinda thing piqued your interest and you want to play around with this idea, paste the following code into https://bauble.studio/:
(def octaves 4)
(def lacunarity 2.00)
(def persistence 0.50)
(def period 100)
(def height 40)
(plane y (fbm octaves :f lacunarity :gain persistence perlin p.xz period * height)
# try s/color/shade on the line below
| color (ss p.y -20 0 blue (ss p.y 0 20 green white))
| slow (20 / height)
| intersect (sphere 200))
(Using the terms from the article.) You can right-click and drag those numbers to see how the parameters affect the result in realtime. Also an easy way to compare perlin and simplex noise. Procedural terrain is fun!Also while this uses 2D perlin noise -- you're just changing the height of a plane -- you can create some pretty detailed neat "rocky" terrain effects by using 3D perlin noise instead. Change "p.xz" to "p.xyz" to see what that looks like.
okay, that's helpful. the "undefined is not an object" bit is definitely a function of the "failed to create webgl2 context" error -- it tries to reference it unconditionally. no idea why it can't create the graphics context in the first place, though (and not much to do without one)
huh, that's no good. it works fine for me on safari 18.1. would you share the line that it's raising on? no other errors before that one?
I ordered them from sculpteo — the only service I could find that would do it. Very happy with everything I’ve gotten from them. It is very very expensive if you want to make anything large, but for small jewelry-scale stuff it’s not too crazy. The balloon is seven custom parts so that was pricey ($250 total?), but the other two models were around $50 each.
It's complicated; Calvin Rose was the original creator of Fennel ("fnl"), but I understand it was just a little side project that he made over the course of a week or so in college. Phil Hagelberg found the project years later and thought it was cool, took over as its maintainer, and basically turned it into the Fennel you know today. So it's like, kind of, I dunno, both things are kinda true.
hi, i am the author of this book and i agree with you. it's not a good connotation. i chose it because: 1. it is kinda memorable (look there are so many programming books with exactly the same name) 2. the language is named after an immortal being who guides mortals through the afterlife in the fictional property the good place, so there's some kinda connection. but i agree the implication that janet is somehow "hard" to learn or that the author is somehow "on a higher plane" is bad. i waffled on this a lot but never came up with another title i could stand and ultimately just pulled the trigger. (but note that i left myself some wiggle room with a neutral domain name.) i hope the tone of the book itself helps to counteract the title, but ya know who knows
delete the contents of the script and then refresh — you’ve loaded an old version of bauble before and have the old tutorial cached in localStorage :/
hey thanks!
Thanks! Here's the source:
(color r2 (vec3
(fbm 8 :f (fn [q] (rotate (q * 2) (pi * sin (t / 100)) + [t 0]))
(fn [q] (cos q.x + sin q.y /)) q (osc t 30 30 10) | remap+)))
Basically taking the function (1 / (cos(x) + sin(y))) and adding it to itself 8 times, each time scaling and rotating the input a little more (:f).I'm curious if it looks the same on all GPUs because it kinda relies on floating point precision errors to give it that film-grainy textured effect. And it definitely divides by zero sometimes.
Oh yeah if you clear localStorage like from dev tools, Bauble will re-save the script before refresh, putting it right back where it was. But if you like cmd-a backspace to empty the contents of the script and then refresh it’ll load the default.
Depending on the data, maybe? SDFs aren't great at rendering large numbers of enumerated objects -- something like a point cloud would be prohibitively expensive, so I wouldn't think to use them for like traditional graphing.
So if you've ever loaded Bauble before you might have a stale and no-longer-working version of the tutorial cached in localStorage -- if you just clear out the script and refresh it will restore the default one. If that's still erroring, please let me know!
oh yeah good point. i probably shouldn't link to that anymore. it's all on mastodon too! https://mastodon.social/@ianthehenry
https://bauble.studio/ is a programmatic 3D art playground that I've been working on for a while now, and I'm pretty excited about it! It's based around signed distance functions, which are a way to represent 3D shapes as, well, functions, and you can do a lot of like weird mathematical distortions and operations that give you cool new shapes. Like average two shapes together, or take the modulo of space to infinitely repeat something... it's a really fun and powerful way to make certain kinds of shapes.
SDFs are very cool in general, and widely used in the generative art communities, but kinda hard to wrangle when you're writing shader code directly. They really are functions, but GLSL doesn't support first-class functions, so if you want to compose shapes you have to manually plumb a bunch of arguments around. So Bauble is essentially a high-level GLSL compiler that lets you model SDFs as first-class values, and as a result you can make a pretty cool 3D shape in just a few lines of code. And then 3D print them!
I need to do some actual work to promote and publicize it once I'm done with the documentation and implement a few more primitives, but it's very close!
The docs have lots of examples of the sorts of things you can do with SDFs: https://bauble.studio/help/
And for examples of some "art" that I've made with it recently:
https://x.com/ianthehenry/status/1839061056301445451 https://x.com/ianthehenry/status/1839649510597013592 https://x.com/ianthehenry/status/1827461714524434883
Yep! It just outputs GLSL. It doesn't do anything smart -- it's a single giant shader that gets recompiled whenever you change anything, so it wouldn't really work for something like a game. I mean, it could handle like basic instancing of the form "union these N models, where N<256" but there's no way to change the scene graph dynamically.
https://bauble.studio/ is a lisp-based procedural 3D art playground that I hacked together a while ago. It's fun to play with, but it's a very limiting tool: you can do a lot to compose signed distance functions, but there's no way to control the rendering or do anything "custom" that the tool doesn't explicitly allow.
So lately I've been working on a "v2" that exposes a full superset of GLSL, so you can write arbitrary shaders -- even foregoing SDFs altogether -- in a high-level lisp language. The core "default" raymarcher is still there, but you can choose to ignore it and implement, say, volumetric rendering, while still using the provided SDF combinators if you want.
The new implementation is much more general and flexible, and it now supports things like 2D extrusions, mesh export for 3D printing, user-defined procedural noise functions... anything you can do in Shadertoy, you can now do in Bauble. One upcoming feature that I'm very excited about is custom uniforms and embedding in other webpages -- so you can write a blog post with interactive 3D visualizations, for example.
(Also as a fun coincidence: my first cast bronze Bauble arrived today! https://x.com/ianthehenry/status/1827461714524434883)
Ah, thanks! Got it. Okay that's wild. I would not expect that to be stable across dynamically-generated template functions, but it seems to work!
Wait this is very interesting but I don't follow -- how do the template arguments let you identify the callsite? I thought this was basically just syntax sugar for memoize(doSomethingVeryExpensive, x)([""]), but there's something extra on that argument list that's stable across invocations?
Eh, by recursion I meant specifically the exponential "fib(n - 1) + fib(n - 2)" flavored definition. If you're writing the linear-time algorithm and happen to do the iteration via tail recursion, I don't think there's anything absurd about that
Are there any existing compilers that could compile a function like this -- non-tail recursive and non-tail recursive modulo cons -- into something that executes in sub-exponential time? How would that even work? I've never heard of a compiler sufficiently smart to optimize a definition like this and now I'm very curious
Yeah this is kinda like... this is definitely an advanced topic within the topic of macro writing and not very intelligible if you haven't written simpler macros for a while already.
I wrote a book whose third chapter is a much "gentler" introduction to macros. I don't know if it's actually intelligible (see: the monad tutorial fallacy) but it presents them the way that I was first able to understand them -- explicitly starting without quasiquote and working up to it. Easier for me than getting lost in the notation. https://janet.guide/macros-and-metaprogramming/
i think this is just very badly worded. the initial conditions are current=8, previous=41, not the other way around. i should make that more clear
ha thanks, you are absolutely right. i updated the table :)
You point out a good general problem that I find when blogging -- like, you don't want this, right? The whole premise is absurd; the point is not to memoize an expression, but rather to demonstrate that you can share values between compile-time and runtime. But in order to do this you need some specific example of the idea so that readers have something concrete to hold onto and generalize from. And then the difficulty is trying to present that specific example in a way that gets the general idea across, right, without the reader overfitting to the specific example you presented. It's hard! I don't think this one really succeeded.