PLines in 3d — PLine3
Field contains quite a bit of support for procedural 2d drawing. This page assumes that you are familiar with this "PLine" drawing system. If not, see BasicDrawing, and the series of pages around DrawingPLines first.
2d is great and all, but what about 3d? For a long time OpenEnded Group managed to get by with absolutely no 3d drawing support inside Field — after all, all of our 3d work was happening in another window. Occasionally we did 3d work in 2d by just "doing the math" directly in Field. After all, you can write a function that filters the position of every part of a 2d PLine to make it appear to be in 3d.
For various recent reasons this has changed. Most pressingly we're having much fun making stereoscopic pieces; we're also spending more time hanging out with architects who are as a discipline constantly doing things that their tools are poorly designed for. Perhaps Field might grow into a platform that has something to say about architectural forms.
A start — support for PLine3
As a start, we've included in beta7 (and the development tree) two pieces of the 3d drawing puzzle. Firstly there's PLine3 class that's just like PLine but with 50% more dimension. Secondly, there's a "3d spline drawer" helper which is a lot like the normal "spline drawer" but adds the things that you typically need for doing 3d.
Here's where we're aiming for right now:
This picture tells pretty much the complete story as of writing. Things to note:
- PLine attributes work just the way that they normally do — see wiki:DrawingPLinesProperties. So you fill, color and so on in just the normal way.
- Those dots you see are in fact the standard PLine editing tools (see BasicDrawing and LineBasedInteraction). You can drag nodes, split them, smooth them and so on just like you can in 2d. Mouse movement moves nodes at the "depth" that they are at, parallel to the viewing plane.
- Fills work, but only when you fill planar things. Filling non-planar things gives you results that appear to be missing triangles. This is intentional. If you want to make complex, non flat surfaces, you need to use NURBS, see below.
- Unlike the normal spline drawing helper, the contents of a 3d spline drawer is clipped to its frame. This means that you have one window per 3d spline drawer. Obviously you can have as many windows as you like. You can drag them around.
- Associated with a 3d spline drawer is a camera. You can change the camera programmatically, or by using the mouse.
Things that are not working that you might expect to work aren't shown in the above screenshot:
- PDF export — pdf export is broken. You can't export 3d PLines that are placed inside a 3d spline drawer. This also breaks the rasterizer used to converts PLines into images. This is a high priority feature #191.
- Cursor — (see [wiki:DrawingPLinesEditing), the Cursor class doesn't know anything about 3d, there needs to be a Cursor3 class. So, distances, intersections and so on based on PLines that happen to have depth will be computed as if z=0 for all of the line.
- .stroke(...) — exotic line styles generated using .stroke(...) don't work correctly in 3d. The stroking (thickness, and dash computation) is done as if z=0 and then re-projected out to the correct z coordinate. This is only what you want if you are drawing in 2 and a half dimensions as a series of xy-planes that happen to have some depth.
Things that are not working that will take some work and thought to implement:
- A respectable surface class — lines in 3d is all well and good, but we need a SurfaceCursor class or something as an analog of the 2d line's {{Cursor}}}. We have some nascent NURBS support (and we'd like subdivision surfaces).
PLine3 basics
The example code largely speaks for itself:
_self.lines.clear() p1 = PLine3() p1.moveTo(0,0,0) p1.lineTo(1,0,4) p1.lineTo(1,1,4) p1.lineTo(0,1,0) p1.lineTo(0,0,0) p1.filled=1 p1.color=Vector4(0,0,0,0.5) p1 *= Vector3(10,10,10) _self.lines.add(p1) _self.camera.position[:]=(0,0,50) _self.camera.target[:]=(0,0,0) _self.camera.up[:]=(0,1,0)
Yields:
Things to note:
- .moveTo and friends can now take 3 parameters rather than 2. .cubicTo takes 9 rather than 6.
- *= (rescale / rotate) and friends can now take Vector3 rather than just Vector2 and they rescale and rotate around the 3d center of the PLine by default
- Pay close attention to where you put the camera. There's nothing worse than being lost in 3d. See here for more information about 3d cameras.
Camera control
Two things might help in placing the camera. Firstly, the three-camera defaults to a position where drawing 2d things (at z=0) appears roughly where they would appear if you were drawing them into a "normal" spline drawer. That is with the origin in the top left in coordinates that are pixels. This is correct until you move the actual three-d spline drawer — then the camera comes along with the spline drawer.
Secondly there's a mouse operated camera, very much like Maya. With the three-d spline viewer selected tap 'space'. Now (until you press space again), hold option. Left mouse rotates, middle mouse translates and right mouse pans.
If all else is lost — press 'f' to frame all.
The above image shows two drawers (that happen to overlap) in camera navigation mode (with space pressed).
Early NURBS support
For a long time now NURBS (that's non-uniform rational b-splines) have been the de facto way of doing smoothly varying 3d pieces of geometry. Forget for a moment the textbook history of NURBS in car and boat design and just look at the influence that Rhino3d has had on the field of Architecture in the last 15 years.
NURBS share some features with the staple of 2d graphics — Field's PLine drawing model (and PDF's drawing model, and Quartz's drawing model and NodeBox's drawing model etc...) of .moveTo() and .cubicTo(). Strictly speaking such cubic splines are uniform and non-rational. Non-uniform and rational are strictly superior, so anything you can .cubicTo() you can express in NURBS and many more things as well.
Field builds its limited NURBS support out of PLines. We have both 2d NURBS curves and 3d NURBS surfaces. We'll start with curves.
Turn it on
First you have to turn on the "advancedGeometry" plugin in the plugin manager.
Then. you can either go to wikipedia or trust the following sentence: NURBS is just another way of interpolating the points that you put into a PLine.
NURBS in 2d
Let's start here, in a fresh, 2d spline drawer, drawing a 2d spline.
line = PLine().moveTo(30,30).cubicTo(-150,30,-30,250,150,150) _self.lines.clear() _self.lines.add(line)
Yields:
No surprises there. If we add line.nurbs=1 to this you'll see exactly no change at all. (I told you that Field's PLine's were a subset of NURBS curves). Let's add another "cubic" segment:
line = PLine().moveTo(30,30).cubicTo(-150,30,-30,250,150,150).cubicTo(450,130,30,250,250,250) line.nurbs=0 _self.lines.clear() _self.lines.add(line)
Gives:
Still, no surprises. Now we mark this PLine as being a NURBS, and change line.nurbs=0 to line.nurbs=1. This gives us something different:
It might be easier to see if we draw the "control points":
Things to note:
- Other than the first and last point the curve doesn't necessarily go through any of the points. This is in contrast to the way that .cubicTo and .lineTo normally work.
- The curve is "completely" smooth (whereas with .nurbs=0 it has a pointy bit between the two segments).
- It's hard to put you're finger on it, but it feels different from a normal kind of cubic spline.
- All of the red points in the image above (all of the control points) are treated the same. It doesn't matter if you write .cubicTo(cx1,cy1,cx2,cy2, x,y) or .lineTo(cx1,cy1).lineTo(cx2, cy2).lineTo(x,y).
With that let's make that "simplification":
line = PLine().m(30,30).l(-150,30).l(-30,250).l(150,150).l(450,130).l(30,250).l(250,250) line.nurbs=1 _self.lines.clear() _self.lines.add(line)
(The line doesn't change). Here we're using the abbreviation l for lineTo.
So far, so boring. What else have NURBS going for them? Two things: the 'R' (Rational) and the 'NU' (Non-Uniform). We'll look at the former here, and we'll level the latter for another update.
Node Weights
In addition to having a position, control points in NURBS can have weights as well. Just as we use a vertex property (z_v) to set the depth as a per vertex thing, we use a vertex property w_v to set the weight:
line = PLine().m(30,30).l(-150,30).l(-30,250)
line.w_v = 30.0
line.l(150,150).l(450,130).l(30,250).l(250,250)
line.samples=300 #increase the resolution of the line
_self.lines.clear()
_self.lines.add(line)
This gives us:
(I've left the original line in in light grey).
By increasing the weight of the third node considerably (normally it's 1.0) we cause the line to try much, much harder to move through that point. That level of control is one of the benefits of NURBS.
NURBS in 3d — surfaces ?
Well that's good summary of where we are right now (in beta7) for NURBS curves in 2d. What about 3d? Firstly, NURBS curves work just as you'd expect in 3d. Use the convenience of PLine3, and make sure you set .nurbs=1 and there you go.
What about surfaces? For those familiar with the drawing functions in Field, this should come as no surprise, we use PLine / PLine3 for those as well.
Here's where we're heading:
A parade of little NURBS surface arbitrary doodles. And here's the arbitrary code to get there:
_self.lines.clear() def drawSurface(a,b,c, off): line = [] for n in range(0, 4): p3 = PLine3() p3.moveTo(30+n*10+b,230+a,100+n*-10) p3.cubicTo(150+n*10,300-n*40-c*n,30, 30+n*10,150,0,150+n*10,250,-10) p3 += off p3(color=Vector4(0,0,0,0.2)) line.append(p3) head = line[0] head.attachSurfaceLines(line) head(color=Vector4(0,0,0,0.1)) #draw ths surface itself _self.lines.add(head) #and draw the lines just for show _self.lines.addAll(line[1:]) for n in range(0, 10): drawSurface(n*4,Math.sin(n/3.0)*50,-n*Math.cos(n/1.0)*4, Vector3(-100+n*30,0,0))
If you run the code above, don't forget to set the _self.camera to something sensible.
The really important call is to .attachSurfaceLines(). That takes a list of PLine and and associates them with just one of them. That line and the list are then the PLine that gives you the NURBS surface. Add a _self.tweaks() to the code above and you can edit the NURBS control surfaces live with the mouse, just like any other PLine.
NURBS, where next?
Right now we are at a very intermediate point with this kind of thing. The priorities (in order):
- We can't customize the "knot vector" (that's the Non-Uniform part of NURBS).
- Obviously our flat, single color, renderer is about the least impressive way that you can show a surface. Using BaseGraphicsSystem we should be able to interpose much better custom renderers.
- There needs to be a way of exporting these pieces of geometry to something industry standard (Collada?)
- We need the equivalent of Cursor for NURBS PLine and a surface evaluation class.
None of these things are particularly hard. It's work in progress. Some feedback would help set priorities.
Mouse Editing works (and that's harder than you think)
The summary is that _self.tweaks() does exactly what you'd expect — see BasicDrawing. Mouse editing on 3d objects gets replayed precisely — nodes that are manipulated get projected onto the 2d 'screen', manipulated, and then put back again to the same depth. This is basically what you'd expect for the simplest thing. Clearly it would be nice to have a greater range of tools, including some that are much more 3d aware, and, since Field's mouse-tool system is already very extensible, this will be pretty straightforward
Getting this to work was a little lest straightforward than you might think, because the simplest case of a mouse edit that we've just described is view dependent — you get a different edit from the same mouse drag if the camera is in a different spot. So, the camera position where edits take place is recorded as well.
In any case, now that this machinery is in place we can grow some interesting and code-based 3d mouse editing tools pretty rapidly.








