## How to code a 3D-Road-Algorithm for Beginners

Started by Midimaster, April 26, 2023, 09:29:07

#### Midimaster

In this tutorial I will explain what you need to draw procedural roads into a 3D-world. This means we have no file of the 3D-oject, but we create it with only code. At the end the road will be like a regular 3D-object: you can move it, scale it, etc...

This GIF with 4 screenshots the result of the tutorial:

The tutorial is for Blitz3D and BlitzMax/MiniB3D. You always will find runable code examples for both in the lessons. I kept the code particularly simple. It could be done more effficient, but I did not want to divert from the kernel 3D methods. So please do not critisize the non 3D related code lines.

If you have questions I would ask for not using this topic until the 5 lessons are written. Please open a new topic in forum BlitzMax or forum Blitz3D.

The lessons I to III are still in 2D explain the road algorithm and its problematic. The lessons IV to V will explain how to create Meshes in general

In the lessons VI to ... we glue together both strategies to a runable code example.

Lesson I: All You Need Is Sinus And Cosinus

Drawing forms in any direction in a plane needs the use of Sinus and Cosinus and an action point. Each new point needs to be transfered from a action point into its direction.

Code (vb) Select
`Function Tranfer(x%, z%, length% ,arc%)    TransX = x + Sin(arc) * length    TransZ = z - Cos(arc) * lengthEnd Function `
To be prepared for the 3D world we already use the name TransZ instead of TransY for the coordinate. Y is for the hight of objects.

Our first examples shows a rotating L-form:

`Cls    Tranfer ActionX, ActionZ, 200, 45    CornerX = TransX    CornerZ = TransZ    Line ActionX, ActionZ, CornerX, CornerZ    Tranfer CornerX, CornerZ, 100, 45+90    Line CornerX, CornerZ, TransX, TransZFlip....    Function Tranfer(x%, z%, length% ,arc%)    TransX = x + Sin(arc) * length    TransZ = z - Cos(arc) * lengthEnd Function  `
The first call of the function Transfer() transfers the given Corner into the direction 45°. Then we transfer the second point in 90° orientation to the first line with Corner as action point.

Here is the complete code for Blitz3D:
`Graphics 800,600Global ActionX% = 300Global ActionZ% = 200Global Arc%     = 45Global TransX%, TransZ%; draw a 200pix 45° line from Actionpoint Repeat    Cls    Arc=Arc+1    Tranfer ActionX, ActionZ, 200, Arc    CornerX = TransX    CornerZ = TransZ    Line ActionX, ActionZ, CornerX, CornerZ    Tranfer CornerX, CornerZ, 100, Arc+90    Line CornerX, CornerZ, TransX, TransZ    Flip 1Until KeyHit(1)EndFunction Tranfer(x%, z%, length% ,arc%)    TransX = x + Sin(arc) * length    TransZ = z - Cos(arc) * lengthEnd Function `
and here for BlitzMax:
`SuperStrictGraphics 800,600Global ActionX% = 300Global ActionZ% = 200Global Arc%     = 45Global TransX%, TransZ%' draw a 200pix 45° line from Actionpoint Repeat    Cls    Arc=Arc+1    Tranfer ActionX, ActionZ, 200, Arc    Local CornerX% = TransX    Local CornerZ% = TransZ    DrawLine ActionX, ActionZ, CornerX, CornerZ    Tranfer CornerX, CornerZ, 100, Arc+90    DrawLine CornerX, CornerZ, TransX, TransZ    Flip 1Until AppTerminate()EndFunction Tranfer(x%, z%, length% ,arc%)    TransX = x + Sin(arc) * length    TransZ = z - Cos(arc) * lengthEnd Function `

3D Likes Curves

Because of permanent use of sin() and cos() it is indeed more easy to draw curves than straight roads in the 3D-world. In the next example we have a top-view to a plane. You can draw a "curved road" with the formula you already know. We use two arrays to store the waypoints and Line (DrawLine) commands to connect them. The red point is the action point (or you can say center of the circle).

Hit the key <S> to step ahead:

`Graphics 800,600Global RADIUS#  = 100Global ActionX# = 200Global ActionZ# = 400Global Arc%     = 180Dim X#(200)Dim Z#(200)Global Count%    = 0Repeat    Cls    If KeyHit(31) ; <S>        X(Count) = ActionX + Sin(arc) * RADIUS        Z(Count) = ActionZ - Cos(arc) * RADIUS        Arc = Arc-10        Count=Count +1    EndIf         DrawAll    Flip 1Until KeyHit(1)EndFunction DrawAll()    Color 255,255,255    Text 100,100," Arc = " + Arc +"°"    Text 100,120,"Count= " + count    Text 100, 20,"Hit <S> to step ahead"    Color 255,0,0    Oval ActionX,ActionZ,5,5    Color 255,255,0    For i%=0 To 199        If X(i+1)=0 Return        Line X(i),Z(i),X(i+1),Z(i+1)    Next  End Function`
You will recognize that the first hit of <S> will reuslt in no street. This is important. With only one first waypoint you cannot draw a street segment. So if you plan to draw 10 street segments you always need 11 waypoints!

Here is the same in BlitzMax:
`SuperStrictGraphics 800,600Global RADIUS:Double  = 100Global ActionX:Double = 200Global ActionZ:Double = 400Global Arc:Int     = 180Global X:Double[200]Global Z:Double[200]Global Count:Int    = 0Repeat    Cls    If KeyHit(KEY_S) ' <S>        X[Count] = ActionX + Sin(arc) * RADIUS        Z[Count] = ActionZ - Cos(arc) * RADIUS        Arc = Arc-10        Count=Count +1    EndIf         DrawAll    Flip 1Until KeyHit(1)EndFunction DrawAll()    SetColor 255,255,255    DrawText " Arc = " + Arc +"°"   ,100,100    DrawText "Count= " + count      ,100,120    DrawText "Hit <S> to step ahead",100, 20    SetColor 255,0,0    DrawOval ActionX,ActionZ,5,5    SetColor 255,255,0    For Local i:Int = 0 To 199        If X[i+1] = 0 Return        DrawLine X[i],Z[i],X[i+1],Z[i+1]    Next  End Function`

...back from Egypt

#### Midimaster

Lesson II: Straight Road and Right Curve

To move the road straight ahead we only have to move the action point. With this change you will build a straight road after step 10:

Code (vb) Select
`Repeat Cls If KeyHit(31) ; <S> X(Count) = ActionX + Sin(arc) * RADIUS Z(Count) = ActionZ - Cos(arc) * RADIUS If Count<9 Arc = Arc-10 Else ActionZ=ActionZ-10 EndIf Count=Count +1 EndIf DrawAll Flip 1Until KeyHit(1)End`

But you can also see, that the action point can stay left outside the road. So to say: A straight road is a left turn without changing the arc.

Here is the complete code for Blitz3D
`Graphics 800,600Global RADIUS#  = 100Global ActionX# = 200Global ActionZ# = 400Global Arc%     = 180Dim X#(200)Dim Z#(200)Global Count%    = 0Repeat Cls If KeyHit(31) ; <S> X(Count) = ActionX + Sin(arc) * RADIUS Z(Count) = ActionZ - Cos(arc) * RADIUS If Count<9 Arc = Arc-10 Else ActionZ=ActionZ-10 EndIf Count=Count +1 EndIf DrawAll Flip 1Until KeyHit(1)EndFunction DrawAll() Color 255,255,255 Text 100,100," Arc = " + Arc +"°" Text 100,120,"Count= " + count Text 100, 20,"Hit <S> more than 10 times to step ahead" Color 255,0,0 Oval ActionX,ActionZ,5,5 Color 255,255,0 For i%=0 To 199 If X(i+1)=0 Return Line X(i),Z(i),X(i+1),Z(i+1) Next  End Function`

And here is the complete code for BlitzMax
`SuperStrictGraphics 800,600Global RADIUS:Double  = 100Global ActionX:Double = 200Global ActionZ:Double = 400Global Arc:Int     = 180Global X:Double[200]Global Z:Double[200]Global Count:Int    = 0Repeat Cls If KeyHit(KEY_S) ' <S> X[Count] = ActionX + Sin(arc) * RADIUS Z[Count] = ActionZ - Cos(arc) * RADIUS If Count<9 Arc = Arc-10 Else ActionZ=ActionZ-10 EndIf Count=Count +1 EndIf DrawAll Flip 1Until AppTerminate()EndFunction DrawAll() SetColor 255,255,255 DrawText " Arc = " + Arc +"°"   ,100,100 DrawText "Count= " + count      ,100,120 DrawText "Hit <S> to step ahead",100, 20 SetColor 255,0,0 DrawOval ActionX,ActionZ,5,5 SetColor 255,255,0 For Local i:Int = 0 To 199 If X[i+1] = 0 Return DrawLine X[i],Z[i],X[i+1],Z[i+1] Next  End Function`

A Right Turn Is Not That Easy

For a right turn of the road we have to move the action point. The action point is always the center of the radius of the road. So if we want to turn right, the action point needs to be right of the road. But a first try to simply move the point to the right...

`ActionX= ActionX+ 2*RADIUS`

... ends in this catastophe:

To understand what happened lets have a look on how the action point sees our street. In the first steps the action point is left of us. He sees us in direction EAST (means 90°). After we placed it on the right, he sees us in direction WEST (270°).

So the action point needs to calculate +180° to our last direction to continue. The road will run from WEST 270° to NORTH 360° during the next steps. We forgot this change a so we got wrong results.

This is necessary:
`ActionX= ActionX+ 2*RADIUSArc = Arc +180`

This is the whole code in Blitz3D
`Graphics 800,600Global RADIUS#  = 100Global ActionX# = 200Global ActionZ# = 400Global Arc%     = 180Dim X#(200)Dim Z#(200)Global Count%    = 0Repeat Cls If KeyHit(31) ; <S> If Count<9 Arc = Arc-10 ElseIf Count<20 ActionZ = ActionZ-10 ElseIf Count=20 ActionX= ActionX+ 2*RADIUS Arc = Arc +180 Else Arc=arc+10 EndIf X(Count) = ActionX + Sin(arc) * RADIUS Z(Count) = ActionZ - Cos(arc) * RADIUS Count=Count +1 EndIf DrawAll Flip 1Until KeyHit(1)EndFunction DrawAll() Color 255,255,255 Text 100,100," Arc = " + Arc +"°" Text 100,120,"Count= " + count Text 100, 20,"Hit <S> more than 20 times to step ahead" Color 255,0,0 Oval ActionX,ActionZ,5,5 Color 255,255,0 For i%=0 To 199 If X(i+1)=0 Return Line X(i),Z(i),X(i+1),Z(i+1) Next  End Function`

This is the whole code in BlitzMax
`SuperStrictGraphics 800,600Global RADIUS:Double  = 100Global ActionX:Double = 200Global ActionZ:Double = 400Global Arc:Int     = 180Global X:Double[200]Global Z:Double[200]Global Count:Int    = 0Repeat Cls If KeyHit(KEY_S) ' <S> If Count<9 Arc = Arc-10 ElseIf Count<20 ActionZ = ActionZ-10 ElseIf Count=20 ActionX= ActionX+ 2*RADIUS Arc = Arc +180 Else Arc=arc+10 EndIf X[Count] = ActionX + Sin(arc) * RADIUS Z[Count] = ActionZ - Cos(arc) * RADIUS Count=Count +1 EndIf DrawAll Flip 1Until AppTerminate()EndFunction DrawAll() SetColor 255,255,255 DrawText " Arc = " + Arc +"°"   ,100,100 DrawText "Count= " + count      ,100,120 DrawText"Hit <S> more than 20 times to step ahead",100, 20 SetColor 255,0,0 DrawOval ActionX,ActionZ,5,5 SetColor 255,255,0 For Local i:Int = 0 To 199 If X[i+1] = 0 Return DrawLine X[i],Z[i],X[i+1],Z[i+1] Next  End Function`

...back from Egypt

#### STEVIE G

Nice work.

I think that by defining road sections as arcs/lengths it might be tricky to build a seamless racetrack, specifically where the final section meets the first.

I prefer the 4pt Bezier method myself.

#### Santiago

Thanks for sharing!!

#### Midimaster

#4
Lesson III: Both Sides Of A Road

Today we work with 2 lines: Left side of the road and right side of the road. Therefore we now use two Array-Pairs

Code (vb) Select
`;for the left road sidexB#[]zB#[];for the right road sidexC#[]zC#[]`
I'm using  the letters B and C now for the road, because later in the tutorial we will expand this to 4 waypoints A,B,C,D and the letters B and C will represent the top road surface.

The Left side of the road (B) has the distance RADIUS to the action point (also on the left side). And the right side of the road has the distance RADIUS+ STREET_WIDTH to the Action Point.

Code (vb) Select
`L = RADIUSR = RADIUS+STREET_WIDTH`
When we draw a right curve the action point is on the right side on the road. This means, that now the RIGHT ROAD SIDE is closer to the action point and the LEFT ROAD SIDE has the distance  RADIUS+ STREET_WIDTH

Code (vb) Select
`L = RADIUS+STREET_WIDTHR = RADIUS`
So we better do this: We differ between three states Dir%

0= LEFT CURVE
2= RIGHT CURVE

Then we can code:

Code (vb) Select
`If dir=0    L = RADIUS    R = RADIUS+STREET_WIDTHElseIf dir=1    L = RADIUS    R = RADIUS+STREET_WIDTHElse     L = RADIUS+STREET_WIDTH    R = RADIUSEndIf;yellow Line (LEFT ROAD SIDE)xB(Count) = ActionX + Sin(arc) * LzB(Count) = ActionZ - Cos(arc) * L;green line  (RIGHT ROAD SIDE)xC(Count) = ActionX + Sin(arc) * RzC(Count) = ActionZ - Cos(arc) * R`
As a last step we need to consider, that the jump distance for the action point, when jumping from left side to right side is now bigger
Code (vb) Select
`ActionX= ActionX+ 2*RADIUS+STREET_WIDTH`

Here is the complete code for Blitz3D:
Code (vb) Select
`Graphics 800,600Global RADIUS#       = 100Global STREET_WIDTH# = 20Global ActionX# = 200Global ActionZ# = 400Global Arc%     = 190Dim xB#(200)Dim zB#(200)Dim xC#(200)Dim zC#(200)Global Count%    = 0Global L#, R#, Dir%Repeat    Cls    If KeyHit(31) ; <S>        If Count<10            Arc = Arc-10            Dir=0        ElseIf Count<20            ActionZ=ActionZ-10            Dir=1        ElseIf Count=20            ActionX= ActionX+ 2*RADIUS+STREET_WIDTH            Arc = Arc +180            Dir=2        Else             Arc=arc+10        EndIf        If dir=0            L = RADIUS            R = RADIUS+STREET_WIDTH        ElseIf dir=1            L = RADIUS            R = RADIUS+STREET_WIDTH        Else             L = RADIUS+STREET_WIDTH            R = RADIUS        EndIf        ;yellow Line (LEFT ROAD SIDE)        xB(Count) = ActionX + Sin(arc) * L        zB(Count) = ActionZ - Cos(arc) * L        ;blue line  (RIGHT ROAD SIDE)        xC(Count) = ActionX + Sin(arc) * R        zC(Count) = ActionZ - Cos(arc) * R            Count=Count +1    EndIf         DrawAll    Flip 1Until KeyHit(1)Function DrawAll()    Color 255,255,255    Text 100,100," Arc = " + Arc +"°"    Text 100,120,"Count= " + count    Text 100, 20,"Hit <S> to step ahead"    Color 255,0,0    Oval ActionX,ActionZ,9,9    Color 255,255,0    For i%=0 To 199        If xB(i+1)=0 Exit        Line xB(i), zB(i), xB(i+1), zB(i+1)    Next      Color 0,255,255    For i%=0 To 199        If xC(i+1)=0 Exit         Line xC(i), zC(i), xC(i+1), zC(i+1)    Next  End Function`

Here is the complete code for BlitzMax:
Code (vb) Select
`SuperStrictGraphics 800,600Global RADIUS:Double  = 100Global STREET_WIDTH:Double = 20Global ActionX:Double = 200Global ActionZ:Double = 400Global Arc:Int     = 180Global xB:Double[200]Global zB:Double[200]Global xC:Double[200]Global zC:Double[200]Global Count:Int    = 0Global L:Double, R:Double, Dir:IntRepeat    Cls    If KeyHit(KEY_S) ' <S>        If Count<9            dir=0            Arc = Arc-10        ElseIf Count<20            dir=1            ActionZ = ActionZ-10            ElseIf Count=20            dir=2            ActionX= ActionX+ 2*RADIUS+STREET_WIDTH            Arc = Arc +180        Else            Arc=arc+10        EndIf        If dir=0            L = RADIUS            R = RADIUS+STREET_WIDTH        ElseIf dir=1            L = RADIUS            R = RADIUS+STREET_WIDTH        Else             L = RADIUS+STREET_WIDTH            R = RADIUS        EndIf        'yellow Line (Left ROAD SIDE)        xB[Count] = ActionX + Sin(arc) * L        zB[Count] = ActionZ - Cos(arc) * L        'blue line  (Right ROAD SIDE)        xC[Count] = ActionX + Sin(arc) * R        zC[Count] = ActionZ - Cos(arc) * R            Count=Count +1    EndIf         DrawAll    Flip 1Until AppTerminate()EndFunction DrawAll()    SetColor 255,255,255    DrawText " Arc = " + Arc +"°"   ,100,100    DrawText "Count= " + count      ,100,120    DrawText"Hit <S> more than 20 times to step ahead",100, 20    SetColor 255,0,0    DrawOval ActionX,ActionZ,5,5    SetColor 255,255,0    For Local i:Int = 0 To 199        If xB[i+1] = 0 Exit        DrawLine xB[i], zB[i], xB[i+1], zB[i+1]    Next      SetColor 0,255,255    For Local i:Int = 0 To 199        If xB[i+1] = 0 Return        DrawLine xC[i], zC[i], xC[i+1], zC[i+1]    Next      End Function`

In lesson II I cheated, when telling you, that a straight road is made by simply adding a number. Of course also for the straight road parts the moving action points need to be calculated with Sinus and Cosinus.

Code (vb) Select
`ActionX = ActionX - Cos(Arc)*STREET_WIDTHActionZ = ActionZ - Sin(Arc)*STREET_WIDTH`

And we don't forget to reset the action point and the 180° at the end of each RIGHT CURVE SEGMENT after we calculated the waypoints.

Code (vb) Select
`;yellow Line (LEFT ROAD SIDE)xB(Count) = ActionX + Sin(arc) * LzB(Count) = ActionZ - Cos(arc) * L;blue line  (RIGHT ROAD SIDE)xC(Count) = ActionX + Sin(arc) * RzC(Count) = ActionZ - Cos(arc) * R; restore action point    If Dir=2    Arc = Arc -180    ActionX= ActionX - Sin(Arc) * (2*RADIUS+STREET_WIDTH)    ActionZ= ActionZ + Cos(Arc) * (2*RADIUS+STREET_WIDTH)EndIf `
With this we can change direction now on each segment. Fell free to play around with the keys <D> for "change direction" and <S> for "step ahead"

Here is the complete code for Blitz3D:
Code (vb) Select
`Graphics 800,600Global RADIUS#       = 100Global STREET_WIDTH# = 20Global ActionX# = 200Global ActionZ# = 400Global Arc%     = 190Dim xB#(200)Dim zB#(200)Dim xC#(200)Dim zC#(200)Global Count%    = 0Global L#, R#, Dir%Global DirWord\$="LEFT"Repeat    Cls    If KeyHit(32) ; <D>        ;change direction Left Right        dir= (dir +1) Mod 3         Select Dir            Case 0                DirWord ="LEFT"            Case 1                    DirWord ="STRAIGHT"            Case 2                DirWord ="RIGHT"        End Select     ElseIf KeyHit(31) ; <S>        If dir=0            Arc = Arc-10            L = RADIUS            R = RADIUS+STREET_WIDTH        ElseIf dir=1            L = RADIUS            R = RADIUS+STREET_WIDTH            ActionX = ActionX - Cos(Arc)*STREET_WIDTH            ActionZ = ActionZ - Sin(Arc)*STREET_WIDTH        Else             ActionX= ActionX + Sin(Arc) * (2*RADIUS+STREET_WIDTH)            ActionZ= ActionZ - Cos(Arc) * (2*RADIUS+STREET_WIDTH)            Arc = Arc +180            Arc = Arc+10            L = RADIUS+STREET_WIDTH            R = RADIUS        EndIf        ;yellow Line (LEFT ROAD SIDE)        xB(Count) = ActionX + Sin(arc) * L        zB(Count) = ActionZ - Cos(arc) * L        ;blue line  (RIGHT ROAD SIDE)        xC(Count) = ActionX + Sin(arc) * R        zC(Count) = ActionZ - Cos(arc) * R            If Dir=2            Arc = Arc -180            ActionX= ActionX - Sin(Arc) * (2*RADIUS+STREET_WIDTH)            ActionZ= ActionZ + Cos(Arc) * (2*RADIUS+STREET_WIDTH)        EndIf         Count=Count +1    EndIf         DrawAll    Flip 1Until KeyHit(1)Function DrawAll()    Color 255,255,255    Text 100,100," Arc = " + Arc +"°"    Text 100,120,"Count= " + count    Text 100,140,"  Dir= " + DirWord    Text 100, 20,"Hit <S> to step ahead"    Text 100, 40,"Hit <D> to change direction LEFT STRAIGHT RIGHT"    Color 255,0,0    Oval ActionX,ActionZ,9,9    Color 255,255,0    For i%=0 To 199        If xB(i+1)=0 Exit        Line xB(i), zB(i), xB(i+1), zB(i+1)    Next      Color 0,255,255    For i%=0 To 199        If xC(i+1)=0 Exit         Line xC(i), zC(i), xC(i+1), zC(i+1)    Next  End Function`

Here is the complete code for BlitzMax:
Code (vb) Select
`SuperStrictGraphics 800,600Global RADIUS:Double  = 100Global STREET_WIDTH:Double = 20Global ActionX:Double = 200Global ActionZ:Double = 400Global Arc:Int     = 180Global xB:Double[200]Global zB:Double[200]Global xC:Double[200]Global zC:Double[200]Global Count:Int    = 0Global L:Double, R:Double, Dir:IntGlobal DirWord\$="LEFT"Repeat    Cls    If KeyHit(KEY_D) ' <D>        'change direction Left Right        dir= (dir +1) Mod 3         Select Dir            Case 0                DirWord ="LEFT"            Case 1                    DirWord ="STRAIGHT"            Case 2                DirWord ="RIGHT"        End Select         ElseIf KeyHit(KEY_S) ' <S>            If dir=0            L = RADIUS            R = RADIUS+STREET_WIDTH            Arc = Arc-10        ElseIf dir=1            L = RADIUS            R = RADIUS+STREET_WIDTH            ActionX = ActionX - Cos(Arc)*STREET_WIDTH            ActionZ = ActionZ - Sin(Arc)*STREET_WIDTH        Else             L = RADIUS+STREET_WIDTH            R = RADIUS            ActionX= ActionX + Sin(Arc) * (2*RADIUS+STREET_WIDTH)            ActionZ= ActionZ - Cos(Arc) * (2*RADIUS+STREET_WIDTH)            Arc = Arc +180            Arc = Arc+10        EndIf        'yellow Line (Left ROAD SIDE)        xB[Count] = ActionX + Sin(arc) * L        zB[Count] = ActionZ - Cos(arc) * L            'blue line  (Right ROAD SIDE)        xC[Count] = ActionX + Sin(arc) * R        zC[Count] = ActionZ - Cos(arc) * R                    If Dir=2            Arc = Arc -180            ActionX= ActionX - Sin(Arc) * (2*RADIUS+STREET_WIDTH)            ActionZ= ActionZ + Cos(Arc) * (2*RADIUS+STREET_WIDTH)        EndIf         Count=Count +1    EndIf         DrawAll    Flip 1Until AppTerminate()Function DrawAll()    SetColor 255,255,255    DrawText " Arc = " + Arc +"°"   ,100,100    DrawText "Count= " + count      ,100,120    DrawText "  Dir= " + DirWord    ,100,140    DrawText "Hit <S>to step ahead",100, 20    DrawText "Hit <D> to change direction LEFT STRAIGHT RIGHT", 100, 40    SetColor 255,0,0    DrawOval ActionX,ActionZ,5,5    SetColor 255,255,0    For Local i:Int = 0 To 199        If xB[i+1] = 0 Exit        DrawLine xB[i], zB[i], xB[i+1], zB[i+1]    Next      SetColor 0,255,255    For Local i:Int = 0 To 199        If xB[i+1] = 0 Return        DrawLine xC[i], zC[i], xC[i+1], zC[i+1]    Next      End Function`

...back from Egypt

#### Midimaster

#5
Lesson IV: Mesh, Vertex and Triangle

Here is a short excursion about the elements of a 3D object. Let us compare the technical terms of a 3D-object with the terms of a dice.

CreateMesh()
....creates an empty object. Related to our dice it is not more than "let us make a thing"

CreateSurface()
...creates an empty list of elements in the Mesh. Related to our dice we can say: The Surface enumerates all we can see from a dice: Corners, Faces,  Colors, etc... Without a Surface the dice is not visible.

...creates a corner in the dice. A dice has 8 corners means, it's Surface has 8 Vertices. This vertices are unvisible, they are only virtual coordinates in 3D space.

...defines faces on the dice. A dice's face is a rectangle. But Mesh cannot handle rectangle, but only Triangles. So we need already 2 triangles to show a rectangle. Our dice has 6 rectangular faces. It's Surface has 12 Triangles. Each triangle is defined by 3 Vertices

Triangles are not comparable with the experience we have from the real world. Lets have a top-view look on a pieces of paper. It is a Mesh with a Surface and has 4 Vertices. If you now would believe that 2 Triangles are enough to completely display it you are wrong. 2 triangles would only display the upper side of the paper. When we look at a such a "paper"-mesh from behind it would be invisible. So the backside needs 2 more Triangles.

So we can say Triangles are only visible from one side, from the other side they are invisible.

In the case of our dice, we would not need the double number of triangles, because there is no possibility to look "inside" the dice. But if this cube is not a dice, but a "transport-box" we immediately would need 24 triangles to keep the box visible in all aspects.

To guarantee, that the triangle has the correct orientation and is visible we have to define the correct sequence of Vertices.

When you enumerate the points clockwise...
`AddTriangle Surface, A, C, BAddTraingle Surface, B, C, D`
... this rectangle would be visible from front side

When you enumerate the points counterclockwise... .
`AddTriangle Surface, A, B, CAddTraingle Surface, B, D, C`
... this rectangle would be visible from back side

EntityTexture()
...adds a colored content to a surface. A final surface without Texture is visible as complete white object. In the case of our paper-mesh this would be sufficient. In the case of our dice the Texture contains the dots

This is the minimum startup example:

Code (vb) Select
`Graphics3D 800,600,32,0Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street = CreateMesh()Global Surf   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#Global xA#, zA#    ; left  street waypointGlobal xB#, zB#    ; right street waypointGlobal xC#, zC#    ; left  street waypoint +1Global xD#, zD#    ; right street waypoint +1StraightRoadUpdateNormals Street;top-view camera:Global Camera = CreateCamera()PositionEntity Camera,0,5,0TurnEntity   Camera, 90,0,0WireFrame True Repeat    UpdateWorld    RenderWorld    Flip 1Until KeyHit(1)`
We create a Mesh and a Surface and a Camera with a top-down-view.
We define 4 waypoints A B C D
We write all this standard stuff for 3D
Have a look on the command WireFrame. This will show us the triangles and not the solid surface.

Calculating the waypoints

Code (vb) Select
`Function StraightRoad()    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)       ; A and B    xA = cosinus * LEFT_SIDE   + ActionX    zA = sinus   * LEFT_SIDE   + ActionZ    xB = cosinus * RIGHT_SIDE  + ActionX    zB = sinus   * RIGHT_SIDE  + ActionZ    ; move ahead    ActionX = ActionX - sinus    ActionZ = ActionZ + cosinus        ; C and D    xC = cosinus * LEFT_SIDE   + ActionX    zC = sinus   * LEFT_SIDE   + ActionZ    xD = cosinus * RIGHT_SIDE  + ActionX    zD = sinus   * RIGHT_SIDE  + ActionZ    AddVertexes    ; now the 2 triangles    AddTraingles      End Function`In the function StraightRoad() we calculate the 4 waypoints related to its position like in lesson III.

Code (vb) Select
`Function AddVertexes()        AddVertex Surf, xA, 0, zA, 0, 0        AddVertex Surf, xB, 0, zB, 0, 0        AddVertex Surf, xC, 0, zC, 0, 0        AddVertex Surf, xD, 0, zD, 0, 0End Function`
A Vertex needs 5 parameters The coordinates for X Y and Z. At the moment our Y is always 0. Here you would be able to give the road a altitude (for a bridge or over a hill)

The 4th and 5th parameter is related to the texture. At the moment we dont use it.
Out  4 waypoints are now added into a list with 4 entries starting with item-0 to item-3 like an array.

The last step is defining the triangles.

Code (vb) Select
`Function AddTraingles()    ;    C--------D    ;    |        |    ;    |        |    ;    |        |    ;    |        |    ;    A--------B    ; vertex-list:    ; 0.  1.  2.  3.        ; A - B - C - D      ; A-D-B:           A   D   B      AddTriangle Surf , 0 , 3 , 1    ; A-C-D:           A   C   D      AddTriangle Surf , 0 , 2 , 3End Function`

When you start the app you will see nothing spectacular: a rectangle as a first street element.

But it is your first selfmade 3D-object. you can move it like a Cube, or colorize it, size it. Try what happens if you comment out the WireFrame command. Try to play around with using a different ARC%. That's enough for today.

Here is the complete code in Blitz3D:
Code (vb) Select
`Graphics3D 800,600,32,0Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street = CreateMesh()Global Surf   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#Global xA#, zA#    ; left  street waypointGlobal xB#, zB#    ; right street waypointGlobal xC#, zC#    ; left  street waypoint +1Global xD#, zD#    ; right street waypoint +1StraightRoadUpdateNormals Street;top-view camera:Global Camera = CreateCamera()PositionEntity Camera,0,5,0TurnEntity   Camera, 90,0,0WireFrame True Repeat    UpdateWorld    RenderWorld    Flip 1Until KeyHit(1)Function StraightRoad()    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)       ; A and B    xA = cosinus * LEFT_SIDE   + ActionX    zA = sinus   * LEFT_SIDE   + ActionZ    xB = cosinus * RIGHT_SIDE  + ActionX    zB = sinus   * RIGHT_SIDE  + ActionZ    ; move ahead    ActionX = ActionX - sinus    ActionZ = ActionZ + cosinus        ; C and D    xC = cosinus * LEFT_SIDE   + ActionX    zC = sinus   * LEFT_SIDE   + ActionZ    xD = cosinus * RIGHT_SIDE  + ActionX    zD = sinus   * RIGHT_SIDE  + ActionZ    AddVertexes    ; now the 2 triangles    AddTraingles      End FunctionFunction AddVertexes()        AddVertex Surf, xA, 0, zA, 0, 0        AddVertex Surf, xB, 0, zB, 0, 0        AddVertex Surf, xC, 0, zC, 0, 0        AddVertex Surf, xD, 0, zD, 0, 0End FunctionFunction AddTraingles()    ;    C--------D    ;    |        |    ;    |        |    ;    |        |    ;    |        |    ;    A--------B    ; vertex-list:    ; 0.  1.  2.  3.        ; A - B - C - D      ; A-D-B:           A   D   B      AddTriangle Surf , 0 , 3 , 1    ; A-C-D:           A   C   D      AddTriangle Surf , 0 , 2 , 3End Function`

Here is the complete code in BlitzMax:

Code (vb) Select
`SuperStrictImport "../../minib3dsf/minib3d.bmx"Graphics3D 800,600,0,32Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street:TMesh = CreateMesh()Global Surf:TSurface   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#Global xA#, zA#    ' Left  street waypointGlobal xB#, zB#    ' Right street waypointGlobal xC#, zC#    ' Left  street waypoint +1Global xD#, zD#    ' Right street waypoint +1StraightRoadUpdateNormals Street'top-view camera:Global Camera:TCamera = CreateCamera()PositionEntity Camera,0,5,0TurnEntity   Camera, 90,0,0WireFrame True Repeat    UpdateWorld    RenderWorld    Flip 1Until AppTerminate()Function StraightRoad()    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)       ' A And B    xA = cosinus * LEFT_SIDE   + ActionX    zA = sinus   * LEFT_SIDE   + ActionZ    xB = cosinus * RIGHT_SIDE  + ActionX    zB = sinus   * RIGHT_SIDE  + ActionZ    ' move ahead    ActionX = ActionX - sinus    ActionZ = ActionZ + cosinus        ' C And D    xC = cosinus * LEFT_SIDE   + ActionX    zC = sinus   * LEFT_SIDE   + ActionZ    xD = cosinus * RIGHT_SIDE  + ActionX    zD = sinus   * RIGHT_SIDE  + ActionZ    AddVertexes    ' now the 2 triangles    AddTraingles      End FunctionFunction AddVertexes()        AddVertex Surf, xA, 0, zA, 0, 0        AddVertex Surf, xB, 0, zB, 0, 0        AddVertex Surf, xC, 0, zC, 0, 0        AddVertex Surf, xD, 0, zD, 0, 0End FunctionFunction AddTraingles()    '    C--------D    '    |        |    '    |        |    '    |        |    '    |        |    '    A--------B    ' vertex-list:    ' 0.  1.  2.  3.        ' A - B - C - D      ' A-D-B:           A   D   B      AddTriangle Surf , 0 , 3 , 1    ' A-C-D:           A   C   D      AddTriangle Surf , 0 , 2 , 3End Function`
...back from Egypt

#### Midimaster

#6
Lesson V: Continously adding street elements

As we now know how we have built a first street element, we will now re-arrange the code towards an endless adding of elements:

Each step adds 2 new waypoints. We now call them B and C, because in the next lesson we will need the letters A and B for something special.
Code (vb) Select
`Global xB#, zB#    ; left  street pointGlobal xC#, zC#    ; right street point`
This has no influence on the calculation. But we need to fix this in all related code lines.
Code (vb) Select
`Function CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)    xB = cosinus * LeftSide   + ActionX    zB = sinus   * LeftSide   + ActionZ    xC = cosinus * RightSide  + ActionX    zC = sinus   * RightSide  + ActionZEnd Function`

Each step this 2 waypoints will be connected with the prior waypoints to get 2 new triangles.
Code (vb) Select
`Function AddTraingles()    ;    .        .    ;    .        .    ;    .        .    ;    B3------C3    ;    |        |    ;    |        |    ;    |        |    ;    B2------C2    ;    |        |    ;    |        |    ;    |        |    ;    |        |    ;    B1------C1    ; vertex-list:    ; 0.   1.   2.   3.   4.   5.   6.   7.       ; B1 - C1 - B2 - C2 - B3 - C3 - B4 - ....    Local LastPoint% = CountVertices(surf)-4    ; B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+0 , LastPoint+3 , LastPoint+1    ; B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+0 , LastPoint+2 , LastPoint+3End Function`

We outsource the calculation of the waypoints in a new function CalculateWayPoints()  to be prepaired for the next lesson. ("no spaghetti code" -> keep functions short!)

Code (vb) Select
`Function CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)xB = cosinus * LeftSide   + ActionXzB = sinus   * LeftSide   + ActionZxC = cosinus * RightSide  + ActionXzC = sinus   * RightSide  + ActionZEnd Function`

To get a more detailed road we shrink the go-ahead-step to 1/4:

Code (vb) Select
`ActionX = ActionX -   sinus/4.0ActionZ = ActionZ + cosinus/4.0    `

Here is the complete code for Blitz3D
Code (vb) Select
`Graphics3D 800,600,32,0Const POINTS%=2Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street = CreateMesh()Global Surf   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#Global xB#, zB#    ; left  street pointGlobal xC#, zC#    ; right street point;top-view camera:Global Camera = CreateCamera()PositionEntity Camera,0,5,0TurnEntity   Camera, 90,0,0WireFrame True Repeat    If KeyHit(31)        StraightRoad        UpdateNormals Street        UpdateWorld    EndIf    RenderWorld    Text 100,100," Arc = " + Arc +"°"    Text 100,120,"Vertices= " + CountVertices(surf)    Text 100, 20,"Hit <S> to step ahead"    Flip 1Until KeyHit(1)Function StraightRoad()    ; adds a straight street element to the road    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)       CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE    AddVertexes    If CountVertices(surf)> POINTS        AddTraingles          EndIf    ActionX = ActionX -   sinus/4.0    ActionZ = ActionZ + cosinus/4.0    End FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)    xB = cosinus * LeftSide   + ActionX    zB = sinus   * LeftSide   + ActionZ    xC = cosinus * RightSide  + ActionX    zC = sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes()    ; 2 points:         x   y  z   u  v         AddVertex Surf, xB, 0, zB, 0, 0        AddVertex Surf, xC, 0, zC, 0, 0End FunctionFunction AddTraingles()    ;    .        .    ;    .        .    ;    .        .    ;    B3------C3    ;    |        |    ;    |        |    ;    |        |    ;    B2------C2    ;    |        |    ;    |        |    ;    |        |    ;    B1------C1    ; vertex-list:    ; 0.   1.   2.   3.   4.   5.   6.   7.       ; B1 - C1 - B2 - C2 - B3 - C3 - B4 - ....    Local LastPoint% = CountVertices(surf)-4    ; B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+0 , LastPoint+3 , LastPoint+1    ; B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+0 , LastPoint+2 , LastPoint+3End Function`

Here is the complete code for BlitzMAX

Code (vb) Select
`SuperStrictImport "../../minib3dsf/minib3d.bmx"Graphics3D 800,600,0,32Const POINTS%=2Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street:TMesh = CreateMesh()Global Surf:TSurface   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#Global xB#, zB#    ' Left  street pointGlobal xC#, zC#    ' Right street point'top-view camera:Global Camera:TCamera = CreateCamera()PositionEntity Camera,0,5,0TurnEntity   Camera, 90,0,0    Print "------------------------"    Print " Arc = " + Arc +"°"    Print "Verteces= " + CountVertices(surf)    Print"Hit <S> to step ahead"WireFrame True Repeat    If KeyHit(KEY_S)        StraightRoad        UpdateNormals Street        UpdateWorld        Print "Vertices= " + CountVertices(surf)        Print"Hit <S> to step ahead"    EndIf    RenderWorld    Flip 1Until AppTerminate()Function StraightRoad()    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)    CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE    AddVertexes    If CountVertices(surf)> POINTS        AddTraingles          EndIf    ActionX = ActionX -   sinus/4.0    ActionZ = ActionZ + cosinus/4.0    End FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)    xB = cosinus * LeftSide   + ActionX    zB = sinus   * LeftSide   + ActionZ    xC = cosinus * RightSide  + ActionX    zC = sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes()        ' 2 points:     x   y  z   u  v        AddVertex Surf, xB, 0, zB, 0, 0        AddVertex Surf, xC, 0, zC, 0, 0End FunctionFunction AddTraingles()    '    .        .    '    .        .    '    .        .    '    B1------C1    '    |        |    '    |        |    '    |        |    '    B1------C1    '    |        |    '    |        |    '    |        |    '    B1------C1    ' vertex-list:    ' 0.   1.   2.   3.   4.   5.   6.   7.       ' B1 - C1 - B2 - C2 - B3 - C3 - B4 - ....    Local LastPoint% = CountVertices(surf)-4    ' B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+0 , LastPoint+3 , LastPoint+1    ' B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+0 , LastPoint+2 , LastPoint+3End Function`

...back from Egypt

#### Midimaster

#7
Lesson VI: Texture and Curves

Today we will finish our Road-Project. We ad the texture and you will learn about U and V parameters

Adding a texture is very easy. We load a picture as "texture" and connect it with the street

Code (vb) Select
`Global TEXTURE = LoadTexture("street.png",9)EntityTexture Street,TEXTUREGlobal Light = CreateLight(2)LightRange Light,1000PositionEntity Light, 10,50,-10`And we need some light to see the surface. A perfect texture would alway be a quadratic image with a edge length of a multiple of 2. So you can create an image with 64, 128, 256, 1024,....pixel

In the surface we have to define how to place the texture. We can move it, stretch it and repeat it. Think about the texture like a skin or a  "stretchable wallpaper" . You can cut out any rectangle of the texture and pin it at the any point of the surface. Here you can stretch it as you like.

The pins are our vertices. Only here we can fix the texture. Therefore we have the U and V parameters. U and V can only run between 0 and 1. 0

`U parameter:0.0 means left edge of the texture  x =   00.5 means middle of the texture     x = 2561.0 means right edge of the texture x = 512V parameter:0.0 means upper edge of the texture  y = 5120.5 means middle of the texture      y = 2561.0 means lower edge of the texture  y =   0`
Some Examples

this is our rectangle:

This shows no texture, because all values are 0:
`A AddVertex Surf, x+0,y,z+0 , 0 , 0B AddVertex Surf, x+1,y,z+0 , 0 , 0C AddVertex Surf, x+0,y,z+1 , 0 , 0D AddVertex Surf, x+1,y,z+1 , 0 , 0`

This shows the complete texture in this rectangle:
`A AddVertex Surf, x+0,y,z+0 , 0 , 0B AddVertex Surf, x+1,y,z+0 , 1 , 0C AddVertex Surf, x+0,y,z+1 , 0 , 1D AddVertex Surf, x+1,y,z+1 , 1 , 1`

This shows the number 6 of our dice:
`A AddVertex Surf, x+0,y,z+0 , 0.25 , 0B AddVertex Surf, x+1,y,z+0 , 0.50 , 0C AddVertex Surf, x+0,y,z+1 , 0.25 , 0.25D AddVertex Surf, x+1,y,z+1 , 0.50 , 0.25`

This shows the numbers 5 6 2 of our dice and will look terrible compressed:
`A AddVertex Surf, x+0,y,z+0 , 0.0  , 0B AddVertex Surf, x+1,y,z+0 , 0.75 , 0C AddVertex Surf, x+0,y,z+1 , 0.0  , 0.25D AddVertex Surf, x+1,y,z+1 , 0.75 , 0.25`

In our road we need the complete texture width so the U is for the left side always 0 and for the ride side always 1

The V runs step by step from 0 to 1 and when it reaches 1 we run backwards down to 0.

This backward run is because we dont want to see a brightness jump when reaching the V=1. you never know, whether your texture has the same color at upper edge like at lower edge. So it is better to mirror the texture, than to restart at 0.

So our V runs like this:
`0.0 - 0.2 - 0.4 - 0-6 - 0.8 - 1.0 - 0.8 - 0.6 - 0.4 - 0.2 - 0.0 - 0.2 - 0.4 - .....`

We place this code in the function AddVertexes(). The variable DistSwap% will run from 0 to 19, and V# will be calculated running from 0 to 1 and then back to 0 in 10 steps:
`Global DistSwap%.....Function AddVertexes()    Local v#    If DistSwap<11        v= DistSwap/10.0    Else        v= (20.0-DistSwap)/10.0        EndIf    DistSwap = (DistSwap+1) Mod 20    ; 2 points:     x   y  z   u  v     AddVertex Surf, xB, 0, zB, 0, v   ;<---- See U and V    AddVertex Surf, xC, 0, zC, 1, v   ;<---- See U and V  End Function`

Banquette

As in real road construction also in the 3D world a baguette is important. It levels out the difference between the street level and the alternating ground altitude. Without banquette we could "look under the street". so we add a banquette to our road. This is our new road profile:

`; PROFILE:;; (left side)                                      (right side);                  B --------Street----------C;                 *                           *; ___Ground______*____________Floor____________*________________________;             A *                               * D  ; `
The waypoints A and D lay below the ground the planes A-B and C-D will prevent the look below the road.

We need 4 waypoints now:

`Global xA#, zA#    ; right banquette pointGlobal xB#, zB#    ; left  street pointGlobal xC#, zC#    ; right street pointGlobal xD#, zD#    ; right banquette point`

And the Y parameter of AddVertex is now used for the deepth of the banquette waypoints:

`        AddVertex Surf, xA,-0.1, zA, 0.0, v        AddVertex Surf, xB,   0, zB, 0.1, v        AddVertex Surf, xC,   0, zC, 0.9, v        AddVertex Surf, xD,-0.1, zD, 1.0, v`
The U parameters show us: The street texture picture starts om the left with a banquette from 0 to 0.1, then the road follows between 0.1 and 0.9 and the right banquette finishs the picture between 0.9 and 1

To have two more waypoints means also to have 4 new triangles to represent the new banquette planes A1-B1-A2-B2 and C1-D1-C2-D2

`Function AddTraingles()    ;    .              .    ;    .              .    ;    .              .    ;    A3-B3------C3-D3    ;    |  |        |  |    ;    |  |        |  |    ;    |  |        |  |    ;    A2-B2------C2-D2    ;    |  |        |  |    ;    |  |        |  |    ;    |  |        |  |    ;    A1-B1------C1-D1    ; vertex-list:    ; 0  1  2  3    4  5  6  7    8  9....      ; A1-B1-C1-D1 - A2-B2-C2-D2 - A3-A4...    Local LastPoint% = CountVertices(surf)-8; left baquette:    ; A1-B2-B1:        A1            B2            B1      AddTriangle Surf , LastPoint+0 , LastPoint+5 , LastPoint+1    ; A1-A2-B2:        A1            A2            B2      AddTriangle Surf , LastPoint+0 , LastPoint+4 , LastPoint+5; road    ; B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+1 , LastPoint+6 , LastPoint+2    ; B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+1 , LastPoint+5 , LastPoint+6;right banquette:    ; C1-D2-D1:        C1            D2            D1      AddTriangle Surf , LastPoint+2 , LastPoint+7 , LastPoint+3    ; C1-C2-D2:        C1            C2            D2      AddTriangle Surf , LastPoint+2 , LastPoint+6 , LastPoint+7End Function`

Ready for today! You need the image street.png from the attachment to run the app!

Here comes the complete code for Blitz3D
`Graphics3D 800,600,32,0Const POINTS%=4Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street = CreateMesh()Global Surf   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#, DistSwap%Global xA#, zA#    ; right banquette pointGlobal xB#, zB#    ; left  street pointGlobal xC#, zC#    ; right street pointGlobal xD#, zD#    ; right banquette pointGlobal TEXTURE = LoadTexture("street.png",9)EntityTexture Street,TEXTURE;top-view camera:Global Camera = CreateCamera()PositionEntity Camera,0,3,-2TurnEntity   Camera, 30,-10,0Global Light = CreateLight(2)LightRange Light,1000PositionEntity Light, 10,50,-10;WireFrame True Repeat    If KeyHit(31)        StraightRoad        UpdateNormals Street        UpdateWorld    EndIf    RenderWorld    Text 100,100," Arc = " + Arc +"°"    Text 100,120,"Vertices= " + CountVertices(surf)    Text 100, 20,"Hit <S> to step ahead"    Flip 1Until KeyHit(1)Function StraightRoad()    ; adds a straight street element to the road    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)       CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE    AddVertexes    If CountVertices(surf)> POINTS        AddTraingles          EndIf    ActionX = ActionX -   sinus/4.0    ActionZ = ActionZ + cosinus/4.0    End FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)    Local banquetL#, banquetR#    If dir=2        banquetL = 1.05        banquetR = 0.95    Else        banquetL = 0.95        banquetR = 1.05    EndIf    xA = banquetL * cosinus * LeftSide   + ActionX    zA = banquetL * sinus   * LeftSide   + ActionZ    xB = cosinus * LeftSide   + ActionX    zB = sinus   * LeftSide   + ActionZ    xC = cosinus * RightSide  + ActionX    zC = sinus   * RightSide  + ActionZ    xD = banquetR *cosinus * RightSide  + ActionX    zD = banquetR *sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes()        Local v#        If DistSwap<11            v= DistSwap/10.0        Else            v= (20.0-DistSwap)/10.0            EndIf        DistSwap = (DistSwap+1) Mod 20    ; 4 points:         x    y   z    u   v         AddVertex Surf, xA,-0.1, zA, 0.0, v        AddVertex Surf, xB,   0, zB, 0.1, v        AddVertex Surf, xC,   0, zC, 0.9, v        AddVertex Surf, xD,-0.1, zD, 1.0, vEnd FunctionFunction AddTraingles()    ;    .              .    ;    .              .    ;    .              .    ;    A3-B3------C3-D3    ;    |  |        |  |    ;    |  |        |  |    ;    |  |        |  |    ;    A2-B2------C2-D2    ;    |  |        |  |    ;    |  |        |  |    ;    |  |        |  |    ;    A1-B1------C1-D1    ; vertex-list:    ; 0  1  2  3    4  5  6  7    8  9....      ; A1-B1-C1-D1 - A2-B2-C2-D2 - A3-A4...    Local LastPoint% = CountVertices(surf)-8; left baquette:    ; A1-B2-B1:        A1            B2            B1      AddTriangle Surf , LastPoint+0 , LastPoint+5 , LastPoint+1    ; A1-A2-B2:        A1            A2            B2      AddTriangle Surf , LastPoint+0 , LastPoint+4 , LastPoint+5; road    ; B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+1 , LastPoint+6 , LastPoint+2    ; B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+1 , LastPoint+5 , LastPoint+6;right banquette:    ; C1-D2-D1:        C1            D2            D1      AddTriangle Surf , LastPoint+2 , LastPoint+7 , LastPoint+3    ; C1-C2-D2:        C1            C2            D2      AddTriangle Surf , LastPoint+2 , LastPoint+6 , LastPoint+7End Function`

