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

it.unibo.alchemist.model.cognitive.environments.EnvironmentWithDynamics.kt Maven / Gradle / Ivy

There is a newer version: 35.0.0
Show newest version
/*
 * Copyright (C) 2010-2023, Danilo Pianini and contributors
 * listed, for each module, in the respective subproject's build.gradle.kts file.
 *
 * This file is part of Alchemist, and is distributed under the terms of the
 * GNU General Public License, with a linking exception,
 * as described in the file LICENSE in the Alchemist distribution's top directory.
 */

package it.unibo.alchemist.model.cognitive.environments

import it.unibo.alchemist.model.GlobalReaction
import it.unibo.alchemist.model.Incarnation
import it.unibo.alchemist.model.Node
import it.unibo.alchemist.model.Node.Companion.asProperty
import it.unibo.alchemist.model.environments.Continuous2DEnvironment
import it.unibo.alchemist.model.obstacles.RectObstacle2D
import it.unibo.alchemist.model.physics.environments.ContinuousPhysics2DEnvironment
import it.unibo.alchemist.model.physics.environments.Dynamics2DEnvironment
import it.unibo.alchemist.model.physics.environments.EuclideanPhysics2DEnvironmentWithObstacles
import it.unibo.alchemist.model.physics.environments.Physics2DEnvironment
import it.unibo.alchemist.model.physics.properties.AreaProperty
import it.unibo.alchemist.model.physics.properties.PhysicalPedestrian2D
import it.unibo.alchemist.model.physics.reactions.PhysicsUpdate
import it.unibo.alchemist.model.positions.Euclidean2DPosition
import org.dyn4j.dynamics.Body
import org.dyn4j.dynamics.PhysicsBody
import org.dyn4j.geometry.Circle
import org.dyn4j.geometry.MassType
import org.dyn4j.geometry.Rectangle
import org.dyn4j.geometry.Transform
import org.dyn4j.geometry.Vector2
import org.dyn4j.world.World
import java.awt.Color

private typealias PhysicsEnvWithObstacles =
    EuclideanPhysics2DEnvironmentWithObstacles, T>

/**
 * This Environment uses hooks provided by [Dynamics2DEnvironment] to update
 * the physical world, It also applies physical properties to any added node to
 * perform collision detection and response.
 * If an image path is provided a backing [ImageEnvironmentWithGraph] is used, otherwise
 * the [Continuous2DEnvironment] will be used.
 */
class EnvironmentWithDynamics @JvmOverloads constructor(
    incarnation: Incarnation,
    path: String? = null,
    zoom: Double = 1.0,
    dx: Double = 0.0,
    dy: Double = 0.0,
    obstaclesColor: Int = Color.BLACK.rgb,
    roomsColor: Int = Color.BLUE.rgb,
    private val backingEnvironment: Physics2DEnvironment = path?.let {
        ImageEnvironmentWithGraph(incarnation, it, zoom, dx, dy, obstaclesColor, roomsColor)
    } ?: ContinuousPhysics2DEnvironment(incarnation),
) : Dynamics2DEnvironment,
    PhysicsEnvWithObstacles by backingEnvironment.asEnvironmentWithObstacles() {

    private val world: World = World()

    private val nodeToBody: MutableMap, PhysicsBody> = mutableMapOf()

    private var physicsUpdate = PhysicsUpdate(this, 1.0)

    private var physicsUpdateHasBeenOverriden: Boolean

    init {
        world.gravity = World.ZERO_GRAVITY
        /*
         * This flag is defaulted to true. The engine automatically detects
         * whether a node body stops (its linear velocity is below a certain threshold)
         * and eventually puts it at rest. This means tha in future world.update() calls,
         * the at-rest node will not be considered for the environment update.
         * We do not want this because we always need no move each node, even if the movement is mimimal,
         * in order to progress their cognitive perception for example.
         *
         * For further references: https://dyn4j.org/pages/advanced.html
         */
        world.settings.isAtRestDetectionEnabled = false
        addGlobalReaction(physicsUpdate)
        physicsUpdateHasBeenOverriden = false
        obstacles.forEach { obstacle ->
            addObstacleToWorld(obstacle)
        }
    }

    override fun addGlobalReaction(reaction: GlobalReaction) {
        if (reaction is PhysicsUpdate) {
            require(!physicsUpdateHasBeenOverriden) {
                "${PhysicsUpdate::class.simpleName} reaction had been already overriden"
            }
            removeGlobalReaction(physicsUpdate)
            physicsUpdateHasBeenOverriden = true
            physicsUpdate = reaction
        }
        backingEnvironment.addGlobalReaction(reaction)
    }

    private fun addObstacleToWorld(obstacle: RectObstacle2D) {
        val obstacleBody = Body()
        obstacleBody.addFixture(Rectangle(obstacle.width, obstacle.height))
        obstacleBody.setMass(MassType.INFINITE)
        obstacleBody.transform = Transform().apply {
            translate(obstacle.center)
        }
        world.addBody(obstacleBody)
    }

    private val RectObstacle2D.center get() =
        Vector2(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2)

    override fun addNode(node: Node, position: Euclidean2DPosition): Boolean {
        if (backingEnvironment.addNode(node, position)) {
            addNodeBody(node)
            moveNodeBodyToPosition(node, position)
            return true
        }
        return false
    }

    private fun addNodeBody(node: Node) {
        val nodeBody = Body()
        addPhysicalProperties(nodeBody, node.asProperty>().shape.radius)
        nodeToBody[node] = nodeBody
        world.addBody(nodeBody)
        node.asProperty>()
            .onFall {
                /*
                 * This disables collision response with the falling agent, as
                 * overlapping is allowed in this case.
                 * Agents approaching a falling agent will be subject to a
                 * proper avoidance force. see the PhysicalPedestrian interface.
                 */
                nodeToBody[it]?.isEnabled = false
            }
    }

    private fun moveNodeBodyToPosition(node: Node, position: Euclidean2DPosition) {
        nodeToBody[node]?.transform = Transform().apply {
            translate(position.x, position.y)
        }
    }

    override fun moveNode(node: Node, direction: Euclidean2DPosition) {
        backingEnvironment.moveNode(node, direction)
        moveNodeBodyToPosition(node, backingEnvironment.getPosition(node))
    }

    private fun addPhysicalProperties(body: PhysicsBody, radius: Double) {
        body.addFixture(Circle(radius))
        body.setMass(MassType.NORMAL)
    }

    override fun setVelocity(node: Node, velocity: Euclidean2DPosition) =
        nodeToBody[node]?.let { it.linearVelocity = Vector2(velocity.x, velocity.y) }
            ?: error("Unable to update $node physical state. Check if it was added to this environment.")

    override fun getVelocity(node: Node) = nodeToBody[node]?.let {
        Euclidean2DPosition(it.linearVelocity.x, it.linearVelocity.y)
    } ?: this.origin

    override fun updatePhysics(elapsedTime: Double) {
        world.update(elapsedTime, Int.MAX_VALUE)
        /*
         * Make world and environment position consistent
         */
        nodeToBody.forEach { (node, body) ->
            moveNodeToPosition(node, body.position)
        }
    }

    override fun getPosition(node: Node): Euclidean2DPosition = nodeToBody[node]?.position
        ?: throw IllegalArgumentException("Unable to find $node's position in the environment.")

    private val PhysicsBody.position get() =
        Euclidean2DPosition(this.transform.translationX, this.transform.translationY)

    override val origin: Euclidean2DPosition get() = backingEnvironment.origin

    override fun makePosition(vararg coordinates: Double): Euclidean2DPosition =
        backingEnvironment.makePosition(*coordinates)

    private companion object {

        private fun  Physics2DEnvironment.asEnvironmentWithObstacles(): PhysicsEnvWithObstacles =
            if (this is EuclideanPhysics2DEnvironmentWithObstacles<*, T>) {
                @Suppress("UNCHECKED_CAST")
                this as EuclideanPhysics2DEnvironmentWithObstacles, T>
            } else {
                object : Physics2DEnvironment by this, PhysicsEnvWithObstacles {
                    override fun getObstaclesInRange(
                        center: Euclidean2DPosition,
                        range: Double,
                    ): List> = emptyList()

                    override fun getObstaclesInRange(
                        centerx: Double,
                        centery: Double,
                        range: Double,
                    ): List> = emptyList()

                    override fun hasMobileObstacles() = false

                    override val obstacles: List>
                        get() = emptyList()

                    override fun intersectsObstacle(start: Euclidean2DPosition, end: Euclidean2DPosition) = false

                    override fun next(current: Euclidean2DPosition, desired: Euclidean2DPosition) = desired

                    override fun removeObstacle(obstacle: RectObstacle2D) =
                        error("This Environment instance does not support obstacle removal")

                    override fun addObstacle(obstacle: RectObstacle2D) =
                        error("This Environment instance does not support adding obstacles")

                    override fun makePosition(vararg coordinates: Double) =
                        [email protected](*coordinates)

                    override fun makePosition(vararg coordinates: Number) =
                        [email protected](*coordinates)

                    override val origin
                        get() = [email protected]
                }
            }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy