Cameras
A Camera object in JMathAnim is responsible for converting mathematical coordinates into screen coordinates. There are two predefined cameras built into JMathAnim: the default camera, accessible through the public variable camera or the scene.getCamera() method in your Scene2D class, and the fixed camera, accessible through the public variable fixedCamera or the scene.getFixedCamera() method.
All MathObjects have an associated Camera object that manages the conversion from mathematical coordinates to screen coordinates. Mathematical coordinates are the usual coordinates in the plane: the origin is at (0,0), and the y-coordinate increases upwards. Screen coordinates determine where the actual rendering takes place (window, movie frame, or generated image). They behave differently; for instance, the point (0,0) is located at the lower-left corner of the window, and the y-coordinate decreases upwards. However, you do not need to worry about this distinction, as this is precisely what the Camera class handles. From now on, we will always refer to mathematical coordinates.
The default camera object, as seen previously, is centered at (0,0) with the x-range from −2 to 2. The y-range is computed according to the aspect ratio of the screen. For example, if you are generating a 16:9 video, the y-range will be from −1.125 to 1.125. In essence, a Camera maps its mathematical range to the screen or movie frame. Any MathObject outside the camera range will be outside the visible area and may be partially rendered or not rendered at all.
As mentioned earlier, Camera class objects can be shifted and uniformly scaled (but not rotated). The camera object is the default camera assigned to objects, whereas fixedCamera is intended for objects that should remain fixed relative to the screen. Consider the following example:
def group = MathObjectGroup.make()
def numBoxes = 16
// Create numbered squares
for (int n = 0; n < numBoxes; n++) {
def square = Shape.square()
.scale(0.25)
.fillColor("violet")
.fillAlpha(1 - (n + 1) / numBoxes)
.thickness(6)
def text = LatexMathObject.make("$n")
.stack().toObject(square)
.layer(1)
group.add(MathObjectGroup.make(square, text))
}
//Locate the boxes in the left side of the screen...
group.stack().toScreen(ScreenAnchor.LEFT)
//and set this layout
group.setLayout(LayoutType.RIGHT, .1)
//Create a title
def title = LatexMathObject.make(r'Hoy many boxes?')
//Locate the title in the upper part of the screen
title.stack()
.withGaps(.1, .1)
.toScreen(ScreenAnchor.UPPER)
scene.add(title, group)//Add everything
//Pan the camera 3 units to the right in 2 seconds
play.cameraShift(2, 3, 0)
You will see an animation where the camera pans over the created boxes, but the title moves as well.

If this behavior is not desired, one possible (but inelegant) solution is to update the title position at every frame instead of using the play.cameraShift method:
def anim = Commands.cameraShift(2, camera, Vec.to(3, 0))
anim.initialize()
while (!anim.processAnimation()) {
//Ensure to relocate the title before generating a new frame
title.stack()
.withGaps(.1, .1)
.toScreen(ScreenAnchor.UPPER)
scene.advanceFrame()
}
Although this approach works, it is not elegant and may lead to maintenance issues as the code becomes more complex. A simpler and more robust solution is to force the title to use the fixedCamera:
def title = LatexMathObject.make(r'Hoy many boxes?')
.setCamera(fixedCamera)
....
play.cameraShift(2, 3, 0) //This will affect only objects with normal camera

Updaters
The Camera class allows you to register CameraUpdater instances that automatically update the camera state on every frame. To do this, use the method camera.registerUpdater(<updater>). Below are a couple of examples.
FollowObject
The FollowObject camera updater keeps the camera centered on a given object.
Point P = Point.at(1.5, 0)
scene.add(
P.layer(1).drawColor('firebrick').thickness(60),
Trail.make(P).drawColor('steelblue').thickness(40),
LatexMathObject.make(r'Turning point'),
CartesianGrid.make(0, 0, 1, 1).drawColor('gray70')
)
camera.registerUpdater(FollowObject.make(P))
play.rotate(5, Vec.origin(), 2*PI, P)

AlwaysAdjusted camera updater
Ensures that the specified objects remain visible on screen. If no objects are provided, all objects added to the scene are considered.
camera.registerUpdater(AlwaysAdjusted.make(.1,.1, P, t))//gaps .1, .1. Adjusto to P and t
camera.registerUpdater(AlwaysAdjusted.make(P, t))//gaps 0, 0. Adjusto to P and t
camera.registerUpdater(AlwaysAdjusted.make())//gaps 0, 0. Adjusto to all objects
In the previous example, if we change the camera.registerUpdater line with
java
camera.registerUpdater(AlwaysAdjusted.make(.1,.1))//gaps .1, .1, adjust to all objects
you will obtain the following animation:

Note that the AlwaysAdjusted updater never zooms in. It only zooms out when necessary to ensure that the selected objects remain visible