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