Jump to content


  • Content Count

  • Joined

  • Last visited

Community Reputation

248 Excellent

About JohnPCAE

  • Rank
  1. Yes, I think this would do the trick!
  2. Here are a couple of thoughts I have, but I think I need some feedback on them. I'm looking at enabling the device by writing a magic value to Inty address $007F (assuming that address doesn't conflict with anything else). It would be divided up like this: fbs3 fbs2 fbs1 fbs0 bs1 bs0 magic The magic field is 10 bits wide. Writing a specific value to it would enable access to the frame buffer, which would always be at $D000. The frame buffer is 16k words in size, and up to 8k of it would be visible at a time. Fields bs0 and bs1 would determine how much of the buffer is visible: 1k, 2k, 4k, or 8k. Fields fbs0-fbs3 would determine the starting origin in the frame buffer in 1k chunks (so from 0k to 15k, wrapping around at 16k). Certain magic areas in the frame buffer would configure the display: screen mode, column/row blanking, column/row offset, glyph offset, and whatever else I can think of. I'm guessing that 16 locations should be enough. Reading a zero/nonzero value from $007F would tell you if the device was active. I'm not sure which should indicate which is which: I need to know what an Inty would normally read from that location first before I can design it in. I'm also considering adding address $007E as a sort of general-purpose read/write user port, which would go to a 16-bit expansion bus at the back. The idea is then you could easily add, for instance PS/2 keyboard or mouse connectivity, or whatever else strikes your fancy. This is just tentative though. At some point I'm going to need some sort of Inty program that lets me peek and poke to memory so I can test it. I don't know if something like that exists.
  3. No breadboards! This includes text and graphics mode support. The small cart board needed a little bit of rework to stabilize my PLL clock generator, which I'll put in its next rev. The only hardware piece left is to design the real RAM interface board that lets the Inty control it. It would replace the narrow board in the foreground that has the 7026J RAM chip on it. That will make the hardware feature-complete and then it's on to finalizing the microcontroller code. The FINAL final step (and I'm not sure I want to do this or leave it to someone else) is to put all this into an FPGA to bring the cost down. By my calculations a total of 31 5V I/O pins would be required, with 8 output, 7 input, and 16 bidirectional. The internals would run at exactly MCLKx4, synchronized with MCLK via PLL.
  4. These are the results of tweaking DAC output values: Intellivision I: Intellivision II: The major reason why I can't achieve really bright colors like yellow on an Inty I is because of the 3k R24 resistor on the Inty motherboard, and why the Inty II is so much brighter. It means that games will have to let users set which palette they want based on which console they have. An Inty II can achieve the dimmer look of the Inty 1 in software but not vice versa. The circuit I'm using tries to maximize the brightness on an Inty I, which results in blown-out colors on an Inty II with the same palette settings. However, I'm providing eight bits of granularity for each color timeslice as opposed to just 4 from the 8915 chip, so there is a lot of flexibility for dimming colors. I don't have an Inty I with the System Changer mod, but from what I see of the schematic after the change it should look identical to the output from an Inty II.
  5. It took a lot of changes to power and ground distribution, but I finally have stable color output! The previous screenshot was a very rare stable frame. Most of the time it was all over the place. The next step is brightness. I need to make the output much brighter. After that it's on to the graphics-mode daughtercard, memory interface to the Master Component, and finalizing capabilities.
  6. I haven't seen any notification of a refund, at least on AA. If he sent it to my email it could have become lost in the endless spam it gets.
  7. I haven't yet come up with a foolproof method for eliminating the missing corner lines without really killing performance, but in the meantime I added an instruction to my multiplication routine to improve its accuracy. It simply rounds the result instead of just chopping off the lower 8 bits. It seems like the missing corner lines happen a lot less often now. raycast_20190214.zip
  8. EDIT: never mind. I finally noticed the flashing counter when you get hit.
  9. Looks amazing. One question: maybe add an indicator the boss' health in boss fights?
  10. I understand the math. I should point out that I'm using similar math in my distance calculation: rayDirX is cos(angle) and rayDirY is sin(angle). In calculating the distance above, it divides by rayDirX or rayDirY, respectively, which is equivalent to multiplying by sec(angle) or csc(angle) respectively -- which is exactly what my code does. The only major difference is in how the code steps from one block to the next: I'm tracing two rays at once whereas the algorithm above alternates on which ray to trace based on distance traveled. That aspect is probably something I should look at. A few months ago I couldn't have used that approach because I wouldn't know which face a ray impacted -- as is the case above. However, with everything broken out now into special cases based on the direction of the ray, I should now be able to determine it by implication.
  11. It is using the DDA approach: after that initial calculation for the two rays, it then always increments by one X unit or one Y unit. Everything I described above relates to the part midway down the page where he calculates the step and initial sideDist values (that page was actually my starting point for the algorithm). The major difference is in how I represent walls in the WorldMap array. The algorithm you linked to has every square as completely solid, which only requires a single 2D array to represent the map (I actually started out with the same scheme at first). My map is represented as four *separate* arrays, with each array representing the walls only along a certain edge of each square (top, bottom, left, and right edges). This allows a square to have walls of different colors depending on what edge you're viewing, as well as potentially making doorways possible. This approach requires that the initial collision check calculate the actual position along the X or Y step value instead of just the distance to that point. It also is why I have to use two rays instead of just one. Maybe the answer lies in revisiting how walls are represented?
  12. I hate it when my allergies keep me up. I especially hate it when I'm supposed to work the next day. Until the Allegra-D kicks in, I decided to do some sleuthing. I hand-calculated the values it's using for the screenshot you posted and it's indeed due to roundoff error. These are the values in the screenshot: X 0DEE Y 0E66 A 010A I should probably point out what coordinate values really mean here. All position values are represented in signed 8.8 fixed-point format: the high 8 bits represent the integer part of the value and the low 8 bits represent the fractional part of the value. The meaning of these values with respect to the source code is as follows: XPos 0DEE (in fixed-point form it equates to 0D.EE, or 13.9296875 in decimal) YPos 0E66 (in fixed-point form it equates to 0E.66, or 12.3984375 in decimal) Angle 010A (266 decimal, which equates to 199.5 degrees) The column on the screen with the problem is column 22 (the left edge is column 0 and the right edge is column 39). The way the individual column angles are calculated is as follows: the exact center of the screen corresponds to the A value given above (199.5 degrees in this case). This leaves 20 columns on either side of that angle. In the engine, there are 480 angle "units" to a full circle, or in other words each unit equals 0.75 degrees (that's how I go from A=266 to 199.5 degrees -- multiply by 0.75). Starting from the center of the screen, the column immediately to the left is at angle A - 1, or in degrees, the central angle minus 0.75. Therefore, column 19 represents an angle of 199.5 - 0.75 = 198.75 degrees. Likewise, column 20 represents an angle of 199.5 + 0.75 = 200.25 degrees. For each subsequent column, the angle unit value increments by TWO. The reason for this is that the angle units are scaled for rendering a bitmap that is 80 pixels wide with a 60-degree field of view (the bitmap-mode view that initially displays). That's where the 0.75 multiplier comes from. However, in colored-squares mode the bitmap has only half the pixels, so the units must increment by two from one column to the next to represent the same 60-degree field of view. Putting all this together, the angle units range from A - 39 for column 0 to A + 39 for column 39, or in degrees, Angle - 29.25 to Angle + 29.25 (giving a 58.5-degree field of view). Given this, column 22 (the problematic one in this case) represents a ray angle of A + 5, or 203.25 degrees. When casting rays, the engine actually casts two rays: one that tests at integer X values and one that tests at integer Y values. Testing at integer X values lets it detect walls that face east or west, and testing at integer Y values lets it detect walls that face north or south. It does this by maintaining two pairs of coordinates: (XNext, YEdge) coordinates where XNext is an integer, i.e. XNext & $00FF = 0 (XEdge, YNext) coordinates where YNext is an integer, i.e. YNext & $00FF = 0 For the first pair of tests, the engine has to look at the viewer's position and calculate where the rays would be at the edges of the "box" that the viewer is currently occupying (the maze is 16 boxes by 16 boxes, where the fixed-point representation of position essentially subdivides each box into a 256x256 grid). In this particular example, the viewer's X position is 0DEE, which means box #13 and a fractional position of $00EE. So for a test where XNext is an integer (0D00), it has to calculate what the Y value would be. Likewise, since the viewer is at Y position 0E66, if the ray is to be at Y = 0E00, it has to calculate what the X value would be. This is where roundoff error becomes a problem. When looking along this angle (west-northwest), the formulas for XEdge and YEdge are as follows: XEdge = XPos - frac(YPos) * cot(CurAng) YEdge = YPos - frac(XPos) * tan(CurAng) So each formula takes the fractional part of the position (which determines the distance to the edge of the box -- get it?), multiplies it by the slope (or the inverse of the slope, depending on which coordinate we are calculating), and then adjusts the coordinate accordingly. Pretty simple. So what actually happens? The program has tables for lots of trigonometric functions, including tan and cos. Each table contains 480 entries, which correspond to the 480 possible angular values. Like everything else, they contain values in 8.8 fixed-point format, which introduces the first level of roundoff error -- limited precision. In this example, cot(CurAng) and tan(CurAng) are: cot = 0253 (02.53 if you think in fixed-point terms, or 2.32421875) tan = 006E (00.6E if you think in fixed-point terms, or 0.4296875) By comparison, the actual values that Calculator returns are: cot(203.25) = 2.3275630393965955716880472651463 tan(203.25) = 0.42963390596683602666819711641139 Then it has to multiply them by the fractional parts of YPos and XPos, respectively: frac(YPos) * cot(CurAng) = 00.66 * 02.53 frac(XPos) * tan(CurAng) = 00.EE * 00.6E We can hand-calculate the result by ignoring the decimal points and just multiplying the raw values together, then shift the result right by 8 bits: 0066 * 0253 = 00ED 00EE * 006E = 0066 Subtracting from XPos and YPos, respectively, we get the final values: XEdge = XPos - 00ED = 0D01 YEdge = YPos - 0066 = 0E00 So the coordinates to be tested for the two rays are: (XNext, YEdge) = (0D00, 0E00) (XEdge, YNext) = (0D01, 0E00) If you look at the map definition at the bottom of the source, neither of these coordinates results in a wall hit. They also don't match, which is a red flag. The first ray is landing exactly at the corner (which is problematic in and of itself and can be a separate cause of a blank column). However, that's not the problem in this case. The problem is that it *shouldn't* be landing at that position at all. The second ray is landing just to the right of it. That's the ray that will move up in integral values of Y. If that is the correct ray, it implies that the first ray should be landing somewhere above Y=0E00. We can get a hint by increasing our precision. Instead of shifting the result right by 8 bits when multiplying, let's see what happens when we keep the entire result: 0066 * 0253 = ED12 00EE * 006E = 6644 We can think of it in fixed-point terms as this: 00.66 * 02.53 = 00.ED12 00.EE * 00.6E = 00.6644 Now if we subtract those from XPos and YPos, we get something different: XEdge = XPos - 00ED[12] = 0D00[EE] (think of it as 0D.00EE) YEdge = YPos - 0066[44] = 0DFF[bC] (think of it as 0D.FFBC) XEdge shifted left a tiny bit, but the "box" is still 0D, which doesn't give a wall hit. However, YEdge moved up a tiny bit, enough to move it up to the next "box", which DOES give a wall hit. The extra precision basically brought the two rays closer together. So precision is a problem here.
  13. Yeah that phenomenon has been driving me batty for years. I think it might be due to roundoff error. If anyone can find a solution, I'm all ears!
  14. *sigh* I was simplifying some of the macros and found another bug that I had to fix. However, I also squeezed out a few more unnecessary instructions from the raycaster, so it's not all bad... raycast_20190117.zip
  • Create New...