And here is the code for BlitzMax:
`SuperStrictImport "../../minib3dsf/minib3d.bmx"Graphics3D 800,600,0,32Const POINTS%=4Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street:TMesh = CreateMesh()Global Surf:TSurface   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#, DistSwap%Global xA#, zA#    ' Right banquette pointGlobal xB#, zB#    ' Left  street pointGlobal xC#, zC#    ' Right street pointGlobal xD#, zD#    ' Right banquette pointGlobal TEXTURE:TTexture = LoadTexture("street.png",9)EntityTexture Street,TEXTURE'top-view camera:Global Camera:TCamera = CreateCamera()PositionEntity Camera,0,3,-2TurnEntity   Camera, 30,-10,0    Print "------------------------"    Print " Arc = " + Arc +"°"    Print "Verteces= " + CountVertices(surf)    Print"Hit <S> to step ahead"Global Light:TLight = CreateLight(2)LightRange Light,1000PositionEntity Light, 10,50,-10'WireFrame True Repeat    If KeyHit(KEY_S)        StraightRoad        UpdateNormals Street        UpdateWorld        Print "Vertices= " + CountVertices(surf)        Print"Hit <S> to step ahead"    EndIf    RenderWorld    Flip 1Until AppTerminate()Function StraightRoad()    Local sinus#   = Sin(Arc)    Local cosinus# = Cos(Arc)    CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE    AddVertexes    If CountVertices(surf)> POINTS        AddTraingles          EndIf    ActionX = ActionX -   sinus/4.0    ActionZ = ActionZ + cosinus/4.0    End FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#)    Local banquetL#, banquetR#    banquetL = 0.95    banquetR = 1.05    xA = banquetL * cosinus * LeftSide   + ActionX    zA = banquetL * sinus   * LeftSide   + ActionZ    xB = cosinus * LeftSide   + ActionX    zB = sinus   * LeftSide   + ActionZ    xC = cosinus * RightSide  + ActionX    zC = sinus   * RightSide  + ActionZ    xD = banquetR *cosinus * RightSide  + ActionX    zD = banquetR *sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes()        Local v#        If DistSwap<11            v= DistSwap/10.0        Else            v= (20.0-DistSwap)/10.0            EndIf        DistSwap = (DistSwap+1) Mod 20    ' 4 points:         x    y   z    u   v         AddVertex Surf, xA,-0.1, zA, 0.0, v        AddVertex Surf, xB,   0, zB, 0.1, v        AddVertex Surf, xC,   0, zC, 0.9, v        AddVertex Surf, xD,-0.1, zD, 1.0, vEnd FunctionFunction AddTraingles()    '    .              .    '    .              .    '    .              .    '    A3-B3------C3-D3    '    |  |        |  |    '    |  |        |  |    '    |  |        |  |    '    A2-B2------C2-D2    '    |  |        |  |    '    |  |        |  |    '    |  |        |  |    '    A1-B1------C1-D1    ' vertex-list:    ' 0  1  2  3    4  5  6  7    8  9....      ' A1-B1-C1-D1 - A2-B2-C2-D2 - A3-A4...    Local LastPoint% = CountVertices(surf)-8' Left baquette:    ' A1-B2-B1:        A1            B2            B1      AddTriangle Surf , LastPoint+0 , LastPoint+5 , LastPoint+1    ' A1-A2-B2:        A1            A2            B2      AddTriangle Surf , LastPoint+0 , LastPoint+4 , LastPoint+5' road    ' B1-C2-C1:        B1            C2            C1      AddTriangle Surf , LastPoint+1 , LastPoint+6 , LastPoint+2    ' B1-B2-C2:        B1            B2            C2      AddTriangle Surf , LastPoint+1 , LastPoint+5 , LastPoint+6'Right banquette:    ' C1-D2-D1:        C1            D2            D1      AddTriangle Surf , LastPoint+2 , LastPoint+7 , LastPoint+3    ' C1-C2-D2:        C1            C2            D2      AddTriangle Surf , LastPoint+2 , LastPoint+6 , LastPoint+7End Function`
...back from Egypt

#### Midimaster

Lesson VII: Curves And Long Sequences

Code (vb) Select
`..StraightRoad 10UpdateNormals Street...Function StraightRoad(Steps%) ; adds steps% straight street elements to the road Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc)    For i%= 0 To Steps-1 CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE AddVertexes AddTraingles  ActionX = ActionX -   sinus/4.0 ActionZ = ActionZ + cosinus/4.0 NextEnd Function`

The Left Curve

In the curved version the sinus calculation needs to be inside the For/Next-loop. And we add 5° to the Arc each loop:

`Function CurvedRoad(Steps%) ; adds steps% straight street elements to the road    For i%= 0 To Steps-1 Arc=Arc+5 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE AddVertexes AddTraingles  NextEnd Function`As the action point does not change during turns we need not to add something to it

Right Curve

This is more complicate. We need to position the action point on the right side of the road, then add 180° to the Arc. Before we leave the function we need to reset these both changes.

As a second thing we have to think about the fact, that right road edge is now nearer to the action point than the left edge. We react by simply swap the distance constants LEFT_SIDE and RIGHT_SIDE in the calculation-call:

`; for left turns: CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE...; for right turns: CalculateWayPoints cosinus, sinus, RIGHT_SIDE, LEFT_SIDE`

The same happens in the banquette calculation

`Function CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#) Local banquetL#=0.95 Local banquetR#=1.05 If LeftSide>RightSide banquetL = 1.035 banquetR = 0.915 EndIf ....`
Don't ask me, why the right banquette needs different values. I adjusted it because of the better look. maybe the use of FLOATs leads to calculation inaccuracies.

The complete right curve:
`Function RightCurve(Steps%) ; adds steps% right turn street elements to the road ActionX= ActionX + Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ + Sin(Arc) * (2*RADIUS+1) Arc=Arc+180    For i%= 0 To Steps-1 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) CalculateWayPoints cosinus, sinus, RIGHT_SIDE, LEFT_SIDE AddVertexes AddTraingles  Arc = Arc -5 Next Arc=Arc-180 ActionX= ActionX - Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ - Sin(Arc) * (2*RADIUS+1)End Function`

Now you can write scrips like this to get a complete racing course:

`StraightRoad 20RightCurve 10StraightRoad 30LeftCurve 9LeftCurve 9StraightRoad 10RightCurve 10RightCurve 10LeftCurve 43StraightRoad 80LeftCurve 18RightCurve 36`

here is the complete code in Blitz3D

