well here it is (kinda), my first attempt in making something using OOP in masm32, combined with the basic OpenGL code from EvilHomer this code can load a mesh from a file into memory and then display it on the screen, so i'd like to see some heavy comments on my code so i can learn from it :D

the modelfiles have the following format
word NumVertices
NumVertices * Sizeof(vertice) --> which is 4 dwords
word NumTriangles
NumTriangles * Sizeof(Triangle) --> which is 6 bytes, 3 words which point to 3 vertices indexes.

the file is located here:

http://members.lycos.nl/thascorp/Mesh.inc.txt
ill upload a zip containing a working exe later on.

edit: here it is http://members.lycos.nl/thascorp/WorkingExample.zip

ps. the Color parameters will be removed once it can handle textures

Scorpie
Posted on 2005-02-27 14:34:28 by Scorpie
Wow . Cool :alright:
I never thought it'd be this simple to load and display a mesh in OpenGL :)
You can skip "ds:" when accessing data, masm will do this automatically for you :) . And you can merge the
set Box as Mesh
set Pyramid as Mesh

into

set Box,Pyramid as Mesh

With ATC, you can use "set" even before the variables have been declared ;)

Nice usage of ATC, btw :)
Is this .hex in a custom mesh format ?
Posted on 2005-02-27 18:23:55 by Ultrano
Thanks for the comment

If i use Set before the declare do i still have to declare the variables or isnt this needed anymore when you Set them?

About the format; first i just listed the vertices and 3 vertices in a row made a triangle but i found out milkshape3D has openSource file format so i studyed it a little and there i found the methode of storing all vertices and then just use a word to specify wich vertices form a triangle which will probably save a lot of space since in a model vertices are used multiple times.

i made a little mistake in my explaination i reserved an extra dword after each vertices so i could use shift instead of mul and this dword will be used later on to store boneID and such. (planning on inplementing bone animation)

advantage with openGL is you need to load a mesh only once and then you can use the same instance to draw it multiple times on the screen :)

Scorpie
Posted on 2005-02-28 11:01:24 by Scorpie
The "set" macro doesn't do allocation of the variable automatically. Because this allows some flexibility: the variable can be a global initialized dword, a global uninitialized dword, a local variable, or even a register


set inited,uninited as Mesh
.data
inited dd 0
.data?
uninited dd ?
.code

someProc proc arg1
local localVar

set arg1,localVar,edx as Mesh
pcall inited.Draw
pcall uninited.Draw
pcall edx.Draw
ret
someProc endp



Ah the boneID is a good idea :) . Just as I was wondering how to make good-looking animated meshes :-D .
So, openGL is exactly like my software 3D engine for PalmOS :) - my DrawMesh() is identical to your Mesh_Draw :) . I'd love to see this Mesh class become complete :alright:
Posted on 2005-02-28 12:41:31 by Ultrano
ah i see, flexibility is good :)

im stil working out a good system for the animations and im thinking of implementing textures first but im not sure wether i want the textures inside the model file or not and if i should do them before or after animations (probably animations since i got a nice idea on how to make it possible :wink: )

btw, a friend of mine didnt see any 3D in my example so i made the following JPG to show that the model is actually 3D but it isnt very clear since i dont use textures nor shading/lightning yet: http://members.lycos.nl/thascorp/see3D.JPG

edit: hmm maybe it'll take some time before i've implemented the animation system, im thinking about two different things i can do:

One way: i can put extra parameters in the call; AnimotionNumber and FrameNumber.
Downside: user needs to keep data of all objects, what framenumber they are and update this theirselves.

Other way: combine a Timer with the mesh include so you only need to call UpdateAnims (or something) and the objects get transformed the right way and displayed.
downside would be you need to create an instance of the Mesh for every object you want to draw so they can all hold their own CurrentFrame.
but im thinking about solving this by making 2 classes, 1 class that just holds data for a certain mesh (ie. Box or Pyramid) and another class which has a datamember to hold a pointer to the mesh its using (for example a pointer to the box class) and other values like CurrentFrame and such.
But i still need to be able to have a dynamic number of the second class (depending on how many object are near the user or in its section or something like that)

Any thoughts on this? (im gona think more now in front of the tv)
Posted on 2005-02-28 13:26:24 by Scorpie
I'm currently working on a more advanced mesh class which currently supports:
A) "Plain" quad surfaces (pervertex color)
B) "Textured" quad surfaces

