package edx.mechanical.mech.grid import resonantengine.api.graph.IUpdate import resonantengine.lib.grid.core.{GridNode, UpdateTicker} import scala.collection.convert.wrapAll._ /** * A grid that manages the mechanical objects * @author Calclavia */ class GridMechanical extends GridNode[NodeMechanical] with IUpdate { /** * Determines if this grid is locked (invalid opposite gear connections) */ private var isLocked = false /** * The nodes that the grid is currently recusing through */ private var allRecursed = Seq.empty[NodeMechanical] private var allDistributed = Seq.empty[NodeMechanical] nodeClass = classOf[NodeMechanical] /** * Rebuild the node list starting from the first node and recursively iterating through its connections. */ override def reconstruct(first: NodeMechanical) { super.reconstruct(first) UpdateTicker.world.addUpdater(this) isLocked = false } override def deconstruct(first: NodeMechanical) { super.deconstruct(first) UpdateTicker.world.removeUpdater(this) } override def update(deltaTime: Double) { nodes synchronized { /** * Consider this as the moment of inertia: how difficult it is to spin this object. */ nodes.foreach( n => { n.torque = 0 n.angularVelocity -= Math.min(Math.abs(n.angularVelocity) * deltaTime * n.friction, Math.abs(n.angularVelocity)) * Math.signum(n.angularVelocity) } ) //TODO: Add deceleration if (!isLocked) { getNodes.filter(n => n.bufferTorque != 0).foreach( n => { allDistributed = Seq(n) recurse(Seq(n), deltaTime, n.bufferTorque, 0) } ) } allDistributed = Seq.empty[NodeMechanical] resetNodes() } } def resetNodes() { getNodes.foreach( n => { if (n.prevTorque != n.torque) { n.prevTorque = n.torque n.onTorqueChanged() } if (n.prevAngularVelocity != n.angularVelocity) { n.prevAngularVelocity = n.angularVelocity n.onVelocityChanged() } n.bufferTorque = 0 } ) } def calculateEquivalentInertia(passed: Seq[NodeMechanical]): Double = { val curr = passed.last allRecursed :+= curr /** * I1 + n^2 * I * where n is the acceleration ratio */ var inertia = curr.inertia inertia += curr.connections.filterNot(allRecursed.contains).map(c => c.radius(curr) / curr.radius(c) * calculateEquivalentInertia(passed :+ c)).foldLeft(0d)(_ + _) return inertia } def recurse(passed: Seq[NodeMechanical], deltaTime: Double, torque: Double, angularVelocity: Double) { val curr = passed.last if (passed.size > 1) { //Pass energy to every single node val prev = passed(passed.size - 2) val ratio = curr.radius(prev) / prev.radius(curr) val invert = if (curr.inverseRotation(prev) && prev.inverseNext(curr)) -1 else 1 val addTorque = torque * ratio * invert val addVel = angularVelocity / ratio * invert curr.torque += addTorque curr.angularVelocity += addVel * deltaTime curr.connections.foreach(c => { if (c != prev) { if (!allDistributed.contains(c)) { allDistributed :+= c recurse(passed :+ c, deltaTime, addTorque, addVel) } else { //Check for grid lock val sudoInvert = if (c.inverseRotation(curr) && curr.inverseNext(c)) -1 else 1 if (Math.signum(c.angularVelocity) != sudoInvert * Math.signum(addVel)) { isLocked = true } } } }) } else { /** * This is the first node. * 1. Calculate equivalent moment of inertia of the mechanical system. * 2. Determine the angular acceleration: * T = I * a * a = T/I * where I = inertia and a = angular acceleration */ val inertia = calculateEquivalentInertia(passed) val netAcceleration = torque / inertia curr.torque += torque curr.angularVelocity += netAcceleration * deltaTime curr.connections.foreach(c => { allDistributed :+= c recurse(passed :+ c, deltaTime, torque, netAcceleration) }) } } override def updatePeriod: Int = if (getNodes.size > 0) 50 else 0 }