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

com.almasb.fxgl.physics.BoundingShape.kt Maven / Gradle / Ivy

There is a newer version: 21.1
Show newest version
/*
 * 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.math.Vec2
import com.almasb.fxgl.entity.components.BoundingBoxComponent
import com.almasb.fxgl.physics.box2d.collision.shapes.ChainShape
import com.almasb.fxgl.physics.box2d.collision.shapes.CircleShape
import com.almasb.fxgl.physics.box2d.collision.shapes.PolygonShape
import com.almasb.fxgl.physics.box2d.collision.shapes.Shape
import javafx.geometry.Dimension2D
import javafx.geometry.Point2D
import java.lang.RuntimeException

/**
 * Defines bounding shapes to be used for hit boxes in local coord system.
 *
 * @author Almas Baimagambetov (AlmasB) ([email protected])
 */
sealed class BoundingShape(val size: Dimension2D) {

    abstract fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape

    override fun toString(): String = javaClass.simpleName.substringBefore("ShapeData")

    companion object {

        /**
         * Constructs new circular bounding shape with given radius.
         *
         * @param radius circle radius
         * @return circular bounding shape
         */
        @JvmStatic fun circle(radius: Double): BoundingShape {
            return CircleShapeData(radius)
        }

        /**
         * Constructs new rectangular bounding shape with given width and height.
         *
         * @param width box width
         * @param height box height
         * @return rectangular bounding shape
         */
        @JvmStatic fun box(width: Double, height: Double): BoundingShape {
            return BoxShapeData(width, height)
        }

        /**
         * Constructs new chain shaped bounding shape.
         * Note: chain shape can only be used with static objects.
         * Note: chain shape must have at least 2 points
         *
         * @param points points to use in a chain
         * @return closed chain bounding shape
         * @throws IllegalArgumentException if number of points is less than 2
         */
        @JvmStatic fun chain(vararg points: Point2D): BoundingShape {
            if (points.size < 2)
                throw IllegalArgumentException("Chain shape requires at least 2 points. Given points: " + points.size)

            var maxX = points[0].x
            var maxY = points[0].y

            for (p in points) {
                if (p.x > maxX) {
                    maxX = p.x
                }

                if (p.y > maxY) {
                    maxY = p.y
                }
            }

            return ChainShapeData(Dimension2D(maxX, maxY), points as Array)
        }

        @JvmStatic fun polygon(points: List): BoundingShape {
            return polygon(*points.toTypedArray())
        }

        @JvmStatic fun polygonFromDoubles(points: List): BoundingShape {
            val array = DoubleArray(points.size)

            for (i in points.indices) {
                array[i] = points[i]
            }

            return polygon(*points.toDoubleArray())
        }

        @JvmStatic fun polygon(vararg points: Double): BoundingShape {
            val array = Array(points.size / 2) { Point2D.ZERO }
            for (i in array.indices) {
                val x = points[i * 2]
                val y = points[i * 2 + 1]

                array[i] = Point2D(x, y)
            }

            return polygon(*array)
        }

        @JvmStatic fun polygon(vararg points: Point2D): BoundingShape {
            if (points.size < 3)
                throw IllegalArgumentException("Polygon shape requires at least 3 points. Given points: " + points.size)

            var maxX = points[0].x
            var maxY = points[0].y

            for (p in points) {
                if (p.x > maxX) {
                    maxX = p.x
                }

                if (p.y > maxY) {
                    maxY = p.y
                }
            }

            return PolygonShapeData(Dimension2D(maxX, maxY), points as Array)
        }

        @JvmStatic fun box3D(width: Double, height: Double, depth: Double): BoundingShape {
            return Box3DShapeData(width, height, depth)
        }
    }
}

class CircleShapeData(val radius: Double) : BoundingShape(Dimension2D(radius * 2, radius * 2)) {

    override fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape {
        // take world center bounds and subtract from entity center (all in pixels) to get local center
        // because box2d operates on vector offsets from the body center, also in local coordinates
        val boundsCenterLocal = box.centerWorld.subtract(bboxComp.centerWorld)
        
        return CircleShape(conv.toVector(boundsCenterLocal), conv.toMetersF(box.width / 2.0))
    }
}

class BoxShapeData(val width: Double, val height: Double) : BoundingShape(Dimension2D(width, height)) {

    override fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape {
        val boundsCenterLocal = box.centerWorld.subtract(bboxComp.centerWorld)

        val shape = PolygonShape()
        shape.setAsBox(conv.toMetersF(box.width / 2), conv.toMetersF(box.height / 2), conv.toVector(boundsCenterLocal), 0f)

        return shape
    }
}

class PolygonShapeData(size: Dimension2D, val points: Array) : BoundingShape(size) {

    override fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape {
        val boundsCenterLocal = box.centerWorld.subtract(bboxComp.centerWorld)
        val bboxCenterLocal = bboxComp.centerLocal
        val t = bboxComp.transform

        val vertices = arrayOfNulls(points.size)

        val bboxCenterLocalNew = Point2D(
                bboxCenterLocal.x * t.scaleX + (1 - t.scaleX) * t.scaleOrigin.x,
                bboxCenterLocal.y * t.scaleY + (1 - t.scaleY) * t.scaleOrigin.y
        )

        val boundsCenterLocalNew = Point2D(
                boundsCenterLocal.x * t.scaleX + (1 - t.scaleX) * t.scaleOrigin.x,
                boundsCenterLocal.y * t.scaleY + (1 - t.scaleY) * t.scaleOrigin.y
        )

        for (i in vertices.indices) {

            val p = Point2D(
                    (points[i].x + box.minX) * t.scaleX + (1 - t.scaleX) * t.scaleOrigin.x,
                    (points[i].y + box.minY) * t.scaleY + (1 - t.scaleY) * t.scaleOrigin.y
            )

            vertices[i] = conv.toVector(p.subtract(boundsCenterLocalNew))
                    .subLocal(conv.toVector(bboxCenterLocalNew))
                    .addLocal(conv.toVector(boundsCenterLocalNew))
                    .subLocal(conv.toMeters(bboxComp.getMinXLocal()), -conv.toMeters(bboxComp.getMinYLocal()))
        }

        val shape = PolygonShape()
        shape.set(vertices)

        return shape
    }
}

/**
 * Effectively, a polyline.
 */
class ChainShapeData(size: Dimension2D, val points: Array) : BoundingShape(size) {

    override fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape {
        val boundsCenterLocal = box.centerWorld.subtract(bboxComp.centerWorld)

        val vertices = arrayOfNulls(points.size)

        for (i in vertices.indices) {
            vertices[i] = conv.toVector(points[i].subtract(boundsCenterLocal)).subLocal(conv.toVector(bboxComp.centerLocal))
        }

        val shape = ChainShape()
        shape.createLoop(vertices, vertices.size)

        return shape
    }
}

class Box3DShapeData(val width: Double, val height: Double, val depth: Double) : BoundingShape(Dimension2D(width, height)) {

    override fun toBox2DShape(box: HitBox, bboxComp: BoundingBoxComponent, conv: PhysicsUnitConverter): Shape {
        throw RuntimeException("2D shape is not supported for Box3D")
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy