Bullet Spray routines

Share graphical tips, notes and queries related to our favourite screen layout and its editors.
User avatar
lexi
Dizzy
Posts: 63
Joined: Mon Dec 04, 2023 10:55 pm
Contact:

Bullet Spray routines

Post by lexi »

Hi! Does anyone know of any good bullet spray routines i could borrow some bytes or ideas from? Musing how much effort it would be to do something like Gravity Force II on the speccy.
User avatar
R-Tape
Site Admin
Posts: 6409
Joined: Thu Nov 09, 2017 11:46 am

Re: Bullet Spray routines

Post by R-Tape »

lexi wrote: Mon Dec 04, 2023 11:42 pm Hi! Does anyone know of any good bullet spray routines i could borrow some bytes or ideas from? Musing how much effort it would be to do something like Gravity Force II on the speccy.
Is it more the vector aspect you're after, or the bullet-hell part? In Jon Cauldwell's book, there's a section on vector movement, and if you're up for disasssembling, there's Lunaris which is pretty much GF2 by the looks of it! There's some vector table code from my own game, Tardigrade, the source code is at the bottom of the page under 'additional file downloads'.
Ralf
Rick Dangerous
Posts: 2289
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Bullet Spray routines

Post by Ralf »

If you want to have a bullet flying from any point to another point then you will in most cases need the old, good Bresenham algorithm
https://en.wikipedia.org/wiki/Bresenham ... _algorithm

Drawing a flying bullet is almost the same as drawing a line, you just don't clear the old positions in case of the line :)

Sometimes you may decide to make it easier and allows movement only on basic paths like horizontal, vertical and 45 degrees path which don't need Bresenham calculations.

And drawing 100 bullets is the same as drawing one bullet, just repeated 100 times ;)
User avatar
lexi
Dizzy
Posts: 63
Joined: Mon Dec 04, 2023 10:55 pm
Contact:

Re: Bullet Spray routines

Post by lexi »

R-Tape wrote: Tue Dec 05, 2023 9:56 am Is it more the vector aspect you're after, or the bullet-hell part?
i have my own int based vector math i'll probably use if it provides any benefit - it's the drawing 100+ bullet-pixels to screen fast enough part that strikes me as possibly both simultaneously:
  • something fascinating i could happily spend a few decades exploring and still failing to map out completely
  • a fully explored playground with don't bother pitfalls and dizzily fast screen slides already well known
but i don't really know :)

R-Tape wrote: Tue Dec 05, 2023 9:56 am In Jon Cauldwell's book, there's a section on vector movement,
Thanks so much for the links - read through the first dozen pages or so and can already tell that book is going to be a solid reference!

R-Tape wrote: Tue Dec 05, 2023 9:56 am and if you're up for disasssembling, there's Lunaris which is pretty much GF2 by the looks of it!
That is really nice!! I get a GF2 vibe from it too.

R-Tape wrote: Tue Dec 05, 2023 9:56 amThere's some vector table code from my own game, Tardigrade, the source code is at the bottom of the page under 'additional file downloads'.
I like how smooth that looks, and the effort put into making the different levels distinct.

Ralf wrote: Tue Dec 05, 2023 10:15 am If you want to have a bullet flying from any point to another point then you will in most cases need the old, good Bresenham algorithm
https://en.wikipedia.org/wiki/Bresenham ... _algorithm

Drawing a flying bullet is almost the same as drawing a line, you just don't clear the old positions in case of the line :)

Sometimes you may decide to make it easier and allows movement only on basic paths like horizontal, vertical and 45 degrees path which don't need Bresenham calculations.

And drawing 100 bullets is the same as drawing one bullet, just repeated 100 times ;)
One of my favourite little touches in GF2 is firing at an angle while accelerating, and you see how the arc of bullets morphs in response to your velocity. i kinda have a high standard for this though - i think it should be possible to achieve at minimum
  • Destructible scenery
  • 2 player local with shared+split screen
  • no sense of bullet austerity
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Bresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.

It's probably better to just use vectors but maintain fractional part (say as the lower 8 bits) to keep track of the overflow.

If bullets move too fast (so they can pass through things) you'll need line segment collision though.

I think 100 bullets may be a tad ambitious on the speccy ;)
Ralf
Rick Dangerous
Posts: 2289
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Bullet Spray routines

Post by Ralf »

I think 100 bullets may be a tad ambitious on the speccy
There is a modern game called Chibi Akumas which may reach 100 bullets at its busiest moments:
https://spectrumcomputing.co.uk/entry/3 ... ibi_Akumas

Click the link and check the video. About 34 minute for example.
It's very unplayable but damn, you asked for bullet hell so you have it :)
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Well yeah it looks bad and chugs terribly.

I heard he had a big sulk and left here (his website articles about z80 programming are very good though).
Timmy
Manic Miner
Posts: 230
Joined: Sat Apr 23, 2022 7:13 pm
Location: The Netherlands

Re: Bullet Spray routines

Post by Timmy »

Let's just say that it's much easier to get lots of bullets on screen, if you're going to do this on a PC.

You will probably need a lot of tricks to get lots of stuff on screen on a Spectrum.

There are many games that can get a lots of bullets on screen, although most of the time they are horizontal bullets for speed reasons.

Rex or Cybernoid or even (sorry for the plug) Future Looter can easily do lots of stuff on screen, you should probably just look at them.

I'm not really a bullet hell fan though.

EDIT: By the way, Gravity Force II? Is that the game that looks like that Thrust game on the Spectrum?
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: Bullet Spray routines

Post by Wall_Axe »

if each bullet was one pixel that would be easy to draw. It might provide a challenge...but I dont know how playable that would be overall.

Are these 100 bullets going to come from a boss? if so, you'd have to decide are they going to shoot bullets out at the 8 traditional directions..or at every angle in a circle.(one for every 3 degrees)

