A.D.A. Amiga Demoscene Archive

  Welcome guest! Please register a new account or log in

  

  

  

Demos Amiga Demoscene Archive Forum / Coding / Sinus / cosinus calculation
 Page:  1  2  »» 
Author Message
z5_
Member
#1 - Posted: 21 Aug 2007 19:15
Reply Quote
Seeing that this is probably used quite often in effects, i created a separate thread for it. First i will repost some stuff from the "general questions" thread where this started.
z5_
Member
#2 - Posted: 21 Aug 2007 19:16
Reply Quote
Posted by Kalms:

Radians is just an alternative scale instead of degrees.

When using the sinus table in code, you will probably want to have a sinus table with 256, 512 or 1024 values for one full period (one period = 360 degrees = 2PI radians). Let's say that you go with a table that has 1024 entries; the difference in angle between two consecutive entries in the table will then be (360/1024) degrees, or (2*PI/1024) radians.

Your sine table will contain values multiplied by a constant, indeed. If you have all values multiplied by 16384, then you can use 16/32-bit maths without having to fear overflow all the time.

If you are using pure integer calculations, you will probably represent your angles in something else than degrees. One useful unit would be: 1024 = one full period (since this is the resolution of your sine table). Another useful unit is: 65536 = one full period.

When you have an angle (a), the sine table is useful for calculating sin(a). Just lookup the entry at position (a) in the table, that's the sine value you are looking for. If your angle values have higher precision than your sine table (i.e. angles are measured in 1/65536ths, but your sine table has only 1024 entries), then you *can* interpolate between neighboring values in the sine table to get a more accurate value. You won't need that for your current demo-effects, though.

cos-table: either you have an extra cos-table, or you use the identity:
cos(a degrees) = sin(a + 90 degrees)
This can be accomplished by having a sine-table which contains 1.25 periods, and having the base-pointer for cosine lookups point 0.25 periods into the table.

For instance, for the 1024 entries case, the sine table should have 1280 words, and the cos-table begins 256 words into the sine table.

Before you do all this in code, you should look at a picture of a sine and a cosine wave. (get out your old maths book, or google a bit.) Familiarize yourself with what the contents of the tables should look like, roughly.
z5_
Member
#3 - Posted: 21 Aug 2007 19:16
Reply Quote
Posted by Stingray:

ASM-One has a built in create sinus table function. I can't remember how to use it but you can find it in the ASM-One docs.

it's rather simple to use (and a pretty nifty Asm-One/Pro feature :D)
type "IS" (I.nsert S.inus) in the commandline and you will get some prompts:

BEG> -your starting angle, e.g. 0
END> -end angle, e.g. 360
AMOUNT> -number of values for your table, e.g. 1024
AMPLITUDE> -max. height of your curve, e.g. 256
YOFFSET> -offset adder
SIZE (B/W/L)>choose between byte/word/longword sized values for your tab
MULTIPLIER> the multiplier, e.g. 1<<14
HALF CORRECTION> no idea, i guess it changes the rounding approach
ROUND CORRECTION> same as above, I always use y/y here :)

and that's all you need to create a sinetable in Asm1. :)
z5_
Member
#4 - Posted: 21 Aug 2007 19:17
Reply Quote
Posted by Blueberry:

Another way to put it is that the i'th number in your table will get the value

MULTIPLIER * (YOFFSET + round(AMPLITUDE * sin(BEG + (i + HC) * (END - BEG) / AMOUNT)))

where HC is 0.5 if HALF CORRECTION is on and 0 otherwise, and round() rounds to nearest if ROUND CORRECTION is on and towards zero (bad) otherwise.

As you see, the MULTIPLIER is multiplied on the rounded result. This is useful whenever you are multiplying the value by some constant anyway, for instance for vertical screen offsets.

A different version of the command is CS (create sinus) which takes a destination address as first parameter and otherwise the same as IS. This is very conveniently used with the AUTO directive, so you simply write in your source:

auto cs\SinusTable\0\360\1024\256\0\b1\ny

There are no backslashes after the SIZE and CORRECTION parameters because these do not need you to press enter.

This way, the definition of your sinus takes up just the space declaration and the auto statement in your source. And you can change the parameters of your sinus just by changing the values in the auto statement.
z5_
Member
#5 - Posted: 21 Aug 2007 19:22 - Edited
Reply Quote
So now for the questions. The "is" command in asm-one is very useful to get started on sinus calculations. First i rendered a few sinus tables with different parameters to get familiar with the tables. I then inserted a sinus table in my code with the auto cs command.

Then i went on to do a small test: drawing a pixel in what i expected to be a circle, like this:

auto cs\sinustable\0\450\1280\50\0\b1\ny

move.w pa2_1_angle(pc),d0
move.w pa2_1_angle_cos(pc),d2
lea screen+320*100+160,a0 ;middlepoint
lea sinustable,a1
move.b (a1,d0.w),d1
ext.w d1
muls.w #320,d1
move.b (a1,d2.w),d3
ext.w d3
add.w d3,a0
move.b #1,(a0,d1.l)
add.w #1,pa2_1_angle
add.w #1,pa2_1_angle_cos
rts

pa2_1_angle:
dc.w 0
pa2_1_angle_cos:
dc.w 256

(note: don't mind the syntax or the way the code is written).

I have a pixel moving in a circle pattern, however it's stuttering. It sometimes plots twice on the same line. I gather this has to do with rounding. So how do i solve this problem or am i going the wrong way about it?
michael phipps
Member
#6 - Posted: 21 Aug 2007 21:15
Reply Quote
Hi m8!!!

Been looking at your code so I thought I'd better give some additional information on how to optimize your code so here's my version as my reward to you hehehe...

My Version Code:-

Move.w count_angle_sin(pc),d0 ; (pc) Program Counter Relative code is good
Move.w count_angle_cos(pc),d2 ;for speeding up CPU sync

Lea SineTable,a0
Lea CosTable,a1

Move.w (a0,d0.w),d1 ;notice I used word.table to remove ext.b DONT
Move.w (a1,d2.w),d3 ;NEED ext.w instruction or Muls instruction either!
;coz this instruction slows down CPU time!


add.w #2,count_angle_sin ;it takes two bytes to get the next word value in
add.w #2,count_angle_cos ;Cos/Sin Table!

count_angle_cos dc.w 0
count_angle_sin dc.w 256*2 ;This is pointer in word table!

If you want more information on Vector Calculations just ask & I'd be more than happy to help!
z5_
Member
#7 - Posted: 21 Aug 2007 22:09 - Edited
Reply Quote
@wizz: damn, there goes my reward... :o)

anyway, some things i need to mention. In my example, i only have a sin table in bytes, hence the ext instruction. I use cos(angle) as sin(angle+90).

I need the muls because i'm plotting my pixel on a chunky screen though.

But that doesn't solve my original question though :o)
michael phipps
Member
#8 - Posted: 22 Aug 2007 04:34
Reply Quote
Hello again!

Okey you are using too many unnecessary instructions to execute your code mate!!! Not only
Does it slow down Amiga CPU, it blocks compatibly on future machines so try this:-

1) convert your Sin/Cos tables from byte table to word table!!!
2) If you still want to use the muls or mulu instruction DON'T coz this instruction takes approx
70 cycles to execute on Amiga 500 so try this:-

move.w d0,d1
lsl.w #3,d1 ;lsl.w only takes approx 4 cycles to execute! Super fast compared muls/mul
add.w d1,d1 ;instruction!!!!
add.w d1,d0

lsl.w #3,d1 ;is a shift instruction for mulu #40,d0 or muls #40,d0.
lsl.w #4,d1 ;is shift for #muls #80,d0 etc...get it!!

Hmmm... I'm confused, use said were using this instruction for your chunky2planer screen but this is for your SinTable calcs!!!

Hope this helps, let me know how you get on by the way what assembler are you using. I used AsmOne
in my demo! f#*king fast man DevPac/Seka is too bloody slow hehehe...
Kalms
Member
#9 - Posted: 22 Aug 2007 11:08
Reply Quote
@z5: Let's pretend that you disable the screen clearing. Let your circle routine run until it has painted a full circle. Count the number of pixels painted.

You will find that noticeably fewer than 1024 pixels have been painted. This means that there are less than 1024 unique positions along the circle. This implies that some neighboring sin/cos pairs have the same numerical value. That's why the dot is occasionally "stopping".

If the circle has a greater radius, then the prolem will be less pronounced, because there will then be more unique pixels along the circle's contour.

There are 3 main ways to rid yourself of the stuttering/halting problem:

1) reduce the number of steps that the pixel is doing (less than 1024 steps around the entire circle). This will make the dot move faster.
2) Increase the resolution of your output image (go to 640x400 and paint a pixel as a 2x2 block). This will allow you to place the 'dot' with higher precision on-screen.
3) Render your dot with anti-aliasing. This allows you to place the 'dot' with higher precision, without increasing image resolution.
z5_
Member
#10 - Posted: 22 Aug 2007 12:47
Reply Quote
Thanks kalms. That was the confirmation i was looking for. I actually started without clearing the screen and adding my pixel to the screen (instead of moving it to the screen). That way i could see that some pixels got a different color (= overwritten).
michael phipps
Member
#11 - Posted: 22 Aug 2007 14:28
Reply Quote
What exactly are you trying to do in your code???! Please tell me because I want to help and don't mind contributing some information on 680x0 knowledge so fill me in, that goe for anyone on this web-site here too!!!!
z5_
Member
#12 - Posted: 22 Aug 2007 17:01
Reply Quote
@michael: it's solved now. As a newbie, i was just trying to get to grips with sinus calculations. So as an experiment, i just tried to move one dot on a circle (radius 50, middlepoint y=100 x=160, chunky screen), with the coords extracted from a sinus table.
winden
Member
#13 - Posted: 22 Aug 2007 19:22 - Edited
Reply Quote
z5, here are relatively easy things to try next:

1. what do you think will show if you draw multiple points in different angles in the same frame?

2. what do you think should be shown if you use a different radius for X and Y coordinate?

3. what if, on each frame, you slowly change any of these parameters?

in fact, you are very very very close to doing a complete family of effects!

btw, are you testing first your formulas in amos or blitzbasic? they are very useful for these kind of things... i used blitz a lot to prototype formulas and make small calculation programs like a sine-table-generator
z5_
Member
#14 - Posted: 22 Aug 2007 19:35 - Edited
Reply Quote
@winden: not sure what/which effects you are hinting at (but please don't tell me yet :o)) but my knowledge is really extremely basic yet. I'm just having a bit of fun experimenting.

In any case, i'm not really doing sinus calculations yet because i'm just rendering a sinus table with a predefined radius using asm-one's built in sinus table generator and using that as coordinates for my pixel. So i still have to take this step before going further: render a normal sinus table with a predefined multiplier and do the coord calculation myself.

And yes, something other than assembler would be great to get to grips with calculations and stuff.
z5_
Member
#15 - Posted: 22 Aug 2007 23:29 - Edited
Reply Quote
I am actually pretty close to what i wanted to achieve in the first place: a circle scroller (not that i would use it as a scroller, but just the circular movement of several objects) but i can't get a smooth scroll movement, no matter what sinus table i generate.

Circle render

Above is a screenshot (click to enlarge) of the circular movement. As you can see, some pixels are put twice on the same positions (= blue color) but there are also quite a few points on one line next to each other (horizontal and vertical). These two things can never produce smooth movement. The render uses 512 angle positions for 360 degrees.

I'm really curious how those circle scrollers were programmed...
Kalms
Member
#16 - Posted: 23 Aug 2007 01:12 - Edited
Reply Quote
Circle scrollers are done in just the same way as your routine.

There are two main mitigating factors: Circle scrollers move faster than the dot in your example, and letters are made up of multiple points (which will randomly distribute the individual dots' wiggliness over each letter, and your mind will smoothen out the result -- similar to some kind of subpixel positioning).

You really need to start animating your dot, and see what happens.

First, make it spin at 1/1024th speed. The dot is going to look extremely jumpy. Why? Well, most frames it will stand still. Some frames it moves 1 pixel vertically. Other frames it moves 1 pixel horizontally. And rarely it will move 1 pixel diagonally.
So the pixel's movement speed is either 0.0, 1.0 or 1.414 pixels/frame, with random switching. Also, the movement direction will randomly switch by 90 degrees (horizontal<->vertical). All this will be very apparent to the eye.


Now, make it spin at, say, 20/1024th speed. The dot will now move with an average speed of 6.28 pixels/frame. What this means is that the pixel will, each frame, move a couple of pixels horizontally and a couple of pixels vertically. The exact number of pixels will vary a little, but:
the minimum distance travelled will be approximately 6 pixels, and maximum distance travelled will be approximately 7 pixels. You will not be able to notice the speed variations very easily. Also, the direction will wobble just a couple of degrees left/right.

End result: Faster movement (larger step between locations) => less visual stuttering/wiggling.
z5_
Member
#17 - Posted: 23 Aug 2007 12:04 - Edited
Reply Quote
@kalms: my dot was already moving before i posted yesterday. I didn't seem to find the right settings for smooth movement though. I'll experiment some more.

@to those interested: if you watch the picture of the circle closely and imagine it as just a single dot moving along all those positions, you will actually see what Kalms explained. You should see the spots where the pixel will move horizontally without moving vertically and vice versa. You will also see spots where the pixel would not move between two plots (= the dark blue spots, where two pixels are overlapping).
winden
Member
#18 - Posted: 31 Aug 2007 13:12 - Edited
Reply Quote
It's cool that you keep experimenting and progressing, keep on it and take a look at kalms' advice on "letters are made of multiple points". Just like the running zebras disturb the 20/20 vision of the lions, multiple points will make your eyes focus on the whole and not on the detail

ps. watch closely the starfield at the trashcan 3 intro in a real amiga (video codecs may mask the effect) and you'll see what I mean about the detail and the whole
z5_
Member
#19 - Posted: 4 Sep 2007 22:03
Reply Quote
On to the next question... So far, i've rendered a sinus table using asm-one and for a certain amplitude (= radius if you want circular movement). It works and it is quite easy if you read all that is written in this thread.

However, i imagine that coders only insert one sinus table into their code and use it for various amplitudes. Which brings me to the next question, quoting Kalms from the first post:

Your sine table will contain values multiplied by a constant, indeed. If you have all values multiplied by 16384, then you can use 16/32-bit maths without having to fear overflow all the time.

Ok, imagine this: i want a dot moving up and down (no x movement) using sinus calculation. Imagine my middlepoint at y=100 and i want the amplitude to be an input variable to my routine.

Values multiplied by 16384 means that the values in my sinus table will range from -16384 to 16384.

My angle will go from 0 -> 1024 and for each of these angles, i will look up the sinus value. However, from this sinus value, i will need to calculate the y-offset, which is dependant on the amplitude.

Hmm... as i'm writing this, i'm just thinking: don't i just needed an equation of a line to calculate my y-offset?... Or is there another possibility i don't see.
Kalms
Member
#20 - Posted: 5 Sep 2007 13:37 - Edited
Reply Quote
If you were using a high-level language and floating-point maths, the code for drawing a swinging dot would look something like this:

procedure DrawDot(position, amplitude)
  x = 160
  y = 100 + (sin(position) * amplitude)
  PutPixel(x, y);


You would call that function once per frame, increasing the value of "position" each time.

Since sin() has a period of 2*PI radians, a suitable increase for position would be approximately 0.1 units per frame. Amplitude could be 50 or so.

Next step is to replace sin() with a table lookup. The table has 1024 floating-point entries, and the sine wave in the table has the amplitude 1.0. The code is similar:

procedure DrawDot(position, amplitude)
  x = 160
  y = 100 + (sin[position] * amplitude)
  PutPixel(x, y);


the biggest difference here is that position needs to be an integer index value. Since sin[] has a period of 1024 units, a suitable increase for position would be 0.1*(1024 / (2*PI)) ~= 16.03, so let's say that position increases by 16 each frame.

The next step would be to make the sinewave in the sin[] table have an amplitude higher than 1.0; let's change it to have amplitude 16384. We are still using floating-point calculations though. We compensate for the scale factor that is built into the table by dividing the final value of x by 16384. Here is the new code:

procedure DrawDot(position, amplitude)
  x = 160
  y = 100 + ((sin[position] * amplitude) / 16384) 
  PutPixel(x, y);


The next thing to do is to convert the sine table and remaining calculations to integer. Make amplitude an integer as well, then we can do:

procedure DrawDot(position, amplitude)
  x = 160
  y = 100 + ((sin[position] * amplitude) >> 14)
  PutPixel(x, y);


(where >> is signed shift right (ASR instruction in 68k assembly))

What is important here is to ensure that there are no overflows during the calculations, i.e. all the intermediate values fit into the variables. You do that by looking at the range of all involved values:

sin[position] -- will be a value from -16384 .. +16384

amplitude -- is 50

sin[position] * amplitude -- is -(16384 * 50) .. +(16384 * 50)

(sin[position] * amplitude) >> 14) -- is -50 .. +50

100 + (sin[position] * amplitude) >> 14) -- is +50 .. +150

