Create copies the shapes vertices to replicate the shape in space. Copies of normals, colors (if defined) and texture coordinates (if defined) will also be made.
use 'DYNAMIC' if the shape's vertices
will be updated very frequently
Options object for a FigureElementPrimitive of a generic 3D shape. Extends OBJ_Generic3All and OBJ_FigurePrimitive


OBJ_GenericGL can be used for shape creation with custom shaders.
But for many custom shapes, only points and normals of the shape need to be defined, without needing to customize the shaders.
OBJ_Generic3 Provides the ability to create many custom shapes that don't need shader customization.
positions of vertices of shape
normals for each vertex
define a color for each vertex if the
shape will be more than just a single color. Otherwise use color if a
single color.
use to overlay a texture onto the shape's surfaces
figure.scene.setProjection({ style: 'orthographic' });
figure.scene.setCamera({ position: [1, 1, 2] });
figure.scene.setLight({ directional: [0.7, 0.5, 1] });
const [points, normals] = Fig.cube({ side: 0.8 });
figure.add({
make: 'generic3',
points,
normals,
texture: {
src: './flowers.jpeg',
coords: [
0, 0, 0.333, 0, 0.333, 0.5,
0, 0, 0.333, 0.5, 0, 0.5,
0.333, 0, 0.666, 0, 0.666, 0.5,
0.333, 0, 0.666, 0.5, 0.333, 0.5,
0.666, 0, 1, 0, 1, 0.5,
0.666, 0, 1, 0.5, 0.666, 0.5,
0, 0.5, 0.333, 1, 0, 1,
0, 0.5, 0.333, 0.5, 0.333, 1,
0.333, 0.5, 0.666, 1, 0.333, 1,
0.333, 0.5, 0.666, 0.5, 0.666, 1,
0.666, 0.5, 1, 1, 0.666, 1,
0.666, 0.5, 1, 0.5, 1, 1,
],
loadColor: [0, 0, 0, 0],
},
});
figure.scene.setProjection({ style: 'orthographic' });
figure.scene.setCamera({ position: [1, 1, 2] });
figure.scene.setLight({ directional: [0.7, 0.5, 1] });
const { sphere, polygon, revolve } = Fig;
const [spherePoints, sphereNormals] = sphere({ radius: 0.15 });
// The ring is a flattened doughnut
const [ringPoints, ringNormals] = revolve({
profile: polygon({
close: true,
sides: 20,
radius: 0.05,
center: [0, 0.3],
direction: -1,
transform: ['s', 0.1, 1, 1],
}),
normals: 'curve',
sides: 50,
transform: ['d', 0, 1, 0],
});
const a = figure.add({
make: 'generic3',
points: [...spherePoints, ...ringPoints],
normals: [...sphereNormals, ...ringNormals],
color: [1, 0, 0, 1],
transform: [['r', 0.15, 1, 0, 0], ['r', 0.3, 0, 1, 0]],
});
// Animate the shape to slowly rotate around the x and y axes
a.animations.new()
.custom({
callback: (t) => {
a.transform.updateRotation(t * 0.15);
a.transform.updateRotation(t * 0.3, null, 1);
},
duration: null,
})
.start();
To test examples, append them to the boilerplate
Sphere shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

By default, a sphere with its center at the origin will be created.
number of sides around sphere's half great circle
radius of sphere
flat normals will make light
shading across a face cone constant. curve will gradiate the shading. Use
curve to make a surface look more round with fewer number of sides.
center position of sphere
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
figure.add({
make: 'sphere',
radius: 0.5,
color: [1, 0, 0, 1],
});
figure.add({
make: 'sphere',
radius: 0.5,
normals: 'curve',
color: [1, 0, 0, 1],
});
figure.add({
make: 'sphere',
radius: 0.5,
sides: 30,
lines: true,
normals: 'curve',
color: [1, 0, 0, 1],
});
figure.add({
make: 'sphere',
radius: 0.1,
color: [1, 0, 0, 1],
center: [0.3, 0, 0],
normals: 'curve',
copy: [
{ along: 'rotation', num: 10, step: Math.PI * 2 / 10 },
],
transform: ['r', Math.PI / 2, 1, 0, 0],
});
To test examples, append them to the boilerplate
Cube shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

