I have a massive image of a map that is much larger than the viewport and centered in the viewport, which can be explored by the user by dragging the screen. In order to create a parallax effect, I used a massive image of clouds in the foreground. As the user explores the map via dragging, both the background and foreground move in a parallax fashion. So far, so good.
However, what I really want to do is give the image of clouds a "default" movement that would be randomly generated on each page load, so that the clouds would always be moving, even if the user is not dragging. I know this can be done by animating the foreground along a path, but I am not exactly sure how to go about this.
How can I randomly generate irregularly curved or wavy paths on each page load?
Does anybody know of any algorithms that can do this?
I also use a copy of the previous answers to realize a simplified version of what I hinted at in the comments.
Use a random walk on the unit circle, that is on the angle, to determine a velocity vector that slowly but randomly changes and move forward using cubic Bezier patches.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 400;
var cx = cw / 4, cy = ch / 2;
var angVel = v.value;
var tension = t.value;
ctx.lineWidth = 4;
var npts = 60;
var dw = Array();
var xs = Array();
var ys = Array();
var vxs = Array();
var vys = Array();
function Randomize() {
for (var i = 0; i < npts; i++) {
dw[i] = (2*Math.random()-1);
}
}
function ComputePath() {
xs[0]=cx; ys[0]=cy;
var angle = 0;
for (var i = 0; i < npts; i++) {
vxs[i]=10*Math.cos(2*Math.PI*angle);
vys[i]=10*Math.sin(2*Math.PI*angle);
angle = angle + dw[i]*angVel;
}
for (var i = 1; i < npts; i++) {
xs[i] = xs[i-1]+3*(vxs[i-1]+vxs[i])/2;
ys[i] = ys[i-1]+3*(vys[i-1]+vys[i])/2;
}
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(xs[0],ys[0]);
for (var i = 1; i < npts; i++) {
var cp1x = xs[i-1]+tension*vxs[i-1];
var cp1y = ys[i-1]+tension*vys[i-1];
var cp2x = xs[i]-tension*vxs[i];
var cp2y = ys[i]-tension*vys[i]
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, xs[i], ys[i]);
}
ctx.stroke();
}
Randomize();
ComputePath();
Draw();
r.addEventListener("click",()=>{
Randomize();
ComputePath();
Draw();
})
v.addEventListener("input",()=>{
angVel = v.value;
vlabel.innerHTML = ""+angVel;
ComputePath();
Draw();
})
t.addEventListener("input",()=>{
tension = t.value;
tlabel.innerHTML = ""+tension;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<table>
<tr><td>angular velocity:</td><td> <input type="range" id="v" min ="0" max = "0.5" step = "0.01" value="0.2" /></td><td id="vlabel"></td></tr>
<tr><td>tension</td><td> <input type="range" id="t" min ="0" max = "1" step = "0.1" value="0.8" /></td><td id="tlabel"></td></tr>
<tr><td>remix</td><td> <button id="r"> + </button></td><td></td></tr>
</table>
If your question is: How can I randomly generate curved or wavy paths? this is how I would do it: I'm using inputs type range to change the value for amplitude and frequency, but you can set those values randomly on load.
I hope it helps.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 800;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>frequency: <input type="range" id="f" min ="0.01" max = "0.1" step = "0.001" value=".05" /></p>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
I was impressed by the functionality to be able to draw canvases in the SO answers, so I "stole" enxaneta code snippet and played a bit with it (hope that is ok).
The idea is to generate several random points (xs, ys) and for each x from the path to interpolate the y as y = sum{ys_i*w_i}/sum{w_i}, where w_i is some interpolation weight as a function of x. For example w_i(x) = (xs_i - x)^(-2). Hope this makes sense - if this is of any interested I'll try to provide more details.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
var npts = 20;
var xs = Array();
var ys = Array();
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = 0.0;
wsum = 0.0;
for (var i = -5; i <= 5; i++) {
xx = x;
ii = Math.round(x/xs[1]) + i;
if (ii < 0) { xx += cw; ii += npts; }
if (ii >= npts) { xx -= cw; ii -= npts; }
w = Math.abs(xs[ii] - xx);
w = Math.pow(w, frequency);
y += w*ys[ii];
wsum += w;
}
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
<p>frequency: <input type="range" id="f" min ="-10" max = "1" step = "0.1" value="-2" hidden/></p>
Deterministic random paths
Storing paths for random movements is not needed. Also random is another way of being very complex, and for humans it does not take much complexity to look randoms.
Thus with a little randomness to add to complexity you can make the appearance of the infinite non repeating sequence that and be rewound, stopped, slowed down speed up, and be fully deterministic and requiring only a single value to store.
Complex cycles.
To move a point in a circle around a center you can use sin and cos.
For example a point x,y and you want to move in a ball around that point at a distance of dist and a rate once a second. Example in snippet.
var px = 100; // point of rotation.
var py = 100;
const RPS = 1; // Rotations Per Second
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
time = (time / 1000) * PI2 * RPS; // convert the time to rotations per secon
const xx = Math.cos(time) * dist;
const yy = Math.sin(time) * dist;
drawCircle(xx, yy)
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
Next let's move the point around which we rotate, using the method above.
Then for the ball we can change the phase of the rotation in x from the rotation in y. This means that the ball rotating around the now rotating point, and the balls rotating axis are out of phase.
The result is a more complex movements.
var px = 100; // point of rotation.
var py = 100;
const RPS_P = 0.1; // point Rotations Per Second 0.1 every 10 seconds
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_P = 30; // distance from center point is
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoint(time) { // move point around center
time = (time / 1000) * PI2 * RPS_P;
px = 100 + Math.cos(time) * dist_P;
py = 100 + Math.sin(time) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoint(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
We can continue to add out of phase rotations. In the next example we now rotate the rotation point around the center, add out of phase rotation to that point and finally draw the ball with its out of phase rotation.
var px = 100; // point of rotation.
var py = 100;
const RPS_C_X = 0.43; // Rotation speed X of rotating rotation point
const RPS_C_Y = 0.47; // Rotation speed X of rotating rotation point
const RPS_P_X = 0.093; // point Rotations speed X
const RPS_P_Y = 0.097; // point Rotations speed Y
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_C = 20; // distance from center point is
const dist_P = 30; // distance from center point is
const dist = 30; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoints(time) { // Move the rotating pointe and rotate the rotation point
// around that point
var phaseX = (time / 1000) * PI2 * RPS_C_X;
var phaseY = (time / 1000) * PI2 * RPS_C_Y;
px = 100 + Math.cos(phaseX) * dist_C;
py = 100 + Math.sin(phaseY) * dist_C;
phaseX = (time / 1000) * PI2 * RPS_P_X;
phaseY = (time / 1000) * PI2 * RPS_P_Y;
px = px + Math.cos(phaseX) * dist_P;
py = py + Math.sin(phaseY) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoints(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
So now we have a very complex rotation. However as it is set to the time, you can repeat the movement by just setting the time back to the start. You don't need to store a long complex path.
Add a little random
You may see some repeating movement but if you make the phases of each axis a prime then the repeat time is the product of all the primes.
If you want many objects each with a different movement you can randomise the rotation rates and many more properties.
Javascript does not have a seeded random generator. However you can create one. With a seeded random generator you can us the seed to generate a random object. But if you use that seed again you get the same object. In the example below I us a seed from 0 to 10000000 to create a cloud. That means there are 10000000 unique clouds, but all repeatable.
Example of deterministic random clouds
Restart and it will repeat exactly the same. To change it to non deterministic random just add randSeed(Math.random() * 100000 | 0)
const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const randSPow = (min, max = min + (min = 0), p = 2) => (max + min) / 2 + (Math.pow(seededRandom.random() / seededRandom.max, p) * (max - min) * 0.5) * (randSI(2) < 1 ? 1 : -1);
const ctx = canvas.getContext("2d");
const W = ctx.canvas.width;
const H = ctx.canvas.height;
const DIAG = (W * W + H * H) ** 0.5;
const colors = {
dark : {
minRGB : [100 * 0.6,200 * 0.6,240 * 0.6],
maxRGB : [255 * 0.6,255 * 0.6,255 * 0.6],
},
light : {
minRGB : [100,200,240],
maxRGB : [255,255,255],
},
}
const getCol = (pos, range) => "rgba(" +
((range.maxRGB[0] - range.minRGB[0]) * pos + range.minRGB[0] | 0) + "," +
((range.maxRGB[1] - range.minRGB[1]) * pos + range.minRGB[1] | 0) + "," +
((range.maxRGB[2] - range.minRGB[2]) * pos + range.minRGB[2] | 0) + "," +(pos * 0.2 + 0.8) + ")";
const Cloud = {
x : 0,
y : 0,
dir : 0, // in radians
wobble : 0,
wobble1 : 0,
wSpeed : 0,
wSpeed1 : 0,
mx : 0, // Move offsets
my : 0,
seed : 0,
size : 2,
detail : null,
reset : true, // when true could resets
init() {
this.seed = randSI(10000000);
this.reset = false;
var x,y,r,dir,dist,f;
if (this.detail === null) { this.detail = [] }
else { this.detail.length = 0 }
randSeed(this.seed);
this.size = randSPow(2, 8); // The pow add bias to smaller values
var col = (this.size -2) / 6;
this.col1 = getCol(col,colors.dark)
this.col2 = getCol(col,colors.light)
var flufCount = randSI(5,15);
while (flufCount--) {
x = randSI(-this.size * 8, this.size * 8);
r = randS(this.size * 2, this.size * 8);
dir = randS(Math.PI * 2);
dist = randSPow(1) * r ;
this.detail.push(f = {x,r,y : 0,mx:0,my:0, move : randS(0.001,0.01), phase : randS(Math.PI * 2)});
f.x+= Math.cos(dir) * dist;
f.y+= Math.sin(dir) * dist;
}
this.xMax = this.size * 12 + this.size * 10 + this.size * 4;
this.yMax = this.size * 10 + this.size * 4;
this.wobble = randS(Math.PI * 2);
this.wSpeed = randS(0.01,0.02);
this.wSpeed1 = randS(0.01,0.02);
const aOff = randS(1) * Math.PI * 0.5 - Math.PI *0.25;
this.x = W / 2 - Math.cos(this.dir+aOff) * DIAG * 0.7;
this.y = H / 2 - Math.sin(this.dir+aOff) * DIAG * 0.7;
clouds.sortMe = true; // flag that coulds need resort
},
move() {
var dx,dy;
this.dir = gTime / 10000;
if(this.reset) { this.init() }
this.wobble += this.wSpeed;
this.wobble1 += this.wSpeed1;
this.mx = Math.cos(this.wobble) * this.size * 4;
this.my = Math.sin(this.wobble1) * this.size * 4;
this.x += dx = Math.cos(this.dir) * this.size / 5;
this.y += dy = Math.sin(this.dir) * this.size / 5;
if (dx > 0 && this.x > W + this.xMax ) { this.reset = true }
else if (dx < 0 && this.x < - this.xMax ) { this.reset = true }
if (dy > 0 && this.y > H + this.yMax) { this.reset = true }
else if (dy < 0 && this.y < - this.yMax) { this.reset = true }
},
draw(){
const s = this.size;
const s8 = this.size * 8;
ctx.fillStyle = this.col1;
ctx.setTransform(1,0,0,1,this.x+ this.mx,this.y +this.my);
ctx.beginPath();
for (const fluf of this.detail) {
fluf.phase += fluf.move + Math.sin(this.wobble * this.wSpeed1) * 0.02 * Math.cos(fluf.phase);
fluf.mx = Math.cos(fluf.phase) * fluf.r / 2;
fluf.my = Math.sin(fluf.phase) * fluf.r / 2;
const x = fluf.x + fluf.mx;
const y = fluf.y + fluf.my;
ctx.moveTo(x + fluf.r + s, y);
ctx.arc(x,y,fluf.r+ s,0,Math.PI * 2);
}
ctx.fill();
ctx.fillStyle = this.col2;
ctx.globalAlpha = 0.5;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s;
const y = fluf.y + fluf.my - s;
ctx.moveTo(x + fluf.r, y);
ctx.arc(x,y,fluf.r,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 0.6;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s * 1.4;
const y = fluf.y + fluf.my - s * 1.4;
ctx.moveTo(x + fluf.r * 0.8, y);
ctx.arc(x,y,fluf.r* 0.8,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 1;
}
}
function createCloud(size){ return {...Cloud} }
const clouds = Object.assign([],{
move() { for(const cloud of this){ cloud.move() } },
draw() { for(const cloud of this){ cloud.draw() } },
sortMe : true, // if true then needs to resort
resort() {
this.sortMe = false;
this.sort((a,b)=>a.size - b.size);
}
});
for(let i = 0; i < 15; i ++) { clouds.push(createCloud(40)) }
requestAnimationFrame(mainLoop)
var gTime = 0;
function mainLoop() {
gTime += 16;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
if(clouds.sortMe) { clouds.resort() }
clouds.move();
clouds.draw();
requestAnimationFrame(mainLoop);
}
body { padding : 0px; margin : 0px;}
canvas {
background : rgb(60,120,148);
border : 1px solid black;
}
<canvas id="canvas" width="600" height="200"></canvas>
I have n+1 hexshapes in a honeycomb grid. The objects are stacked close together. With this code:
// Get intersected objects, a.k.a objects "hit" by mouse, a.k.a objects that are mouse-overed
const intersects = raycaster.intersectObjects(hexObjects);
// If there is one (or more) intersections
let scaleTween = null;
if (intersects.length > 0) {
// If mouse is not currently over an object
// Set cursor to pointer so that the user can see that the object is clickable
document.body.style.cursor = 'pointer';
// Get the last intersected object, it's most likely that object we are currently hovering
const is = intersects.length > 0 ? intersects.length - 1 : 0;
// Is the object hovered over for the first time?
if (INTERSECTED === null) {
// Save current hovered object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
} else {
// If the mouse is over an object
// Do we have a previous hovered item?
if (INTERSECTED !== null) {
// Revert color
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT Z-INDEX
INTERSECTED.position.z = 1;
}
// Save current intersected object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
}
} else {
// If there are no intersections
// Reset cursor
document.body.style.cursor = 'default';
// Restore previous intersection object (if it exists) to its original color
if (INTERSECTED !== null) {
// REVERT COLOR
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT "Z-INDEX"
INTERSECTED.position.z = 1;
}
// Remove previous intersection object reference by setting current intersection object to "nothing"
INTERSECTED = null;
}
I've managed to highlight the object and scale it up with a tween quite nicely, but when I move the mouse out of the object onto the next object (the scaled object is scaled over the next object a bit), the highlight is gone, but the scale persists. How do I manage to scale the object down? And preferably with a tween?
A pen for this code can be found here: https://codepen.io/phun-ky/pen/erBZZy, the relevant part is at about line 1284 or search for INTERSECTED.
I wrote my own one. It's hell imperfect, but, at least, it scales up and down the hexagons:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x101010);
document.body.appendChild(renderer.domElement);
var hexes = [];
var colCount = 5;
var rowCount = 4;
var hexDiameter = 3;
var xStart = -(colCount) * hexDiameter * 0.5;
var rowSpace = Math.sqrt(3) * hexDiameter * 0.5;
var yStart = (rowCount - 1) * rowSpace * 0.5;
var hexGeom = new THREE.CylinderGeometry(hexDiameter * 0.5, hexDiameter * 0.5, 0.0625, 6, 1);
hexGeom.rotateX(Math.PI * 0.5);
for (let j = 0; j < rowCount; j++) {
for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
color: Math.random() * 0x7e7e7e + 0x7e7e7e,
wireframe: false
}));
hex.position.set(xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0), yStart - j * rowSpace, 0);
hex.userData.scaleUp = function(h) {
if (h.userData.scaleDownTween) h.userData.scaleDownTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(2);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
hex.userData.scaleDown = function(h) {
if (h.userData.scaleUpTween) h.userData.scaleUpTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(1);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
scene.add(hex);
hexes.push(hex);
}
}
window.addEventListener("mousemove", onMouseMove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var intersected;
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(hexes);
if (intersects.length > 0) {
if (intersected != intersects[0].object) {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = intersects[0].object;
intersected.userData.scaleUp(intersected);
}
} else {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = null;
}
}
render();
function render() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
I have 2 spheres 1 inner and 1 outer sphere. on the outer sphere i have 128 particles/sprites.
i would like to move each sprite/particle out and away from the sphere to a certain distance.
The particles/sprites must act like an audio equalizer moving away from the sphere to a certain distance and the back to the rest position.
Please can you assist.
var geometryInner = new THREE.SphereGeometry(60, 32, 32);
geometryInner.applyMatrix( new THREE.Matrix4().makeScale( 1.0, 1.8, 1.0 ) );
//var geometryMid = new THREE.SphereGeometry(90, 32, 32);
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexShaderGeneral').textContent,
fragmentShader: document.getElementById('fragmentShaderLineColors').textContent,
transparent: false
});
sphereInner = THREE.SceneUtils.createMultiMaterialObject( geometryInner, [
material,
new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true} )
]);
// sphereInner.rotation.x = 0.3;
// sphereInner.position.y = -30;
scene.add(sphereInner);
//
geometryOuter = new THREE.SphereGeometry(80, 32, 32);
geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 1.0, 1.8, 1.0 ) );
var materialOuter = new THREE.MeshLambertMaterial({color: 0xFF0000, transparent: true, opacity: 0.1});
var sphereOuter = new THREE.Mesh(geometryOuter, materialOuter);
scene.add(sphereOuter);
points = THREE.GeometryUtils.randomPointsInGeometry( geometryOuter, particleCount );
var pMaterial = new THREE.PointCloudMaterial({
color: 0xFFFFCC,
size: 20,
map: THREE.ImageUtils.loadTexture(
"particle.png"
),
blending: THREE.AdditiveBlending,
opacity : 0.8,
depthWrite : false,
transparent: true
});
// create the particle variables
particles = new THREE.Geometry();
// create the particle variables
// now create the individual particles
for (var p = 0; p < points.length; p++)
{
// create a particle with random
// position values, -250 -> 250
pX = points[p].x;
pY = points[p].y;
pZ = points[p].z;
// add it to the geometry
particles.vertices.push(point);
}
// create the particle system
particleSystem = new THREE.PointCloud(particles,
pMaterial);
// also update the particle system to
// sort the particles which enables
// the behaviour we want
particleSystem.sortParticles = true;
// add it to the scene
scene.add(particleSystem);
I have tried this...when i render. I just need some help.
function render()
{
var timer = 0.0001 * Date.now();
// add some rotation to the system
//particleSystem.rotation.y += boost * 0.0001;
var delta = clock.getDelta();
if(typeof array === 'object' && array.length > 0) {
var k = 0;
for(var i = 0; i < particleSystem.geometry.vertices.length ; i++) {
particleSystem.geometry.verticesNeedUpdate = true;
// particleSystem.position.z = boost/2;
particleSystem.geometry.vertices[k].z = array[i]/4 - 30;
// geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 2, 1.8, 1.0 ) );
// particleSystem.rotation.y += array[2] * 0.0000005 ;
// sphereInner.rotation.z -= array[0] * 0.0000005;
// sphereInner.position.z = boost/3;
sphereInner.rotation.y += array[0] * 0.0000005;
uniforms.time.value -= array[0] * 0.0000005;
uniformsPlane.time.value += array[1] * 0.0000008;
k += (k < array.length ? 1 : 0);
}
}
// uniforms2.time.value = clock.elapsedTime;
// camera.position.x += ( mouseX - camera.position.x ) * .05;
// camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
renderText();
// renderer.clear();
effect.render( scene, camera );
}
Here is the fix...
if(typeof array === 'object' && array.length > 0) {
var k = 0;
for(var i = 0; i < particleSystem.geometry.vertices.length ; i++) {
particleSystem.geometry.verticesNeedUpdate = true;
// particleSystem.position.z = boost/2;
if(array[k] == 0)
{
particleSystem.geometry.vertices[k].z = points[k].z;
}
else if(points[k].z + array[k] + 100 < points[k].z)
{
particleSystem.geometry.vertices[k].z = points[k].z;
}
else
{
particleSystem.geometry.vertices[k].z = points[k].z + array[k] + 100;// * 0.1 - 60;
}
// particleSystem.geometry.vertices[k].x = -array[i];
// particleSystem.geometry.vertices[k].y = array[i];
// geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 2, 1.8, 1.0 ) );
// particleSystem.rotation.y += array[2] * 0.0000005 ;
// sphereInner.rotation.z -= array[0] * 0.0000005;
// sphereInner.position.z = boost/3;
sphereInner.rotation.y += array[0] * 0.0000005;
uniforms.time.value -= array[0] * 0.0000005;
uniformsPlane.time.value += array[1] * 0.0000008;
k += (k < array.length ? 1 : 0);
}
}
I'm trying to create a cuboid from 5 cuboids one of top of the other - that rotates 180 CW degrees every two seconds.
It's kinda working but I'm not getting smooth results... I thought a good strategy would be that during the rotating period- the z-position of the top-left and top-right vertices of a single face will be compared- if they are the same then it's time to stop the rotating period.
The problem is that the difference between the vertices is never zero.. it's very close to zero... so I check if it's in a margin of 0.1 and thus I have a problem with the beginning of a rotation that is jammed because sometimes it's less than 0.1. Also sometimes the rotations continue when they should stop because the difference is not less than 0.1.
var spinningPeriod = false, counter = 0, lastTime = 0;
function animate(){
counter++;
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = 0.2 * timeDiff * 2 * Math.PI / 1000;
if (counter%200==0 && counter > 0) {
spinningPeriod = true;
}
if (spinningPeriod) {
var v1 = cubes[0].geometry.vertices[0].clone();
var v2 = cubes[0].geometry.vertices[3].clone();
cubes[0].updateMatrixWorld();
cubes[0].localToWorld(v1);
cubes[0].localToWorld(v2);
if (Math.abs(v1.x - v2.x) < 0.1) {
spinningPeriod = false;
}
for (var ii =0; ii<5; ++ii) {
cubes[ii].rotation.y += angleChange;
}
}
lastTime = time;
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function(){
animate();
});
}
// renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 500;
// scene
var scene = new THREE.Scene();
// cube
var cubes = new Array();
var currentHeight = -150;
for (var ii =0; ii<5; ++ii) {
cubes[ii] = new THREE.Mesh(new THREE.CubeGeometry(400, 50, 20));
currentHeight += 50;
cubes[ii].position.y = currentHeight;
cubes[ii].overdraw = true;
scene.add(cubes[ii]);
}
// start animation
animate();
I agree, looking at the position is probably not the best way to do it. If you're concerned with the angle, then the angle should be your focus. Basically you want to have your test be based on the spin vs a max spin - when you're more than that, stop and adjust as needed (in this case, it was sometimes > 1 with the amount of angleChange, so I just reset it to 1).
Instead of your animate function above, try this one - it looks like it works pretty reliably for me.
var spinningPeriod = false, counter = 0, lastTime = 0;
var spinAngle = 0, spinMax = 1 * Math.PI;
function animate(){
counter++;
var time = (new Date()).getTime(),
timeDiff = time - lastTime,
speed = 0.2 * 2 * Math.PI / 1000,
angleChange = speed * timeDiff;
if (counter%200==0 && counter > 0) {
spinningPeriod = true;
spinAngle = 0;
}
console.log(counter, spinningPeriod, cubes[0].rotation.y / Math.PI);
if (spinningPeriod) {
spinAngle += angleChange;
if (spinAngle > spinMax) {
spinningPeriod = false;
spinAngle = spinMax;
}
for (var ii =0; ii<5; ++ii) {
cubes[ii].rotation.y = spinAngle;
}
}
lastTime = time;
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function(){
animate();
});
}