Textured surfaces support persurface normal for lighting and collision detection. That means you can bounce particles off any surface of your model. Since "plain" surfaces don't currently support Normal, they are not currently useful for lighting/collisions.
So far, I've implemented a Sphere/Plane collision and response.
It uses the "Planar Sweep" method to determine the precise collision time and position, determines if the collision is in the current timestep, and if so, "goes back in time", fixes the collision situation (response), then "winds time forward".
The class also provides for BoundingSphere for the entire "cluster of surfaces", as an aid to frustum culling.
Finally, the class supports Loading and Saving of surface data.

ToDo: Write a flexible Vertex arraymanager class and then implement Indexed Vertices to eliminate vertex duplication in reference models.
Write a Modelmanager class to instance our ReferenceModels.
Add support for collisions of Sphere/Sphere, Sphere/Box and Sphere/Cylinder (all pretty easy).

The aim of the current work is to implement a robust class to manage surface arrays, handle collisions and responses, etc.
I'm only working with Spheres because it's the first geometric primitive which I implemented, and many physics tutorials mention Spheres.

This will all come together to form my next 2 or 3 OGL tutorials.
Interesting?

Scorpie, you are currently running into some of the very same issues that I have of recent days. One of the first things I did about Textures was to write a robust TextureManager class.
I humbly offer this class here. It may or may not be bugfree, but I'm already using it quite happily to render my textured quads.





class TextureManager, ,C++ compatible
void GetNameByID:dwID
void GetIDByName:pName
void AddNewTexture:pName
long pNamesArray ;<-- ptr to CVector object which contains an array of stringptrs
long pIDsArray ;<-- ptr to CVector object which contains an array of ID values
static CurrentTextureID dd 0
endclass


;=============================================================================
;The TextureManager class stores two closely-associated arrays of data.
;One array contains stringpointers for the filenames from which textures were sourced,
;the other contains the associated "ID" of each loaded texture.

;The following class methods are provided for your general use:
;GetIDByName
;GetNameByID
;AddNewTexture

;GetIDByName attempts to return the ID for a given Name, or -1 for Error (not found).
;GetNameByID attempts to return the stringpointer for a given ID, or -1 for Error (as above).
;AddNewTexture is what we use to load new textures.
;Ostensibly, it returns the ID of the loaded texture, and E_FAIL for error (could not load).
;It contains a call to the GetIDByName method which it uses to determine whether
;the given filename has already been loaded, in which case it returns the existing ID.
;=============================================================================

AUX_RGBImageRec struct
dwsizeX GLint ?
dwsizeY GLint ?
data dd ?
AUX_RGBImageRec ends


;=============================================================================
;This "Helper Procedure" creates an OpenGL texture from a source image file (most media types)
;It is called from TextureManager class's "AddNewTexture" method.
;=============================================================================
CreateTexture proc ptexture, pstrFileName
local pBitmap:ptr AUX_RGBImageRec

.if !pstrFileName ; Return from the function if no file name was passed in
return E_FAIL
.endif

invoke auxDIBImageLoad, pstrFileName ;Load the bitmap and store the data
.if eax == NULL ; If we can't load the file, quit!
return E_FAIL
.endif
mov pBitmap , eax

invoke glGenTextures,1, ptexture ; Generate a texture with the associated texture variable
invoke glPixelStorei , GL_UNPACK_ALIGNMENT, 1 ; This sets the alignment requirements for the start of each pixel row in memory.

mov eax,ptexture
invoke glBindTexture,GL_TEXTURE_2D, dword ptr[eax] ; Bind the texture to the texture variable passed in

; Build Mipmaps (builds different versions of the picture for distances - looks better)
mov ebx, pBitmap
invoke gluBuild2DMipmaps,GL_TEXTURE_2D, 3, [ebx].AUX_RGBImageRec.dwsizeX, [ebx].AUX_RGBImageRec.dwsizeY, GL_RGB, GL_UNSIGNED_BYTE, [ebx].AUX_RGBImageRec.data

; Lastly, we need to tell OpenGL the quality of our texture map. GL_LINEAR_MIPMAP_LINEAR
; is the smoothest. GL_LINEAR_MIPMAP_NEAREST is faster than GL_LINEAR_MIPMAP_LINEAR,
; but looks blochy and pixilated. Good for slower computers though.
invoke glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST
invoke glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR

; Now we need to free the bitmap data that we loaded since openGL stored it as a texture
mov ebx, pBitmap
free [ebx].AUX_RGBImageRec.data
free pBitmap
return S_OK
CreateTexture endp
;=============================================================================

TextureManager_TextureManager proc
mov [ecx].TextureManager.pNamesArray, new (CVector)
mov [ecx].TextureManager.pIDsArray, new (CVector)
ret
TextureManager_TextureManager endp

TextureManager_$TextureManager proc
local cunt
local me
mov me,ecx
;Since the IDsArray only contains numbers, we can just delete the whole thing..simple.
delete [ecx].TextureManager.pIDsArray
;Since the NamesArray contains stringpointers, we need to "sweep" the array
;ie For each StringPointer in array : free StringPointer
mov cunt,$icall ([ecx].TextureManager.pNamesArray, CVector, getcount)
xor ebx,ebx
.while ebx<cunt
push ebx
icall [ecx].TextureManager.pNamesArray, CVector, getbyindex, ebx
free eax
pop ebx
inc ebx
.endw
delete [ecx].TextureManager.pNamesArray
ret
TextureManager_$TextureManager endp

;This procedure scans the IDsArray for the given ID.
;If found, it returns the associated StringPointer.
;Otherwise it returns Error=-1
TextureManager_GetNameByID proc dwID
local cunt
local me
mov me,ecx
mov cunt,$icall ([ecx].TextureManager.pIDsArray, CVector, getcount)
xor ebx,ebx
.while ebx<cunt
push ebx
icall [ecx].TextureManager.pIDsArray, CVector, getbyindex, ebx
.if eax==dwID
pop ebx
push ebx
icall [ecx].TextureManager.pNamesArray, CVector, getbyindex, ebx
ret
.endif
pop ebx
inc ebx
.endw
return -1 ;Not Found
TextureManager_GetNameByID endp

;This procedure scans the NamesArray for the given Name.
;If found, it returns the associated ID.
;Otherwise it returns Error=-1
TextureManager_GetIDByName proc pName
local cunt
local me
mov me,ecx
icall [ecx].TextureManager.pNamesArray, CVector, getcount
.if eax==NULL
return -1
.else
mov cunt,eax
.endif
xor ebx,ebx
.while ebx<cunt
push ebx
icall [ecx].TextureManager.pNamesArray, CVector, getbyindex, ebx
invoke lstrcmpi, eax, pName
.if eax==0
pop ebx
push ebx
mov ecx,me
icall [ecx].TextureManager.pIDsArray, CVector, getbyindex, ebx
ret
.endif
pop ebx
inc ebx
.endw
return -1 ;Not Found
TextureManager_GetIDByName endp

TextureManager_AddNewTexture proc pName
local me
local dwID
mov me,ecx
;First, check to see if the Texture has already been loaded
icall me, TextureManager, GetIDByName, pName
.if eax!=-1
ret ;If the Texture was already Loaded, we return its ID
.endif
;The Texture has not been Loaded yet, attempt to do so now.
invoke CreateTexture, addr dwID, pName
.if eax==E_FAIL
ret ;If we fail to load the Texture for WHATEVER reason, return E_FAIL
.endif
;The Texture was successfully Loaded, so add it to the Array(s)
;Store the ID in the IDsArray
mov ecx,me
icall [ecx].TextureManager.pIDsArray, CVector, push_back, dwID
;Store the Name in the NamesArray
invoke AllocString, pName ;Allocate string memory and copy string into it
mov ecx,me
icall [ecx].TextureManager.pNamesArray, CVector, push_back, eax
return dwID
TextureManager_AddNewTexture endp



Have a nice day :)
Posted on 2005-03-01 01:52:34 by Homer
My only complaint about your basic mesh class is that you could have used the malloc() and free() macros to allocate the array memory as Heap memory rather than as virtual process memory via VirtualAlloc.
Posted on 2005-03-01 02:02:47 by Homer
i tried reading a bit on various allocation types and i couldnt find much about it but i did find some benchmark results and VirtuallAlloc came out as the fastest way possible and its what i've always used to allocate memory.
But im always willing to learn :) what is the advantage of using heapalloc instead if virtualalloc?

Scorpie

edit: im at school now (in class) ill read your code later on today and ill post some comment on it.
Posted on 2005-03-01 04:18:07 by Scorpie
Firstly, understand that every Process has a "Process Heap".
Then understand that we're not using it when we use malloc and free.
We can create as many heaps as we require, and ATC creates one such Heap (on demand, when we first use the malloc macro).

