home back

Basic Objects

This chapter covers the fundamental building blocks of JMathAnim animations. All drawable objects inherit from the MathObject class (or implement the Drawable interface), which provides common methods for transformations like scale, rotate, and shift.

How to read this chapter: it's a catalog, not a novel. Read the Vec, Point, Shape and LatexMathObject sections first (you will use them in every single animation), skim the rest, and come back when you need an arrow or a grid. And remember: almost every object here has a ready-made snippet in the editor: press Alt+S, type the first letters of the object name, and the code writes itself. If a snippet inserts a DSL block, Ctrl+Space inside it shows all the available parameters.

Table of Contents

The Vec Class

The Vec class represents a 2D vector (with an additional z-coordinate for future 3D support). It's not at object can be added to scene or drawn, but it's the fundamental class for representing coordinates in the math view and defining parameters in most objects.

Creating Vectors

def v = Vec.to(4, 5)        // Creates vector (4, 5)
def w = Vec.to(2, -1)       // Creates vector (2, -1)
def origin = Vec.origin()   // Creates vector (0, 0)
def random = Vec.random()   // Creates a vector in a random screen location

Accessing Components

def x = v.x                 // Get x-coordinate
def y = v.y                 // Get y-coordinate
def norm = v.norm()         // Get Euclidean norm (length)

Vector Operations

JMathAnim provides two styles of vector operations:

  1. Immutable operations - Return a new vector, leaving the original unchanged:

    def sum = v.add(w)             // Returns v + w (new vector)
    def scaled = v.copy().scale(3) // Returns 3v (new vector, v unchanged)
    def diff = v.to(w)             // Returns w - v (new vector)
    

  2. In-place operations - Modify the original vector:

    v.scale(3)                  // Multiplies v by 3 (modifies v)
    v.shift(w)                  // Adds w to v (modifies v)
    v.rotate(45 * DEGREES)      // Rotates v by 45° (modifies v)
    

Additional Methods

def dot = v.dot(w)          // Dot product of v and w
def angle = v.getAngle()    // Angle in radians (0 to 2π)

Note: The DEGREES constant converts degrees to radians. Use it for all angle operations: angle * DEGREES.


The Point Class

The Point class is the most basic MathObject, representing a single point in space. The main difference between a Point and a Vec class is that the Vec is a lightweight object to hold coordinates, while Point is a MathObject that can be drawn on screen.

Creating Points

def p = Point.at(1, 1)      // Point at coordinates (1, 1)
def q = Point.at(v)         //Point at coordinates given by vector v
def r = Point.origin()      // Point at (0, 0)
def s = Point.random()      // Point at random screen location

Accessing Coordinates

Points contain a Vec object storing their position:

def v = p.getVec()          // Get position vector
def x = p.getVec().x        // Get x-coordinate
def d = p.norm()            // Distance from origin
def AB = A.to(B)            // Vector from point A to point B

Point Styles

Points can be displayed in various styles using the DotStyle enum:

def A = Point.at(-1.75, 0).dotStyle(DotStyle.CIRCLE)
def B = Point.at(-1.25, 0).dotStyle(DotStyle.CROSS)
def C = Point.at(-0.75, 0).dotStyle(DotStyle.PLUS)
def D = Point.at(-0.25, 0).dotStyle(DotStyle.RING)
def E = Point.at(0.25, 0).dotStyle(DotStyle.TRIANGLE_UP_HOLLOW)
def F = Point.at(0.75, 0).dotStyle(DotStyle.TRIANGLE_DOWN_HOLLOW)
def G = Point.at(1.25, 0).dotStyle(DotStyle.TRIANGLE_UP_FILLED)
def H = Point.at(1.75, 0).dotStyle(DotStyle.TRIANGLE_DOWN_FILLED)

scene.add(A, B, C, D, E, F, G, H)

// Add reference axis
scene.add(
    Line.XAxis()
        .thickness(2)
        .drawColor("gray")
        .layer(-1)  // Draw behind points
)

dots

I showed you this 'manual code', which describes each point individually, to demonstrate the various style names. Of course, a similar image could also be created using loops:

