#8505 - johnny_north - Sat Jul 12, 2003 3:11 pm
I'm trying to implement a spotlight effect similar to the one used in the Super Mario snes game. I decided to try this method:
use mode 1 and one of its scaleable bgs
load a map and tiles that have a circle drawn in RGB(0,0,0) and the rest of the tiles RGB(1,1,1) so the spotlight is transparent
center the "spot light" on the target area
scale sufficiently far out so that the entire screen in the "spot light"
slowly scale in until the "spotlight" is as small as I want it
Everything works fairly well using the BIOS affine call, but even when I sync to the vblank, the spot effect doen't happen as smoothly as I'd like. I've tried scalling by different amounts, I'd like to achieve the same smoothness as the Mario games. Any ideas?
typedef struct tbgaffinesource
{
s32 x; //Original data's center X coordinate (8bit frac portion)
s32 y; //Original data's center Y coordinate (8bit frac portion)
s16 tx; //Display's center X coordinate
s16 ty; //Display's center Y coordinate
s16 sx; //Scaling ratio in X direction (8bit fractional portion)
s16 sy; //Scaling ratio in Y direction (8bit fractional portion)
u16 r; //Angle of rotation (8bit fract portion) Effect Range 0-FFFF
} bgaffinesource;
typedef struct tbgaffinedest
{
s16 pa; //Difference in X coordinate along same line
s16 pb; //Difference in X coordinate along next line
s16 pc; //Difference in Y coordinate along same line
s16 pd; //Difference in Y coordinate along next line
s32 x; //Start X coordinate
s32 y; //Start Y coordinate
} bgaffinedest;
//the scaling code
for (int i=0; i<256; i+=0x10){
RotateBackground(&bg[2], 0,208, 113, i);
}
void Background::RotateBackground(Bg* bg, int angle,int center_x, int center_y, FIXED zoom)
{
bgaffinesource src_bga;
bgaffinedest dest_bga;
src_bga.x = 209*256;
src_bga.y = 115*256;
src_bga.tx = center_x;
src_bga.ty = center_y;
src_bga.sx = zoom;
src_bga.sy = zoom;
src_bga.r = angle;
swi_bg_affine_set(&src_bga, &dest_bga);
while (*(volatile unsigned short *)0x04000006 != 160);
*(unsigned long *) 0x04000028 = dest_bga.x;
*(unsigned long *) 0x0400002C = dest_bga.y;
*(unsigned short *)0x04000020 = dest_bga.pa;
*(unsigned short *)0x04000022 = dest_bga.pb;
*(unsigned short *)0x04000024 = dest_bga.pc;
*(unsigned short *)0x04000026 = dest_bga.pd;
}
#8506 - Daikath - Sat Jul 12, 2003 3:17 pm
This is a laymans advice here but have you tried fixed point match so you can let it increment by less then a whole integer?
_________________
?There are no stupid questions but there are a LOT of inquisitive idiots.?
#8508 - DekuTree64 - Sat Jul 12, 2003 3:35 pm
That's done with the window registers. Just make a solid black BG, set BLDMOD to make it slightly transparent, and set up a table of WIN0H values to DMA in on HBlank. Set WIN0V to the source of the light for the top, and the bottom of the screen for the bottom. Then set WINOUT to all BG layers, including the black one, and WININ to all BG layers except for the black one, so anything outside the window is darkened by it, and anything inside looks normal.
To make the table of WIN0H values, make a for loop from the light's height to the bottom of the screen. First start with just a still light (not moving back and fourth like the one in Mario), so you just have to interpolate between the starting and endign X values. So take like 120 (center of the screen) as the starting X position, and then 120 + lightWidth/2 and 120 - lightWidth/2 for the ending X points. Then you take (endPointX - startPointX) / (160 - startPointY) and add that to your horizontal position each time (keep a startX and an endX, and a startXInc and endXInc), and then set the entry in your table to startX | (endX << 8). Then DMA in on HBLank, and voila, you have a light.
#8513 - johnny_north - Sat Jul 12, 2003 6:09 pm
Know of any source code example of this? Just to make it easier?
#8519 - johnny_north - Sat Jul 12, 2003 9:33 pm
DekuTree64-
I know explaining this stuff is tedious at best, but I can't figure from your explanation how this would work using only one of the windows. I could see how it might be accomplished using both windows and over laping them. Am I wrong thinking that each window has 2 variable edges and 2 fixed edges? It would seem like only a 1/2 circle per window could be made.
#8524 - DekuTree64 - Sun Jul 13, 2003 12:36 am
Sorry, that explanation was knida rushed, so it could have been a lot clearer. Basically a window is a square, you can set the top, bottom, left and right coordinates of it, so there aren't any 'fixed' sides. making shapes other than a square is sort of a cheat, as you just change the left/right edges of the square each scanline, so it looks like a different shape, but really it's just a square that you keep changing the width of down the screen.
Also note that changing the vertical position on HBlank is kind of pointless, since you're going down one full line at a time, so you might as well have set it to wherever your shape starts/ends in the first place. Unless you're trying to drawing 2 shapes with one window, of course, but I don't think I've ever seen that done before.
Buf anyway, I don't know of any examples, so I'll just make one up. This will generate a table for a spotlight start starts at the pixel xStart, yStart, and goes down to the bottom of the screen, to where the edges will be at positions xEndLeft and xEndRight when it hits the bottom.
It uses a little fixed-point math, but it's not too hard. If you've done any algebra, think of 65536 as the variable x. You can have like 4x + 9x, which is 13x. The x just stays there, and you don't have to pay too much attention to it. Same for subtraction. Then for multiplication, 4x * 9x would give you 36x^2 (that's x squared), because multiplication is basically just adding factors, and since you have a 4, an x, a 9, and another x, you end up with 36 * x * x, or 36x^2. In order to get back to having a single x, you have to divide by x, so 36x^2/x = 36x. So divide by 65536 after multiplying. Division is like subtracting factors, so take 12x / 3x for example, and you get 4. Your factors are 4, 3, and x, divided by 3 and x. You take out a 3 and an x, and you're left with the 4. So you need to multiply by x to get back to where you can work with other x's. But since we're working with integers (no fractions/decimals) you need to do the multiply by x before dividing. It's a problem with 16-bit accuracy though, cause if you multiply by 65536 again you're shifted 32 total bits to the left, so you lose your whole integer portion. So what I do is divide the divisor by 256, which is the square root of 65536 (65536=1<<16, 256=1<<8), so it's like 12x / 3sqrt(x), which leaves you with 4sqrt(x), so you can multiply by sqrt(x) again to get back to a full x. So basically you take ((4<<16) / ((3<<16)>>8))<<8. The GBA has a 64-bit multiply instruction, so ending up with a <<32 number after ultiplying 2 <<16 ones together isn;t a problem, but you have to write a multiply function in ASM to use it. For now, use something like (((3<<16)>>8) * ((4<<16)>>8), which would get you 12<<16, which is what you want.
But anyway, I hope you really didn't know fixed-point, or explaning that was a waste of effort. Didn't mean to make it so long, but it happened anyway.
Now, on with the code.
Code: |
void MakeSpotlightTable(u16 *winHTable, u32 xStart, u32 yStart, u32 xEndLeft, u32 xEndRight)
{
s32 i, xl, xr, xlInc, xrInc;
xl = xStart << 16;
xr = xStart << 16;
xlInc = ((xEndLeft - xStart) << 16) / (160 - yStart); //yStart isn't shifted by 16, so you can just divide like normal
xrInc = ((xEndRight - xStart) << 16) / (160 - yStart);
for(i = yStart; i < 160; i++)
{
winHTable[i] = (xl >> 16) | ((xr >> 16) << 8);
xl += xlInc;
xr += xrInc;
}
}
|
And set up a DMA that transfers 16 bits at a time to REG_WIN0H on HBL, and repeats, with the dest address fixed, and the source address incrementing. Don't forget to reset the source each frame though.
Does that help at all? I could write the whole thing, setting up the window registers and stuff, but I figure that's easy enough to do.
#8557 - headspin - Mon Jul 14, 2003 1:47 pm
Darkcloud has some example code using windows of various shapes, just need to adjust the code to the shape of a circle..
http://sinewave3.tripod.com/ScreenTutorial/screentutpg1.html
#8804 - johnny_north - Sun Jul 20, 2003 5:09 pm
Thanks guys for the info the detailed instruction, the source and esp. pointing out my incorrect assumption. I need one more bit of help.
The spotlight effect works great using the hblank and an array of "spotlight" values. These values are stored in an array of scanline/win0 l&r pairs in rom in an array called circle[]. Right now the spot starts large and narrows in on a sprite using the hblank and values from circle[]. The problem is that (all operations that follow this code after the hblank irq is turned off) all tile copies to vram fail (possibly all vram activity fails). This is the pertainant code:
while(REG_VCOUNT<160){}; //wait until the vblank
REG_WIN0H = 239; //set the window to wide open
REG_WIN0V = 159;
REG_WININ = BIT0|BIT4; //inside win should show bg0 and sprites
REG_WINOUT = BIT1; //outside is a black bg
REG_DISPCNT |= BIT13; //turn on win0
ShowBG(1); //show the black bg
REG_IME = 0x00; // Disable interrupts
REG_IE |= BIT1; // Enable H-Blank IRQ.
REG_DISPSTAT |= BIT4; // Enable Display H-Blank IRQ also.
REG_IME = BIT0; // Enable interrupts
cnectr = 0; //global class counter into an array of
//left and right win0/scanline values
while(cnectr < 13920){} //wait until the hblank stuff is finished
REG_DISPCNT &= ~BIT13; //disable win0
REG_IME = 0x00; //Disable interrupts
REG_DISPSTAT &= ~BIT4; //disable Display H-Blank IRQ also.
REG_IE &= ~BIT1; //disable H-Blank IRQ.
REG_IME = BIT0; //Enable interrupts
void Background::hbl(){
u16 windowSettings = 0; //dimensions of the window
static u16 circleIndex = 0; //index into the circle[] array
if(cnectr >= 13920) return; //don't run off the end of circle[]
if(REG_VCOUNT <115) circleIndex+=2; //if we are in the top half of cir
else if(REG_VCOUNT>159)return; //if we are in the vblank return
else circleIndex-=2; //we are drawing the bottom 1/2 of cir
windowSettings = (circle[circleIndex]<<8| circle[circleIndex+1]);
DMA_Copy(3, (void*)&windowSettings, (void*)®_WIN0H, 1, DMA_16NOW);
if(REG_VCOUNT == 159){ //do this after each screen pass
cnectr+=232; //increment to the next circle radius
circleIndex = cnectr; //set the circle[] index to this val
}
}
The suspect code seems to be the code that turns on the hblank irq. If this code executes, even if hbl() does nothing, the vram functions following(atleast) are corrupt. I suspect there is another step I'm not doing to reset from an hblank.
Let me know if you see a fix.