I've been researching and playing with examples of particle clouds in Three.js. Most use shape geometries to define a field of particles, or parameters for distributing them randomly throughout the field of view. What I would like to do is create a particle cloud in which each particle has a relative proximity to an invisible vector path. For example, if I defined a lightly curved vector path, all the particles might float within a consistent radius along that invisible and then maybe taper toward the ends to form a hotdog-shaped cloud of particles. So, I know how to create particles and I know how to create vector paths, how do I link these two things together? Thanks!
1 Answers
You can define the path using two points. Let those points be p
and q
and let v = p - q
. Any point M
lying on the path must satisfy the vector equation
M = (1 - lambda) * p + lambda * q
for some 0 <= lambda <= 1
. Thus, you can generate a random point on the path by generating a random lambda
and using its value in the equation above:
// p and q are instances of THREE.Vector3
function pointOnPath(p, q) {
var lambda = Math.random();
var scaledp = (new THREE.Vector3()).copy(p).multiplyScalar(1 - lambda);
var scaleq = (new THREE.Vector3()).copy(q).multiplyScalar(lambda);
var result = (new THREE.Vector3()).addVectors(scaledp, scaledq);
return result;
}
Next, you want to modify the computed coordinates with some small radius so that they circle around the path. You do that by adding a small vector offset. How do we compute that vector then?
The vector we are after lies in a plane that's perpendicular to the line from p
to q
. There are an infinite number of vectors satisfying the above condition, two of them being e1 = (v.y, -v.x, 0)
and e2 = (v.z, 0, -v.x)
. Any vector of the form lambda * e1 + mu * e2
will also be perpendicular to the v
. Thus, we need only generate lambda
and mu
and everything's ready.
NOTE: lambda
and mu
must be random numbers in the interval [-1; 1], not [0; 1]. Since we are normalizing the offset
vector, the interval [-0.5; 0.5] will suffice because normalization will map it to [-1; 1]
function getVectorOffset(p, q, radius) {
var v = (new THREE.Vector3()).subVectors(q, p);
v.normalize();
var e1 = new THREE.Vector3(v.y, -v.x, 0),
e2 = new THREE.Vector3(v.z, 0, -v.x);
e1.normalize();
e2.normalize();
var lambda = Math.random() - 0.5,
mu = Math.random() - 0.5;
var offset = e1.multiplyScalar(lambda).add(e2.multiplyScalar(mu));
offset.normalize();
offset.multiplyScalar(radius) // multiply the compute offset by the radius you'd like it to circle around
return offset;
}
Finally, to generate your desired point:
function pointOnHotDog(p, q, radius) {
return pointOnPath(p, q).add(getVectorOffset(p, q, radius));
}
Here's a working jsfiddle

- 6,127
- 2
- 25
- 31
-
Thanks so much for this! I've been messing around with it in my file and can't quite get it to work. The particles fan out into the distance on the z axis, but don't seem to radiate on the x and y axes. I've done up a fiddle with a perspective looking down so you can see what I mean. Thoughts? http://jsfiddle.net/gromiczek/32nzX/ – gromiczek Jul 29 '14 at 15:14
-
I had made a small typo - `e1` should be `(v.y, -v.x, 0)` instead of `(v.y, 0, -v.x)`. Here's a working fiddle: http://jsfiddle.net/6uL48/ Note that I changed the `getVectorOffset` to return either `e1` or `e2` so that you can identify the ellipsoid pattern more easily, switch back to offset for your needs. – Nikola Dimitroff Jul 29 '14 at 17:49
-
I just coded up your edits in my file and discovered that when I turned the cloud around (I have OrbitControls installed) and looked at it, it turned out to be a rectangle rather than a tube. However, then I changed `offset.multiplyScalar(radius)` to `offset.setLength(radiusRange * Math.random())` and got a quarter of a tube shape. See here: http://jsfiddle.net/gromiczek/6uL48/1/ (It's hard to see without the orbit controls.) I'm still meddling with it to get the last three quarters. ;) – gromiczek Jul 29 '14 at 18:41
-
Figured it out, the problem was missing negative `lambda`s and `mu`s. See edit and fiddle http://jsfiddle.net/USd8Y/ – Nikola Dimitroff Jul 29 '14 at 19:29
-
Thanks for your persistence on this and pardon my delay replying - I coded up the changes and the cloud is still rectilinear rather than a tube. I'm still working on this problem myself. Any further thoughts are welcome! – gromiczek Aug 07 '14 at 18:25
-
Unfortunately, the previous jsfiddles had expired so I rewrote my changes from your first one. Here's the working [fiddle](http://jsfiddle.net/NikolaDimitroff/x70tpwkf/1/) and here's a link to a non-expiring [pastebin](http://pastebin.com/gkK8Ttqg). Note that it looks like some of the points don't lie on the tube but that's an optical illusion and the reason behind it is that they're closer to the camera (thus look bigger). – Nikola Dimitroff Aug 07 '14 at 19:11
-
Sweet! I modified the radius down to 30, and used the old camera settings to get a good look at it (my forked fiddle here: http://jsfiddle.net/gromiczek/znk0pe7m/ ). Fabulous. Thank you so much - you've more than earned your keep on this one! Optionally - How would a person distribute the points throughout the volume of the form as well? (I will nevertheless approve your answer.) – gromiczek Aug 07 '14 at 19:23
-
To generate a point inside the tube, use a radius value that's less than the radius of the tube. One possible way to do that is to use `getVectorOffset(p, q, radius * Math.random())`. – Nikola Dimitroff Aug 07 '14 at 19:42