Jump to content
• Advertisement
• entries
222
• comments
606
• views
593003

### About this blog

Z80 and C#-related shenanigans - now with added electronics.

## Emerson Beta 2

Sorry about the lack of updates, but I have been incredibly busy with work related programming.

One small project I've had a chance to update is Emerson - my keyboard and mouse library for the TI-83 series calculator.

I know, I can't type layout. [rolleyes] Download the library (and demo) here.

## Stuff and nonsense

Brass

Important: all versions rr (ix+1)) meaning that they are not output correctly. Please upgrade to 1.0.4.6 as soon as possible.

Other stuff

I haven't had much of a chance to do anything especially exciting of late. I found my PICAXE chips and also found out the reason that one of them never seemed to work was that I was using an old version of the programming software and the chip was an 18X, not a regular 18 (so lots of extra space - can only be a good thing). I'm not sure what to do with them, though. I had thought of using one of the PICAXE-08Ms as a super-cheap "greylink" replacement (cable for TI calculators that deals with the TI byte transfer protocol at one end and RS232 serial at the other - unlike the blacklink that implements the TI byte transfer protocol in software), but I can't read serial data in and output it fast enough and just end up dropping bytes.

Ideas on the back of a postcard, please.

I've been trying to help someone get started with FMOD Ex - they're using VB.NET, and FMOD Ex is only supplied with a C? wrapper. No matter, it gave me an opportunity to experiment with class libraries. It was a bit of an anticlimax... just create a new 'class library' project, add the C? wrapper files, hit build then add the output DLL as a reference to the VB.NET project. Couldn't be easier. [grin] Just use the System.Runtime.Interop.Marshal class to dump data around rather than the unsafe pointers from the examples and it works like a champ.

## Multithreading on the TI-83 Plus

I'm not really clued up on the way these new-fangled modern operating systems handle running multiple threads, so I apologise in advance if this isn't really proper multithreading.

Basically, I was pondering (as one does) whether it would be possible to run any number of threads on the TI-83 Plus. I daresay others have done this in the past, but not having access to the internet at the time to check I thought I'd do my own bit of experimentation.

The way I can see this working is to give each thread a small amount of time to run in (time slice). At the end of one thread running, I would save the calculator's state away, and load the state for the next thread. I would keep on swapping threads several times a second, giving each their own bit of time and preserving their state.

What I really needed to know was what needed to be preserved between executions. Really, there isn't a lot - just the registers. PC and SP clearly need to be preserved, so that when a thread is resumed it resumes running at the point it was left at. Keeping the other general-purpose registers (AF, BC, DE, HL, IX, IY) safe would be important as well.

Naturally, I'll need to also preserve the stack. Or rather, for each thread, I'll need to allocate some memory for a stack and point the new thread's SP register at the end of that new memory. To keep things simple, I'll just point my test thread's SP at $FFFF-200, as the TI is arranged to have 400 bytes of stack RAM, growing backwards from$FFFF.

To perform the swapping, I'll use a custom interrupt (run in IM 2). The timer hardware will trigger this interrupt many times a second, so it is ideal. At the end of my custom interrupt, I'll jump into the default handler provided by the TIOS so the TIOS functions that rely on it behave correctly.

The way I see it working is like this:

Effectively, all this does is take the pointer stored when the current thread was resumed, dump all the registers into the memory it points to, hunts the next thread, reloads the registers and saves the pointer for next interrupt.

Does it work?

The above screenshot doesn't look too impressive, I'll give you that. There are only two threads running. The secondary thread is drawing all those random dots on the LCD. The other thread (which is the main program thread) just contains "bcall(_getkey)" - hence the 2nd/Alpha icons appearing,

I'll make the primary thread do something a bit more exotic - display random characters on the left side of the screen, the secondary work on the right side of the screen:

The code for this is simply:

Main
.include "Multithread/Multithread.asm"

; Kick things into action.
call Multithread.Init

; For the moment, the secondary thread is hard-coded, and
; the 'multithreading' code ONLY switches back and forth between
; two hard-coded thread slots.
-

halt ; Slow things down a bit here.

ld b,8
call ionRandom
ld (curCol),a

ld b,8
call ionRandom
ld (curRow),a

ld b,0
call ionRandom
bcall(_PutMap)
bcall(_getCSC)
cp skClear
jr nz,{-}

call Multithread.End
ret

; ---------------------------------------------------------

SecondaryThread

di ; Don't want any other thread writing to the LCD!

ld b,64
call ionRandom
add a,$80 push af ; Save for later... out ($10),a

ld b,48
call ionRandom
add a,48
ld b,a
srl a
srl a
srl a
add a,$20 out ($10),a

ld a,b
and 7
ld c,%10000000
jr z,{+}
ld b,a
- srl c
djnz {-}
+

in a,($11) call LcdBusy in a,($11)
xor c
ld c,a

call LcdBusy

pop af
out ($10),a call LcdBusy ld a,c out ($11),a

ei

jr SecondaryThread

LcdBusy
push af
inc hl
dec hl
pop af
ret

As you can see, there are two discrete threads running there.

Well, that's two threads up and running. What's needed to extend this to any number of threads?
Thread management. Basically, the ability to add/remove threads at will, and have the handler be able to switch between as many threads as required.
Allocation of new stack space. New threads will need more stack space, so I shall need to add some mechanism to steadily allocate it.
Idle threads. One big problem is that as you add threads, each one gets progressively slower. If threads flag themselves as idle, they can be skipped so threads that do need CPU time get it. The easiest way to do this is to set a "sleep" counter which is checked when threads are switched around - if it's zero (when it runs out), the thread is given some time to run.

Off the top of my head, that's about it. There are some issues I have come across when working with this:
TIOS routines are not thread-safe. This speaks for itself, pretty much - if you have two threads, both of which are calling bcall(_putC) to display a character, they interfere with eachother (for example - incorrectly setting the value of curCol or curRow as one thread changes it as the other one is reading it) which causes crashes.
Variables can become an issue. Same reason as above - if two threads call a function which uses a set RAM location for a local variable, the variable can be changed half way through.

One possible way to get around thread-unsafety of functions is to disable interrupts before calling them and reenabling them afterwards, which prevents the thread switcher from swapping them. It's a pretty lousy workaround, though. [sad]

## Map Editing

I added five-level grey:

The greyscale effect is optional (adds about 8 lines of code, only runs once per frame and can be disabled with a single switch in the engine package's Settings.inc file.

Anyway, I've started throwing together a very primitive level editor. (GDI+)

Insert inserts a new vertex at the mouse location; delete removes the closest vertex. Point at a vertex, hold W, move to another vertex and release W to insert a new wall. (Single-sided, hold shift at the same time to add a double-sided wall). Use D to remove walls in the same way. That sort of thing.

The two different types of wall are represented with white lines (single-sided) and grey lines (double-sided). There are therefore 9 different sectors in the above image.

Imagine that the walls have been kept simple (fixed floor/ceiling heights, single segment) and can have one of 6 different 'colours' - from 0 (invisible/transparent) to 1 (white) through to 3 (50% grey) and finally 5 (black).

Therefore, for the sake of simplicity, assume that all of the double-sided grey walls in the above screenshot are invisible. The room in the south-west of the screenshot is an octogonal room with a doorway in the eastern wall and a square pillar in the middle.

The four double-sided walls in this south-western room are not redundant. You will notice that the level has been cut up into convex sectors. This has one small advantage - it reduces the number of walls that will need to be sorted, as I only need to sort entire sectors for occlusion purposes.

Also, I can see that I can get some sort of portal*-based system up and running. Suppose I use the editor to create this:

If I was to start in the left sector, I would go through and draw each wall. I'd hit the double-sided wall at some point, and know that after this sector I'd have to move into the sector in the middle. After that sector, I'd know that I'd have to move to the sector to the right. With me so far?

Let's do something radical and make that central sector a door, and close it (move the ceiling height down to meet the floor height). This time, when I move through the sectors, I'll hit this sector and see that it's closed. No matter, say I - rather than carry on walking through the sectors, I stop at that point.

By liberally sprinkling a world with doors, and keeping them shut most of the time, you can cut out a fair chunk of geometry. :)

I cannot think of the best way to handle sorting. I don't think I can just use the order in which I wander through the sectors - imagine this:

Imagine you're in the north 'triangle' and looking south. As you can see, you need to travel through more sectors to get to the larger rectangular room than the south triangular room - and using that for sorting is just wrong.

One potential workaround I can think of is that each sector has a visibility list - a list of in which order sectors appear (in front->back order - else we wouldn't be able to take advantage of portals). Problems; MASSIVE amount of data that grows exponentially with each added sector! From what I can also tell, that also seems a precomputed BSP list, done per sector (rather than work out which side of the split to follow each branch, it's already calculated for us). On the other side, this can also be used to calculate (through some nifty ray/plane intersection algorithm) which sectors will NEVER be visible from a particular sector... Hmm. I'm not afraid of 'wasting' RAM with gigantic tables - if we assume a smallish, plain level of 50 sectors, each sector would need a list of at most 49 other sectors. 49*50*2=4900B - not actually too bad. :
(Bah) Just realised that would not work (precomputed sorting based on sectors) at all.

Let's say the red line is your 'view' (not a wall, quick and dirty diagram). It intersects the front of two walls, so it appears that the sort should go north->east->west sectors. However, imagine I move to the other side of the sector and look the same way (mirror the diagram, basically). Now the order goes north->west->east, and I'm still in the same sector :( Do I just clump all the sectors together and sort them all together based on the distance to the central (average) coordinate of each and the camera? (Not fast!)

Thoughts?

*I hope that's the correct word.

## Coloured walls and FPS counters

I fixed most of the wall filling code. It now fills by scanline (so is considerably faster), which gives me more flexibility over what I can do with it.

For the moment, I have sacrificed speed with a routine that can handle three different fills - black, white, or 50% dithered. The routine will always blank the wall, mask out the pattern you wish to fill then OR it - which works nicely, but wastes time if you are filling plain black or white walls.

The result is quite pleasing:

As you can also see, I added scrolling skies.

The dithered walls look a bit nicer on hardware. The option to switch the initial dither pattern on alternate frames (resulting in a pseudo-greyscale) can be switched on or off, as it looks rather bad in emulators.

There is one horrible problem with the physical LCD, though - it really cannot cope with columns of alternating pixels. Compare the two shots, one being much closer to the wall (and so having more columns of alternating pixels):

Not so good. Hopefully the world in-game will have enough content to break up the columns, but I'll have to wait and see on that count.

One major concern I've had is keeping the speed of the engine constant. Just moving the player X units per frame is not an option, as the framerate can vary wildly. I need some sort of timing!

Luckily, the calculator has fairly limited timing hardware built-in. I can use this timer via interrupts. There are various pieces of hardware that can trigger interrupts - including the On key, activity on the link IO port (any line held low will trigger an interrupt) or this timer. The TIOS seems to use this timer to scan the keypad a few times a second, so it can read a key and store it for the key reading routines.

Fortunately, it is possible to take advantage of interrupt mode 2 on these calculators to install your own handler. When the engine is initialised, it adds the new handler - which is very simple. All it does is increment a 16-bit counter!

I added a piece of code that read this value and set it to zero each frame, displaying the result (ticks). I then added a piece of code that toggled the data lines of the link IO port, and used a PC program to monitor this rate (I used this technique before to work out interrupt-based timing for the 60Hz CHIP-8 clock). By comparing two extremes - a large viewpoint where I could see all of the walls, close up and a viewpoint where I could see nothing, I could see that one view ran at 16FPS and accounted for 20 ticks, and another ran at 44FPS and accounted for 8 ticks. This shows that one tick is approximately 3ms long, and that a FPS counter could be implemented by dividing 333 by the tick count.

A quick check with my PC program shows that the counter is fairly accurate (close enough for my liking). I can now use these tick counts to scale the movement of the player, so the speed is consistent. Another advantage is that I can set the SE edition calculators to 15MHz, and they can benefit from more fluid framerates (nb: any program I run will still need to run merrily on the 6MHz calcs for me to be happy. I'm not going SE-only!)

That's not to say I can now go and build an exciting level and release a nifty demo. There are still big holes in the engine - simple graphics bugs, like the wall filling code still not supporting top edge slopes with a y difference of over 127: (overflow-related bug)

...to there still not being any proper occlusion (sorting)...

I'd like to get occlusion up and running, so I can throw a nifty level together (for example, showing off non-90? walls). Thing is, it's tricky trying to show off cool stuff and not walk into an area where you can see the world isn't quite as solid as it should be. [sad]

## Filling

I chucked some simple filling in to the engine as a test. It scans along the top and bottom edges of the shape, then fills columns. This is rather inefficient (filling scanlines is much faster - the screen is shorter than it is wide, so tracing Ys for the boundaries is faster than scanning Xs, and when filling by scanline you can write 8 pixels - a byte - in a single shot) but it served the purpose of a quick demo. It's rather broken, and doesn't match the lines correctly (so there are slight gaps and overflows). It's only temporary.

I'd like to extend the filling to support different dithered fills - white, 25%, 50%, 75% and black walls would look nice.

There is no sorting as yet, I just arranged the walls in back-to-front order in the level file.

@philipptr: I don't know if you ever completed your triangle filling, but the way I've done it in the past is to split the triangle into two halves (y0->y1 and y1->y2, where y012). I would then trace down the sides of the triangle, calcuating the X bounds for the top and bottom halves (Bresenham), then filling in the horizontal lines using these values. Only uses integer arithmetic, so nice and fast.

Whether this will be turned into a game or not depends on whether I can get it to scale sensibly with decent-sized levels; especially with the addition of scaled sprites. If I can get a nice sized level that you can walk around nicely, then I'll try and add a game - pointless planning a game around an engine that isn't up to par!

## Clipping works! (Almost)

Clipping now works in nearly all cases:

One of the problems was lack of precision. Here's the typical case that worked, running in my mockup:

In this case, where |dX| > |dY|, all is good. However, switch that around so that |dY| > |dX|, and you get the following:

(Fixed point isn't so good for holding values that tend towards infinity). The solution is to have two branches for the clipping arithmetic, switching to the alternative form when |dY| > |dX|.

Preparation:
m = (Y1 - Y0) / (X0 - X1) ; m is negative gradient.
c = Y0 + m x X0 ; Not sure what to call this, but it's used in all the below calculations

Clipping to y = 0:
x = c / m
y = 0

Clipping to y = +x:
y = c / (1 + m)
x = +y ; not used as we can skip having to transform.

Clipping to y = -x:
y = c / (1 - m)
x = -y ; not used as we can skip having to transform.

The reason for clipping to y = 0 is to make sure the line is clipped to the correct side.

If you look at the red and blue lines, you can see that the far out-of-range points both have x also has the far end at x
To fix the steep gradient problem, I just need to switch to these alternative sums:

First up, the gradient and c are calculated as:

m = (X0 - X1) / (Y1 - Y0) ; Flipped
c = X0 + m x Y0 ; Again, X/Y flipped.

For clipping to y = 0:
x = c ; No need to divide by m!

The only other difference is to make sure that when clipping y = -x:
y = c / (m - 1) ; as opposed to (1 - m)

...and all is hunky-dory.

The only remaining problem is if you are positioned directly inside a wall, which collision detection should fix.

## Clipping

Clipping (adjusting walls to ensure that they are in the field of view so that they can be drawn correctly is being a right pain).

Firstly, I rewrote some of the really ugly wall-handling code:

Clipping follows the fairly simple maths:

Clip = (Y0 - m x X0) / (1 - m)

(Where m is the gradient of the wall). The resulting Clip value then corresponds to the new (X,Y) coordinate - if you want to clip to x=-y all that's needed is (1 - m) becomes (1 + m) and Clip must be negated before using it as the new X coordinate.

The result (with added adjustment layer to turn the grey clipped line into red for clarity's sake) seems to work pretty well.

...or not. When implemented with the rest of the code, something goes rather wrong when I look at the wall from the wrong side. Thankfully, that was a simple bug in the division routine, causing it to trash the accumulator when one of the operands was negative (the accumulator itself formed part of one of the operands).

With that fixed, things look a bit more positive, but it's still rather unstable.

## Latenite 1.0.6.0

There's a new Latenite beta - 1.0.6.0 - available from my site. I hope this is the last Latenite 1 release; I'm moving away from the current IDE towards a revised Latenite 2, the focus more on interactivity with the assembler and the debugger (better 'Intellisense', breakpoints, variable watching and so on). This mainly revolves around a much improved text editor, currently in the works.

Not a very exciting thing to look at, but handling text selection, editing, copying, pasting, and highlighting - and keeping it fast - is being a bit of a pain.
For the most part, it is fast enough, but gets seriously sluggish when scrolling up and down at high resolutions (has to repaint the entire control as none of it is buffered).

That little 3D engine for the calculator I was working for (codenamed Nostromo) is not dead, as you might have thought;

(Old screenshot to refresh your collective memories). I have recently 'ported' the TASM-style code to the new Brass syntax, rewritten all of the wall handling code for increased performance and fixed some of the graphical glitches caused by overflow (lines wrapping incorrectly around the display). I have increased the resolution and accuracy of the maths in use as well, but it's still pretty sluggish with large amounts of data and still lacks occlusion and wall clipping to the view.

One (easy) way of speeding things up would be to split the world up into 'rooms' (or zones, pick whatever terminology you like) where each room is entirely self-contained and cannot be seen from any other room. By opening and closing doors between rooms you can add and remove which rooms are in the list to be drawn, keeping the geometry count low.

Of course, for this to work, I'd need a bit of collision detection to be able to work out which room the camera is in. [sad]

In terms of occlusion, I'm a bit stumped. As far as I can tell, BSP might be the way to go, but have never been able to write a working 'BSP engine'. If anyone had any links they'd recommend on the theory and implementation behind this, I'd be very grateful.

## Text Editing Woes

I've had enough of my syntax-highlighting text editor in Latenite (it's a hack on top of the RichTextBox control, using all sorts of ugly Win32 hacks to beat it into submission). I'd much rather have a control I had some... 'control' over, so have taken to writing my own custom control.

One useful thing is that it supports Unicode characters nicely, unlike the RichTextBox

It doesn't currently support text selections (which is a bit of a problem) but once that's added it should be simple enough to start adding goodies to it that weren't easily possible via the RichTextBox control (reliable line highlighting, tab width manipulation, whitespace glyphs, collapsing code blocks and so on).

## Applications

I've been attempting to add native TI-83+/TI-73 application support to Brass. Testing with Flash Debugger has been an entertaining experience, not least thanks to helpful messages like:

I haven't managed to get a signed application running in Flash Debugger or on hardware yet - I suspect the header is wrong - but seeing as the demo application has a different header structure to what the header generator creates, and both have generally conflicting information, I need to find some better resources.

If you want to sign applications from C? via Wappsign, this wrapper around the COM object might be useful. You'd use it like this:

// Sign the app (where BinaryFile is the filename of the .hex file to sign)
try {

AppSigner Signer = new AppSigner(AppSigner.Mode.DetectType);

// Get the key file
string KeyFile = Signer.GetKeyFile(BinaryFile);
if (KeyFile == "") throw new Exception("Couldn't find the key file.");

// Get the output filename
string SignedAppFilename = Signer.FormatOutput(BinaryFile);
if (SignedAppFilename == "") throw new Exception("Couldn't establish output filename.");

// Sign the bugger
int Signed = Signer.Sign(BinaryFile, KeyFile, SignedAppFilename);
if (Signed != 0) throw new Exception(Signer.GetErrorMessage(Signed));

} catch (Exception ex) {
// Didn't work as it should
DisplayError(ErrorType.Error, "Could not sign application: " + ex.Message);
}

## JavaScript Raycaster

A silly project from over the weekend: JavaScript raycaster.

I couldn't work out how to handle keypresses in Firefox, and Firefox is too slow and flickery to make it worthwhile. IE runs it fine but at less than 1FPS, meaning that more often than not you end up walking through the walls. Opera is about the only browser it works happily in (~12FPS). Ah well.

## Debugging fun

One part of the Latenite 'suite' that's needed some dire attention is the PindurTI interface. This talks to the excellent TI emulator via a non-interactive mode, and can therefore sit between Latenite (and your source code) and the emulated calculator (which is running your binary).

The current incarnation of this tool is very primitive - you have a calculator that you can run/pause and send files to. That's it.

A picture should illustrate what the new one's like and what the old one was missing:

There's a memory viewer, register viewer, and breakpoint window. Breakpoints are caught and highlighted in the breakpoint window.
Currently, there is no editing of the calculator state - PindurTI has yet to support writing back the status (it can only dump it at the moment). All the breakpoint and label information comes from the debug file exported directly by Brass.

## Patch files

I added Emukon patch file export to Brass - Brass will export all the labels, variables and breakpoints from the source and Emukon can then load them in. It makes debugging much simpler...

Click for legible

In my Game Gear-related fiddling, I optimised the tween demo code significantly, but then slowed it back down again by adding 4 more points, extending the rotation code to using 16.16 rather than 16.8 fixed-point and using a proper linear tween. It's still a bit smoother, though:

Video: 00'58" WMV [1007KB]

## Structs and sensible variable layout

When developing an assembly program, you need to 'declare variables' by attaching an address (in RAM) to a label.

The problem here, of course, is having to calculate all the relevant positions in RAM for each variable. For example,
ram = $C000 ; Assume RAM starts at address$C000

var1 = ram+0
var2 = ram+1
var3 = ram+3 ; var2 is 2 bytes!
; ... and so on ...

Now this is fairly painful. So, I added .varloc and .var directives to Brass:ram = $C000 ; Assume RAM starts at address$C000

.varloc ram, 1024 ; 1024B in size

.var 1, var1
.var 2, var2
.var 1, var3
; ... and so on ...

This eases things a bit, but what if you have lots of variables and a number of different RAM areas? Now you still have to shuffle things around to fit. So, now, Brass allows you to define multiple areas of memory for variables (through multiple .varloc statements) and shuffles around all the variables to best fit in the available memory. Variables defined using .tempvar can even overwrite eachother (provided they are in different, not-nested modules) to save space.

Of course, sometimes you need variables to share consecutive areas of RAM, so I also added structure support.; Define it

.struct Point2D
.var db, X
.var db, Y
.endstruct

; Use it

.var Point2D, Me

ld a,10
ld (Me.X),a

ld a,32
ld (Me.Y),a

ld hl,10+32*256
ld (Me),hl

; Or even:

.struct Point3D
.var Point2D, P
.var db, Z
.endstruct

.var Point3D, You

ld a,(You.P.X)

And, to comply with the picture requirement, have something ancient and completely unrelated.

## A productive weekend

What with the weekend having an extra Monday tacked on for good measure (Labour Day), I felt the need to be productive.

I also felt the need to listen to VGM files converted to MIDI, so rustled up a VGM to MIDI converter. There already is one (available on the SMS Power! site), but I could never get it to work.
Having never really puzzled out the YM2413 ('OPLL', FM chip) I limited it to the square-wave generating PSG.

First of all, you need to be able to convert a tone register value (from 0 to 1023), the period of the output square wave, to a MIDI key value (0 to 127, where every 12 keys represent an octave).
This is easiest if you have a real frequency (in Hertz) to work with, so I have the formulae:
Frequency = ClockSpeed / (32 x ToneReg)
Key = 12 x Log2|Frequency x Constant| ClockSpeed is the clock speed of the PSG in Hertz. Constant is a precalculated constant used to scale the frequency to a range so that 440Hz ends up being played as key A, octave 5.

As it is unlikely you'll get a round number with this, I rely on adjusting the MIDI pitch wheel. Now it's a case of detecting attacks (when the volume of a channel increases) and releases (when the volume of a channel is set to 0) to create MIDI key press and release messages.
Percussion (white noise from the PSG) is handled the same way, except that instead of using mapping frequencies to keys and pitch wheels it plays one of 3 different drums corresponding to the 3 different pitches of noise.

It works fairly well, and you can download the software and source from the site here.
You can also listen to some samples: Power Strike 2 (GG) - Weapon Select Ristar (GG) - Du-Di-Da! Marble Madness - Tune 09 The Flash - Trickster for Mayor Sega Chess - White Wins Sonic Chaos - Mecha Green Hill Zone Sonic the Hedgehog - Bonus Zone Ys - Holders of Power

The problem with sound-related apps is that they don't provide very interesting screenshots, so I took on a little side project that I hoped would. (After all, journals are a bit dull if they're plain text).

Yep, it's that 3MHz, 32-colour, 8KB powerhouse the Sega Game Gear again.
Looking on pouet.net, there is only one Game Gear demo on there. If I haven't got it in me to complete a full-blown game, I can at least try and contribute something. [rolleyes] After all, it's a relatively simple Z80 system to write programs for.

Have the typically poorly-shot video to see it in action:

Video: 00'51" WMV [964KB]

(The odd flickering horizontal band that sometimes appears is a case of me using up the 8 sprite per scanline ration).

## Further Brass development

One of my ongoing projects is Brass, a Z80 assembler.

The newest release adds all sort of goodness, especially nested modules - for example: .nestmodules
.local
.module Animals

.module Cat
Legs = 4
.endmodule

.echo "Humans have ", Human.Legs, " legs.\n"

.module Human
Legs = 2
.module Brother
Age = 17
.echo "My sister is ", Animals.Human.Sister.Age, " years old.\n"
.endmodule
.module Sister
Age = 21
.global
Arms = 2
.endglobal
.endmodule
.endmodule

.module Spider
Legs = 8
.echo "A spider has ", Legs, " legs.\n"
.endmodule

.endmodule

.echo "Cats have ", Animals.Cat.Legs, " legs.\n"
.echo "My brother is ", Animals.Human.Brother.Age, " years old.\n"
.echo "My sister has ", Arms, " arms (global!)\n"
It also now allows for unsquished binaries (where each byte is expanded to two ASCII characters - the hexadecimal representation of the byte. This is used in native TI-83 programs).

I'm trying to unify (to some extent) 82, 83 and 83+ programming (as the hardware is fairly standard between them) - hopefully, fairly carefully written source code should be able to be assembled to 82, 83 and 83+ binaries for a variety of shells with a single keypress from Latenite. TI haven't made this easy with large inconsistency between system call names and variable names...

## DOOM Source

You can download the source to the DOOM project below here.
It's a real mess in places, mainly in the way it loads level geometry. DirectX initialisation is hard-coded with no fallback code if some feature is missing or unsupported.

Usage: DOOM
eg:
DOOM DOOM2.WAD MAP31
DOOM DOOM.WAD E1M3
DOOM TNT.WAD MAP03

Keys: use the cursor keys to move around, A and Z to move up and down.

Broken:
Floor splitting is abysmal. It is highly inefficient and occasionally wrong. The floor splitting code breaks sectors into horizontal spans which it then splits into triangles - uneven vertex placement results in largish cracks between sectors or "hairline" dancing-pixel cracks inside sectors. Large holes are usually the case of a sector that "overflows" another one. Ideally; decode floors via ssector/seg information or just use glBSP. Timing (level animation) is done via the primitive Timer class. If something starts to hog CPU time, the timers all slow down leading to inconsistent animation speeds. Scrolling walls and light effects are controlled differently; sector light effects (which affect a large amount of geometry) save the vertex array written to the vertex buffer away; this array is changed then sent to a vertex buffer, overwriting the existing data. The scrolling walls (affect a small amount of geometry) read the vertex buffer, alter the texture coordinates, then write it back. This results in a confict; if a linedef has both scrolling AND lighting effects, every time the light level changes the scroll offset is reset to the original linedef's value. Skies that occlude geometry that is visible normally aren't handled correctly. Player start angle is occasionally backwards. Some of the more bizarre walls still don't display properly.

## Variable height sectors

I switched from 16/8 division to 24/16 division, and wall heights are now calculated rather than being dragged off a lookup table.

There's also backface culling (the cylinder around the central cube is marked as double-sided, hence no culling there) but there is still no clipping or occlusion.

@philipptr: I'm guessing your technique is to calculate the angle of the point, add an offset to it, then convert back into a new coordinate? I just adapted some 3D point rotation code I had, simplifying (mathematically) it and removing any references to z.

I will probably not use textured walls - not so much for performance issues, but for aesthetic reasons. Scaling a texture down on a wall displayed on a 96x64 monochrome display (without antialiasing) looks pretty ugly. Simple lines look a lot cleaner.

Oh, and hello aCiD2 [wink]

## Oldschool 3D

All this DOOM work had got me interested again in simple 3D engines, and so thoughts turned to my favourite Z80 platform, the TI-83 Plus.

There are a handful of 3D engines out there for it already; Matt3D is a vector-based wireframe engine put to good use in a roller-coaster builder/simulation game and a racing game (that even supports two-player over the link port). However, it's 8-bit and so worlds created with it tend to be very small or distorted thanks to low-resolution (I tried writing a Quake game with it years ago).

The best "wall" engines (displaying a 3D world rather than a 3D object) have been the raycasters. Gemini impresses me the most; it's a Wolfenstein-level engine with objects, sliding "half-block" doors and moving wall blocks, all neatly texture-mapped.

I'm going to try, therefore, to get a vector-based "wall" engine (there must be a proper term for it) up and running. Keeping the map to 2D simplifies the maths a lot; hopefully with a few tricks things should be fast enough! Also, I'll use 16-bit arithmetic throughout to enhance the quality and scale of the maps.

Like DOOM, I'm isolating the vertices from the wall definitions. This way, I can cut down on the number of rotations/transformations required. The maths required to rotate a point around the origin is fairly simple;

Xr = Xo x sin(a) + Yo x cos(a)
Yr = Xo x cos(a) - Yo x sin(a)

For the moment, I'll use a simple lookup-table for wall heights. Ultimately, I'd like to have variable height walls, but these will do for the moment:

The translated X is just 48+(Xr/Yr), as the screen is 96 pixels wide.

Throwing in a few extra lines for walls...

As you might be able to see, there is no clipping if any part of a wall falls outside the viewing range. Also, the walls are not occluding eachother, something you'd really want.

## Lights, camera - action!

I've also added a MUS lump (music format used by DOOM) player, using the MIDI message functions in winmm. The only feature left unsupported is the pitch wheel, as I can't quite work out the MIDI command for it.

If somebody could please explain the logic behind MUS, please tell me. It's essentially the same as MIDI, except each command has a different byte (so you have to convert to the correct byte first), uses channel 15 for percussion (so you have to swap 15 with 9 and 9 with 15 - as 9 is the MIDI percussion channel) and uses a controller for a patch change, whereas MIDI has a specific patch change command rather than lumping it with the controllers.

## Making the world live

It might have looked liked I'd got all the walls drawing and texturing correctly, some particular combinations were still wrong.
Some walls looked completely wrong until you got up close, and with a flickering burst of z-fighting they'd resolve themselves. Or rather, fuzzy patches would resolve themselves.
This would not do!

Here's a good (or bad) example of where things were going wrong:

Something tells me I shouldn't hire the UAC's architect. The problem, it turns out, is the way connected sectors handle which coordinates to use for upper, middle and lower sections.

I was using the current sector's ceiling and floor height as the midsection, and the areas between the two sectors' ceilings or floors as the upper and lower coordinates.

The problem is if the current sector is taller than the adjacent sector. This makes the "middle" section very tall, and the upper and lower sections end up folding back down on themselves - like this:

I tried all combinations of ordering, none of which worked - either rendering that area correctly and removing old healthy walls; or displaying correctly but with texture coordinates awry. The solution in the end was very simple - sort the four height levels into TopCeiling, MidCeiling, MidFloor, and LowFloor.

Two things in that image are wrong. Most noticably - no alpha blending on the walls. You should be able to see through that grille. The second is that in the real DOOM, you can see under the grille itself.

The latter is quite easy to fix; looking in my WAD spec guide, I see:There are some transparent textures which can be used as middle textures on 2-sided sidedefs (between sectors). These textures need to be composed of a single patch (see [8-4]), and note that on a very tall wall, they will NOT be tiled. Only one will be placed, at the spot determined by the "lower unpegged" flag being on/off and the sidedef's y offset. And if a transparent texture is used as an upper or lower texture, then the good old "Tutti Frutti" effect will have its way.

In short; if the ceiling_height - floor_height > texture_height then floor_height = ceiling_height - texture_height (the Z axis, floor height, points "upwards", so ceilings have a greater Z coordinate than floors).

As for transparency, my aim to keeping this simple prevailed. Rather than sort the walls from back to front manually, I isolate the vertex buffers relating to textures with transparent areas and draw them after all the other walls. This would still lead to problems where looking through one transparent wall at another would leave holes in the far wall if it had been drawn afterwards, so I draw all the walls twice - the first time with z-writes disabled, the second with them reenabled.

The entry room from E1M3 with alpha blending and fixed wall coordinates looks like this, now:

I bought the collector's edition of DOOM for this project (it's quite cheap) and picked it up over the weekend. Mostly because it had Final DOOM on it (I only had Ultimate DOOM and DOOM II), but also because it claimed to run on Windows XP. It didn't even list DOS as a supported platform.

I was surprised to find that it's just DOOM95, which quite simply doesn't work on Windows XP. Neither does it work on 2000. The mouse doesn't work, and the colours are all wrong. Using XP's compatibility mode and 256 colour mode seems to cure it for a few minutes before it goes all weird again. (I don't know if it's entirely graphics-card related, as DOOM95 ran fine under Windows 95 on my old PC - then went funky colours after upgrading to 2000).

There is a relevance to this - loading the Plutonium WAD file slowed my PC right down for about 3 minutes - though the CPU usage was extremely low. The reason? A slight memory hogging issue...

Turns out it was trying to decode some extra files that had appeared in the WAD file as images, with dimensions of 10,000 by 10,000 or greater. No wonder it was consuming memory at such a rate!

Loading the Plutonia WAD also showed this error:

The pillar isn't really meant to be a pillar - in reality, only the top skull is meant to display. This sounds a bit like a middle section not tiling glitch - and looking at the lines that make up the skull, each is marked as double sided. Double sided lines are generally used for transparent wall sections - so rather than check the transparency of a wall section, I check to see if it's double sided or not.

Much better!

Two things that made the DOOM environment come to life - excluding, of course, actually coming to life and moving around - were the changes in sector lighting and animated textures on floors and walls.

Adjusting the light level of sectors - blinking or pulsating lights, for example - would require quite a lot of code. On the other hand, all the animated textures need is a bit of extra code on the WAD parser to work out which textures come between the animated texture markers and a bit of extra code on the renderer to swap texture indices around every ~300ms.

Well, that's the easy bit done. Interesting to note, however, that Doomsday (jDoom) - or at least my copy of it - does not handle animated textures correctly. A particular set of textures used in Final DOOM - the spinning tape drives on computers - appear static in jDoom, or only use the first few frames.

Currently, the level's geometry is split up by texture - so I cycle through each different texture, setting it as current, then sending all the geometry that uses that texture (as a vertex buffer) to be drawn. Better control over sectors would mean that I should split by texture, then by sector.

Unfortunately, my attempt resulted in a, uh, slight performance hit. By slight, I mean from ~370FPS on my Radeon 9550 to ~4FPS. Whoops!

It turns out that I was accidentally repeating each sector - even with that fixed, I was still at about 50FPS.

It would probably be simpler - not to mention faster - to order the vertices in sector order, but to record the offset and length inside the vertex buffer for each sector so that should I need to change it I could very easily lock and update it. To put it more clearly, I should maintain a list, for each sector, recording which vertex buffers form part of it and the offset and length (in bytes) to the vertices specific to that sector. That way I can easily (not to mention quickly) tweak the vertices to my heart's content - be it adjusting their Color property to flicker lights, or their heights to open and close doors.

## No more holes.

With the exception of some very bizarrely shaped sectors, I've patched up the holes by making the floor splitter a bit more intelligent. It also removes some redundancies.
The more exotic levels generate about 5000 triangles, which is fairly reasonable.

Now I need to do battle with the physically impossible levels DOOM creates...

In DOOM, skies and floors are actually marked as the special texture SKY_1. However, rather than drawing it using the normal mapping, it's drawn fixed to the background.

I'm currently just drawing a sky cylinder, and not drawing ceilings where the sky texture would normally be. I need to find some way of masking out the buildings that shouldn't be visible.

## World's Worst Sector Splitter

As with many programming problems, a potential solution presents itself at about 2AM when you suddenly awake and hunt paper and pencil before the brilliant idea vanishes into the groggy murk that is forgetfulness.

In this instance, a solution did present itself, but it's far from 'brilliant'.

I was pondering DOOM's floors, and how to split them up. I came up with a simple, optimal solution - optimal, provided that the sector I was splitting into triangles was a rectangle with sides perfectly aligned the the (x,y) axes. Happily, DOOM follows that pattern fairly often.

Here we have the typical sector. It's sector number one from E1M1, the ground that surrounds the acid pool outside the window. As you can see, it's not entirely convex, and has a hole in the middle. I have access to the lines and points that surround the sector.

My technique runs like this: Split the sector up into horizontal spans. To do this, I cycle through every single line in the sector, and for every different Y coordinate I split the sector. For each individual span, go through each line in the sector. If it's completely outside the span, discard it. If it's partially outside (no line will ever have a point half-way inside the span), clip it to the upper/lower Y bounds. Sort these lines from left to right, then group into pairs. Each pair, when combined with the top and bottom Y boundary, forms a trapezium. Split this into two triangles - there's a part of your floor.

This method is fairly simple, and appears to work pretty well:

Note the large magenta shape at the top (maze in E1M2). That's a single sector!

The image is less pretty if I dump out an image showing the triangles generated:

Hopefully that makes my method slightly clearer.

For some areas, it does pretty well. In others (sectors not perfectly aligned to the (x,y) axes) it generates an absolutely horrible mess of triangles.

Grouping walls by texture and generating a new vertex buffer for each texture group worked pretty well, so I've done the same for floors.

The first floor test looked pretty good (minus texture coordinates):

...so I fixed it up a bit, and flipped the vertex order to support ceilings as well.

One problem I had been worried about had reared it's ugly head, however - hairline cracks between triangles, showing the lovely bright blue through the dark and dismal UAC facility. It was trivial to fix, however; replace the floating-point linear interpolation (to clip lines to the single horizontal span in the floor splitting code) with the integer-based method. This got rid of all the rounding errors, and the cracks were gone.

The floor splitting code might inefficient, but it is simple - even then, however, it does fail on certain segments resulting in slightly-larger-than-hairline cracks on certain levels.

I'm not quite sure why that's happening. I have a hunch that in certain cases there is an extra line when breaking up sectors into horizontal spans and you end up with an odd number of dividing lines. In some cases, this extra line is at the end - and it is ignored safely. If it's at the beginning, though, everything is shunted along one and the whole sector span is drawn incorrectly.

Also, (and this can be verifed), some sector floors/ceilings end up being drawn back-to-front, and fall prey to the backface culling. Ducking below the surface of the floor, you can clearly see the missing floor deciding to be a ceiling.

One potential improvement would be to split the floor twice - once in horizontal spans, once in vertical spans - and use the one that produces the least triangles.

The texturing issues from last post were fixed with a variety of code changes - some sector-to-sector wall segments were being drawn upside down (the code now flips the heights around so they're in the correct order); the light level of a sector's wall is calculated by the brightest between it and any adjacent sector; lower texture pegging fixed to align correctly.

## Managed DirectX is awesome

A few years back I had a go with DirectX 8, helped along by the excellent DirectX4VB website and tutorials.
The best thing I cranked out was a simple DOOM-ish 3D engine, minus sprites (just walls and floors).

(All images are click-for-big).

I fancied another go, this time not in VB6 but in C# and not with DirectX 8 but Managed DirectX 9.
I had an old copy of the SDK so got the obligatory spinning coloured untextured triangle going, then hunted for a proper project to learn with.

Keeping with the theme of last time's venture, I decided to go back to DOOM, but not DOOM-ish - I'd load the levels and graphics from an original DOOM WAD file. There's something oddly satisfying about pressing F5 and watch a familiar 3D world spring into view [smile]

I'd written a very simple VB.NET WAD resource viewer, so rewrote the various graphics functions in C# and dug out a hex editor to work out what goes on inside DOOM.

Each level has a similar bunch of data lumps ("lump" is the name of a single resource within the WAD); THINGS that define the location of objects inside the map, NODES for the BSP tree, VERTEXES for the vertices that make up a level and so on.

One problem with DOOM is that there is no "vectorised" floor. The walls are easy enough; by studying the LINEDEF/SIDEDEF/SECTOR lumps you can work out the VERTEXES you need to use to build up a single wall. Floors are filled in after the walls are drawn; to be able to draw a floor on 3D hardware I'd need to split down the sectors into triangles manually. For the moment I shall concentrate on walls.

A quick bit of coding later; drawing by splitting each wall into a small vertex buffer (two triangles) then drawing (one end of the wall is white, the other grey) gives me this:

Something's working... but something isn't, as well. Moving around looks odd, and no wonder; no Z-buffering!
Switching on Z-buffering and colouring each wall based on the light level of the sector gives me these rather better results:

One thing that I've never been aware of when it comes to 3D graphics are the best practices for how I send data to the graphics card. For this, I've grouped every single wall by texture. I've loaded all the textures I'll need for the walls into an array, then built up a vertex buffer for each texture's walls. When I render, I cycle through each texture, setting it as active then drawing that texture's vertex buffer. It appears to be fast enough, but then again DOOM with it's ~2000 poly levels isn't demanding much!

Texture mapping adds a lot of detail to the world... so:

The textures appear a bit odd as they're being simply stretched to fill the entire wall they're put on rather than tile neatly as DOOM intended. That's an easy enough fix:

There are still some texturing glitches, most noticeably on walls where the lower texture (the texture that spans the gap between the floors of two sectors of different heights) where they are marked as "unpegged":

I can't quite work out how the texturing is meant to go there... so I've left it for the moment, and had a bash at floors.

It appears that a combination of subsectors (SSECTORS) and segments (SEGS) can be used to represent convex polygons that make up sectors. I added a bit of code to the level loader that rips out the subsector and segment information - it produced these images:

Subsectors

Segments
This doesn't look too promising; if you look at the central part (where the green acid pool is -- this is E1M1) you can see there's a rather large area with no segments in it at all. I added a simple bit of code that broke up the segments into fans; here's the result:

The hole is very noticable in that image. Quite a few of the segments are made up of single lines (just two points), which leads me to believe that I'm thinking that they're something they're not.

For the moment DOOM has to go without floors.

• Advertisement
• Advertisement
• ### Blog Entries

• Advertisement
×

## Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!