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.
Some scripts to try:
- Wiggly Thing
- Bumpy Cube
- Tree
- Cubes with lights (I talk about how this was made to work in the entry on importing .obj files)
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, :
starts
a word definition, dup
, drop
, swap
,
and 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).
!
and @
store and fetch values to/from a variable, respectively.
You can define dynamically-scoped variables using the context-variable:
word.
A few context variables have special meaning, but you still need to declare them
to use them.
$t
is 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-index
is the index of the material that will be plotted when youplot-sphere
orfill-polygon
.
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.
Words
Listed with (parameters and -- return values)
Cursor transformations
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.
Drawing
- 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.
Context loading/saving
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.
Jumps
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 jump:label
and $label
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.
Arithmetic
+
,-
,*
,/
,**
(a b -- c) add, substract, multiply, divide, and exponentiate, respectively.
Trigonometry
sin
,cos
(a -- b)
Comparison
<
,<=
,=
,>=
,>
(a b -- c) less than, less than or equal, equal, greater or equal, greater.-1
is pushed onto the stack if true,0
otherwise.
Stack operations
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.
Definition words
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,$variablename
will refer to the variable itself, andvariablename
can 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-zero
if you're going to bejump-if-zero
ing 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.
Materials
There are only a few defined.
0
-3
- reserved for special meanings4
,5
- gray steel6
- black steel8
,9
- pink stone12
-15
- foliage16
-20
- tree bark240
-247
- glowing lights of different colors (247 is white)
I usually : mati! $material-index ! ;
so that I can just type
material-number mati!
to set the current material rather
than the whole $material-index
rigamarole.
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.