import { Shape } from "../shape" ; import { Util } from "../util" ; import { BallCollisionShape } from "../physics/collision_shape" ; import { Vector3 } from "../math/vector3" ; import { Matrix4 } from "../math/matrix4" ; /** A shape with force areas that can push or pull the marble. */ export abstract class ForceShape extends Shape { /** Creates a cone-shaped force area that widens as it gets farther away its origin. */ addConicForce ( distance , arcangle , strength ) { let semiverticalangle = arcangle / 2 ; let actualStrength = strength - ( strength * ( 0.7 / distance ) ) ; let actualDistance = distance - 0.7 ; this . addCollider ( ( ) => new BallCollisionShape ( distance ) , ( t , dt ) => { let marble = this . level . marble ; let perpendicular = new Vector3 ( 0 , 0 , 1 ) ; perpendicular . applyQuaternion ( this . worldOrientation ) ; let conetip = this . worldPosition . clone ( ) . sub ( perpendicular . multiplyScalar ( 0.7 ) ) ; let vec = marble . body . position . clone ( ) . sub ( conetip ) ; if ( vec . length ( ) === 0 ) return ; if ( vec . length ( ) > actualDistance ) return ; let maxF = Util . lerp ( actualStrength , 0 , vec . length ( ) / actualDistance ) ; let theta = perpendicular . angleTo ( vec ) ; if ( theta > semiverticalangle / 2 ) return ; let forcemag = Math . abs ( - maxF * ( theta - semiverticalangle ) * ( theta + semiverticalangle ) ) ; forcemag *= Math . sign ( actualStrength ) ; let force = vec . clone ( ) ; force . normalize ( ) ; marble . body . linearVelocity . addScaledVector ( force , forcemag * dt ) ; } , new Matrix4 ( ) ) ; } /** Like `addConicForce`, but directly ported from OpenMBU (which did some reverse-engineering magic) */ addConicForceExceptItsAccurateThisTime ( forceRadius , forceArc , forceStrength ) { this . addCollider ( ( ) => new BallCollisionShape ( forceRadius ) , ( t , dt ) => { let force = this . computeAccurateConicForce ( forceRadius , forceArc , forceStrength ) ; force . multiplyScalar ( dt ) ; this . level . marble . body . linearVelocity . add ( force ) ; } , new Matrix4 ( ) ) ; } computeAccurateConicForce ( forceRadius , forceArc , forceStrength ) { let marble = this . level . marble ; let pos = marble . body . position ; let strength = 0.0 ; let dot = 0.0 ; let posVec = new Vector3 ( ) ; let retForce = new Vector3 ( ) ; let node = this . worldMatrix . clone ( ) ; let nodeVec = new Vector3 ( node . elements [ 4 ] , node . elements [ 5 ] , node . elements [ 6 ] ) ; nodeVec . normalize ( ) ; posVec = pos . clone ( ) . sub ( new Vector3 ( ) . setFromMatrixPosition ( node ) ) ; dot = posVec . length ( ) ; if ( forceRadius < dot ) { return retForce ; } strength = ( 1 - dot / forceRadius ) * forceStrength ; posVec . multiplyScalar ( 1 / dot ) ; let newDot = nodeVec . dot ( posVec ) ; let arc = forceArc ; if ( arc < newDot ) { retForce . add ( posVec . multiplyScalar ( strength ) . multiplyScalar ( newDot - arc ) . multiplyScalar ( 1 / ( 1 - arc ) ) ) ; } return retForce ; } /** Creates a spherical-shaped force whose force always acts in the direction away from the center. */ addSphericalForce ( radius , strength ) { this . addCollider ( ( ) => new BallCollisionShape ( radius ) , ( t , dt ) => { let marble = this . level . marble ; let vec = marble . body . position . clone ( ) . sub ( this . worldPosition ) ; if ( vec . length ( ) === 0 ) return ; let strengthFac = 1 - Util . clamp ( vec . length ( ) / radius , 0 , 1 ) ** 2 ; marble . body . linearVelocity . addScaledVector ( vec . normalize ( ) , strength * strengthFac * dt ) ; } , new Matrix4 ( ) ) ; } /** Creates a spherical-shaped force whose force acts in the direction of the vector specified. */ addFieldForce ( radius , forceVector ) { this . addCollider ( ( ) => new BallCollisionShape ( radius ) , ( t , dt ) => { let marble = this . level . marble ; if ( marble . body . position . distanceTo ( this . worldPosition ) >= radius ) return ; marble . body . linearVelocity . addScaledVector ( forceVector , dt ) ; } , new Matrix4 ( ) ) ; } }