def x0=-1.75 //Initial value 0f x0
for (style in DotStyle) { //Iterate over DotStyle values
    def P=Point.at(x0,0).dotStyle(style)
    scene.add(P)
    x0+=0.5 //Add 0.5 to x0
}

//The axis code is the same...

Note: Points require higher thickness values (typically 20-40) to be clearly visible, as the thickness represents the point's diameter.

You can also create it using the DSL:

def A = point(at: [-1.75, 0], style: [dotStyle: "cross", color: "blue", thickness: 30])

The Shape Class

The Shape class is one of the most important classes in JMathAnim. It represents curves (closed or open) and provides extensive functionality for creating and manipulating 2D shapes.

Basic Shape Constructors

JMathAnim provides convenient static methods for common shapes:

// Circle with radius 1, centered at origin
def circ = Shape.circle()

// Unit square with lower-left corner at origin
def sq = Shape.square()

// Regular pentagon with first two vertices at (0,0) and (1,0)
def reg = Shape.regularPolygon(5)

// Triangle from specified vertices
// Accepts Vec, Point, or any Coordinates object
def poly = Shape.polygon(
    Vec.to(0.25, -0.5),
    Vec.to(1.25, -0.5),
    Vec.to(0.25, 0.5)
)

// Axis-aligned rectangle
// Parameters: lower-left corner, upper-right corner
def rect = Shape.rectangle(Vec.to(1, 2), Vec.to(3, 5))

// Line segment between two points
def seg = Shape.segment(Vec.to(-1, -1), Vec.to(-0.5, 1.5))

// Circular arc
// Parameters: arc length in radians
def arc = Shape.arc(PI / 4)

scene.add(circ, sq, reg, poly, rect, seg, arc)

basicShapes

Creating Shapes with LOGO Commands

Shapes can also be created using LOGO turtle graphics commands, which is useful for complex or iterative patterns:

// CLO (or CLOSE) is a non-standard command to close the path
def logoCmd = "REPEAT 12 [FD .5 RT 150 FD .5 LT 120] CLO"
def logoShape = Shape.logo(logoCmd)

scene.add(
    logoShape
        .center()
        .style("solidblue")
)

Example of Shape created with LOGO commands

Supported LOGO Commands

Command Aliases Description
FORWARD n FD n Move forward n units
BACKWARD n BK n Move backward n units
RIGHT angle RT angle Turn right by angle degrees
LEFT angle LT angle Turn left by angle degrees
REPEAT n [commands] Execute commands n times
PENUP PU Lift pen (move without drawing)
PENDOWN PD Lower pen (resume drawing)
LARC / RARC angle radius Left/Right arc (JMathAnim extension)
CLOSE CLO Close the path (JMathAnim extension)

Commands are case-insensitive. Full reference available at Logo Commands Reference.

DSL Syntax

Since core version 1.3.5, the script interpreter admits several commands defined in DSL syntax to build objects in a more accessible way:

def s = shape(
    type: "circle",
    segments: 30,
    style:[thickness: 30, drawcolor: 'maroon',fillcolor: 'deeppink1'],
    addToScene: true
)

Is equivalent to

def s=Shape.circle(30).thickness(30).drawColor('maroon').fillColor('deeppink1')
scene.add(s)

The gui editor has built autocomplete for most DSL commands as well as a snippet sidebar to speed code generation. You can see a cheatsheet of DSL commands here

Working with Shape Paths

Each Shape contains a JMPath object that manages its path. Points can be accessed using zero-based circular indexing:

def pentagon = Shape.regularPolygon(5) //Has points with indices 0, 1, 2, 3, 4

def p0 = pentagon.getPoint(0)    // First vertex
def p1 = pentagon.getPoint(1)    // Second vertex
def p5 = pentagon.getPoint(5)    // Wraps around to first vertex (same as p0)

Important: Shape points use circular array indexing, so getPoint(n) and getPoint(n + vertexCount) return the same point.

Shape Centers

Shapes provide two methods for finding their center:

def bbox_center = shape.getCenter()     // Center of bounding box (returns a Vec object)
def geometric_center = shape.getCentroid()  // Average of all vertices

Note: For regular polygons, .getCentroid() returns the true geometric center, while .getCenter() returns the center of the bounding box, which may differ for rotated shapes.


The LatexMathObject Class

The LatexMathObject class renders mathematical expressions using LaTeX, allowing you to display formulas and text with professional typesetting.

Creating LaTeX Objects

def text = LatexMathObject.make("This is a \\LaTeX equation \$e^{i\\pi}+1=0\$")
scene.add(text)

LaTeX 1

You can also create it using the DSL, with the latex(...) command (a plain, non-LaTeX text object is available through text(...)):

def text = latex(text: r"This is a \LaTeX equation $e^{i\pi}+1=0$")

Handling Special Characters

LaTeX uses $ and \ as special characters. In Groovy, these must be escaped:

// INCORRECT - Groovy will interpret $ and \ as escape sequences
LatexMathObject.make("$e^{i\pi}+1=0$")  // ERROR!

// CORRECT - Escape the special characters
LatexMathObject.make("\$e^{i\\pi}+1=0\$")  // Works!

// BETTER - Use raw strings (prefix with 'r')
LatexMathObject.make(r"$e^{i\pi}+1=0$")  // Easiest!

Tip: Raw strings (prefixed with r) ignore escape sequences. Use either single or double quotes:

LatexMathObject.make(r"$e^{i\pi}+1=0$")  // Both work
LatexMathObject.make(r'$e^{i\pi}+1=0$')  // identically

Let the editor do it: if writing LaTeX by hand is not your idea of fun, place the cursor inside the string and open the LaTeX Editor (Tools -> LaTeX Editor..., or Ctrl+Shift+L). You get a live preview while you type, and the Insert Code button writes the (correctly escaped) string back into your script for you.

Also, you can use triple quotes to enable multiline strings:

def text = LatexMathObject.make(r'''
$$
ax^2+bx+c=0
$$
''')
scene.add(text)

Compilation Modes

JMathAnim offers two LaTeX compilation methods:

  1. Default (JLaTeXMath) - Fast, built-in renderer for most formulas:

    def formula = LatexMathObject.make(r'$e^{i\pi}+1=0$')
    

  2. External LaTeX - Uses system LaTeX for advanced features:

    def formula = LatexMathObject.make(
        r'$e^{i\pi}+1=0$',
        CompileMode.CompileFile
    )
    

The external compiler:

  • Requires a LaTeX distribution installed on your system
  • Compiles to .dvi, converts to .svg, imports as MultiShapeObject
  • Caches .svg files for reuse in subsequent runs
  • Supports commands not available in JLaTeXMath (e.g., \begin{verbatim}), but in most cases JLaTexMath will be enough.

Recommendation: Use the default mode (JLaTeXMath) unless you need advanced LaTeX features.


The Line Class

The Line class represents infinite mathematical lines. In practice, it draws the visible portion within the current view.

Creating Lines

def A = Point.at(1, 1)
def B = Point.at(0, 1)
def v = Vec.to(1, 0.2)

// Line through points A and B
def line1 = Line.make(A, B)
    .drawColor("red")
    .thickness(20)

// Line through A in direction v
def line2 = Line.make(A, A.add(v))
    .drawColor("blue")
    .thickness(10)

// Predefined axes
def line3 = Line.XAxis().drawColor("darkorange")        // y = 0
def line4 = Line.YAxis().drawColor("darkmagenta")       // x = 0
def line5 = Line.XYBisector().drawColor("darkgreen")    // y = x

scene.add(line1, line2, line3, line4, line5)
play.shift(5, -1, -1.5, A)  // Animate point A

Line01

Note: Lines use direction vectors only at construction time. Moving a point after creating the line won't update the line's direction (unlike constraints, which are covered in advanced topics).

The Ray Class

Similar to Line, the Ray class represents half-infinite rays with syntax identical to Line constructors.


The Axes Class

The Axes class creates Cartesian coordinate axes with customizable ticks and labels. It's essentially a container managing multiple Line, Shape, and LatexMathObject instances.