Theres some handy functions for "walking" the data structures stored in Heap memory, but I've never needed them yet although they offer potential as debugging tools. For example, its possible to enumerate every chunk of memory that was allocated on the heap, how large it is, etc. Since ATC only creates and uses ONE such Heap, those enumeration functions are less valuable. Perhaps CLASS33 will support more powerful malloc and free macros which operate on one of N heaps, but for now, malloc uses one heap.

If you dig down into the kernel code for VirtualAlloc and GlobalAlloc, you will find that both eventually call HeapAlloc iirc. This implies that those api functions carry some overhead which we don't need (I assume its related to automatic garbage collection). It also implies that HeapAlloc "should" be faster.

VirtualAlloc and GlobalAlloc (and LocalAlloc for that matter) have a default "page granularity" which means they are not very friendly for allocating lots of small memory blocks, and implying that a "memory manager" codebase is required to manage objects allocated in such an environment to prevent them from straddling pages.

Heap memory, if allocated correctly in the first place, can GROW ON DEMAND, and will ensure that objects never straddle memory pages.

In summary: if you want to allocate one or two largish chunks of memory and only need to do so once or twice, then you're fine with the api Alloc functions. But if you need to allocate memory for numerous small objects (with no idea how many may be required) and/or need to frequently release arbitrary objects, then the Heap is your friend.
It took a long time for me to stop using GlobalAlloc and be convinced that Heap memory was actually better for this kind of thing, I think it happened around the same time I started writing particle demos under DirectX 8...

If anyone has another point of view on this, or wishes to correct me on any point or points, please feel free.
Posted on 2005-03-01 06:21:12 by Homer
Thanks for the explanation.

By "GROW ON DEMAND" do you mean the heap gets bigger but the existing data stays as it is since this would be very nice to fix one of my problems (dynamic number of objects).

GlobalAlloc is out of the question anyway since it has some limitations, bugs and is slow compared to most other methodes for allocating mem. Ill look up the benchmark results again since VirtuallAlloc did come out faster then other methodes but if it calls HeapAlloc that wouldnt be very logical.

But reading this i think using HeapAlloc would be better.

ps. ill read your texture code after dinner.

Scorpie
Posted on 2005-03-01 11:18:53 by Scorpie
Homer, you got things mixed up a bit here - local/globalalloc end up as HeapAlloc calls, which internally uses a VirtualAlloc-like routine. heap+local/globalalloc has "whatever" alignment (4byte, I think), VirtualAlloc has 64kb alignment - so VA is not good for small chunks.

Local/GlobalAlloc are deprecated and shouldn't be used, use HeapAlloc instead.

VirtualAlloc is good if you need huge chunks of memory, or want to design your own heap manager.
Posted on 2005-03-01 11:22:25 by f0dder
The working example download doesn't work.
Posted on 2005-03-01 13:21:00 by drhowarddrfine
hmm i had the same problem at school and just ago but now it seems to be working again, if it doenst work try to copy the url to the adresbar or rightclick --> save the file.

sorry for the inconveniance
Posted on 2005-03-01 14:01:05 by Scorpie
I thought I might have been mixed up - it was late at night when I replied.
Generally speaking I wasn't far off the mark - VirtualAlloc for large allocation, HeapAlloc for small allocation.

Yes, the Heap can Grow on demand, leaving existing data in place.
Better yet, ATC already contains some basic macros to help implement heap memory objects, and in fact, the malloc() macro I mentioned is internally used by ATC to allocate memory for CLASS OBJECTS when you call the new() macro...
Posted on 2005-03-01 22:35:27 by Homer
I've managed to get animations working (animations stored in model and to display them), im thinking about making the following system:

Mesh class to load and display a model

Texture manager for all the textures (i like the idea Homer :))

Object class which holds a reference to a Mesh class (so every model only needs to be loaded once) with private values like position in world, rotation in world, current animation, currentframe etc

Timer class which works with 3 counters inside, 1 counter to Update the 'Currentframe' of all objects, 1 counter which allows you to run code on a certain fps (so objects move at the same speed on all hardware) and the last counter counts how many times the Timer.Update is called every sec to display the FPS.

I dont have much time to program this week but ill let you know when i got some new things working
Posted on 2005-03-08 11:00:31 by Scorpie