Immediate
Mode: Loading Objects
By: Jack Hoxley (With initial help from 'Corre')
Written: November 2000
Download: IM_ASEObjects.Zip (12Kb)
Anyone
who makes their game in full 3D will require objects to populate the world,
they are as essential to 3D as sprites are to 2D. But unfortunately they aren't
as simple as you might think. The main problem is that you'll need compatability
with a common 3D package (3D Studio Max or Lightwave) and probably a 3D modeller/Artist
- unless you're very good anyway. There is support in DirectX for X-Files (a
Microsoft format), but they have some nasty limitations.
- It's
difficult to get them to work with textures
- It's
not very fast, as it uses retained mode to load/process them initilially.
- They
only work with D3DVERTEX - not D3DLVERTEX; unless you play around with them....
So
the only option is to make your own format, or decipher an existing one.
This
tutorial will show you how to read, compile and load a 3D scene from an ASE
file; this is featured in the 3D Studio Max series, as well as several other
major programs. The reason I chose this format is that it's extremely simple
to understand; you can open it up in notepad and read it! By the end of this
tutorial you'll have made a class module that will work all the time, every
time - a class module that you need only import into a new project and it's
ready to use...
This
tutorial is divided into the following sections:
- Understanding
the ASE format
- Reading
it into a program and processing it
- Saving
it from our compiler
- Loading
it into a class
- Manipulating
the object
- Rendering
the object
- Using
the Class
1:
Understanding the ASE format.
If you just look at this simple ASE file:
*3DSMAX_ASCIIEXPORT
200
*COMMENT
"AsciiExport
Version
2.00 -
Thu
Nov 09
21:39:57
2000"
*SCENE
{
*SCENE_FILENAME
""
*SCENE_FIRSTFRAME
0
*SCENE_LASTFRAME
100
*SCENE_FRAMESPEED
30
*SCENE_TICKSPERFRAME
160
*SCENE_BACKGROUND_STATIC
0.0000
0.0000
0.0000
*SCENE_AMBIENT_STATIC
0.0431
0.0431
0.0431
}
*MATERIAL_LIST
{
*MATERIAL_COUNT
1
*MATERIAL
0 {
*MATERIAL_NAME
"Material
#1"
*MATERIAL_CLASS
"Standard"
*MATERIAL_AMBIENT
0.1791
0.0654
0.0654
*MATERIAL_DIFFUSE
0.5373
0.1961
0.1961
*MATERIAL_SPECULAR
0.9000
0.9000
0.9000
*MATERIAL_SHINE
0.2500
*MATERIAL_SHINESTRENGTH
0.0500
*MATERIAL_TRANSPARENCY
0.0000
*MATERIAL_WIRESIZE
1.0000
*MATERIAL_SHADING
Blinn
*MATERIAL_XP_FALLOFF
0.0000
*MATERIAL_SELFILLUM
0.0000
*MATERIAL_FALLOFF
In
*MATERIAL_SOFTEN
*MATERIAL_XP_TYPE
Filter
*MAP_DIFFUSE
{
*MAP_NAME
"Map
#2"
*MAP_CLASS
"Bitmap"
*MAP_SUBNO
1
*MAP_AMOUNT
1.0000
*BITMAP
"C:\GRAPHICS\3DSMAX2\MAPS\AGRAGATE.JPG"
*MAP_TYPE
Spherical
*UVW_U_OFFSET
0.0000
*UVW_V_OFFSET
0.0000
*UVW_U_TILING
1.0000
*UVW_V_TILING
1.0000
*UVW_ANGLE
0.0000
*UVW_BLUR
1.0000
*UVW_BLUR_OFFSET
0.0000
*UVW_NOUSE_AMT
1.0000
*UVW_NOISE_SIZE
1.0000
*UVW_NOISE_LEVEL
1
*UVW_NOISE_PHASE
0.0000
*BITMAP_FILTER
Pyramidal
}
}
}
*GEOMOBJECT
{
*NODE_NAME
"Box01"
*NODE_TM
{
*NODE_NAME
"Box01"
*INHERIT_POS
0 0 0
*INHERIT_ROT
0 0 0
*INHERIT_SCL
0 0 0
*TM_ROW0
1.0000
0.0000
0.0000
*TM_ROW1
0.0000
1.0000
0.0000
*TM_ROW2
0.0000
0.0000
1.0000
*TM_ROW3
0.0000
0.0000
-5.0000
*TM_POS
0.0000
0.0000
-5.0000
*TM_ROTAXIS
0.0000
0.0000
0.0000
*TM_ROTANGLE
0.0000
*TM_SCALE
1.0000
1.0000
1.0000
*TM_SCALEAXIS
0.0000
0.0000
0.0000
*TM_SCALEAXISANG
0.0000
}
*MESH
{
*TIMEVALUE
0
*MESH_NUMVERTEX
8
*MESH_NUMFACES
12
*MESH_VERTEX_LIST
{
*MESH_VERTEX
0
-5.0000
-5.0000
-5.0000
*MESH_VERTEX
1
5.0000
-5.0000
-5.0000
*MESH_VERTEX
2
-5.0000
5.0000
-5.0000
*MESH_VERTEX
3
5.0000
5.0000
-5.0000
*MESH_VERTEX
4
-5.0000
-5.0000
5.0000
*MESH_VERTEX
5
5.0000
-5.0000
5.0000
*MESH_VERTEX
6
-5.0000
5.0000
5.0000
*MESH_VERTEX
7
5.0000
5.0000
5.0000
}
*MESH_FACE_LIST
{
*MESH_FACE
0:
A:
0
B:
2
C:
3
AB:
1
BC:
1
CA:
0
*MESH_FACE
1:
A:
3
B:
1
C:
0
AB:
1
BC:
1
CA:
0
*MESH_FACE
2:
A:
4
B:
5
C:
7
AB:
1
BC:
1
CA:
0
*MESH_FACE
3:
A:
7
B:
6
C:
4
AB:
1
BC:
1
CA:
0
*MESH_FACE
4:
A:
0
B:
1
C:
5
AB:
1
BC:
1
CA:
0
*MESH_FACE
5:
A:
5
B:
4
C:
0
AB:
1
BC:
1
CA:
0
*MESH_FACE
6:
A:
1
B:
3
C:
7
AB:
1
BC:
1
CA:
0
*MESH_FACE
7:
A:
7
B:
5
C:
1
AB:
1
BC:
1
CA:
0
*MESH_FACE
8:
A:
3
B:
2
C:
6
AB:
1
BC:
1
CA:
0
*MESH_FACE
9:
A:
6
B:
7
C:
3
AB:
1
BC:
1
CA:
0
*MESH_FACE
10:
A:
2
B:
0
C:
4
AB:
1
BC:
1
CA:
0
*MESH_FACE
11:
A:
4
B:
6
C:
2
AB:
1
BC:
1
CA:
0
}
*MESH_NUMTVERTEX
12
*MESH_TVERTLIST
{
*MESH_TVERT
0
0.0000
0.0000
0.0000
*MESH_TVERT
1
1.0000
0.0000
0.0000
*MESH_TVERT
2
0.0000
1.0000
0.0000
*MESH_TVERT
3
1.0000
1.0000
0.0000
*MESH_TVERT
4
0.0000
0.0000
0.0000
*MESH_TVERT
5
1.0000
0.0000
0.0000
*MESH_TVERT
6
0.0000
1.0000
0.0000
*MESH_TVERT
7
1.0000
1.0000
0.0000
*MESH_TVERT
8
0.0000
0.0000
0.0000
*MESH_TVERT
9
1.0000
0.0000
0.0000
*MESH_TVERT
10
0.0000
1.0000
0.0000
*MESH_TVERT
11
1.0000
1.0000
0.0000
}
*MESH_NUMTVFACES
12
*MESH_TFACELIST
{
*MESH_TFACE
0 9 11
10
*MESH_TFACE
1 10 8
9
*MESH_TFACE
2 8 9
11
*MESH_TFACE
3 11
10 8
*MESH_TFACE
4 4 5
7
*MESH_TFACE
5 7 6
4
*MESH_TFACE
6 0 1
3
*MESH_TFACE
7 3 2
0
*MESH_TFACE
8 4 5
7
*MESH_TFACE
9 7 6
4
*MESH_TFACE
10 0 1
3
*MESH_TFACE
11 3 2
0
}
}
*PROP_MOTIONBLUR
0
*PROP_CASTSHADOW
1
*PROP_RECVSHADOW
1
*MATERIAL_REF
0
} |
|
|
|
This
was created, as you can see, by 3D Studio Max 2. If you have this program to
create an ASE file you go File=>Export and select ASE file; then choose the
following options : Mesh Definition, Materials, Mapping Coordinates, Geometric
(under object type).
Now
we have this data we can manually go through it and remove what we dont need.
Depending on what your engine is capable of you may want different bits of information;
but in general you can cut it all back to just the mesh definition (with texture
coords) and the texture information. Like so:
MATERIAL_COUNT 1 //How many materials were used in the scene - materials
in this instance are textures
MATERIAL 0 { //there'll be more of these depending on how many were used
BITMAP "C:\GRAPHICS\3DSMAX2\MAPS\AGRAGATE.JPG" //the file name - this is
the only part of the material definition we kept
}
}
GEOMOBJECT { //Beginning of a new object - a useful reference point for
when we have multiple objects
MESH_NUMVERTEX 8 //How many vertices
MESH_NUMFACES 12 //How many faces
MESH_VERTEX_LIST { //this lists the number of vertices in this object
MESH_VERTEX 0 -5.0000 -5.0000 -5.0000
MESH_VERTEX 1 5.0000 -5.0000 -5.0000
MESH_VERTEX 2 -5.0000 5.0000 -5.0000
MESH_VERTEX 3 5.0000 5.0000 -5.0000
MESH_VERTEX 4 -5.0000 -5.0000 5.0000
MESH_VERTEX 5 5.0000 -5.0000 5.0000
MESH_VERTEX 6 -5.0000 5.0000 5.0000
MESH_VERTEX 7 5.0000 5.0000 5.0000
}
MESH_FACE_LIST { //A face is defined as a triangle - of 3 vertices
MESH_FACE 0: A: 0 B: 2 C: 3 //Face 0 is made up of the points 023 which
are given in the list above.
MESH_FACE 1: A: 3 B: 1 C: 0
MESH_FACE 2: A: 4 B: 5 C: 7
MESH_FACE 3: A: 7 B: 6 C: 4
MESH_FACE 4: A: 0 B: 1 C: 5
MESH_FACE 5: A: 5 B: 4 C: 0
MESH_FACE 6: A: 1 B: 3 C: 7
MESH_FACE 7: A: 7 B: 5 C: 1
MESH_FACE 8: A: 3 B: 2 C: 6
MESH_FACE 9: A: 6 B: 7 C: 3
MESH_FACE 10: A: 2 B: 0 C: 4
MESH_FACE 11: A: 4 B: 6 C: 2
}
MESH_NUMTVERTEX 12 //This rarely equals the number of vertices.
MESH_TVERTLIST {
MESH_TVERT 0 0.0000 0.0000 //You may have noticed that the original had
three values
MESH_TVERT 1 1.0000 0.0000 //these were UVW coordinates; D3D doesn't use
W coordinates, so we can ignore them.
MESH_TVERT 2 0.0000 1.0000
MESH_TVERT 3 1.0000 1.0000
MESH_TVERT 4 0.0000 0.0000
MESH_TVERT 5 1.0000 0.0000
MESH_TVERT 6 0.0000 1.0000
MESH_TVERT 7 1.0000 1.0000
MESH_TVERT 8 0.0000 0.0000
MESH_TVERT 9 1.0000 0.0000
MESH_TVERT 10 0.0000 1.0000
MESH_TVERT 11 1.0000 1.0000
}
MESH_NUMTVFACES 12 //This should equal the number of faces in the model
- if not we're a little stuck.
MESH_TFACE 0 9 11 10 //This is the same as the face list earlier - face
0 has the texture coordinates of vertex 9,11,10
MESH_TFACE 1 10 8 9
MESH_TFACE 2 8 9 11
MESH_TFACE 3 11 10 8
MESH_TFACE 4 4 5 7
MESH_TFACE 5 7 6 4
MESH_TFACE 6 0 1 3
MESH_TFACE 7 3 2 0
MESH_TFACE 8 4 5 7
MESH_TFACE 9 7 6 4
MESH_TFACE 10 0 1 3
MESH_TFACE 11 3 2 0
}
}
MATERIAL_REF 0 //References the list at the beginning of the file
} |
|
|
|
That's
quite a lot of information we've cut out now. The only parts that I've cut out
that are likely to be of any interest to you are the material definitions. I've
also add ed some comments about what the information means. I've also dropped
the formatting - as we dont really need the Tab indents.
Now
we have the data in simple form we can just parse it into arrays and then store
it in our own file format for later use.
2:
Reading it into a program and processing it
We're now going to design our first class. This class will take a raw ASE file
and output a custom format 3D file with only the information that we require.
There are various methods you can use to process the data - usually a variation
of "All in one go" or in several passes. I prefer to do it with several
passes. First we read the data in, then we remove any junk that we dont like,
then we compile it into information we need - in arrays, then we store it in
a file.
First
I'll list which functions we'll be creating and their prototypes; then we'll
see what they do.
Private Function CompileRetrievedData() As Boolean //This saves the data to a file that we can understand Private Function DumpToFile(Filename As String) As Boolean //Creates an intermediate file //These 5 functions take the raw ASE data and alter it so it only contains stuff we need... Private Function FormatBITMAPline(Line As String) As String Private Function FormatFACEline(Line As String) As String Private Function FormatTCOORDline(Line As String) As String Private Function FormatTFACEline(Line As String) As String Private Function FormatVERTEXline(Line As String) As String //These next 4 functions retrieve information from formatted lines and reads them into arrays Private Function GetASEFaceDataFromLine(Line As String, ObjNum As Integer) As tASEFace Private Function GetASETCoordDataFromLine(Line As String) As tASETexCoord Private Function GetASETFaceDataFromLine(Line As String, ObjNum As Integer) As tASEFace Private Function GetASEVertexFromLine(Line As String) As tASEVertex //This next function starts the whole thing rolling... Public Function LOAD(ASEfilename As String) As Boolean //This just appends information to a log that you can specify.. Private Sub LogText(Str As String) //This is the first step - it reads the data into an array of lines Private Function ReadDataIntoArray(Filename As String) As Boolean //This is one of the biggest sections - removing stuff we decide isn't useful... Private Function RemoveUnwantedData() As Boolean //This is done after we remove the junk, and at the end of it our arrays will hold all the info we might need. Private Function RetrieveRawData() As Boolean |
|
|
|
One
note on these functions, first you'll notice that all of them are functions,
no "sub"s. Alot of them would need to return things anyway but there
are others that normally wouldn't. All the ones that return a boolean datatype
are functions that need to be checked for errors - if the function returns false
we need to stop before trying to do other things. Consider this function template:
Private Function Something() as Boolean On Error goto ErrHand:
//Do stuff now
Something=True Exit function ErrHand: Something=False End Function |
|
|
|
If
you use this model for all the procedures you normally declare as a "sub"
you can check if it succeeded or failed. We can now use this following code
quite easily:
if Not(SomeFunction()=True) then goto ErrHand: |
|
|
|
If
the function fails we go straight to the parents error handler and cease exececution
- before any other errors occur.
Now
we'll go through each function.
2a.
Declarations.
Before we write any of the functions we will want some declarations; remember
that we're designing a class module, so alot of this will be private (hidden)
from the rest of our project.
'////////////////////////////////////////////////////////////////////////////////////
'////
'////
clsASECompiler
: Used
to
import
ASE
data
from
3DSMax
and
'////
compile
it
into a
format
that
the
game
'////
'The
Man
With
The
Digital
Gun'
can
read
'////
'////
Written
:
November
7th
2000
'////
By :
Jack
Hoxley
'////
'////
Contact:
Jollyjeffers@Greenonions.netscapeonline.co.uk
'////
www.vbexplorer.com/DirectX4vb/
'////
www.CloneSoftware.Cjb.Net
'//////////////////////////////////////////////////////////////////////////////////
'//November
06
2000
'Class
created.
Data
read
from
file
and
uneeded
data
removed
'//November
07
2000
'Data
formated,
read
into
correct
arrays
'//November
08
2000
'Added
support
for
log
files
'Final
Data-Array
copying
finished
'Fixed
bug
with
high-number
vertices.
'Now
collects
Texture
Filenames
and
references
them
against
each
object...
'The
Class
now
exports
a
final
file
listing
all
the
required
information...
'COMPLETE
Option Explicit '//Removes any Aliasing...
Private Declare Function GetTickCount Lib "kernel32" () As Long '//For statistical Timing Purposes..
Public LogBox As TextBox '//Where we stick log entries
Public ExportTo As String '//The destination for the compiled file...
Private ASEData() As
String 'Open ended array for the length of the file...
Private ASEData_NumLines As Long 'How many
lines are in the ASEData() array
'//CORRE's suggestions....
Private Type tASEVertex
X As Single
Y As Single
Z As Single
End Type
Private Type tASETexCoord
u As Single
v As Single
End Type
Private Type tASEFace
p1 As tASEVertex '//Triangle Vertices
p2 As tASEVertex
p3 As tASEVertex
n1 As tASEVertex '//Vertex Normals - Not used.
n2 As tASEVertex
n3 As tASEVertex
t1 As tASETexCoord '//Texture Coordinates
t2 As tASETexCoord
t3 As tASETexCoord
End Type
'//Designed so that num objects and num vertices/faces can be redefined later...
Private Type ASEObject
StartLine As Long 'Which line in memory does this object start @
Texture As String '//What file we want to use...
TextureReference As Integer 'What number it is...
numVertices As Long 'How many vertices we need to deal with
numTVertices As Long 'How many TVertices we have
numFaces As Long 'How many faces we have to deal with...
ASE_Vertices() As tASEVertex '//Temporary arrays
ASE_TVertices() As tASETexCoord
ASE_Faces() As tASEFace '//The final results for the model
End Type
'Only the ASE_Faces() and the Texture are to be exported...
Private NumObjectsInFile As Long '//How many objects do we have to deal with?
Private OBJECT() As ASEObject '//Open ended....
Private numTextureNames As Integer '//How many textures we require...
Private TextureNames() As String '//Open Ended array for the textures used in the model.. |
|
|
|
note
the header at the top - this is actually a class that I designed for use in
my upcoming game. Aren't I nice sharing this with you :)
2b:
LOAD()
This is a very simple function, and uses the above error handling techniques
in order to return a sucess/fail code to it's parent.
Public
Function
LOAD(ASEfilename
As
String)
As
Boolean
'//This is the only publicly visible class...
Dim
ProcessTime
As
Long
ProcessTime
=
GetTickCount() '//For statistics
'//1. Get the data from the file
'This part just reads the data in, line by line, into memory
Call
LogText("Retrieving
ASE
data
from
File
{"
&
ASEfilename
&
"}")
If Not
(ReadDataIntoArray(ASEfilename)
=
True)
Then
GoTo
FATAL:
'//2. Remove junk
'Now we need to remove all the parts that aren't needed.
Call
LogText("Removing
Unwanted
Data
from
File....")
If Not
(RemoveUnwantedData()
=
True)
Then
GoTo
FATAL:
'//2a. Dump the data to a file should you need to look at it
Call
LogText("Dumping
Data
to
file")
If Not
(DumpToFile(ASEfilename
&
"_Intermediate.txt")
=
True)
Then
GoTo
FATAL:
'//3. Retrieve the first set of data
'We now get the information from the formatted data - in the
same format
Call
LogText("Collecting
Information
On
Objects...")
If Not
(RetrieveRawData()
=
True)
Then
GoTo
FATAL:
'//4. Final pass through the data
'We now compile all this stuff into information that we want
to use
Call
LogText("Compiling
and
Saving...")
If Not
(CompileRetrievedData()
=
True)
Then
GoTo
FATAL:
ProcessTime
=
GetTickCount()
-
ProcessTime '//Finalise the statistical information.
Call
LogText("")
Call
LogText("Model
Compiled
Successfully
in
"
&
Format$(ProcessTime
/
1000,
"0.000")
&
"s")
LOAD =
True '//If we're here we have loaded the data successfully
Exit
Function
FATAL:
LOAD =
False '//If we're here we had an error somewhere....
'Might as well explain why:
Call
LogText("")
Call
LogText("ERROR
OCCURED")
Call
LogText("
Number:
"
&
Err.Number)
Call
LogText("
Description:
"
&
Err.Description)
End
Function |
|
|
|
2c.
Retrieving the data from the file.
Before we can start storing the data and processing it we need to get it into
our program. I've decided to load each line into an individual element of an
array. We can then examine each individual line - which usually only contain
one thing.
Private
Function
ReadDataIntoArray(Filename
As
String)
As
Boolean
On
Error
GoTo
ERRHAND:
Dim
FileNum
As
Integer,
Dummy
As
String
FileNum
=
FreeFile '//Get us a file port to use.
Open
Filename
For
Input
As #FileNum
Do
While
Not
EOF(FileNum)
'Increment the number of lines
ASEData_NumLines
=
ASEData_NumLines
+ 1
'resize the array to suit the number of lines
ReDim
Preserve
ASEData(ASEData_NumLines)
As
String
'read the line from the file
Line
Input
#FileNum,
Dummy
'add the new line into the array
ASEData(ASEData_NumLines)
=
Dummy
Loop
Close
#FileNum
Call
LogText("
Number
of
lines
Read:
"
&
ASEData_NumLines) '//This can range from 100 to 8000+...
ReadDataIntoArray
= True
Exit
Function
ERRHAND:
ReadDataIntoArray
=
False
End
Function |
|
|
|
Nothing
greatly complicated there, but what we get is the basis of everything else we're
doing - so it's probably the most important function in the class.
2d.
Removing the Junk.
This is where it gets fun. You can use various forms of data processing/manipulation
but the code shown here works fine... Also bare in mind that this uses several
smaller functions that I'll explain a little later. This function also formats
the data, as well as removing stuff we dont need.
'//This loops through the file removing any data we dont need...
Private Function RemoveUnwantedData() As Boolean
On Local Error Resume Next:
'//Variables
Dim i As
Long 'Main Loops
Dim i2 As
Long 'Loop for clearing the *SCENE header
Dim LinesRemoved As Long
'Statistical information
'//Main Loop
For i = 0 To ASEData_NumLines
'//Strip all TAB chars from the lines
'Maximum of 3 Tabs at the start of a line...
If Left(ASEData(i), 1) = Chr$(9) Then
ASEData(i) = Right(ASEData(i),
Len(ASEData(i)) - 1)
End If
If Left(ASEData(i), 1) = Chr$(9) Then
ASEData(i) = Right(ASEData(i),
Len(ASEData(i)) - 1)
End If
If Left(ASEData(i), 1) = Chr$(9) Then
ASEData(i) = Right(ASEData(i),
Len(ASEData(i)) - 1)
End If
'//First two header lines...
If UCase(Left(ASEData(i), 5)) = "*3DSM" Then '//first line header...
ASEData(i) = "" '//Clear this entry
ElseIf UCase(Left(ASEData(i), 5)) = "*COMM" Then '//Second line comment
ASEData(i) = "" '//Clear this entry
End If
'//Scene Description
If UCase(Left(ASEData(i), 5)) = "*SCEN" Then '//Beginning of a scene block...
For i2 = i To i + 30 '//scan 30 lines ahead for the end of the SCENE block
If UCase(Left(ASEData(i2), 1)) =
"}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDSCENEHEADER: '//about 5 lines below here...
Else '//End hasn't been found yet...
ASEData(i2) =
""
End If
Next i2
FINISHEDSCENEHEADER:
End If
'//GEOMOBJECT HEADERS
'We'll keep the actual GEOMOBJECT parts... Just lose the
rest.
If UCase(Left(ASEData(i), 10)) = "*NODE_NAME" Then ASEData(i) =
""
If UCase(Left(ASEData(i), 8)) = "*NODE_TM" Then '//Beginning of a Node block...
i2 = 0 'variable has already been used, so clear it first
For i2 = i To i + 30 '//scan 30 lines ahead for the end of the Node block
If Left(ASEData(i2), 2) = Chr$(9)
& "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDNODEHEADER: '//about 5 lines below here...
Else '//End hasn't been found yet...
ASEData(i2) =
""
End If
Next i2
FINISHEDNODEHEADER:
End If
If UCase(Left(ASEData(i), 19)) = "*MESH_VERTEX_LIST {" Then '//Beginning of a MESH block...
ASEData(i) = "" '//Remove the "*MESH {" part
i2 = 0 'variable has already been used, so clear it first
For i2 = i To ASEData_NumLines '//Vertex lists can be quite large
If Left(ASEData(i2), 3) = Chr$(9)
& Chr$(9) & "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDVLISTHEADER: '//about 5 lines below here...
End If
Next i2
FINISHEDVLISTHEADER:
End If
If UCase(Left(ASEData(i), 17)) = "*MESH_FACE_LIST {" Then '//Beginning of a MESH block...
ASEData(i) = "" '//Remove the "*MESH_FACE_LIST {" part
i2 = 0 'variable has already been used, so clear it first
For i2 = i To ASEData_NumLines 'Face lists can be enourmous
If Left(ASEData(i2), 3) = Chr$(9)
& Chr$(9) & "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDFACEHEADER: '//about 5 lines below here...
End If
Next i2
FINISHEDFACEHEADER:
End If
If UCase(Left(ASEData(i), 17)) = "*MESH_TVERTLIST {" Then '//Beginning of a TVert block...
ASEData(i) = "" '//Remove the "*MESH_TVERTLIST {" part
i2 = 0 'variable has already been used, so clear it first
For i2 = i To ASEData_NumLines 'TVERT lists can be enourmous
If Left(ASEData(i2), 3) = Chr$(9)
& Chr$(9) & "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDTVERTHEADER: '//about 5 lines below here...
End If
Next i2
FINISHEDTVERTHEADER:
End If
If UCase(Left(ASEData(i), 17)) = "*MESH_TFACELIST {" Then '//Beginning of a TFACE block...
ASEData(i) = "" '//Remove the "*MESH_TFACELIST {" part
i2 = 0 'variable has already been used, so clear it first
For i2 = i To ASEData_NumLines 'TFACE lists can be enourmous
If Left(ASEData(i2), 3) = Chr$(9)
& Chr$(9) & "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDTFACEHEADER: '//about 5 lines below here...
End If
Next i2
FINISHEDTFACEHEADER:
End If
If UCase(Left(ASEData(i), 7)) = "*MESH {" Then '//Beginning of a TFACE block...
ASEData(i) = "" '//Remove the "*MESH {" part
i2 = 0 'variable has already been used, so clear it first
For i2 = i To ASEData_NumLines 'MESH headers can be enourmous
If Left(ASEData(i2), 2) = Chr$(9)
& "}" Then '//End found
ASEData(i2) =
""
GoTo
FINISHEDMESHHEADER: '//about 5 lines below here...
End If
Next i2
FINISHEDMESHHEADER:
End If
'//PROPERTY VALUES
If UCase(Left(ASEData(i), 6)) = "*PROP_" Then ASEData(i) =
"" '//Clear the line should we find it
'//Time indices
If UCase(Left(ASEData(i), 10)) = "*TIMEVALUE" Then ASEData(i) =
"" '//Clear the line should we find it
If UCase(Left(ASEData(i), 4)) = "*UVW" Then ASEData(i) =
"" '//Clear the line should we find it
If UCase(Left(ASEData(i), 13)) = "*MATERIAL_REF" Then ASEData(i) =
"*OBJECT_TEXTURE=" & Right(ASEData(i), Len(ASEData(i)) - 13)
If UCase(Left(ASEData(i), 10)) = "*MATERIAL " Then ASEData(i) =
"*TEXTURE " & Right(ASEData(i), Len(ASEData(i)) - 10)
If UCase(Left(ASEData(i), 10)) = "*MATERIAL_" Then ASEData(i) =
"" '//Clear the line should we find it
If UCase(Left(ASEData(i), 5)) = "*MAP_" Then ASEData(i) =
"" '//Clear the line should we find it
If UCase(Left(ASEData(i), 8)) = "*BITMAP_" Then ASEData(i) =
"" '//Clear the line should we find it
'//Remove things that were, but no longer are needed
If UCase(Left(ASEData(i), 1)) = "}" Then ASEData(i) =
"" '//Clear the line should we find it
If UCase(Left(ASEData(i), 1)) = "*" Then ASEData(i) = Right(ASEData(i),
Len(ASEData(i)) - 1) '//Clear the line should we find it
'//Convert useful lines to shorter versions...
If UCase(Left(ASEData(i), 11)) = "MESH_VERTEX" Then
ASEData(i) = "VERTEX" & Right(ASEData(i),
Len(ASEData(i)) - 11)
ASEData(i) = FormatVERTEXline(ASEData(i))
End If
If UCase(Left(ASEData(i), 9)) = "MESH_FACE" Then
ASEData(i) = "FACE" & Right(ASEData(i),
Len(ASEData(i)) - 9)
ASEData(i) = FormatFACEline(ASEData(i))
End If
If UCase(Left(ASEData(i), 10)) = "MESH_TVERT" Then
ASEData(i) = "TCOORD" & Right(ASEData(i),
Len(ASEData(i)) - 10)
ASEData(i) = FormatTCOORDline(ASEData(i))
End If
If UCase(Left(ASEData(i), 10)) = "MESH_TFACE" Then
ASEData(i) = "TFACE" & Right(ASEData(i),
Len(ASEData(i)) - 10)
ASEData(i) = FormatTFACEline(ASEData(i))
End If
If UCase(Left(ASEData(i), 6)) = "BITMAP" Then
ASEData(i) = FormatBITMAPline(ASEData(i))
End If
If Not (ASEData(i) = "") Then
LinesRemoved = LinesRemoved + 1 '//Statistical information
End If
DoEvents
Next i
Debug.Print "File reduced from " & ASEData_NumLines & "
to " & LinesRemoved & " " _
& Format$(100 - ((LinesRemoved / ASEData_NumLines) * 100),
"0.000") & "% reduction in size"
RemoveUnwantedData = True
Exit Function
ERRHAND:
RemoveUnwantedData = False
End Function |
|
|
|
Again,
not greatly complicated, and a lot of it is very similiar - but repeated. I
mentioned above that we need several "helper" functions for this function
to operate. Here they are:
'//Nothing complicated about this one. Private
Function
FormatBITMAPline(Line
As
String)
As
String
Dim i
As
Integer,
Temp
As
String,
FoundSlashAt
As
Integer
'Remove the Line Title
FormatBITMAPline
=
Right(Line,
Len(Line)
- 7)
'Remove the " " 's
FormatBITMAPline
=
Right(FormatBITMAPline,
Len(FormatBITMAPline)
- 1)
FormatBITMAPline
=
Left(FormatBITMAPline,
Len(FormatBITMAPline)
- 1)
'Now remove everything but the actual file name
For i
=
Len(FormatBITMAPline)
To 1
Step
-1 'In
reverse
order
Temp =
Mid$(FormatBITMAPline,
i, 1)
If
Temp =
"\"
Then
FoundSlashAt
= i:
Exit
For
Next i
FormatBITMAPline
=
Right(FormatBITMAPline,
Len(FormatBITMAPline)
-
FoundSlashAt)
End
Function
'//This is probably the least 'clean' of the lot... but it works. Private
Function
FormatFACEline(Line
As
String)
As
String
'//We only need to remove anything from "AB:" onwards
Dim i
As
Integer,
Temp
As
String,
Compiled
For i
= 1 To
Len(Line)
- 1 '//Run through each character
Temp =
Mid$(Line,
i, 2)
If
Temp =
"AB"
Then
Compiled
=
Left(Line,
i - 1)
End If
Next i
'//The string now only contains information upto what we need...
Compiled
=
Trim(Compiled)
For i
= 1 To
Len(Compiled)
Temp =
Mid$(Compiled,
i, 1)
If
Temp =
"A"
Then
If i
> 4
Then
FormatFACEline
=
FormatFACEline
&
""
Else
FormatFACEline
=
FormatFACEline
&
Temp
ElseIf
Temp =
"B"
Then
If i
> 4
Then
FormatFACEline
=
FormatFACEline
&
""
Else
FormatFACEline
=
FormatFACEline
&
Temp
ElseIf
Temp =
"C"
Then
If i
> 4
Then
FormatFACEline
=
FormatFACEline
&
""
Else
FormatFACEline
=
FormatFACEline
&
Temp
ElseIf
Temp =
":"
Then
If i
> 4
Then
FormatFACEline
=
FormatFACEline
&
""
Else
FormatFACEline
=
FormatFACEline
&
Temp
ElseIf
Temp =
"
"
Then
FormatFACEline
=
FormatFACEline
&
"•"
Else
FormatFACEline
=
FormatFACEline
&
Temp
End If
Next i
FormatFACEline
=
Replace(FormatFACEline,
"••••••••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"•••••••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"••••••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"•••••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"••••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"•••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"••••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"•••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"••",
",")
FormatFACEline
=
Replace(FormatFACEline,
"•",
"")
End
Function
'//A nice simple one next. Private
Function
FormatTCOORDline(Line
As
String)
As
String
'//We need to replace all TABs with comma's....
Dim i
As
Integer,
Temp
As
String
For i
= 1 To
Len(Line)
Temp =
Mid$(Line,
i, 1)
If
Temp =
Chr$(9)
Then
FormatTCOORDline
=
FormatTCOORDline
&
","
Else
FormatTCOORDline
=
FormatTCOORDline
&
Temp
End If
Next i
End
Function
'//Pretty much identical to the above function. Private
Function
FormatTFACEline(Line
As
String)
As
String
'//We need to replace all TABs with comma's....
Dim i
As
Integer,
Temp
As
String
For i
= 1 To
Len(Line)
Temp =
Mid$(Line,
i, 1)
If
Temp =
Chr$(9)
Then
FormatTFACEline
=
FormatTFACEline
&
","
Else
FormatTFACEline
=
FormatTFACEline
&
Temp
End If
Next i
End
Function
'//What a surprise :-) it's the same as the previous two. Private
Function
FormatVERTEXline(Line
As
String)
As
String
'//We need to replace all TABs with comma's....
Dim i
As
Integer,
Temp
As
String
For i
= 1 To
Len(Line)
Temp =
Mid$(Line,
i, 1)
If
Temp =
Chr$(9)
Then
FormatVERTEXline
=
FormatVERTEXline
&
","
Else
FormatVERTEXline
=
FormatVERTEXline
&
Temp
End If
Next i
End
Function |
|
|
|
From
this point we should be able to read all the data in and remove any junk, as
well as formatting it so it's easier to use later on. At this point the program
dumps an intermediate file - not important at all, but sometimes can be useful
if you think it's going wrong.
'//Not greatly difficult...
Private
Function
DumpToFile(Filename
As
String)
As
Boolean
On
Error
GoTo
ERRHAND:
Dim
Free
As
Integer,
i As
Long
Free =
FreeFile
Open
Filename
For
Output
As
#Free
For i
= 0 To
ASEData_NumLines
If
ASEData(i)
<>
""
Then
Print
#Free,
ASEData(i)
End If
Next i
Close
#Free
DumpToFile
= True
Exit
Function
ERRHAND:
DumpToFile
=
False
End
Function |
|
|
|
2e.
This is another very important function - before we execute this we'll have
a list of useful data, at the end of it we'll have several structures that hold
all the information needed to create an object in a Direct3D environment. It's
a rather long function as well; and the majority of the processing time is spent
here.
'//This function will get all the information that we need...
Private Function RetrieveRawData() As Boolean
'On Error GoTo ERRHAND:
'//We want to loop through the file and get all the information on each object
'//Object Starting Positions
'//Texture Coords
'//Vertices
'//Faces
'//Textured Faces
'//Material ID...
'//1. Variables required...
Dim i As Long 'For Main Loop
Dim Ic As Long 'For counting things...
Dim Temp As String 'Temporary data holder
Dim tNumber As Long 'Temporary Number...
Dim TotVerts As Long 'Total Number of Vertices
Dim TotFaces As Long 'Total Number of Faces
Dim tFace As tASEFace 'Temporary Face Object
'//2. Count objects
'First we need to count the number
of objects
'and bookmark their positions in the array...
For i = 0 To ASEData_NumLines
'Search for the string: GEOMOBJECT
If UCase(Left(ASEData(i), 10)) =
"GEOMOBJECT" Then
NumObjectsInFile = NumObjectsInFile + 1
ReDim
Preserve OBJECT(NumObjectsInFile) As ASEObject
OBJECT(NumObjectsInFile).StartLine = i
End If
Next i
Call LogText("Objects Found In File: " &
NumObjectsInFile)
'This stops the program getting an
"Subscript Out of Bounds" error when
'scanning between two objects.... see later on...
ReDim Preserve OBJECT(NumObjectsInFile + 1) As ASEObject
OBJECT(NumObjectsInFile + 1).StartLine = ASEData_NumLines
'//Just some simple Statistical Information...
Debug.Print "Number of Objects Found in File = "
& NumObjectsInFile
'//3. We now loop through every Object
i = 0 'Variable has been used, reset it...
For i = 1 To NumObjectsInFile
Call LogText("") '//Insert A Blank Line
Call LogText("Processing Object
" & i & " of " & NumObjectsInFile)
'3a.
Calculate how many vertices we have....
'starts with MESH_NUMVERTEX
Call LogText(" Information:")
For Ic = OBJECT(i).StartLine To OBJECT(i + 1).StartLine 'Scan just the text for this object
If UCase(Left(ASEData(Ic), 14)) = "MESH_NUMVERTEX" Then
'we now need to parse this string and get the value at the end...
Temp = Right(ASEData(Ic), Len(ASEData(Ic)) - 14)
Temp = Trim(Temp)
OBJECT(i).numVertices = Val(Temp) 'Convert the string into a number
End If
Next Ic
Call LogText(" Vertices in Model: " &
OBJECT(i).numVertices)
'3b.
How many TVertices we have...
'starts with MESH_NUMTVERTEX
For Ic = OBJECT(i).StartLine To OBJECT(i + 1).StartLine 'Scan just the text for this object
If UCase(Left(ASEData(Ic), 15)) = "MESH_NUMTVERTEX" Then
'we now need to parse this string and get the value at the end...
Temp = Right(ASEData(Ic), Len(ASEData(Ic)) - 15)
Temp = Trim(Temp)
OBJECT(i).numTVertices = Val(Temp) 'Convert the string into a number
End If
Next Ic
'3c.
How many Faces we have...
'starts with MESH_NUMFACES
For Ic = OBJECT(i).StartLine To OBJECT(i + 1).StartLine 'Scan just the text for this object
If UCase(Left(ASEData(Ic), 13)) = "MESH_NUMFACES" Then
'we now need to parse this string and get the value at the end...
Temp = Right(ASEData(Ic), Len(ASEData(Ic)) - 13)
Temp = Trim(Temp)
OBJECT(i).numFaces = Val(Temp) 'Convert the string into a number
End If
Next Ic
Call LogText(" Triangles in Model: " &
OBJECT(i).numFaces)
'3d. Get the Texture Reference Index
'starts with OBJECT_TEXTURE=
For Ic = OBJECT(i).StartLine To OBJECT(i + 1).StartLine 'Scan just the text for this object
If UCase(Left(ASEData(Ic), 15)) = "OBJECT_TEXTURE=" Then
'we now need to parse this string and get the value at the end...
Temp = Right(ASEData(Ic), Len(ASEData(Ic)) - 15)
Temp = Trim(Temp)
OBJECT(i).TextureReference = Val(Temp) 'Convert the string into a number
End If
Next Ic
'3e. Get the Vertex coordinates
Call LogText(" Progress:")
Call LogText(" Collecting Vertex Information")
For Ic = OBJECT(i).StartLine To OBJECT(i + 1).StartLine 'Scan just the text for this object
If UCase(Left(ASEData(Ic), 6)) = "VERTEX" Then
'We've found a vertex; lets get some more data...
Temp = Right(ASEData(Ic), Len(ASEData(Ic)) - 6)
Temp = Trim(Temp)
tNumber = Val(Temp) 'This now holds the vertex number
ReDim Preserve OBJECT(i).ASE_Vertices(tNumber) As tASEVertex
'resize the array to suit
'the
number
of
vertices
in our
model...
OBJECT(i).ASE_Vertices(tNumber)
=
GetASEVertexFromLine(Temp)
End If
Next
Ic
'3f. Get the face indices and compile a face list...
Call
LogText("
Compiling
Face
List")
For Ic
=
OBJECT(i).StartLine
To
OBJECT(i
+
1).StartLine 'Scan just the text for this object
If
UCase(Left(ASEData(Ic),
4)) =
"FACE"
Then
'We've found a vertex; lets get some more data...
Temp =
Right(ASEData(Ic),
Len(ASEData(Ic))
- 4)
If
Left(Temp,
1) =
","
Then
Temp =
Right(Temp,
Len(Temp)
- 1)
Temp =
Trim(Temp)
tNumber
=
Val(Temp) 'This now holds the Face number
ReDim
Preserve
OBJECT(i).ASE_Faces(tNumber)
As
tASEFace 'resize the array to suit the number of faces....
OBJECT(i).ASE_Faces(tNumber)
=
GetASEFaceDataFromLine(Temp,
CInt(i))
End If
Next
Ic
'3g. Get the Textured Vertex Values
Call
LogText("
Collecting
Texture
Coordinates")
For Ic
=
OBJECT(i).StartLine
To
OBJECT(i
+
1).StartLine 'Scan just the text for this object
If
UCase(Left(ASEData(Ic),
6)) =
"TCOORD"
Then
'We've found a vertex; lets get some more data...
Temp =
Right(ASEData(Ic),
Len(ASEData(Ic))
- 6)
Temp =
Trim(Temp)
tNumber
=
Val(Temp) 'This now holds the Face number
ReDim
Preserve
OBJECT(i).ASE_TVertices(tNumber)
As
tASETexCoord 'Resize the array...
OBJECT(i).ASE_TVertices(tNumber)
=
GetASETCoordDataFromLine(Temp)
End If
Next
Ic
'3h. Get the TFace values and compile texture coordinates
Call
LogText("
Compiling
Texture
Coordinate
Information")
For Ic
=
OBJECT(i).StartLine
To
OBJECT(i
+
1).StartLine 'Scan just the text for this object
If
UCase(Left(ASEData(Ic),
5)) =
"TFACE"
Then
'We've found a vertex; lets get some more data...
Temp =
Right(ASEData(Ic),
Len(ASEData(Ic))
- 5)
Temp =
Trim(Temp)
tNumber
=
Val(Temp) 'This now holds the Face number
'//To Solve bug where the Vertex Coordinates are lost we must use
'//A temporary variable, then copy the TCoords from this...
tFace
=
GetASETFaceDataFromLine(Temp,
CInt(i))
OBJECT(i).ASE_Faces(tNumber).t1.u
=
tFace.t1.u
OBJECT(i).ASE_Faces(tNumber).t1.v
=
tFace.t1.v
OBJECT(i).ASE_Faces(tNumber).t2.u
=
tFace.t2.u
OBJECT(i).ASE_Faces(tNumber).t2.v
=
tFace.t2.v
OBJECT(i).ASE_Faces(tNumber).t3.u
=
tFace.t3.u
OBJECT(i).ASE_Faces(tNumber).t3.v
=
tFace.t3.v
End If
Next
Ic
Call
LogText("
Object
Compiled
Successfully.")
'3i. Get some statistical Information...
TotVerts
=
TotVerts
+
OBJECT(i).numVertices
TotFaces
=
TotFaces
+
OBJECT(i).numFaces
Next i
Call
LogText("")
Call
LogText("All
Objects
Compiled
Successfully")
Call
LogText("
Total
Number
of
Vertices
in
Model:
"
&
TotVerts)
Call
LogText("
Total
Number
of
Triangles
in
Model:
"
&
TotFaces)
Call
LogText("")
'////////////////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
'|||||| AT THIS POINT OBJECT(I).ASE_Faces() HOLDS ALL THE INFORMATION WE NEED
|||||||||||||
'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\////////////////////////////////////////////////////////
'4. We now need to extract the texture names that we are
using.
'we have the reference, now we need the string...
Dim J
As
Long
i = 0 'Reset variables
For i
= 0 To
ASEData_NumLines 'Scan the whole file
If
UCase(Left(ASEData(i),
3)) =
"TEX"
Then
'//We've found a Texture Line....
tNumber
=
Val(Trim(Right(ASEData(i),
Len(ASEData(i))
- 7)))
If
tNumber
>
numTextureNames
Then
numTextureNames
=
tNumber 'Record the maximum Size...
ReDim
Preserve
TextureNames(numTextureNames)
As
String
'//We now need to scan until the next non-empty line
For J
= i +
1 To i
+ 75 'Scan 75 lines beyond...
If
ASEData(J)
<>
""
Then
TextureNames(tNumber)
=
ASEData(J)
Exit
For
End If
Next J
End If
Next i
'//Now copy this data into the object variable
For i
= 1 To
NumObjectsInFile
OBJECT(i).Texture
=
TextureNames(OBJECT(i).TextureReference)
Next i
'//Information for the user.
Call
LogText("Textures
Found:
"
&
numTextureNames
+ 1)
For i
= 0 To
numTextureNames
Call
LogText("
"
&
TextureNames(i))
Next i
Call
LogText("") '//Insert a blank line...
RetrieveRawData
= True
Exit
Function
ERRHAND:
RetrieveRawData
=
False
End
Function
|
|
|
|
There
are several parts in there that you may well need to change, should you use
more of the material information than just the bitmap line. Otherwise, this
function should work pretty much perfectly.
3:
Saving it from our compiler
At this point we have all the information we need to create an object in Direct3D
- but it's not in the easiest format to understand. So we'll now format the
data and save it to a file. The method we'll use to save it will be similiar
to the original ASE format - Ascii. You could save it using Binary access or
databases, but I felt that was a little unnecessary - there's no great speed
advantage in this case and no ones likely to steal any secrets from reading
the file...
Private
Function
CompileRetrievedData()
As
Boolean
On
Error
GoTo
ERRHAND:
'//We now format the data into a medium from which we can
save it...
'//Convert them all into the faces...
Dim
FFile
As
Integer
Dim i
As
Integer 'Object Loop
Dim J
As
Long 'Inner Loop
Dim
Compiled
As
String 'The line we want to write..
FFile
=
FreeFile '//Get a free file number
Open
ExportTo
For
Output
As #FFile
Print
#FFile,
"HEADER:
The
Man
With
The
Digital
Gun 3D
MODEL
{STATIC}"
For i
= 1 To
NumObjectsInFile
LogText
("
Saving
Object
"
&
i)
Print
#FFile,
"TEX:
"
&
OBJECT(i).Texture '//Write the Texture
For J
= 0 To
OBJECT(i).numFaces
- 1
'compiled=x,y,z, x,y,z, x,y,z, t1,t2,t3
'Point1:
Compiled
=
OBJECT(i).ASE_Faces(J).p1.X
&
","
&
OBJECT(i).ASE_Faces(J).p1.Y
&
","
_
&
OBJECT(i).ASE_Faces(J).p1.Z
&
","
'Point2:
Compiled
=
Compiled
&
OBJECT(i).ASE_Faces(J).p2.X
&
","
&
_
OBJECT(i).ASE_Faces(J).p2.Y
&
","
&
OBJECT(i).ASE_Faces(J).p2.Z
&
","
'Point3:
Compiled
=
Compiled
&
OBJECT(i).ASE_Faces(J).p3.X
&
","
&
OBJECT(i).ASE_Faces(J).p3.Y
&
","
_
&
OBJECT(i).ASE_Faces(J).p3.Z
&
","
'TexCoord1:
Compiled
=
Compiled
&
OBJECT(i).ASE_Faces(J).t1.u
&
","
&
OBJECT(i).ASE_Faces(J).t1.v
&
","
'TexCoord2:
Compiled
=
Compiled
&
OBJECT(i).ASE_Faces(J).t2.u
&
","
&
OBJECT(i).ASE_Faces(J).t2.v
&
","
'TexCoord3:
Compiled
=
Compiled
&
OBJECT(i).ASE_Faces(J).t3.u
&
","
&
OBJECT(i).ASE_Faces(J).t3.v
&
","
Print
#FFile,
Compiled '//Finally, Stick the line in the file...
Next J
Next i
Close
#FFile
CompileRetrievedData
= True
Exit
Function
ERRHAND:
CompileRetrievedData
=
False
End
Function |
|
|
|
As
you can see, especially if you open the final file in notepad, is that it is
divided into blocks defined by their textures. Each line defines a triangle
which can be rendered by Direct3D - as we'll see later on. An example of the
final format would be something like this:
HEADER:
The
Man
With
The
Digital
Gun 3D
MODEL
{STATIC}
TEX:
Bubing_2.bmp
-5,-5,-5,-5,5,-5,5,5,-5,1,0,1,1,0,1,
5,5,-5,5,-5,-5,-5,-5,-5,0,1,0,0,1,0,
-5,-5,5,5,-5,5,5,5,5,0,0,1,0,1,1,
5,5,5,-5,5,5,-5,-5,5,1,1,0,1,0,0,
-5,-5,-5,5,-5,-5,5,-5,5,0,0,1,0,1,1,
5,-5,5,-5,-5,5,-5,-5,-5,1,1,0,1,0,0,
5,-5,-5,5,5,-5,5,5,5,0,0,1,0,1,1,
5,5,5,5,-5,5,5,-5,-5,1,1,0,1,0,0,
5,5,-5,-5,5,-5,-5,5,5,0,0,1,0,1,1,
-5,5,5,5,5,5,5,5,-5,1,1,0,1,0,0,
-5,5,-5,-5,-5,-5,-5,-5,5,0,0,1,0,1,1,
-5,-5,5,-5,5,5,-5,5,-5,1,1,0,1,0,0, |
|
|
|
The
above would render a cube using the texture "Bubing_2.bmp"...
4:
Loading it into a class for use with Direct3D.
Now it gets interesting. Fine, we can save all the data to a file as a long
list of numbers - but as of yet we can't see anything for our efforts. So we'll
now design a second class that reads in this data and renders it in Direct3D.
The added advantage of using a class system is that once we've created it we
can create any number of objects - all with the same code. We'll also [later]
add some functions so the parent application can set the rotation, scale and
position of an object....
4a.
The Declarations
'////////////////////////////////////////////////////////////////////////////////////
'////
'////
clsGameModel:
Used
to
load
pre-compiled
.Dat
Models
into
'////
a
Direct3D7
application
'////
'////
Written
:
November
8th
2000
'////
By :
Jack
Hoxley
'////
'////
Contact:
Jollyjeffers@Greenonions.netscapeonline.co.uk
'////
www.vbexplorer.com/DirectX4vb/
'////
www.CloneSoftware.Cjb.Net
'//////////////////////////////////////////////////////////////////////////////////
'//November
08
2000
'Class
Created.
'Basic
Loading
And
Object
Detection
Code
'//November
09
2000
'Loading
Finished
'Texture
Loading
Complete
'Object
Rendering
Complete
Option Explicit
Public Dx As DirectX7 '//We need the graphics engine to pass a valid Reference...
Public DD As DirectDraw7 '//We need another reference for the DirectDraw Object...
Public Device As Direct3DDevice7 '//A reference to the Graphics Engine Renderer...
Public TexturePrefix As String '//A pointer to the folder where the textures are stored...
'//Mathematics Constants
Private Const Pi As Single = 3.14159265358979 '4*atn(1)
Private Const Rad As Single = Pi / 180 '//Multiply Degrees by this to get Radians
Private Const Deg As Single = 180 / Pi '//Multiply Radians by this to get Degrees
Enum RETURNPARAM
retX = 0
retY = 1
retZ = 2
End Enum
Private Type OBJECT3D
StartLine As Long 'Whereabouts in the file this object starts...
Texture As
String 'Filename
TextureIndex As Integer 'Internal
Vertices() As D3DLVERTEX 'Open ended array of Lit vertices
End Type
Private NumModels As Integer
Private ObjMdl() As OBJECT3D 'Open ended array of models...
Private NumLines As Long 'How many lines are in the array & file
Private FileData() As String 'The actual Lines
Private NumTextures As Long 'How many Textures we require
Private oTexture() As DirectDrawSurface7 'The actual Textures...
'//Model Transformations
Private vPosition As D3DVECTOR 'Where the object is
Private vRotation As D3DVECTOR 'Where is it facing
Private vScale As D3DVECTOR 'How big it is... |
|
|
|
Note
that you'll need to have your project reference the DirectX7 type library for
this class to compile/work. A lot of the declarations here are similiar to the
compiler classes; as the data we're loading is the same [to a certain extent].
Also notice that there are three variables at the bottom for rotation, Scale
and position - we'll get these working a little later on.
4b.
The function prototypes
Fortunately the function prototypes are much simpler than in the previous class:
//These functions can be seen from *outside* Public Function LOAD(Model As String) As Boolean Public Sub RENDER() 'Pretty Obvious this one...
//these functions help with the loading of the data Private Function GetDataFromLine(Line As String, nEntry As Integer) As D3DLVERTEX Private Function CreateTexture(sFile As String, pWidth As Long, pHeight As Long, Optional ColKey As Integer = 0) As DirectDrawSurface7
//These next 3 functions set the world-space information for our object: Public Sub SetPosition(X As Single, Y As Single, Z As Single) Public Sub SetRotation(X As Single, Y As Single, Z As Single) Public Sub SetScale(X As Single, Y As Single, Z As Single)
//Then these 3 allow the host to get the current world-space information Public Function GetPosition(Param As RETURNPARAM) As Single Public Function Getrotation(Param As RETURNPARAM) As Single Public Function GetScale(Param As RETURNPARAM) As Single
//Then there are two helper functions for the Matrix Maths Private Sub ScaleMatrix(pMatrix As D3DMATRIX, pVector As D3DVECTOR) Private Sub TranslateMatrix(pMatrix As D3DMATRIX, pVector As D3DVECTOR) |
|
|
|
4c.
Loading the data into the class.
The first thing that we need to do is get the data from the DAT file, we then
need to read off the relevent information - which is easy because we compiled
it to be. So off we go:
Public
Function
LOAD(Model
As
String)
As
Boolean
On
Error
GoTo
FATAL:
Debug.Print
"Model
to
Load:
"
&
Model
'//0. Variables
Dim
FFile
As
Integer
Dim
Temp
As
String
Dim I
As
Long
Dim J
As
Long
Dim
nVerts
As
Long
Dim
ArrayI
As
Long 'Where we are in the Vertices() array
Dim
tArg
As
String
'Temporarary
Argument
String
'//1.
Setup
Variables,
Error
Check...
FFile
=
FreeFile '//Choose
a free
file
place
'//2.
Read
in the
data
Open
Model
For
Input
As #FFile
'2a. Check the file header
Line
Input
#FFile,
Temp '//Remember to change this line should you change the header...
If Not
(Temp
=
"HEADER:
The
Man
With
The
Digital
Gun 3D
MODEL
{STATIC}")
Then
GoTo
FATAL:
'2b. Read in the data
Do
While
Not
EOF(FFile)
'Increment the number of lines
NumLines
=
NumLines
+ 1
'resize the array to suit the number of lines
ReDim
Preserve
FileData(NumLines)
As
String
'read the line from the file
Line
Input
#FFile,
Temp
'add the new line into the array
FileData(NumLines)
= Temp
Loop
Close
#FFile
'Some statistical Information
Debug.Print
"Loaded
"
&
NumLines
&
"
lines
from
model
File"
'//3. Now we calculate the number of objects in the file
For I
= 0 To
NumLines
Temp =
Left(FileData(I),
3)
If
UCase(Temp)
=
"TEX"
Then
'We've found a texture Heading. Must be a new object....
NumModels
=
NumModels
+ 1
ReDim
Preserve
ObjMdl(NumModels)
As
OBJECT3D
ObjMdl(NumModels).StartLine
= I
ObjMdl(NumModels).Texture
=
Trim(Right(FileData(I),
Len(FileData(I))
- 4))
Debug.Print
"Object
"
&
NumModels
&
"
starts
on
line
"
&
ObjMdl(NumModels).StartLine
&
"
and
has
'"
_
&
ObjMdl(NumModels).Texture
&
"'
As a
texture"
End If
Next I
'To Avoid Subscript errors we'll create a final - empty - object...
ReDim
Preserve
ObjMdl(NumModels
+ 1)
As
OBJECT3D
ObjMdl(NumModels
+
1).StartLine
=
NumLines
+ 1
'//4. Now we need to work out how many triangles/Vertices there are in our Object
For I
= 1 To
NumModels
nVerts
= (ObjMdl(I
+
1).StartLine
- 1) -
ObjMdl(I).StartLine
Debug.Print
"Object
"
&
I
&
"
has
"
&
nVerts
&
"
faces"
nVerts
=
nVerts
* 3
'Each
line
has 1
triangle
= 3
vertices
Debug.Print
"Object
"
&
I
&
"
has
"
&
nVerts
&
"
vertices"
ReDim
Preserve
ObjMdl(I).Vertices(nVerts)
As
D3DLVERTEX
Next I
'//5. Now we fill out our ready-sized array with the data from the file...
For I
= 1 To
NumModels
ArrayI
= 0 'Reset the array marker
For J
=
ObjMdl(I).StartLine
To
ObjMdl(I
+
1).StartLine
- 1
'We're now scanning through the data for object I
If Not
(Left(FileData(J),
3) =
"TEX")
Then
'We're not on a Texture Header
'So we can
format this line...
'We need to fill out three vertices per Face...
tArg =
FileData(J)
ObjMdl(I).Vertices(ArrayI)
=
GetDataFromLine(tArg,
0)
ArrayI
=
ArrayI
+ 1
tArg =
FileData(J)
ObjMdl(I).Vertices(ArrayI)
=
GetDataFromLine(tArg,
1)
ArrayI
=
ArrayI
+ 1
tArg =
FileData(J)
ObjMdl(I).Vertices(ArrayI)
=
GetDataFromLine(tArg,
2)
ArrayI
=
ArrayI
+ 1
End If
Next J
Next I
'//6. We now need to Create The relevent Textures...
NumTextures
=
NumModels 'There should be 1 texture per model.
ReDim
Preserve
oTexture(1
To
NumTextures)
As
DirectDrawSurface7
For I
= 1 To
NumTextures
Set
oTexture(I)
=
CreateTexture(TexturePrefix
&
ObjMdl(I).Texture,
0, 0,
1)
ObjMdl(I).TextureIndex
= I '//So we know which texture to use when rendering...
Next I
'//7. Set the Default Values
vScale.X
= 1:
vScale.Y
= 1:
vScale.Z
= 1 'otherwise the object wont appear
LOAD =
True '//We succeeded
Exit
Function
FATAL:
LOAD =
False '//We didn't succeed
End
Function
|
|
|
|
A
note on Texture usage in this class. At the moment the class just loads a new
texture for each entry it finds in a file. This has the obvious downside of
loading multiple copies of the texture should it be used twice in a model file
- even worse if the texture is big. You could easily get around this by checking
if the texture is already loaded, and if it is just reference that one. But
there is another disadvantage - what if we create 10 objects, all from the same
DAT file - or all 10 objects use the same texture? you'd then have 10 copies
of the same texture in memory - not a good idea. A possible optimisation would
be to create a third class that holds a global set of textures and each object
class references the textures there... that part is up to you.
The
CreateTexture function is in the downloadable archive, and if you've done any
work with textures/other tutorials from this site you'll have seen it before
anyway. At the end of this procedure we'll have all the information that we
need to render the object. But first we need to allow the host to set/get the
world information...
5:
Manipulating the object
This part is extremely easy, it just involves creating a few interfaces from
which the host can set the rotation/scale/position of the object; depending
on your engine you may well want to improve this to add other features...
'//THESE
NEXT
THREE
ALLOW
THE
HOST
TO SET
HOW
THE
MODEL
'//IS
REPRESENTED
IN 3D
SPACE
Public Sub SetRotation(X As Single, Y As Single, Z As Single)
vRotation.X = X: vRotation.Y = Y: vRotation.Z = Z
If vRotation.X > 360 Then vRotation.X = vRotation.X - 360
If vRotation.Y > 360 Then vRotation.Y = vRotation.Y - 360
If vRotation.Z > 360 Then vRotation.Z = vRotation.Z - 360
If vRotation.X < 0 Then vRotation.X = vRotation.X + 360
If vRotation.Y < 0 Then vRotation.Y = vRotation.Y + 360
If vRotation.Z < 0 Then vRotation.Z = vRotation.Z + 360
End Sub
Public Sub SetPosition(X As Single, Y As Single, Z As Single)
vPosition.X = X: vPosition.Y = Y: vPosition.Z = Z
End Sub
Public Sub SetScale(X As Single, Y As Single, Z As Single)
vScale.X = X: vScale.Y = Y: vScale.Z = Z
End Sub
'//THESE NEXT THREE FUNCTIONS RETURN INFORMATION ABOUT
'//THE POSITION,ROTATION & SCALE OF THE OBJECT
Public Function Getrotation(Param As RETURNPARAM) As Single
If Param = retX Then
Getrotation = vRotation.X
ElseIf Param = retY Then
Getrotation = vRotation.Y
ElseIf Param = retZ Then
Getrotation = vRotation.Z
End If
End Function
Public Function GetPosition(Param As RETURNPARAM) As Single
If Param = retX Then
GetPosition = vPosition.X
ElseIf Param = retY Then
GetPosition = vPosition.Y
ElseIf Param = retZ Then
GetPosition = vPosition.Z
End If
End Function
Public Function GetScale(Param As RETURNPARAM) As Single
If Param = retX Then
GetScale = vScale.X
ElseIf Param = retY Then
GetScale = vScale.Y
ElseIf Param = retZ Then
GetScale = vScale.Z
End If
End Function |
|
|
|
6:
Rendering the object
Now we have all the information we require to render the object - we've loaded
the data and formatted it, we've allowed the host to set any world-space information,
so now we need to make it appear on screen. We'll assume that the host application
has provided us with a valid reference to a rendering device, and that the call
is between a "BeginScene...EndScene" block - if either of these two
aren't okay you'll get errors...
Public
Sub
RENDER()
Dim I
As
Integer,
Temp
As
D3DMATRIX,
Final
As
D3DMATRIX
'//0. Setup the relevent Matrix...
'a. Rotate
Dx.RotateXMatrix
Final,
vRotation.X
* Rad
Dx.RotateYMatrix
Temp,
vRotation.Y
* Rad
Dx.MatrixMultiply
Final,
Final,
Temp
Dx.RotateZMatrix
Temp,
vRotation.Z
* Rad
Dx.MatrixMultiply
Final,
Final,
Temp
'b. Scale
ScaleMatrix
Temp,
vScale
Dx.MatrixMultiply
Final,
Final,
Temp
'c. Translate
TranslateMatrix
Temp,
vPosition
Dx.MatrixMultiply
Final,
Final,
Temp
'd. Set the Matrix
Device.SetTransform
D3DTRANSFORMSTATE_WORLD,
Final
'//1. Render
For I
= 1 To
NumModels 'Go through each part...
Device.SetTexture
0,
oTexture(I)
Device.DrawPrimitive
D3DPT_TRIANGLELIST,
D3DFVF_LVERTEX,
ObjMdl(I).Vertices(0),
UBound(ObjMdl(I).Vertices())
+ 1,
D3DDP_DEFAULT
Next I
End
Sub
|
|
|
|
Again,
not very difficult at all; you can render an entire object with one call - and
if you have only 3-4 objects in the model you can have the whole thing done
pretty easily....
7:
Using the Class.
This is the final part that we have to do. There are several things that we
must bare in mind if we to use this class in a real-world application. The following
code will initialise the object:
'Create the Object
Set CPlayer = New clsGameModel
'Add the references
Set CPlayer.DD = DD
Set CPlayer.Device = Device
Set CPlayer.Dx = Dx
CPlayer.TexturePrefix = App.Path & "\textures\"
'Load the object and output the result...
Debug.Print "Load Model returned: ", CPlayer.LOAD(App.Path &
"\mole.dat") |
|
|
|
You'll
need to customise this slightly for your own needs, the main things to bare
in mind are:
1. The object is Late Bound, if you're using it alot (as you will be) you should
change this to be early bound:
Dim CPlayer as clsGameModel
Set CPlayer = New clsGameModel
2. You'll need to make the references valid - this means only creating an object
after you have initialised the rest of your Direct3D application - see the other
tutorials for this. Ideally you should alreadys know this though.
3. You might need to change the path to the textures.
Other
than that you're done. Download the completed classes from the top of the page,
or from the Downloads Page. And well done - you've
reached the end of this rather large tutorial... In fact, I wonder if anyone's
still reading..... :)
If
you have time, check out my site for "The Man With The Digital Gun"
- this class was designed for that game, and there wasn't really any reason
other than my kindness to make it open source... Go
Here
|