All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almasb.fxgl.physics.SAT Maven / Gradle / Ivy

/*
 * FXGL - JavaFX Game Library. The MIT License (MIT).
 * Copyright (c) AlmasB ([email protected]).
 * See LICENSE for details.
 */

package com.almasb.fxgl.physics;

import com.almasb.fxgl.core.collection.Array;
import com.almasb.fxgl.core.collection.UnorderedArray;
import com.almasb.fxgl.core.math.Vec2;
import com.almasb.fxgl.core.pool.Pools;
import com.almasb.fxgl.entity.components.TransformComponent;

/**
 * Separating Axis Theorem based check for collision.
 *
 * @author Almas Baimagambetov ([email protected])
 */
public final class SAT {

    private SAT() {}

    // there can be only 2 axes per angle, hence 2 * 2 = 4
    private static final Array axes = new UnorderedArray<>(4);

    // each hit box has 4 corners
    private static final Array corners1 = new UnorderedArray<>(4);
    private static final Array corners2 = new UnorderedArray<>(4);

    /**
     * Note: NOT thread-safe but GC-friendly.
     *
     * @param box1 hit box 1
     * @param box2 hit box 2
     * @param angle1 angle of hit box 1
     * @param angle2 angle of hit box 2
     * @param t1 transform of hit box 1
     * @param t2 transform of hit box 2
     * @return true if two hit boxes are colliding
     */
    public static boolean isColliding(HitBox box1, HitBox box2, double angle1, double angle2,
                                      TransformComponent t1, TransformComponent t2) {
        populateAxes(angle1);
        populateAxes(angle2);

        corners(box1, angle1, t1, corners1);
        corners(box2, angle2, t2, corners2);

        boolean result = true;

        for (Vec2 axis : axes) {
            float e1Min = getMin(corners1, axis);
            float e1Max = getMax(corners1, axis);

            float e2Min = getMin(corners2, axis);
            float e2Max = getMax(corners2, axis);

            if (e1Max < e2Min || e2Max < e1Min) {
                result = false;
                break;
            }
        }

        cleanArrays();

        return result;
    }

    private static void cleanArrays() {
        for (Vec2 v : axes)
            freeVec(v);

        for (Vec2 v : corners1)
            freeVec(v);

        for (Vec2 v : corners2)
            freeVec(v);

        axes.clear();
        corners1.clear();
        corners2.clear();
    }

    private static void populateAxes(double angle) {
        axes.add(newVec(cos(angle), sin(angle)).normalizeLocal());
        axes.add(newVec(cos(angle + 90), sin(angle + 90)).normalizeLocal());
    }

    private static void corners(HitBox box, double angle, TransformComponent t, Array array) {
        Vec2 origin = new Vec2(t.getRotationOrigin()).addLocal(t.getX(), t.getY());

        // this needs to be scaled accordingly, so               centerX * scale + (1-scale) * pivot.x
        //origin.x = (float) (t.getScaleOrigin().getX() - (t.getScaleOrigin().getX() - origin.x) * t.getScaleX() + origin.x);
        //origin.y = (float) (t.getScaleOrigin().getY() - (t.getScaleOrigin().getY() - origin.y) * t.getScaleY() + origin.y);

        //origin

        Vec2 topLeft = newVec(box.getMinXWorld(), box.getMinYWorld());
        Vec2 topRight = newVec(box.getMaxXWorld(), box.getMinYWorld());
        Vec2 botRight = newVec(box.getMaxXWorld(), box.getMaxYWorld());
        Vec2 botLeft = newVec(box.getMinXWorld(), box.getMaxYWorld());

        array.addAll(topLeft, topRight, botRight, botLeft);

        double cos = cos(angle);
        double sin = sin(angle);

        for (Vec2 v : array) {
            v.subLocal(origin);
            v.set((float)(v.x * cos - v.y * sin), (float)(v.x * sin + v.y * cos));
            v.addLocal(origin);
        }

        freeVec(origin);
    }

    private static float getMin(Array arrayCorners, Vec2 axis) {
        float min = Float.MAX_VALUE;

        for (Vec2 corner : arrayCorners) {
            float value = Vec2.dot(corner, axis);
            if (value < min)
                min = value;
        }

        return min;
    }

    private static float getMax(Array arrayCorners, Vec2 axis) {
        float max = Integer.MIN_VALUE;

        for (Vec2 corner : arrayCorners) {
            float value = Vec2.dot(corner, axis);
            if (value > max)
                max = value;
        }

        return max;
    }

    private static Vec2 newVec(double x, double y) {
        return Pools.obtain(Vec2.class)
                .set((float)x, (float)y);
    }

    private static void freeVec(Vec2 vec) {
        Pools.free(vec);
    }

    private static double cos(double angle) {
        return Math.cos(Math.toRadians(angle));
    }

    private static double sin(double angle) {
        return Math.sin(Math.toRadians(angle));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy