## projectile physics (with ellipsoid / with linepick)

Started by RemiD, October 27, 2018, 19:13:53

#### RemiD

projectile physics : with ellipsoid and collidables (see next posts)
`removed buggy code ! `

projectile physics : with linepick and pickables (see next posts)
`removed buggy code!`

(i have noticed that sometimes (rarely), the linepick does not detect the obstacle, and the projectile goes through it... i think that this happens because the length of the linepick is too short to detect a collision, so this part can be improved...)

#### phodiS

Wow RemiD you are amazing!!!. I won't even pretend to understand how you did this, but it is impressive!

#### RemiD

#2
i have just read a few others codes, tried to understand what is going on, extracted the math formulas, simplified it my way (in words), and coded it in Blitz3d code.

the difficult thing is to understand the difference between local/global position/orientation/vector

However the version with linepick is buggy, i am working on it...

#### RemiD

I have found the cause of the bug, the bug is present in both codes (with ellipsoid and colidables | with linepick and pickables), but i have difficulties to find the right way (in code) to solve it.

Basically :
When an entity is translated by a vector, it has a direction and a length.
When no collision is detected, the entity is simply translated along the whole vector
When a collision is detected, a before (collision) vector is calculated, the entity is translated along this before vector, then an after (collision) vector (considering the reflection) is calculated, the entity is translated along this after vector.
but what if during the translation along the after vector, another collision happens ? well the entity will go through the obstacle...

so in a way the solution to the bug is to check for collisions not only once but before each translation... Now to put this in code is another story

#### RemiD

#4
Finally ! i have found a right approach (procedure) to have projectile physics, using only linepick and pickables, and without having the projectile go through an obstacle when the translation vector can hit several obstacles when reflected.