All values fit within a 32-bit integer, so the calculations will work fine if you implement it using longword arithmetic on a 68k processor.
korruptor
Member
#21 - Posted: 5 Sep 2007 18:11
Reply Quote
That's an excellent write up :D
michael phipps
Member
#22 - Posted: 6 Sep 2007 14:10
Reply Quote
Yeah! I agree but how about an assembler version or do i have to write an example to lol!!!
d0DgE
Member
#23 - Posted: 6 Sep 2007 18:46
Reply Quote
hooray for this collection \o/
Let's see if I finally get this in my brain for I'm a complete math disaster :D
z5_
Member
#24 - Posted: 6 Sep 2007 20:12
Reply Quote
@michael: i prefer pseudo code + explanation over any actual assembler code myself :o)

@d0DgE: in that case, you are the living proof that enjoyable demos can be done without being a maths wizard :o) Which is reassuring as i was beginning to wonder and doubt... But seriously, if you follow this tutorial, you'll get the basics right fast imo because the explanations are excellent. Even i am digging in...
Rebb
Member
#25 - Posted: 11 Jun 2009 23:19
Reply Quote
I'm still having huge problems with this :)
Trying to calculate new x when rotated with 10 degrees.

so formula goes like: x' = x * cos(angle) - y * sin(angle)

code:
move.w #10,d4 ; angle
lea sintable,a1 ; IS 0,360,1024,256,0,w,256,y,y

angles:
move.w (a2,d4.w),d3 ;sin angle to d3
asr.l #8,d3
move.l d3,sinAngle

add.w #90,d4
move.w (a3,d4.w),d5
asr.l #8,d5
move.l d5,cosAngle

move.l #100,d0 ; original x
move.l #100,d1 ; original y

move.l cosAngle,d2
move.l sinAngle,d3
muls d0,d2 ; x * cosAngle
muls d1,d3 ; y * sinAngle
sub d2,d3 ; x*cos(angle) - y*sin(angle)
rts

I'm obviously doing something very wrong here as values for x' goes skyrocketing. Any ideas?
Kalms
Member
#26 - Posted: 11 Jun 2009 23:29 - Edited
Reply Quote
1) you probably have crap in the upper half of d3 and d5 before entering the code. Guess what happens then?

2) you should shift down (asr #8) *after* the multiplication. Otherwise you are just converting your sin/cos values to... well, -1 or 0 or +1 before the multiplication takes place.

Consider difference between:

x' = (x * cosvalue_times_256_from_table) / 256 + ...

against:

x' = x * (cosvalue_times_256_from_table / 256) + ...

assuming that all values being used are integers.
Rebb
Member
#27 - Posted: 12 Jun 2009 00:43
Reply Quote
Kalms: Registers where actually cleared otherwhere, my bad they didn't made up to forum post.

Good point about shifting, don't know what i was thinking :D
Also has an error in reading sintable, should be move.w (a2,d4.w*2),d3

This points out once again, how blind one can be for his own errors in code. Many thanks!
Vektor
Member
#28 - Posted: 13 Jun 2009 10:28
Reply Quote
This was a plotter I wrote once, I don't know how fast it exactly is but with the same technique I managed to plot 2000+ pixels per frame on a A500.


screenwidth = 40 ; bytes / *8 = pixels

Start:
bsr Init_shift_table ; init shift table

bsr plot ;run plot loop

rts

Plot:
lea coords+2(pc),a0
lea Shift_Table(pc),a1
move.L screen(pc),a2

lea [2000/80].w,a3
lea 0.w,a4

Plot_loop:
rept 20
movem.w (a0)+,d0-d7 ; d0;d2;d4;d6=x /d1;d3;d5;d7=y

lsl.w #6,d1 ;* width
add.b (a1,d0.w),d1 ;add x-co word
not.w d0 ;shift bit
bset d0,(a2,d1.W) ;set pixel

lsl.w #6,d3 ;* width
add.b (a1,d2.w),d3 ;add x-co word
not.w d2 ;shift bit
bset d2,(a2,d3.W) ;set pixel

lsl.w #6,d5 ;* width
add.b (a1,d4.w),d5 ;add x-co word
not.w d4 ;shift bit
bset d4,(a2,d5.W) ;set pixel

lsl.w #6,d7 ;* width
add.b (a1,d6.w),d7 ;add x-co word
not.w d6 ;shift bit
bset d6,(a2,d7.W) ;set pixel
endr

subq.w #1,a3
cmp.w a4,a3
bne.w plot_loop
rts

Init_shift_table:
lea Shift_Table(pc),a0
moveq #screenwidth-1,d7
moveq #0,d0
.Shift_loop:
rept 8
move.b d0,(a0)+
endr
addq.b #1,d0
dbf d7,.shift_loop
rts

Shift_Table:
blk.b screenwidth*8,0
Vektor
Member
#29 - Posted: 13 Jun 2009 10:47
Reply Quote
1) you probably have crap in the upper half of d3 and d5 before entering the code. Guess what happens then?

Exactly, I always multiplied my sin tables with 2^15 so you only needed two instructions (see below) to have the least round off error

add.l d1,d1 ;*2 (or shift left)
swap d1 ;swap lower 16 bits with upper 16 bit (or shift right 16 bits)

To improve the routine further you could precalc the "muls" in the rotation with a 64kb table. I will look for an example....
Rebb
Member
#30 - Posted: 14 Jun 2009 02:23 - Edited
Reply Quote
Another newbie question!

How do i handle overflow when multiplying words?

Example, I got vertices x=-4 and y=-2
Converting them to screencordinates gives: x=156 and y=258
x is centered by adding 160 to it, and y is getted by screen height - y

I got sintable as in previous example ( IS 0,360,1024,256,0,w,256,y,y )
so Sin from angle 10 is $F00 and cosin is $9300

X*cosAngle = $FFBD9400
asr.w #8 -> $FFBDFF94
Y*sinAngle = $F1E00
asr.w #8 -> $F001E

This is obviously very wrong, what is right way to handle this situation?

EDITED correct values.
 Page:  1  2  »» 

  Please register a new account or log in to comment

  

  

  

 

A.D.A. Amiga Demoscene Archive, Version 3.0