Code: Select all
; Author: Kelebrindae
; Date: january, 12, 2009, updated to PB v4.60 november, 10, 2011
; PB version: v4.60
; OS: Windows XP
; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; A simple "car race" demo, with checkpoints, "special" zones (boosters) and a "ghost" of your best time for opponent.
; The physics engine isn't realistic enough for a simulation, but it could be sufficient for an arcade game.
;
; The physics code is entirely based on Marco Monster's famous "Car Physics" tutorial. Thanks a lot, Marco!
; Tutorial is available here: http://web.archive.org/web/20040827084133/home.planet.nl/~monstrous/
; ---------------------------------------------------------------------------------------------------------------
; Known bugs and limitations:
; ---------------------------------------------------------------------------------------------------------------
; - In some cases, skidding seems (much) too long
; - Only Rear Wheel Drive is implemented
; - Relief and terrain types (road/dirt/...) are not implemented
; - Collisions are not implemented
; - The "CHRONO2TEXT" macro seems ugly to me, but I can't find a better way to do it... Suggestions are welcomed!
; ---------------------------------------------------------------------------------------------------------------
#PB_Engine_Space_Local=1
#PB_Engine_Space_Parent=2
#PB_Engine_Space_World=4
#PB_Engine_Absolute_Rotation=8
#PB_Engine_Relative_Rotation=16
#PB_Engine_Quaternion_Rotation=32
#PB_Engine_Euler_Rotation=64
Absolute = #PB_Engine_Space_world | #PB_Engine_euler_Rotation | #PB_Engine_Absolute_Rotation
Relative = #PB_Engine_Euler_Rotation | #PB_Engine_Relative_Rotation
;- Initialization
Global FullScreen.b
Resultat = MessageRequester("Car Physics","Full Screen ?",#PB_MessageRequester_YesNo)
If Resultat = 6
FullScreen=#True
Else
FullScreen=#False
EndIf
If InitEngine3D() = 0
MessageRequester( "Error" , "Can't initialize 3D, check if engine3D.dll is available" , 0 )
End
ElseIf InitSprite() = 0 Or InitKeyboard() = 0 Or InitMouse() = 0
MessageRequester( "Error" , "Can't find DirectX 7.0 or above" , 0 )
End
EndIf
If Fullscreen = #True
OpenScreen(1024,768,32,"Car Physics",#PB_Screen_SmartSynchronization)
Else
OpenWindow(0,0, 0, 800 , 600 ,"Car Physics")
OpenWindowedScreen(WindowID(0),0,0, 800 , 600,0,0,0,#PB_Screen_SmartSynchronization)
EndIf
Add3DArchive(".", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\schemes", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\imagesets", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\fonts", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\looknfeel", #PB_3DArchive_FileSystem)
Add3DArchive("GUI\layouts", #PB_3DArchive_FileSystem)
;- Constants and enums
#HIDEFTIMER = #False ; Use Hi-Def timer (Windows API)
#REALISTICWEIGHTTRANSFER = #False ; More realistic weight transfer model
#CARFOLLOWTERRAIN = #False ; Can you drive through terrain or not ?
#CAMERA = 0
#NBMAXCARTYPES = 3
#NBMAXCARS = 1
#NBMAXCHECKPOINT = 20
#RAD2DEGMULT = 57.295779513082320877 ;(180/#PI)
#DEG2RADMULT = 0.0174532925199432958 ;(#PI/180)
#CRLF = Chr(13) + Chr(10)
#MAX_GRIP = 2.0
; Gadgets 3D
Enumeration
#Window3D
#Chrono
#Speed
EndEnumeration
; Camera views
Enumeration
#CAMERA_ABOVE
#CAMERA_FOLLOW
#CAMERA_LEFT
#CAMERA_RIGHT
#CAMERA_INSIDE
#CAMERA_FRONT
#CAMERA_SPOT
EndEnumeration
; Car drive: only Rear Wheel Drive is used in this version
Enumeration
#DRIVE_4WD
#DRIVE_FWD
#DRIVE_RWD
EndEnumeration
; Special Zones types: only Boost is used
Enumeration
#BOOST
#SLOW
#ICE
#INVERTCONTROLS
EndEnumeration
;- Structures and global definitions
Structure Vertex
px.f
py.f
pz.f
nx.f
ny.f
nz.f
Couleur.l
U.f
V.f
EndStructure
Structure Polygon
numVert1.w
numVert2.w
numVert3.w
EndStructure
Structure vector3
X.f
Y.f
Z.f
EndStructure
Structure rectangle_struct
x1.f
y1.f
x2.f
y2.f
x3.f
y3.f
x4.f
y4.f
EndStructure
Structure specialZone_struct
rootNode.i
position.vector3
angle.vector3
width.f
length.f
zonetype.i
rectangle.rectangle_struct
EndStructure
Structure checkpoint_struct
rootNode.i
bannerEntity.i
materialActive.i
materialInactive.i
width.f
height.f
position.vector3
angle.f
xMast1.f
zMast1.f
xMast2.f
zMast2.f
EndStructure
Structure raceRecord_struct
time.f
position.vector3
angle.vector3
roll.f
tilt.f
steerangle.f
EndStructure
Structure carType_struct
name.s
width.f
length.f
height.f
b.f ; in m, distance from center of gravity to front axle
c.f ; in m, idem to rear axle
h.f ; in m, height of CM from ground
wheelBase.f ; wheelbase (distance between front and rear axles) in m
wheelRadius.f ; radius of wheels
wheelWidth.f ; width of wheels
drive.b ; 4WD, FW Drive, RW Drive
maxThrottle.i ; maximum engine power
maxBrake.i ; maximum brake power
engineBrake.i ; engine brake (when you're not accelerating nor braking)
aeroDrag.f ; wind resistance which slows the car down proprtionally To forward velocity
rollingResist.f ; rolling resistance (slows the car down when free rolling)
frontgrip.f ; cornering stiffness front = front slippyness of car .lower the number the less traction there is .
reargrip.f ; cornering stiffness rear = rear slippyness of car . lower the number the less traction there is .
mass.f ; in kg, effects the sluggishness of the handling
inertia.f ; in kg.m, how much momentum is generated by the car . Usual to set this equal To mass .
suspensionHardness.f ; affects how much the car rolls and tilt on its wheels due to lateral/longitudinal forces
maxRoll.f ; maximum roll, in rads
maxTilt.f ; maximum tilt, in rads
EndStructure
Structure car_struct
rootNode.i
bodyNode.i
frontLeftWheelNode.i
frontRightWheelNode.i
rearLeftWheelNode.i
rearRightWheelNode.i
bodyEntity.i
*ptrCarType.carType_struct
position_wc.vector3 ; position of car centre in world coordinates
velocity_wc.vector3 ; velocity vector of car in world coordinates
angle.vector3 ; angle of car orientation (in rads)
angularVelocity.f ; angular velocity of car
steerAngle.f ; angle of steering (input)
throttle.f ; amount of throttle (input)
brake.f ; amount of braking (input)
roll.f ; left/right angle of the car on its suspension, due to lateral forces
tilt.f ; front/rear angle of the car on its suspension, due to longitudinal forces
speedometer.f
frontWheelRoll.f
rearWheelRoll.f
terrainGripFront.f ; grip for front wheels, affected by the terrain currently under the front wheels
terrainGripRear.f ; grip for rear wheels, affected by the terrain currently under the rear wheels
currentCheckpoint.i ; current target checkpoint
EndStructure
Global Dim cartype.carType_struct(#NBMAXCARTYPES)
Global Dim car.car_struct(#NBMAXCARS)
; Checkpoints management
Global Dim checkpoint.checkpoint_struct(#NBMAXCHECKPOINT)
Global currentCheckpoint.i
; Special zones (ex: boost, slow, swap left/right controls, etc.)
Global NewList zone.specialZone_struct()
; This list stores the current race, to make it the new ghost if it's better than the previous
Global NewList thisRace.raceRecord_struct()
; Infos about the ghost racer: its next position, its previous position,
; and its current position (which is an interpolation of the former two)
Global NewList ghostNextPos.raceRecord_struct()
Global *ghostPrevPos.raceRecord_struct,ghostCurrentPos.raceRecord_struct
Global ghostInterpolation.f,ghostTimer.f
Global ghostFinishTime.i
Global ghostExist.b = #False
Global cameraMode.b = #CAMERA_FOLLOW
Global cameraCarToFollow.i = 1
Global started.b = #False, finished.b = #False, stopped.b = #False
; Forced-timing variables
Global Dim timer.f(10)
Global currentTimer.f,oldTimer.f,numTimer.i,speedFactor.f
Global hiDefTimer.LARGE_INTEGER,hiDefTimerFreq.LARGE_INTEGER
; Chrono
Global chrono.f, chronoFreqHD.f
Global chronoFinish.i
Global minutes.i,seconds.i,millisec.i,chronoText.s
Global numMesh.i,terrain.i
;- ------ Macros ------
;************************************************************************************
; Les deux macros ci-dessous sont de Comtois. Merci Comtois !
Macro NEWXVALUE(x, Angle, Distance)
((x) + Cos((Angle) * #DEG2RADMULT) * (Distance))
EndMacro
Macro NEWZVALUE(z, Angle, Distance)
((z) - Sin((Angle) * #DEG2RADMULT) * (Distance))
EndMacro
Macro POW2(x)
((x)*(x))
EndMacro
; Put "Max(value1,value2)" in var "result"
Macro MAXIMUM(result,value1,value2)
If value1>value2
result=value1
Else
result=value2
EndIf
EndMacro
; Put "Min(value1,value2)" in "result"
Macro MINIMUM(result,value1,value2)
If value1<value2
result=value1
Else
result=value2
EndIf
EndMacro
; From a time in milliseconds, constructs a string looking like "01:23:456" (minutes:seconds:millisecs)
; and puts it in the "chronotext" global var
Macro CHRONO2TEXT(time)
minutes = Round( (time) /60000,#PB_Round_Down)
seconds = Round(( (time) - minutes*60000)/1000,#PB_Round_Down)
millisec = Int(time) % 1000
chronoText = Str(minutes)+":"+RSet(Str(seconds),2,"0")+":"+Str(millisec)
EndMacro
;************************************************************************************
;- ------ Procedures ------
EnableExplicit
;- -- Meshes and entities --
;************************************************************************************
; Name: myCreateTerrain
; Purpose: Create a terrain mesh
; Parameters:
; - name of the heightmap
; - terrain size X
; - terrain size Y
; - maximum height (= height of the white points in the heightmap)
; - numbers of cols in the mesh
; - numbers of rows in the mesh
; Return-Value: number of the car's root node
;************************************************************************************
Procedure.i MyCreateTerrain(heightmap.s,sizeX.f,sizeZ.f,maxHeight.f,nbCols.i,nbRows.i)
Protected numImage.i,imSizeX.f,imSizeY.f, height.i
Protected numMesh.i,i.i,j.i
Protected v0.i,v1.i,v2.i,v3.i
Protected cellSizeX.f, cellSizeZ.f,scaleY.f
; Load the heightmap
numImage = LoadImage(#PB_Any,heightmap)
If IsImage(numImage) = #False
ProcedureReturn 0
EndIf
imSizeX = ImageWidth(numImage) - 1
imSizeY = ImageHeight(numImage) - 1
; Create the mesh
numMesh = CreateMesh(#PB_Any)
cellSizeX = sizeX / nbCols
cellSizeZ = sizeZ / nbRows
scaleY = maxHeight / 255.0
; Create the vertices, according to heightmap values
StartDrawing(ImageOutput(numImage))
For j = 0 To nbCols
For i = 0 To nbRows
height = Red( Point(i * (imSizeX/nbCols) , j * (imSizeY/nbRows) ) )
AddMeshVertex(i*cellSizeX,scaleY * height,j*cellSizeZ)
MeshVertexColor($FFFFFF)
MeshVertexTextureCoordinate( i * (1 / nbCols),j * (1 / nbRows) )
Next i
Next j
StopDrawing()
FreeImage(numImage)
; Create the faces
For j=0 To nbRows - 1
For i = 0 To nbCols - 1
v0 = i + j*(nbRows + 1)
v1 = i + j*(nbRows + 1) + 1
v2 = i + (j+1)*(nbRows + 1)
v3 = i + (j+1)*(nbRows + 1) + 1
AddMeshFace(v0, v2, v3 )
AddMeshFace(v0, v3, v1 )
Next i
Next j
; Finih and compute the normals
FinishMesh()
NormalizeMesh(numMesh)
ProcedureReturn numMesh
EndProcedure
;************************************************************************************
; Name: makeCarObject
; Purpose: Create a car
; Parameters:
; - number of the car in the "car" array
; - ghosted or not
; Return-Value: number of the car's root node
;************************************************************************************
Procedure.i makeCarObject(numcar.i,isGhost.b = #False)
Protected carbody.i
Protected carwheel1.i,carwheel2.i,carwheel3.i,carwheel4.i
Protected wheel1Entity.i,wheel2Entity.i,wheel3Entity.i,wheel4Entity.i
Protected numMaterialBody.i = 1,numMaterialWheel.i = 7
; Ghost => transparent material
If isGhost = #True
numMaterialBody = 2
numMaterialWheel = 2
EndIf
; Create/Load meshes
carbody = LoadMesh (#PB_Any, "corvette.mesh")
carwheel1 = LoadMesh(#PB_Any,"wheel.mesh")
carwheel2 = carwheel1:carwheel3 = carwheel1:carwheel4 = carwheel1
; Create entities
car(numcar)\bodyEntity = CreateEntity(#PB_Any,MeshID(carbody),MaterialID(numMaterialBody))
; Specific to the sportcar mesh used
ScaleEntity(car(numcar)\bodyEntity,0.1,0.1,0.1)
RotateEntity(car(numcar)\bodyEntity,180,270,90)
EntityLocate(car(numcar)\bodyEntity,0,-1.15,-0.3)
; Wheels
wheel1Entity = CreateEntity(#PB_Any,MeshID(carwheel1),MaterialID(numMaterialWheel))
wheel2Entity = CreateEntity(#PB_Any,MeshID(carwheel2),MaterialID(numMaterialWheel))
wheel3Entity = CreateEntity(#PB_Any,MeshID(carwheel3),MaterialID(numMaterialWheel))
wheel4Entity = CreateEntity(#PB_Any,MeshID(carwheel4),MaterialID(numMaterialWheel))
; Shadow (not for ghost cars)
If isGhost = #True
EntityRenderMode(car(numcar)\bodyEntity, #PB_Entity_CastShadow)
EntityRenderMode(wheel1Entity, 0)
EntityRenderMode(wheel2Entity, 0)
EntityRenderMode(wheel3Entity, 0)
EntityRenderMode(wheel4Entity, 0)
EndIf
; Configure nodes
; ParentNode (not visible)
car(numCar)\rootNode = CreateNode(#PB_Any)
; Body node
car(numCar)\bodyNode = CreateNode(#PB_Any)
AttachNodeObject(car(numCar)\bodyNode,EntityID(car(numcar)\bodyEntity),#PB_Node_Entity)
; Child nodes (wheels)
car(numCar)\frontLeftWheelNode = CreateNode(#PB_Any)
AttachNodeObject(car(numCar)\frontLeftWheelNode,EntityID(wheel1Entity),#PB_Node_Entity)
NodeLocate(car(numCar)\frontLeftWheelNode,car(numCar)\ptrCarType\width / -2,car(numCar)\ptrCarType\height/-2,car(numCar)\ptrCarType\b)
car(numCar)\frontRightWheelNode = CreateNode(#PB_Any)
AttachNodeObject(car(numCar)\frontRightWheelNode,EntityID(wheel2Entity),#PB_Node_Entity)
NodeLocate(car(numCar)\frontRightWheelNode,car(numCar)\ptrCarType\width / 2,car(numCar)\ptrCarType\height/-2,car(numCar)\ptrCarType\b)
car(numCar)\rearLeftWheelNode = CreateNode(#PB_Any)
AttachNodeObject(car(numCar)\rearLeftWheelNode,EntityID(wheel3Entity),#PB_Node_Entity)
NodeLocate(car(numCar)\rearLeftWheelNode,car(numCar)\ptrCarType\width / -2,car(numCar)\ptrCarType\height/-2,-car(numCar)\ptrCarType\c)
car(numCar)\rearRightWheelNode = CreateNode(#PB_Any)
AttachNodeObject(car(numCar)\rearRightWheelNode,EntityID(wheel4Entity),#PB_Node_Entity)
NodeLocate(car(numCar)\rearRightWheelNode,car(numCar)\ptrCarType\width / 2,car(numCar)\ptrCarType\height/-2,-car(numCar)\ptrCarType\c)
; Attach nodes together
AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\bodyNode),#PB_Node_Node)
AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\frontLeftWheelNode),#PB_Node_Node)
AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\frontRightWheelNode),#PB_Node_Node)
AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\rearLeftWheelNode),#PB_Node_Node)
AttachNodeObject(car(numCar)\rootNode,NodeID(car(numCar)\rearRightWheelNode),#PB_Node_Node)
; Place Car
;car(numCar)\position_wc\y = TerrainHeight(car(numCar)\position_wc\x,car(numCar)\position_wc\z) + car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
car(numCar)\position_wc\y = car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
NodeLocate(car(numCar)\rootNode,car(numCar)\position_wc\x,car(numCar)\position_wc\y,car(numCar)\position_wc\z)
ProcedureReturn car(numCar)\rootNode
EndProcedure
;************************************************************************************
; Name: makeCheckpointObject
; Purpose: create a checkpoint
; Parameters:
; - number of the checkpoint in the "checkpoint" array
; - Material
; Return-Value: number of the checkpoint's root node
;************************************************************************************
Procedure.i makeCheckpointObject(numcheckpoint.i,numMaterial.i = 0)
Protected mastMesh.i,bannerMesh.i
Protected leftMast.i,rightMast.i,banner.i
Protected rootNode.i, bannerNode.i,leftMastNode.i,rightMastNode.i
Protected height.f = 8, bannerHeight.f = 2
; Create/load meshes
mastMesh = CreateCylinder(#PB_Any,0.2,height)
;bannerMesh = createBoxMesh(1,checkpoint(numcheckpoint)\width,bannerHeight,0.1)
bannerMesh = CreateCube(#PB_Any,1)
; Create entities
checkpoint(numcheckpoint)\bannerEntity = CreateEntity(#PB_Any,MeshID(bannerMesh),MaterialID(numMaterial))
ScaleEntity(checkpoint(numcheckpoint)\bannerEntity,checkpoint(numcheckpoint)\width,bannerHeight,0.1)
leftMast = CreateEntity(#PB_Any,MeshID(mastMesh),MaterialID(0))
rightMast = CreateEntity(#PB_Any,MeshID(mastMesh),MaterialID(0))
EntityRenderMode(checkpoint(numcheckpoint)\bannerEntity, #PB_Entity_CastShadow)
EntityRenderMode(leftMast, #PB_Entity_CastShadow)
EntityRenderMode(rightMast, #PB_Entity_CastShadow)
; Create Nodes
; root node (not visible)
rootNode = CreateNode(#PB_Any)
; Child nodes (masts and banner)
bannerNode = CreateNode(#PB_Any)
AttachNodeObject(bannerNode,EntityID(checkpoint(numcheckpoint)\bannerEntity),#PB_Node_Entity)
NodeLocate(bannerNode,0,height - (bannerHeight/2),0)
leftMastNode = CreateNode(#PB_Any)
AttachNodeObject(leftMastNode,EntityID(leftMast),#PB_Node_Entity)
NodeLocate(leftMastNode,(checkpoint(numcheckpoint)\width / -2)-0.1,height/2,0)
rightMastNode = CreateNode(#PB_Any)
AttachNodeObject(rightMastNode,EntityID(rightMast),#PB_Node_Entity)
NodeLocate(rightMastNode,(checkpoint(numcheckpoint)\width / 2)+0.1,height/2,0)
; Attach nodes together
AttachNodeObject(rootNode,NodeID(bannerNode),#PB_Node_Node)
AttachNodeObject(rootNode,NodeID(leftMastNode),#PB_Node_Node)
AttachNodeObject(rootNode,NodeID(rightMastNode),#PB_Node_Node)
ProcedureReturn rootNode
EndProcedure
;************************************************************************************
; Name: MakeSpecialZoneObject
; Purpose: create a "special zone" on the track
; (only one kind is supported yet: booster)
; Parameters:
; - pointer to the special zone in the "zone()" list
; Return-Value: number of the zone's entity (an horizontal plane)
;************************************************************************************
Procedure.i MakeSpecialZoneObject(*ptrZone.specialZone_struct)
Protected nummesh.i, numentity.i, nummaterial.i
Select *ptrZone\zoneType
Case #BOOST
numMaterial = 6
EndSelect
;nummesh = CreateMesh(#PB_Any,100)
;SetMeshData(nummesh, #PB_Mesh_Vertex | #PB_Mesh_Normal | #PB_Mesh_UVCoordinate, ?PlanVertices, 4)
;SetMeshData(nummesh, #PB_Mesh_Face, ?PlanFaces, 2)
nummesh = CreatePlane(#PB_Any,1,1,1,1,1,1)
numEntity = CreateEntity(#PB_Any,MeshID(nummesh),MaterialID(numMaterial),*ptrZone\position\x,*ptrZone\position\y+0.1,*ptrZone\position\z)
ScaleEntity(numEntity,*ptrZone\width,0,*ptrZone\length)
RotateEntity(numEntity,0,*ptrZone\angle\y,0)
ProcedureReturn numEntity
EndProcedure
;- -- Trigo and utility procs --
Macro myAtan2(y,x)
ATan2(x,y)
EndMacro
; La procédure ci-dessous est de Comtois. Merci Comtois !
; Calcule une valeur progressive allant de la valeur actuelle à la valeur cible
Procedure.f curveValue(actuelle.f, Cible.f, P.f)
If P > 1000.0
P = 1000.0
EndIf
ProcedureReturn (actuelle + ( (Cible - actuelle) * P / 1000.0))
EndProcedure
; La procédure ci-dessous est de cpl_Bator. Merci cpl_Bator !
; Pareil qu'au-dessus, mais pour un angle
Procedure.f curveAngle(oldangle.f,newangle.f, increments.f)
If (oldangle+360)-newangle<newangle-oldangle
oldangle=360+oldangle
EndIf
If (newangle+360)-oldangle<oldangle-newangle
newangle=360+newangle
EndIf
oldangle=oldangle-(oldangle-newangle)/increments
ProcedureReturn oldangle
EndProcedure
;************************************************************************************
; Name: ComputeSegmentIntersection
; Purpose: Check if 2 segments intersect => used to know if the car crosses
; a checkpoint line.
; Parameters:
; - [x,y] -> [x,y] of the first segment
; - [x,y] -> [x,y] of the second segment
; Return-Value: #True if the 2 segments intersect
;************************************************************************************
Procedure.b ComputeSegmentIntersection(x1.f,y1.f,x2.f,y2.f,x3.f,y3.f,x4.f,y4.f)
Protected d.f
Protected xi.f,yi.f ; coordonnées du point d'intersection
Protected xmin.f,xmax.f
d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
If d=0
ProcedureReturn #False
EndIf
; Computes the point of intersection between the two lines
xi = ((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d
yi = ((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d
; Is this point inside the segments' limits ?
; segment 1
If x1 > x2
xmax = x1
xmin = x2
Else
xmax = x2
xmin = x1
EndIf
If xi < xmin Or xi > xmax
ProcedureReturn #False
EndIf
; Segment 2
If x3 > x4
xmax = x3
xmin = x4
Else
xmax = x4
xmin = x3
EndIf
If xi < xmin Or xi > xmax
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndProcedure
; Les 2 procédures ci-dessous sont de Typhoon. Merci Typhoon !
Procedure Signe(a.i)
If a>0
ProcedureReturn 1
ElseIf a<0
ProcedureReturn -1
Else
ProcedureReturn 0
EndIf
EndProcedure
Procedure.b ComputeRectangleCollision(x.f,y.f,*ptrRect.rectangle_struct)
Protected xu.f,yu.f,c.f,P.f,AX.f
;Plan 1
xu=*ptrRect\X2-*ptrRect\X1
yu=*ptrRect\Y2-*ptrRect\Y1
c=*ptrRect\Y1*xu-*ptrRect\X1*yu
P=*ptrRect\X3*yu-*ptrRect\Y3*xu+c
AX=x*yu-y*xu+c
If Signe(AX) <> Signe(P)
ProcedureReturn #False
EndIf
;Plan 2
xu=*ptrRect\X3-*ptrRect\X2
yu=*ptrRect\Y3-*ptrRect\Y2
c=*ptrRect\Y2*xu-*ptrRect\X2*yu
P=*ptrRect\X1*yu-*ptrRect\Y1*xu+c
AX=x*yu-y*xu+c
If Signe(AX) <> Signe(P)
ProcedureReturn #False
EndIf
;Plan 3
xu=*ptrRect\X4-*ptrRect\X3
yu=*ptrRect\Y4-*ptrRect\Y3
c=*ptrRect\Y3*xu-*ptrRect\X3*yu
P=*ptrRect\X1*yu-*ptrRect\Y1*xu+c
AX=x*yu-y*xu+c
If Signe(AX) <> Signe(P)
ProcedureReturn #False
EndIf
;Plan 4
xu=*ptrRect\X1-*ptrRect\X4
yu=*ptrRect\Y1-*ptrRect\Y4
c=*ptrRect\Y4*xu-*ptrRect\X4*yu
P=*ptrRect\X3*yu-*ptrRect\Y3*xu+c
AX=x*yu-y*xu+c
If Signe(AX) <> Signe(P)
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndProcedure
;- -- Car Physics --
;************************************************************************************
; Name: PlaceCarOnGround
; Purpose: Computes a rough approximation of car height and X,Z angles to make it
; "follow" the terrain's slope
; Parameters:
; - number of the car in the "car" array
; Return-value: none
;************************************************************************************
CompilerIf #CARFOLLOWTERRAIN =#True
Procedure placeCarOnGround(numCar.i)
Static FrontWheelDist.f,RearWheelDist.f ; static => computed only once
Protected angleToWheel.f
Protected frontleftx.f,frontleftz.f,frontlefth.f
Protected frontrightx.f,frontrightz.f,frontrighth.f
Protected backleftx.f,backleftz.f,backlefth.f
Protected backrightx.f,backrightz.f,backrighth.f
Protected across.f,length.f,carHeight.f
; Distance between car center and front/rear wheels
If FrontWheelDist = 0 And RearWheelDist = 0
FrontWheelDist = Sqr( NodeX(car(numCar)\frontLeftWheelNode)*NodeX(car(numCar)\frontLeftWheelNode) + NodeZ(car(numCar)\frontLeftWheelNode)*NodeZ(car(numCar)\frontLeftWheelNode) )
RearWheelDist = Sqr( NodeX(car(numCar)\rearLeftWheelNode)*NodeX(car(numCar)\rearLeftWheelNode) + NodeZ(car(numCar)\rearLeftWheelNode)*NodeZ(car(numCar)\rearLeftWheelNode) )
EndIf
; Coords X,Z of each wheel
angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 315
frontleftx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,FrontWheelDist)
frontleftz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,FrontWheelDist)
angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 225
frontrightx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,FrontWheelDist)
frontrightz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,FrontWheelDist)
angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 45
backleftx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,RearWheelDist)
backleftz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,RearWheelDist)
angleToWheel=car(numCar)\angle\Y * (#RAD2DEGMULT) + 135
backrightx=NEWXVALUE(car(numCar)\position_wc\X,angleToWheel,RearWheelDist)
backrightz=NEWZVALUE(car(numCar)\position_wc\Z,angleToWheel,RearWheelDist)
; Height of each wheel
frontlefth=TerrainHeight(frontleftx,frontleftz)
frontrighth=TerrainHeight(frontrightx,frontrightz)
backlefth=TerrainHeight(backleftx,backleftz)
backrighth=TerrainHeight(backrightx,backrightz)
; X,Z inclination
length=((frontlefth-frontrighth)+(backlefth-backrighth))/2.0
across=((backlefth-frontlefth)+(backrighth-frontrighth))/2.0
; Update car height / X & Z angles
carHeight=(frontlefth+frontrighth+backlefth+backrighth)/4.0
car(numCar)\position_wc\Y = carHeight + car(numCar)\ptrCarType\wheelRadius + car(numCar)\ptrCarType\height/2
car(numCar)\angle\X = across/4.0
car(numCar)\angle\Z = length/4.0
; This procedure could be a good place to check what sort of terrain you've got under your wheels.
; But... well, it isn't not done yet.
car(numCar)\terraingripFront = 1 ; 1 = normal grip = road
car(numCar)\terraingripRear = 1 ; 1 = normal grip = road
EndProcedure
CompilerEndIf
;************************************************************************************
; Name: simulateCarPhysics
; Purpose: A very simple physic model of car physics (good for arcade games!)
; Heavily based on Marco Monster's famous "Car Physics" tutorial.
; Thanks a lot, Marco!
; Parameters:
; - number of the car in the "car" array
; - grip factor for the front wheels
; - grip factor for the rear wheels
; - time (in seconds) since the last call
; Return-value: none
;************************************************************************************
Procedure simulateCarPhysics(numCar.i,delta_t.f)
Protected velocity.vector3, acceleration_wc.vector3, resistance.vector3, force.vector3, ftraction.vector3
Protected sn.d, cs.f
Protected rot_angle.f, yawspeed.f
Protected sideslip.f,slipanglefront.f,slipanglerear.f
Protected torque.f
Protected angular_acceleration.f
Protected weightOnFrontAxle.f,weightOnRearAxle.f
Protected flatf.vector3, flatr.vector3
Protected roll.f,tilt.f, wheelRollDistRatio.f
Protected sgn.b
CompilerIf #REALISTICWEIGHTTRANSFER = #True
Static acceleration.vector3
CompilerElse
Protected acceleration.vector3
CompilerEndIf
CompilerIf #CARFOLLOWTERRAIN = #True
; This procedure updates the X/Z angles of the car => from these, you could compute effects on speed, sideslip, etc..
; Note: this procedure also computes the amount of grip for front/rear wheels, based on the terrain type currently under the wheels
placeCarOnGround(numCar)
CompilerEndIf
sn = Sin(car(numCar)\angle\Y)
cs = Cos(car(numCar)\angle\Y)
; SAE convention: x is to the front of the car, y is to the right, z is down
; transform velocity in world reference frame to velocity in car reference frame
velocity\X = cs * car(numCar)\velocity_wc\Y + sn * car(numCar)\velocity_wc\X
velocity\Y = -sn * car(numCar)\velocity_wc\Y + cs * car(numCar)\velocity_wc\X
car(numCar)\speedometer = velocity\X * 3.6 ; velocity is in units of meters/second => v * 3600 /1000 = km/h
; Lateral force on wheels
; Resulting velocity of the wheels As result of the yaw rate of the car body
; v = yawrate * r where r is distance of wheel To CG => cartype\b
; yawrate (ang.velocity) must be in rad/s
yawspeed = car(numCar)\ptrCarType\b * car(numCar)\angularvelocity
; Calculate the side slip angle of the car (a.k.a. beta)
If velocity\x = 0
rot_angle = 0
sideslip = 0
Else
rot_angle = myAtan2( yawspeed, velocity\x)
sideslip = myAtan2( velocity\y, velocity\x)
EndIf
; Calculate slip angles For front And rear wheels (a.k.a. alpha)
slipanglefront = sideslip + rot_angle - car(numCar)\steerangle
slipanglerear = sideslip - rot_angle
; Ugly fix to avoid physics inconsistency at low speed
If velocity\x < 0.01 And velocity\y < 0.01 And velocity\x > -0.01 And velocity\y > -0.01
car(numCar)\velocity_wc\X = 0
car(numCar)\velocity_wc\Z = 0
car(numCar)\angularvelocity = 0
car(numCar)\speedometer = 0
car(numCar)\brake = 0
velocity\x = 0
velocity\y = 0
acceleration\x = 0
acceleration\y = 0
yawspeed = 0
rot_angle = 0
sideslip = 0
slipanglefront = 0
slipanglerear = 0
EndIf
; Weight repartition on each axle
CompilerIf #REALISTICWEIGHTTRANSFER = #True
; This is a more realistic version, with weight transfer according to acceleration => far more difficult to handle
; To activate it, set the #REALISTICWEIGHTTRANSFER constant to #True
weightOnFrontAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\c / car(numCar)\ptrCarType\wheelbase) - (car(numCar)\ptrCarType\h / car(numCar)\ptrCarType\wheelbase)*car(numCar)\ptrCarType\mass*acceleration\x
weightOnRearAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\b / car(numCar)\ptrCarType\wheelbase) + (car(numCar)\ptrCarType\h / car(numCar)\ptrCarType\wheelbase)*car(numCar)\ptrCarType\mass*acceleration\x
CompilerElse
; weight per axle = car mass x 1G (=9.8m/s^2) * distance axle->CG / wheelbase
weightOnFrontAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\c / car(numCar)\ptrCarType\wheelbase)
weightOnRearAxle = (car(numCar)\ptrCarType\mass * 9.8) * (car(numCar)\ptrCarType\b / car(numCar)\ptrCarType\wheelbase)
CompilerEndIf
flatf\x = 0
flatr\x = 0
flatf\y = car(numCar)\ptrCarType\frontGrip * slipanglefront
flatr\y = car(numCar)\ptrCarType\rearGrip * slipanglerear
; lateral force on front wheels = (Ca * slip angle) capped To friction circle * load
If #MAX_GRIP < flatf\y
flatf\y = #MAX_GRIP
EndIf
If -#MAX_GRIP > flatf\y
flatf\y = -#MAX_GRIP
EndIf
flatf\y * weightOnFrontAxle
; lateral force on rear wheels
If #MAX_GRIP < flatr\y
flatr\y = #MAX_GRIP
EndIf
If -#MAX_GRIP > flatr\y
flatr\y = -#MAX_GRIP
EndIf
flatr\y * weightOnRearAxle
; longtitudinal force on rear wheels - very simple traction model
If velocity\x<0
sgn=-1
Else
sgn=1
EndIf
ftraction\x = car(numcar)\ptrCarType\maxThrottle *(car(numCar)\throttle - car(numCar)\brake * sgn)
ftraction\y = 0
; Influence of terrain on wheel grip
flatr\y * car(numcar)\terrainGripRear
flatf\y * car(numcar)\terrainGripRear
flatf\x * car(numcar)\terrainGripFront
flatr\x * car(numcar)\terrainGripFront
; Forces And torque on body
; drag And rolling resistance
resistance\x = -( car(numCar)\ptrCarType\rollingResist * velocity\x + car(numCar)\ptrCarType\aeroDrag * velocity\x * Abs(velocity\x) )
resistance\y = -( car(numCar)\ptrCarType\rollingResist * velocity\y + car(numCar)\ptrCarType\aeroDrag * velocity\y * Abs(velocity\y) )
; sum forces
force\x = ftraction\x + Sin(car(numCar)\steerangle) * flatf\x + flatr\x + resistance\x
force\y = ftraction\y + Cos(car(numCar)\steerangle) * flatf\y + flatr\y + resistance\y
; torque on body from lateral forces
torque = car(numCar)\ptrCarType\b * flatf\y - car(numCar)\ptrCarType\c * flatr\y
; Acceleration
; Newton F = m.a, therefore a = F/m
acceleration\x = force\x / car(numCar)\ptrCarType\mass
acceleration\y = force\y / car(numCar)\ptrCarType\mass
angular_acceleration = torque / car(numCar)\ptrCarType\inertia
; Velocity And position
; transform acceleration from car reference frame to world reference frame
acceleration_wc\x = cs * acceleration\y + sn * acceleration\x
acceleration_wc\y = -sn * acceleration\y + cs * acceleration\x
; velocity is integrated acceleration
car(numCar)\velocity_wc\x + delta_t * acceleration_wc\x
car(numCar)\velocity_wc\y + delta_t * acceleration_wc\y
; position is integrated velocity
car(numCar)\position_wc\x + delta_t * car(numCar)\velocity_wc\x
car(numCar)\position_wc\z + delta_t * car(numCar)\velocity_wc\y
; Angular velocity And heading
; integrate angular acceleration To get angular velocity
car(numCar)\angularvelocity + delta_t * angular_acceleration
; integrate angular velocity To get angular orientation
car(numCar)\angle\Y + delta_t * car(numCar)\angularvelocity
; Car roll and tilt target values
roll = force\y / car(numCar)\ptrCarType\suspensionHardness
If roll > 0
MINIMUM(roll,roll,car(numCar)\ptrCarType\maxRoll)
Else
MAXIMUM(roll,roll,-car(numCar)\ptrCarType\maxRoll)
EndIf
tilt = force\x / car(numCar)\ptrCarType\suspensionHardness
If tilt> 0
MINIMUM(tilt,tilt,car(numCar)\ptrCarType\maxTilt)
Else
MAXIMUM(tilt,tilt,-car(numCar)\ptrCarType\maxTilt)
EndIf
; Compute car actual roll/tilt
If car(numCar)\roll < roll
car(numCar)\roll + delta_t
If car(numCar)\roll > roll
car(numCar)\roll = roll
EndIf
ElseIf car(numCar)\roll > roll
car(numCar)\roll - delta_t
If car(numCar)\roll < roll
car(numCar)\roll = roll
EndIf
EndIf
If car(numCar)\tilt < tilt
car(numCar)\tilt + delta_t
If car(numCar)\tilt > tilt
car(numCar)\tilt = tilt
EndIf
ElseIf car(numCar)\tilt > tilt
car(numCar)\tilt - delta_t
If car(numCar)\tilt < tilt
car(numCar)\tilt = tilt
EndIf
EndIf
; wheels rotation
; Conversion distance => wheel rotation
wheelRollDistRatio = delta_t * #RAD2DEGMULT * 2 * car(numcar)\ptrCartype\wheelradius
; Front wheels: velocity X according to steering angle
car(numCar)\frontWheelRoll + (wheelRollDistRatio * (Cos(car(numCar)\angle\Y + car(numCar)\steerangle) * car(numCar)\velocity_wc\Y + Sin(car(numCar)\angle\Y + car(numCar)\steerangle) * car(numCar)\velocity_wc\X) )
; Rear wheels: velocity X, except when braking is maximum (rear wheels are blocked)
If car(numcar)\brake < car(numcar)\ptrCarType\maxbrake
car(numCar)\rearWheelRoll + (velocity\X * wheelRollDistRatio)
EndIf
EndProcedure
;- -- Manage checkpoints and finish line
;************************************************************************************
; Name: activateNextCheckpoint
; Purpose: Activate the next checkpoint in the "chekpoint" array for the current car
; and change the texture of its banner to show it
; Parameters:
; - number of the current car
; Return-Value: True if there's no next checkpoint (race is over!)
;************************************************************************************
Procedure.b activateNextCheckpoint(numcar.i)
SetEntityMaterial(checkpoint(car(numcar)\currentCheckpoint)\bannerEntity,MaterialID(checkpoint(car(numcar)\currentCheckpoint)\materialInactive))
car(numcar)\currentCheckpoint + 1
If checkpoint(car(numcar)\currentCheckpoint)\angle = 999 ; No next checkpoint? => Finish!
ProcedureReturn #True
Else
SetEntityMaterial(checkpoint(car(numcar)\currentCheckpoint)\bannerEntity,MaterialID(checkpoint(car(numcar)\currentCheckpoint)\materialActive))
EndIf
ProcedureReturn #False
EndProcedure
;************************************************************************************
; Name: checkCurrentCheckpoint
; Purpose: checks if the car crosses its target checkpoint
; Parameters:
; - number of the current car
; Return-Value: True if the last checkpoint has been crossed (race is over!)
;************************************************************************************
Procedure.b checkCurrentCheckpoint(numcar.i)
Protected x1.f,y1.f,x2.f,y2.f
; First segment: Front -> back
x1 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\length/2 * Cos(-car(numcar)\angle\Y+#PI/2))
y1 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\length/2 * Sin(-car(numcar)\angle\Y+#PI/2))
x2 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\length/-2 * Cos(-car(numcar)\angle\Y+#PI/2))
y2 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\length/-2 * Sin(-car(numcar)\angle\Y+#PI/2))
; If this segment crosses the checkpoint line, activate the next checkpoint
If computeSegmentIntersection(x1,y1,x2,y2,checkpoint(car(numcar)\currentCheckpoint)\xMast1,checkpoint(car(numcar)\currentCheckpoint)\zMast1,checkpoint(car(numcar)\currentCheckpoint)\xMast2,checkpoint(car(numcar)\currentCheckpoint)\zMast2) = #True
ProcedureReturn activateNextCheckpoint(numcar.i)
EndIf
; Second segment: Left -> right
x1 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\width/2 * Cos(-car(numcar)\angle\Y))
y1 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\width/2 * Sin(-car(numcar)\angle\Y))
x2 = car(numcar)\position_wc\X + (car(numcar)\ptrCarType\width/-2 * Cos(-car(numcar)\angle\Y))
y2 = car(numcar)\position_wc\Z + (car(numcar)\ptrCarType\width/-2 * Sin(-car(numcar)\angle\Y))
; If this segment crosses the checkpoint line, activate the next checkpoint
If computeSegmentIntersection(x1,y1,x2,y2,checkpoint(car(numcar)\currentCheckpoint)\xMast1,checkpoint(car(numcar)\currentCheckpoint)\zMast1,checkpoint(car(numcar)\currentCheckpoint)\xMast2,checkpoint(car(numcar)\currentCheckpoint)\zMast2) = #True
ProcedureReturn activateNextCheckpoint(numcar.i)
EndIf
ProcedureReturn #False
EndProcedure
;************************************************************************************
; Name: finishRace
; Purpose: Race is over => stores the current race if it is better than the ghost,
; and display a message to congratulate or taunt the player
; Parameters:
; - race time of the player
; - current best time
; Return-Value: none
;************************************************************************************
Procedure finishRace(finishTime.i, bestTime.i)
Protected diffTimeText.s
; If this is your first race, or if you set the new record, you win: your race becomes the new ghost
If finishTime < bestTime Or bestTime = 0
CreateFile(1,"ghost.rcr")
WriteLong(1,finishTime)
ForEach thisRace()
WriteFloat(1,thisRace()\time)
WriteFloat(1,thisRace()\position\X)
WriteFloat(1,thisRace()\position\Y)
WriteFloat(1,thisRace()\position\Z)
WriteFloat(1,thisRace()\angle\X)
WriteFloat(1,thisRace()\angle\Y)
WriteFloat(1,thisRace()\angle\Z)
WriteFloat(1,thisRace()\roll)
WriteFloat(1,thisRace()\tilt)
WriteFloat(1,thisRace()\steerangle)
Next thisRace()
If bestTime > 0
CHRONO2TEXT(bestTime - finishTime)
diffTimeText = #CRLF+"( -" + chronoText + " )"
EndIf
CHRONO2TEXT(finishtime)
OpenWindow3D(10, 300, 200, 200, 120, "Congratulations !")
TextGadget3D(11, 20, 30, 160, 26, " New Lap Record!")
TextGadget3D(12, 30, 63, 140, 26, " Your time: " + chronoText)
If bestTime > 0
CHRONO2TEXT(bestTime - finishTime)
TextGadget3D(13, 50, 90, 100, 26, " ( - "+ chronoText + " )")
EndIf
Else
; Else... Well, you lose.
CHRONO2TEXT(finishtime)
diffTimeText = #CRLF+"( +"+ chronoText + " )"
OpenWindow3D(10, 300, 200, 200, 120, "Too bad...")
TextGadget3D(11, 20, 30, 160, 26, " Game Over, you slug!")
TextGadget3D(12, 30, 63, 140, 26, " Your time: " + chronoText)
CHRONO2TEXT(finishTime - bestTime)
TextGadget3D(13, 50, 90, 100, 26, " ( + "+ chronoText + " )")
EndIf
EndProcedure
;- -- Manage "special zones"
;************************************************************************************
; Name: manageSpecialZones
; Purpose: checks if the car is in one of the "special zones", and affect the car accordingly
; Parameters:
; - number of the current car
; Return-Value: none
;************************************************************************************
Procedure manageSpecialZones(numcar.i)
Protected distx.f, distz.f
ForEach zone()
If computeRectangleCollision(car(numcar)\position_wc\X,car(numcar)\position_wc\Z,@zone()+OffsetOf(specialzone_struct\rectangle)) = #True
Select zone()\zonetype
Case #BOOST
car(numcar)\throttle = car(numcar)\ptrCarType\maxThrottle*5
Case #SLOW
car(numcar)\brake = car(numcar)\ptrCarType\maxBrake/10
EndSelect
Break
EndIf
Next zone()
EndProcedure