Skip to Content
Quarks
DocumentationContributing

Contributing to Quarks VFX Engine

Thank you for your interest in contributing to three.quarks! This guide will help you get started with the development process and outline how you can contribute to the project.

Development Setup

Prerequisites

Getting Started

  1. Fork the repository on GitHub.
  2. Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/three.quarks.git cd three.quarks
  1. Install dependencies:
npm install # or yarn
  1. Create a branch for your changes:
git checkout -b feature/your-feature-name

Development Workflow

Building the Project

To build the project:

npm run build # or yarn build

Running Tests

To run the test suite:

npm test # or yarn test

Development Server

To start the development server with examples:

npm run dev # or yarn dev

This will start a local server, typically at http://localhost:3000.

Coding Guidelines

TypeScript

All code should be written in TypeScript. Here’s an example of a properly typed component:

import { Vector3, Vector4 } from "three"; import { Behavior, Particle } from "three.quarks"; /** * A custom behavior that applies a force in a specific direction */ export class DirectionalForce implements Behavior { private force: Vector3; private color: Vector4 | null; /** * @param direction - The direction of the force * @param strength - The strength of the force * @param applyColor - Whether to change particle color based on force */ constructor( direction: Vector3, private strength: number = 1.0, applyColor: boolean = false, ) { this.force = direction.normalize().multiplyScalar(strength); this.color = applyColor ? new Vector4(0.5, 0.7, 1.0, 1.0) : null; } /** * Initialize the behavior */ initialize(particle: Particle): void { // Initialization code if needed if (this.color && particle.startColor) { particle.startColor.copy(this.color); } } /** * Update the particle based on this behavior * @param particle - The particle to update * @param deltaTime - Time since last update in seconds */ update(particle: Particle, deltaTime: number): void { // Apply force to velocity particle.velocity.add(this.force.clone().multiplyScalar(deltaTime)); } /** * Clone this behavior * @returns A new instance of DirectionalForce with the same properties */ clone(): DirectionalForce { return new DirectionalForce( this.force.clone().normalize(), this.strength, this.color !== null, ); } }

Code Style

  • Use 2 spaces for indentation
  • Use semicolons
  • Follow the TypeScript ESLint recommended rules
  • Add JSDoc comments for all public APIs

Commit Messages

Follow the Conventional Commits specification:

feat: add new directional force behavior fix: correct particle lifetime calculation docs: update API documentation for emitters test: add tests for particle system serialization refactor: improve performance of particle update loop

Pull Request Process

  1. Ensure your code follows the style guidelines and passes all tests.
  2. Update documentation if you’re adding or changing features.
  3. Add tests for new functionality.
  4. Submit a pull request with a clear description of the changes and any relevant issue numbers.

Adding New Components

Creating a New Behavior

Here’s how to create a new behavior:

import { Behavior, Particle } from "three.quarks"; /** * Behavior that simulates wind effects on particles */ export class WindBehavior implements Behavior { /** * @param direction - Wind direction vector * @param speed - Wind speed * @param turbulence - Amount of random variation */ constructor( private direction: THREE.Vector3, private speed: number = 1.0, private turbulence: number = 0.1, ) { this.direction.normalize(); } initialize(particle: Particle): void { // No initialization needed } update(particle: Particle, deltaTime: number): void { // Apply main wind force const force = this.direction.clone().multiplyScalar(this.speed * deltaTime); // Add turbulence if (this.turbulence > 0) { force.x += (Math.random() - 0.5) * this.turbulence * deltaTime; force.y += (Math.random() - 0.5) * this.turbulence * deltaTime; force.z += (Math.random() - 0.5) * this.turbulence * deltaTime; } particle.velocity.add(force); } clone(): WindBehavior { return new WindBehavior( this.direction.clone(), this.speed, this.turbulence, ); } }

Creating a New Emitter

Here’s an example of creating a custom emitter:

import { EmitterShape, Particle, ParticleSystem, EmitterMode, ValueGenerator, FunctionValueGenerator, GeneratorMemory, } from "three.quarks"; import * as THREE from "three"; /** * Options for the SpiralEmitter */ export interface SpiralEmitterParameters { radius: number; riseSpeed: number; rotationSpeed: number; arc: number; thickness: number; mode: EmitterMode; spread: number; speed: ValueGenerator | FunctionValueGenerator; } /** * Emits particles in a spiral pattern */ export class SpiralEmitter implements EmitterShape { type = "spiral"; radius: number; riseSpeed: number; rotationSpeed: number; arc: number; thickness: number; mode: EmitterMode; spread: number; speed: ValueGenerator | FunctionValueGenerator; memory: GeneratorMemory; private currentValue = 0; private _m1: Matrix4; constructor(parameters: SpiralEmitterParameters = {}) { this.radius = parameters.radius ?? 10; this.riseSpeed = parameters.riseSpeed ?? 0.5; this.rotationSpeed = parameters.rotationSpeed ?? 2.0; this.arc = parameters.arc ?? 2.0 * Math.PI; this.thickness = parameters.thickness ?? 1; this.mode = parameters.mode ?? EmitterMode.Random; this.spread = parameters.spread ?? 0; this.speed = parameters.speed ?? new ConstantValue(1); this.memory = []; this._m1 = new Matrix4(); } update(system: IParticleSystem, delta: number): void { if (EmitterMode.Random != this.mode) { this.currentValue += this.speed.genValue( this.memory, system.emissionState.time / system.duration, ) * delta; } } initialize(p: Particle, emissionState: EmissionState) { const u = getValueFromEmitterMode( this.mode, this.currentValue, this.spread, emissionState, ); const rand = MathUtils.lerp(1 - this.thickness, 1, Math.random()); const theta = u * this.arc; const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); p.position.x = this.radius * cosTheta; p.position.y = this.riseSpeed * theta; p.position.z = this.radius * sinTheta; p.velocity .set( -this.rotationSpeed * sinTheta, this.riseSpeed, this.rotationSpeed * cosTheta, ) .multiplyScalar(p.startSpeed * rand); if (p.rotation instanceof Quaternion) { this._m1.lookAt(ZERO_VEC3, p.velocity, UP_VEC3); p.rotation.setFromRotationMatrix(this._m1); } } toJSON(): ShapeJSON { return { type: "spiral", radius: this.radius, riseSpeed: this.riseSpeed, rotationSpeed: this.rotationSpeed, arc: this.arc, thickness: this.thickness, mode: this.mode, spread: this.spread, speed: this.speed.toJSON(), }; } static fromJSON(json: any): SpiralEmitter { return new SpiralEmitter({ radius: json.radius, riseSpeed: json.riseSpeed, rotationSpeed: json.rotationSpeed, arc: json.arc, thickness: json.thickness, mode: json.mode, speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined, spread: json.spread, }); } clone(): EmitterShape { return new SpiralEmitter({ radius: this.radius, riseSpeed: this.riseSpeed, rotationSpeed: this.rotationSpeed, arc: this.arc, thickness: this.thickness, mode: this.mode, speed: this.speed.clone(), spread: this.spread, }); } }

Documentation

When adding new features, please update the relevant documentation files. Documentation is written in MDX format.

Next Steps

Now that you know how to contribute to three.quarks, you might want to explore:

If you have any questions about contributing, please open an issue on GitHub or reach out to the maintainers.

Thank you for helping improve three.quarks!

Last updated on