By default, a cube will be constructed around the origin, with the xyz axes being normal to the cube faces.
side length
center point ([0, 0])
points of cube
if true then points representing
the 12 edges of the cube will be returned. If false, then points
representing two triangles per face (12 triangles, 36 points) and an
associated normal for each point will be returned.
figure.add({
make: 'cube',
side: 0.5,
color: [1, 0, 0, 1],
});
figure.add({
make: 'cube',
side: 0.2,
color: [1, 0, 0, 1],
copy: [
{ along: 'x', num: 2, step: 0.22 },
{ along: 'y', num: 2, step: 0.22 },
{ along: 'z', num: 2, step: 0.22 },
],
});
figure.add({
make: 'cube',
side: 0.5,
lines: true,
color: [1, 0, 0, 1],
});
To test examples, append them to the boilerplate
Cylinder shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

By default, a cylinder along the x axis will be created.
number of cylinder sides
radius of cylinder
flat normals will make
shading (from light source) across a face cone constant.
curve will gradiate the shading. Use curve to make a surface look more
round with fewer number of sides.
line that can position and orient the cylinder. First point of line is cylinder base center, and second point is the top center.
length of the cylinder if line isn't
defined
true fills both ends of the cylinder.
false does not fill ends. 1 fills only the first end. 2 fills only the
the second end.
rotation of base - this is only noticable for
small numbers of sides (0)
points of cube
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
figure.add({
make: 'cylinder',
radius: 0.2,
length: 0.5,
sides: 20,
color: [1, 0, 0, 1],
});
figure.add({
make: 'cylinder',
radius: 0.2,
length: 0.5,
sides: 20,
normals: 'curve',
color: [1, 0, 0, 1],
});
figure.add({
make: 'cylinder',
radius: 0.2,
length: 0.2,
lines: true,
sides: 50,
ends: false,
color: [1, 0, 0, 1],
});
figure.add([
{
make: 'cylinder',
radius: 0.02,
line: [[0, 0, 0], [0.5, 0, 0]],
color: [1, 0, 0, 1],
},
{
make: 'cylinder',
radius: 0.02,
line: [[0, 0, 0], [0, 0.5, 0]],
color: [0, 1, 0, 1],
},
{
make: 'cylinder',
radius: 0.02,
line: [[0, 0, 0], [0, 0, 0.5]],
color: [0, 0, 1, 1],
},
]);
To test examples, append them to the boilerplate
3D Line options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

A 3D line is a cylinder with optional arrows on the end. Unlike a 2D line, the arrow profiles can only be simple triangles.
width of line
define to use arrows at one or both ends of the line
number of sides
flat normals will make light
shading across a line face constant. curve will gradiate the shading. Use
curve to make a surface look more round with fewer number of sides.
rotation of line around its axis - this is only noticable for small numbers of sides
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
figure.add({
make: 'line3',
p1: [0, 0, 0],
p2: [0, 1, 0],
color: [1, 0, 0, 1],
});
figure.add({
make: 'line3',
p1: [0, 0, 0],
p2: [0, 1, 0],
arrow: { ends: 'all', width: 0.1, length: 0.1 },
sides: 30,
width: 0.05,
color: [1, 0, 0, 1],
});
figure.add({
make: 'line3',
p1: [0, 0, 0],
p2: [0, 1, 0],
arrow: { ends: 'end' },
color: [1, 0, 0, 1],
lines: true,
});
figure.add(
{
make: 'line3',
p1: [0, 0, 0],
p2: [0, 0.4, 0],
color: [1, 0, 0, 1],
width: 0.01,
arrow: { end: 'end', width: 0.02 },
copy: [
{ along: 'rotation', num: 20, step: Math.PI * 2 / 20 },
{ along: 'rotation', axis: [0, 1, 0], num: 20, step: Math.PI * 2 / 20 },
],
},
);
To test examples, append them to the boilerplate
Cone shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

By default, a cone with its base at the origin and its tip along the x axis will be created.
number of sides
radius of cube base
flat normals will make light
shading across a face cone constant. curve will gradiate the shading. Use
curve to make a surface look more round with fewer number of sides.
line that can position and orient the cone. First point of line is cone base center, and second point is cone tip.
length of the cone along the x axis if
line isn't defined
rotation of base - this is only noticable for
small numbers of sides (0)
points of cube
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
figure.add({
make: 'cone',
radius: 0.2,
sides: 20,
color: [1, 0, 0, 1],
line: [[0, 0, 0], [0, 0.5, 0]],
});
figure.add({
make: 'cone',
radius: 0.2,
normals: 'curve',
sides: 20,
color: [1, 0, 0, 1],
line: [[0, 0, 0], [0, 0.5, 0]],
});
figure.add({
make: 'cone',
radius: 0.2,
normals: 'curve',
sides: 20,
color: [1, 0, 0, 1],
line: [[0, 0, 0], [0, 0.5, 0]],
lines: true,
});
To test examples, append them to the boilerplate
Prism shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive

A prism base is defined in the XY plane, and it's length extends
into +z. Use transform to orient it in any other way.
Triangles will be created for the ends if the base is convex. If the base
is not convex, use baseTriangles to define the triangles.
base border points defined in the XY plane - the points should be defined in the counter-clock-wise direction.
triangles in the XY plane that create the base fill. If the base is convex, then the triangles can be auto-generated and this property left undefined.
length of the prism
if true then points representing
the edges of the prism will be returned. If false, then points
representing triangle faces and associated normals will be returned.
figure.add(
{
make: 'prism',
base: [[0, 0], [0.5, 0], [0.5, 0.2], [0, 0.2]],
color: [1, 0, 0, 1],
},
);
figure.add(
{
make: 'prism',
base: Fig.polygon({ radius: 0.1, sides: 6 }),
color: [1, 0, 0, 1],
transform: ['r', Math.PI / 2, 0, 1, 0],
lines: true,
},
);
figure.add(
{
make: 'prism',
base: [[0, 0], [0.25, 0.1], [0.5, 0], [0.5, 0.3], [0.25, 0.2], [0, 0.3]],
baseTriangles: [
[0, 0], [0.25, 0.1], [0.25, 0.2],
[0, 0], [0.25, 0.2], [0, 0.3],
[0.25, 0.1], [0.5, 0], [0.5, 0.3],
[0.25, 0.1], [0.5, 0.3], [0.25, 0.2],
],
color: [1, 0, 0, 1],
transform: ['r', Math.PI / 2, 0, 1, 0],
},
);
To test examples, append them to the boilerplate
Revolve shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive.

Revolve (or radially sweep) a profile in the XY plane around the x axis to form a 3D surface. The profile y values must be greater than or equal to 0.
Profiles can be open (start and end point are different) or closed (start and end point are equal).
For predictable lighting results, profiles should be created with the following rules:
If an open profile's start and ends points are at y = 0, then the final shape will look solid.
If an open profile's start and/or ends points have y > 0, then the final shape will look like a surface open at the ends with y > 0. As a surface can have only one color, then looking inside the shape the surface lighting will be opposite to that expected. To create open ended solids with lighting that is as expected, create the outside and inside surface with a closed profile.

