raco pkg install pict3d
The GitHub page is here:
https://github.com/ntoronto/pict3d
All features are documented. The API is stable. I've verified that it
runs on Racket 6.1.1 and the current development version. There are
reports of it working on at least two Windows systems, two Mac OS X
systems, and two Linux systems. It might not eat your graphics card.
Current features:
* Works in untyped and Typed Racket
* Spheres, rectangles, triangles and quads with per-vertex attributes
* A system of groups and affine (arbitrary parallel-line-preserving)
transformations for sticking Pict3Ds together
* Ray-against-scene and line-against-scene collision detection
* Render targets: an interactive debugging view in DrRacket's REPL,
`pict3d->bitmap` and `pict3d-canvas%`
* big-bang3d (currently without networking)
------------------
Now we get to crowdsourcing.
I've reached the limits of what I can do without working on Pict3D
full-time. In particular, I don't know whether the API is awkward or
actually quite nice for the uses it'll be put to. I've drawn on some
personal game design and coding experience, but that only goes so far.
This is where you come in.
If you've ever had the slightest hankering to do some real 3D but
avoided it because of the pain that usually goes with it, try Pict3D.
(If it fails to work, please submit a bug report on the GitHub page.)
Got a visualization project? Try Pict3D. Want to make a game? Try
Pict3D's version of Big Bang. Want to just fool around in 3D space for a
bit? Try Pict3D, and report back on how it goes.
Anything is fair game for criticism, praise or suggestions: groups and
pinning, distinguishing direction and position vectors, presence or lack
of shapes you need, how to allow shader programming, file formats that
would be nice to import models from, information that would be nice to
have in the interactive debugging view, how to add texturing to the API,
and even the tone of the documentation.
I'm looking forward to your reports, all my little minions. Wait, did I
say that out loud?
Neil ⊥
____________________
Racket Users list:
http://lists.racket-lang.org/users
It looks like we'll need alternative input methods for movement as well.
(Turns out that WASD aren't necessarily near each other on all
keyboards.) I'll add this to my input revamp list.
Neil ⊥
From the same place I downloaded the spare hours to make it. :p
If you need justification, this will render molecular structures
thousands of times faster than Plot does. It won't output PDFs, but you
can set the background color to white or transparent and render up to
1024x1024 bitmaps and save them as JPEGs or PNGs.
Neil ⊥
I have some ideas. We'll see how they turn out.
What I really need is mouse grab, but Racket's GUI framework doesn't
provide a way to do it.
Neil ⊥
How about: when the snip has the focus, shift and mouse down rotates
things and normal mouse down just moves the mouse? Then you'd get the
ability to slide along left and then go back and slide along some
more. Does something like that work?
Robby
What do browsers do to implement mouse lock on Macs?
> If you've ever had the slightest hankering to do some real 3D but avoided it because of the pain that usually goes with it, try Pict3D. (If it fails to work, please submit a bug report on the GitHub page.) Got a visualization project? Try Pict3D. Want to make a game? Try Pict3D's version of Big Bang. Want to just fool around in 3D space for a bit? Try Pict3D, and report back on how it goes.
I made a version of the game unpossible using pict3d:
https://github.com/AlexKnauth/my-unpossible
It’s a bit slow and jerky, but that’s probably entirely because I didn’t try too hard to make my end of it fast.
If you have any suggestions that would be great.
On Mar 9, 2015, at 5:58 PM, Neil Toronto <neil.t...@gmail.com> wrote:
> On 03/09/2015 03:37 PM, Alexander D. Knauth wrote:
>> Is there a good way to draw a smooth curved cylinder?
> Sure! You could assemble it yourself out of triangles and quads. :)
>
> OK, maybe that's not "good". It's a fun problem, though, so I gave it a shot. I've attached my solution. The `parametric-cylinder` function accepts a function that takes a "time" parameter `t` and returns four values: the center of the cylinder at time `t`, the first derivative of the function from `t` to center, the second derivative, and a radius. It should work on any parametric function that doesn't have any collinear segments.
>
> You probably want something like the `helix` example.
>
> An easier-to-use solution would probably derive the first and second derivatives from samples of the center position.
>
> Neil ⊥
Thanks!
And by the way, for my use case, it was good that I could provide the derivative myself.
I ended up using it like this:
(parametric-cylinder
#:samples 1
(match-lambda
[0.0 (values pos dir (dir- new-dir dir) 1/2)]
[1.0 (values new-pos new-dir (dir- new-dir dir) 1/2)])
0.0 1.0)
Very cool!
Let's be fair to you: it's not obvious how to make it fast. I'd love to
make the API so that it *is* obvious, or so that the techniques to make
it fast are done automatically.
Here's an indication about what's going on. Try these in the REPL:
(time (void (get-tube+obstacles (make-world-stream) +x 40)))
(time (void (combine (get-tube+obstacles (make-world-stream) +x 40))))
(Using `void` just keeps the Pict3Ds from getting rendered.) For both, I
get 75-125ms, so the culprit isn't `combine` - it's `get-tube+obstacles`.
But the `get-tube+obstacles` code isn't doing anything in a particularly
slow way. It's just that making shapes is slow. As evidence, the game
goes at a good clip, though the tube looks terrible, if I add
"#:segments 8" to the call to `parametric-cylinder`.
(I could probably speed `parametric-cylinder` way up by writing it using
a lower-level interface to the 3D engine, but this would eventually come
up again.)
What you need is to 1) create and combine the necessary Pict3Ds as much
as possible before the game starts; and 2) reuse them as much as
possible during the game.
You can even make the reused Pict3Ds fairly large if you freeze them.
Any modern 3D card won't have any problem with this, for example:
(freeze (combine (get-tube+obstacles (make-world-stream) +x 1000)))
Freezing takes a while, though, and has this annoying property that it
takes the most time the first time the frozen Pict3D is rendered. (The
actual freezing has to be done with an active OpenGL context.) I'm not
sure what to do about the latter problem.
If you don't mind, I have a couple of questions.
1. What was the most annoying surprise?
2. What was the most pleasant one?
Again, very cool!
Neil ⊥
I'd generate a few different kinds of segments and then transform them
into place. Don't use `rotate` for that, though - there's an easier way.
Transforming a shape is pretty fast. Here's an insane example, using
many more segments for a cylinder than you'd normally ever need:
#lang racket
(require pict3d)
(define v1 (pos 1 1 1))
(define v2 (pos 2 3 2))
(define cyl
(time
(with-color (rgba "lightblue")
;; WOO 65536 TRIANGLES YEAH
(move-z (cylinder origin (dir 1/4 1/4 1/2) #:segments 16384)
1/2))))
(define pict
(time
(combine (sphere v1 0.2)
(sphere v2 0.2)
(transform cyl (point-at v1 v2 #:normalize? #f)))))
(time (pict3d->bitmap pict))
(time (pict3d->bitmap pict))
On my computer, creating `cyl` takes 5 seconds. Transforming it takes no
measurable time. Rendering it the first time takes 1.5 seconds, and
rendering it the second time takes 11 milliseconds. (Cylinders are
frozen, so they'll always render faster the second time.)
To stretch the cylinder between two points, the program
1. Creates the cylinder so that its bottom is at the origin and its
top is at (pos 0 0 1).
2. Uses `point-at` without normalization to move the origin to `v1` and
move (pos 0 0 1) to `v2`.
Based on this and your other feedback, I think Pict3D needs
* A `rotate-around` function that rotates a shape around a given point
(with the default being the center of its bounding box).
* A note in the docs for `rotate` et al that explain they rotate around
the origin.
* A "How Do I" section that includes things like the above example.
* Notes in the docs about performance characteristics of different
functions (e.g. that `cylinder` and `cone` return frozen Pict3Ds, and
what that means you can expect from them).
I expect performance characteristics to change, though, so that last one
might be late coming.
Neil ⊥
Actually, now that I think about it something like this would be really helpful, and probably make more sense:
Maybe a version of point-at that would let you specify which pos’s should match up where?
(my-point-at v1 v2 v3 v4 #:normalize? #f) ; maps v1 to v3 and v2 to v4
(my-point-at (pos 0 0 0) (pos 1 0 0) v1 v2 #:normalize? #f) ; maps the origin to v1, and (pos 1 0 0) to v2.
(my-point-at (pos 0 0 0) (pos 0 0 1) v1 v2) ; equivalent to (point-at v1 v2)
(my-point-at v1 dv1 v2 dv2) ; equivalent to (my-point-at v1 (pos+ v1 dv1) v2 (pos+ v2 dv2))
If you want to control *rotation* around the v1-v2 and v3-v4 axes,
you'll need to add a couple of arguments corresponding to the #:up and
#:angle arguments of `point-at`. I don't know how you'd pass those along
to the two `point-at` calls or how they'd interact. That doesn't mean
something reasonable is impossible, though.
To understand `point-at` better, run the following program and play with
the #:up and #:angle arguments. Most of the code retesselates the
vertices of an octahedron to try to get a uniform distribution of
directions. The important stuff is at the end.
#lang racket
(require pict3d)
(define (retesselate dvs)
(match-define (list dv0 dv1 dv2) dvs)
(define dv01 (dir-normalize (dir-scale (dir+ dv0 dv1) 0.5)))
(define dv12 (dir-normalize (dir-scale (dir+ dv1 dv2) 0.5)))
(define dv20 (dir-normalize (dir-scale (dir+ dv2 dv0) 0.5)))
(list (list dv0 dv01 dv20)
(list dv1 dv12 dv01)
(list dv2 dv20 dv12)
(list dv01 dv12 dv20)))
(define octahedron-dirs
(list (list +x +y +z)
(list +y -x +z)
(list -x -y +z)
(list -y +x +z)
(list +y +x -z)
(list -x +y -z)
(list -y -x -z)
(list +x -y -z)))
(define dvs
(remove-duplicates
(append*
(for/fold ([dvss octahedron-dirs]) ([_ (in-range 3)])
(append* (map retesselate dvss))))))
(combine
(for/list ([dv (in-list dvs)])
(basis 'camera (point-at (pos+ origin dv 2) origin
#:up +z #:angle 0))))
With the default #:up +z, there's a singularity at +z and another at -z,
where `point-at` isn't smooth. (Generally, with #:angle 0, the red x
axes point to their counterclockwise neighbors. At a pole, the only
counterclockwise neighbor is the pole itself.) Using #:up +x, the
singularities are at +x and -x. There's nothing we can do about this:
we're looking straight down the barrel of the Hairy Ball Theorem [2].
The `point-at` function must have at least one cowlick.
All this complexity comes from trying to make a rigid transformation
that's in some way reasonable when specified using only 5 degrees of
freedom: a position and a normalized direction. The result is mostly
intuitive, and works well enough.
Neil ⊥
[1] http://en.wikipedia.org/wiki/Shear_mapping
[2] http://en.wikipedia.org/wiki/Hairy_ball_theorem
On 03/15/2015 07:38 PM, Alexander D. Knauth wrote:
> Does the attached file look like a good implementation of my-point-at, or would it do things I wouldn’t expect for things not on the line between the two points, or ? I’m asking because I don’t think I completely understand point-at and what it does.
>
>
>
>
And if it does work like that, then would it make sense for you to extend point-at so that when it takes 4 arguments it does this?
Or should it be a separate function?
Or, would it make sense to provide a function like this?
(define (from-to t1 t2) ; It would probably need a better name, but I think it could be very useful and intuitive
(affine-compose t2 (affine-inverse t1)))
So then my-point-at could be defined as
(define (my-point-at v1 v2 v3 v4)
(from-to (point-at v1 v2) (point-at v3 v4)))
And instances of
(pin
(combine pict1 (basis ‘b1 t1)) ‘(b1)
(combine pict2 (basis ‘b2 t2)) ‘(b2))
could be replaced with
(combine pict1 (transform pict2 (from-to t2 t1)))
Would that make sense?
A `from-to` function is a nice idea. In general, it says "change the
basis from t1 to t2". I'll add it to my list.
Neil ⊥
(: relocate (case-> (-> Affine Affine Affine)
(-> Pict3D Affine Affine Pict3D)))
;; Moves `pict` from coordinate system `t1` to coordinate system
;; `t2`, or returns a transform that does so
(define relocate
(case-lambda
[(t1 t2) (affine-compose t2 (affine-inverse t1))]
[(pict t1 t2) (transform pict (relocate t1 t2))]))
And this derived function, which is usually called a "change of basis"
(though that term is overloaded, overused and ambiguous):
(: local-transform (case-> (-> Affine Affine Affine)
(-> Pict3D Affine Affine Pict3D)))
;; Applies `t` within coordinate system `local-t`
(define local-transform
(case-lambda
[(t local-t) (affine-compose local-t (relocate local-t t))]
[(pict t local-t) (transform pict (local-transform t local-t))]))
The `local-transform` function makes it easy to write something like
`rotate/center`.
Neil ⊥
Pict3D is a really awesome project and fun to use!
I have a question about the architecture and performance of Pict3D for scenes with millions of triangle. I want to visualize a robot having about 50 movable joints and each link of the robot is made from some 10^5 triangles (which works with no problem with 3D engines like Coin or OpenScenegraph). Has Pict3D to transfer all the triangles each time I want to render the robot with different joint positions or are they cached on the GPU and only the changed transformation matrices for each link have to be transferred? You mentioned that using "freeze" optimizes the transport (in my case I would freeze each link), but have freezed parts still to be transferred each time anew?
I made a simple test program which draws some cylinders with many segments. Making the assumption that each segment is made of two triangles, even for about 4*10^5 triangles each frame already takes about 70ms to render on my computer consuming 100% of a cpu core. On the same computer I can render the full robot at 30Hz moving all its joints taking only 20% of the cpu.
Did I miss something in making my example more performant or are there future plans for making pict3d faster?
Best,
Berthold
simple example:
#lang racket
(require pict3d)
(require pict3d/universe)
(define p(combine
(for/list ([i (in-range 100)])
(freeze (set-emitted
(cylinder (pos i 0 0) (pos (+ i 1/2) 1/2 1/2) #:segments 2000)
(emitted "lightgreen" 2))))))
(define (on-frame s n t)
(displayln (- t s))
t)
(define (on-draw s n t)
p)
(big-bang3d 0 #:on-frame on-frame #:on-draw on-draw)
> On 09 Mar 2015, at 03:53, Neil Toronto <neil.t...@gmail.com> wrote:
>
> Pict3D is finally ready for public consumption. You can install the package either in DrRacket using "File -> Install Package..." or from the command line using
>
> raco pkg install pict3d
>
> The GitHub page is here:
>
> https://github.com/ntoronto/pict3d
>
> All features are documented. The API is stable. I've verified that it runs on Racket 6.1.1 and the current development version. There are reports of it working on at least two Windows systems, two Mac OS X systems, and two Linux systems. It might not eat your graphics card.
>
> Current features:
>
> * Works in untyped and Typed Racket
>
> * Spheres, rectangles, triangles and quads with per-vertex attributes
>
> * A system of groups and affine (arbitrary parallel-line-preserving)
> transformations for sticking Pict3Ds together
>
> * Ray-against-scene and line-against-scene collision detection
>
> * Render targets: an interactive debugging view in DrRacket's REPL,
> `pict3d->bitmap` and `pict3d-canvas%`
>
> * big-bang3d (currently without networking)
>
> ------------------
>
> Now we get to crowdsourcing.
>
> I've reached the limits of what I can do without working on Pict3D full-time. In particular, I don't know whether the API is awkward or actually quite nice for the uses it'll be put to. I've drawn on some personal game design and coding experience, but that only goes so far.
>
> This is where you come in.
>
> If you've ever had the slightest hankering to do some real 3D but avoided it because of the pain that usually goes with it, try Pict3D. (If it fails to work, please submit a bug report on the GitHub page.) Got a visualization project? Try Pict3D. Want to make a game? Try Pict3D's version of Big Bang. Want to just fool around in 3D space for a bit? Try Pict3D, and report back on how it goes.
>
> Anything is fair game for criticism, praise or suggestions: groups and pinning, distinguishing direction and position vectors, presence or lack of shapes you need, how to allow shader programming, file formats that would be nice to import models from, information that would be nice to have in the interactive debugging view, how to add texturing to the API, and even the tone of the documentation.
>
> I'm looking forward to your reports, all my little minions. Wait, did I say that out loud?
>
> Neil ⊥
> ____________________
> Racket Users list:
> http://lists.racket-lang.org/users
--
-----------------------------------------------------------------------
Berthold Bäuml -- Head of Autonomous Learning Robots Lab
DLR, Robotics and Mechatronics Center (RMC)
Münchner Str. 20, D-82234 Wessling
Phone +49 8153 282489
http://www.robotic.de/Berthold.Baeuml
Currently, the vertex data for frozen shapes is transferred every frame.
The 3D engine is saturating your AGP bus.
I have plans to add either `freeze/send` or a `#:send?` argument to
`freeze`, which will send the vertex data once to reside on the GPU,
until the frozen Pict3D is garbage-collected.
Since you seem to know what you're talking about, I have a couple of
questions for you about this.
1. I've also considered having a data size threshold after which the
data will be sent once. I'm not sure how to set this threshold, nor
even whether it's a good idea. Any thoughts on automating the
decision on what gets sent to the GPU?
2. Attribute-updating functions such as `set-color` could be much more
expensive for GPU-resident Pict3Ds. Here are a few options for
dealing with it:
A. Just update the attribute and re-freeze. If the old Pict3D hangs
around on the GPU too, let the user deal with memory issues.
B. Update the attribute and re-freeze, and warn the user somehow.
C. Update the attribute and *don't* re-freeze.
What do you think would be least annoying overall?
I also have plans to add engine support for indexed triangle meshes to
reduce vertex data duplication. I haven't settled on the front-end API
for this, but I'll probably introduce a `smooth` combiner and a `mesh`
constructor.
I'll bump these up on my to-do list. Thanks for trying it out!
Neil ⊥
The year 1861 sent you a telegraph: they want their insult back. :p
Anyway... there are things missing from Pict3D not because they're
difficult, but because I haven't decided on a design. This one's a prime
example. The 3D engine already uses vertex buffer objects (i.e. current
tech) to stream vertex data. It would be easy to give each frozen Pict3D
its own, and send to and evict from the GPU based on some caching
policy. But I haven't researched good policies yet.
Texture and normal maps are another prime example. From a UI
perspective, texture coordinates have got to be the worst way to
associate sub-geometry details with a shape. I don't know what Pict3D
will have instead. I'm half-inclined to make a mini-DSL that compiles to
vertex and fragment shaders and let you figure it out. :D
Neil ⊥
It has been brought to my attention that AGP buses have gone extinct,
and you were probably talking about that.
That's what I get for not putting computers together anymore. What's
next, machines without 3.5-inch floppy drives? What'll I do with all
these Turbo C++ and Windows NT install disks?
>
> I have plans to add either `freeze/send` or a `#:send?` argument to `freeze`, which will send the vertex data once to reside on the GPU, until the frozen Pict3D is garbage-collected.
My first intuition was to keep the data on the GPU and the "current" pict3d synchronized. Each time a pict3d should be rendered all frozen sub-picts which are not already on the GPU are sent and all data which is not part of the current pict3d is deleted from the GPU (or can be kept there for caching).
> Since you seem to know what you're talking about, I have a couple of questions for you about this.
I am no expert at all but only a user with large models to work with ...
> 1. I've also considered having a data size threshold after which the
> data will be sent once. I'm not sure how to set this threshold, nor
> even whether it's a good idea. Any thoughts on automating the
> decision on what gets sent to the GPU?
You talk only about the frozen data? Then I would do it the way described above.
> 2. Attribute-updating functions such as `set-color` could be much more
> expensive for GPU-resident Pict3Ds. Here are a few options for
> dealing with it:
I am not sure but don't modern OpenGL versions have the possibility to change the color without having to retransfer the complete geometry data anew (kind of a color array and a index array)?
> A. Just update the attribute and re-freeze. If the old Pict3D hangs
> around on the GPU too, let the user deal with memory issues.
>
> B. Update the attribute and re-freeze, and warn the user somehow.
>
> C. Update the attribute and *don't* re-freeze.
>
> What do you think would be least annoying overall?
Option A in combination with he "synchronization" scheme above would be my favorite: completely transparent to the user. Or I am missing something?
> I also have plans to add engine support for indexed triangle meshes to reduce vertex data duplication. I haven't settled on the front-end API for this, but I'll probably introduce a `smooth` combiner and a `mesh` constructor.
A 'mesh' constructor looks obvious to me. But what do you mean by a 'smooth' combinator? Should meshes combine differently than other primitives?
Best,
Berthold
--
-----------------------------------------------------------------------
Berthold Bäuml -- Head of Autonomous Learning Robots Lab
DLR, Robotics and Mechatronics Center (RMC)
Münchner Str. 20, D-82234 Wessling
Phone +49 8153 282489
http://www.robotic.de/Berthold.Baeuml