`Graphics3D 800,600,32,0Const POINTS%=4Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street = CreateMesh()Global Surf   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#, DistSwap%Global xA#, zA# ; right banquette pointGlobal xB#, zB# ; left  street pointGlobal xC#, zC# ; right street pointGlobal xD#, zD# ; right banquette pointGlobal TEXTURE = LoadTexture("street.png",9)EntityTexture Street,TEXTURE;' 3D-view camera:Global Camera = CreateCamera()PositionEntity Camera,0,6,-5RotateEntity   Camera, 30,-10,0; top-view camera If you like:;PositionEntity Camera,0,5,5;rotateEntity   Camera, 90,0,0Global Light = CreateLight(2)LightRange Light,1000PositionEntity Light, 10,50,-10StraightRoad 20RightCurve 10StraightRoad 30LeftCurve 9LeftCurve 9StraightRoad 10RightCurve 10RightCurve 10LeftCurve 43StraightRoad 80LeftCurve 18RightCurve 36UpdateNormals Street; two balls To show the action points:kugel= CreateSphere()ScaleEntity Kugel,0.1,0.1,0.1kugel2= CreateSphere()ScaleEntity Kugel2,0.1,0.1,0.1EntityColor kugel2, 2550,0,0;WireFrame True Repeat UpdateWorld PositionEntity kugel, ActionX,0, ActionZ PositionEntity kugel2, ActionX + Cos(Arc) * (2*RADIUS+1)  , 0, ActionZ+ Sin(Arc) * (2*RADIUS+1) RenderWorld Flip 1Until KeyHit(1)Function StraightRoad(Steps%) ; adds steps% straight street elements to the road Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc)    For i%= 0 To Steps-1 CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE AddVertexes AddTraingles  ActionX = ActionX -   sinus/4.0 ActionZ = ActionZ + cosinus/4.0 NextEnd FunctionFunction RightCurve(Steps%) ; adds steps% right turn street elements to the road ActionX= ActionX + Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ + Sin(Arc) * (2*RADIUS+1) Arc=Arc+180    For i%= 0 To Steps-1 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) CalculateWayPoints cosinus, sinus, RIGHT_SIDE, LEFT_SIDE AddVertexes AddTraingles  Arc = Arc -5 Next Arc=Arc-180 ActionX= ActionX - Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ - Sin(Arc) * (2*RADIUS+1)End FunctionFunction LeftCurve(Steps%) ; adds steps% left turn street elements to the road    For i%= 0 To Steps-1 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE AddVertexes AddTraingles  Arc = Arc + 5 NextEnd FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#) Local banquetL#=0.95 Local banquetR#=1.05 If LeftSide>RightSide banquetL = 1.035 banquetR = 0.915 EndIf xA = banquetL * cosinus * LeftSide   + ActionX zA = banquetL * sinus   * LeftSide   + ActionZ xB = cosinus * LeftSide   + ActionX zB = sinus   * LeftSide   + ActionZ xC = cosinus * RightSide  + ActionX zC = sinus   * RightSide  + ActionZ xD = banquetR *cosinus * RightSide  + ActionX zD = banquetR *sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes() Local v# If DistSwap<11 v= DistSwap/10.0 Else v= (20.0-DistSwap)/10.0 EndIf DistSwap = (DistSwap+1) Mod 20 ; 4 points:         x    y   z    u   v AddVertex Surf, xA,-0.1, zA, 0.0, v AddVertex Surf, xB,   0, zB, 0.1, v AddVertex Surf, xC,   0, zC, 0.9, v AddVertex Surf, xD,-0.1, zD, 1.0, vEnd FunctionFunction AddTraingles() If CountVertices(surf)<= POINTS Return ;    .              . ;    .              . ;    .              . ;    A3-B3------C3-D3 ;    |  |        |  | ;    |  |        |  | ;    |  |        |  | ;    A2-B2------C2-D2 ;    |  |        |  | ;    |  |        |  | ;    |  |        |  | ;    A1-B1------C1-D1 ; vertex-list: ; 0  1  2  3    4  5  6  7    8  9....  ; A1-B1-C1-D1 - A2-B2-C2-D2 - A3-A4... Local LastPoint% = CountVertices(surf)-2*POINTS; left baquette: ; A1-B2-B1:        A1            B2            B1  AddTriangle Surf , LastPoint+0 , LastPoint+5 , LastPoint+1 ; A1-A2-B2:        A1            A2            B2  AddTriangle Surf , LastPoint+0 , LastPoint+4 , LastPoint+5; road ; B1-C2-C1:        B1            C2            C1  AddTriangle Surf , LastPoint+1 , LastPoint+6 , LastPoint+2 ; B1-B2-C2:        B1            B2            C2  AddTriangle Surf , LastPoint+1 , LastPoint+5 , LastPoint+6;right banquette: ; C1-D2-D1:        C1            D2            D1  AddTriangle Surf , LastPoint+2 , LastPoint+7 , LastPoint+3 ; C1-C2-D2:        C1            C2            D2  AddTriangle Surf , LastPoint+2 , LastPoint+6 , LastPoint+7End Function`

And here is the complete code in BlitzMax.

`SuperStrictImport "../../minib3dsf/minib3d.bmx"Graphics3D 800,600,0,32Const POINTS%=4Const RADIUS#=2Const LEFT_SIDE#=RADIUS , RIGHT_SIDE#=RADIUS+1Global Street:TMesh = CreateMesh()Global Surf:TSurface   = CreateSurface(Street)Global Arc%, ActionX# , ActionZ#, DistSwap%Global xA#, zA# ' Right banquette pointGlobal xB#, zB# ' Left  street pointGlobal xC#, zC# ' Right street pointGlobal xD#, zD# ' Right banquette pointGlobal TEXTURE:TTexture = LoadTexture("street.png",9)EntityTexture Street,TEXTURE' 3D-view camera:Global Camera:TCamera = CreateCamera()PositionEntity Camera,0,6,-5RotateEntity   Camera, 30,-10,0' top-view camera if you like:'PositionEntity Camera,0,5,5'RotateEntity   Camera, 90,0,0Global Light:TLight = CreateLight(2)LightRange Light,1000PositionEntity Light, 10,50,-10StraightRoad 20RightCurve 10StraightRoad 30LeftCurve 9LeftCurve 9StraightRoad 10RightCurve 10RightCurve 10LeftCurve 43StraightRoad 80LeftCurve 18RightCurve 36UpdateNormals Street' two balls to show the action points:Global kugel:Tentity = CreateSphere()ScaleEntity Kugel,0.1,0.1,0.1Global kugel2:TEntity = CreateSphere()ScaleEntity Kugel2,0.1,0.1,0.1EntityColor kugel2, 2550,0,0'WireFrame True Repeat UpdateWorld PositionEntity kugel, ActionX,0, ActionZ PositionEntity kugel2, ActionX + Cos(Arc) * (2*RADIUS+1)  , 0, ActionZ+ Sin(Arc) * (2*RADIUS+1) RenderWorld Flip 1Until AppTerminate()Function StraightRoad(Steps%) ' adds steps% straight street elements To the road Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) For Local i%= 0 Until Steps CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE AddVertexes If CountVertices(surf)> POINTS AddTraingles  EndIf ActionX = ActionX -   sinus/4.0 ActionZ = ActionZ + cosinus/4.0 Next End FunctionFunction LeftCurve(Steps%) ' adds steps% Left turn street elements To the road    For Local i%= 0 To Steps-1 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) CalculateWayPoints cosinus, sinus, LEFT_SIDE, RIGHT_SIDE Arc = Arc + 5 AddVertexes If CountVertices(surf)> POINTS AddTraingles  EndIf NextEnd FunctionFunction RightCurve(Steps%) ' adds steps% Right turn street elements To the road ActionX= ActionX + Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ + Sin(Arc) * (2*RADIUS+1) Arc=Arc+180    For Local i%= 0 To Steps-1 Local sinus#   = Sin(Arc) Local cosinus# = Cos(Arc) Arc = Arc -5 CalculateWayPoints cosinus, sinus, RIGHT_SIDE, LEFT_SIDE AddVertexes If CountVertices(surf)> POINTS AddTraingles  EndIf Next Arc=Arc-180 ActionX= ActionX - Cos(Arc) * (2*RADIUS+1) ActionZ= ActionZ - Sin(Arc) * (2*RADIUS+1)End FunctionFunction CalculateWayPoints(cosinus#, sinus#, LeftSide#, RightSide#) Local banquetL#=0.95 Local banquetR#=1.05 If LeftSide>RightSide banquetL = 1.035 banquetR = 0.915 EndIf xA = banquetL * cosinus * LeftSide   + ActionX zA = banquetL * sinus   * LeftSide   + ActionZ xB = cosinus * LeftSide   + ActionX zB = sinus   * LeftSide   + ActionZ xC = cosinus * RightSide  + ActionX zC = sinus   * RightSide  + ActionZ xD = banquetR *cosinus * RightSide  + ActionX zD = banquetR *sinus   * RightSide  + ActionZEnd FunctionFunction AddVertexes() Local v# If DistSwap<11 v= DistSwap/10.0 Else v= (20.0-DistSwap)/10.0 EndIf DistSwap = (DistSwap+1) Mod 20 ' 4 points:         x    y   z    u   v AddVertex Surf, xA,-0.1, zA, 0.0, v AddVertex Surf, xB,   0, zB, 0.1, v AddVertex Surf, xC,   0, zC, 0.9, v AddVertex Surf, xD,-0.1, zD, 1.0, vEnd FunctionFunction AddTraingles() If CountVertices(surf)<= POINTS Return '    .              . '    .              . '    .              . '    A3-B3------C3-D3 '    |  |        |  | '    |  |        |  | '    |  |        |  | '    A2-B2------C2-D2 '    |  |        |  | '    |  |        |  | '    |  |        |  | '    A1-B1------C1-D1 ' vertex-list: ' 0  1  2  3    4  5  6  7    8  9....  ' A1-B1-C1-D1 - A2-B2-C2-D2 - A3-A4... Local LastPoint% = CountVertices(surf)-8' Left baquette: ' A1-B2-B1:        A1            B2            B1  AddTriangle Surf , LastPoint+0 , LastPoint+5 , LastPoint+1 ' A1-A2-B2:        A1            A2            B2  AddTriangle Surf , LastPoint+0 , LastPoint+4 , LastPoint+5' road ' B1-C2-C1:        B1            C2            C1  AddTriangle Surf , LastPoint+1 , LastPoint+6 , LastPoint+2 ' B1-B2-C2:        B1            B2            C2  AddTriangle Surf , LastPoint+1 , LastPoint+5 , LastPoint+6'Right banquette: ' C1-D2-D1:        C1            D2            D1  AddTriangle Surf , LastPoint+2 , LastPoint+7 , LastPoint+3 ' C1-C2-D2:        C1            C2            D2  AddTriangle Surf , LastPoint+2 , LastPoint+6 , LastPoint+7End Function`
...back from Egypt