Orbital Resonance
- Pixi.js
- Experiment
- Personal project
- WebGL
9:2
Try changing the values above to explore various patterns.
See more examplesStory
Ever since I was little, I was always interested in stars and planets. I remember once seeing some charts about how some of the planets’ orbital periods resonates with each other, and I wanted to visualise the drawing of these patterns.
After a quick prototype I realized that using SVG wasn’t going to cut it: there’s just too many DOM elements moving and overlapping at the same time, slowing down the frame rate way too much for comfort. For this I needed to draw pixels on a <canvas>
…
I also suspected that this was a job for the GPU rather than the CPU, so I started researching web frameworks that utilized WebGL. First hit on Google was Three.js, which I already knew about, but had never really played around much with. I quickly learned that Three.js is a monster of a framework, and the learning curve seemed scaringly steep.
Me, reading the Three.js docs.
After ripping out a lot of my hair in despair trying to actually understand WebGL and Three.js, I was relieved when I discovered Pixi.js. After spending some time with various simple tutorials and reading through some of their - from this beginner’s point of view - pretty good documentation, I managed to get things moving on the screen.
Once I understood how the PIXI.Graphics()
and PIXI.Sprite()
methods worked, I just needed to apply some trigonometry and write the animation loop and finally tweaking the speeds and opacities until I was happy with the result.
Key takeaways
- Using Pixi.js made rendering 2D graphics accelerated by WebGL a lot easier than with Three.js or other libraries.
- Animation performance with WebGL and
<canvas>
is superb compared with SVG - Math and geometry is super interesting
Tools used for this project
- Pixi.js: The HTML5 Creation Engine
- Trigonometry
Code
import * as PIXI from 'pixi.js'
export default function run(container, resonance, size = 200) {
const renderer = PIXI.autoDetectRenderer(size, size, {
transparent: true,
antialias: true,
})
renderer.backgroundColor = 0x061639
renderer.view.width = size
renderer.view.height = size
renderer.options.antialias = true
container.appendChild(renderer.view)
const stage = new PIXI.Container()
const sun = new PIXI.Graphics().lineStyle(1, 0xf3a33f, 0.04)
const graphics = new PIXI.Graphics()
const pattern = new PIXI.Graphics().lineStyle(1, 0xf3a33f, 0.04)
stage.addChild(sun)
stage.addChild(pattern)
let tick = 0
const duration = 120
sun.beginFill(0xffffff, 0.07)
sun.drawCircle(size / 2, size / 2, 16)
sun.endFill()
graphics.beginFill(0x8fd6c5, 0.9)
const planet = graphics.drawCircle(size / 2, size / 2, 8)
graphics.endFill()
const sprite1 = new PIXI.Sprite(renderer.generateTexture(planet))
sprite1.anchor.set(0.5, 0.5)
const sprite2 = new PIXI.Sprite(renderer.generateTexture(planet))
sprite2.anchor.set(0.5, 0.5)
stage.addChild(sprite1)
stage.addChild(sprite2)
function animate() {
tick++
const radian1 =
((tick % ((duration * resonance[0]) / resonance[1])) / ((duration * resonance[0]) / resonance[1])) * Math.PI * 2
const radian2 = ((tick % duration) / duration) * Math.PI * 2
const pos1 = getPos(radian1 - Math.PI, size * 0.25, size)
const pos2 = getPos(radian2, size * 0.35, size)
sprite1.position.set(...pos1)
sprite2.position.set(...pos2)
if (tick < duration * Math.max(...resonance)) {
pattern.moveTo(...pos1)
pattern.lineTo(...pos2)
}
renderer.render(stage)
requestAnimationFrame(animate)
}
animate()
}
function getPos(radian, radius, size) {
const x = size / 2 + Math.sin(radian) * radius
const y = size / 2 + Math.cos(radian) * radius
return [x, y]
}
and then in Vis.vue
I put the following:
<template>
<div class="container">
<h2 class="heading" v-text="`${resonance[0]}:${resonance[1]}`"></h2>
<div ref="canvasContainer"></div>
</div>
</template>
After defining the resonance and size in the component, I can run the script:
const run = require('./script.js').default
run(this.$refs.canvasContainer, this.resonance, this.size)