Creating Axes

def axes = Axes.make()

// Add primary ticks
axes.generatePrimaryXTicks(-2, 2, 0.5)  // x: -2, -1.5, -1, ..., 2
axes.generatePrimaryYTicks(-2, 2, 0.5)  // y: -2, -1.5, -1, ..., 2

// Add custom ticks
axes.addXTicksLegend(0.75, TickType.PRIMARY)  // Tick at x = 0.75
axes.addYTicksLegend(r"$\pi/4$", PI / 4, TickType.PRIMARY)  // Custom label

scene.add(
    axes,
    Shape.circle()
        .scale(0.5)
        .drawColor("darkblue")
)

axes01

Note: By default, axes are created without ticks. Use generatePrimaryXTicks and generatePrimaryYTicks to add them.

Using the DSL format is even easier:

def axes = axes(
    range:[-2, 2],
    secondary: [maxwidth:0],
    ticksx: [0.75],
    ticksy: [[at: PI/4, label: '$\\pi/4$', type:"sec"]]
)

The CartesianGrid Class

Creates a grid with primary and secondary divisions, useful for reference and measurements.

// Grid centered at origin with subdivisions
def grid = CartesianGrid.make(
    0, 0,       // Center point (usually origin)
    2,          // Horizontal subdivisions for secondary grid
    2           // Vertical subdivisions for secondary grid
)

scene.add(
    grid,
    Shape.circle()
        .drawColor("blue")
        .fillColor("blue")
        .fillAlpha(0.5)
)

Cartesian grid example

Styling: Grids use the gridPrimaryDefault and gridSecondaryDefault styles defined in your config files. See the Styling chapter for customization details.

You can also use the DSL version

def grid = grid(
    center:[0, 0], //Optional
    divisions: 2 //equivalent to divisions:[2,2]
)

Looking for arrows, connectors or delimiters? Those objects (which point at, join or bracket other objects) now live in their own chapter: Arrows, connectors and delimiters. Function graphs and other calculus-flavored objects are in Calculus objects.


Importing Images

JMathAnim supports both bitmap and vector images. Images are loaded from the resources/images/ folder by default (configurable in styling settings).

Bitmap Images

Import any bitmap format supported by JavaFX (PNG, JPG, etc.):

def img = JMImage.make("euler.jpg")
    .center()
    .rotate(-5 * DEGREES)

def text = LatexMathObject.make("All hail the great Euler!")
    .stack()
    .withGaps(0.1, 0.1)
    .withDestinyAnchor(AnchorType.LOWER)
    .toObject(img)

play.run(
    Commands.moveIn(2, ScreenAnchor.LEFT, img),
    ShowCreation.make(2, text)
)
scene.waitSeconds(2)

image01

SVG Images

Import and animate SVG vector graphics:

def svg = SVGImport.load("donaldKnuth.svg")
svg.setHeight(2).center()

play.showCreation(svg)  // Animates drawing the SVG
scene.waitSeconds(2)

svgCreation

File Location: Place SVG files in <project_root>/resources/images/

SVG Import Details: - Creates a MultiShape object containing multiple Shape instances - Each SVG element becomes a separate Shape with a JMPath - Supports standard transformations and animations - Limitation: Some elements (e.g., gradients) may not import with 100% accuracy


The MathObjectGroup Class

MathObjectGroup manages collections of MathObject instances as a single entity, enabling batch operations and layouts.

Creating Groups

def square = Shape.square()
def triangle = Shape.regularPolygon(3)
def circle = Shape.circle()

def group = MathObjectGroup.make(square, triangle, circle)

Group Operations

Groups support all standard MathObject operations:

group.shift(1, 1)              // Move entire group
group.rotate(45 * DEGREES)      // Rotate all members
group.scale(2)                  // Scale all members
group.setFillColor("cadetblue") // Apply to all members

Adding Groups to Scene

Adding a group is equivalent to adding all its members:

scene.add(group)  // Same as: scene.add(square, triangle, circle)

