VERSION 1.0 CLASS BEGIN MultiUse = -1 'True Persistable = 0 'NotPersistable DataBindingBehavior = 0 'vbNone DataSourceBehavior = 0 'vbNone MTSTransactionMode = 0 'NotAnMTSObject END Attribute VB_Name = "cPuck" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = True Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Private Const mnMaxSpinSpeed As Single = 0.9 'Here we will encapsulate all of the code needed for the puck 'Local variables for the properties of the puck Private moPosition As D3DVECTOR 'Current position of the puck Private moVelocity As D3DVECTOR 'Current velocity of the puck Private moLastPosition As D3DVECTOR 'Last position of the puck Public Spinning As Boolean 'Is the puck currently spinning? Public MaximumPuckVelocity As Single Private mnSpinDir As Single 'Direction of the pucks spinning Private mlPuckTime As Long 'Last time the puck was updated Private mnPuckSpin As Single Private moPuck As CD3DFrame 'D3D Mesh for the puck 'Default spin speed Private mnDefaultSpin As Single 'Position property Public Property Let Position(oPos As D3DVECTOR) moPosition = oPos End Property Public Property Get Position() As D3DVECTOR Position = moPosition End Property 'Velocity property Public Property Let Velocity(oVel As D3DVECTOR) moVelocity = oVel 'Update the velocity, but make sure it isn't too high EnsurePuckVelocityIsBelowMax End Property Public Property Get Velocity() As D3DVECTOR Velocity = moVelocity End Property 'LastPosition prop Public Property Let LastPosition(oLastPos As D3DVECTOR) moLastPosition = oLastPos End Property Public Property Get LastPosition() As D3DVECTOR LastPosition = moLastPosition End Property 'Different methods from the puck. Public Sub Init(ByVal sMedia As String, sFile As String) Set moPuck = D3DUtil_LoadFromFile(AddDirSep(sMedia) & sFile, Nothing, Nothing) End Sub Public Sub UpdatePosition() Dim RealVelocity As D3DVECTOR 'Here we will update the position of the puck 'and move it based on the velocity assigned. If mlPuckTime = 0 Then mlPuckTime = timeGetTime 'First calculate the 'real' velocity (based on the time) RealVelocity.X = ((timeGetTime - mlPuckTime) / 100) * moVelocity.X RealVelocity.Y = ((timeGetTime - mlPuckTime) / 100) * moVelocity.Y RealVelocity.z = ((timeGetTime - mlPuckTime) / 100) * moVelocity.z 'Let's save our current position moLastPosition = moPosition moPosition.X = moPosition.X + RealVelocity.X moPosition.Y = moPosition.Y + RealVelocity.Y moPosition.z = moPosition.z + RealVelocity.z If Spinning Then 'Update Puck Spin mnPuckSpin = mnPuckSpin + ((((timeGetTime - mlPuckTime) / 100) * mnDefaultSpin) * mnSpinDir) If mnPuckSpin > 2 * g_pi Then mnPuckSpin = 0 End If mlPuckTime = timeGetTime End Sub Public Sub Render(dev As Direct3DDevice8) Dim matRot As D3DMATRIX, matTrans As D3DMATRIX Dim matPuck As D3DMATRIX D3DXMatrixRotationAxis matRot, vec3(0, 1, 0), mnPuckSpin D3DXMatrixTranslation matTrans, moPosition.X, moPosition.Y, moPosition.z D3DXMatrixMultiply matPuck, matRot, matTrans moPuck.SetMatrix matPuck moPuck.Render dev End Sub Public Sub LaunchPuck() Randomize DefaultStartPosition Do While (D3DXVec3Length(moVelocity) < (MaximumPuckVelocity / 4)) And (Abs(moVelocity.z) < 0.2) 'Make sure there is *some* z movement moVelocity.z = Rnd * (MaximumPuckVelocity / 3) moVelocity.X = Rnd * (MaximumPuckVelocity / 3) If Rnd > 0.5 Then moVelocity.X = moVelocity.X * -1 If Rnd < 0.5 Then moVelocity.z = moVelocity.z * -1 Loop End Sub Public Sub DefaultStartPosition() moPosition = vec3(0, 2.5, 0) moVelocity = vec3(0, 0, 0) moLastPosition = vec3(0, 0, 0) End Sub Public Sub ChangePuckVelocity(oPaddle As cPaddle, oAudio As cAudio, Optional ByVal fIgnoreMax As Boolean = False) Dim vDir As D3DVECTOR Dim a As Single, b As Single, c As Single Dim t0 As Single, t1 As Single Dim vIntersect As D3DVECTOR, vIntersectHigh As D3DVECTOR Dim oPlane As D3DPLANE, matReflect As D3DMATRIX Dim oPoint As D3DVECTOR, vNewVelDir As D3DVECTOR Dim vPuck As D3DVECTOR, tSmall As Single Dim nVelocity As Single, nVelocityPaddle As Single Dim vNewVelPad As D3DVECTOR 'We hit with the paddle, randomly change the spin direction UpdatePuckSpin glPaddleCollideTime = timeGetTime 'gfRecentlyHitPaddle = True 'Notify the user that the puck hit the paddle by playing a sound If Not (oAudio Is Nothing) Then oAudio.PlayHitSound 'Let's store the original velocity nVelocity = D3DXVec3Length(moVelocity) nVelocityPaddle = D3DXVec3Length(oPaddle.Velocity) * gnPaddleMass 'First we need to find the intersection point 'To do that we first need to solve for t: 'x = Dxt + x0 'z = Dzt + z0 D3DXVec3Subtract vPuck, moPosition, oPaddle.Position D3DXVec3Normalize vDir, moVelocity a = 1 ' (vDir.x ^ 2) + (vDir.z ^ 2) will always be one since the vector is normalized b = (2 * vPuck.X * vDir.X) + (2 * vPuck.z * vDir.z) c = ((vPuck.X ^ 2) + (vPuck.z ^ 2) - ((gnPaddleRadius + gnPuckRadius) ^ 2)) 't = (-b ± SQR(bČ-4ac))/2a If (b ^ 2) - (4 * a * c) > 0 Then t0 = (-b + Sqr((b ^ 2) - (4 * a * c))) / (2 * a) t1 = (-b - Sqr((b ^ 2) - (4 * a * c))) / (2 * a) Else 'We shouldn't hit this case, but just in case. t0 = 0 t1 = 0 End If Dim vInt1 As D3DVECTOR, vInt2 As D3DVECTOR Dim vDifInt1 As D3DVECTOR, vDifInt2 As D3DVECTOR 'Find both possible intersection points vInt1.X = (vDir.X * t0) + vPuck.X: vInt1.z = (vDir.z * t0) + vPuck.z vInt2.X = (vDir.X * t1) + vPuck.X: vInt2.z = (vDir.z * t1) + vPuck.z 'Find the difference from the starting location D3DXVec3Subtract vDifInt1, oPaddle.Position, vInt1 D3DXVec3Subtract vDifInt2, oPaddle.Position, vInt2 'Find the smallest t 'If t0 > t1 Then If D3DXVec3Length(vDifInt1) < D3DXVec3Length(vDifInt2) Then tSmall = t1 Else tSmall = t0 End If 'Let's get the intersected point vIntersect.X = (vDir.X * tSmall) + vPuck.X vIntersect.z = (vDir.z * tSmall) + vPuck.z 'Create a new vector with an enormously high Y field to create our reflection plane vIntersectHigh = vIntersect vIntersectHigh.Y = 500 'Let's create a plane from this point D3DXPlaneFromPoints oPlane, vec3(0, 0, 0), vIntersect, vIntersectHigh 'Now we can create a reflection matrix based on this plane D3DXMatrixReflect matReflect, oPlane 'Create a new point that is reflected D3DXVec3TransformCoord oPoint, vPuck, matReflect D3DXVec3Subtract vNewVelDir, oPoint, vIntersect 'Normalize the vector D3DXVec3Normalize vNewVelDir, vNewVelDir vNewVelDir.X = -vNewVelDir.X vNewVelDir.z = -vNewVelDir.z D3DXVec3Scale moVelocity, vNewVelDir, nVelocity If nVelocityPaddle > 0 Then 'The paddle is moving, add it's velocity 'Now let's add the velocity of the paddle to our resulting velocity D3DXVec3Normalize vNewVelPad, oPaddle.Velocity D3DXVec3Scale vNewVelPad, vNewVelPad, nVelocityPaddle D3DXVec3Add moVelocity, moVelocity, vNewVelPad End If Debug.Print "Old Velocity:"; nVelocity; " - New Velocity:"; D3DXVec3Length(moVelocity) 'If we are limiting the velocity to it's maximum (most times), do so If Not fIgnoreMax Then EnsurePuckVelocityIsBelowMax End Sub Public Sub CheckCollisions(oPaddle() As cPaddle, Optional oAudio As cAudio = Nothing) 'First we should check to see if we are scoring in this frame. Dim nDistance As Single Dim lCount As Long, fCollided As Boolean Dim lCollided As Long, nCollideDist As Single If gfScored Then Exit Sub 'Check to see if the puck has collided with any of the walls 'We could do an exhaustive check to see if any of the polygons collide, but since the table 'is static, in the name of faster calculations, we will use a group of constants defining the 'edges of the walls. We will check those instead. 'If the puck does hit one of the walls, we can easily calculate it's new direction by simply reversing 'it's velocity (of that vector). If we want to be even more accurate we can lower the velocity by a small amount as well 'The left and right walls are bound to the X axis If moPosition.X > (gnSideLeftWallEdge - (gnPuckRadius)) Then 'We hit the wall 'Reverse the velocity of the X axis moVelocity = vec3((moVelocity.X * -1) * gnVelocityDamp, 0, moVelocity.z) moPosition = vec3((gnSideLeftWallEdge - (gnPuckRadius)), moPosition.Y, moPosition.z) If Not (oAudio Is Nothing) Then oAudio.PlayBankSound gfRecentlyHitPaddle = False ElseIf moPosition.X < (gnSideRightWallEdge + (gnPuckRadius)) Then 'We hit the wall moVelocity = vec3((moVelocity.X * -1) * gnVelocityDamp, 0, moVelocity.z) moPosition = vec3((gnSideRightWallEdge + (gnPuckRadius)), moPosition.Y, moPosition.z) If Not (oAudio Is Nothing) Then oAudio.PlayBankSound gfRecentlyHitPaddle = False End If 'The front and rear walls are count to the Z axis If moPosition.z > (gnNearWallEdge - (gnPuckRadius)) Then 'Only reverse the velocity if we hit the sides of the 'scoring area' If (moPosition.X > (gnScoringEdgeLeft - (gnPuckRadius))) Or (moPosition.X < (gnScoringEdgeRight + (gnPuckRadius))) Then 'We hit the wall 'Reverse the velocity of the Z axis moVelocity = vec3(moVelocity.X, 0, (moVelocity.z * -1) * gnVelocityDamp) moPosition = vec3(moPosition.X, moPosition.Y, gnNearWallEdge - (gnPuckRadius)) If Not (oAudio Is Nothing) Then oAudio.PlayBankSound gfRecentlyHitPaddle = False End If ElseIf moPosition.z < (gnFarWallEdge + (gnPuckRadius)) Then If (moPosition.X > (gnScoringEdgeLeft - (gnPuckRadius))) Or (moPosition.X < (gnScoringEdgeRight - (gnPuckRadius))) Then 'We hit the wall moVelocity = vec3(moVelocity.X, 0, (moVelocity.z * -1) * gnVelocityDamp) moPosition = vec3(moPosition.X, moPosition.Y, gnFarWallEdge + (gnPuckRadius)) If Not (oAudio Is Nothing) Then oAudio.PlayBankSound gfRecentlyHitPaddle = False End If End If 'Next we should check to see if the puck has collided with either of the paddles 'We will use a simple formula to determine if the puck has collided with one of the 'paddles. Simply put if the distance between the center of the puck, and the center 'of the paddle in question is greater than the radius of the puck + the radius of the 'paddle, they haven't collided Dim vecDif As D3DVECTOR If ((timeGetTime - glPaddleCollideTime) > glMinDelayPaddleHit) Or (Not gfRecentlyHitPaddle) Then gfRecentlyHitPaddle = False For lCount = 0 To 1 'Both paddles 'We only check the X/Z coords because in this demo the puck will never leave the table 'so it will maintain a constant Y coord. D3DXVec3Subtract vecDif, moPosition, oPaddle(lCount).Position nDistance = D3DXVec3Length(vecDif) If nDistance < (gnPaddleRadius + gnPuckRadius) Then 'They have collided nCollideDist = nDistance lCollided = lCount fCollided = True If gfMultiplayer Then 'Let each client handle it's own collision detection 'in a multiplayer game. This balances the load between 'the host machine, and the client machine and gives the 'most realistic playing feel. If glMyPaddleID = lCount Then 'We collided with our paddle ChangePuckVelocity oPaddle(lCount), oAudio SendPuck SendCollidePaddle End If Else ChangePuckVelocity oPaddle(lCount), oAudio End If End If Next End If ' Make sure we aren't colliding anymore If fCollided Then EnsurePuckIsNotInPaddle nCollideDist, oPaddle(lCollided) 'Lastly we should check if we have scored (on either side) If gfMultiplayer And (Not gfHost) Then Exit Sub 'Only the host should check for scoring If moPosition.z > (gnNearWallEdge) Then 'We scored! goPuck.DropPuckIntoScoringPosition goAudio ElseIf moPosition.z < (gnFarWallEdge) Then 'We scored! goPuck.DropPuckIntoScoringPosition goAudio End If End Sub Public Sub EnsurePuckIsNotInPaddle(ByVal nDistance As Single, oPaddle As cPaddle, Optional ByVal fSentPaddle As Boolean = False) 'Move the paddle back out so it's not hitting the puck Dim vDir As D3DVECTOR, vScale As D3DVECTOR, vPaddleVel As D3DVECTOR If fSentPaddle Then D3DXVec3Subtract vPaddleVel, oPaddle.LastPosition, oPaddle.Position 'Get the direction vector by normalizing the paddle's velocity D3DXVec3Normalize vDir, vPaddleVel Else 'Get the direction vector by normalizing the pucks velocity D3DXVec3Normalize vDir, moVelocity End If 'Scale the vector, just enough to get it out of the paddle D3DXVec3Scale vScale, vDir, (gnPuckRadius + gnPaddleRadius) - nDistance 'Move the puck to it's new location D3DXVec3Add moPosition, moPosition, vScale 'Now, let's increase the pucks velocity that much as well.. If fSentPaddle Then D3DXVec3Add moVelocity, moVelocity, vScale End Sub Private Sub UpdatePuckSpin() Randomize If Rnd > 0.5 Then mnSpinDir = mnSpinDir * -1 'Update the spin, change speed from 75%-125% of current speed.. mnDefaultSpin = (Rnd * (mnSpinDir * 0.75)) + (mnSpinDir * 0.5) If Abs(mnDefaultSpin) > mnMaxSpinSpeed Then mnDefaultSpin = mnMaxSpinSpeed * (Abs(mnDefaultSpin) \ mnDefaultSpin) End If End If End Sub Public Sub CleanupFrame() moPuck.Destroy Set moPuck = Nothing End Sub Public Sub DropPuckIntoScoringPosition(oAudio As cAudio, Optional ByVal fFromReceive As Boolean = False) gfScored = True glTimeCompPaddle = 0 If Not gfMultiplayer Then With goPaddle(1).Velocity .X = 0: .z = 0 End With End If glTimePuckScored = timeGetTime oAudio.PlayScoreSound If gfMultiplayer Then If Not gfHost And Not fFromReceive Then Exit Sub End If 'First stop the velocity moVelocity = vec3(0, 0, 0) With moPosition 'Now position the puck If .z < 0 Then gPlayer(1).Score = gPlayer(1).Score + 1 .z = gnFarWallEdge - 1.2 ElseIf .z > 0 Then .z = gnNearWallEdge + 1.2 gPlayer(0).Score = gPlayer(0).Score + 1 End If If Abs(.X) > gnScoringEdgeLeft / 3 Then If Abs(.X) <> .X Then .X = gnScoringEdgeRight / 3 Else .X = gnScoringEdgeLeft / 3 End If End If .Y = gnPuckScored End With Spinning = False 'If we are the host, notify everyone that we've scored If gfMultiplayer Then NotifyPlayersWeScored End Sub Public Function FadeMesh(FadeInterval As Single) As Boolean Dim lNumMaterial As Long Dim lCount As Long Dim oMaterial As D3DMATERIAL8 Dim fDoneFading As Boolean Dim oMesh As CD3DMesh Dim nInternalInterval As Single Static lFadeTime As Long nInternalInterval = FadeInterval If lFadeTime = 0 Then lFadeTime = timeGetTime Exit Function 'We'll do the fade next render pass End If nInternalInterval = (((timeGetTime - lFadeTime) / 1000000) * nInternalInterval) Set oMesh = moPuck.FindChildObject("puck", 0) fDoneFading = True lNumMaterial = oMesh.GetMaterialCount For lCount = 0 To lNumMaterial - 1 oMaterial = oMesh.GetMaterial(lCount) If nInternalInterval > 0 And oMaterial.diffuse.a <= 1 Then oMaterial.diffuse.a = oMaterial.diffuse.a + nInternalInterval fDoneFading = False ElseIf nInternalInterval < 0 And oMaterial.diffuse.a >= -1 Then oMaterial.diffuse.a = oMaterial.diffuse.a + nInternalInterval fDoneFading = False End If oMesh.SetMaterial lCount, oMaterial Next FadeMesh = fDoneFading End Function Public Sub PauseSystem(ByVal fPause As Boolean) If Not fPause Then mlPuckTime = timeGetTime End If End Sub '************ 'Private functions that the public subs here will call, but the main application doesn't need to know about Private Sub EnsurePuckVelocityIsBelowMax() Dim VelVec As D3DVECTOR 'Let's make sure the puck's velocity isn't above the max, 'and if it is, lower it to the max velocity If D3DXVec3Length(moVelocity) > MaximumPuckVelocity Then 'Yup, lower the velocity to the max Dim vNrm As D3DVECTOR D3DXVec3Normalize vNrm, moVelocity D3DXVec3Scale VelVec, vNrm, MaximumPuckVelocity moVelocity = VelVec End If End Sub Private Sub Class_Initialize() mnSpinDir = 1 mnDefaultSpin = 0.15 Set moPuck = Nothing DefaultStartPosition End Sub Private Sub Class_Terminate() If Not moPuck Is Nothing Then moPuck.Destroy Set moPuck = Nothing End Sub