[PB4.31] Car Physics (based on Marco Monster's Tutorial)

Advanced game related topics
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

[PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

Hi,

Like many coders on their daily quest for digital enlighment before me, I've one day stumbled upon the famous Car Physics tutorial from Marco Monster. And like many before me, I gazed with amazement into the sheer clarity of its indications, and wondered if I could translate it to my language of choice.
[end of the melodramatic introduction :roll: ]

So, this is a little "arcade-ish" 3D car sim, where you gotta drive your sports car through a bunch of checkpoints and try to set the new track record. And when you have completed the race at least once, a "ghost" car will figure your best time on the track (to spice up the challenge a little).

Controls:
cursor keys: left/right to steer, up to accelerate, down to brake (reverse gear isn't properly implemented)
F1: changes camera view
F2: centers camera on your car or on the ghost

Limitations:
- Marco Monster's tutorial is a 2D tutorial, so the physics in this game are only 2D physics: no relief, no jump nor fancy stunts (ain't no Trackmania, bub)...
- There's no collision either.
- The track is just "drawn" on the terrain; whether you're on the road or not won't change the amount of grip...
- There's still a few bugs in the physics, and a few ugly fixes in the code. Sorry! :P
- The car is as easy to handle as a piece of wet soap on a teflon-coated ice rink; you can quick-fix by increasing the value of the #MAXGRIP constant, but I'm not sure it's a correct approach...

A last note:
At the beginning of the code, you will find three constants:
#HIDEFTIMER: activates Hi-Def timer, in Windows only; to compile for another OS, set it to False.
#REALISTICWEIGHTTRANSFER: activates a more realistic weight transfer model, but it makes the car even more difficult to handle...
#CARFOLLOWTERRAIN: prevents the car from going through the mountains (not very useful).

Big thanks go to:
Comtois (for the meshes generation procedures and NewValue/CurveValue), Dr Dri (for the ASM Atan2 procedure), and Typhoon (for the "point in rect" procedure. And thanks to Marco Monster, of course !

I post the code, but as it needs a lot of media to work, here's a link to the complete archive too: http://keleb.free.fr/codecorner/downloa ... hysics.zip (3 Mo).
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

code, part 1:

Code: Select all

; Author: Kelebrindae
; Date: january, 12, 2009, updated to PB v4.60 november, 10, 2011
; PB version: v4.60
; OS: Windows XP

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; A simple "car race" demo, with checkpoints, "special" zones (boosters) and a "ghost" of your best time for opponent.
; The physics engine isn't realistic enough for a simulation, but it could be sufficient for an arcade game.
;
; The physics code is entirely based on Marco Monster's famous "Car Physics" tutorial. Thanks a lot, Marco!
; Tutorial is available here: http://web.archive.org/web/20040827084133/home.planet.nl/~monstrous/
; ---------------------------------------------------------------------------------------------------------------
; Known bugs and limitations:
; ---------------------------------------------------------------------------------------------------------------
; - In some cases, skidding seems (much) too long
; - Only Rear Wheel Drive is implemented
; - Relief and terrain types (road/dirt/...) are not implemented
; - Collisions are not implemented
; - The "CHRONO2TEXT" macro seems ugly to me, but I can't find a better way to do it... Suggestions are welcomed!
; ---------------------------------------------------------------------------------------------------------------

#PB_Engine_Space_Local=1
#PB_Engine_Space_Parent=2
#PB_Engine_Space_World=4
#PB_Engine_Absolute_Rotation=8
#PB_Engine_Relative_Rotation=16
#PB_Engine_Quaternion_Rotation=32
#PB_Engine_Euler_Rotation=64

Absolute = #PB_Engine_Space_world | #PB_Engine_euler_Rotation | #PB_Engine_Absolute_Rotation
Relative = #PB_Engine_Euler_Rotation | #PB_Engine_Relative_Rotation


;- Initialization
Global FullScreen.b
Resultat = MessageRequester("Car Physics","Full Screen ?",#PB_MessageRequester_YesNo)
If Resultat = 6     
  FullScreen=#True
Else           
  FullScreen=#False
EndIf

If InitEngine3D() = 0
  MessageRequester( "Error" , "Can't initialize 3D, check if engine3D.dll is available" , 0 )
End
ElseIf InitSprite() = 0 Or InitKeyboard() = 0 Or InitMouse() = 0
  MessageRequester( "Error" , "Can't find DirectX 7.0 or above" , 0 )
  End
EndIf

If Fullscreen = #True
  OpenScreen(1024,768,32,"Car Physics",#PB_Screen_SmartSynchronization)
Else
  OpenWindow(0,0, 0, 800 , 600 ,"Car Physics")
  OpenWindowedScreen(WindowID(0),0,0, 800 , 600,0,0,0,#PB_Screen_SmartSynchronization)
EndIf

Add3DArchive(".", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\schemes", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\imagesets", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\fonts", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\looknfeel", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\layouts", #PB_3DArchive_FileSystem)

;- Constants and enums
#HIDEFTIMER = #False                ; Use Hi-Def timer (Windows API)
#REALISTICWEIGHTTRANSFER = #False   ; More realistic weight transfer model
#CARFOLLOWTERRAIN = #False          ; Can you drive through terrain or not ?


#CAMERA = 0
#NBMAXCARTYPES = 3
#NBMAXCARS = 1
#NBMAXCHECKPOINT = 20

#RAD2DEGMULT = 57.295779513082320877  ;(180/#PI)
#DEG2RADMULT = 0.0174532925199432958  ;(#PI/180)
#CRLF = Chr(13) + Chr(10)
#MAX_GRIP   = 2.0

; Gadgets 3D
Enumeration
  #Window3D
  #Chrono
  #Speed
EndEnumeration


; Camera views
Enumeration
  #CAMERA_ABOVE
  #CAMERA_FOLLOW
  #CAMERA_LEFT
  #CAMERA_RIGHT
  #CAMERA_INSIDE
  #CAMERA_FRONT
  #CAMERA_SPOT
EndEnumeration

; Car drive: only Rear Wheel Drive is used in this version
Enumeration
  #DRIVE_4WD
  #DRIVE_FWD
  #DRIVE_RWD
EndEnumeration

; Special Zones types: only Boost is used
Enumeration
  #BOOST
  #SLOW
  #ICE
  #INVERTCONTROLS
EndEnumeration


;- Structures and global definitions
Structure Vertex
  px.f
  py.f
  pz.f
  nx.f
  ny.f
  nz.f
  Couleur.l
  U.f
  V.f
EndStructure

Structure Polygon
  numVert1.w
  numVert2.w
  numVert3.w
EndStructure

Structure vector3
   X.f
   Y.f
   Z.f
EndStructure

Structure rectangle_struct
  x1.f
  y1.f
  x2.f
  y2.f
  x3.f
  y3.f
  x4.f
  y4.f
EndStructure

Structure specialZone_struct
  rootNode.i
 
  position.vector3
  angle.vector3
  width.f
  length.f
  zonetype.i
 
  rectangle.rectangle_struct
EndStructure

Structure checkpoint_struct
  rootNode.i
  bannerEntity.i
  materialActive.i
  materialInactive.i
 
  width.f
  height.f
 
  position.vector3
  angle.f
  xMast1.f
  zMast1.f
  xMast2.f
  zMast2.f
EndStructure

Structure raceRecord_struct
  time.f
 
  position.vector3
  angle.vector3
  roll.f
  tilt.f
 
  steerangle.f 
EndStructure

Structure carType_struct
  name.s
 
  width.f
  length.f
  height.f
 
  b.f               ; in m, distance from center of gravity to front axle
  c.f               ; in m, idem to rear axle
  h.f               ; in m, height of CM from ground
  wheelBase.f       ; wheelbase (distance between front and rear axles) in m
  wheelRadius.f     ; radius of wheels
  wheelWidth.f      ; width of wheels
 
  drive.b           ; 4WD, FW Drive, RW Drive
  maxThrottle.i     ; maximum engine power
  maxBrake.i        ; maximum brake power
  engineBrake.i     ; engine brake (when you're not accelerating nor braking)
  aeroDrag.f        ; wind resistance which slows the car down proprtionally To forward velocity
  rollingResist.f   ; rolling resistance (slows the car down when free rolling)
  frontgrip.f       ; cornering stiffness front = front slippyness of car .lower the number the less traction there is .
  reargrip.f        ; cornering stiffness rear = rear slippyness of car . lower the number the less traction there is .
  mass.f            ; in kg, effects the sluggishness of the handling
  inertia.f         ; in kg.m, how much momentum is generated by the car . Usual to set this equal To mass .
  suspensionHardness.f ; affects how much the car rolls and tilt on its wheels due to lateral/longitudinal forces
  maxRoll.f         ; maximum roll, in rads
  maxTilt.f         ; maximum tilt, in rads
EndStructure

Structure car_struct
  rootNode.i
  bodyNode.i
  frontLeftWheelNode.i
  frontRightWheelNode.i
  rearLeftWheelNode.i
  rearRightWheelNode.i
  bodyEntity.i

  *ptrCarType.carType_struct
  position_wc.vector3 ; position of car centre in world coordinates
  velocity_wc.vector3 ; velocity vector of car in world coordinates
  angle.vector3       ; angle of car orientation (in rads)
  angularVelocity.f   ; angular velocity of car
  steerAngle.f        ;   angle of steering (input)
  throttle.f          ; amount of throttle (input)
  brake.f             ; amount of braking (input)
  roll.f              ; left/right angle of the car on its suspension, due to lateral forces
  tilt.f              ; front/rear angle of the car on its suspension, due to longitudinal forces
  speedometer.f
  frontWheelRoll.f
  rearWheelRoll.f
 
  terrainGripFront.f  ; grip for front wheels, affected by the terrain currently under the front wheels
  terrainGripRear.f   ; grip for rear wheels, affected by the terrain currently under the rear wheels
 
  currentCheckpoint.i ; current target checkpoint
EndStructure

Global Dim cartype.carType_struct(#NBMAXCARTYPES)
Global Dim car.car_struct(#NBMAXCARS)

; Checkpoints management
Global Dim checkpoint.checkpoint_struct(#NBMAXCHECKPOINT)
Global currentCheckpoint.i

; Special zones (ex: boost, slow, swap left/right controls, etc.)
Global NewList zone.specialZone_struct()

; This list stores the current race, to make it the new ghost if it's better than the previous
Global NewList thisRace.raceRecord_struct()

; Infos about the ghost racer: its next position, its previous position,
; and its current position (which is an interpolation of the former two)
Global NewList ghostNextPos.raceRecord_struct()
Global *ghostPrevPos.raceRecord_struct,ghostCurrentPos.raceRecord_struct
Global ghostInterpolation.f,ghostTimer.f
Global ghostFinishTime.i
Global ghostExist.b = #False

Global cameraMode.b = #CAMERA_FOLLOW
Global cameraCarToFollow.i = 1
Global started.b = #False, finished.b = #False, stopped.b = #False

; Forced-timing variables
Global Dim timer.f(10)
Global currentTimer.f,oldTimer.f,numTimer.i,speedFactor.f
Global hiDefTimer.LARGE_INTEGER,hiDefTimerFreq.LARGE_INTEGER

; Chrono
Global chrono.f, chronoFreqHD.f
Global chronoFinish.i
Global minutes.i,seconds.i,millisec.i,chronoText.s

Global numMesh.i,terrain.i

;- ------ Macros ------
;************************************************************************************
; Les deux macros ci-dessous sont de Comtois. Merci Comtois !
Macro NEWXVALUE(x, Angle, Distance)
  ((x) + Cos((Angle) * #DEG2RADMULT) * (Distance))
EndMacro

Macro NEWZVALUE(z, Angle, Distance)
  ((z) - Sin((Angle) * #DEG2RADMULT) * (Distance))
EndMacro

Macro POW2(x)
  ((x)*(x))
EndMacro

; Put "Max(value1,value2)" in var "result"
Macro MAXIMUM(result,value1,value2)
  If value1>value2
    result=value1
  Else
    result=value2
  EndIf
EndMacro

; Put "Min(value1,value2)" in "result"
Macro MINIMUM(result,value1,value2)
  If value1<value2
    result=value1
  Else
    result=value2
  EndIf
EndMacro

; From a time in milliseconds, constructs a string looking like "01:23:456" (minutes:seconds:millisecs)
; and puts it in the "chronotext" global var
Macro CHRONO2TEXT(time)
  minutes  = Round( (time) /60000,#PB_Round_Down)
  seconds  = Round(( (time) - minutes*60000)/1000,#PB_Round_Down)
  millisec = Int(time) % 1000
 
  chronoText = Str(minutes)+":"+RSet(Str(seconds),2,"0")+":"+Str(millisec)
EndMacro
;************************************************************************************


;- ------ Procedures ------
EnableExplicit

;- -- Meshes and entities --

;************************************************************************************
; Name: myCreateTerrain
; Purpose: Create a terrain mesh
; Parameters:
;   - name of the heightmap
;   - terrain size X
;   - terrain size Y
;   - maximum height (= height of the white points in the heightmap)
;   - numbers of cols in the mesh
;   - numbers of rows in the mesh
; Return-Value: number of the car's root node
;************************************************************************************
Procedure.i MyCreateTerrain(heightmap.s,sizeX.f,sizeZ.f,maxHeight.f,nbCols.i,nbRows.i)
  Protected numImage.i,imSizeX.f,imSizeY.f, height.i
  Protected numMesh.i,i.i,j.i
  Protected v0.i,v1.i,v2.i,v3.i
  Protected cellSizeX.f, cellSizeZ.f,scaleY.f
  
  ; Load the heightmap
  numImage = LoadImage(#PB_Any,heightmap)
  If IsImage(numImage) = #False
    ProcedureReturn 0
  EndIf
  imSizeX = ImageWidth(numImage) - 1
  imSizeY = ImageHeight(numImage) - 1
  
  ; Create the mesh
  numMesh = CreateMesh(#PB_Any)
  cellSizeX = sizeX  / nbCols
  cellSizeZ = sizeZ  / nbRows
  scaleY = maxHeight / 255.0
  
  ; Create the vertices, according to heightmap values
  StartDrawing(ImageOutput(numImage))
  For j = 0 To nbCols
    For i = 0 To nbRows
      height = Red( Point(i * (imSizeX/nbCols) , j * (imSizeY/nbRows) ) )
      
      AddMeshVertex(i*cellSizeX,scaleY * height,j*cellSizeZ)
      MeshVertexColor($FFFFFF)
      MeshVertexTextureCoordinate( i * (1 / nbCols),j * (1 / nbRows) )
    Next i
  Next j
  StopDrawing()
  FreeImage(numImage)
  
  ; Create the faces
  For j=0 To nbRows - 1
    For i = 0 To nbCols - 1
      v0 = i + j*(nbRows + 1)
      v1 = i + j*(nbRows + 1) + 1
      v2 = i + (j+1)*(nbRows + 1)
      v3 = i + (j+1)*(nbRows + 1) + 1
      
      AddMeshFace(v0, v2, v3 ) 
      AddMeshFace(v0, v3, v1 )
    Next i
  Next j
  
  ; Finih and compute the normals
  FinishMesh()
  NormalizeMesh(numMesh)
  
  ProcedureReturn numMesh
EndProcedure


;************************************************************************************
; Name: makeCarObject
; Purpose: Create a car
; Parameters:
;   - number of the car in the "car" array
;   - ghosted or not
; Return-Value: number of the car's root node
;************************************************************************************
Procedure.i makeCarObject(numcar.i,isGhost.b = #False)

  Protected carbody.i
  Protected carwheel1.i,carwheel2.i,carwheel3.i,carwheel4.i
  Protected wheel1Entity.i,wheel2Entity.i,wheel3Entity.i,wheel4Entity.i
  Protected numMaterialBody.i = 1,numMaterialWheel.i = 7

  ; Ghost => transparent material
  If isGhost = #True
    numMaterialBody = 2
    numMaterialWheel = 2
  EndIf

  ; Create/Load meshes
  carbody = LoadMesh (#PB_Any, "corvette.mesh")
  carwheel1 = LoadMesh(#PB_Any,"wheel.mesh")
  carwheel2 = carwheel1:carwheel3 = carwheel1:carwheel4 = carwheel1
 
  ; Create entities
  car(numcar)\bodyEntity = CreateEntity(#PB_Any,MeshID(carbody),MaterialID(numMaterialBody))
  ; Specific to the sportcar mesh used
  ScaleEntity(car(numcar)\bodyEntity,0.1,0.1,0.1)
  RotateEntity(car(numcar)\bodyEntity,180,270,90)
  EntityLocate(car(numcar)\bodyEntity,0,-1.15,-0.3)

  ; Wheels
  wheel1Entity = CreateEntity(#PB_Any,MeshID(carwheel1),MaterialID(numMaterialWheel))
  wheel2Entity = CreateEntity(#PB_Any,MeshID(carwheel2),MaterialID(numMaterialWheel))
  wheel3Entity = CreateEntity(#PB_Any,MeshID(carwheel3),MaterialID(numMaterialWheel))
  wheel4Entity = CreateEntity(#PB_Any,MeshID(carwheel4),MaterialID(numMaterialWheel))
 
  ; Shadow (not for ghost cars)
  If isGhost = #True
    EntityRenderMode(car(numcar)\bodyEntity, #PB_Entity_CastShadow)
    EntityRenderMode(wheel1Entity, 0)
    EntityRenderMode(wheel2Entity, 0)
    EntityRenderMode(wheel3Entity, 0)
    EntityRenderMode(wheel4Entity, 0)
  EndIf
 
  ; Configure nodes 
  ; ParentNode (not visible)
  car(numCar)\rootNode = CreateNode(#PB_Any)
 
  ; Body node
  car(numCar)\bodyNode = CreateNode(#PB_Any)
  AttachNodeObject(car(numCar)\bodyNode,EntityID(car(numcar)\bodyEntity),#PB_Node_Entity)
   
  ; Child nodes (wheels)
  car(numCar)\frontLeftWheelNode = CreateNode(#PB_Any)
  AttachNodeObject(car(numCar)\frontLeftWheelNode,EntityID(wheel1Entity),#PB_Node_Entity)
  NodeLocate(car(numCar)\frontLeftWheelNode,car(numCar)\ptrCarType\width / -2,car(numCar)\ptrCarType\height/-2,car(numCar)\ptrCarType\b)
 
  car(numCar)\frontRightWheelNode = CreateNode(#PB_Any)
  AttachNodeObject(car(numCar)\frontRightWheelNode,EntityID(wheel2Entity),#PB_Node_Entity)
  NodeLocate(car(numCar)\frontRightWheelNode,car(numCar)\ptrCarType\width / 2,car(numCar)\ptrCarType\height/-2,car(numCar)\ptrCarType\b)
 
  car(numCar)\rearLeftWheelNode = CreateNode(#PB_Any)
  AttachNodeObject(car(numCar)\rearLeftWheelNode,EntityID(wheel3Entity),#PB_Node_Entity)
  NodeLocate(car(numCar)\rearLeftWheelNode,car(numCar)\ptrCarType\width / -2,car(numCar)\ptrCarType\height/-2,-car(numCar)\ptrCarType\c)
 
  car(numCar)\rearRightWheelNode = CreateNode(#PB_Any)
  AttachNodeObject(car(numCar)\rearRightWheelNode,EntityID(wheel4Entity),#PB_Node_Entity)
  NodeLocate(car(numCar)\rearRightWheelNode,car(numCar)\ptrCarType\width / 2,car(numCar)\ptrCarType\height/-2,-car(numCar)\ptrCarType\c)
 
 
  ; Attach nodes together
  AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\bodyNode),#PB_Node_Node)     
  AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\frontLeftWheelNode),#PB_Node_Node)
  AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\frontRightWheelNode),#PB_Node_Node)
  AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\rearLeftWheelNode),#PB_Node_Node)
  AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\rearRightWheelNode),#PB_Node_Node)
 
  ; Place Car
  ;car(numCar)\position_wc\y = TerrainHeight(car(numCar)\position_wc\x,car(numCar)\position_wc\z) + car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
  car(numCar)\position_wc\y = car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
  NodeLocate(car(numCar)\rootNode,car(numCar)\position_wc\x,car(numCar)\position_wc\y,car(numCar)\position_wc\z)
 
  ProcedureReturn car(numCar)\rootNode
 
EndProcedure


;************************************************************************************
; Name: makeCheckpointObject
; Purpose: create a checkpoint
; Parameters:
;   - number of the checkpoint in the "checkpoint" array
;   - Material
; Return-Value: number of the checkpoint's root node
;************************************************************************************
Procedure.i makeCheckpointObject(numcheckpoint.i,numMaterial.i = 0)
  Protected mastMesh.i,bannerMesh.i
  Protected leftMast.i,rightMast.i,banner.i
  Protected rootNode.i, bannerNode.i,leftMastNode.i,rightMastNode.i
  Protected height.f = 8, bannerHeight.f = 2
 
  ; Create/load meshes
  mastMesh = CreateCylinder(#PB_Any,0.2,height)
  ;bannerMesh = createBoxMesh(1,checkpoint(numcheckpoint)\width,bannerHeight,0.1)
  bannerMesh = CreateCube(#PB_Any,1)
  
  ; Create entities
  checkpoint(numcheckpoint)\bannerEntity = CreateEntity(#PB_Any,MeshID(bannerMesh),MaterialID(numMaterial))
  ScaleEntity(checkpoint(numcheckpoint)\bannerEntity,checkpoint(numcheckpoint)\width,bannerHeight,0.1)
  
  leftMast = CreateEntity(#PB_Any,MeshID(mastMesh),MaterialID(0))
  rightMast = CreateEntity(#PB_Any,MeshID(mastMesh),MaterialID(0))
 
  EntityRenderMode(checkpoint(numcheckpoint)\bannerEntity, #PB_Entity_CastShadow)
  EntityRenderMode(leftMast, #PB_Entity_CastShadow)
  EntityRenderMode(rightMast, #PB_Entity_CastShadow)


  ; Create Nodes
  ; root node (not visible)
  rootNode = CreateNode(#PB_Any)
   
  ; Child nodes (masts and banner)
  bannerNode = CreateNode(#PB_Any)
  AttachNodeObject(bannerNode,EntityID(checkpoint(numcheckpoint)\bannerEntity),#PB_Node_Entity)
  NodeLocate(bannerNode,0,height - (bannerHeight/2),0)

  leftMastNode = CreateNode(#PB_Any)
  AttachNodeObject(leftMastNode,EntityID(leftMast),#PB_Node_Entity)
  NodeLocate(leftMastNode,(checkpoint(numcheckpoint)\width / -2)-0.1,height/2,0)

  rightMastNode = CreateNode(#PB_Any)
  AttachNodeObject(rightMastNode,EntityID(rightMast),#PB_Node_Entity)
  NodeLocate(rightMastNode,(checkpoint(numcheckpoint)\width / 2)+0.1,height/2,0)

  ; Attach nodes together
  AttachNodeObject(rootNode,NodeID(bannerNode),#PB_Node_Node)
  AttachNodeObject(rootNode,NodeID(leftMastNode),#PB_Node_Node)
  AttachNodeObject(rootNode,NodeID(rightMastNode),#PB_Node_Node)

  ProcedureReturn rootNode

EndProcedure


;************************************************************************************
; Name: MakeSpecialZoneObject
; Purpose: create a "special zone" on the track
;         (only one kind is supported yet: booster)
; Parameters:
;   - pointer to the special zone in the "zone()" list
; Return-Value: number of the zone's entity (an horizontal plane)
;************************************************************************************
Procedure.i MakeSpecialZoneObject(*ptrZone.specialZone_struct)
  Protected nummesh.i, numentity.i, nummaterial.i

  Select *ptrZone\zoneType
    Case #BOOST
      numMaterial = 6
  EndSelect

  ;nummesh = CreateMesh(#PB_Any,100)
  ;SetMeshData(nummesh, #PB_Mesh_Vertex | #PB_Mesh_Normal | #PB_Mesh_UVCoordinate, ?PlanVertices, 4)
  ;SetMeshData(nummesh, #PB_Mesh_Face, ?PlanFaces, 2)
  nummesh = CreatePlane(#PB_Any,1,1,1,1,1,1)

  numEntity = CreateEntity(#PB_Any,MeshID(nummesh),MaterialID(numMaterial),*ptrZone\position\x,*ptrZone\position\y+0.1,*ptrZone\position\z)
  ScaleEntity(numEntity,*ptrZone\width,0,*ptrZone\length)
  RotateEntity(numEntity,0,*ptrZone\angle\y,0)
 
  ProcedureReturn numEntity

EndProcedure


;- -- Trigo and utility procs --
Macro myAtan2(y,x)
  ATan2(x,y)
EndMacro

; La procédure ci-dessous est de Comtois. Merci Comtois !
; Calcule une valeur progressive allant de la valeur actuelle à la valeur cible
Procedure.f curveValue(actuelle.f, Cible.f, P.f)
  If P > 1000.0
    P = 1000.0
  EndIf
  ProcedureReturn (actuelle + ( (Cible - actuelle) * P / 1000.0))
EndProcedure

; La procédure ci-dessous est de cpl_Bator. Merci cpl_Bator !
; Pareil qu'au-dessus, mais pour un angle
Procedure.f curveAngle(oldangle.f,newangle.f, increments.f)

    If (oldangle+360)-newangle<newangle-oldangle
      oldangle=360+oldangle
    EndIf
    If (newangle+360)-oldangle<oldangle-newangle
      newangle=360+newangle
    EndIf
    oldangle=oldangle-(oldangle-newangle)/increments

  ProcedureReturn oldangle
EndProcedure


;************************************************************************************
; Name: ComputeSegmentIntersection
; Purpose: Check if 2 segments intersect => used to know if the car crosses
;          a checkpoint line.
; Parameters:
;   - [x,y] -> [x,y] of the first segment
;   - [x,y] -> [x,y] of the second segment
; Return-Value: #True if the 2 segments intersect
;************************************************************************************
Procedure.b ComputeSegmentIntersection(x1.f,y1.f,x2.f,y2.f,x3.f,y3.f,x4.f,y4.f)

  Protected d.f
  Protected xi.f,yi.f       ; coordonnées du point d'intersection
  Protected xmin.f,xmax.f

  d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
  If d=0
    ProcedureReturn #False
  EndIf
 
  ; Computes the point of intersection between the two lines
  xi = ((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d
  yi = ((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d
 
  ; Is this point inside the segments' limits ?
  ; segment 1
  If x1 > x2
    xmax = x1
    xmin = x2
  Else
    xmax = x2
    xmin = x1
  EndIf
  If xi < xmin Or xi > xmax
    ProcedureReturn #False
  EndIf
 
  ; Segment 2
  If x3 > x4
    xmax = x3
    xmin = x4
  Else
    xmax = x4
    xmin = x3
  EndIf
  If xi < xmin Or xi > xmax
    ProcedureReturn #False
  EndIf
 
  ProcedureReturn #True

EndProcedure


; Les 2 procédures ci-dessous sont de Typhoon. Merci Typhoon !
Procedure Signe(a.i)
  If a>0
    ProcedureReturn 1
  ElseIf a<0
    ProcedureReturn -1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure.b ComputeRectangleCollision(x.f,y.f,*ptrRect.rectangle_struct)
  Protected xu.f,yu.f,c.f,P.f,AX.f

  ;Plan 1
  xu=*ptrRect\X2-*ptrRect\X1
  yu=*ptrRect\Y2-*ptrRect\Y1
  c=*ptrRect\Y1*xu-*ptrRect\X1*yu
  P=*ptrRect\X3*yu-*ptrRect\Y3*xu+c
  AX=x*yu-y*xu+c
  If Signe(AX) <> Signe(P)
    ProcedureReturn #False
  EndIf
 
  ;Plan 2
  xu=*ptrRect\X3-*ptrRect\X2
  yu=*ptrRect\Y3-*ptrRect\Y2
  c=*ptrRect\Y2*xu-*ptrRect\X2*yu
  P=*ptrRect\X1*yu-*ptrRect\Y1*xu+c
  AX=x*yu-y*xu+c
  If Signe(AX) <> Signe(P)
    ProcedureReturn #False
  EndIf
 
  ;Plan 3
  xu=*ptrRect\X4-*ptrRect\X3
  yu=*ptrRect\Y4-*ptrRect\Y3
  c=*ptrRect\Y3*xu-*ptrRect\X3*yu
  P=*ptrRect\X1*yu-*ptrRect\Y1*xu+c
  AX=x*yu-y*xu+c
  If Signe(AX) <> Signe(P)
    ProcedureReturn #False
  EndIf
 
  ;Plan 4
  xu=*ptrRect\X1-*ptrRect\X4
  yu=*ptrRect\Y1-*ptrRect\Y4
  c=*ptrRect\Y4*xu-*ptrRect\X4*yu
  P=*ptrRect\X3*yu-*ptrRect\Y3*xu+c
  AX=x*yu-y*xu+c
  If Signe(AX) <> Signe(P)
    ProcedureReturn #False
  EndIf

  ProcedureReturn #True

EndProcedure


;- -- Car Physics --

;************************************************************************************
; Name: PlaceCarOnGround
; Purpose: Computes a rough approximation of car height and X,Z angles to make it
;          "follow" the terrain's slope
; Parameters:
;   - number of the car in the "car" array
; Return-value: none
;************************************************************************************
CompilerIf #CARFOLLOWTERRAIN =#True
  Procedure placeCarOnGround(numCar.i)
 
    Static FrontWheelDist.f,RearWheelDist.f ; static => computed only once
    Protected angleToWheel.f
    Protected frontleftx.f,frontleftz.f,frontlefth.f
    Protected frontrightx.f,frontrightz.f,frontrighth.f
    Protected backleftx.f,backleftz.f,backlefth.f
    Protected backrightx.f,backrightz.f,backrighth.f
    Protected across.f,length.f,carHeight.f
   
    ; Distance between car center and front/rear wheels
    If FrontWheelDist = 0 And RearWheelDist = 0
      FrontWheelDist = Sqr( NodeX(car(numCar)\frontLeftWheelNode)*NodeX(car(numCar)\frontLeftWheelNode) + NodeZ(car(numCar)\frontLeftWheelNode)*NodeZ(car(numCar)\frontLeftWheelNode) )
      RearWheelDist  = Sqr( NodeX(car(numCar)\rearLeftWheelNode)*NodeX(car(numCar)\rearLeftWheelNode) + NodeZ(car(numCar)\rearLeftWheelNode)*NodeZ(car(numCar)\rearLeftWheelNode) )
    EndIf
   
    ; Coords X,Z of each wheel
    angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 315
    frontleftx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,FrontWheelDist)
    frontleftz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,FrontWheelDist)
   
    angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 225
    frontrightx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,FrontWheelDist)
    frontrightz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,FrontWheelDist)
     
    angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 45
    backleftx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,RearWheelDist)
    backleftz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,RearWheelDist)
   
    angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 135
    backrightx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,RearWheelDist)
    backrightz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,RearWheelDist)
   
    ; Height of each wheel
    frontlefth=TerrainHeight(frontleftx,frontleftz)
    frontrighth=TerrainHeight(frontrightx,frontrightz)
    backlefth=TerrainHeight(backleftx,backleftz)
    backrighth=TerrainHeight(backrightx,backrightz)
   
    ; X,Z inclination
    length=((frontlefth-frontrighth)+(backlefth-backrighth))/2.0
    across=((backlefth-frontlefth)+(backrighth-frontrighth))/2.0
   
    ; Update car height / X & Z angles
    carHeight=(frontlefth+frontrighth+backlefth+backrighth)/4.0
   
    car(numCar)\position_wc\Y = carHeight + car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
    car(numCar)\angle\X = across/4.0
    car(numCar)\angle\Z = length/4.0
   
   
    ; This procedure could be a good place to check what sort of terrain you've got under your wheels.
    ; But... well, it isn't not done yet.
    car(numCar)\terraingripFront = 1  ; 1 = normal grip = road
    car(numCar)\terraingripRear = 1   ; 1 = normal grip = road
   
  EndProcedure
CompilerEndIf


;************************************************************************************
; Name: simulateCarPhysics
; Purpose: A very simple physic model of car physics (good for arcade games!)
;          Heavily based on Marco Monster's famous "Car Physics" tutorial.
;          Thanks a lot, Marco!
; Parameters:
;   - number of the car in the "car" array
;   - grip factor for the front wheels
;   - grip factor for the rear wheels
;   - time (in seconds) since the last call
; Return-value: none
;************************************************************************************
Procedure simulateCarPhysics(numCar.i,delta_t.f)

   Protected velocity.vector3, acceleration_wc.vector3, resistance.vector3, force.vector3, ftraction.vector3
   Protected sn.d, cs.f
   Protected rot_angle.f, yawspeed.f
   Protected sideslip.f,slipanglefront.f,slipanglerear.f
   Protected torque.f
   Protected angular_acceleration.f
   Protected weightOnFrontAxle.f,weightOnRearAxle.f
   Protected flatf.vector3, flatr.vector3
  Protected roll.f,tilt.f, wheelRollDistRatio.f
  Protected sgn.b
   CompilerIf #REALISTICWEIGHTTRANSFER = #True
     Static acceleration.vector3
   CompilerElse
     Protected acceleration.vector3
   CompilerEndIf


  CompilerIf #CARFOLLOWTERRAIN = #True
    ; This procedure updates the X/Z angles of the car => from these, you could compute effects on speed, sideslip, etc..
    ; Note: this procedure also computes the amount of grip for front/rear wheels, based on the terrain type currently under the wheels
    placeCarOnGround(numCar)
  CompilerEndIf

  sn = Sin(car(numCar)\angle\Y)
  cs = Cos(car(numCar)\angle\Y)
 
  ; SAE convention: x is to the front of the car, y is to the right, z is down

  ; transform velocity in world reference frame to velocity in car reference frame
  velocity\X = cs * car(numCar)\velocity_wc\Y + sn * car(numCar)\velocity_wc\X
  velocity\Y = -sn * car(numCar)\velocity_wc\Y + cs * car(numCar)\velocity_wc\X
 
  car(numCar)\speedometer = velocity\X * 3.6 ; velocity is in units of meters/second => v * 3600 /1000 = km/h
 
  ; Lateral force on wheels
  ;    Resulting velocity of the wheels As result of the yaw rate of the car body
  ;    v = yawrate * r where r is distance of wheel To CG => cartype\b
  ;    yawrate (ang.velocity) must be in rad/s
  yawspeed = car(numCar)\ptrCarType\b * car(numCar)\angularvelocity

  ; Calculate the side slip angle of the car (a.k.a. beta)   
  If velocity\x = 0
   rot_angle = 0
   sideslip = 0
  Else
    rot_angle = myAtan2( yawspeed, velocity\x)
    sideslip = myAtan2( velocity\y, velocity\x)
  EndIf
   
  ; Calculate slip angles For front And rear wheels (a.k.a. alpha)
  slipanglefront = sideslip + rot_angle - car(numCar)\steerangle
  slipanglerear  = sideslip - rot_angle
 
  ; Ugly fix to avoid physics inconsistency at low speed
  If velocity\x < 0.01 And velocity\y < 0.01 And velocity\x > -0.01 And velocity\y > -0.01
    car(numCar)\velocity_wc\X = 0
    car(numCar)\velocity_wc\Z = 0
    car(numCar)\angularvelocity = 0
    car(numCar)\speedometer = 0
    car(numCar)\brake = 0
    velocity\x = 0
    velocity\y = 0
    acceleration\x = 0
    acceleration\y = 0
    yawspeed = 0
    rot_angle = 0
    sideslip = 0
    slipanglefront = 0
    slipanglerear = 0
  EndIf
     
  ; Weight repartition on each axle
  CompilerIf #REALISTICWEIGHTTRANSFER = #True
    ; This is a more realistic version, with weight transfer according to acceleration => far more difficult to handle
    ; To activate it, set the #REALISTICWEIGHTTRANSFER constant to #True
    weightOnFrontAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\c / car(numCar)\ptrCarType\wheelbase) - (car(numCar)\ptrCarType\h / car(numCar)\ptrCarType\wheelbase)*car(numCar)\ptrCarType\mass*acceleration\x
    weightOnRearAxle  = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\b / car(numCar)\ptrCarType\wheelbase) + (car(numCar)\ptrCarType\h / car(numCar)\ptrCarType\wheelbase)*car(numCar)\ptrCarType\mass*acceleration\x
  CompilerElse
    ; weight per axle = car mass x 1G (=9.8m/s^2) * distance axle->CG / wheelbase
    weightOnFrontAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\c / car(numCar)\ptrCarType\wheelbase)
    weightOnRearAxle  = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\b / car(numCar)\ptrCarType\wheelbase)
  CompilerEndIf
      
      
  flatf\x = 0
  flatr\x = 0
   flatf\y = car(numCar)\ptrCarType\frontGrip * slipanglefront
   flatr\y = car(numCar)\ptrCarType\rearGrip * slipanglerear

  ; lateral force on front wheels = (Ca * slip angle) capped To friction circle * load
   If #MAX_GRIP < flatf\y    
     flatf\y = #MAX_GRIP
   EndIf
   If -#MAX_GRIP > flatf\y
     flatf\y = -#MAX_GRIP
   EndIf
   flatf\y * weightOnFrontAxle

  ; lateral force on rear wheels
   If #MAX_GRIP < flatr\y    
     flatr\y = #MAX_GRIP
   EndIf
   If -#MAX_GRIP > flatr\y
     flatr\y = -#MAX_GRIP
   EndIf
   flatr\y * weightOnRearAxle

  ; longtitudinal force on rear wheels - very simple traction model
  If velocity\x<0
    sgn=-1
  Else
    sgn=1
  EndIf
  ftraction\x = car(numcar)\ptrCarType\maxThrottle *(car(numCar)\throttle - car(numCar)\brake * sgn)
  ftraction\y = 0

  ; Influence of terrain on wheel grip
  flatr\y * car(numcar)\terrainGripRear
  flatf\y * car(numcar)\terrainGripRear
  flatf\x * car(numcar)\terrainGripFront
  flatr\x * car(numcar)\terrainGripFront
 
 
  ; Forces And torque on body
   
  ; drag And rolling resistance
  resistance\x = -( car(numCar)\ptrCarType\rollingResist * velocity\x + car(numCar)\ptrCarType\aeroDrag * velocity\x * Abs(velocity\x) )
  resistance\y = -( car(numCar)\ptrCarType\rollingResist * velocity\y + car(numCar)\ptrCarType\aeroDrag * velocity\y * Abs(velocity\y) )
 
 
  ; sum forces
  force\x = ftraction\x + Sin(car(numCar)\steerangle) * flatf\x + flatr\x + resistance\x
  force\y = ftraction\y + Cos(car(numCar)\steerangle) * flatf\y + flatr\y + resistance\y

  ; torque on body from lateral forces
  torque = car(numCar)\ptrCarType\b * flatf\y - car(numCar)\ptrCarType\c * flatr\y

  ; Acceleration

  ; Newton F = m.a, therefore a = F/m
   acceleration\x = force\x / car(numCar)\ptrCarType\mass
   acceleration\y = force\y / car(numCar)\ptrCarType\mass

  angular_acceleration = torque / car(numCar)\ptrCarType\inertia

  ; Velocity And position
   
  ; transform acceleration from car reference frame to world reference frame
  acceleration_wc\x =  cs * acceleration\y + sn * acceleration\x
  acceleration_wc\y = -sn * acceleration\y + cs * acceleration\x
     
  ; velocity is integrated acceleration
  car(numCar)\velocity_wc\x + delta_t * acceleration_wc\x
  car(numCar)\velocity_wc\y + delta_t * acceleration_wc\y

  ; position is integrated velocity
  car(numCar)\position_wc\x + delta_t * car(numCar)\velocity_wc\x
  car(numCar)\position_wc\z + delta_t * car(numCar)\velocity_wc\y
 
  ; Angular velocity And heading
 
  ; integrate angular acceleration To get angular velocity
  car(numCar)\angularvelocity + delta_t * angular_acceleration

  ; integrate angular velocity To get angular orientation
  car(numCar)\angle\Y + delta_t * car(numCar)\angularvelocity
 
 
  ; Car roll and tilt target values
  roll = force\y / car(numCar)\ptrCarType\suspensionHardness
  If roll > 0
    MINIMUM(roll,roll,car(numCar)\ptrCarType\maxRoll)
  Else
    MAXIMUM(roll,roll,-car(numCar)\ptrCarType\maxRoll)
  EndIf
  tilt = force\x / car(numCar)\ptrCarType\suspensionHardness
  If tilt> 0
    MINIMUM(tilt,tilt,car(numCar)\ptrCarType\maxTilt)
  Else
    MAXIMUM(tilt,tilt,-car(numCar)\ptrCarType\maxTilt)
  EndIf
 
  ; Compute car actual roll/tilt
  If car(numCar)\roll < roll
    car(numCar)\roll + delta_t
    If car(numCar)\roll > roll
      car(numCar)\roll = roll
    EndIf   
  ElseIf car(numCar)\roll > roll
    car(numCar)\roll - delta_t
    If car(numCar)\roll < roll
      car(numCar)\roll = roll
    EndIf   
  EndIf
 
  If car(numCar)\tilt < tilt
    car(numCar)\tilt + delta_t
    If car(numCar)\tilt > tilt
      car(numCar)\tilt = tilt
    EndIf
  ElseIf car(numCar)\tilt > tilt
    car(numCar)\tilt - delta_t
   If car(numCar)\tilt < tilt
      car(numCar)\tilt = tilt
    EndIf
  EndIf
 
 
  ; wheels rotation 
 
  ; Conversion distance => wheel rotation
  wheelRollDistRatio = delta_t * #RAD2DEGMULT * 2 * car(numcar)\ptrCartype\wheelradius
 
  ; Front wheels: velocity X according to steering angle
  car(numCar)\frontWheelRoll + (wheelRollDistRatio * (Cos(car(numCar)\angle\Y + car(numCar)\steerangle) * car(numCar)\velocity_wc\Y + Sin(car(numCar)\angle\Y + car(numCar)\steerangle) * car(numCar)\velocity_wc\X) )
 
  ; Rear wheels: velocity X, except when braking is maximum (rear wheels are blocked)
  If car(numcar)\brake < car(numcar)\ptrCarType\maxbrake
    car(numCar)\rearWheelRoll + (velocity\X * wheelRollDistRatio)
  EndIf

EndProcedure


;- -- Manage checkpoints and finish line

;************************************************************************************
; Name: activateNextCheckpoint
; Purpose: Activate the next checkpoint in the "chekpoint" array for the current car
;          and change the texture of its banner to show it
; Parameters:
;   - number of the current car
; Return-Value: True if there's no next checkpoint (race is over!)
;************************************************************************************
Procedure.b activateNextCheckpoint(numcar.i)
  
  
  SetEntityMaterial(checkpoint(car(numcar)\currentCheckpoint)\bannerEntity,MaterialID(checkpoint(car(numcar)\currentCheckpoint)\materialInactive))
  car(numcar)\currentCheckpoint + 1
  If checkpoint(car(numcar)\currentCheckpoint)\angle = 999 ; No next checkpoint? => Finish!
    ProcedureReturn #True
  Else
    SetEntityMaterial(checkpoint(car(numcar)\currentCheckpoint)\bannerEntity,MaterialID(checkpoint(car(numcar)\currentCheckpoint)\materialActive))
  EndIf
 
  ProcedureReturn #False

EndProcedure

;************************************************************************************
; Name: checkCurrentCheckpoint
; Purpose: checks if the car crosses its target checkpoint
; Parameters:
;   - number of the current car
; Return-Value: True if the last checkpoint has been crossed (race is over!)
;************************************************************************************
Procedure.b checkCurrentCheckpoint(numcar.i)
  Protected x1.f,y1.f,x2.f,y2.f

  ; First segment: Front -> back
  x1 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\length/2 * Cos(-car(numcar)\angle\Y+#PI/2))
  y1 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\length/2 * Sin(-car(numcar)\angle\Y+#PI/2))
  x2 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\length/-2 * Cos(-car(numcar)\angle\Y+#PI/2))
  y2 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\length/-2 * Sin(-car(numcar)\angle\Y+#PI/2))
 
  ; If this segment crosses the checkpoint line, activate the next checkpoint
  If computeSegmentIntersection(x1,y1,x2,y2,checkpoint(car(numcar)\currentCheckpoint)\xMast1,checkpoint(car(numcar)\currentCheckpoint)\zMast1,checkpoint(car(numcar)\currentCheckpoint)\xMast2,checkpoint(car(numcar)\currentCheckpoint)\zMast2) = #True
    ProcedureReturn activateNextCheckpoint(numcar.i)
  EndIf

  ; Second segment: Left -> right
  x1 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\width/2 * Cos(-car(numcar)\angle\Y))
  y1 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\width/2 * Sin(-car(numcar)\angle\Y))
  x2 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\width/-2 * Cos(-car(numcar)\angle\Y))
  y2 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\width/-2 * Sin(-car(numcar)\angle\Y)) 
 
  ; If this segment crosses the checkpoint line, activate the next checkpoint
  If computeSegmentIntersection(x1,y1,x2,y2,checkpoint(car(numcar)\currentCheckpoint)\xMast1,checkpoint(car(numcar)\currentCheckpoint)\zMast1,checkpoint(car(numcar)\currentCheckpoint)\xMast2,checkpoint(car(numcar)\currentCheckpoint)\zMast2) = #True
    ProcedureReturn activateNextCheckpoint(numcar.i)
  EndIf   
 
  ProcedureReturn #False
 
EndProcedure

;************************************************************************************
; Name: finishRace
; Purpose: Race is over => stores the current race if it is better than the ghost,
;          and display a message to congratulate or taunt the player
; Parameters:
;   - race time of the player
;   - current best time
; Return-Value: none
;************************************************************************************
Procedure finishRace(finishTime.i, bestTime.i)
  Protected diffTimeText.s

  ; If this is your first race, or if you set the new record, you win: your race becomes the new ghost
  If finishTime < bestTime Or bestTime = 0
    CreateFile(1,"ghost.rcr")
    WriteLong(1,finishTime)
    ForEach thisRace()
      WriteFloat(1,thisRace()\time)
      WriteFloat(1,thisRace()\position\X)
      WriteFloat(1,thisRace()\position\Y)
      WriteFloat(1,thisRace()\position\Z)
      WriteFloat(1,thisRace()\angle\X)
      WriteFloat(1,thisRace()\angle\Y)
      WriteFloat(1,thisRace()\angle\Z)
      WriteFloat(1,thisRace()\roll)
      WriteFloat(1,thisRace()\tilt)
      WriteFloat(1,thisRace()\steerangle)
    Next thisRace()
   
    If bestTime > 0
      CHRONO2TEXT(bestTime - finishTime)
      diffTimeText = #CRLF+"( -" + chronoText + " )"
    EndIf
   
    CHRONO2TEXT(finishtime)
    OpenWindow3D(10, 300, 200, 200, 120, "Congratulations !")
    TextGadget3D(11, 20, 30, 160, 26, "       New Lap Record!")
    TextGadget3D(12, 30, 63, 140, 26, " Your time: " + chronoText)
    If bestTime > 0
      CHRONO2TEXT(bestTime - finishTime)
      TextGadget3D(13, 50, 90, 100, 26, "  ( - "+ chronoText + " )")
    EndIf

  Else
    ; Else... Well, you lose.

    CHRONO2TEXT(finishtime)
    diffTimeText = #CRLF+"( +"+ chronoText + " )"
    OpenWindow3D(10, 300, 200, 200, 120, "Too bad...")
    TextGadget3D(11, 20, 30, 160, 26, "    Game Over, you slug!")
    TextGadget3D(12, 30, 63, 140, 26, " Your time: " + chronoText)
    CHRONO2TEXT(finishTime - bestTime)   
    TextGadget3D(13, 50, 90, 100, 26, "  ( + "+ chronoText + " )")

  EndIf

EndProcedure


;- -- Manage "special zones"

;************************************************************************************
; Name: manageSpecialZones
; Purpose: checks if the car is in one of the "special zones", and affect the car accordingly
; Parameters:
;   - number of the current car
; Return-Value: none
;************************************************************************************
Procedure manageSpecialZones(numcar.i)
  Protected distx.f, distz.f
 
  ForEach zone()
    If computeRectangleCollision(car(numcar)\position_wc\X,car(numcar)\position_wc\Z,@zone()+OffsetOf(specialzone_struct\rectangle)) = #True
      Select zone()\zonetype
        Case #BOOST
          car(numcar)\throttle = car(numcar)\ptrCarType\maxThrottle*5
        Case #SLOW
          car(numcar)\brake = car(numcar)\ptrCarType\maxBrake/10
      EndSelect
     
      Break   
    EndIf
  Next zone()
EndProcedure 
Last edited by Kelebrindae on Thu Jan 12, 2012 9:54 am, edited 2 times in total.
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

Code, part 2:

Code: Select all

;- -- Gestion camera

;************************************************************************************
; Name: manageCamera
; Purpose: place the camera
; Parameters: 
;   - camera mode (plane view, behind view, inside view...)
;   - Number of the car to follow (usually the player's car)
;   - time (in milliseconds) since the last call
; Return-Value: none
;************************************************************************************
Procedure manageCamera(mode.b, numCarToFollow.i, delta_t.f)
  Protected Px.f, Py.f, Pz.f, Pv.f,azimuth.f
  Protected dist.i = 10
  Static AngleCamera.f, oldmode.b, oldTarget.i

  pv = 5.5 * delta_t
  azimuth =  car(numCarToFollow)\angle\Y * (#RAD2DEGMULT)

  If oldmode = #CAMERA_INSIDE
    If mode <> oldmode
      HideEntity(car(numCarToFollow)\bodyEntity,#False)
    ElseIf numCarToFollow <> oldTarget
      HideEntity(car(oldTarget)\bodyEntity,#False)
    EndIf
  EndIf  
  Select Mode

    Case #CAMERA_ABOVE
      AngleCamera = CurveAngle(AngleCamera, azimuth + 90, 500/pv)
      Px = CurveValue(CameraX(#CAMERA), NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, dist), Pv)
      Py = CurveValue(CameraY(#CAMERA), car(numCarToFollow)\position_wc\Y + dist*10, Pv)
      Pz = CurveValue(CameraZ(#CAMERA), NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, dist), Pv)

    Case #CAMERA_FOLLOW
      AngleCamera = CurveAngle(AngleCamera, azimuth + 90, 500/pv)
      Px = CurveValue(CameraX(#CAMERA), NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, dist*0.6), pv)
      Py = CurveValue(CameraY(#CAMERA), car(numCarToFollow)\position_wc\Y+2, pv)
      Pz = CurveValue(CameraZ(#CAMERA), NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, dist*0.6), pv)

    Case #CAMERA_LEFT
      AngleCamera = CurveAngle(AngleCamera, azimuth + 30, 500/pv)
      Px = CurveValue(CameraX(#CAMERA), NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, dist*2), Pv)
      Py = CurveValue(CameraY(#CAMERA), car(numCarToFollow)\position_wc\Y + dist, Pv)
      Pz = CurveValue(CameraZ(#CAMERA), NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, dist*2), Pv)

    Case #CAMERA_RIGHT
      AngleCamera = CurveAngle(AngleCamera, azimuth + 150, 500/pv)
      Px = CurveValue(CameraX(#CAMERA), NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, dist*2), Pv)
      Py = CurveValue(CameraY(#CAMERA), car(numCarToFollow)\position_wc\Y + dist, Pv)
      Pz = CurveValue(CameraZ(#CAMERA), NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, dist*2), Pv)

    Case #CAMERA_FRONT
      AngleCamera = CurveAngle(AngleCamera, azimuth-90, 500/pv)
      Px = CurveValue(CameraX(#CAMERA), NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, dist*2), Pv)
      Py = CurveValue(CameraY(#CAMERA), car(numCarToFollow)\position_wc\Y + dist, Pv)
      Pz = CurveValue(CameraZ(#CAMERA), NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, dist*2), Pv)
      
    Case #CAMERA_INSIDE
      AngleCamera = azimuth+90
      Px = NEWXVALUE(car(numCarToFollow)\position_wc\X, AngleCamera, 1)
      Py = car(numCarToFollow)\position_wc\Y
      Pz = NEWZVALUE(car(numCarToFollow)\position_wc\Z, AngleCamera, 1)
      If oldmode <> mode Or numCarToFollow <> oldTarget
        HideEntity(car(numCarToFollow)\bodyEntity,#True)
      EndIf

  EndSelect
  
  If Mode <> #CAMERA_SPOT
    CameraLocate(#CAMERA, Px, Py, Pz)
  EndIf
  
  CameraLookAt(#CAMERA, car(numCarToFollow)\position_wc\X, car(numCarToFollow)\position_wc\Y, car(numCarToFollow)\position_wc\Z)
  
  If Mode = #CAMERA_INSIDE
    MoveCamera(#CAMERA,0,0,-1.5)
    RotateCamera(#CAMERA,car(numCarToFollow)\tilt*(#RAD2DEGMULT),0,car(numCarToFollow)\roll*(#RAD2DEGMULT),#PB_Relative)
  EndIf
  
  oldmode = mode
  oldtarget = numCarToFollow
  
EndProcedure

; ***********************  End of procedures  *******************************************


;- Terrain
Add3DArchive("media\", #PB_3DArchive_FileSystem)
CreateMaterial  (0, LoadTexture(0, "terrain.png"))
AddMaterialLayer(0, LoadTexture(1, "detail.png"), 1) ; detail.png
CreateTerrain("heightmap.png", MaterialID(0), 1, 0.5, 1, 4)
SkyDome("clouds.jpg",1)

    
;- Camera
CreateCamera(#CAMERA, 0, 0, 100, 100)
CameraLocate(#CAMERA, 500, 200, 500)

;- Light
AmbientColor(RGB(100,100,100))
CreateLight(0,RGB(255, 255, 255),    0, 500,    500)
WorldShadows(#PB_Shadow_Modulative)

;- Materials
; White material, used to show vertices colors
CreateImage(0,64,64)
StartDrawing(ImageOutput(0))
  Box(0,0,64,64,$FFFFFF)
StopDrawing()
SaveImage(0,"media\temp.bmp")
LoadTexture(0,"temp.bmp")
CreateMaterial(0,TextureID(0))
MaterialAmbientColor(0,#PB_Material_AmbientColors)

; Sportcar texture
LoadTexture(1,"corvette.bmp")
CreateMaterial(1,TextureID(1))

; Ghost texture
StartDrawing(ImageOutput(0))
  Box(0,0,64,64,$777777)
StopDrawing()
SaveImage(0,"media\temp2.bmp")
LoadTexture(2,"temp2.bmp")
CreateMaterial(2,TextureID(2))
MaterialBlendingMode(2,#PB_Material_Add)

; checkpoint textures
LoadTexture(3,"checkpoint.bmp")
CreateMaterial(3,TextureID(3))
MaterialFilteringMode(3,#PB_Material_None)
LoadTexture(4,"checkpoint-inactive.bmp")
CreateMaterial(4,TextureID(4))
MaterialFilteringMode(4,#PB_Material_None)

; Finish texture
LoadTexture(5,"finish.bmp")
CreateMaterial(5,TextureID(5))
MaterialFilteringMode(5,#PB_Material_None)

; Boost texture
LoadTexture(6,"boost.bmp")
CreateMaterial(6,TextureID(6))

; wheels texture
LoadTexture(7,"wheel.jpg")
CreateMaterial(7,TextureID(7))


;- Init car type
cartype(1)\name = "Corvette (RWD)"
cartype(1)\width = 1.8
cartype(1)\length = 4.6
cartype(1)\height = 1.46
cartype(1)\b = 1.4
cartype(1)\c = 1.4
cartype(1)\h = 1
cartype(1)\wheelBase = cartype(1)\b + cartype(1)\c
cartype(1)\wheelRadius = 0.5
cartype(1)\wheelWidth = 0.3
cartype(1)\aeroDrag = 5
cartype(1)\rollingResist = 30
cartype(1)\frontgrip  = -5.0
cartype(1)\reargrip = -5.2
cartype(1)\mass = 1451
cartype(1)\inertia = 1451
cartype(1)\drive = #DRIVE_RWD
cartype(1)\maxThrottle = 100
cartype(1)\maxBrake = 100
cartype(1)\engineBrake = 20
cartype(1)\suspensionHardness = 200000
cartype(1)\maxRoll = #PI/50
cartype(1)\maxTilt = #PI/100


;- Init Car
car(1)\ptrCarType = @cartype(1)
car(1)\position_wc\X = 282
car(1)\position_wc\Z = 533
car(1)\angle\Y = 125 * (#DEG2RADMULT)
car(1)\throttle = 0
car(1)\brake = 0
car(1)\terrainGripFront = 1
car(1)\terrainGripRear = 1

;- make a car object
car(1)\rootNode = makeCarObject(1)
RotateNode(car(1)\rootNode,0,car(1)\angle\Y *(#RAD2DEGMULT),0)


;- Load ghost datas
If ReadFile(1,"ghost.rcr")

  ghostExist = #True
  ghostFinishTime = ReadLong(1)
  While Eof(1) = #False
    AddElement(ghostNextPos())
    ghostNextPos()\time = ReadFloat(1)
    ghostNextPos()\position\X = ReadFloat(1)
    ghostNextPos()\position\Y = ReadFloat(1)
    ghostNextPos()\position\Z = ReadFloat(1)
    ghostNextPos()\angle\X = ReadFloat(1)
    ghostNextPos()\angle\Y = ReadFloat(1)
    ghostNextPos()\angle\Z = ReadFloat(1)
    ghostNextPos()\roll = ReadFloat(1)
    ghostNextPos()\tilt = ReadFloat(1)
    ghostNextPos()\steerangle = ReadFloat(1)
  Wend

  ;- make the "ghost" car
  car(0)\ptrCarType = @cartype(1)
  car(0)\rootNode = makeCarObject(0,#True)
  
  FirstElement(ghostNextPos())
  car(0)\position_wc\X = ghostNextPos()\position\X
  car(0)\position_wc\Y = ghostNextPos()\position\Y
  car(0)\position_wc\Z = ghostNextPos()\position\Z
  car(0)\angle\X = ghostNextPos()\angle\X
  car(0)\angle\Y = ghostNextPos()\angle\Y
  car(0)\angle\Z = ghostNextPos()\angle\Z
      
  *ghostPrevPos = @ghostNextPos()
  NextElement(ghostNextPos())
  RotateNode(car(0)\rootNode,0,ghostNextPos()\angle\Y *(#RAD2DEGMULT),0)
  NodeLocate(car(0)\rootNode,ghostNextPos()\position\X,-500,ghostNextPos()\position\Z)
EndIf

;- make checkpoints
currentCheckpoint = 0
Restore checkpointsData
Repeat
  Read.f checkpoint(currentCheckpoint)\position\x
  Read.f checkpoint(currentCheckpoint)\position\z
  Read.f checkpoint(currentCheckpoint)\angle
  Read.f checkpoint(currentCheckpoint)\width

  
  If checkpoint(currentCheckpoint)\angle <> 999
    checkpoint(currentCheckpoint)\rootNode = makeCheckpointObject(currentCheckpoint,4)
    NodeLocate(checkpoint(currentCheckpoint)\rootNode,checkpoint(currentCheckpoint)\position\X,0,checkpoint(currentCheckpoint)\position\Z)
    RotateNode(checkpoint(currentCheckpoint)\rootNode,0,checkpoint(currentCheckpoint)\angle,0)
    
    checkpoint(currentCheckpoint)\xMast1 = checkpoint(currentCheckpoint)\position\X + (checkpoint(currentCheckpoint)\width/-2.0)*Cos(-checkpoint(currentCheckpoint)\angle*(#DEG2RADMULT))
    checkpoint(currentCheckpoint)\zMast1 = checkpoint(currentCheckpoint)\position\Z + (checkpoint(currentCheckpoint)\width/-2.0)*Sin(-checkpoint(currentCheckpoint)\angle*(#DEG2RADMULT))
    checkpoint(currentCheckpoint)\xMast2 = checkpoint(currentCheckpoint)\position\X + (checkpoint(currentCheckpoint)\width/2.0)*Cos(-checkpoint(currentCheckpoint)\angle*(#DEG2RADMULT))
    checkpoint(currentCheckpoint)\zMast2 = checkpoint(currentCheckpoint)\position\Z + (checkpoint(currentCheckpoint)\width/2.0)*Sin(-checkpoint(currentCheckpoint)\angle*(#DEG2RADMULT))
    
    checkpoint(currentCheckpoint)\materialActive = 3
    checkpoint(currentCheckpoint)\materialInactive = 4
  EndIf
  
  currentCheckpoint+1
Until checkpoint(currentCheckpoint-1)\angle = 999

checkpoint(currentCheckpoint-2)\materialActive = 5
checkpoint(currentCheckpoint-2)\materialInactive = 5
EntityMaterial(checkpoint(currentCheckpoint-2)\bannerEntity,MaterialID(5))

EntityMaterial(checkpoint(0)\bannerEntity,MaterialID(checkpoint(0)\materialActive))


;- Make special zones
Restore specialZonesData
Repeat
  AddElement(zone())
  Read.f zone()\position\x
  Read.f zone()\position\z
  Read.f zone()\angle\y
  Read.f zone()\width
  Read.f zone()\length
  Read.i zone()\zonetype

  If zone()\zoneType <> 999
    makeSpecialZoneObject(@zone())
    zone()\rectangle\x1 = NEWXVALUE(zone()\position\x,90 + zone()\angle\y + ATan2(-zone()\width/2,-zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\y1 = NEWZVALUE(zone()\position\z,90 + zone()\angle\y + ATan2(-zone()\width/2,-zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\x2 = NEWXVALUE(zone()\position\x,90 + zone()\angle\y + ATan2(zone()\width/2,-zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\y2 = NEWZVALUE(zone()\position\z,90 + zone()\angle\y + ATan2(zone()\width/2,-zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\x3 = NEWXVALUE(zone()\position\x,90 + zone()\angle\y + ATan2(zone()\width/2,zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\y3 = NEWZVALUE(zone()\position\z,90 + zone()\angle\y + ATan2(zone()\width/2,zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\x4 = NEWXVALUE(zone()\position\x,90 + zone()\angle\y + ATan2(-zone()\width/2,zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
    zone()\rectangle\y4 = NEWZVALUE(zone()\position\z,90 + zone()\angle\y + ATan2(-zone()\width/2,zone()\length/2)* #RAD2DEGMULT,Sqr(POW2(zone()\width/2) + POW2(zone()\length/2)))
  EndIf
Until zone()\zoneType = 999
DeleteElement(zone())

; A little fog, for the depth cue
Fog(RGB(0,188,200),10,20,2000)

;- Window3D
OpenWindow3D(#Window3D, 0, 0, 600, 50, "Infos",#PB_Window_BorderLess)
TextGadget3D(#Chrono, 1, 30, 100, 25, "-:--:---")
TextGadget3D(#Speed, 120, 30, 120, 25, "Speed: 0")
ShowGUI(127,#False)

;- Init Forced-timing
CompilerIf #HIDEFTIMER = #True
  ; get number of HD ticks per seconds
  QueryPerformanceFrequency_(@hiDefTimerFreq)
  ; convert it to millisecs, to keep compatibility with "ElapsedMilliseconds()"
  chronoFreqHD = (hiDefTimerFreq\highpart*Pow(2,32)+hiDefTimerFreq\lowpart)/1000
  ; get current value of HD timer
  QueryPerformanceCounter_(@hiDefTimer) 
  currentTimer = hiDefTimer\highpart*Pow(2,32)+hiDefTimer\lowpart 
CompilerElse
  ; get current value of timer
  currentTimer = ElapsedMilliseconds()
CompilerEndIf
oldTimer = currentTimer


;- **** MAIN LOOP *******************************************************************************
Repeat
  If FullScreen = #False 
    While WindowEvent() : Wend 
  EndIf  
  
  ;- Keyboard
  ExamineKeyboard()
  
  ; F1 changes the camera view
  If KeyboardReleased(#PB_Key_F1)
    cameraMode = (cameraMode+1) % 7
  EndIf
  ; F2 changes the car followed by the camera (ghost or player)
  If KeyboardReleased(#PB_Key_F2) And ghostExist = #True
    cameraCarToFollow = 1-cameraCarToFollow
  EndIf
  
  ; If race isn't over, check keyboard inputs
  If finished = #False
  
    ; UP = accelerate
    If KeyboardPushed(#PB_Key_Up)
      ; The first time you strike the Up key is the signal to start the race
      If started = #False
        started = #True
        chrono = 0
      EndIf ; if started...
      ; Accelerate until maxThrottle is reached
      car(1)\throttle + (speedfactor/10)
      If car(1)\throttle > car(1)\ptrCarType\maxThrottle 
        car(1)\throttle = car(1)\ptrCarType\maxThrottle
      EndIf
      car(1)\brake=0
    ElseIf KeyboardPushed(#PB_Key_Down) ; DOWN = brake
      car(1)\throttle = 0
      car(1)\brake = car(1)\ptrCarType\maxBrake 
    Else ; Neither Up nor Down are pressed => engine brake
      car(1)\brake = car(1)\ptrCarType\engineBrake
      car(1)\throttle = 0  
    EndIf
    
    ; LEFT / RIGHT = steering  
    If KeyboardPushed(#PB_Key_Right)
      car(1)\steerangle-(#PI/32)*(speedFactor/40)
      If car(1)\steerangle < -#PI/4.0 
        car(1)\steerangle = -#PI/4.0 
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Left)
        car(1)\steerangle+(#PI/32.0)*(speedFactor/40)
        If car(1)\steerangle > #PI/4.0 
          car(1)\steerangle = #PI/4.0 
        EndIf
    Else
      ; Neither Left nor Right are pressed => steering returns to 0
      If car(1)\steerangle > 0  ; steering is greater than 0 => decrease it until 0 is reached
        car(1)\steerangle -(#PI/32.0)*(speedFactor/20)
        If car(1)\steerangle < 0
          car(1)\steerangle = 0
        EndIf
      ElseIf car(1)\steerangle < 0 ; steering is lesser than 0 => increase it until 0 is reached
        car(1)\steerangle +(#PI/32.0)*(speedFactor/20)
        If car(1)\steerangle > 0
          car(1)\steerangle = 0
        EndIf
      EndIf    
    EndIf  
        
    ;- Special zones
    ; Check if the car is in a "special zone"
    manageSpecialZones(1)

    
    ;- Checkpoints
    If checkCurrentCheckpoint(1) = #True
      ; If "checkCurrentCheckpoint" returns #True, the last checkpoint has been crossed and the race is over
      finished = #True
      chronoFinish = chrono
    EndIf 
    
  Else ; race is over
    ; Brake and steer to init a powerslide
    car(1)\throttle = 0
    car(1)\steerangle = (#PI/-16.0)
    If car(1)\speedometer > 5
      car(1)\brake=car(1)\ptrcarType\maxBrake
    Else
      car(1)\brake=car(1)\ptrcarType\engineBrake
      ; When car is stopped, run the finish sequence
      If car(1)\speedometer = 0 And stopped = #False
        stopped = #True
        FinishRace(chronoFinish,ghostFinishTime)
      EndIf
    EndIf
  EndIf
  
  ;- Car physics
  simulateCarPhysics(1,speedfactor/1000)
  
  
  ;- Move/Rotate car
  RotateNode(car(1)\rootNode,0,car(1)\angle\Y * (#RAD2DEGMULT),0)  
  CompilerIf #CARFOLLOWTERRAIN = #True
    RotateNode(car(1)\rootNode,car(1)\angle\X * (#RAD2DEGMULT),0,car(1)\angle\Z * (#RAD2DEGMULT),#PB_Relative)  ; rotation in 2 passes to avoid gimbal lock
  CompilerEndIf
  NodeLocate(car(1)\rootNode,car(1)\position_wc\X,car(1)\position_wc\Y,car(1)\position_wc\Z)
  ; tilt/roll due to longitudinal/lateral forces
  RotateNode(car(1)\bodyNode,-car(1)\tilt*(#RAD2DEGMULT),0,car(1)\roll*(#RAD2DEGMULT))
  ; show steering and wheels rotation
  RotateNode(car(1)\frontLeftWheelNode,0,car(1)\steerangle*(#RAD2DEGMULT),0)
  RotateNode(car(1)\frontRightWheelNode,0,car(1)\steerangle*(#RAD2DEGMULT),0)
  RotateNode(car(1)\frontLeftWheelNode,car(1)\frontWheelRoll,0,0,#PB_Relative)  ; rotation in 2 passes to avoid gimbal lock
  RotateNode(car(1)\frontRightWheelNode,car(1)\frontWheelRoll,0,0,#PB_Relative) ; rotation in 2 passes to avoid gimbal lock
  RotateNode(car(1)\rearLeftWheelNode,car(1)\rearWheelRoll,0,0)
  RotateNode(car(1)\rearRightWheelNode,car(1)\rearWheelRoll,0,0)

  ;- Store current position for the ghost racer
  If started = #True And stopped = #False And chrono - ghosttimer >= 15 ; 60x per second max
    AddElement(thisRace())
    thisRace()\time = chrono
    thisRace()\position\X = car(1)\position_wc\X
    thisRace()\position\Y = car(1)\position_wc\Y
    thisRace()\position\Z = car(1)\position_wc\Z
    thisRace()\angle\X = car(1)\angle\X
    thisRace()\angle\Y = car(1)\angle\Y
    thisRace()\angle\Z = car(1)\angle\Z
    thisRace()\roll = car(1)\roll
    thisRace()\tilt = car(1)\tilt
    thisRace()\steerangle = car(1)\steerangle
    ghosttimer = chrono
  EndIf
 
 
  ;- Move Ghost racer
  ; If a ghost exists and race is started, move ghost
  If ghostExist = #True And started = #True
    ; Look for the next position of the ghost
    While ghostExist = #True And ghostNextPos()\time < chrono
      *ghostPrevPos = @ghostNextPos()
      ; If there's no next position, then the ghost's race is over
      If NextElement(ghostNextPos()) = 0
        ghostExist = #False
        LastElement(ghostNextPos())
        ghostCurrentPos\position\X = ghostNextPos()\position\X
        ghostCurrentPos\position\Y = ghostNextPos()\position\Y
        ghostCurrentPos\position\Z = ghostNextPos()\position\Z
        ghostCurrentPos\steerangle = ghostNextPos()\steerangle
        ghostCurrentPos\angle\Y = ghostNextPos()\angle\Y
        ghostCurrentPos\roll = 0
        ghostCurrentPos\tilt = 0
      EndIf
    Wend
    If ghostExist = #True ; always true, except when you hit the last line in the list      
      ghostInterpolation =  (chrono - *ghostPrevPos\time) / (ghostNextPos()\time - *ghostPrevPos\time) 
    
      ghostCurrentPos\position\X = *ghostPrevPos\position\X + (ghostNextPos()\position\X - *ghostPrevPos\position\X)*ghostInterpolation
      ghostCurrentPos\position\Y = *ghostPrevPos\position\Y + (ghostNextPos()\position\Y - *ghostPrevPos\position\Y)*ghostInterpolation
      ghostCurrentPos\position\Z = *ghostPrevPos\position\Z + (ghostNextPos()\position\Z - *ghostPrevPos\position\Z)*ghostInterpolation
      ghostCurrentPos\angle\Y = *ghostPrevPos\angle\Y + (ghostNextPos()\angle\Y - *ghostPrevPos\angle\Y)*ghostInterpolation
      ghostCurrentPos\steerangle = *ghostPrevPos\steerangle + (ghostNextPos()\steerangle - *ghostPrevPos\steerangle)*ghostInterpolation
      ghostCurrentPos\roll= *ghostPrevPos\roll + (ghostNextPos()\roll - *ghostPrevPos\roll)*ghostInterpolation
      ghostCurrentPos\tilt = *ghostPrevPos\tilt + (ghostNextPos()\tilt - *ghostPrevPos\tilt)*ghostInterpolation
      
    EndIf

    ; Move the ghost car to its new position and angle
    car(0)\position_wc\X = ghostCurrentPos\position\X
    car(0)\position_wc\Y = ghostCurrentPos\position\Y
    car(0)\position_wc\Z = ghostCurrentPos\position\Z
    car(0)\angle\X = ghostCurrentPos\angle\X
    car(0)\angle\Y = ghostCurrentPos\angle\Y
    car(0)\angle\Z = ghostCurrentPos\angle\Z
    car(0)\roll = ghostCurrentPos\roll
    car(0)\tilt = ghostCurrentPos\tilt
    car(0)\steerangle = ghostCurrentPos\steerangle
    RotateNode(car(0)\rootNode,0,car(0)\angle\Y *(#RAD2DEGMULT),0)
    NodeLocate(car(0)\rootNode,car(0)\position_wc\X,car(0)\position_wc\Y,car(0)\position_wc\Z)
    RotateNode(car(0)\frontLeftWheelNode,0,car(0)\steerangle*(#RAD2DEGMULT),0)
    RotateNode(car(0)\frontRightWheelNode,0,car(0)\steerangle*(#RAD2DEGMULT),0)
    RotateNode(car(0)\bodyNode,-car(0)\tilt*(#RAD2DEGMULT),0,car(0)\roll*(#RAD2DEGMULT))
    CompilerIf #CARFOLLOWTERRAIN = #True
      RotateNode(car(0)\rootNode,car(0)\angle\X * (#RAD2DEGMULT),0,car(0)\angle\Z * (#RAD2DEGMULT),#PB_Relative)  
    CompilerEndIf
        
  EndIf
  
  
  ; Place the camera
  manageCamera(cameraMode, cameraCarToFollow, speedfactor)


  ;- Forced-timing
  ; how many milliseconds since the last loop?
  CompilerIf #HIDEFTIMER = #True
    QueryPerformanceCounter_(@hiDefTimer) 
    currentTimer = hiDefTimer\highpart*Pow(2,32)+hiDefTimer\lowpart
    timer(numTimer)=(currentTimer-oldTimer)/chronoFreqHD 
  CompilerElse
    currentTimer = ElapsedMilliseconds()
    timer(numTimer)=currentTimer-oldTimer    
  CompilerEndIf
  ; if timer has wrapped or more than a second since last frame => ignore it and replace current loop time with average loop time
  If currentTimer < oldTimer Or timer(numTimer) > 1000
    ;Debug "Old = " + StrF(oldtimer) + " / New = "+StrF(currentTimer)+ " / Diff = "+StrF(currentTimer-oldTimer)+ " / Average = " + StrF(speedfactor)
    timer(numTimer) = speedfactor
  EndIf
  oldTimer=currentTimer
  
  ; Chrono = how much time has passed since race started
  chrono + timer(numTimer)
  
  ; Average the ten last loops, to "smooth" the differences
  speedFactor=(timer(0)+timer(1)+timer(2)+timer(3)+timer(4)+timer(5)+timer(6)+timer(7)+timer(8)+timer(9))/10
  numtimer+1:If numtimer=10:numtimer=0:EndIf
  
 
  ; A little info
  SetWindowTitle3D(#Window3D, "Car Physics           ("+Str(CountRenderedTriangles()) + " polys, " + Str(Engine3DFrameRate(#PB_Engine3D_Average)) + " FPS)")
  SetGadgetText3D(#Speed,"Speed: "+StrF(car(1)\speedometer,1) + "Km/H")
  If started = #True And finished = #False 
    CHRONO2TEXT(chrono)
    SetGadgetText3D(#Chrono,chronoText )
  EndIf

  ; Show it all
  RenderWorld()
  
  ; Flip buffers to avoid tearing  
  FlipBuffers()
  
Until KeyboardPushed(#PB_Key_Escape)


End

;- data Section
DataSection

checkpointsData:
; X,Z,angle,width
Data.f 422,370,165,30
Data.f 485,122,115,30
Data.f 561,151,28,30
Data.f 558,245,4,30
Data.f 658,315,42,30
Data.f 770,422,67,30
Data.f 944,358,155,30
Data.f 749,517,363,30
Data.f 683,639,311,30
Data.f 431,796,379,30
Data.f 232,878,269,30
Data.f 113,720,175,30
Data.f 292,515,125,30
Data.f 999,999,999,999

SpecialZonesData:
; X,Z,angle,width,length,type
Data.f 741,360,0,23,50
Data.i #BOOST
Data.f 374,861,282,22,50
Data.i #BOOST
Data.f 999,999,999,999,999
Data.i 999

PlanVertices:
Data.f -0.5, -0.5, -0.5
Data.f 1, 1, 1
Data.f 0, 1
Data.f 0.5, -0.5, -0.5
Data.f 1,1,1
Data.f 1, 1
Data.f 0.5, -0.5, 0.5
Data.f 1,1,1
Data.f 1, 0
Data.f -0.5, -0.5, 0.5
Data.f 1,1,1
Data.f 0,0

PlanFaces:
Data.w 2, 1, 0
Data.w 0, 3, 2

EndDataSection
Have fun!
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Mistrel »

I downloaded the sample with media but it asks for Engine3D.dll, which is missing. I tried the dll from PB 4.31 and 4.40 but with both demos the camera just zooms out until everything turns black.

I tried to compile from the source but it encounters an "invalid memory access at address 0" on line 1714 with PB 4.40. It compiles with PB 4.31 but has the same camera problem.
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

mmh, strange... :?

I have downloaded the ZIP file, unzip it in a folder, added "engine3d.dll" from PB 4.31 int this folder, and launched the EXE => works fine.

- Did you try it Windowed / full screen ?
- What Fps do you get ?
- Try to hit F1 just after game start to switch to another camera mode (there's 5 different views); perhaps one of them will work...
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by c4s »

Wow this looks really good :!:

With the last view (bird) I thought of a GTA Clone... :mrgreen:
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
gnasen
Enthusiast
Enthusiast
Posts: 282
Joined: Wed Sep 24, 2008 12:21 am

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by gnasen »

Everything works fine here, I was testing it with the pb4.31 engine3d.dll.
By the way: Nice done !
pb 5.11
User avatar
Blue
Addict
Addict
Posts: 964
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Blue »

Done everything suggested above, but still can't get it to work on my machine.
The problem occurs on line 1028 Procedure.f ATan2(y.f, x.f)
I get an "Invalid Memory Access" error. (write error at address 0)

Could it be because of the x64 in "Windows 7 x64" ?

@All of you above: Are any running this on a 64-bit system ?
PB Forums : Proof positive that 2 heads (or more...) are better than one :idea:
gnasen
Enthusiast
Enthusiast
Posts: 282
Joined: Wed Sep 24, 2008 12:21 am

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by gnasen »

Blue wrote:@All of you above: Are any running this on a 64-bit system ?
Im running WinXP SP3 32Bit
pb 5.11
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Fred »

It's really impressive !
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

:shock: Wow! A comment from Fred himself ! I'm really honored! Squeeee! :D Thanks a lot, Fred!

@Blue:
Yep, you're probably right about Atan2 and PB 64 bits : it's an ASM proc conceived for PB4.20, so it's not so surprising...
Alas, I know absolutely nothing about ASM, so I'll need help to fix that (wink wink nudge nudge)...
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

Fourth part of the serie "post all my old code before the 4.40 is out" is this way !
Anonymous

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Anonymous »

Doesn't work here too , with the 4.31 dll and 4.4.

i see just the interior view , and when i press the up key , camera turn on all axis...


Error :

Title : MS visual c++ runtime library

contend :
assertion failled
File = y:/ogremain/include/ogrealignedaxisbox.h
line 246
expression : blabla ( the min corner must be less or equal to max corner )
blalbla... - JIT must be activated )
Helle
Enthusiast
Enthusiast
Posts: 178
Joined: Wed Apr 12, 2006 7:59 pm
Location: Germany
Contact:

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Helle »

ATan2 for 64-Bit:

Code: Select all

Procedure.f ATan2(y.f, x.f)    ;[-Pi, Pi[
  !FLD dword [p.v_y]
  !FLD dword [p.v_x]
  !FPATAN
  !ADD rsp,40
  !RET
EndProcedure
Gruss
Helle
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: [PB4.31] Car Physics (based on Marco Monster's Tutorial)

Post by Kelebrindae »

Thanks a lot, Helle! :D

In the part 1 of the code, juste after the "-- Trigo and utility procs --" tag, I've edited the code:

Code: Select all

;- -- Trigo and utility procs --
CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
  ; La procédure ci-dessous est de Dr Dri. Merci Dr Dri !
  Procedure.f ATan2(y.f, x.f)    ;[-Pi, Pi[
    !FLD dword [p.v_y]
    !FLD dword [p.v_x]
    !FPATAN
    !RET 8
  EndProcedure
CompilerEndIf  
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
  ; Same thing for 64bits, from Helle. Thanks Helle !
  Procedure.f ATan2(y.f, x.f)    ;[-Pi, Pi[
    !FLD dword [p.v_y]
    !FLD dword [p.v_x]
    !FPATAN
    !ADD rsp,40
    !RET
  EndProcedure
CompilerEndIf
But I can't test it, 'cos I don't have a x64 machine...
Post Reply