Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combining shapes #1225

Open
6 of 7 tasks
lhunath opened this issue Jan 14, 2021 · 5 comments
Open
6 of 7 tasks

Combining shapes #1225

lhunath opened this issue Jan 14, 2021 · 5 comments
Labels
documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers Hacktoberfest help wanted Extra attention is needed no-issue-activity pinned Pinned issues plugin A feature that could become a plugin sweep Assigns Sweep to an issue or pull request. up-for-grabs

Comments

@lhunath
Copy link

lhunath commented Jan 14, 2021

Which version are you using?
1.18.0

Is the latest version affected?

  • Yes
  • No

Which library are you using?

  • Vanilla JS (tsparticles)

Is your feature request related to a problem? Please describe.
I need particles that are characters with a background. There does not appear to be a way to achieve this. This feature request proposes a solution that would allow for a lot of flexibility.

Describe the solution you'd like
I'd like to propose particles that allow combining multiple types of shapes into one.

For instance, one might define:

     "type": [ "circle", "character" ],

The resulting effect would behave as if both shapes were layered on top of each other when rendered.

A concern would be how the particle's configuration is applied. A simple solution would be to apply the same configuration to both shapes of the particle (ie. position, color, etc). A more ambitions solution would allow for configuration to specify a shape layer to apply the configuration onto, allowing, for example, a background circle shape with a different color from the foreground character shape.

Describe alternatives you've considered
Alternative solutions:

  1. Add properties to the "character" shape that allow for a background shape.
  2. Add a generic "parent" property to a shape which allows creating a hierarchy of shapes.
  3. Manually rendering characters into a pre-rendered bitmap that combines the shapes, then use the image shape instead of the character and circle shapes. This is very inflexible, doesn't allow for dynamic content and very tedious for static content.

Additional context
If there are other solutions, please advise!

Side questions

  1. Why is there a character and a char shape type?
  2. There doesn't appear to be good documentation out there for all known configuration values (eg. what is sync, how does speed work, frequency, how to use character style, etc)
Checklist
  • shapes/combined/src/CombinedDrawer.ts ✅ Commit 54b5939
• Import necessary classes and interfaces from "tsparticles-engine". • Create a new class "CombinedDrawer" that implements the "IShapeDrawer" interface. • Add an array property to the "CombinedDrawer" class that will hold the shapes to combine. • Implement the "draw" method to draw the shapes in the array in the correct order. • Implement the "getSidesCount" method to return the maximum sides count of the shapes in the array. • Export the "CombinedDrawer" class.
  • engine/src/Options/Interfaces/IOptions.ts ❌ Failed
• Add "combined" to the list of valid shape types.
  • engine/src/Core/Particle.ts ❌ Failed
• Modify the "init" method to check if the particle's shape type is "combined" and then initialize the shapes in the "combined" shape's array.
  • engine/src/Utils/CanvasUtils.ts ✅ Commit c7f3460
• Modify the "drawShape" and "drawShapeAfterEffect" methods to check if the particle's shape type is "combined" and then draw the shapes in the "combined" shape's array in the correct order.
@xscode-auto-reply
Copy link

Thanks for opening a new issue. The team has been notified and will review it as soon as possible.

For urgent issues and priority support, visit https://xscode.com/matteobruni/tsparticles

@matteobruni
Copy link
Collaborator

Hi @lhunath,

Thanks for the suggestions!

I try to answer all of them:

  • Particles already allow multiple types but they're random picked to give a single shape to any particles, maybe a new shape type like combined with extra options for types to combine (like an array that describes also the drawing order) is the easiest way to achieve it.
  • Starting from the point above a new shape type can be done externally from the main project like spiral, heart and others. Since 2.0 is on its way this will be the solution.
  • characterand char are the same shape type, they are both available for compatibility of older versions
  • sync is animation related for color, rotate, size and opacity. If specified in the animation object the related animation is synchronized between all particles (except those one pushed with the onClick event or the one spawned from an emitter)
  • speed I don't know how to explain it since it looks clear for the name, it's the animation speed or the particle speed, they are both called speed because they are both a speed property.
  • frequency is a little more complex, it's used as a threshold for a random value (between 0 and 1) to draw or not an object (links for example) or to apply an effect or not (twinkle for example)
  • character style property is part of the standard HTML canvas font string: you can see the composition here. For more documentation of additional options you can see here

Hope to have answered all questions, and thanks again for the combined shapes suggestion, it's interesting to bring it to 2.0

@matteobruni matteobruni added Core documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed pinned Pinned issues plugin A feature that could become a plugin up-for-grabs labels Jan 14, 2021
@matteobruni matteobruni added this to the 2.0.0 milestone Jan 14, 2021
@matteobruni matteobruni added this to To do in Low Priority via automation Jan 14, 2021
@matteobruni matteobruni pinned this issue Jan 14, 2021
@github-actions
Copy link

Stale issue message

@matteobruni matteobruni removed this from the 2.0.0 milestone Dec 7, 2021
@matteobruni matteobruni unpinned this issue Apr 15, 2022
@matteobruni matteobruni removed the Core label May 15, 2023
@matteobruni matteobruni added the sweep Assigns Sweep to an issue or pull request. label Sep 18, 2023
@sweep-ai
Copy link

sweep-ai bot commented Sep 18, 2023

Here's the PR! #5213.

⚡ Sweep Free Trial: I used GPT-4 to create this ticket. You have 4 GPT-4 tickets left for the month and 2 for the day. For more GPT-4 tickets, visit our payment portal.


Actions (click)

  • Restart Sweep

Step 1: 🔎 Searching

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.

import type { IOptionsColor } from "../IOptionsColor";
/**
* The background options used by the canvas element, it's not drawn, it's applied in the style
* [[include:Options/Background.md]]
*/
export interface IBackground {
/**
* The `color` property can be set to a HEX string or to a {@link IOptionsColor | color object}, that is the same as the one used in `particles.color` options.
*
* This color is set to canvas style `background-color` property, if this property is not set the background will be transparent.
*/
color: string | IOptionsColor;
/**
* The `image` property sets the canvas style `background-image` property.
*
* This property doesn't have a default value, anyway if you need a background image you need to specify the same CSS syntax with the `url()` function.
*/
image: string;
/**
* The `opacity` property sets the `color` property opacity, so you can set a semi-transparent background.
*
* This value is by default to `1` and it accepts any value between `0` and `1` included.
*/
opacity: number;
/**
* The `position` property sets the canvas style `background-position` property.
*
* This [link](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position) can be useful to set the right value to this property.
*/
position: string;
/**
* The `repeat` property sets the canvas style `background-repeat` property.
*
* This [link](https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat) can be useful to set the right value to this property.
*/
repeat: string;
/**
* The `size` property sets the canvas style `background-size` property.
*
* This [link](https://developer.mozilla.org/en-US/docs/Web/CSS/background-size) can be useful to set the right value to this property.
*/
size: string;

import type {
IAnimatableColor,
IParticlesOptions,
IRangedCoordinates,
MoveDirection,
MoveDirectionAlt,
RecursivePartial,
SingleOrMultiple,
} from "tsparticles-engine";
import type { EmitterShapeType } from "../../Enums/EmitterShapeType";
import type { IEmitterLife } from "./IEmitterLife";
import type { IEmitterRate } from "./IEmitterRate";
import type { IEmitterSize } from "./IEmitterSize";
/**
* Particles emitter object options
* [[include:Options/Plugins/Emitters.md]]
*/
export interface IEmitter {
/**
* Starts the emitter automatically
*/
autoPlay: boolean;
/**
* The direction of the emitted particles, {@link MoveDirection} is the enum used for values
*/
direction?: MoveDirection | keyof typeof MoveDirection | MoveDirectionAlt | number;
/**
* Using this id to link the emitter to an HTML element
*/
domId?: string;
/**
* Sets if the particles will spawn at the emitter perimeter or inside the area
*/
fill: boolean;
/**
* The emitter life options
*/
life: IEmitterLife;
/**
* The emitter name
*/
name?: string;
/**
* Particles emitted customization.
* These settings will overrides other particles settings for the particles emitted by this emitter
* Particles number options won't override anything, they will be ignored completely
*/
particles?: SingleOrMultiple<RecursivePartial<IParticlesOptions>>;
/**
* The relative position (in percent) of the emitter, where particles spawns.
* If size is specified the position will be the center of the size options
*/
position?: RecursivePartial<IRangedCoordinates>;
/**
* The particles emitting rate options
*/
rate: IEmitterRate;
/**
* The emitter shape type (circle or square)
*/
shape: EmitterShapeType | keyof typeof EmitterShapeType;
/**
* The size of the particles emitter area
*/
size?: IEmitterSize;
/**
* The particle spawn color
*/
spawnColor?: IAnimatableColor;
/**
* The number of starting particles of the emitter
*/
startCount: number;

context.shadowBlur = shadow.blur;
context.shadowColor = getStyleFromRgb(shadowColor);
context.shadowOffsetX = shadow.offset.x;
context.shadowOffsetY = shadow.offset.y;
}
if (colorStyles.fill) {
context.fillStyle = colorStyles.fill;
}
const strokeWidth = particle.strokeWidth ?? 0;
context.lineWidth = strokeWidth;
if (colorStyles.stroke) {
context.strokeStyle = colorStyles.stroke;
}
drawShape(container, context, particle, radius, opacity, delta);
if (strokeWidth > 0) {
context.stroke();
}
if (particle.close) {
context.closePath();
}
if (particle.fill) {
context.fill();
}
drawShapeAfterEffect(container, context, particle, radius, opacity, delta);
context.globalCompositeOperation = "source-over";
context.setTransform(1, 0, 0, 1, 0, 0);
}
/**
* Draws the particle shape using the plugin's shape renderer.
* @param container - The container of the particle.
* @param context - The canvas context.
* @param particle - The particle to draw.
* @param radius - The radius of the particle.
* @param opacity - The opacity of the particle.
* @param delta - this variable contains the delta between the current frame and the previous frame
*/
export function drawShape(
container: Container,
context: CanvasRenderingContext2D,
particle: Particle,
radius: number,
opacity: number,
delta: IDelta,
): void {
if (!particle.shape) {
return;
}
const drawer = container.drawers.get(particle.shape);
if (!drawer) {
return;
}
drawer.draw(context, particle, radius, opacity, delta, container.retina.pixelRatio);
}
/**
* Draws the particle effect after the plugin's shape renderer.
* @param container - The container of the particle.
* @param context - The canvas context.
* @param particle - The particle to draw.
* @param radius - The radius of the particle.
* @param opacity - The opacity of the particle.
* @param delta - this variable contains the delta between the current frame and the previous frame
*/
export function drawShapeAfterEffect(
container: Container,
context: CanvasRenderingContext2D,
particle: Particle,
radius: number,
opacity: number,
delta: IDelta,
): void {
if (!particle.shape) {
return;
}
const drawer = container.drawers.get(particle.shape);
if (!drawer || !drawer.afterEffect) {
return;
}
drawer.afterEffect(context, particle, radius, opacity, delta, container.retina.pixelRatio);
}
/**
* Draws the given plugin in the canvas.
* @param context - The canvas context.
* @param plugin - The plugin to draw.
* @param delta - this variable contains the delta between the current frame and the previous frame
*/
export function drawPlugin(context: CanvasRenderingContext2D, plugin: IContainerPlugin, delta: IDelta): void {
if (!plugin.draw) {
return;
}
plugin.draw(context, delta);
}
/**
* Draws the given particle plugin in the canvas.
* @param context - The canvas context.
* @param plugin - The particle plugin to draw.
* @param particle - The particle to draw.
* @param delta - this variable contains the delta between the current frame and the previous frame
*/
export function drawParticlePlugin(
context: CanvasRenderingContext2D,
plugin: IContainerPlugin,
particle: Particle,
delta: IDelta,
): void {
if (!plugin.drawParticle) {
return;
}
plugin.drawParticle(context, particle, delta);
}
/**
* Alters HSL values for enlighten or darken the given color.
* @param color - The color to enlighten or darken.
* @param type - The type of alteration.
* @param value - The value of the alteration.
* @returns the altered {@link IHsl} color
*/
export function alterHsl(color: IHsl, type: AlterType, value: number): IHsl {
return {
h: color.h,
s: color.s,
l: color.l + (type === AlterType.darken ? -1 : 1) * value,
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Container } from "../../Core/Container";
import type { IBackground } from "./Background/IBackground";
import type { IBackgroundMask } from "./BackgroundMask/IBackgroundMask";
import type { IFullScreen } from "./FullScreen/IFullScreen";
import type { IInteractivity } from "./Interactivity/IInteractivity";
import type { IManualParticle } from "./IManualParticle";
import type { IParticlesOptions } from "./Particles/IParticlesOptions";
import type { IResponsive } from "./IResponsive";
import type { ITheme } from "./Theme/ITheme";
import type { RangeValue } from "../../Types/RangeValue";
import type { RecursivePartial } from "../../Types/RecursivePartial";
import type { SingleOrMultiple } from "../../Types/SingleOrMultiple";
/**
* The Options interface, defines all the options that can be used by `tsParticles`
* [[include:Options.md]]
*/
export interface IOptions {
/**
* More custom options for external plugins or customizations
*/
[name: string]: unknown;
/**
* Sets if the animations should start automatically or manually
*/
autoPlay: boolean;
/**
* Background options, these background options will be used to the canvas element, they are all CSS properties
*/
background: IBackground;
/**
* Background Mask options, what's behind the canvas will become hidden and particles will uncover it
*/
backgroundMask: IBackgroundMask;
/**
* Sets the animated background mode for particles canvas bringing it to the back
* @deprecated use the new fullScreen instead
*/
backgroundMode: RecursivePartial<IFullScreen> | boolean;
/**
* The initial delay before starting the animation
*/
delay: RangeValue;
/**
* Enables the retina detection, if disabled the ratio used by canvas will be always 1 and not the device setting.
*/
detectRetina: boolean;
/**
* The Particles effect duration in seconds, then the container will be destroyed
*/
duration: RangeValue;
/**
* The FPS (Frame Per Second) limit applied to all particles animations.
*/
fpsLimit: number;
/**
* The Frame Per Second limit applied to all particles animations.
* @deprecated use the new fpsLimit instead
*/
fps_limit: number;
/**
* Sets the animated background mode for particles canvas bringing it to the back
*/
fullScreen: RecursivePartial<IFullScreen> | boolean;
/**
* The particles interaction options
*/
interactivity: IInteractivity;
/**
* Particles inserted at load time with a specific position
*/
manualParticles: IManualParticle[];
name?: string;
/**
* The particles options
*/
particles: IParticlesOptions;
/**
* Enables or disabled the animation on window blur
*/
pauseOnBlur: boolean;
/**
* Enable or disabled the animation if the element is outside the viewport
*/
pauseOnOutsideViewport: boolean;
/**
* This property will be used to add specified presets to the options
*/
preset?: SingleOrMultiple<string>;
/**
* This sets custom options based on canvas size
*/
responsive: IResponsive[];
/**
* Enables the retina detection, if disabled the ratio used by canvas will be always 1 and not the device setting.
* @deprecated use the new detectRetina instead
*/
retina_detect: boolean;
/**
* Enables a smooth effect, by default it's disabled
* When enabled the animation will speed up or slow down depending on fps
* The {@link IOptions.fpsLimit} field will be used as a reference for the animation speed
* Some examples:
* - with a {@link IOptions.fpsLimit} of 60 the animation will be twice faster on 120 fps devices
* - with a {@link IOptions.fpsLimit} of 120 the animation will be twice slower on 60 fps devices
* The animation will be always smooth, but the behavior could be affected by the user screen refresh rate
* It's recommended to keep this disabled, be careful.
*/
smooth: boolean;
style: RecursivePartial<CSSStyleDeclaration>;
/**
* User-defined themes that can be retrieved by the particles {@link Container}
*/
themes: ITheme[];
/**
* The maximum layers used in the z-axis
*/
zLayers: number;

import {
type Container,
type IShapeDrawer,
type SingleOrMultiple,
executeOnSingleOrMultiple,
isInArray,
itemFromSingleOrMultiple,
loadFont,
} from "tsparticles-engine";
import type { ICharacterShape } from "./ICharacterShape";
import type { TextParticle } from "./TextParticle";
export const validTypes = ["text", "character", "char"];
/**
*/
export class TextDrawer implements IShapeDrawer {
draw(context: CanvasRenderingContext2D, particle: TextParticle, radius: number, opacity: number): void {
const character = particle.shapeData as ICharacterShape;
if (character === undefined) {
return;
}
const textData = character.value;
if (textData === undefined) {
return;
}
if (particle.text === undefined) {
particle.text = itemFromSingleOrMultiple(textData, particle.randomIndexData);
}
const text = particle.text,
style = character.style ?? "",
weight = character.weight ?? "400",
size = Math.round(radius) * 2,
font = character.font ?? "Verdana",
fill = particle.fill,
offsetX = (text.length * radius) / 2;
context.font = `${style} ${weight} ${size}px "${font}"`;
const pos = {
x: -offsetX,
y: radius / 2,
};
context.globalAlpha = opacity;
if (fill) {
context.fillText(text, pos.x, pos.y);
} else {
context.strokeText(text, pos.x, pos.y);
}
context.globalAlpha = 1;
}
getSidesCount(): number {
return 12;
}
async init(container: Container): Promise<void> {
const options = container.actualOptions;
if (validTypes.find((t) => isInArray(t, options.particles.shape.type))) {
const shapeOptions = validTypes
.map((t) => options.particles.shape.options[t])
.find((t) => !!t) as SingleOrMultiple<ICharacterShape>,
promises: Promise<void>[] = [];
executeOnSingleOrMultiple(shapeOptions, (shape) => {
promises.push(loadFont(shape.font, shape.weight));
});
await Promise.all(promises);
}
}
/**
* Loads the text shape to the given particle
* @param container - the particles container
* @param particle - the particle loading the text shape
*/
particleInit(container: Container, particle: TextParticle): void {
if (!particle.shape || !validTypes.includes(particle.shape)) {
return;
}
const character = particle.shapeData as ICharacterShape;
if (character === undefined) {
return;
}
const textData = character.value;
if (textData === undefined) {
return;
}
particle.text = itemFromSingleOrMultiple(textData, particle.randomIndexData);
}

I also found the following external resources that might be helpful:

Summaries of links found in the content:

https://www.w3schools.com/tags/canvas_font.asp:

The page is about the HTML canvas font property. It provides a tutorial and reference for using the font property to set or return the font properties for canvas text. The font property uses the same syntax as the CSS font property. The default value is 10px sans-serif. The page also includes an example of how to write text on the canvas using the font property. The page mentions other related properties such as fillStyle, textAlign, and textBaseline. It also provides syntax and property value information for the font property. The page concludes with information about browser support for the canvas element and a link to the canvas reference. There are also comments from users discussing a feature request for particles that combine multiple types of shapes, as well as questions about the character shape and documentation for configuration values.

https://github.com/matteobruni/tsparticles/blob/4340750b22588bc764845f58b2848377b20ca9ee/core/main/src/ShapeDrawers/TextDrawer.ts#L70:

The user is requesting a feature in the tsparticles library that allows combining multiple types of shapes into one particle. They propose adding a new shape type called "combined" that would allow for layering different shapes on top of each other. The user suggests that the configuration for the particle should be applied to both shapes, and potentially allow for different configurations for each layer. The user also asks about the difference between the "character" and "char" shape types, and requests more documentation on certain configuration values. The library maintainer responds by suggesting that the user can create a custom shape to handle the combined shapes, and mentions that a new shape type can be added in the upcoming 2.0 version of the library. The maintainer also provides explanations for the "sync", "speed", "frequency", and "style" configuration properties.


Step 2: ⌨️ Coding

  • shapes/combined/src/CombinedDrawer.ts ✅ Commit 54b5939
• Import necessary classes and interfaces from "tsparticles-engine". • Create a new class "CombinedDrawer" that implements the "IShapeDrawer" interface. • Add an array property to the "CombinedDrawer" class that will hold the shapes to combine. • Implement the "draw" method to draw the shapes in the array in the correct order. • Implement the "getSidesCount" method to return the maximum sides count of the shapes in the array. • Export the "CombinedDrawer" class.
  • engine/src/Options/Interfaces/IOptions.ts ❌ Failed
• Add "combined" to the list of valid shape types.
  • engine/src/Core/Particle.ts ❌ Failed
• Modify the "init" method to check if the particle's shape type is "combined" and then initialize the shapes in the "combined" shape's array.
  • engine/src/Utils/CanvasUtils.ts ✅ Commit c7f3460
• Modify the "drawShape" and "drawShapeAfterEffect" methods to check if the particle's shape type is "combined" and then draw the shapes in the "combined" shape's array in the correct order.

Step 3: 🔁 Code Review

I have finished reviewing the code for completeness. I did not find errors for sweep/combined-shapes.

.


🎉 Latest improvements to Sweep:


💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request.
Join Our Discord

@matteobruni
Copy link
Collaborator

I think this can be closed, if someone needs a combined shape, it can create a custom shape to handle correctly the double values. Just a thought.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers Hacktoberfest help wanted Extra attention is needed no-issue-activity pinned Pinned issues plugin A feature that could become a plugin sweep Assigns Sweep to an issue or pull request. up-for-grabs
Projects
No open projects
Low Priority
  
To do
Development

Successfully merging a pull request may close this issue.

2 participants