This demo illustrates new capabilities of the WebGL mode in the next major upcoming version of p5.quadrille.js, currently under development. It showcases how Platonic solids can be stored in quadrille cells and rendered using either immediate or retained mode with the p5.platonic library.
Platonic Cells
Platonic cells are cell functions (cellFn
) that implement the filling of Platonic solid cells in a quadrille game
.
Retained Mode
(mouse click to clear/add Platonic solids, drag to navigate; press s
(or c
) to save)
code
'use strict'
let cnv
let game, solids
let font
let images = []
const r = 5, c = 5, l = Quadrille.cellLength
function preload() {
font = loadFont('noto_sans.ttf')
for (let i = 1; i <= 10; i++) {
images.push(loadImage(`p${i}.jpg`))
}
}
function setup() {
cnv = createCanvas(r * l, c * l, WEBGL)
solids = createQuadrille(r, c)
const args = [l / 2, true, ['yellow', 'blue', 'red',
'cyan', 'magenta', 'yellow']]
visitQuadrille(solids, (row, col) => solids.fill(row, col,
platonicGeometry(...args)))
game = createQuadrille(5, 5)
visitQuadrille(game, (row, col) => game.fill(row, col, random(images)))
game.rand(15).rand(10, cellFn)
}
function draw() {
background('Gold')
drawQuadrille(game, {
textFont: font,
origin: CORNER,
options: { origin: CENTER }
})
}
function cellFn({ row, col }) {
push()
background('black')
stroke('lime')
fill('blue')
rotateX(millis() * 0.001)
rotateY(millis() * 0.001)
rotateZ(millis() * 0.001)
model(solids.read(row, col))
pop()
}
function mouseClicked() {
const row = game.mouseRow
const col = game.mouseCol
game.isEmpty(row, col) ? game.fill(row, col, cellFn) : game.clear(row, col)
}
function keyPressed() {
key === 's' && game.toImage('game.png', {
textFont: font,
origin: CORNER,
options: { origin: CENTER }
})
key === 'c' && save(cnv, 'platonic_cells.png')
}
const mouseWheel = () => false
Retained mode rendering of the Platonic cells requires the geometry to be built and stored in a separate solids
quadrille in setup
:
function setup() {
// ...
solids = createQuadrille(r, c)
const args = [l / 2, true, ['yellow', 'blue', 'red',
'cyan', 'magenta', 'yellow']]
// When no specific Platonic solid is passed to the p5.platonic
// platonicGeometry function (as done here), it returns a p5.Geometry
// instance of a random one. See: https://bit.ly/3XcQYUs
visitQuadrille(solids, (row, col) => solids.fill(row, col,
platonicGeometry(...args)))
}
The cellFn
rendering procedure reads the solid located at (row, col)
in the solids
quadrille and renders it after applying some rotations, in retained mode with p5 model:
function cellFn({ row, col }) {
push()
background('black')
stroke('lime')
fill('blue')
rotateX(millis() * 0.001)
rotateY(millis() * 0.001)
rotateZ(millis() * 0.001)
model(solids.read(row, col))
pop()
}
Note that the cellFn
may be handled by the quadrille like any other value:
game.fill(row, col, cellFn)
Retained mode is the fastest approach but it requires the geometry to be computed in setup
so that it is set in GPU memory only once.
Immediate Mode
(mouse click to clear/add Platonic solids, drag to navigate; press s
to save)
code
'use strict'
let cnv
let game
let font
let images = []
const r = 5, c = 5, l = Quadrille.cellLength
function preload() {
font = loadFont('noto_sans.ttf')
for (let i = 1; i <= 10; i++) {
images.push(loadImage(`p${i}.jpg`))
}
}
function setup() {
cnv = createCanvas(r * l, c * l, WEBGL)
game = createQuadrille(r, c)
visitQuadrille(game, (row, col) =>
game.fill(row, col, random() < 0.5 ?
createCellFn() :
random(images)))
game.rand(10)
}
function draw() {
background('DeepSkyBlue')
drawQuadrille(game, {
outline: 'magenta',
textFont: font,
origin: CORNER,
options: { origin: CENTER }
})
}
function createCellFn() {
const solid = random([tetrahedron, hexahedron, octahedron,
dodecahedron, icosahedron])
return function cellFn() {
push()
background('black')
stroke('lime')
fill('red')
rotateX(millis() * 0.001)
rotateY(millis() * 0.001)
rotateZ(millis() * 0.001)
solid(Quadrille.cellLength / 2)
pop()
}
}
function mouseClicked() {
const row = game.mouseRow
const col = game.mouseCol
game.isEmpty(row, col) ? game.fill(row, col, createCellFn()) :
game.clear(row, col)
}
function keyPressed() {
key === 's' && game.toImage('game.png', {
outline: 'magenta',
textFont: font,
origin: CORNER,
options: { origin: CENTER }
})
key === 'c' && save(cnv, 'platonic_cells.png')
}
const mouseWheel = () => false
Immediate mode requires a cellFn
function creator to create and return a random Platonic solid function:
function createCellFn() {
// The Platonic functions tetrahedron, hexahedron, etc.,
// are defined in the p5.platonic library. See: https://bit.ly/4bMaxYg
const solid = random([tetrahedron, hexahedron, octahedron,
dodecahedron, icosahedron])
return function cellFn() {
push()
background('black')
stroke('lime')
fill('red')
rotateX(millis() * 0.001)
rotateY(millis() * 0.001)
rotateZ(millis() * 0.001)
solid(Quadrille.cellLength / 2)
pop()
}
}
The function value returned by the creator is then stored in the game
quadrille, just like any other data type:
game.fill(row, col, createCellFn())
Note that createCellFn
is referred to as a higher-order function.
Immediate mode rendering is less performant than retained mode as it requires constantly transferring the Platonic solid geometry data once per frame to the GPU.
Cell Functions
Cell functions offer all the drawing features of the main canvas, extending beyond just displaying Platonic solids. Since each cell can incorporate complex WebGL content, a wide range of creative possibilities opens up, such as:
- Interactive Animations: Embedding animated sequences within individual cells, making each cell an interactive component.
- Textured Surfaces: Applying custom textures or procedural shaders to each cell for intricate visual effects.
- Miniature Scenes: Creating miniature 3D scenes or models within each cell, turning the grid into a collection of diverse, detailed environments.
- Dynamic Data Visualization: Visualizing dynamic data within cells, where each cell could represent a different data point or metric with its unique graphical representation.
Leveraging p5.Framebuffer for these features allows for more advanced rendering techniques and enhanced performance compared to p5.Graphics.
Future Work
WebGL in p5.quadrille.js requires at least:
- Implement cell picking, i.e., make
mouseRow
andmouseCol
work when navigating the scene, such as with orbitControl. - Improve the parameterization of the
cellFn
(e.g., to support cell camera interaction), as it currently only supportsrow
andcol
(as shown above), andorigin
to define where to place the reference frame within each cell drawing, eitherCENTER
(default) orCORNER
. - Use WebGL mode to enhance the performance of cell sorting.
- Properly document the new displayFunction functionality, which leverages the capabilities of p5.Framebuffer.
p5.quadrille.js API References
- createQuadrille: Creates a new quadrille instance.
- visitQuadrille: Visits each cell in a quadrille and applies a function.
- drawQuadrille: Renders the quadrille on the canvas.
- fill: Fills a cell with a specified content.
- clear: Clears the content of a cell.
- isEmpty: Checks if a cell is empty.
- toImage: Converts the quadrille to an image.