VertexCount = 3456 PrT = PrimitiveType.TriangleList VxB = New VertexBuffer(GetType(CustomVertex.PositionNormalTextured), VertexCount, D9, Usage.WriteOnly, CustomVertex.PositionNormalTextured.Format, Pool.Managed) 'That is a LOT of non-trivial parameters. TextureAndMeshSetup() 'Set up our textures. SetVertexBufferData(Vertices, VxB, D9) |

Private Sub SetVertexBufferData(ByRef vs() As CustomVertex.PositionNormalTextured, ByVal vxbf As VertexBuffer, ByVal D9 As Device) Dim LX, LY As Single, Ctr As Integer Const dL As Single = 1.0F 'After that workout, we now have to actually set the vertices. 'We do this by locking the surface out of D9 while we "operate" on it. Dim A As Array = vxbf.Lock(0, LockFlags.None) 'It returns a vanilla array... vs = DirectCast(A, CustomVertex.PositionNormalTextured()) 'So we have to convert it to an array of the appropriate vertices. 'Z = F(X, Y), but this time with texture. 'Our texture should be 0,0 at X,Y = -3,-3 and 1,1 at X,Y = 3,3 'So, we will simply do an interpolation during this process. 'An interpolation is, again, like this: 'Vf = L + Percent * (H - L), where Vf is the texture value, L is the lowest texture value, and H is the highest texture value. 'Percent would also be an interpolation: Percent = (Vo - L) / (H - L) 'where Vo is the loop variable value, L is the lowest loop value, and H is the highest loop value. 'The first formula would be Vf = 0 + Percent * (1 - 0) = Percent. 'The second formula would be Percent = (Vo - -3) / (3 - -3) = (Vo + 3) / 6, 'Therefore, Vf = (Vo + 3) / 6 'with L + dL 'Vf = (Vo + dVo + 3) / 6 For LY = -12.0F To 11.999F Step dL For LX = -12.0F To 11.999F Step dL 'triangle 1: vs(Ctr) = New CustomVertex.PositionNormalTextured(LX, LY, F(LX, LY), 0.0F, 0.0F, 0.0F, (LX + 3.0F) / 6.0F, (LY + 3.0F) / 6.0F) vs(Ctr + 1) = New CustomVertex.PositionNormalTextured(LX + dL, LY, F(LX + 0.25F, LY), 0.0F, 0.0F, 0.0F, (LX + 3.0F + dL) / 6.0F, (LY + 3.0F) / 6.0F) vs(Ctr + 2) = New CustomVertex.PositionNormalTextured(LX, LY + dL, F(LX, LY + 0.25F), 0.0F, 0.0F, 0.0F, (LX + 3.0F) / 6.0F, (LY + 3.0F + dL) / 6.0F) 'triangle 2: vs(Ctr + 3) = New CustomVertex.PositionNormalTextured(LX + dL, LY + dL, F(LX + dL, LY + dL), 0.0F, 0.0F, 0.0F, (LX + 3.0F + dL) / 6.0F, (LY + 3.0F + dL) / 6.0F) vs(Ctr + 4) = vs(Ctr + 2) vs(Ctr + 5) = vs(Ctr + 1) Ctr += 6 Next Next NormalizeTriangles(vs, PrT) 'It's important to unlock the vertex buffer, so that D9 can see what we put in there. vxbf.Unlock() End Sub Private Function F(ByVal X As Single, ByVal Y As Single) As Single 'Try out your own functions of X and Y. 'Return 0.05F * X * X 'Z = 1/4 (X²)... parabolic sheet of paper. 'Return 0.25F * X * Y 'Origami? Looks like the beginning of a paper bird. 'Return 0.25F * X * X + 0.25F * Y * Y 'A hammock with an invisible person sitting in it. 'Return Convert.ToSingle(Math.Sin(X * PiHalf)) 'This looks more sinusoidal at a higher resolution. 'Return 0.25F * X * X - 0.25F * Y * Y 'Saddle paper. 'Return Convert.ToSingle(Math.Cos(X * PiHalf)) * Convert.ToSingle(Math.Sin(Y * PiHalf)) 'Eggshell bed comforter. Return 0.0F 'Plane Z = 0 End Function |

'So, we'd be drawing here. 'To draw a vertex buffer, we set the device to receive from a vertex stream. 'The data is ready to stream out of the vertex buffer into the device and the device should draw it onto the screen. D9.SetStreamSource(0, VxB, 0) 'StreamNumber is usually zero while we only have one vertex buffer that streams vertex data. 'Of course, we also have to tell it which vertex buffer to get vertex data from. 'Offset as 0 just tells the device how many vertices to skip before drawing. D9.VertexFormat = CustomVertex.PositionNormalTextured.Format 'We also need to tell the device what kind of vertices to draw. 'We have not told the device which vertices these are (that was the vertex buffer). 'This could actually come out of the loop, but there will usually be many different types of vertex types 'set within this loop. D9.DrawPrimitives(PrT, 0, GetPrimitiveCount(PrT, VertexCount)) |

D9.Lights(0).Type = LightType.Point 'Regular lightbulb. D9.Lights(0).Position = New Vector3(0.0F, 0.0F, 2.0F) 'Place the bulb here. D9.Lights(0).Range = 48.8F D9.Lights(0).Attenuation0 = 0.0F D9.Lights(0).Attenuation1 = 0.45F D9.Lights(0).Ambient = Color.White |

