Skip to content

Commit

Permalink
feat: spin movement enabled from v2
Browse files Browse the repository at this point in the history
  • Loading branch information
matteobruni committed Jul 25, 2021
1 parent 0f7f622 commit 240a38f
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 44 deletions.
116 changes: 116 additions & 0 deletions demo/vanilla/public/presets/spin.json
@@ -0,0 +1,116 @@
{
"fpsLimit": 60,
"particles": {
"number": {
"value": 80,
"density": {
"enable": false,
"value_area": 800
}
},
"color": {
"value": "#ff0000",
"animation": {
"enable": true,
"speed": 20,
"sync": true
}
},
"shape": {
"type": "circle"
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 3,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 10,
"random": false,
"anim": {
"enable": false,
"speed": 20,
"size_min": 0.1,
"sync": false
}
},
"move": {
"enable": true,
"speed": {
"min": 1,
"max": 5
},
"direction": "none",
"random": false,
"straight": false,
"outMode": "out",
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
},
"spin": {
"acceleration": {
"min": -1,
"max": 1
},
"enable": true
},
"trail": {
"enable": true,
"fillColor": "#000",
"length": 30
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "repulse"
},
"onclick": {
"enable": false,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 0.8
},
"repulse": {
"distance": 200
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true,
"background": {
"color": "#000000",
"image": "",
"position": "50% 50%",
"repeat": "no-repeat",
"size": "cover"
}
}
1 change: 1 addition & 0 deletions demo/vanilla/views/index.pug
Expand Up @@ -116,6 +116,7 @@ html(lang="en")
option(value="slow") Slow on Hover
option(value="snow") Snow
option(value="speedDecay") Speed Decay
option(value="spin") Spin
option(value="star") Star
option(value="strokeAnimation") Stroke Animation
option(value="svgReplace") Svg Replace
Expand Down
10 changes: 10 additions & 0 deletions engine/src/Core/Interfaces/IParticleSpin.ts
@@ -0,0 +1,10 @@
import type { RotateDirection } from "../../Enums";
import type { ICoordinates } from "./ICoordinates";

export interface IParticleSpin {
acceleration: number;
angle: number;
direction: RotateDirection;
radius: number;
center: ICoordinates;
}
1 change: 1 addition & 0 deletions engine/src/Core/Interfaces/index.ts
Expand Up @@ -20,6 +20,7 @@ export * from "./IParticleLife";
export * from "./IParticleLoops";
export * from "./IParticlesInteractor";
export * from "./IParticleUpdater";
export * from "./IParticleSpin";
export * from "./IParticleValueAnimation";
export * from "./IPlugin";
export * from "./IRangeValue";
Expand Down
22 changes: 22 additions & 0 deletions engine/src/Core/Particle.ts
Expand Up @@ -47,6 +47,7 @@ import type {
IParticleLife,
IParticleLoops,
IParticleNumericValueAnimation,
IParticleSpin,
IParticleTiltValueAnimation,
IParticleValueAnimation,
IRgb,
Expand Down Expand Up @@ -110,6 +111,7 @@ export class Particle implements IParticle {
readonly orbitColor?: IHsl;
readonly velocity: Vector;
readonly shape: ShapeType | string;
readonly spin?: IParticleSpin;
readonly initialPosition: Vector;
readonly initialVelocity: Vector;
readonly shapeData?: IShapeValues;
Expand Down Expand Up @@ -508,6 +510,26 @@ export class Particle implements IParticle {
this.life = this.loadLife();
this.spawning = this.life.delay > 0;

if (this.options.move.spin.enable) {
const spinPos = this.options.move.spin.position ?? { x: 50, y: 50 };

const spinCenter = {
x: (spinPos.x / 100) * this.container.canvas.size.width,
y: (spinPos.y / 100) * this.container.canvas.size.height,
};

const pos = this.getPosition();
const distance = getDistance(pos, spinCenter);

this.spin = {
center: spinCenter,
direction: this.velocity.x >= 0 ? RotateDirection.clockwise : RotateDirection.counterClockwise,
angle: this.velocity.angle,
radius: distance,
acceleration: getRangeValue(this.options.move.spin.acceleration),
};
}

this.shadowColor = colorToRgb(this.options.shadow.color);

if (drawer && drawer.particleInit) {
Expand Down
121 changes: 78 additions & 43 deletions engine/src/Core/Particle/Mover.ts
@@ -1,7 +1,7 @@
import { clamp, getDistance, getDistances, getRangeMax, getRangeValue, isInArray, isSsr, Plugins } from "../../Utils";
import type { Container } from "../Container";
import type { Particle } from "../Particle";
import { HoverMode } from "../../Enums";
import { HoverMode, RotateDirection } from "../../Enums";
import type { IDelta } from "../Interfaces";

function applyDistance(particle: Particle): void {
Expand Down Expand Up @@ -73,26 +73,28 @@ export class Mover {
}

private moveParticle(particle: Particle, delta: IDelta): void {
const particlesOptions = particle.options;
const particleOptions = particle.options;
const moveOptions = particleOptions.move;

if (!particlesOptions.move.enable) {
if (!moveOptions.enable) {
return;
}

const container = this.container;
const slowFactor = this.getProximitySpeedFactor(particle);
const baseSpeed =
(particle.moveSpeed ?? getRangeValue(particle.options.move.speed) * container.retina.pixelRatio) *
container.retina.reduceFactor;
const maxSize = getRangeMax(particle.options.size.value) * container.retina.pixelRatio;
const sizeFactor = particlesOptions.move.size ? particle.getRadius() / maxSize : 1;
const moveSpeed = (baseSpeed / 2) * sizeFactor * slowFactor * delta.factor;
const moveDrift =
particle.moveDrift ?? getRangeValue(particle.options.move.drift) * container.retina.pixelRatio;
const container = this.container,
slowFactor = this.getProximitySpeedFactor(particle),
baseSpeed =
(particle.moveSpeed ?? getRangeValue(moveOptions.speed) * container.retina.pixelRatio) *
container.retina.reduceFactor,
moveDrift = particle.moveDrift ?? getRangeValue(particle.options.move.drift) * container.retina.pixelRatio,
maxSize = getRangeMax(particleOptions.size.value) * container.retina.pixelRatio,
sizeFactor = moveOptions.size ? particle.getRadius() / maxSize : 1,
diffFactor = 2,
speedFactor = (sizeFactor * slowFactor * delta.factor) / diffFactor,
moveSpeed = baseSpeed * speedFactor;

this.applyPath(particle, delta);

const gravityOptions = particlesOptions.move.gravity;
const gravityOptions = moveOptions.gravity;
const gravityFactor = gravityOptions.enable && gravityOptions.inverse ? -1 : 1;

if (gravityOptions.enable) {
Expand Down Expand Up @@ -124,45 +126,78 @@ export class Mover {
const zIndexOptions = particle.options.zIndex,
zVelocityFactor = 1 - zIndexOptions.velocityRate * particle.zIndexFactor;

velocity.multTo(zVelocityFactor);
if (moveOptions.spin.enable) {
this.spin(particle, moveSpeed);
} else {
velocity.multTo(zVelocityFactor);

particle.position.addTo(velocity);
particle.position.addTo(velocity);

if (particlesOptions.move.vibrate) {
particle.position.x += Math.sin(particle.position.x * Math.cos(particle.position.y));
particle.position.y += Math.cos(particle.position.y * Math.sin(particle.position.x));
}

const initialPosition = particle.initialPosition;
const initialDistance = getDistance(initialPosition, particle.position);

if (particle.maxDistance) {
if (initialDistance >= particle.maxDistance && !particle.misplaced) {
particle.misplaced = initialDistance > particle.maxDistance;
particle.velocity.x = particle.velocity.y / 2 - particle.velocity.x;
particle.velocity.y = particle.velocity.x / 2 - particle.velocity.y;
} else if (initialDistance < particle.maxDistance && particle.misplaced) {
particle.misplaced = false;
} else if (particle.misplaced) {
if (
(particle.position.x < initialPosition.x && particle.velocity.x < 0) ||
(particle.position.x > initialPosition.x && particle.velocity.x > 0)
) {
particle.velocity.x *= -Math.random();
}
if (moveOptions.vibrate) {
particle.position.x += Math.sin(particle.position.x * Math.cos(particle.position.y));
particle.position.y += Math.cos(particle.position.y * Math.sin(particle.position.x));
}

if (
(particle.position.y < initialPosition.y && particle.velocity.y < 0) ||
(particle.position.y > initialPosition.y && particle.velocity.y > 0)
) {
particle.velocity.y *= -Math.random();
const initialPosition = particle.initialPosition;
const initialDistance = getDistance(initialPosition, particle.position);

if (particle.maxDistance) {
if (initialDistance >= particle.maxDistance && !particle.misplaced) {
particle.misplaced = initialDistance > particle.maxDistance;
particle.velocity.x = particle.velocity.y / 2 - particle.velocity.x;
particle.velocity.y = particle.velocity.x / 2 - particle.velocity.y;
} else if (initialDistance < particle.maxDistance && particle.misplaced) {
particle.misplaced = false;
} else if (particle.misplaced) {
if (
(particle.position.x < initialPosition.x && particle.velocity.x < 0) ||
(particle.position.x > initialPosition.x && particle.velocity.x > 0)
) {
particle.velocity.x *= -Math.random();
}

if (
(particle.position.y < initialPosition.y && particle.velocity.y < 0) ||
(particle.position.y > initialPosition.y && particle.velocity.y > 0)
) {
particle.velocity.y *= -Math.random();
}
}
}
}

applyDistance(particle);
}

private spin(particle: Particle, moveSpeed: number): void {
const container = this.container;

if (!particle.spin) {
return;
}

const updateFunc = {
x: particle.spin.direction === RotateDirection.clockwise ? Math.cos : Math.sin,
y: particle.spin.direction === RotateDirection.clockwise ? Math.sin : Math.cos,
};

particle.position.x = particle.spin.center.x + particle.spin.radius * updateFunc.x(particle.spin.angle);
particle.position.y = particle.spin.center.y + particle.spin.radius * updateFunc.y(particle.spin.angle);
particle.spin.radius += particle.spin.acceleration;

const maxCanvasSize = Math.min(container.canvas.size.width, container.canvas.size.height);

if (particle.spin.radius > maxCanvasSize / 2) {
particle.spin.radius = maxCanvasSize / 2;
particle.spin.acceleration *= -1;
} else if (particle.spin.radius < 0) {
particle.spin.radius = 0;
particle.spin.acceleration *= -1;
}

particle.spin.angle += (moveSpeed / 100) * (1 - particle.spin.radius / maxCanvasSize);
}

private applyPath(particle: Particle, delta: IDelta): void {
const particlesOptions = particle.options;
const pathOptions = particlesOptions.move.path;
Expand Down
4 changes: 4 additions & 0 deletions engine/src/Core/Retina.ts
Expand Up @@ -119,6 +119,10 @@ export class Retina {
particle.sizeAnimationSpeed = options.size.animation.speed * ratio;
particle.orbitRadius = orbit?.radius !== undefined ? orbit.radius * ratio : undefined;

if (particle.spin) {
particle.spin.acceleration = getRangeValue(options.move.spin.acceleration) * ratio;
}

const maxDistance = particle.maxDistance;

maxDistance.horizontal = moveDistance.horizontal !== undefined ? moveDistance.horizontal * ratio : undefined;
Expand Down
7 changes: 6 additions & 1 deletion engine/src/Options/Classes/Particles/Move/Move.ts
Expand Up @@ -9,7 +9,8 @@ import { MoveAngle } from "./MoveAngle";
import { MoveGravity } from "./MoveGravity";
import { OutModes } from "./OutModes";
import { deepExtend, setRangeValue } from "../../../../Utils";
import { IDistance } from "../../../../Core/Interfaces";
import type { IDistance } from "../../../../Core/Interfaces";
import { Spin } from "./Spin";

/**
* [[include:Options/Particles/Move.md]]
Expand Down Expand Up @@ -107,6 +108,7 @@ export class Move implements IMove, IOptionLoader<IMove> {
random;
size;
speed: RangeValue;
spin;
straight;
trail;
vibrate;
Expand All @@ -126,6 +128,7 @@ export class Move implements IMove, IOptionLoader<IMove> {
this.random = false;
this.size = false;
this.speed = 2;
this.spin = new Spin();
this.straight = false;
this.trail = new Trail();
this.vibrate = false;
Expand Down Expand Up @@ -201,6 +204,8 @@ export class Move implements IMove, IOptionLoader<IMove> {
this.speed = setRangeValue(data.speed);
}

this.spin.load(data.spin);

if (data.straight !== undefined) {
this.straight = data.straight;
}
Expand Down

0 comments on commit 240a38f

Please sign in to comment.