Course de voiture (Basé sur tuto "Car Physics")

Programmation avancée de jeux en PureBasic
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Course de voiture (Basé sur tuto "Car Physics")

Message par kelebrindae »

Bonjour à tous!

Je suis tombé (comme beaucoup d'autres avant moi) sur le célèbre tutorial "Car Physics" de Marco Monster, et je me suis dit que ça pourrait être intéressant d'essayer de la suivre avec PB.
Voici donc le résultat, un petit embryon de jeu de bagnole plutôt orienté Arcade.

Je mets le listing, mais comme ça nécessite pas mal de média, j'ai aussi zippé le tout à l'adresse suivante:
http://keleb.free.fr/codecorner/downloa ... hysics.zip (environ 3Mo)

Les contrôles sont simples:
- touches du curseur pour gauche/droite/accélérer/freiner
- F1 pour changer de caméra
- ESC pour quitter

Pour ajouter un peu de sel, j'ai ajouté des checkpoints, des zones "booster", et un système de ghost (une voiture fantôme figure votre meilleur temps; bien sûr, il faut au préalable avoir fini la course au moins une fois)

Remarques:
- le tutorial de Marco Monster concerne la 2D, et je ne suis pas allé plus loin: même si les graphismes sont en 3D, le moteur du jeu est 2D seulement (cf. la vue aérienne, qui l'illustre bien) => le relief du circuit n'est pas géré.
- les collisions avec le décor ne sont pas gérées non plus.
- le fait que vous soyez sur la route ou non n'est pas géré (l'adhérence ne change pas)
- la physique n'est pas parfaite: certains dérapages (à l'arrivée, souvent) sont anormalement longs...
- j'ai expérimenté le timer haute-résolution de Windows (queryPerformanceCounter), mais j'ai l'impression que ça rend parfois le programme un peu instable... :?

Au debut du code, vous trouverez trois variables prépro (#HIDEFTIMER,#REALISTICWEIGHTTRANSFER,#CARFOLLOWTERRAIN) qui permettent d'activer/désactiver respectivement: le timer haute résolution (pour compiler sous Linux, par exemple), un modèle de transfert de poids plus réaliste mais plus difficile à contrôler, et une approximation de prise en compte du relief du terrain.

Que dire d'autre? Ah oui: j'ai emprunté certains bouts de code à des membres éminents de ce forum: Comtois (pour la génération de meshes et NEWVALUE/CurveValue), Dr Dri pour la procédure ASM Atan2, et Typhoon pour la procédure qui teste si un point est dans un rectangle. Grand merci à vous, les gars!

Voilà, bonne course! (en espérant que le programme marche chez tout le monde...)

[EDIT]
Zut! Le code est trop long! Je suis obligé de couper le listing en deux...

[EDIT 19/01/2009]
Mise à jour du code avec simplifications/corrections de bugs

[EDIT 19/01/2009 n°2]
- Correction de la caméra
- F2 permet de pointer la caméra sur le ghost ou sur le joueur

[EDIT 23/01/2008]
- Correction de curveAngle
- Un peu moins de saccades pour le ghost avec le hi-def timer

Partie 1:

Code : Tout sélectionner

; Author: Kelebrindae
; Date: january, 12, 2009
; PB version: v4.30
; 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!
; ---------------------------------------------------------------------------------------------------------------


;- 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") 
Else 
  OpenWindow(0,0, 0, 800 , 600 ,"Car Physics") 
  OpenWindowedScreen(WindowID(0),0,0, 800 , 600,0,0,0) 
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 = #True                 ; 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

; Cylinder mesh caps
Enumeration
  #CYLCAP_NONE
  #CYLCAP_BOTH
  #CYLCAP_TOP
  #CYLCAP_BOTTOM
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


;- ------ 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: createBoxMesh
; Purpose: create a "box" mesh
; Parameters: 
;   - number of subdivisions (given "3", each face of the box will be a 3x3 grid)
;   - sizeX, sizeY, sizeZ : size of the box
;   - pivotX, pivotY, pivotZ : location of the pivot of the box, around which it rotates (default = center = 0,0,0)
;   - Uorigin, Vorigin : UV map coordinates (default = top left of the map = 0,0)
;   - Uscale, Vscale : UV map scale (default = 1,1)
;   - color of the vertices (default = white)
; Return-Value: number of the resulting mesh, -1 if creation failed
;************************************************************************************
Procedure.l createBoxMesh(nbdiv.w,sizeX.f,sizeY.f,sizeZ.f,pivotX.f = 0,pivotY.f = 0,pivotZ.f = 0,Uorigin.f = 0,Vorigin.f = 0,Uscale.f = 1,Vscale.f = 1,color.l = $FFFFFF)

  Protected sizediv.f
  Protected x1.f,y1.f,z1.f                  ; vertex position
  Protected x2.f,y2.f,z2.f                  ; vertex position
  Protected x3.f,y3.f,z3.f                  ; vertex position
  Protected x4.f,y4.f,z4.f                  ; vertex position
  Protected nx.f,ny.f,nz.f                  ; vertex normals
  Protected u.f,v.f                         ; vertex UV coords (texture mapping)
  Protected numvert.w,numvert0.w            ; vertices of a poly
  Protected *PtrV.Vertex,*PtrV0.Vertex      ; vertices buffer in memory
  Protected *ptrP.Polygon,*ptrP0.Polygon    ; Polys buffer in memory
  Protected *vertexBuffer.l
  Protected *polygonBuffer.l
  Protected num.l,i.l,j.l,nbtri.l,nbvert
  Protected maxSize.f
  Protected newmesh.l                       ; Procedure Result

  nbtri  = 6 * (nbDiv * nbDiv * 2) ; 6 sides * nb divisions * nb divisions * 2 triangles per division
  nbvert = 6 * (nbDiv * nbDiv * 4) ; 6 sides * nb divisions * nb divisions * 4 vertices per division
  
  ; Allocate the needed memory for vertices
  *vertexBuffer = AllocateMemory(SizeOf(Vertex)*nbVert) 
  *PtrV = *vertexBuffer 
  
  ; Allocate the needed memory for faces info
  *polygonBuffer=AllocateMemory(SizeOf(Polygon)*nbTri) 
  *ptrP=*polygonBuffer

  sizeDiv = 1/nbDiv
  ; Top
  x1=-0.5:y1=0.5:z1=-0.5
  x2=-0.5+sizeDiv:y2=0.5:z2 = z1
  x3=x2:y3=0.5:z3=-0.5+sizeDiv
  x4=x1:y4=0.5:z4=z3
  
  For i=1 To nbDiv
    For j=1 To nbDiv
      
      ; 1 square = 4 vertices
      *PtrV\px = x1
      *PtrV\py = y1
      *PtrV\pz = z1
      *PtrV\nx = *PtrV\px 
      *PtrV\ny = *PtrV\py
      *PtrV\nz = *PtrV\pz
      *PtrV\couleur = color
      *PtrV\u = uorigin + (uscale/nbdiv)*(i-1)
      *PtrV\v = vorigin + (vscale/nbdiv)*(j-1)
      *PtrV + SizeOf(Vertex) 

      *PtrV\px = x2
      *PtrV\py = y2
      *PtrV\pz = z2  
      *PtrV\nx = *PtrV\px 
      *PtrV\ny = *PtrV\py
      *PtrV\nz = *PtrV\pz
      *PtrV\couleur = color
      *PtrV\u = uorigin + (uscale/nbdiv)*(i-1) + uscale/nbdiv
      *PtrV\v = vorigin + (vscale/nbdiv)*(j-1)
      *PtrV + SizeOf(Vertex) 

      *PtrV\px = x3 
      *PtrV\py = y3
      *PtrV\pz = z3  
      *PtrV\nx = *PtrV\px 
      *PtrV\ny = *PtrV\py
      *PtrV\nz = *PtrV\pz
      *PtrV\couleur = color
      *PtrV\u = uorigin + (uscale/nbdiv)*(i-1) + uscale/nbdiv
      *PtrV\v = vorigin + (vscale/nbdiv)*(j-1) + vscale/nbdiv
      *PtrV + SizeOf(Vertex) 
      
      *PtrV\px = x4 
      *PtrV\py = y4
      *PtrV\pz = z4 
      *PtrV\nx = *PtrV\px 
      *PtrV\ny = *PtrV\py
      *PtrV\nz = *PtrV\pz
      *PtrV\couleur = color
      *PtrV\u = uorigin + (uscale/nbdiv)*(i-1)
      *PtrV\v = vorigin + (vscale/nbdiv)*(j-1) + vscale/nbdiv
      *PtrV + SizeOf(Vertex) 

      ; 1 square = 2 triangles
      *ptrP\numVert1=numvert+3  
      *ptrP\numVert2=numvert+2
      *ptrP\numVert3=numvert 
      *ptrP + SizeOf(Polygon)

      *ptrP\numVert1=numvert  
      *ptrP\numVert2=numvert+2
      *ptrP\numVert3=numvert+1
      *ptrP + SizeOf(Polygon)
      
      numvert+4     

      z1=z4
      z2=z3
      z3+sizeDiv
      z4=z3

    Next j
    
    x1=x2
    x4=x3
    x2+sizeDiv
    x3=x2
    z1=-0.5
    z2=z1
    z3=-0.5+sizeDiv
    z4=z3
     
  Next i
  numvert0=numvert
  
  ; Bottom
  *PtrV0 = *vertexBuffer   
  For i=1 To numvert0
    *PtrV\px = -*PtrV0\px
    *PtrV\py = -*PtrV0\py
    *PtrV\pz = *PtrV0\pz  
    *PtrV\nx = *PtrV\px 
    *PtrV\ny = *PtrV\py
    *PtrV\nz = *PtrV\pz
    *PtrV\couleur = *PtrV0\couleur
    *PtrV\u = *PtrV0\u
    *PtrV\v = *PtrV0\v
    
    *PtrV + SizeOf(Vertex) 
    *PtrV0 + SizeOf(Vertex) 
    numvert+1
    
    If i%4=0
      *ptrP\numVert1=numvert - 2 
      *ptrP\numVert2=numvert - 3
      *ptrP\numVert3=numvert - 4 
      *ptrP + SizeOf(Polygon)  
    
      *ptrP\numVert1=numvert - 4 
      *ptrP\numVert2=numvert - 1
      *ptrP\numVert3=numvert - 2 
      *ptrP + SizeOf(Polygon)   
    EndIf
    
  Next i

  ; Right
  *PtrV0 = *vertexBuffer   
  For i=1 To numvert0
    *PtrV\px = *PtrV0\py
    *PtrV\py = -*PtrV0\px
    *PtrV\pz = *PtrV0\pz  
    *PtrV\nx = *PtrV\px 
    *PtrV\ny = *PtrV\py
    *PtrV\nz = *PtrV\pz
    *PtrV\couleur = *PtrV0\couleur
    *PtrV\u = 1-*PtrV0\v
    *PtrV\v = *PtrV0\u
    
    *PtrV + SizeOf(Vertex) 
    *PtrV0 + SizeOf(Vertex) 
    numvert+1
    
    If i%4=0
      *ptrP\numVert1=numvert - 2 
      *ptrP\numVert2=numvert - 3
      *ptrP\numVert3=numvert - 4 
      *ptrP + SizeOf(Polygon)  
     
      *ptrP\numVert1=numvert - 4 
      *ptrP\numVert2=numvert - 1
      *ptrP\numVert3=numvert - 2 
      *ptrP + SizeOf(Polygon)  
    EndIf
    
  Next i

  ; Left
  *PtrV0 = *vertexBuffer   
  For i=1 To numvert0
    *PtrV\px = -*PtrV0\py
    *PtrV\py = -*PtrV0\px
    *PtrV\pz = *PtrV0\pz  
    *PtrV\nx = *PtrV\px 
    *PtrV\ny = *PtrV\py
    *PtrV\nz = *PtrV\pz
    *PtrV\couleur = *PtrV0\couleur
    *PtrV\u = *PtrV0\v
    *PtrV\v = *PtrV0\u
    
    *PtrV + SizeOf(Vertex) 
    *PtrV0 + SizeOf(Vertex) 
    numvert+1
    
    If i%4=0
      *ptrP\numVert1=numvert - 4 
      *ptrP\numVert2=numvert - 3
      *ptrP\numVert3=numvert - 2 
      *ptrP + SizeOf(Polygon)  
    
      *ptrP\numVert1=numvert - 2 
      *ptrP\numVert2=numvert - 1
      *ptrP\numVert3=numvert - 4 
      *ptrP + SizeOf(Polygon)  
    EndIf
    
  Next i

  ; Front
  *PtrV0 = *vertexBuffer   
  For i=1 To numvert0
    *PtrV\px = -*PtrV0\pz
    *PtrV\py = -*PtrV0\px
    *PtrV\pz = -*PtrV0\py  
    *PtrV\nx = *PtrV\px 
    *PtrV\ny = *PtrV\py
    *PtrV\nz = *PtrV\pz
    *PtrV\couleur = *PtrV0\couleur
    *PtrV\u = *PtrV0\v
    *PtrV\v = *PtrV0\u
    
    *PtrV + SizeOf(Vertex) 
    *PtrV0 + SizeOf(Vertex) 
    numvert+1
    
    If i%4=0
      *ptrP\numVert1=numvert - 4 
      *ptrP\numVert2=numvert - 3
      *ptrP\numVert3=numvert - 2 
      *ptrP + SizeOf(Polygon)  
    
      *ptrP\numVert1=numvert - 2 
      *ptrP\numVert2=numvert - 1
      *ptrP\numVert3=numvert - 4 
      *ptrP + SizeOf(Polygon)  
    EndIf
    
  Next i
  
  ; Back
  *PtrV0 = *vertexBuffer   
  For i=1 To numvert0
    *PtrV\px = -*PtrV0\pz
    *PtrV\py = -*PtrV0\px
    *PtrV\pz = *PtrV0\py  
    *PtrV\nx = *PtrV\px 
    *PtrV\ny = *PtrV\py
    *PtrV\nz = *PtrV\pz
    *PtrV\couleur = *PtrV0\couleur
    *PtrV\u = 1-*PtrV0\v
    *PtrV\v = *PtrV0\u
    
    *PtrV + SizeOf(Vertex) 
    *PtrV0 + SizeOf(Vertex) 
    numvert+1
    
    If i%4=0
      *ptrP\numVert1=numvert - 2 
      *ptrP\numVert2=numvert - 3
      *ptrP\numVert3=numvert - 4 
      *ptrP + SizeOf(Polygon)  
    
      *ptrP\numVert1=numvert - 4 
      *ptrP\numVert2=numvert - 1
      *ptrP\numVert3=numvert - 2 
      *ptrP + SizeOf(Polygon)  
    EndIf
    
  Next i

  ; Resize
  If sizeX<>1 Or sizeY<>1 Or sizeZ<>1
    *ptrV = *vertexBuffer
    For i=1 To nbVert
      *PtrV\px = *PtrV\px*sizeX - pivotX
      *PtrV\py = *PtrV\py*sizeY - pivotY
      *PtrV\pz = *PtrV\pz*sizeZ - pivotZ
      
      *PtrV+SizeOf(vertex)
    Next i  
  EndIf

  ; Create mesh from stored infos
  maxSize = sizeX
  If sizeY > sizeX
    maxSize = sizeY
  EndIf
  If sizeZ > maxSize
    maxSize = sizeZ
  EndIf
  
  newMesh = CreateMesh(#PB_Any,maxSize)
  If IsMesh(newMesh) 
    SetMeshData(newMesh,#PB_Mesh_Vertex | #PB_Mesh_Normal | #PB_Mesh_UVCoordinate | #PB_Mesh_Color,*vertexBuffer,nbVert) 
    SetMeshData(newMesh,#PB_Mesh_Face,*polygonBuffer,nbTri)     
    FreeMemory(*vertexBuffer)
    FreeMemory(*polygonBuffer)
    ProcedureReturn newMesh
  Else 
    ; free memory if "createMesh" has failed
    FreeMemory(*vertexBuffer)
    FreeMemory(*polygonBuffer)
    ProcedureReturn -1    
  EndIf 

EndProcedure


;************************************************************************************
; Name: createCylinderMesh
; Purpose: create a cylinder mesh
; Parameters: 
;   - number of sides ("3" gives a prism, "4" gives a box)
;   - height
;   - radius
;   - uncapped (=0), caps on top an bottom (=1), top cap only (=2), bottom cap only (=3). (default = 1)
;   - color of the vertices (default = white)
; Return-Value: number of the resulting mesh, -1 if creation failed
;************************************************************************************
Procedure.l createCylinderMesh(nbSides.l,height.f,radius.f,capped.b = 1,coul.l = $FFFFFF)
  Protected *PtrV.Vertex,*vertexBuffer.l    ; vertices buffer in memory
  Protected *PtrF.Polygon,*facetriBuffer.l  ; Faces buffer in memory
  Protected i.l,nbVert.l,nbTri.l, numVertTop.l,numVertBottom.l
  Protected h2.f,theta.f
  Protected newmesh.l                       ; Procedure Result
   
  If nbSides<3
    ProcedureReturn 0
  EndIf

  h2 = height / 2.0
  nbVert = 4*(nbSides+1)+2
  If capped = #CYLCAP_TOP Or capped = #CYLCAP_BOTTOM
    nbVert-1
  Else
    If capped = #CYLCAP_NONE
      nbVert-2
    EndIf
  EndIf   
  
  *vertexBuffer = AllocateMemory(SizeOf(Vertex)*nbVert)
  *PtrV = *vertexBuffer
   
  ;Vertices at the bottom of the cylinder
  For i = 0 To nbSides
    theta =2*#PI*i/nbSides
     
    *PtrV\px = radius*Cos(theta)
    *PtrV\py = -h2
    *PtrV\pz = radius*Sin(theta)
    *PtrV\nx = Cos(theta)
    *PtrV\ny = 0
    *PtrV\nz = Sin(theta)
    *PtrV\couleur = Coul
    *PtrV\u = Theta / (2.0*#PI)
    *PtrV\v = 0
    *PtrV + SizeOf(Vertex)
  Next i

   
  ;Vertices at the top of the cylinder
  For i = 0 To nbSides
    theta =2*#PI*i/nbSides
     
    *PtrV\px = radius*Cos(theta)
    *PtrV\py = h2
    *PtrV\pz = radius*Sin(theta)
    *PtrV\nx = Cos(theta)
    *PtrV\ny = 0
    *PtrV\nz = Sin(theta)
    *PtrV\couleur = Coul
    *PtrV\u = Theta / (2.0*#PI)
    *PtrV\v = 1
    *PtrV + SizeOf(Vertex)
  Next i

      
  ;Vertices at the bottom of the cylinder
  For i = 0 To nbSides
    theta =2*#PI*i/nbSides
     
    *PtrV\px = radius*Cos(theta)
    *PtrV\py = -h2
    *PtrV\pz = radius*Sin(theta)
    *PtrV\nx = 0
    *PtrV\ny = -1
    *PtrV\nz = 0
    *PtrV\couleur = Coul
    *PtrV\u = Theta / (2.0*#PI)
    *PtrV\v = 1
    *PtrV + SizeOf(Vertex)
  Next i
               
  ;Vertices at the top of the cylinder
  For i = 0 To nbSides
    theta =2*#PI*i/nbSides
     
    *PtrV\px = radius*Cos(theta)
    *PtrV\py = h2
    *PtrV\pz = radius*Sin(theta)
    *PtrV\nx = 0
    *PtrV\ny = 1
    *PtrV\nz = 0
    *PtrV\couleur = Coul
    *PtrV\u = Theta / (2.0*#PI)
    *PtrV\v = 1
    *PtrV + SizeOf(Vertex)
  Next i
   
  ;Bottom cap center
  If capped = #CYLCAP_BOTH Or capped = #CYLCAP_BOTTOM
    numVertBottom = (*PtrV - *vertexBuffer) / SizeOf(Vertex)
    
    *PtrV\px = 0
    *PtrV\py = -h2
    *PtrV\pz = 0
    *PtrV\nx = 0
    *PtrV\ny = -1
    *PtrV\nz = 0
    *PtrV\couleur = Coul
    *PtrV\u = 0.5
    *PtrV\v = 0.5
    *PtrV + SizeOf(Vertex)
  EndIf
  
  ;Top cap center
  If capped = #CYLCAP_BOTH Or capped = #CYLCAP_TOP
    numVertTop = (*PtrV - *vertexBuffer) / SizeOf(Vertex)
    
    *PtrV\px = 0
    *PtrV\py = h2
    *PtrV\pz = 0
    *PtrV\nx = 0
    *PtrV\ny = 1
    *PtrV\nz = 0
    *PtrV\couleur = Coul
    *PtrV\u = 0.5
    *PtrV\v = 0.5
  EndIf 
   
  ;Facets
  nbTri = 4*nbSides
  If capped = #CYLCAP_BOTTOM Or capped = #CYLCAP_TOP
    nbTri - nbSides
  Else
    If capped = #CYLCAP_NONE
      nbTri - (nbSides*2)
    EndIf
  EndIf
    
  *facetriBuffer=AllocateMemory(SizeOf(Polygon)*nbTri)
  *PtrF=*facetriBuffer
  
  For i=0 To nbSides-1
      *PtrF\numVert3=i
      *PtrF\numVert2=i + 1
      *PtrF\numVert1=nbSides + i + 2
      *PtrF + SizeOf(Polygon)
    
    
      *PtrF\numVert1=i 
      *PtrF\numVert3=nbSides + i + 2
      *PtrF\numVert2=nbSides + i + 1
      *PtrF + SizeOf(Polygon)
  Next i
   
  
  ;Bottom cap
  If capped = #CYLCAP_BOTH Or capped = #CYLCAP_BOTTOM   
    For i=0 To nbSides-1
      *PtrF\numVert1= numVertBottom
      *PtrF\numVert2= 2 * nbSides + 2 + i
      *PtrF\numVert3= 2 * nbSides + 3 + i
      *PtrF + SizeOf(Polygon)
    Next i      
  EndIf 
  ;Top cap
  If capped = #CYLCAP_BOTH Or capped = #CYLCAP_TOP  
    For i=0 To nbSides-1
      *PtrF\numVert1= numVertTop
      *PtrF\numVert3= 3 * nbSides + 3 + i
      *PtrF\numVert2= 3 * nbSides + 4 + i
      *PtrF + SizeOf(Polygon)
    Next i      
  EndIf
  
  ; Create mesh from stored infos
  newmesh = CreateMesh(#PB_Any,radius)
  If IsMesh(newmesh) 
    SetMeshData(newmesh,#PB_Mesh_Vertex | #PB_Mesh_Normal | #PB_Mesh_UVCoordinate | #PB_Mesh_Color,*vertexBuffer,nbVert) 
    SetMeshData(newmesh,#PB_Mesh_Face,*facetriBuffer,nbTri) 
    ; and don't forget to free memory
    FreeMemory(*vertexBuffer)
    FreeMemory(*facetriBuffer)
    ProcedureReturn newmesh 
  Else 
    ; even if "createMesh" has failed
    FreeMemory(*vertexBuffer)
    FreeMemory(*facetriBuffer)
    ProcedureReturn -1    
  EndIf 
   
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,270,0,270):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
  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 hte 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 = createCylinderMesh(5,height,0.2,#CYLCAP_TOP,$AA6600)
  bannerMesh = createBoxMesh(1,checkpoint(numcheckpoint)\width,bannerHeight,0.1)
  
  ; Create entities
  checkpoint(numcheckpoint)\bannerEntity = CreateEntity(#PB_Any,MeshID(bannerMesh),MaterialID(numMaterial))
  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)

  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 --

; 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

; 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 = ATan2( yawspeed, velocity\x)
    sideslip = ATan2( 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)

  EntityMaterial(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
    EntityMaterial(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
Dernière modification par kelebrindae le ven. 23/janv./2009 18:11, modifié 4 fois.
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Message par kelebrindae »

Partie 2:

Code : Tout sélectionner

;- -- 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  
  Debug pv
  Debug 200/pv
  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

Dernière modification par kelebrindae le ven. 23/janv./2009 18:13, modifié 4 fois.
Avatar de l’utilisateur
Cool Dji
Messages : 1126
Inscription : ven. 05/sept./2008 11:42
Localisation : Besançon
Contact :

Message par Cool Dji »

Yaouh,
La classe, bravo...merci pour le code !

Bonne continuation !!!
Only PureBasic makes it possible
Anonyme

Message par Anonyme »

tu peut mettre un exe , le code marche pas , j'ai des rotations bizarre avec un joli plantage.
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Message par kelebrindae »

@Cpl.Bator:
L'exe se trouve ici: http://keleb.free.fr/codecorner/downloa ... hysics.exe (il faut quand même les médias contenus dans le zip)

L'erreur que tu me décris est bizarre... :?
Essaie de désactiver le timer haute-résolution (#HIDEFTIMER = #False), peut-être. Ou alors c'est la petite procédure Atan2 en ASM qui se comporte différemment chez toi? (c'est possible, ça?) Tu as quoi, comme processeur?

@Cool Dji:
Merci! :)
As-tu remarqué des bugs ou des plantages chez toi?
Avatar de l’utilisateur
Cool Dji
Messages : 1126
Inscription : ven. 05/sept./2008 11:42
Localisation : Besançon
Contact :

Message par Cool Dji »

J'ai eu un petit plantage :cry: :
Image
ça c'est produit quand je faisais un joli dérrapage sous un CheckPoint.
Je ne sais pas si ces infos seront suffisantes pour débuger !!!
Amuses-toi bien... :?
Only PureBasic makes it possible
comtois
Messages : 5186
Inscription : mer. 21/janv./2004 17:48
Contact :

Message par comtois »

Wow, excellent !

pour l'instant ça n'a pas planté chez moi, mais j'ai testé quelques minutes, c'est trop sensible, on part en dérapage trop vite.

Mais j'aime, la réalisation est superbe.

Pour le défaut, je l'ai déjà rencontré
voir ici

@Cool Dji

tu sais que tu peux cliquer sur la fenêtre du défaut, puis faire un [CTRL]+[C] pour copier le texte ? Tu n'as plus qu'à faire un [CTRL]+[V] pour le coller dans le post, c'est quand même plus simple :)
Dernière modification par comtois le jeu. 15/janv./2009 14:57, modifié 1 fois.
http://purebasic.developpez.com/
Je ne réponds à aucune question technique en PV, utilisez le forum, il est fait pour ça, et la réponse peut profiter à tous.
Avatar de l’utilisateur
djes
Messages : 4252
Inscription : ven. 11/févr./2005 17:34
Localisation : Arras, France

Message par djes »

Tiens je croyais que Fred l'avait corrigé celui-là :)
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Message par kelebrindae »

@Cool Dji:
ça, c'est le genre de plantages causés par le timer haute-résolution.

Pour faire simple, chaque mouvement dans le jeu est "multiplié" par le temps écoulé depuis la dernière boucle. Ce temps est mesuré par le timer.

Or, ce timer revient à zéro de temps en temps (lorsqu'il a atteint sa valeur maxi). Donc, le temps calculé pour la boucle => T(n) - T(n-1) est non seulement négatif (ce qui n'est pas encore trop grave) mais surtout extrêmement grand (de l'ordre de la valeur maxi d'un quad, je pense).
Le mouvement de ta voiture sera donc multiplié par une valeur immense, ce qui la fait apparemment sortir des limites supportées par Ogre.

Je pensais avoir résolu le problème en excluant les valeurs négatives, mais apparemment, ce n'est pas encore ça...
Si ça se produit trop souvent, tu peux désactiver le timer haute-résolution => le programme utilisera alors "elapsedMilliseconds()", et la différence est assez minime.


@Comtois:
Merci pour le compliment! :D
Oui, il est possible que le maniement soit un peu délicat au début...
(Je ne m'en rends plus compte - c'est l'inconvénient d'être le testeur de ses propres programmes: on les a lancés tellement souvent qu'on ne voit plus les défauts...)
beauregard
Messages : 1307
Inscription : dim. 08/juil./2007 18:32
Localisation : Toulouse

Message par beauregard »

kelebrindae a écrit :@Cpl.Bator:
L'exe se trouve ici: http://keleb.free.fr/codecorner/downloa ... hysics.exe (il faut quand même les médias contenus dans le zip)

L'erreur que tu me décris est bizarre... :?
Essaie de désactiver le timer haute-résolution (#HIDEFTIMER = #False), peut-être. Ou alors c'est la petite procédure Atan2 en ASM qui se comporte différemment chez toi? (c'est possible, ça?) Tu as quoi, comme processeur?

@Cool Dji:
Merci! :)
As-tu remarqué des bugs ou des plantages chez toi?
C'est la fête au village aujourd'hui ! :)
Bon, sans raconter ma vie( je me décide à installer la 4.30, oui je suis grave à la bourre, ou grave tout court, ExamineJoystick(0) et JoystickButton(0,1) ok c'est bon pour moi. Bon alors, voyons voir de nos yeux ce jeu de course, mmh, fenêtre error, ok, alors copier/coller de 2 dll comme pour pureblock, pas normal cette histoire de dll mais passons, mmh, replantage, mercredi, voyons le code, fortiche dis donc, F5, error, milliard, mmh, bon ben y a un blème avec cette dll, mmh...

...j'ai trouvé, c'est la dll engine3D.dll, faut la version 2008 pour que cela fonctionne et qu'une voiture rouge vienne illuminer mon regard, glop ! :)
comtois
Messages : 5186
Inscription : mer. 21/janv./2004 17:48
Contact :

Message par comtois »

beauregard, faut pas mélanger les dll pour la 4.20 et la 4.30 hein ? :)

kelebrindae , sais-tu que tu peux remplacer tout ça ,en utilisant les objets dynamiques ? avec #PB_any ?

Code : Tout sélectionner

Procedure.i FindFreeEntity(whereToStart.i = 1)
  While IsEntity(whereToStart) <> #False
    whereToStart+1
  Wend
  ProcedureReturn whereToStart
EndProcedure

Procedure.i FindFreeNode(whereToStart.i = 1)
  While IsNode(whereToStart) <> #False
    whereToStart+1
  Wend
  ProcedureReturn whereToStart
EndProcedure
http://purebasic.developpez.com/
Je ne réponds à aucune question technique en PV, utilisez le forum, il est fait pour ça, et la réponse peut profiter à tous.
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Message par kelebrindae »

...
Remplacer avec...
#PB_any?...
...
:oops:

(Aaaaarh! le con, le con, le con! Mais quel con je suis! Aaaaaaaaarghhh!)



Hum...
Euh, oui, je le savais, mais sur ce coup, je n'y ai tout simplement pas pensé.
Je crois que mon passé de DarkBasiciste m'a tout contaminé...
(Bin j'ai l'air fin, tiens! :lol: )
beauregard
Messages : 1307
Inscription : dim. 08/juil./2007 18:32
Localisation : Toulouse

Message par beauregard »

comtois a écrit :beauregard, faut pas mélanger les dll pour la 4.20 et la 4.30 hein ? :)
eh oui, c'est exactement çà ! :) Maintenant que tu es là, j'aimerai voir ce que pb a dans le ventre avec Ogre, j'étais entrain de chercher un lien...

pour la tuture, la gestion de l'ombre m'a étonné, je dois bien l'avouer :)
La marche arrière n'est pas encore implémenté...
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Message par kelebrindae »

@beauregard:
Oui, la marche arrière est une des nombreuses choses qui ne sont pas implémentées... C'est loin d'être un jeu complet, en fait!

Pour les ombres, qu'entends-tu par là?

(Pour tester, tu peux changer

Code : Tout sélectionner

WorldShadows(#PB_Shadow_Modulative)
par

Code : Tout sélectionner

WorldShadows(#PB_Shadow_Additive)
, mais ça ne marche pas avec toutes les cartes graphiques...)
beauregard
Messages : 1307
Inscription : dim. 08/juil./2007 18:32
Localisation : Toulouse

Message par beauregard »

kelebrindae a écrit :@beauregard:
Oui, la marche arrière est une des nombreuses choses qui ne sont pas implémentées... C'est loin d'être un jeu complet, en fait!

Pour les ombres, qu'entends-tu par là?

(Pour tester, tu peux changer

Code : Tout sélectionner

WorldShadows(#PB_Shadow_Modulative)
par

Code : Tout sélectionner

WorldShadows(#PB_Shadow_Additive)
, mais ça ne marche pas avec toutes les cartes graphiques...)
ben y a des ombres alors que je pensais que pb n'étais pas capable d'en produire... heu , dis moi, comtois avais laissé un lien avec plein de fichier rapport à Ogre, si tu pouvais me dire où se trouve, chuis à la masse.

faudrait peut-être un post-it Ogre3D dans forum-3D. Et aussi un dossier Sources3D serai le bienvenue.
Répondre