[racket] Crowdsourcing Pict3D's design

From: Neil Toronto (neil.toronto at gmail.com)
Date: Mon Mar 16 17:37:01 EDT 2015

I think I like this incarnation:

(: 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 ⊥

On 03/16/2015 03:20 PM, Alexander D. Knauth wrote:
> Would it work so that you could all instances of:
> (pin
>   (combine pict1 (basis 'b1 (point-at v1 v2))) ‘(b1)
>   (combine pict2 (basis ‘b2 (point-at v3 v4))) ‘(b2))
> Could be replaced with:
> (combine
>   pict1
>   (transform pict2 (my-point-at v1 v2 v3 v4)))
> I tried doing that with my game, and nothing weird happened, but am I missing something?
>
> 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?
>
>
> On Mar 16, 2015, at 7:12 AM, Neil Toronto <neil.toronto at gmail.com> wrote:
>
>> It should work fine for what you want to do with it. (It's exactly what I thought of when I read your spec for it.) The only possible thing that can go wrong with affine transformations is unwanted shear [1], and `point-at` never produces a transformation that shears. Aside from the z-axis stretch, it's all rigid.
>>
>> 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.
>>>
>>>
>>>
>>>
>>> On Mar 15, 2015, at 6:29 PM, Alexander D. Knauth <alexander at knauth.org> wrote:
>>>
>>>> Thanks!
>>>>
>>>> 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))
>>>>
>>>> On Mar 15, 2015, at 5:58 PM, Neil Toronto <neil.toronto at gmail.com> wrote:
>>>>
>>>>> 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 ⊥
>>>>>
>>>>
>>>>
>>>> ____________________
>>>>   Racket Users list:
>>>>   http://lists.racket-lang.org/users
>>>
>>
>


Posted on the users mailing list.