Note: The MathObjectGroup is a logical group. It does not represent an object by itself and cannot be added to the scene. It is just a way to tell the interpreter "work with this group of objects".

Why Use Groups?

Groups excel at: - Batch transformations: Transform multiple objects together - Layout management: Arrange objects automatically (see TransformingObjects chapter) - Animation coordination: Animate multiple objects as one unit

Example using layouts:

def group = MathObjectGroup.make(square, triangle, circle)
group.setLayout(LayoutType.LOWER, 0.1, 0.1)  // Arrange vertically
scene.add(group) //The same as scene.add(square, triangle, circle)

The Rect Class

The Rect class represents axis-aligned bounding boxes. While not directly drawable, it's essential for positioning and collision detection or understanding how stacking and positioning works.

Getting Bounding Boxes

Every MathObject (and anything implementing Boxable) has a bounding box:

def ellipse = Shape.circle()
    .scale(1, 0.5)
    .rotate(45 * DEGREES)
    .style("solidorange")

def bbox = ellipse.getBoundingBox()  // Returns Rect
def rect = Shape.rectangle(bbox).drawColor("blue")

scene.add(ellipse, rect)

Bounding box example

Rect Properties and Methods

// Corner coordinates
def xMin = bbox.xmin     // Left edge
def xMax = bbox.xmax     // Right edge  
def yMin = bbox.ymin     // Bottom edge
def yMax = bbox.ymax     // Top edge

// Dimensions
def width = bbox.getWidth()  //...or bbox.width
def height = bbox.getHeight() //...or bbox.height

// Corner points
def center = bbox.getCenter() //or bbox.center
def upperLeft = bbox.getUpperLeft() // ... or bbox.upperLeft
def upperRight = bbox.getUpperRight() // ... bbox.upperRight
def lowerLeft = bbox.getLowerLeft()  // ... or bbox.lowerLeft
def lowerRight = bbox.getLowerRight() //... or bbox.lowerRight

// Relative positioning
def point = bbox.getReltoAbsCoordinates(0.25, 0.75)  // 25% across, 75% up
def centerAlt = bbox.getReltoAbsCoordinates(0.5, 0.5)  // Same as getCenter()

// Transformations
def expanded = bbox.addGap(0.1, 0.2)  // Add 0.1 horizontal, 0.2 vertical gap
bbox.centerAt(destination)  // Move center to destination
def rotated = bbox.getRotatedRect(45 * DEGREES)  // Smallest rect containing rotated bbox

Working with the Math View

The math view (visible screen area) is represented as a Rect:

def mathView = scene.getMathView()

// Create points at key screen positions
def center = Point.at(mathView.getCenter())
    .dotStyle(DotStyle.PLUS)

def corners = [
    Point.at(mathView.getUpperLeft()),
    Point.at(mathView.getUpperRight()),
    Point.at(mathView.getLowerLeft()),
    Point.at(mathView.getLowerRight())
]

// Points at 25% and 75% positions
def Q1 = Point.at(mathView.getReltoAbsCoordinates(0.25, 0.25))
    .drawColor("red")
def Q2 = Point.at(mathView.getReltoAbsCoordinates(0.75, 0.25))
    .drawColor("green")
def Q3 = Point.at(mathView.getReltoAbsCoordinates(0.75, 0.75))
    .drawColor("yellow")
def Q4 = Point.at(mathView.getReltoAbsCoordinates(0.25, 0.75))
    .drawColor("blue")

scene.add(center, *corners, Q1, Q2, Q3, Q4)
scene.add(
    Shape.rectangle(mathView)
        .scale(0.9)  // 90% of screen size
)

Math view example

Default Math View Coordinates

At initialization (16:9 aspect ratio): - Center: (0, 0) - X range: -2 to 2 - Y range: -1.125 to 1.125

Note: Y boundaries adjust automatically based on aspect ratio while maintaining the center and X boundaries.


Next Steps

Now that you understand the basic objects, explore: - Styling: Customize colors, thickness, and visual properties - Transforming Objects: Advanced positioning, scaling, and animation - Adding labels: Put names on vertices, sides and braces

home back