Once that is set up, let's do some movement: First, we set up our camera so that it is facing in some direction and the distance from the Camera to the FocusPt is one. Below, our camera is at 3,3 with a height of 1 and is looking south (due to the FocusPt Y value being 1 larger than the camera's point).

Camera = New Vector3(3.0F, 3.0F, 1.0F) FocusPt = New Vector3(3.0F, 4.0F, 1.0F) CamNormal = New Vector3(0.0F, 0.0F, 1.0F) |

Dim AnglePl, AngleEv As Double |

FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv)) |

Our first movement is a "look up". When you press a "look up" button, your character should look up. This can be done by altering the AngleEv, which tells us the angle above horizontal that the player is currently looking. In some games, this is done with the up arrow - in some it is done with the down arrow - and of course, it can be done with the mouse as well (but that is beyond this lesson). After every movement, we update the focus point and the camera, if applicable (not in this case since the character is not changing location).

Case Keys.NumPad2 'Down If AngleEv < PiHalf Then AngleEv += (Pi / 30) End If FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv)) |

Case Keys.NumPad8 'Up If AngleEv > -PiHalf Then AngleEv -= (Pi / 30) End If FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv)) |

Next, we'll do a "turn left" or "look left". In this one, we only alter the AnglePl to give the appearance that the player turned or looked to the left. The trick is to remember which angle increase corresponds to left. AnglePl = 90°, according to the above spherical functions, is in the positive Y direction (Hint: at 0°, Cos(AnglePl) dominates, and at 90°, Sin(AnglePl) dominates). So, if we were to increase the value of AnglePl, the player would turn from the positive X direction to the positive Y direction. In a right-hand system, this would be turning to the left. So, we would have to add to the angle to make the player turn left.

Case Keys.NumPad6 'Left AnglePl += (Pi / 30) FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv)) |

Case Keys.NumPad4 'Left AnglePl -= (Pi / 30) FocusPt.X = Camera.X + Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) FocusPt.Y = Camera.Y + Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) FocusPt.Z = Camera.Z + Convert.ToSingle(Math.Sin(AngleEv)) |

Next, we'll do walking forward. This one is a little more challenging since we need the angle that the player is facing to actually determine how far the player should move in each direction, X, and Y. Fortunately, with a little trigonometry, we can figure out the difference in both directions with Cosine and Sine. To make it easy to keep track of these changes we can declare some quick reference values:

Dim dX, dY, dZ As Single |

Case Keys.Up dX = Convert.ToSingle(Math.Cos(AnglePl)) dY = Convert.ToSingle(Math.Sin(AnglePl)) Camera.X += dX Camera.Y += dY FocusPt.X += dX FocusPt.Y += dY |

Case Keys.Down dX = Convert.ToSingle(Math.Cos(AnglePl)) dY = Convert.ToSingle(Math.Sin(AnglePl)) Camera.X -= dX Camera.Y -= dY FocusPt.X -= dX FocusPt.Y -= dY |

Next, we will do "strafe left". This is very similar to walking forward except that we have the challenge of figuring out which direction goes with which trigonometric function. Obviously, if the character is facing at 0°, strafing left means he should walk to the positive Y direction (or negative Y direction, for left-hand coordinate systems) and strafing right means negative Y direction (or positive). At any other angle, this change in the Y direction is smaller, so this must correspond to a maximum. So, what function is the maximum at 0°? You guessed it, cosine. So, we apply cosine to the Y direction. It would follow that sine would be used for the X direction. Now, what about the signs? Since strafing left at 90° (where Sine is at a maximum) would mean that you move in the negative X and strafing right at 90° means moving in the positive X direction, we can possibly see that strafing left means that we would subtract dX and add dY (figure this one out by asking if the character walks in the negative direction or in the positive direction). So, strafing left would be:

Case Keys.Right dX = Convert.ToSingle(Math.Sin(AnglePl)) dY = Convert.ToSingle(Math.Cos(AnglePl)) Camera.X -= dX Camera.Y += dY FocusPt.X -= dX FocusPt.Y += dY |

Case Keys.Left dX = Convert.ToSingle(Math.Sin(AnglePl)) dY = Convert.ToSingle(Math.Cos(AnglePl)) Camera.X += dX Camera.Y -= dY FocusPt.X += dX FocusPt.Y -= dY |

Next, we will do a "fly forward" - this merely means the character goes forward in whatever direction the character is facing - even if the character is looking upwards. For this, we merely apply the spherical formula to figure out how far to move in each direction:

dX = Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) dY = Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) dZ = Convert.ToSingle(Math.Sin(AngleEv)) Camera.X += dX Camera.Y += dY Camera.Z += dZ FocusPt.X += dX FocusPt.Y += dY FocusPt.Z += dZ |

Case Keys.Down dX = Convert.ToSingle(Math.Cos(AnglePl) * Math.Cos(AngleEv)) dY = Convert.ToSingle(Math.Sin(AnglePl) * Math.Cos(AngleEv)) dZ = Convert.ToSingle(Math.Sin(AngleEv)) Camera.X -= dX Camera.Y -= dY Camera.Z += dZ FocusPt.X -= dX FocusPt.Y -= dY FocusPt.Z -= dZ |

.vb file