2
\$\begingroup\$

This code structure allows me to quickly create and manage behaviours on p5.js sketches. I'm planning to move to Java but wanted feedback beforehand.

The behaviour interface:

interface Behaviour<Element> {
    apply(...elements: Element[]): void;
}

interface LinearBehaviour<Element> extends Behaviour<Element> {
    apply(element: Element): void;
}
interface QuadraticBehaviour<Element> extends Behaviour<Element> {
    apply(left: Element, right: Element): void;
}

The following implementation of a list of behaviours allows me to add behaviours taking in any number of elements:

class Behaviours<Element> implements Behaviour<Element[]> {
    private readonly behaviours: Behaviour<Element>[][] = [];

    apply(elements: Element[]) {
        this.applyBehaviours(elements);
    }

    add(behaviour: Behaviour<Element>) {
        const dimension = behaviour.apply.length;

        if(!this.behaviours[dimension])
            this.behaviours[dimension] = [behaviour];
        else
            this.behaviours[dimension].push(behaviour);
    }

    private applyBehaviours(elements: Element[], dimension = 1, index = 0, previous: Element[] = []) {
        for (let n = index; n < elements.length; n++) {
            let currents = [...previous, elements[n]]

            if (this.behaviours.length > dimension)
                this.applyBehaviours(elements, dimension + 1, n + 1, currents);

            if (this.behaviours[dimension - 1])
                for (let behaviour of this.behaviours[dimension - 1])
                    behaviour.apply(...currents);
        }
    }
}

Implementation examples of behaviours:

class Wrapping implements LinearBehaviour<Particle> {
    constructor(private readonly margin = 0) {}

    apply(helium: Particle): void {
        if (helium.location.x < this.margin)
            helium.location.x = this.width;
        else if (helium.location.x > this.width)
            helium.location.x = this.margin;

        if (helium.location.y < this.margin)
            helium.location.y = this.height;
        else if (helium.location.y > this.height)
            helium.location.y = this.margin;
    }

    /** The canva's width with margins. */
    protected get width() { return width - this.margin; }
    /** The canva's height with margins. */
    protected get height() { return height - this.margin; }
}

class Gravity implements QuadraticBehaviour<Celestial> {
    constructor(public G: number = 1) {}

    /** Applies, to both celestials, Newton's altered law of universal gravitation. */
    apply(celestial: Celestial, melancholia: Celestial): void {
        let gravity = p5.Vector.sub(melancholia.location, celestial.location), // Angle from vector substraction
            distance = gravity.mag(), // Distance from vector substraction
            strength = -1 < distance && distance > 1
                ? (this.G * celestial.mass * melancholia.mass) / distance // Altered formula of gravitation
                : 0;
        gravity.setMag(strength);

        // Add gravity to celestial's acceleration
        celestial.apply(gravity, false);

        // Add reversed gravity to melancholia's acceleration
        gravity.mult(-1);
        melancholia.apply(gravity);
    }
}

With this structure I can easily define a process as follow:

abstract class Process<Element> implements Drawable, Behaviour<Element> {
    protected elements: Element[];

    public readonly forms: Behaviours<Element>;
    public readonly behaviours: Behaviours<Element>;

    protected constructor() {
        this.behaviours = new Behaviours();
        this.forms = new Behaviours();
    }

    /** Draws every form for every element. */
    public draw(): void {
        this.forms.apply(this.elements);
    } 

    /** Applies every behaviour to every element. */
    public apply() {
        this.behaviours.apply(this.elements);
    }
}

And implement a sketch like this:

class Universe extends Process<Atom> {
    public constructor(density = 60) {
        super();

        // Initialize elements
        this.elements = new Array(density);
        for (let n = 0; n < density; n++)
            this.elements[n] = Particle.random(); // Get particle with random values

        // Initialize 
        this.forms.add(new Circle(2)); // Draws ellements as circles with radius 2

        // Initialize behaviours
        this.behaviours.add(new Update()); // Update every atom's location
        this.behaviours.add(new Wrapping(-100)); // Keeps atoms visible
        this.behaviours.add(new Gravity());
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.