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
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/three.quarks.git
cd three.quarks
- Install dependencies:
npm install
# or
yarn
- 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
- Ensure your code follows the style guidelines and passes all tests.
- Update documentation if you’re adding or changing features.
- Add tests for new functionality.
- 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:
- Core Components - Learn about the building blocks of particle systems
- Advanced Features - Dive into more complex features like custom behaviors
- Examples - See working examples of particle systems
- FAQ & Troubleshooting - Find answers to common questions and issues
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!