`;projectile physics (with linepick and pickables) 20181004Graphics3D(1000,624,32,2)HidePointer()SeedRnd(MilliSecs())Global MXDiff%Global MYDiff%;cameraGlobal Camera = CreateCamera()CameraRange(Camera,1,1000)CameraClsColor(Camera,001,001,001);origineOrigine = CreateCube()ScaleMesh(Origine,0.03/2,0.03/2,0/03.2)EntityColor(Origine,255,000,255)EntityFX(Origine,1)Global GhostRootGlobal GhostRootYaw#Global GhostEyesGlobal GhostEyesPitch#AddGhost();roomGlobal Room = CreateCube() ;CreateCube() CreateCylinder(4) CreateCylinder(8) CreateSphere(2) CreateSphere(4) CreateSphere(8)FlipMesh(Room)ScaleMesh(Room,100.0/2,50.0/2,100.0/2)PositionMesh(Room,50,25,50)For GX% = 0 To 90 Step 10 For GZ% = 0 To 90 Step 10  TPart = CreateCube()  ScaleMesh(TPart,10.0/2,50.0/2,10.0/2)  PositionMesh(TPart,10.0/2,-50.0/2,10.0/2)  PositionMesh(TPart,GX,Rand(1,12),GZ)  AddMesh(TPart,Room) : FreeEntity(TPart) NextNextPositionEntity(Room,0,0,0,True)EntityColor(Room,125,125,125);projectilesGlobal ProjectilesCount%Type Projectile Field State% Field Shape Field Radius# Field Gravity# Field VX#, VY#, VZ# Field VLength#End TypeConst CIdle% = 1Const CTranslate% = 2Global Pivot = CreatePivot()EntityPickMode(Room,2)DLight = CreateLight(1)LightColor(DLight,255,255,255)PositionEntity(DLight,50,1000,-1000)RotateEntity(DLight,45,0,0)AmbientLight(064,064,064)PositionEntity(GhostRoot,50,25+1.65,90,True)GhostRootYaw = 180Global MainLoopTimer = CreateTimer(30);mainloopMain()End()Function Main() Repeat   MainLoopMilliStart% = MilliSecs()  MXDiff = MouseXSpeed()  MYDiff = MouseYSpeed()  UpdateGhost()  ;update projectiles  UpdateProjectiles()  PositionRotateEntityLikeOtherEntity(Camera,GhostEyes)  ;wireframe mode off/on  WireFrame(False)  If( KeyDown(2)=True )   WireFrame(True)  EndIf  ;render  SetBuffer(BackBuffer())  RenderWorld()    Color(255,255,255)  Text(0,0,"ProjectilesCount = "+ProjectilesCount)   PhysicsCount% = 0  For pr.Projectile = Each Projectile   If( pr\State = CTranslate )    PhysicsCount = PhysicsCount + 1   EndIf  Next  Text(0,15,"Physics active on "+PhysicsCount+" projectiles")  Text(500,0,"Tris = "+TrisRendered())   Text(500,16,"FPS = "+FPS)  ;display the result on the screen  ;Flip(1)  WaitTimer(MainLoopTimer)  VWait():Flip(False)  MainLoopMilliTime = MilliSecs() - MainLoopMilliStart  If( MainLoopMilliTime < 1 )   MainLoopMilliTime = 1  EndIf  FPS% = 1000.0/MainLoopMilliTime Until( KeyDown(1)=1 )End FunctionFunction PositionEntityLikeOtherEntity(Entity,OEntity) PositionEntity(Entity,EntityX(OEntity,True),EntityY(OEntity,True),EntityZ(OEntity,True),True)End FunctionFunction RotateEntityLikeOtherEntity(Entity,OEntity) RotateEntity(Entity,EntityPitch(OEntity,True),EntityYaw(OEntity,True),EntityRoll(OEntity,True),True)End FunctionFunction PositionRotateEntityLikeOtherEntity(Entity,OEntity) PositionEntity(Entity,EntityX(OEntity,True),EntityY(OEntity,True),EntityZ(OEntity,True),True) RotateEntity(Entity,EntityPitch(OEntity,True),EntityYaw(OEntity,True),EntityRoll(OEntity,True),True)End FunctionFunction Distance2D#(PAX#,PAZ#,PBX#,PBZ#) Distance2D# = Sqr( ( ( PBX - PAX ) * ( PBX - PAX ) ) + ( ( PBZ - PAZ ) * ( PBZ - PAZ ) ) ) Return Distance2DEnd FunctionFunction Distance3D#(PAX#,PAY#,PAZ#,PBX#,PBY#,PBZ#) Distance3D# = Sqr( ( ( PBX - PAX ) * ( PBX - PAX ) ) + ( ( PBY - PAY ) * ( PBY - PAY ) ) + ( ( PBZ - PAZ ) * ( PBZ - PAZ ) ) ) Return Distance3DEnd FunctionFunction Angle3D#(V1X#,V1Y#,V1Z#,V2X#,V2Y#,V2Z#) D# = (V1X*V2X) + (V1Y*V2Y) + (V1Z*V2Z) M# = Sqr( V1X*V1X + V1Y*V1Y + V1Z*V1Z ) * Sqr( V2X*V2X + V2Y*V2Y + V2Z*V2Z ) A# = ACos(D#/M#) Return A#End FunctionFunction AddGhost() GhostRoot = CreatePivot() GhostEyes = CreatePivot() PositionRotateEntityLikeOtherEntity(GhostEyes,GhostRoot) EntityParent(GhostEyes,GhostRoot,True)End FunctionFunction UpdateGhost() MoveMouse(GraphicsWidth()/2,GraphicsHeight()/2) GhostEyesPitch = GhostEyesPitch + Float(MYDiff)/10 If( GhostEyesPitch < -89 )  GhostEyesPitch = -89 Else If( GhostEyesPitch > 89 )  GhostEyesPitch = 89 EndIf RotateEntity(GhostEyes,GhostEyesPitch,0,0,False) GhostRootYaw = GhostRootYaw - Float(MXDiff)/10 RotateEntity(GhostRoot,0,GhostRootYaw,0,False) If( KeyDown(42) = 0 And KeyDown(29) = 0 )  Speed# = 0.1 Else If( KeyDown(42) = 1 And KeyDown(29) = 0 )  Speed# = 1 Else If( KeyDown(42) = 0 And KeyDown(29) = 1 )  Speed# = 0.01 EndIf If( KeyDown(17)=1 )  MoveEntity(GhostRoot,0,0,Speed) Else If( KeyDown(31)=1 )  MoveEntity(GhostRoot,0,0,-Speed) EndIf If( KeyDown(30)=1 )  MoveEntity(GhostRoot,-Speed,0,0) Else If( KeyDown(32)=1 )  MoveEntity(GhostRoot,Speed,0,0) EndIf If( KeyDown(16)=1 )  MoveEntity(GhostRoot,0,-Speed,0) Else If( KeyDown(18)=1 )  MoveEntity(GhostRoot,0,Speed,0) EndIf If( MouseHit(1)=1 )  DebugLog("")  ;create a new projectile  ProjectilesCount = ProjectilesCount + 1  pr.Projectile = New Projectile    pr\Radius# = Rnd(0.05,0.5)/2*10  pr\Shape = CreateSphere(16)  ScaleMesh(pr\Shape,pr\Radius,pr\Radius,pr\Radius)  ThrowForce# = 46.940*2.0/30.0  pr\Gravity = 0   ;calculate translation vector (whole vector)   TFormVector(0,0,+ThrowForce,GhostEyes,0)  pr\VX = TFormedX() : pr\VY = TFormedY() : pr\VZ = TFormedZ()  ;calculate translation length (whole length)  pr\VLength = Sqr( pr\VX*pr\VX + pr\VY*pr\VY + pr\VZ*pr\VZ )  PositionEntity(pr\Shape,EntityX(GhostEyes,True),EntityY(GhostEyes,True),EntityZ(GhostEyes,True),True)   ;state = translate  pr\State = CTranslate  EntityColor(pr\Shape,000,000,255)  EntityRadius(pr\Shape,pr\Radius-0.01)  EntityPickMode(pr\Shape,1) EndIf End FunctionFunction UpdateProjectiles() For pr.Projectile = Each Projectile  If( pr\State = CIdle )   ;  Else If( pr\State = CTranslate )   ;calculate new Whole vector   ;increase gravity force   pr\Gravity = 9.81/30.0/2   ;add gravity force to Whole vector   pr\VX = pr\VX : pr\VY = pr\VY-pr\Gravity : pr\VZ = pr\VZ   ;(add air friction to Whole vector)   ;pr\VX = pr\VX*0.999 : pr\VY = pr\VY*0.999 : pr\VZ = pr\VZ*0.999   ;calculate new Whole length   pr\VLength = Sqr( pr\VX*pr\VX + pr\VY*pr\VY + pr\VZ*pr\VZ )   ;initialize Remaining vector   RVX# = pr\VX : RVY# = pr\VY : RVZ# = pr\VZ   ;initialize Remaining length   RLength# = pr\VLength   Repeat    ;store Before position    befX# = EntityX(pr\Shape,True) : befY# = EntityY(pr\Shape,True) : befZ# = EntityZ(pr\Shape,True)    ;check if there is an obstacle in this direction at the Remaining length (+radius)    picEnt% = LinePick(befX,befY,befZ,RVX,RVY,RVZ,pr\Radius)     ;if yes    If( picEnt <> 0 )      ;store pic position     picX# = PickedX() : picY# = PickedY() : picZ# = PickedZ()     ;store pic normal     picNX# = PickedNX() : picNY# = PickedNY() : picNZ# = PickedNZ()     ;calculate When position     PositionEntity(Pivot,picX,picY,picZ,True) : AlignToVector(Pivot,picNX,picNY,picNZ,3,1.0) : MoveEntity(Pivot,0,0,pr\Radius)     wheX# = EntityX(Pivot,True) : wheY# = EntityY(Pivot,True) : wheZ# = EntityZ(Pivot,True)     ;calculate Before length (Before position -> When position)     BLength# = Distance3D(befX,befY,befZ,wheX,wheY,wheZ)     ;calculate Before coef (BLength/RLength)     BCoef# = BLength/RLength     ;calculate Before vector     BVX# = RVX*BCoef : BVY# = RVY*BCoef : BVZ# = RVZ*BCoef     ;translate entity with Before vector     TranslateEntity(pr\Shape,BVX,BVY,BVZ,True)     ;calculate New vector (reflection vector)     DP# = RVX*picNX + RVY*picNY + RVZ*picNZ      NFX# = -2.0*picNX*DP : NFY# = -2.0*picNY*DP : NFZ# = -2.0*picNZ*DP     NVX# = RVX+NFX*1.1 : NVY# = RVY+NFY*1.1 : NVZ# = RVZ+NFZ*1.1     ;calculate angle between Remaining vector and New vector     A# = Angle3D(RVX,RVY,RVZ,NVX,NVY,NVZ)     ;attenuate New vector depending on angle     AttCoef# = (1.0-(A/180.0))          ;DebugLog(A+" "+AttCoef)     NVX = NVX*AttCoef : NVY = NVY*AttCoef : NVZ = NVZ*AttCoef     ;attenuate New vector depending on arbitrary coef     ;NVX = NVX*0.9 : NVY = NVY*0.9 : NVZ = NVZ*0.9     ;calculate New length     NLength# = Sqr( NVX*NVX + NVY*NVY + NVZ*NVZ )     ;initialize Remaining vector     RVX = NVX : RVY = NVY : RVZ = NVZ     ;initialize Remaining length     RLength = NLength     ;     pr\VX = NVX : pr\VY = NVY : pr\VZ = NVZ     pr\VLength = NLength     ;check if Remaining length is inferior or equal to gravityforce     ;if yes     If( RLength < pr\Gravity )         EntityColor(pr\Shape,125,255,255)      ;check if the ground below is near or far      picEnt% = LinePick(wheX,wheY,wheZ,0,-100,0,pr\Radius)      If( picEnt <> 0 )       picX# = PickedX() : picY# = PickedY() : picZ# = PickedZ()            ;if near       If( wheY-picY < pr\Radius+pr\Gravity )              ;0 Remaining vector        RVX = 0 : RVY = 0 : RVZ = 0        ;0 Remaining length        RLength = 0        ;0 gravity force        pr\Gravity = 0        ;state = idle        pr\State = CIdle        EntityColor(pr\Shape,125,125,255)       EndIf       ;if far        ;      EndIf     EndIf         Else If( picEnt = 0 )         ;translate entity with Remaining vector     TranslateEntity(pr\Shape,RVX,RVY,RVZ,True)     ;0 Remaining vector     RVX = 0 : RVY = 0 : RVZ = 0     ;0 Remaining length     RLength = 0    EndIf   Until( Rlength = 0 )  EndIf  If( pr <> Null )   If( EntityY(pr\Shape,True) < -100 )    ProjectilesCount = ProjectilesCount - 1    FreeEntity(pr\Shape) : Delete(pr)   EndIf  EndIf NextEnd Function`

