Although not every effect or demo in the wilderness is circular, it's common to see a lot of them using circular functions in one way or another.
Math.sin and Math.cos are handy because they loop, like a wave that goes up and down. When the given value goes over *Math.PI 2, the value they give you it's going to repeat. Both sin and cos are actually both the same function but offset by HALF_PI.
Their circular nature makes it super useful for repeating things. Want a repeating scale animation? Slap a cosine to it.
let scale = Math.cos(time)
Want something to go in circles? Slap both of them with the same angle.
position.x = Math.sin(time);
position.y = Math.cos(time);
- Rose Patterns with circular functions
- Sine and cosine from a shader perspective
- Using Circular functions for mouse trails.
In daydream, Keita creates 3 spheres. In their fragment shader, he samples simplex-noise using the normals of the spheres. Then, discards any pixel where the noise is lower than
0.3 . This create the gaps that allow us to see the most inner spheres
float n = snoise(vec3(normal.x + time , normal.y, normal.z));
float visibility = step(0.3, n);
if (visibility < 1.0) discard;
This animation is similar to the daydream sphere. However, the effect is limited to a single stripe, and it has a lot more spheres. Here's a basic version of what it would look like in the fragment shader:
float stripeSize = 0.2;
float stripe = step(0.5 -stripeSize, uv.y) * step(uv.y, 0.5 + stripeSize);
float n = snoise(vec3(normal.x + time , normal.y, normal.z)) * stripe;
Using the sphere UVs I calculate a white-centered stripe. Then, multiply that by the noise to get the noise only happening in the center of the sphere.
To position a lot of rectangles like in mrDoob's demo, we first divide 2PI by the number of rectangles:
let anglePerSquare = (Math.PI * 2.) / count
Then we loop over all our meshes and calculate their x and y position:
// in some loop
let angle = index * anglePerSquare
mesh.position.x = Math.cos(angle) * radius
mesh.position.y = Math.sin(angle) * radius
While this looks way more complicated than the last demo. A torus is a set of circles around another circle. It's not even an extra step from the last demo, It's the same step repeated twice!
// 1s loop
let pivot = new Object3d()
let torusAngle= index * anglePerPivot
pivot.position.x = Math.cos(torusAngle) * torusRadius
pivot.position.y = Math.sin(torusAngle) * torusRadius
Then, for every pivot (an inner loop), we repeat the process above but add each mesh to the pivot.
This kind of circular motion also worsk with cosine and sine. However, they are applied to the scale and y positoning of the rings:
// loop over all rings.
let offset = ringIndex * Math.PI + time * Math.PI
let minScale = 0.2;
let maxScale = 1.;
ring.scale.setScalar( (Math.sin(offset) * 0.5 + 0.5) * (maxScale - minScale) + minScale )
ring.position.z = Math.cos(offset) *0.8
For the circular motion to work correctly, you need to use both cos and sin.