Skip to content

Commit

Permalink
feat: added easings to attract and repulse interactions
Browse files Browse the repository at this point in the history
feat: added factor and maxSpeed to attract and repulse interactions
  • Loading branch information
matteobruni committed May 6, 2021
1 parent 767429d commit 071f515
Show file tree
Hide file tree
Showing 17 changed files with 530 additions and 12 deletions.
10 changes: 10 additions & 0 deletions core/main/src/Enums/Types/EasingType.ts
@@ -0,0 +1,10 @@
export enum EasingType {
easeOutBack = "ease-out-back",
easeOutCirc = "ease-out-circ",
easeOutCubic = "ease-out-cubic",
easeOutQuad = "ease-out-quad",
easeOutQuart = "ease-out-quart",
easeOutQuint = "ease-out-quint",
easeOutExpo = "ease-out-expo",
easeOutSine = "ease-out-sine",
}
1 change: 1 addition & 0 deletions core/main/src/Enums/Types/index.ts
Expand Up @@ -3,3 +3,4 @@ export * from "./ProcessBubbleType";
export * from "./ShapeType";
export * from "./StartValueType";
export * from "./DivType";
export * from "./EasingType";
9 changes: 7 additions & 2 deletions core/main/src/Interactions/External/Attractor.ts
Expand Up @@ -64,6 +64,7 @@ export class Attractor implements IExternalInteractor {

private processAttract(position: ICoordinates, attractRadius: number, area: Range): void {
const container = this.container;
const attractOptions = container.actualOptions.interactivity.modes.attract;
const query = container.particles.quadTree.query(area);

for (const particle of query) {
Expand All @@ -73,8 +74,12 @@ export class Attractor implements IExternalInteractor {
y: dy / distance,
};

const velocity = container.actualOptions.interactivity.modes.attract.speed;
const attractFactor = NumberUtils.clamp((1 - Math.pow(distance / attractRadius, 2)) * velocity, 0, 50);
const velocity = attractOptions.speed * attractOptions.factor;
const attractFactor = NumberUtils.clamp(
NumberUtils.calcEasing(distance / attractRadius, attractOptions.easing) * velocity,
0,
attractOptions.maxSpeed
);

particle.position.x -= normVec.x * attractFactor;
particle.position.y -= normVec.y * attractFactor;
Expand Down
11 changes: 9 additions & 2 deletions core/main/src/Interactions/External/Repulser.ts
Expand Up @@ -113,6 +113,7 @@ export class Repulser implements IExternalInteractor {

private processRepulse(position: ICoordinates, repulseRadius: number, area: Range, divRepulse?: RepulseDiv): void {
const container = this.container;
const repulseOptions = container.actualOptions.interactivity.modes.repulse;
const query = container.particles.quadTree.query(area);

for (const particle of query) {
Expand All @@ -122,8 +123,14 @@ export class Repulser implements IExternalInteractor {
y: dy / distance,
};

const velocity = (divRepulse?.speed ?? container.actualOptions.interactivity.modes.repulse.speed) * 100;
const repulseFactor = NumberUtils.clamp((1 - Math.pow(distance / repulseRadius, 2)) * velocity, 0, 50);
console.log(distance, repulseRadius);

const velocity = (divRepulse?.speed ?? repulseOptions.speed) * repulseOptions.factor;
const repulseFactor = NumberUtils.clamp(
NumberUtils.calcEasing(1 - distance / repulseRadius, repulseOptions.easing) * velocity,
0,
repulseOptions.maxSpeed
);

particle.position.x += normVec.x * repulseFactor;
particle.position.y += normVec.y * repulseFactor;
Expand Down
21 changes: 20 additions & 1 deletion core/main/src/Options/Classes/Interactivity/Modes/Attract.ts
@@ -1,23 +1,30 @@
import type { IAttract } from "../../../Interfaces/Interactivity/Modes/IAttract";
import type { RecursivePartial } from "../../../../Types";
import type { IOptionLoader } from "../../../Interfaces/IOptionLoader";
import { EasingType } from "../../../../Enums";

/**
* @category Options
*/
export class Attract implements IAttract, IOptionLoader<IAttract> {
distance;
duration;
easing;
factor;
maxSpeed;
speed;

constructor() {
this.distance = 200;
this.duration = 0.4;
this.easing = EasingType.easeOutQuad;
this.factor = 1;
this.maxSpeed = 50;
this.speed = 1;
}

load(data?: RecursivePartial<IAttract>): void {
if (data === undefined) {
if (!data) {
return;
}

Expand All @@ -29,6 +36,18 @@ export class Attract implements IAttract, IOptionLoader<IAttract> {
this.duration = data.duration;
}

if (data.easing !== undefined) {
this.easing = data.easing;
}

if (data.factor !== undefined) {
this.factor = data.factor;
}

if (data.maxSpeed !== undefined) {
this.maxSpeed = data.maxSpeed;
}

if (data.speed !== undefined) {
this.speed = data.speed;
}
Expand Down
21 changes: 20 additions & 1 deletion core/main/src/Options/Classes/Interactivity/Modes/RepulseBase.ts
@@ -1,22 +1,29 @@
import type { IRepulseBase } from "../../../Interfaces/Interactivity/Modes/IRepulseBase";
import type { RecursivePartial } from "../../../../Types";
import { EasingType } from "../../../../Enums";

/**
* @category Options
*/
export abstract class RepulseBase implements IRepulseBase {
distance;
duration;
factor;
speed;
maxSpeed;
easing;

constructor() {
this.distance = 200;
this.duration = 0.4;
this.factor = 100;
this.speed = 1;
this.maxSpeed = 50;
this.easing = EasingType.easeOutQuad;
}

load(data?: RecursivePartial<IRepulseBase>): void {
if (data === undefined) {
if (!data) {
return;
}

Expand All @@ -28,8 +35,20 @@ export abstract class RepulseBase implements IRepulseBase {
this.duration = data.duration;
}

if (data.easing !== undefined) {
this.easing = data.easing;
}

if (data.factor !== undefined) {
this.factor = data.factor;
}

if (data.speed !== undefined) {
this.speed = data.speed;
}

if (data.maxSpeed !== undefined) {
this.maxSpeed = data.maxSpeed;
}
}
}
@@ -1,8 +1,13 @@
/**
* @category Options
*/
import { EasingType } from "../../../../Enums";

export interface IAttract {
distance: number;
duration: number;
easing: EasingType;
factor: number;
maxSpeed: number;
speed: number;
}
@@ -1,8 +1,13 @@
/**
* @category Options
*/
import type { EasingType } from "../../../../Enums";

export interface IRepulseBase {
distance: number;
duration: number;
factor: number;
speed: number;
maxSpeed: number;
easing: EasingType;
}
39 changes: 33 additions & 6 deletions core/main/src/Utils/NumberUtils.ts
@@ -1,8 +1,8 @@
import type { IValueWithRandom } from "../Options/Interfaces/IValueWithRandom";
import type { ICoordinates } from "../Core/Interfaces/ICoordinates";
import { MoveDirection, MoveDirectionAlt } from "../Enums/Directions";
import { EasingType, MoveDirection, MoveDirectionAlt } from "../Enums";
import type { IVelocity } from "../Core/Interfaces/IVelocity";
import { RangeValue } from "../Types/RangeValue";
import { RangeValue } from "../Types";
import { Vector } from "../Core/Particle/Vector";

export class NumberUtils {
Expand Down Expand Up @@ -60,9 +60,9 @@ export class NumberUtils {

return value !== undefined
? {
min: Math.min(min, value),
max: Math.max(max, value),
}
min: Math.min(min, value),
max: Math.max(max, value),
}
: NumberUtils.setRangeValue(min, max);
}

Expand Down Expand Up @@ -97,7 +97,7 @@ export class NumberUtils {

/**
* Get Particle base velocity
* @param particle the particle to use for calculating the velocity
* @param direction the direction to use for calculating the velocity
*/
static getParticleBaseVelocity(direction: MoveDirection | keyof typeof MoveDirection | MoveDirectionAlt): Vector {
const baseVelocity = Vector.origin;
Expand Down Expand Up @@ -148,4 +148,31 @@ export class NumberUtils {
static collisionVelocity(v1: Vector, v2: Vector, m1: number, m2: number): Vector {
return Vector.create((v1.x * (m1 - m2)) / (m1 + m2) + (v2.x * 2 * m2) / (m1 + m2), v1.y);
}

static calcEasing(value: number, type: EasingType): number {
switch (type) {
case EasingType.easeOutQuad:
return 1 - (1 - value) ** 2;
case EasingType.easeOutCubic:
return 1 - (1 - value) ** 3;
case EasingType.easeOutQuart:
return 1 - (1 - value) ** 4;
case EasingType.easeOutQuint:
return 1 - (1 - value) ** 5;
case EasingType.easeOutExpo:
return value === 1 ? 1 : 1 - Math.pow(2, -10 * value);
case EasingType.easeOutSine:
return Math.sin((value * Math.PI) / 2);
case EasingType.easeOutBack: {
const c1 = 1.70158;
const c3 = c1 + 1;

return 1 + c3 * Math.pow(value - 1, 3) + c1 * Math.pow(value - 1, 2);
}
case EasingType.easeOutCirc:
return Math.sqrt(1 - Math.pow(value - 1, 2));
default:
return value;
}
}
}
59 changes: 59 additions & 0 deletions demo/main/public/presets/repulseBack.json
@@ -0,0 +1,59 @@
{
"fpsLimit": 60,
"particles": {
"number": {
"value": 200,
"density": {
"enable": true,
"area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle"
},
"opacity": {
"value": 0.5
},
"size": {
"value": {
"min": 1,
"max": 3
}
},
"move": {
"enable": true,
"speed": 0,
"direction": "none",
"random": false,
"straight": false,
"outModes": {
"default": "out"
}
}
},
"interactivity": {
"detectsOn": "canvas",
"events": {
"onHover": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"repulse": {
"distance": 200,
"factor": 1,
"speed": 5,
"easing": "ease-out-back"
}
}
},
"detectRetina": true,
"background": {
"color": "#000000"
}
}
59 changes: 59 additions & 0 deletions demo/main/public/presets/repulseCirc.json
@@ -0,0 +1,59 @@
{
"fpsLimit": 60,
"particles": {
"number": {
"value": 200,
"density": {
"enable": true,
"area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle"
},
"opacity": {
"value": 0.5
},
"size": {
"value": {
"min": 1,
"max": 3
}
},
"move": {
"enable": true,
"speed": 0,
"direction": "none",
"random": false,
"straight": false,
"outModes": {
"default": "out"
}
}
},
"interactivity": {
"detectsOn": "canvas",
"events": {
"onHover": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"repulse": {
"distance": 200,
"factor": 1,
"speed": 5,
"easing": "ease-out-circ"
}
}
},
"detectRetina": true,
"background": {
"color": "#000000"
}
}

0 comments on commit 071f515

Please sign in to comment.