`;projectile physics (with ellipsoid and collidables) 20181004Graphics3D(1000,624,32,2)HidePointer()SeedRnd(MilliSecs())Global MXDiff%Global MYDiff%;cameraGlobal Camera = CreateCamera()CameraRange(Camera,1,1000)CameraClsColor(Camera,001,001,001);origineOrigine = CreateCube()ScaleMesh(Origine,0.03/2,0.03/2,0/03.2)EntityColor(Origine,255,000,255)EntityFX(Origine,1)Global GhostRootGlobal GhostRootYaw#Global GhostEyesGlobal GhostEyesPitch#AddGhost();roomGlobal Room = CreateCube() ;CreateCube() CreateCylinder(4) CreateCylinder(8) CreateSphere(2) CreateSphere(4) CreateSphere(8)FlipMesh(Room)ScaleMesh(Room,100.0/2,50.0/2,100.0/2)PositionMesh(Room,50,25,50)For GX% = 0 To 90 Step 10 For GZ% = 0 To 90 Step 10  TPart = CreateCube()  ScaleMesh(TPart,10.0/2,50.0/2,10.0/2)  PositionMesh(TPart,10.0/2,-50.0/2,10.0/2)  PositionMesh(TPart,GX,Rand(1,12),GZ)  AddMesh(TPart,Room) : FreeEntity(TPart) NextNextPositionEntity(Room,0,0,0,True)EntityColor(Room,125,125,125);projectilesGlobal ProjectilesCount%Type Projectile Field State% Field Shape Field Radius# Field Gravity# Field VX#, VY#, VZ# Field VLength# Field befX#, befY#, befZ#End TypeConst CIdle% = 1Const CTranslate% = 2Global Pivot = CreatePivot()Const GRoom% = 1Const GProjectile% = 2EntityType(Room,GRoom)Collisions(GProjectile,GRoom,2,1)Collisions(GProjectile,GProjectile,1,1)EntityPickMode(Room,2)DLight = CreateLight(1)LightColor(DLight,255,255,255)PositionEntity(DLight,50,1000,-1000)RotateEntity(DLight,45,0,0)AmbientLight(064,064,064)PositionEntity(GhostRoot,50,25+1.65,90,True)GhostRootYaw = 180Global MainLoopTimer = CreateTimer(30);mainloopMain()End()Function Main() Repeat   MainLoopMilliStart% = MilliSecs()  MXDiff = MouseXSpeed()  MYDiff = MouseYSpeed()  UpdateGhost()  ;update projectiles  UpdateProjectilesBefore()  UpdateWorld()  UpdateProjectilesAfter()  PositionRotateEntityLikeOtherEntity(Camera,GhostEyes)  ;wireframe mode off/on  WireFrame(False)  If( KeyDown(2)=True )   WireFrame(True)  EndIf  ;render  SetBuffer(BackBuffer())  RenderWorld()    Color(255,255,255)  Text(0,0,"ProjectilesCount = "+ProjectilesCount)   PhysicsCount% = 0  For pr.Projectile = Each Projectile   If( pr\State = CTranslate )    PhysicsCount = PhysicsCount + 1   EndIf  Next  Text(0,15,"Physics active on "+PhysicsCount+" projectiles")  Text(500,0,"Tris = "+TrisRendered())   Text(500,16,"FPS = "+FPS)  ;display the result on the screen  ;Flip(1)  WaitTimer(MainLoopTimer)  VWait():Flip(False)  MainLoopMilliTime = MilliSecs() - MainLoopMilliStart  If( MainLoopMilliTime < 1 )   MainLoopMilliTime = 1  EndIf  FPS% = 1000.0/MainLoopMilliTime Until( KeyDown(1)=1 )End FunctionFunction PositionEntityLikeOtherEntity(Entity,OEntity) PositionEntity(Entity,EntityX(OEntity,True),EntityY(OEntity,True),EntityZ(OEntity,True),True)End FunctionFunction RotateEntityLikeOtherEntity(Entity,OEntity) RotateEntity(Entity,EntityPitch(OEntity,True),EntityYaw(OEntity,True),EntityRoll(OEntity,True),True)End FunctionFunction PositionRotateEntityLikeOtherEntity(Entity,OEntity) PositionEntity(Entity,EntityX(OEntity,True),EntityY(OEntity,True),EntityZ(OEntity,True),True) RotateEntity(Entity,EntityPitch(OEntity,True),EntityYaw(OEntity,True),EntityRoll(OEntity,True),True)End FunctionFunction Distance2D#(PAX#,PAZ#,PBX#,PBZ#) Distance2D# = Sqr( ( ( PBX - PAX ) * ( PBX - PAX ) ) + ( ( PBZ - PAZ ) * ( PBZ - PAZ ) ) ) Return Distance2DEnd FunctionFunction Distance3D#(PAX#,PAY#,PAZ#,PBX#,PBY#,PBZ#) Distance3D# = Sqr( ( ( PBX - PAX ) * ( PBX - PAX ) ) + ( ( PBY - PAY ) * ( PBY - PAY ) ) + ( ( PBZ - PAZ ) * ( PBZ - PAZ ) ) ) Return Distance3DEnd FunctionFunction Angle3D#(V1X#,V1Y#,V1Z#,V2X#,V2Y#,V2Z#) D# = (V1X*V2X) + (V1Y*V2Y) + (V1Z*V2Z) M# = Sqr( V1X*V1X + V1Y*V1Y + V1Z*V1Z ) * Sqr( V2X*V2X + V2Y*V2Y + V2Z*V2Z ) A# = ACos(D#/M#) Return A#End FunctionFunction AddGhost() GhostRoot = CreatePivot() GhostEyes = CreatePivot() PositionRotateEntityLikeOtherEntity(GhostEyes,GhostRoot) EntityParent(GhostEyes,GhostRoot,True)End FunctionFunction UpdateGhost() MoveMouse(GraphicsWidth()/2,GraphicsHeight()/2) GhostEyesPitch = GhostEyesPitch + Float(MYDiff)/10 If( GhostEyesPitch < -89 )  GhostEyesPitch = -89 Else If( GhostEyesPitch > 89 )  GhostEyesPitch = 89 EndIf RotateEntity(GhostEyes,GhostEyesPitch,0,0,False) GhostRootYaw = GhostRootYaw - Float(MXDiff)/10 RotateEntity(GhostRoot,0,GhostRootYaw,0,False) If( KeyDown(42) = 0 And KeyDown(29) = 0 )  Speed# = 0.1 Else If( KeyDown(42) = 1 And KeyDown(29) = 0 )  Speed# = 1 Else If( KeyDown(42) = 0 And KeyDown(29) = 1 )  Speed# = 0.01 EndIf If( KeyDown(17)=1 )  MoveEntity(GhostRoot,0,0,Speed) Else If( KeyDown(31)=1 )  MoveEntity(GhostRoot,0,0,-Speed) EndIf If( KeyDown(30)=1 )  MoveEntity(GhostRoot,-Speed,0,0) Else If( KeyDown(32)=1 )  MoveEntity(GhostRoot,Speed,0,0) EndIf If( KeyDown(16)=1 )  MoveEntity(GhostRoot,0,-Speed,0) Else If( KeyDown(18)=1 )  MoveEntity(GhostRoot,0,Speed,0) EndIf If( MouseHit(1)=1 )  DebugLog("")  ;create a new projectile  ProjectilesCount = ProjectilesCount + 1  pr.Projectile = New Projectile    pr\Radius# = Rnd(0.05,0.5)/2*10  pr\Shape = CreateSphere(16)  ScaleMesh(pr\Shape,pr\Radius,pr\Radius,pr\Radius)  ThrowForce# = 46.940*2.0/30.0  pr\Gravity = 0   ;calculate translation vector (whole vector)   TFormVector(0,0,+ThrowForce,GhostEyes,0)  pr\VX = TFormedX() : pr\VY = TFormedY() : pr\VZ = TFormedZ()  ;calculate translation length (whole length)  pr\VLength = Sqr( pr\VX*pr\VX + pr\VY*pr\VY + pr\VZ*pr\VZ )  PositionEntity(pr\Shape,EntityX(GhostEyes,True),EntityY(GhostEyes,True),EntityZ(GhostEyes,True),True)   ;state = translate  pr\State = CTranslate  EntityColor(pr\Shape,000,000,255)  EntityRadius(pr\Shape,pr\Radius-0.01)  EntityType(pr\Shape,GProjectile)  EntityPickMode(pr\Shape,2) EndIf End FunctionFunction UpdateProjectilesBefore() For pr.Projectile = Each Projectile  If( pr\State = CIdle )   ;  Else If( pr\State = CTranslate )   ;calculate new Whole vector   ;increase gravity force   pr\Gravity = 9.81/30.0/2   ;add gravity force to Whole vector   pr\VX = pr\VX : pr\VY = pr\VY-pr\Gravity : pr\VZ = pr\VZ   ;(add air friction to Whole vector)   ;pr\VX = pr\VX*0.999 : pr\VY = pr\VY*0.999 : pr\VZ = pr\VZ*0.999   ;calculate new Whole length   pr\VLength = Sqr( pr\VX*pr\VX + pr\VY*pr\VY + pr\VZ*pr\VZ )   ;store Before position   pr\befX# = EntityX(pr\Shape,True) : pr\befY# = EntityY(pr\Shape,True) : pr\befZ# = EntityZ(pr\Shape,True)   ;translate entity with whole vector   TranslateEntity(pr\Shape,pr\VX,pr\VY,pr\VZ,True)   EndIf  If( pr <> Null )   If( EntityY(pr\Shape,True) < -100 )    ProjectilesCount = ProjectilesCount - 1    FreeEntity(pr\Shape) : Delete(pr)   EndIf  EndIf NextEnd FunctionFunction UpdateProjectilesAfter() For pr.Projectile = Each Projectile  If( pr\State = CIdle )   ;  Else If( pr\State = CTranslate )   ;check if a collision happened when translating entity along whole vector   nc% = CountCollisions(pr\Shape)   ;if yes   If( nc > 0 )    ;collision detected, get infos about the collision    colEnt% = CollisionEntity(pr\Shape,1)    ;store collision position    colX# = CollisionX(pr\Shape,1) : colY# = CollisionY(pr\Shape,1) : colZ# = CollisionZ(pr\Shape,1)    ;store collision normal    colNX# = CollisionNX(pr\Shape,1) : colNY# = CollisionNY(pr\Shape,1) : colNZ# = CollisionNZ(pr\Shape,1)     ;calculate When position    wheX# = EntityX(pr\Shape,True) : wheY# = EntityY(pr\Shape,True) : wheZ# = EntityZ(pr\Shape,True)    ;calculate New vector (reflection vector)    DP# = pr\VX*colNX + pr\VY*colNY + pr\VZ*colNZ     NFX# = -2.0*colNX*DP : NFY# = -2.0*colNY*DP : NFZ# = -2.0*colNZ*DP    NVX# = pr\VX+NFX*1.1 : NVY# = pr\VY+NFY*1.1 : NVZ# = pr\VZ+NFZ*1.1    ;calculate angle between Remaining vector and New vector    A# = Angle3D(pr\VX,pr\VY,pr\VZ,NVX,NVY,NVZ)    ;attenuate New vector depending on angle    AttCoef# = (1.0-(A/180.0))         ;DebugLog(A+" "+AttCoef)    NVX = NVX*AttCoef : NVY = NVY*AttCoef : NVZ = NVZ*AttCoef    ;calculate New length    NLength# = Sqr( NVX*NVX + NVY*NVY + NVZ*NVZ )    pr\VX = NVX : pr\VY = NVY : pr\VZ = NVZ    pr\VLength = NLength    ;check if Remaining length is inferior or equal to gravityforce    ;if yes    If( pr\VLength < pr\Gravity )     EntityColor(pr\Shape,125,255,255)     ;check if the ground below is near or far     picEnt% = LinePick(wheX,wheY,wheZ,0,-100,0,pr\Radius-0.01)     If( picEnt <> 0 )      picX# = PickedX() : picY# = PickedY() : picZ# = PickedZ()           ;if near      If( wheY-picY < pr\Radius+pr\Gravity )             ;0 Remaining vector       pr\VX = 0 :  pr\VY = 0 : pr\VZ = 0       ;0 Remaining length       pr\VLength = 0       ;0 gravity force       pr\Gravity = 0       ;state = idle       pr\State = CIdle       EntityColor(pr\Shape,125,125,255)      EndIf      ;if far       ;     EndIf    EndIf        Else If( collEnt = 0 )        ;   EndIf   EndIf NextEnd Function`