XY plane profile to be radially swept around the x axis
number of sides in the radial sweep
flat normals will make shading (from a light source) across a face of the
object a constant color. curveProfile will gradiate the shading along the
profile. curveRadial will gradiate the shading around the radial sweep.
curve will gradiate the shading both around the radial sweep and along the
profile. Use curve, curveProfile, or curveRadial to make a surface
look more round with fewer number of sides.
by default the profile will start in the XY
plane and sweep around the x axis following the right hand rule. Use
rotation to start the sweep at some angle where 0º is in the XY for +y and
90º is in the XZ plane for +z. initial angle of the revolve rotation
orient the draw space vertices of the shape so its axis is along this vector
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
figure.add({
make: 'revolve',
profile: [[0, 0], [0, 0.05], [0.5, 0.05], [0.6, 0.1], [0.7, 0]],
axis: [0, 1, 0],
color: [1, 0, 0, 1],
sides: 20,
});
// lighting more correct (note there is not shaddows).
// Ensure to close the shell by adding the first point to the end of the
// profile
figure.add({
make: 'revolve',
profile: [[0, 0.15], [0.5, 0.3], [0.5, 0.29], [0, 0.14], [0, 0.15]],
color: [1, 0, 0, 1],
sides: 30,
normals: 'curveRadial',
});
const x = Fig.range(0, 0.5, 0.01);
const profile = x.map(_x => [_x, 0.1 + 0.05 * Math.sin(_x * 2 * Math.PI * 2)]);
figure.add({
make: 'revolve',
profile: [...profile, [0.4, 0], [0, 0], [0, 0.1]],
axis: [0, 1, 0],
color: [1, 0, 0, 1],
sides: 30,
});
// the x axis, a hole will be created
// Try using `normals: 'curve'`, `normals: 'curveProfile'`, and
// `normals: 'curveRadial'` to see different curve options.
const { polygon } = Fig;
figure.add({
make: 'revolve',
profile: polygon({
radius: 0.1, center: [0, 0.3], sides: 20, direction: -1, close: true,
}),
color: [1, 0, 0, 1],
sides: 30,
});
figure.add({
make: 'revolve',
profile: [[0, 0.03], [0.4, 0.03], [0.4, 0.09], [0.7, 0]],
axis: [0, 1, 0],
color: [1, 0, 0, 1],
sides: 20,
lines: true,
});
figure.add({
make: 'revolve',
profile: [[0, 0], [0, 0.3], [0.5, 0.2], [1, 0.3], [1, 0]],
color: [1, 0, 0, 1],
sides: 30,
});
figure.add({
make: 'revolve',
profile: [[0, 0.3], [0.5, 0.2], [1, 0.3]],
color: [1, 0, 0, 1],
sides: 30,
});
figure.add({
make: 'revolve',
profile: [[0, 0.3], [0.5, 0.2], [1, 0.3], [1, 0.29], [0.5, 0.19], [0, 0.29], [0, 0.3]],
color: [1, 0, 0, 1],
sides: 30,
});
To test examples, append them to the boilerplate
Surface shape options object that extends OBJ_Generic3All and OBJ_FigurePrimitive.


A surface is defined with a 2D matrix of points. Triangles that fill the surface are created between neighboring points in the matrix.
If a surface is defined with 9 points (an array or arrays in JavaScript):

