I've added another new object, this one's called BrickManager.
Unlike the D3DCamera, you have to create and destroy BrickManager yourself (preferably within the reload and unload callbacks).

What the heck is BrickManager?
What's a Brick?

We're going to be writing a 3D Breakout-style game as a demo.
The main objective of Breakout is to smash 'colored bricks' with a 'bouncing ball'.

I've designed the Bricks as textured cubes which are 10 units in size (thus matching our Grid).

BrickManager is an object which keeps a Collection of Bricks and up to 256 Textures.
You can create as many Bricks as you like, and each Brick can be uniquely positioned in 3D space, and have any one of those 256 textures.
(I've created just ONE instance in this demo, but the theoretical limits are huge, depending mostly on system ram).

BrickManager performs its textureloading via D3DApp's TextureManager subobject, so it never has to worry about unloading them.

BrickManager inherits from the DataCollection object (which in turn inherits from Collection, which inherits from Primer).

In the BrickManager object class, I've made use of a field called pOwner that is actually inherited from Primer.
This special field is often set by the Init method of various objects.
It's meaning is up to us, but typically it's a way of telling a subobject which other object is its daddy.

In our case, the BrickManager::Init method sets the BrickManager.pOwner field to point to our D3DApp instance.
Don't confuse this with inheritance, as I said, the pOwner field is meaningless until we want to use it for something.
I'm using pOwner to allow BrickManager to call methods in D3DApp.
To be more precise, BrickManager::AddTexture makes a call to D3DApp::LoadTexture, which in turn calls its TextureManager::LoadFromFile.
Thus, we are marshalling BrickManager's texture loading to D3DApp's TextureManager.
In fact, we'd like to send ANY texture loading there, because any textures loaded by TextureManager never need to be manually unloaded due to its design.

Now we're starting to use OOP in more complex ways, I expect a few people will have questions.
Attachments:
Posted on 2006-07-13 03:27:02 by Homer
I tried an approach which is main thread used for AI and diskload etc, and rendering a frame is called from TIMER, isnt that enough for games, which isnt fast FPS games, but RTS, RPG etc, to get more cpu power to AI, with more fixed framerate?, also leaves bandwidth to diskload inbetween rendering for example for load new terrain all the time?


Posted on 2006-07-13 08:51:14 by daydreamer
The most important thing to note is that ALL D3D CALLS should be performed from a single thread - there's a D3D Multithreading model, but it's actually not what it sounds like, you just end up passing a Lock around.

Since you can't guarantee that Disk IO will complete in the allotted timeslice, it's prudent to perform all Disk IO in a separate thread which communicates its loaded data to the main thread.

Better still is to split disk accesses up into smaller pieces, and feed the smaller pieces to the main thread (especially for loading textures, since partial textures can be uploaded as they become available).

Desynchronizing the Disk IO from the main thread is an advanced topic which I hope to cover at some point.
Posted on 2006-07-13 09:35:38 by Homer

In the previously-posted zip, I forgot to include the new BrickManager class - I apologize to those of you who downloaded it, and I've attached the zip here with the missing file included.

Moving right along, we have just a few more things to cover before we start talking about game logic.
We're going to want to draw a sphere for the 'ball' used to destroy our Bricks. We're going to require some kind of collision detection.We still have no audio. We haven't decided what a Player looks like. We have no Networking ... etc.

I'll be adding code for rendering a Sphere in the next exciting episode... but I won't bother with an Instance manager until we start looking at multiplayer networking, which is some way off.

I can describe the collision detection right now though, so let's talk about that.

Collision Detection and Response can be very complicated, or very simple, depending apon the requirements of the project.
Our demo game does not require sophisticated collision detection, which is good news for the aspiring game developers among you.
As a matter of fact, I feel that hardcore collision detection and response is beyond the scope of this project.
We can cheat and perform a kind of 'point test' to determine which Grid squares the Sphere is currently overlapping.
If the Sphere overlaps a grid square containing a Brick, we'll consider that to be a Collision, and we'll make the Brick explode, and change the Direction of the Sphere to simulate a Response.
Piece of cake :)

This is one of the reasons why I chose the Breakout style demo, and why the Bricks were designed to match the size of the Grid... the Sphere will have a Radius which is half the Grid size, so that the Sphere is diametrally the same size as the Grid (and the Bricks).
This vastly simplifies the collision detection requirements.

So in order to implement the collision detection for the Sphere, we first figure out which Grid square the Sphere's Center exists within, and then we check only the SURROUNDING grid squares.
In this way, we have greatly reduced the number of tests that need to be performed. Note that we are taking advantage of the PARTITIONING of the World (our Grid squares).
I've said before that it's common to partition the World in order to reduce the number of collision tests.
Another way we can make use of the partitioning is to implement 'culling' by simply NOT drawing stuff that's not visible to the camera (ie is off-screen).
If you have a World made of 200 million triangles, but you can only see 250 triangles from where you are, how many do we want to draw?
Since this demo will have a relatively small world, visibility culling isn't really something that we need to discuss yet, but I figured I'd mention it anyway.
Just note that there's two major ways of culling what's not visible.
One is by NOT drawing anything owned in a Partition that is clearly not visible (for example, its too far away). The other is called 'frustum culling', this allows us to determine what is VISIBLE (either totally or partially on-screen), ie to enlist what MUST be drawn.
I'll discuss 'frustum culling' at some point in the future.

Attachments:
Posted on 2006-07-13 19:53:37 by Homer

As promised, this version has a sphere being rendered inside the cube (our Brick). Note that the sphere diameter is the same size as the Brick (and the grid granularity).
I've drawn both the Brick and the Sphere in WIREFRAME so you can see the Sphere more easily, and what its geometry looks like.
There's currently no Texture for the Sphere, and I probably will NOT be adding that in this demo.

D3D supports a handful of 'highlevel primitives': Polygon, Box, Cylinder, Sphere, Torus and Teapot (yeah, TEAPOT !!!)
I've used the D3DXCreateSphere api.

Here's an amusing quote:

Aside from that, people have pointed out that it is a useful object to test with. It's instantly recognizable, it has complex topology, it self-shadows, there are hidden surface issues, it has both convex and concave surfaces, as well as 'saddle points.' It doesn't take much storage spaceóit's rumored that some of the early pioneers of computer graphics could type in the teapot from memory.


The D3DXCreateBlah api functions actually create a 'Mesh Object'.
Meshes are something that I'll be talking about REALLY soon.
We'll be adding support for loading Meshes from .X files, and for rendering them.

Before we do that however, we might cover a few more basic topics such as simple Lighting and Materials (so we can see that our textureless Sphere is more than just a filled circle when not using wireframe) and perhaps we can even look at making things move about, using simple animation and simple dynamic physics.
Don't worry about the word 'physics', I said SIMPLE, and I meant it.
Attachments:
Posted on 2006-07-13 21:25:02 by Homer
I STILL don't get why the damed teapot is a primitive... lol.
Posted on 2006-07-14 02:25:01 by Bobbias
there's a D3D Multithreading model, but it's actually not what it sounds like, you just end up passing a Lock around.

And it's slower. Probably because of many critical sections and such.


The most important thing to note is that ALL D3D CALLS should be performed from a single thread

Well, actually they don't have to be performed from single thread. It can be any thread as long as you know what you're doing. Any errors here can give strange results ^^ So in other words: Direct3d doesn't require you to call its methods form a single thread (except some creation/deletion methods). The documentation recommends it, because programmers tend to make stupid mistakes when coding miltithreaded applications.
Posted on 2006-07-15 03:24:12 by ti_mo_n
Hi Homer,

From GameClient10 onwards the output in DebugCenter starts with the following lines:

D3DApp.Init
World Error : Out Of Bounds (Array)
World Error : Out Of Bounds (Array)
eax = 0t


The error messages are coming from World.GetCellPtr.
What goes wrong?

I can't see an effect on the running application though.

Running GameClient15 and GameClient16 on my desktop WinXP computer, the cube (and sphere) are presented.

Running them on my laptop I get a "Read or Write failure".
Translation of the original message: The instruction on address 0x7c911c48 addresses memory at location 0xfffffff8.

Original message:
De instructie op 0x7c911c48 verwijst naar geheugen op 0xfffffff8. De lees- of schrijfbewerking ("read") op het geheugen is mislukt.

Klik op OK om het programma te beŽindigen.
Klik op Annuleren om fouten op te sporen in het programma.


Both applications abort after the Read/Write error.
Any idea why there would be a difference between my desktop and my laptop? They both run on WinXP SP2, using DirectX SDK december 2005.

I added the output of GameClient15 from my (succeeding) desktop and my (failing) laptop.

Friendly regards,
mdevries.
Posted on 2006-07-15 10:57:13 by mdevries

The array out of bounds errors are deliberate.
They were generated as a result of two deliberate attempts to access an array with a bad index.
That array isn't being used yet, so it's not important.
It will hold the 'map of cells' for a 'game level'.

As for the RW Failure, the D3DApp class attempts to use a config file to store the most recent working video settings (it will create the file if none exists, using the exe file's name and path but with a dot cfg extension).
Maybe thats not ok in your temp internet folder, as its suspicious behaviour to create a file?
I'm just guessing it must be related to the config file anyway.
If you have a config file, delete it and try again.
Also, if you are trying to run the exe from inside the zip, maybe extracting it someplace first may help :)
Posted on 2006-07-15 12:08:07 by Homer
Hi Homer,

I did not use the exe from the zip, because the zip doesn't contain the exe file.
I had to extract the project, change a few paths in the SuperTimer include, and build the projects using RadAsm 2.1.0.8 on my laptop, and RadAsm 2.2.0.5 on my laptop, before I had an exe. So, that can't be the problem.

I deleted the cfg file, and ran the exe again.
The cfg file was created indeed, but stays empty. Though the problems remain.

I extracted the project to a folder within c:\masm32. So that should not be a problem either.

B.t.w. You forgot to include the Res folder in most of your zip's, so I copied it from the one it was included in to all the other projects where it was needed.
Was this intentionally, or did you plan to include it, but simply forgot.

Friendly regards,
mdevries.
Posted on 2006-07-15 14:25:15 by mdevries
In the attached zip, I've added a Material and a Light.
These are applied to the Sphere, which is filled as a Solid.

I've added a second Brick instance, and code to 'render all bricks'.

I've updated the Camera's motion code such that camera position changes with Time. The user won't be able to move around with the Camera like this during the game, I've done this as a beginner's example of physics.
Physics is all about how stuff changes over time.
Later we'll be applying similar simple physics to make our 'Ball' move around.

I can't think what else I did, but theres a few little bugfixes etc.

Next I'll be adding code for loading static Meshes from .X files and for instancing and rendering them.
Attachments:
Posted on 2006-07-15 14:51:14 by Homer
Thanks, I hadn't noticed about the RES being missing.
Thats very important.
It relates to the video options dialog, which should show three combo boxes and two buttons.
Attachments:
Posted on 2006-07-15 14:55:51 by Homer

I've extended the Brick vertex definition to include a per-vertex Normal component.
Now the spotlight can be seen on the Bricks.
Less obviously, we can now obtain the Surface Normal for any of the Surfaces of a Brick from any Vertex forming that Surface.
I haven't talked about Surface Normals yet, so let's do that.

All you have to understand is that a Surface Normal is an imaginary 3D arrow pointing away from a flat Surface, it describes which direction the Surface is facing.

Per-Vertex Normals give more fine control over how the light reflects from a surface. They make stuff look nicer under lights.

Surface normals are usually calculated by averaging all the normals of the vertices which define that surface.
Since our vertex normals are all the same for a given Brick face, we can skip the averaging calculation altogether.

We might use Surface Normals as part of our Collision Detection.
Surface Normals give us most of the information required to define a Plane.
A Plane is an imaginary flat 3D surface which is infinitely large.
You might say "a Plane cuts the Universe into two halves".
We can define a Plane for each of a Brick's faces.
Planes are useful in collision detection because it's very easy to compare a Plane and any point in 3D space, ie, we can tell whether a point is behind, in front of, or resting apon a Plane, and we can tell how far away a point is from a plane, etc.
We can extend that Point test to create tests that work for more complex primitives like edges (formed between two points) and Spheres (since a Sphere's radius is the same length in any direction, we can find the distance from the Sphere's center to any Plane, and see if it's at least as large as the Sphere's radius).

So .. Normals are useful for more than just Lighting :)
Attachments:
Posted on 2006-07-15 15:41:17 by Homer
for the RW Failure, the D3DApp class attempts to use a config file to store the most recent working video settings (it will create the file if none exists, using the exe file's name and path but with a dot cfg extension).
Maybe thats not ok in your temp internet folder, as its suspicious behaviour to create a file?
I'm just guessing it must be related to the config file anyway.
If you have a config file, delete it and try again.
Also, if you are trying to run the exe from inside the zip, maybe extracting it someplace first may help


As this didn't solve the problem, I did some more research.
I haven't solved the problems yet. But maybe someone can help any further.

I'll describe what I have done to find the cause of the problem in GameClient_15 that I encounter on my laptop.
The last message I got was:

(re-)loading resources


This code comes from GameReloadVolatiles. In order to find out what cause the problem I put some DbgText lines in the procedure.
It appeared that the following line was the last line that was executed:

mov pBricks,$New(BrickManager,Init,pGame)


This leads to the BrickManager.Init method.
After having put some debug code there, I saw the problem had to do with the following line:

ACall Init,pOwner,16,256,-1


So, who is the ancestor of BrickManager? BrickManager derives from DataCollection.
But DataCollection doesn't have an Init method.
DataCollection itself derives from Collection. And Collection does have an Init method.
So I put some debug lines in Collection.Init.
Problems arive at the execution of the following line:

OCall ecx.SetLimit, eax


We are comming closer. The search continues in Collection.SetLimit.
In Collection.SetLimit the crash is on MemAlloc (after label @@3).
This is involved code block: (The debug lines are mine)

align @WordSize
@@3:

    DbgText "CheckPoint: Collection.SetLimit 4a"
   
    mov ecx,             ;dNewLimit
    shl ecx, 2                      ;eax * sizeof(Pointer)
    mov eax, .pItems
    or eax, eax
    jne @@4

    DbgText "CheckPoint: Collection.SetLimit 4b"
    DbgDec ecx
   
    MemAlloc ecx

    DbgText "CheckPoint: Collection.SetLimit 4c"
   
    or eax, eax                    ;function failed?
    jz @@Err

    DbgText "CheckPoint: Collection.SetLimit 4d"
   
    mov .pItems, eax
    m2m .dLimit,     ;dNewLimit
    jmp @@Exit


There is no return from MemAlloc
What can cause MemAlloc to fail?

Friendly regards,
mdevries.
Posted on 2006-07-16 17:16:41 by mdevries
MemAlloc is just a wrapper for a HeapAlloc call, aimed at the Process heap.

We're trying to allocate a block of memory large enough to hold 16 dwords, and it's failing... that's indeed strange.

I shall add more code to check for failed allocations, but this does strike me as quite strange to fail allocation of such an insignificant amount of memory.

Posted on 2006-07-17 19:41:12 by Homer
Maybe it'll sound naive, but make sure that it is indeed the memalloc failing and not YOUR debug code. I have once caught myself on this one ^^'

memalloc just can't fail :P (at least the msvcrt's one that is. I don't know about your 'memalloc' macro). There must be some problem with the parameters, or the macro itself, or whatever. If you allocate too much (like all physical RAM) then Windows will start to massacre the HDD rather than simply fail.
Posted on 2006-07-18 04:53:29 by ti_mo_n
Maybe it'll sound naive, but make sure that it is indeed the memalloc failing and not YOUR debug code.


As you can see in de code I put 2 debuglines in front of the MemAlloc.

DbgText "CheckPoint: Collection.SetLimit 4b"
DbgDec ecx


In order to execute the DbgDec macro the DbgText macro must have finished execution first.
Both produce output, so DbgText is finished.

At first I did not have a DbgDec line. Only the DbgText line.
As we know yet, DbgText has ended.
So the execution of MemAlloc must have started.

Friendly regards,
mdevries
Posted on 2006-07-18 11:42:28 by mdevries
Hi mdevries
Iíll try to find out what happens, but I need the code you use. Can you send me a zip with your files?

Regards,

Biterider
Posted on 2006-07-18 15:05:53 by Biterider
Putting some DbgText lines in the MemAlloc macro did not have much success.

So I decided to put a MessageBox in the MemAlloc macro.
I had to push the Enter button many times before the options dialog appeared.
After my selection a few more Enter's were needed. And there was something new:

Suddenly a window called "Object Error" was added to the DebugCenter:
It wasn't there before!
And it contained dozens and dozens of errors saying:

COL_OUTOFMEMORY: There is no more free memory
COL_OVERFLOW: Limit greater MaxSize


and a few errors saying:

COL_INDEXERROR: Index not in range?  [0..Count-1]


Attached is the complete listing of the output in the Object Errors window.

memalloc just can't fail?  (at least the msvcrt's one that is. I don't know about your 'memalloc' macro). There must be some problem with the parameters, or the macro itself, or whatever. If you allocate too much (like all physical RAM) then Windows will start to massacre the HDD rather than simply fail.


Biterider, I don't think your MemAlloc macro is wrong. Because it worked on my desktop. It only didn't on my laptop!
Sending you the files will not make it crash on your computer: They are just the files I downloaded from Homer's example GameClient15, the second version, in this thread.
But maybe the errors in the attached file are more usefull.

The errors are screaming: You don't have any more free memory!
How can you ever run out of memory when calling HeapAlloc (wrapped in MemAlloc)
My laptop has 1 GB of memory, from which up to 256 MB can be used for the graphics card. But the remaining MB's should be more than enough, shouldn't they.

What further tests can I do?

Friendly regards,
mdevries.
Attachments:
Posted on 2006-07-18 16:19:43 by mdevries
Hi mdevries
Reading your comments and the rtf file, it seems that there must be something wrong on the demo. The DataCollection (ID=23) seems to want to allocate too much memory. The rest is a consequence of that. Even the debugging system didnít work properly without memory.

The best chance to find the bug is to check the values passed to the Init method of the DataCollection used in the demo.

Regards,

Biterider
Posted on 2006-07-19 01:10:08 by Biterider