On this page:
23.1 Move the object, not its window
23.2 Keep the object still and change the view
23.3 Use the complete three-file lesson
23.4 Use the ordinary software renderer
9.3

23 3D objects and cameras🔗ℹ

For the step-by-step route, start at Make a first 3D picture. The short recipes here are executable. The complete multi-file lesson remains under "scribblings/examples/" because the file relationships are part of that example.

The following shared setup creates one spatial object inside a view3d and places that view in an ordinary Scene:

(define brick
  (box3d 5/2 3/2 1
         #:id 'brick
         #:material (material3d #:color "cornflowerblue" #:shading 'flat)))
(define model
  (view3d (list brick)
          #:id 'model
          #:width 10 #:height 45/8
          #:camera (perspective-camera3d
                    #:position (vec3 3 2 5)
                    #:look-at origin3)
          #:render-mode 'opaque
          #:background "aliceblue"))
(define still
  (scene-add (make-scene) model))
> (spatial-visual? brick)

#t

> (view3d? model)

#t

23.1 Move the object, not its window🔗ℹ

A rooted spatial path names the object inside its owning view. Use a vec3 for the spatial destination:

(define object-translation
  (scene-play still
              (move3d-to '(model brick) (vec3 1 0 0))
              #:duration 2))
> (scene-duration object-translation)

2

t = 0 st = 0 st = 1 st = 1 st = 2 st = 2 s

Frames sampled directly from this example; times are in seconds.

Ordinary move-to with 'model and a vec2 moves the entire rectangular viewport in the surrounding 2D composition.

23.2 Keep the object still and change the view🔗ℹ

Camera requests target the owning view ID, not the spatial child path:

(define camera-motion
  (scene-play still
              (camera3d-orbit-by 'model #:azimuth (/ pi 2))
              #:duration 2))
> (scene-duration camera-motion)

2

t = 0 st = 0 st = 1/2 st = 1/2 st = 1 st = 1 st = 3/2 st = 3/2 st = 2 st = 2 s

Frames sampled directly from this example; times are in seconds.

The orbit changes the view’s 3D camera. It does not rotate brick.

23.3 Use the complete three-file lesson🔗ℹ

These are intentionally external programs: the second and third files require the first by a relative filename, so the file boundary is part of the example. Requiring them creates descriptions, not videos or GUI windows.

scribblings/examples/first-spatial-picture.rkt

#lang racket/base

(require animate animate/3d)

(provide brick model still picture-at)

 

(define brick

  (box3d 5/2 3/2 1

         #:id 'brick

         #:material (material3d #:color "cornflowerblue"

                                #:shading 'flat)))

 

(define viewpoint

  (perspective-camera3d #:position (vec3 3 2 5)

                        #:look-at origin3))

 

(define model

  (view3d (list brick)

          #:id 'model

          #:width 10

          #:height 45/8

          #:camera viewpoint

          #:render-mode 'opaque

          #:background "aliceblue"))

 

(define still

  (scene-add (make-scene) model))

 

(define (picture-at time)

  (scene-state->pict (scene-sample still time)

                     #:camera (scene-camera-at still time)))

 

;; Call (picture-at 0) in DrRacket to see the picture.

 

scribblings/examples/spatial-motion.rkt

#lang racket/base

(require (only-in racket/math pi) animate animate/3d

         "first-spatial-picture.rkt")

(provide object-motion camera-motion panel-motion translated-object

         make-lesson lesson)

 

(define object-motion

  (scene-play still

              (rotate3d-by '(model brick)

                           (axis-angle y-axis3 (/ pi 2)))

              #:duration 2))

 

(define translated-object

  (scene-play still

              (move3d-to '(model brick) (vec3 1 0 0))

              #:duration 2))

 

(define camera-motion

  (scene-play still

              (camera3d-orbit-by 'model #:azimuth (/ pi 2))

              #:duration 2))

 

(define panel-motion

  (scene-play still

              (move-to 'model (vec2 2 0))

              #:duration 2))

 

(define (make-lesson initial)

  (define turn

    (scene-play initial

                (rotate3d-by '(model brick)

                             (axis-angle y-axis3 (/ pi 2)))

                #:duration 2))

  (define read-turn (scene-wait turn 1))

  (define orbit

    (scene-play read-turn

                (camera3d-orbit-by 'model #:azimuth (/ pi 2))

                #:duration 2))

  (scene-wait orbit 1))

 

(define lesson (make-lesson still))

 

scribblings/examples/spatial-slides.rkt

#lang racket/base

(require animate animate/3d

         "first-spatial-picture.rkt" "spatial-motion.rkt")

(provide captioned-lesson)

 

(define heading

  (plain-text "Turn the object, then move the camera"

              #:id 'heading #:center (vec2 0 10/3)

              #:font-size 9/25 #:color "navy"))

 

(define box-label

  (follow-projected-spatial

   (plain-text "Box" #:id 'box-label

               #:font-size 1/4 #:color "navy")

   #:view 'model

   #:target '(brick)

   #:offset (vec2 14 16)))

 

(define labelled-move

  (scene-play (scene-add still heading box-label)

              (move3d-to '(model brick) (vec3 1 0 0))

              #:duration 2))

 

(define captioned-lesson

  (make-lesson labelled-move))

 

(require animate/slides animate/slides/scene)

(provide clip film)

 

(define card

  (slide #:id 'spatial-lesson #:layout 'title+figure

    [title "Two ways to change a 3D picture"]

    [figure (scene-content lesson)]

    [body "First turn the box. Then move the camera."]))

 

(define clip

  (build-slide card

    (beat 'look #:duration 1)

    (beat 'play #:duration 6

      (play-content 'figure #:to 'end))

    (beat 'read #:duration 2)))

 

(define film

  (storyboard

    (storyboard-shot 'demonstration clip)))

 

23.4 Use the ordinary software renderer🔗ℹ

The examples select filled faces with #:render-mode 'opaque; they do not select OpenGL. Start with the software path. OpenGL is an explicit backend choice, requires a suitable GUI process, and currently has its own one-worker restriction. It is not the same restriction as the slide geometry adapter, which supports the shared subprocess worker path.