Then triangles will be created between adb, deb, bec, efc, dge,
ghe, ehf, and hif.
The normal for triangle 'adb' is in the direction of the cross product
of vector 'ad' with vector 'ab' (use the right hand rule where the fingers
of the right hand curl from vector 'ad' to 'ab', and the thumb will then be
the direction of the normal). Similarly, the normal of hif will be the
direction of the cross product of hi with hf.
Use the property invertNormals to make all the normals go in the reverse
direction.
A surface can be open or closed at the end rows or columns of the matrix. For example, a surface has closed columns if the first and last column of the matrix have identical points. A surface is has open rows if the first and last row of the matrix have different points.
If using curved normals ('curve', 'curveRows' or 'curveColumns') with
closed surfaces, use closeRows or closeColumns to ensure normal
curvature is maintained at the end rows
and columns.
A grid of points that define a 3D surface
flat normals will make shading (from a light source) across a face of the
object a constant color. curveRows will gradiate the shading along the
rows of the grid. curveColumns will gradiate the shading along the columns
of the grid. curve will gradiate the shading along both rows and columns.
Use curve, curveRows, or curveColumns to make a surface
look more round with fewer number of sides.
Set to true if first row and last row are
the same, and normals is 'curveRows' or 'curve' to get correct normal
calculations
Set to true if first row and last
column are the same, and normals is 'curveColumns' or 'curve' to get
correct normal calculations (false)
shape
if true then points representing
the edes of the faces will be returned. If false, then points
representing two triangles per face and an
associated normal for each point will be returned.
if true then all normals will be
inverted
const points = Fig.surfaceGrid({
x: [-0.8, 0.7, 0.03],
y: [-0.8, 0.7, 0.03],
z: x => 0.2 * Math.cos(x * 2 * Math.PI),
});
figure.scene.setCamera({ up: [0, 0, 1] });
figure.add({
make: 'surface',
points,
color: [1, 0, 0, 1],
});
const points = Fig.surfaceGrid({
x: [-0.8, 0.8, 0.03],
y: [-0.8, 0.8, 0.03],
z: (x, y) => y * 0.2 * Math.cos(x * 2 * Math.PI),
});
figure.scene.setCamera({ position: [-1, -1, 0.7], up: [0, 0, 1] });
figure.add({
make: 'surface',
points,
lines: true,
color: [1, 0, 0, 1],
});
const points = Fig.surfaceGrid({
x: [-0.8, 0.8, 0.03],
y: [-0.8, 0.8, 0.03],
z: (x, y) => {
const r = Math.sqrt(x * x + y * y) * Math.PI * 2 * 2;
return Math.sin(r) / r;
},
});
// Orient the camera so z is up
figure.scene.setCamera({ up: [0, 0, 1] });
figure.add({
make: 'surface',
points,
color: [1, 0, 0, 1],
});
figure.add({
make: 'surface',
points,
lines: true,
color: [0, 0, 0, 1],
});
figure.add({
make: 'surface',
normals: 'curveColumns',
closeRows: true,
points: [
[[0, 0, 0.5], [1, 0, 0.5]],
[[0, -0.5, 0], [1, -0.5, 0]],
[[0, 0, -0.5], [1, 0, -0.5]],
[[0, 0.5, 0], [1, 0.5, 0]],
[[0, 0, 0.5], [1, 0, 0.5]],
],
color: [1, 0, 0, 1],
});
figure.add({
make: 'surface',
normals: 'curveColumns',
closeRows: true,
points: [
[[0, 0, 0.5], [1, 0, 0.5]],
[[0, -0.5, 0], [1, -0.5, 0]],
[[0, 0, -0.5], [1, 0, -0.5]],
[[0, 0.5, 0], [1, 0.5, 0]],
[[0, 0, 0.5], [1, 0, 0.5]],
],
color: [1, 0, 0, 1],
});
// it around the x axis
const { Point, Transform } = Fig;
const points = [];
// Rotation step
const dr = Math.PI * 2 / 50;
for (let r = 0; r < Math.PI * 2 + dr / 2; r += dr) {
// Rotation matrix of rotation step around x axis
const m = new Transform().rotate(r, 1, 0, 0).matrix();
// A row of points is a profile rotated by some amount r
points.push([]);
// Make a profile for x values from 0 to 1
for (let x = 0; x < 1; x += 0.05) {
// The y coordinate of the profile changes with both x value, and
// rotation value
const y = 0.1 * Math.sin(6 * x) + 0.25 + 0.1 * Math.cos(3 * r);
const p = new Point(x, y).transformBy(m);
points[points.length - 1].push(p);
}
}
figure.add({
make: 'surface',
points,
color: [1, 0, 0, 1],
});
To test examples, append them to the boilerplate
the scene light that will be cast on the shape. Use
nullfor no lighting - all surfaces will have the defined color.