subject: Procedural Shape Editor thumbnail-image-url: http://picture-files.nuke24.net/uri-res/raw/urn:bitprint:2DK3UN74KGHEIBUGK7PG7PNFEXB4YWUY.AX6O5MF7BRTGBMLSTXCS56OVHQ5GX57YVWCBOYY/treef.png date: 2016-07-14
Procedural Shape Editor - 2016-07-14 - Entry 11
I've made a Procedural Shape Editor! By 'editor' I mean a text area that you can type programs into and some canvases to show the result. It's rudimentary but it means that it's at least possible for people to come up with some interesting models while I go and work on other stuff.
Scroll down for instructions.
Shapes are defined using a Forth-like language. Defining them as a procedure allows us to re-render them at different rotations and scales and under different lighting conditions and at different points in their animation cycle (for animated shapes).
The end result of the Forth program is a 'shape sheet', which consists of a material index and corner depths for each pixel. This shape sheet can then be rendered as an RGB image by providing materials for each material index and a set of lights.
This means we could, for instance, swap out steel for copper without having to re-run the Forth script.
The text area on the left is where you write your script. Hit 'compile' to compile it and update the shape views on the right. If there are errors they'll be printed to your browser's console. You can drag around the shape views to change the rotation, use the scroll wheel to zoom in and out, and use the '⏸'/'▶' button to toggle auto-rotation.
The second preview does 4x supersampling, which is why it takes longer to render than the others.
Now I will try to describe the language.
Similar to traditional Forth, words are delimited by whitespace,
literal numbers push themselves onto the data stack,
a word definition,
pick have their traditional meanings.
; inserts an
exit (return) and marks the end of word definitions;
anything outside of a function definition is run immediately at the beginning of the program.
Rational numbers, e.g.
1/5, can also be represented literally
(though internally they are just floats).
@ store and fetch values to/from a variable, respectively.
You can define dynamically-scoped variables using the
A few context variables have special meaning, but you still need to declare them
to use them.
$tis the animation phase, a number [0-1) indicating the point in the animation that we're currently drawing. If your thing isn't animated you can ignore it.
$material-indexis the index of the material that will be plotted when you
Drawing is done by transforming the 'cursor' by translating, rotating, and scaling, and plotting spheres or polygons. Cursor transformations are always relative to and alter the current transformation. e.g. if you move by 3,0,0, scale by 2, and then move by 0,1,0, the cursor will be at 3,2,0, and a sphere plotted with size=1 will actually have radius=2 relative to the original coordinate system.
The initial coordinate system is +x = right, +y = down, and +z = into the screen. One unit = one meter. I expect standard-sized blocks in the game to be 1m by 1m, with bounds from -0.5,-0.5,-0.5 to +0.5,+0.5,+0.5, so if you want to design a block-sized thing, keep it approximately within those bounds.
Listed with (parameters and -- return values)
move(x y z --) move the 'cursor' by the specified amount.
scale(scale --) scale the cursor by some amount.
aarotate(x y z ang --) rotate the cursor by some amount.
- plot-sphere (radius --) plot a sphere of the specified radius at the cursor position.
- open-polygon (--) start drawing a polygon at the cursor position.
- polygon-point (--) add an additional polygon vertex.
- fill-polygon (--) add an additional point and fill the polygon.
Back sides of polygons will not be drawn. The 'front' side is the side that appears to be drawn clockwise.
save-context(--) save the current context (transformation, material index, polygon points) to the context stack.
restore-context(--) replace the current context with one popped off the context stack.
!(value variable --) save a value into a context variable.
@(variable -- value) retrieve a value from a context variable.
jump(location --) jump to the program location popped from the top of the data stack.
call(location --) push the address of the next instruction to the return stack jump to the program location popped from the top of the data stack.
jump-if-zero(value location --) jump to the specified location if value is zero.
jump-if-nonzero(value location --) jump to the specified location if value is not zero.
exit(--) pop the return location off the return stack and jump to it.
>r(value --) pop a value off the data stack and push it onto the return stack.
r>(-- value) pop a value off the return stack onto the data stack.
There will also be
variants of any user-defined wors that jump to and push the location of any user-defined words.
You can play 2/3 Tower of Hanoi by shuffling data between the data and return stacks.
**(a b -- c) add, substract, multiply, divide, and exponentiate, respectively.
cos(a -- b)
>(a b -- c) less than, less than or equal, equal, greater or equal, greater.
-1is pushed onto the stack if true,
drop(a --) throw away the topmost value on the stack.
dup(a -- a a) duplicate the top stack value.
swap(a b -- b a)
pick(x ... n -- x) duplicate the nth value from the stack (n=0 being the value directly under n) onto the top of the stack.
These are interpreted at compile-time rather than run-time, and don't use the stack. Any arguments tend to be symbolic and come after the word.
context-variable: $variablename- declare a context variable. After this,
$variablenamewill refer to the variable itself, and
variablenamecan be used as shorthand for getting the variable's value onto the stack.
alias: newname oldname- create an alias for a word. e.g.
alias: jz jump-if-zeroif you're going to be
jump-if-zeroing a lot and get tired of typing it out.
code-label: label- declare a code label. This allows you to refer to code labels that aren't yet defined.
: label- start defining a word or define a code label (they're the same thing).
;- stop defining words and continue defining the top-level program.
There are only a few defined.
3- reserved for special meanings
5- gray steel
6- black steel
9- pink stone
20- tree bark
: mati! $material-index ! ; so that I can just type
material-number mati! to set the current material rather
than the whole
Eventually one would be able to define custom materials and indicate your preferred palette in your script, but that's for another day.
I probably forgot some things. You can ask me questions if you want.