After looking at gravity force it seems like most of the bullets will be coming from turrets? and some from the player.

i did implement line of sight code for turrets a long time ago, in a basic type language. Also the code to make a turret turn towards a player turned out to be an absolute nightmare in a basic type language. :lol:

if i were you i'd code it in this language first:
https://blitzmax.org/

you would need code to decide if the turret was facing the player..
bresenham line algorithm to see if there is a clear path to the player
then you would need the code to turn the turret towards the player
and of course shooting, which is the easiest thing.

You'd also need yer vector library to add gravity and acceleration to the player.
User avatar
lexi
Dizzy
Posts: 63
Joined: Mon Dec 04, 2023 10:55 pm
Contact:

Re: Bullet Spray routines

Post by lexi »

Ralf wrote: Thu Dec 07, 2023 7:49 pm There is a modern game called Chibi Akumas which may reach 100 bullets at its busiest moments:
https://spectrumcomputing.co.uk/entry/3 ... ibi_Akumas

Click the link and check the video. About 34 minute for example.
It's very unplayable but damn, you asked for bullet hell so you have it :)
This is amazing - thankyou! In that it's a lot of things happening on the screen at once which is maybe 5x what i think i need. But even at 2x playspeed the animation looks less stuttery - so it gives me much hope.

Wall_Axe wrote: Thu Dec 07, 2023 9:07 pm if each bullet was one pixel that would be easy to draw. It might provide a challenge...but I dont know how playable that would be overall.

Are these 100 bullets going to come from a boss? if so, you'd have to decide are they going to shoot bullets out at the 8 traditional directions..or at every angle in a circle.(one for every 3 degrees)

After looking at gravity force it seems like most of the bullets will be coming from turrets? and some from the player.
So i realise i didn't state the problem well - this is the original creators of GF2 playing together in their modern remake:


it shows all the bullets of varying speeds flying around and hecka collision detection, but i way overestimated the number of bullets typically on screen to handle. but i picked GF2 as an example since it just requires vertical smooth scrolling and is smaller scale

Wall_Axe wrote: Thu Dec 07, 2023 9:07 pm i did implement line of sight code for turrets a long time ago, in a basic type language. Also the code to make a turret turn towards a player turned out to be an absolute nightmare in a basic type language. :lol:
was it the trig, slow pixel reads, or something else?

some basics try to be too helpful :)

Wall_Axe wrote: Thu Dec 07, 2023 9:07 pm if i were you i'd code it in this language first:
https://blitzmax.org/
ok, done: https://sourceforge.net/projects/gp2/

i love blitz basic! i have an amiga port of Chaos i wrote using it kicking around somewhere, and so much more on forgotten disks..

anyway, wrote the above in blitz basic on an coughing 486 as one of the example demos for the release of BlitzPC. shame since i was so rushed i never had time to clean up the code - worst example code for a language ever - and now it runs 20x too fast to be playable :lol:
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

You don't need trig for facing towards/which way should I turn/am I pointing directly at my target.

Dot product for the win.
User avatar
lexi
Dizzy
Posts: 63
Joined: Mon Dec 04, 2023 10:55 pm
Contact:

Re: Bullet Spray routines

Post by lexi »

nice! i'm probably gonna avoid using a cartesian coordinate system if at all possible though, e.g. https://www.shadertoy.com/view/DdVBWG
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: Bullet Spray routines

Post by Wall_Axe »

Oh that's cool that you have it done in blitz basic already.


I don't remember exactly what I did but I wanted to know if the player was in a 90 degree sight cone of an enemy.
So the enemy wouldn't see you unless he was pointing in the correct one of the four main directions ( as in up down left right)

I had trig code that gave the angle between two points. That's one of the well known and simplest things to do.
Then I compared that to the angle that the enemy was facing.
If the enemy is facing right his angle would be zero.
Down,his angle is 90.
Left is 180.
If it was within 90 degrees difference then the player is within the cone of sight.

You could then use that number to decide if a turret should turn left or right to aim at the player.

The difficulty came in dealing with the threshold of when the angle goes from 359 back to zero.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Really, don't use trig for that. Dot products are much easier (and faster)

Let p be the player position vector. Let f be the player facing vector (probably want this normalised i.e. length is 1).

Let e be the enemy position vector.

Vector from player to enemy = e - p [this is just vector componentwise subtraction]

To see if the player is facing towards the enemy (180degrees spread)

isFacingTowardsEnemy = dot_product((e - p), f) > 0

For an arbitrary cone angle, you need to normalise (e - p) and f must be normalised also (I assume it will be).

then

dot_product(normalise(e - p), f) > cos (theta/2)

tells you if you are within the sightcone or not.

So you would use cos 45 degrees = 1/sqrt(2) for a 90 degree view cone.

If 2 vectors have a dot_product of 0 they are perpendicular.

If 2 normalised vectors have a dot_product of 1, they are pointing in the same direction. -1 if they are pointing in opposite directions.

That comes from this formula:

dot_product(a, b) = length(a) * length(b) * cos(angle between a and b).

Remember most programming languages use radians instead of degrees though. 360 degrees = 2pi radians

where the angle between a and b is the smallest angle i.e. between 0 and 180 degrees.

dot_product(a, b) = a.x * b.x + a.y * b.y // + a.z * b.z if using 3d vectors

EDIT: If you can only face in 8 directions the normalised facing vectors are

East: (1, 0)
Northeast: (1/sqrt(2), 1/sqrt(2))
North: (0, 1)
Northwest: (-1/sqrt(2), 1/sqrt(2))

etc.

normalise(v) = sqrt(dot_product(v, v)) * v

since dot_product(v, v) gives you the magnitude squared
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

You don't even need trig to draw circles :)

e.g. here is an efficient way to draw a circle in BASIC

Code: Select all

5 REM cx,cy are the circle centre, r is the radius
10 LET cx=128: LET cy=88: LET r=80
15 REM x, y are the offsets from the centre
16 REM d is the difference between the distance from the centre
17 REM squared, and the actual radius squared
20 LET x=0: LET y=r: LET d=0
25 REM plot all eight quadrants
30 REM PLOT cx+x,cy+y
40 REM PLOT cx+x,cy-y
50 REM PLOT cx-x,cy+y: DRAW x+x,0
60 REM PLOT cx-x,cy-y: DRAW x+x,0
70 REM PLOT cx+y,cy+x
80 PLOT cx-y,cy+x: DRAW y+y,0
90 REM PLOT cx+y,cy-x
100 PLOT cx-y,cy-x: DRAW y+y,0
105 REM compute the new d if we increase x
106 REM d=(x*x)+(y*y)-(r*r)
107 REM d2=(x+1)*(x+1)+(y*y)-(r*r)=(x*x)+(2*x)+1+(y*y)-(r*r)
108 REM d2-d = (2*x)+1
110 LET d=d+x+x+1
120 LET x=x+1
125 REM check if we should move closer to the circle
126 REM e=new d if y=y-1 using similar equation as above
130 LET e=d-y-y+1
140 IF e<0 THEN GO TO 170
150 LET d=e

151 PLOT cx-(x-1),cy+y: DRAW x+x-2,0
152 PLOT cx-(x-1),cy-y: DRAW x+x-2,0
160 LET y=y-1
165 REM once y<x we are past 45 degrees, so stop
170 IF y>=x THEN GO TO 30
175 REM keep drawing smaller circles down to zero
180 CLS : IF r=0 THEN STOP 
190 LET r=r-1
200 GO TO 20
Stuff REM'd out since it was based on a version which drew 8 pixels at a time (version shown draws horizontal lines 4 at a time).

No trig and no division either ;) EDIT: Also - only multiplication is by 2 which makes it especially good for ASM :)

Original version which draws 8 pixels at a time

Code: Select all

5 REM cx,cy are the circle centre, r is the radius
10 LET cx=128:LET cy=88:LET r=80
15 REM x, y are the offsets from the centre
16 REM d is the difference between the distance from the centre
17 REM squared, and the actual radius squared
20 LET x=0:LET y=r:LET d=0
25 REM plot all eight quadrants
30 PLOT cx+x,cy+y
40 PLOT cx+x,cy-y
50 PLOT cx-x,cy+y
60 PLOT cx-x,cy-y
70 PLOT cx+y,cy+x
80 PLOT cx-y,cy+x
90 PLOT cx+y,cy-x
100 PLOT cx-y,cy-x
105 REM compute the new d if we increase x
106 REM d=(x*x)+(y*y)-(r*r)
107 REM d2=(x+1)*(x+1)+(y*y)-(r*r)=(x*x)+(2*x)+1+(y*y)-(r*r)
108 REM d2-d = (2*x)+1
110 LET d=d+x+x+1
120 LET x=x+1
125 REM check if we should move closer to the circle
126 REM e=new d if y=y-1 using similar equation as above
130 LET e=d-y-y+1
140 IF e<0 THEN GO TO 170
150 LET d=e
160 LET y=y-1
165 REM once y<x we are past 45 degrees, so stop
170 IF y>=x THEN GO TO 30
175 REM keep drawing smaller circles down to zero
180 IF r=0 THEN STOP
190 LET r=r-1
200 GO TO 20
EDIT: I also optimised that and converted it to ASM ;) Does not do clipping though (but it does draw filled circles as well)

- code is very long (sjasmplus ASM syntax)

Spoiler

Code: Select all

	ORG #8000

RETURN_TO_BASIC EQU 1
NOPS_AT_START EQU 0
SCRBUF_BASEADDR EQU #4000
SPECIAL_CASE_LINE256 EQU 0

    MACRO ldim regpair, val1, val2
    ld regpair, ((val1&#FF)<<8)|(val2&#FF)
    ENDM

codestart:
    IF NOPS_AT_START
    nop
    nop
    nop
    nop
    ENDIF

main:
    IF RETURN_TO_BASIC
    ld (stashed_iy), iy
    exx
    ld (stashed_hl_alt), hl
	ld (stashed_sp), sp
    ENDIF

.mainloop
	ld a, 7
	out (#fe), a	
	halt
	ld a, 1
	out (#fe), a	

	IF 1
	ldim hl, 128, 96
	ld d, 63
	call draw_circle
	jp .mainloop
	ENDIF
	IF 0;1

	ldim hl, 128, 96
	ld d, 95
	call draw_circle

	ld b, 50
.morehalt
	halt
	djnz .morehalt
	ld a, 0|(7<<3)
	call cls

	ldim hl, 128, 96
	ld d, 95
	call draw_fill_circle

	ld b, 50
.morehalt2
	halt
	djnz .morehalt2
	ld a, 0|(7<<3)
	call cls

	jp .mainloop
	ENDIF
	IF 1;0
	ldim hl, 128, 96
	ld d, 95
.nextrad
	push de
	push hl
	call draw_circle
	pop hl
	pop de
	dec d
	jp p, .nextrad
	ENDIF

    IF RETURN_TO_BASIC
returntobasic:
    ld iy, (stashed_iy)
    ld hl, (stashed_hl_alt)
	ld sp, (stashed_sp)
    exx
    ENDIF
	ret

; H - cx
; L - cy
; D - radius
draw_circle:
	; B = xoffset
	xor a
	; A' = sx
	ex af, af'
	xor a
	; C = yoffset
	ld b, a
	ld c, d
	ld d, a

	IF 1
	; push everything
	; do the very first line, just draw 2 pixels
	push de
	push bc
	push hl
	; cy+x
	;ld a, l
	;add b
	;ld l, a ; B is 0 first line

	; cx-y
	ld a, h
	sub c
	ld b, a

	; cx+y
	ld a, h
	add c
	ld c, a

	xor a ; A=0 and clear carry
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l
	ld h, (hl)
	ld l, a

	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, c
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a

	ld a, l
	and ~31
	ld l, a
	ld a, b
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, b
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a
	jp .doneveryfirstline
	ENDIF
.nextline

	push de
	push bc
	push hl
	; cy+x
	ld a, l
	add b
	ld l, a

	; cx-y
	ld a, h
	sub c
	ld b, a

	; cx+y
	ld a, h
	add c
	ld c, a

	xor a ; A=0 and clear carry
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l
	ld h, (hl)
	ld l, a

	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, c
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a

	ld a, l
	and ~31
	ld l, a
	ld a, b
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, b
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a

	pop hl
	pop bc
	push bc
	push hl
	; cy-x
	ld a, l
	sub b
	ld l, a

	; cx-y
	ld a, h
	sub c
	ld b, a

	; cx+y
	ld a, h
	add c
	ld c, a

	xor a ; A=0 and clear carry
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l
	ld h, (hl)
	ld l, a

	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, c
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a

	ld a, l
	and ~31
	ld l, a
	ld a, b
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, b
	ld de, col2pix
	and 7
	add e
	ld e, a
	ld a, (de)
	or (hl)
	ld (hl), a

.doneveryfirstline
	pop hl
	pop bc
	pop de

	push hl
	ld h, 0
	ld l, d
	ld d, h
	ld e, b
	add hl, de
	add hl, de
	inc hl
	inc b
	ld a, l ; A is new D if we jump to .nexty soon
	ld d, 0
	ld e, c
	or a
	sbc hl, de
	or a
	sbc hl, de
	inc hl
	bit 7, h
	ld d, a
	jr nz, .nexty
	ld d, l

	pop hl
	ld a, b
	sub c
	dec a
	jr z, .justdecy ; we already drew this line (at 45 degrees)

	push de

	push bc
	push hl
	; cy-y
	ld a, l
	sub c
	ld l, a
	; cx - x + 1
	ex af, af'
	ld e, a
	ex af, af'
	ld a, h
	sub b
	inc a
	ld c, a
	; b = sx - x
	ld a, b
	sub e
	ld b, a

	xor a
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l ; this won't overflow because of align 256
	ld h, (hl)
	ld l, a

	push hl
	call draw_line_horz_knowaddr
	pop de

	pop hl
	pop bc
	push bc
	push hl
	; cx + sx
	ld a, h
	ex af, af'
	ld h, a
	ex af, af'
	add h
	ld c, a
	; b = x - sx
	ld a, b
	sub h
	ld b, a

	ld h, d
	ld l, e
	call draw_line_horz_knowaddr
	pop hl
	pop bc

	push bc
	push hl
	; cy+y
	ld a, l
	add c
	ld l, a
	; cx - x + 1
	ld a, h
	sub b
	inc a
	ld c, a
	; b = sx - x
	ld a, b
	ex af, af'
	ld b, a
	ex af, af'
	sub b
	ld b, a

	xor a
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l ; this won't overflow because of align 256
	ld h, (hl)
	ld l, a

	push hl
	call draw_line_horz_knowaddr
	pop de
	pop hl
	pop bc

	push bc
	push hl
	; cx + sx
	ld a, h
	ex af, af'
	ld h, a
	ex af, af'
	add h
	ld c, a
	; b = x - sx
	ld a, b
	sub h
	ld b, a

	ld h, d
	ld l, e
	call draw_line_horz_knowaddr
	pop hl

	pop bc

	pop de

.justdecy
	dec c

	ex af, af'
	ld a, b
	ex af, af'

	push hl
.nexty
	ld a, c
	sub b
	pop hl
	jp p, .nextline
	ret

; H - cx
; L - cy
; D - radius
draw_fill_circle:
	; B = xoffset
	xor a
	; C = yoffset
	ld b, a
	ld c, d
	ld d, a
	; push everything
	push de

	; draw the very first line
	push bc
	push hl
	; cy+x
	;ld a, l
	;add b
	;ld l, a ; B is 0 here
	ld a, c
	add c
	inc a
	ld b, a
	; cx-y
	ld a, h
	sub c
	ld c, a
	call draw_line_horz_xor
	jp .doneveryfirstline
.nextline
	push de

	push bc
	push hl
	; cy+x
	ld a, l
	add b
	ld l, a
	ld a, c
	add c
	inc a
	ld b, a
	; cx-y
	ld a, h
	sub c
	ld c, a
	call draw_line_horz_xor
	pop hl
	pop bc
	push bc
	push hl
	; cy-x
	ld a, l
	sub b
	ld l, a
	ld a, c
	add c
	inc a
	ld b, a
	; cx-y
	ld a, h
	sub c
	ld c, a
	call draw_line_horz_xor

.doneveryfirstline
	pop hl
	pop bc

	; work out new d
	pop de

	push hl
	ld h, 0
	ld l, d
	ld d, h
	ld e, b
	add hl, de
	add hl, de
	inc hl
	inc b
	ld a, l ; A is new D if we jump to .nexty soon
	ld d, 0
	ld e, c
	or a
	sbc hl, de
	or a
	sbc hl, de
	inc hl
	bit 7, h
	ld d, a
	jr nz, .nexty

	ld d, l
	pop hl
	ld a, b
	sub c
	dec a
	jr z, .justdecy ; we already drew this line (at 45 degrees)

	push de

	push bc
	push hl
	; cy-y
	ld a, l
	sub c
	ld l, a
	; cx - x + 1
	ld a, h
	sub b
	inc a
	ld c, a
	; b = x+x-1
	ld a, b
	add b
	dec a
	ld b, a
	call draw_line_horz_xor
	pop hl
	pop bc
	push bc
	push hl
	; cy+y
	ld a, l
	add c
	ld l, a
	; cx - x + 1
	ld a, h
	sub b
	inc a
	ld c, a
	; b = x+x-1
	ld a, b
	add b
	dec a
	ld b, a
	call draw_line_horz_xor
	pop hl
	pop bc

	pop de

.justdecy
	dec c

	push hl
.nexty
	ld a, c
	sub b
	pop hl
	jp p, .nextline
	ret

	IF 0
; C - column
; L - row
plot:
	xor a
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l
	ld h, (hl)
	ld l, a
	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	ld a, c
	exx
	ld hl, col2pix
	and 7
	add l
	ld l, a
	ld a, (hl)
	exx
	or (hl)
	ld (hl), a
	ret
	ENDIF

; B - number of pixels
; C - column offset
; L - row
draw_line_horz:
	xor a
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l ; this won't overflow because of align 256
	ld h, (hl)
	; work out the column address
	ld l, a
draw_line_horz_knowaddr:
	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	; if C&7 == 0 and B&7 == 0, just draw the middle of the line
	ld a, c
	or b
	and 7
	jr z, .drawlinemiddle
.notmiddleonly
	; work out (C&7), amount of pixels to draw on left hand side
	ld a, c
	and 7
	jr z, .justdrawrhs
	ld e, a ; remember (C&7) in E. will subtract this from line length in a bit
	; if 8-C&7 > B (line length), we only draw 1 cell and not all of the pixels
	ld a, 8
	sub e
	cp b
	jr nc, .draw1cellpartial
	ld c, a
	ld a, e ; amount to shift down
	exx
	ld hl, srlFFtable
	add l
	ld l, a
	ld a, (hl)
	exx
	; put lefthandside
	or (hl)
	ld (hl), a
	inc l
	ld a, b
	sub c ; adjust line length for pixels we just drew
	ld b, a
	and 7
.justdrawrhs
	ld e, b ; remember B&7 in E
	ld a, b
	and ~7
	rrca
	rrca
	rrca
	ld b, a
	or a
	jr z, .dontdrawmiddle
	ld a, #FF
.middleloop
	ld (hl), a
	inc l
	djnz .middleloop
.dontdrawmiddle
	; work out (B&7), amount of pixels to draw on left hand side
	ld a, e
	and 7
	dec a
	ret m
	jr z, .putlinerhsnoshift ; no need to shift
	exx
	ld hl, sra80table
	add l
	ld l, a
	ld a, (hl)
	exx
	; put righthandside
	or (hl)
	ld (hl), a
	ret
.putlinerhsnoshift
	ld a, #80
	or (hl)
	ld (hl), a
	ret
.draw1cellpartial
	dec b
	ld a, #80
	jr z, .nosra
	ld a, b
	exx
	ld hl, sra80table
	add l
	ld l, a
	ld a, (hl)
	exx
.nosra
	ld b, e
.rrcaagain
	rrca
	djnz .rrcaagain
	or (hl)
	ld (hl), a
	ret
.drawlinemiddle
	ld a, b
	rrca
	rrca
	rrca
	ld b, a
	ld a, #FF
.middleonlyloop
	ld (hl), a
	inc l
	djnz .middleonlyloop
	ret

; B - number of pixels
; C - column offset
; L - row
draw_line_horz_xor:
	xor a
	sla l
	ld h, tbl_scraddr/256 ; this requires tbl_scraddr be aligned to a 256 byte boundary
	adc h ; A = H + carry flag
	ld h, a ; write it back
	; look up screen address from  the table
	ld a, (hl)
	inc l ; this won't overflow because of align 256
	ld h, (hl)
	; work out the column address
	ld l, a
	ld a, c
	and ~7
	rrca
	rrca
	rrca
	add l
	ld l, a
	; if C&7 == 0 and B&7 == 0, just draw the middle of the line
	ld a, c
	or b
	and 7
	jr z, .drawlinemiddle
.notmiddleonly
	; work out (C&7), amount of pixels to draw on left hand side
	ld a, c
	and 7
	jr z, .justdrawrhs
	ld e, a ; remember (C&7) in E. will subtract this from line length in a bit
	; if 8-C&7 > B (line length), we only draw 1 cell and not all of the pixels
	ld a, 8
	sub e
	cp b
	jr nc, .draw1cellpartial
	ld c, a
	ld a, e ; amount to shift down
	exx
	ld hl, srlFFtable
	add l
	ld l, a
	ld a, (hl)
	exx
	; put lefthandside
	xor (hl)
	ld (hl), a
	inc l
	ld a, b
	sub c ; adjust line length for pixels we just drew
	ld b, a
	and 7
.justdrawrhs
	ld e, b ; remember B&7 in E
	ld a, b
	and ~7
	rrca
	rrca
	rrca
	ld b, a
	or a
	jr z, .dontdrawmiddle
	;ld a, #FF
	ld c, #FF
.middleloop
	ld a, c
	xor (hl)
	ld (hl), a
	inc l
	djnz .middleloop
.dontdrawmiddle
	; work out (B&7), amount of pixels to draw on left hand side
	ld a, e
	and 7
	dec a
	ret m
	jr z, .putlinerhsnoshift ; no need to shift
	exx
	ld hl, sra80table
	add l
	ld l, a
	ld a, (hl)
	exx
	; put righthandside
	xor (hl)
	ld (hl), a
	ret
.putlinerhsnoshift
	ld a, #80
	xor (hl)
	ld (hl), a
	ret
.draw1cellpartial
	dec b
	ld a, #80
	jr z, .nosra
	ld a, b
	exx
	ld hl, sra80table
	add l
	ld l, a
	ld a, (hl)
	exx
.nosra
	ld b, e
.rrcaagain
	rrca
	djnz .rrcaagain
	xor (hl)
	ld (hl), a
	ret
.drawlinemiddle
	ld a, b
	rrca
	rrca
	rrca
	ld b, a
	ld c, #FF
.middleonlyloop
	ld a, c
	xor (hl)
	ld (hl), a
	inc l
	djnz .middleonlyloop
	ret

cls:
	di                  ;disable interrupt
	ld (.stack+1), sp	;store current stack pointer
	ld sp, 16384 + 6144 + 768
	ld b, 128			; clear attribs in 128 * 3 pushes
	ld h, a
	ld l, a
.attribloop
	push hl
	push hl
	push hl
	djnz .attribloop
	ld hl, 0
	ld b, l             ;set B to 0. it causes that DJNZ will repeat 256 times
.loop1
	push hl             ;store hl on stack
	push hl             ;next
	push hl             ;these four push instruction stores 8 bytes on stack
	push hl
	push hl             ;store hl on stack
	push hl             ;next
	push hl             ;these four push instruction stores 8 bytes on stack
	push hl
	push hl             ;store hl on stack
	push hl             ;next
	push hl             ;these four push instruction stores 8 bytes on stack
	push hl
	djnz .loop1			;repeat for next 12*2 bytes
.stack
	ld sp, 0            ;parameter will be overwritten
	ei
	ret

data_section:
    IF RETURN_TO_BASIC
stashed_iy dw 0
stashed_hl_alt dw 0
stashed_sp dw 0
    ENDIF

	ALIGN 256
; screen address table. This must be 256 byte aligned
tbl_scraddr	dw SCRBUF_BASEADDR + #0000, SCRBUF_BASEADDR + #0100, SCRBUF_BASEADDR + #0200, SCRBUF_BASEADDR + #0300, SCRBUF_BASEADDR + #0400, SCRBUF_BASEADDR + #0500, SCRBUF_BASEADDR + #0600, SCRBUF_BASEADDR + #0700 
			dw SCRBUF_BASEADDR + #0020, SCRBUF_BASEADDR + #0120, SCRBUF_BASEADDR + #0220, SCRBUF_BASEADDR + #0320, SCRBUF_BASEADDR + #0420, SCRBUF_BASEADDR + #0520, SCRBUF_BASEADDR + #0620, SCRBUF_BASEADDR + #0720
			dw SCRBUF_BASEADDR + #0040, SCRBUF_BASEADDR + #0140, SCRBUF_BASEADDR + #0240, SCRBUF_BASEADDR + #0340, SCRBUF_BASEADDR + #0440, SCRBUF_BASEADDR + #0540, SCRBUF_BASEADDR + #0640, SCRBUF_BASEADDR + #0740
			dw SCRBUF_BASEADDR + #0060, SCRBUF_BASEADDR + #0160, SCRBUF_BASEADDR + #0260, SCRBUF_BASEADDR + #0360, SCRBUF_BASEADDR + #0460, SCRBUF_BASEADDR + #0560, SCRBUF_BASEADDR + #0660, SCRBUF_BASEADDR + #0760
			dw SCRBUF_BASEADDR + #0080, SCRBUF_BASEADDR + #0180, SCRBUF_BASEADDR + #0280, SCRBUF_BASEADDR + #0380, SCRBUF_BASEADDR + #0480, SCRBUF_BASEADDR + #0580, SCRBUF_BASEADDR + #0680, SCRBUF_BASEADDR + #0780
			dw SCRBUF_BASEADDR + #00a0, SCRBUF_BASEADDR + #01a0, SCRBUF_BASEADDR + #02a0, SCRBUF_BASEADDR + #03a0, SCRBUF_BASEADDR + #04a0, SCRBUF_BASEADDR + #05a0, SCRBUF_BASEADDR + #06a0, SCRBUF_BASEADDR + #07a0
			dw SCRBUF_BASEADDR + #00c0, SCRBUF_BASEADDR + #01c0, SCRBUF_BASEADDR + #02c0, SCRBUF_BASEADDR + #03c0, SCRBUF_BASEADDR + #04c0, SCRBUF_BASEADDR + #05c0, SCRBUF_BASEADDR + #06c0, SCRBUF_BASEADDR + #07c0
			dw SCRBUF_BASEADDR + #00e0, SCRBUF_BASEADDR + #01e0, SCRBUF_BASEADDR + #02e0, SCRBUF_BASEADDR + #03e0, SCRBUF_BASEADDR + #04e0, SCRBUF_BASEADDR + #05e0, SCRBUF_BASEADDR + #06e0, SCRBUF_BASEADDR + #07e0
			dw SCRBUF_BASEADDR + #0800, SCRBUF_BASEADDR + #0900, SCRBUF_BASEADDR + #0a00, SCRBUF_BASEADDR + #0b00, SCRBUF_BASEADDR + #0c00, SCRBUF_BASEADDR + #0d00, SCRBUF_BASEADDR + #0e00, SCRBUF_BASEADDR + #0f00 
			dw SCRBUF_BASEADDR + #0820, SCRBUF_BASEADDR + #0920, SCRBUF_BASEADDR + #0a20, SCRBUF_BASEADDR + #0b20, SCRBUF_BASEADDR + #0c20, SCRBUF_BASEADDR + #0d20, SCRBUF_BASEADDR + #0e20, SCRBUF_BASEADDR + #0f20
			dw SCRBUF_BASEADDR + #0840, SCRBUF_BASEADDR + #0940, SCRBUF_BASEADDR + #0a40, SCRBUF_BASEADDR + #0b40, SCRBUF_BASEADDR + #0c40, SCRBUF_BASEADDR + #0d40, SCRBUF_BASEADDR + #0e40, SCRBUF_BASEADDR + #0f40
			dw SCRBUF_BASEADDR + #0860, SCRBUF_BASEADDR + #0960, SCRBUF_BASEADDR + #0a60, SCRBUF_BASEADDR + #0b60, SCRBUF_BASEADDR + #0c60, SCRBUF_BASEADDR + #0d60, SCRBUF_BASEADDR + #0e60, SCRBUF_BASEADDR + #0f60
			dw SCRBUF_BASEADDR + #0880, SCRBUF_BASEADDR + #0980, SCRBUF_BASEADDR + #0a80, SCRBUF_BASEADDR + #0b80, SCRBUF_BASEADDR + #0c80, SCRBUF_BASEADDR + #0d80, SCRBUF_BASEADDR + #0e80, SCRBUF_BASEADDR + #0f80
			dw SCRBUF_BASEADDR + #08a0, SCRBUF_BASEADDR + #09a0, SCRBUF_BASEADDR + #0aa0, SCRBUF_BASEADDR + #0ba0, SCRBUF_BASEADDR + #0ca0, SCRBUF_BASEADDR + #0da0, SCRBUF_BASEADDR + #0ea0, SCRBUF_BASEADDR + #0fa0
			dw SCRBUF_BASEADDR + #08c0, SCRBUF_BASEADDR + #09c0, SCRBUF_BASEADDR + #0ac0, SCRBUF_BASEADDR + #0bc0, SCRBUF_BASEADDR + #0cc0, SCRBUF_BASEADDR + #0dc0, SCRBUF_BASEADDR + #0ec0, SCRBUF_BASEADDR + #0fc0
			dw SCRBUF_BASEADDR + #08e0, SCRBUF_BASEADDR + #09e0, SCRBUF_BASEADDR + #0ae0, SCRBUF_BASEADDR + #0be0, SCRBUF_BASEADDR + #0ce0, SCRBUF_BASEADDR + #0de0, SCRBUF_BASEADDR + #0ee0, SCRBUF_BASEADDR + #0fe0
			dw SCRBUF_BASEADDR + #1000, SCRBUF_BASEADDR + #1100, SCRBUF_BASEADDR + #1200, SCRBUF_BASEADDR + #1300, SCRBUF_BASEADDR + #1400, SCRBUF_BASEADDR + #1500, SCRBUF_BASEADDR + #1600, SCRBUF_BASEADDR + #1700
			dw SCRBUF_BASEADDR + #1020, SCRBUF_BASEADDR + #1120, SCRBUF_BASEADDR + #1220, SCRBUF_BASEADDR + #1320, SCRBUF_BASEADDR + #1420, SCRBUF_BASEADDR + #1520, SCRBUF_BASEADDR + #1620, SCRBUF_BASEADDR + #1720
			dw SCRBUF_BASEADDR + #1040, SCRBUF_BASEADDR + #1140, SCRBUF_BASEADDR + #1240, SCRBUF_BASEADDR + #1340, SCRBUF_BASEADDR + #1440, SCRBUF_BASEADDR + #1540, SCRBUF_BASEADDR + #1640, SCRBUF_BASEADDR + #1740
			dw SCRBUF_BASEADDR + #1060, SCRBUF_BASEADDR + #1160, SCRBUF_BASEADDR + #1260, SCRBUF_BASEADDR + #1360, SCRBUF_BASEADDR + #1460, SCRBUF_BASEADDR + #1560, SCRBUF_BASEADDR + #1660, SCRBUF_BASEADDR + #1760
			dw SCRBUF_BASEADDR + #1080, SCRBUF_BASEADDR + #1180, SCRBUF_BASEADDR + #1280, SCRBUF_BASEADDR + #1380, SCRBUF_BASEADDR + #1480, SCRBUF_BASEADDR + #1580, SCRBUF_BASEADDR + #1680, SCRBUF_BASEADDR + #1780
			dw SCRBUF_BASEADDR + #10a0, SCRBUF_BASEADDR + #11a0, SCRBUF_BASEADDR + #12a0, SCRBUF_BASEADDR + #13a0, SCRBUF_BASEADDR + #14a0, SCRBUF_BASEADDR + #15a0, SCRBUF_BASEADDR + #16a0, SCRBUF_BASEADDR + #17a0
			dw SCRBUF_BASEADDR + #10c0, SCRBUF_BASEADDR + #11c0, SCRBUF_BASEADDR + #12c0, SCRBUF_BASEADDR + #13c0, SCRBUF_BASEADDR + #14c0, SCRBUF_BASEADDR + #15c0, SCRBUF_BASEADDR + #16c0, SCRBUF_BASEADDR + #17c0
			dw SCRBUF_BASEADDR + #10e0, SCRBUF_BASEADDR + #11e0, SCRBUF_BASEADDR + #12e0, SCRBUF_BASEADDR + #13e0, SCRBUF_BASEADDR + #14e0, SCRBUF_BASEADDR + #15e0, SCRBUF_BASEADDR + #16e0, SCRBUF_BASEADDR + #17e0

col2pix	db #80, #40, #20, #10, #08, #04, #02, #01
; #FF >> 0..7
srlFFtable db #FF, #7F, #3F, #1F, #0F, #07, #03, #01
; same but for arithmetic shift right
sra80table db #80, #C0, #E0, #F0, #F8, #FC, #FE, #FF
endofprog:
Last edited by ParadigmShifter on Fri Dec 08, 2023 7:19 pm, edited 2 times in total.
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: Bullet Spray routines

Post by Wall_Axe »

So doing it your way means you never have to deal with the threshold thing. Cos you are just checking for bigger than zero.
Sounds pretty good.
User avatar
lexi
Dizzy
Posts: 63
Joined: Mon Dec 04, 2023 10:55 pm
Contact:

Re: Bullet Spray routines

Post by lexi »

ParadigmShifter wrote: Thu Dec 07, 2023 7:24 pm Bresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.
exactly!

ParadigmShifter wrote: Thu Dec 07, 2023 7:24 pm It's probably better to just use vectors but maintain fractional part (say as the lower 8 bits) to keep track of the overflow.

If bullets move too fast (so they can pass through things) you'll need line segment collision though.
i've been developing simple integer-only hexgrid-voronoi collision detection algorithms which should obviate that pre-requisite.

i'm not sure what you mean by using vectors - i understand it to mean the relationship between two points - but does it refer to a specific speccy progamming technique?

ParadigmShifter wrote: Thu Dec 07, 2023 7:24 pm I think 100 bullets may be a tad ambitious on the speccy ;)
Challenge Accepted :twisted:

See ya in 10 years! :)

Wall_Axe wrote: Fri Dec 08, 2023 4:56 pm Oh that's cool that you have it done in blitz basic already.
Olde BlitzPC code requires some porting effort to run in BlitzMax - got halfway through porting an old BlitzPC Complex-X remake i originally made for revstu into BlitzMax before putting it on hold - though it'd probably be a lot quicker for someone who wasn't learning the newest version of the language as they went along!

Wall_Axe wrote: Fri Dec 08, 2023 4:56 pm You could then use that number to decide if a turret should turn left or right to aim at the player.

The difficulty came in dealing with the threshold of when the angle goes from 359 back to zero.
I love how you can learn so much just from playing around from game problems like this :) I learned how to pass trig exams in school, but i didn't start to understand it until well after i found reason to learn it. Math lessons teach us to avoid trig - it's such a sin.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

You need to check against the cosine of the cone half angle (so 45 degrees for a 90 degree cone, cos(45deg) = 1/sqrt(2)) - which I did mention.

> 0 only works for detecting in front (and < 0 means behind)
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

A vector is just a set of coordinates (relative to the origin for position vectors, relative to the object position for a facing vector).

So if you have a player at position (2, 1) the position vector is just (2, 1).

Facing northeast is relative to the player position so it's just (1/sqrt(2), 1/sqrt(2)).

So vectors as in maths rather than say the C++ container/vector CPU instructions or whatever.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Wall_Axe wrote: Fri Dec 08, 2023 4:56 pm The difficulty came in dealing with the threshold of when the angle goes from 359 back to zero.
Easy way to get around that issue - in assembler or any language which has fixed size integer variables is to split your circle into 256 units instead of 360 and use integer wrap around.

Although any power of 2 also works well (doubt you'll want more than 256 units of rotation on a speccy though, you may want less e.g. number of possible facings (8 or 16 say)). PS1 (which only had integer maths, no floating point, all fixed point) used 4096 units for 360 degrees.

Then you can use a lookup table for sin and cos if you really need that (chances are you don't need to use sin or cos very often if at all).
Ralf
Rick Dangerous
Posts: 2289
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Bullet Spray routines

Post by Ralf »

Bresenham is only really useful if you want to move 1 pixel at a time, which bullets rarely will want to I expect.
Well, if you want to move two pixels at a time you can always call it twice :)
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Ralf wrote: Fri Dec 08, 2023 6:43 pm Well, if you want to move two pixels at a time you can always call it twice :)
Nah you just want to use vectors again.

If you want to move from a to b in x frames do this:

position at time t = a + (b-a) * (t / x)

and let t range from 0 to 1. With that method you can use a curve instead of a straight line to vary the speed along the path if you want.

This function (lerp - Linear intERPolation)

lerp(a, b, t) = a + (b-a) * t // move from a to b, returns a when t is 0, b when t is 1

is so useful it deserves its own function.

If you want to use a bullet velocity instead (don't care about the endpoint) just use

position at time t = initial pos + vel * t

or just do

pos.x = pos.x + vel.x
pos.y = pos.y + vel.y
; and z if in 3d

each frame (looks crap if frame rate is not constant).

For non constant frame rates do

pos.x = pos.x + deltaT * vel.x
pos.y = pos.y + deltaT * vel.y

instead (deltaT is number of frames since last render). That looks crap if your velocity changes and the frame rate is variable though ;)

where t is the bullet lifetime counter and vel is the velocity.

You want to use vectors instead of the y = mx + c equation of a line since that does not work for firing straight up or down. Vectors work for any direction (and in any number of dimensions).

You'll want to use fixed point or floating point numbers to be accurate of course.
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

lexi wrote: Fri Dec 08, 2023 6:07 pm I love how you can learn so much just from playing around from game problems like this :) I learned how to pass trig exams in school, but i didn't start to understand it until well after i found reason to learn it. Math lessons teach us to avoid trig - it's such a sin.
Missed that joke first time around lol ;) Here's 2 more

Image

Image
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: Bullet Spray routines

Post by ParadigmShifter »

Mistake in my normalise function... should have been

normalise(v) = v / sqrt(dot_product(v, v))
Wall_Axe
Manic Miner
Posts: 500
Joined: Mon Nov 13, 2017 11:13 pm

Re: Bullet Spray routines

Post by Wall_Axe »

I get that bullet code you write in regards to velocity but the other stuff goes over my head.
I'd need to study normalise and dot product to really understand what's going on.

Such a sin... d'ohhh
And the TAN line :|

Would it easy using this maths to get the computer to control a car on super sprint?
Codemasters seem to have cheated on their early games as the computer car can't be interrupted. It drives on rails.

Or to allow the computer to move the ship in gravity force to a certain place.
Post Reply