Jump to content
IGNORED

IntyBASIC acceleration/velocity demo


intvnut

Recommended Posts

After seeing a comment on another thread about not knowing how to perform smooth acceleration of objects, I decided to throw together a quick demo of how I implement such things.

 

This may not be the most idiomatic IntyBASIC code. But, looking at the compiled output, it's not too bad.

 

nanochess: If you think this is worth including in the contrib directory, be my guest.

 

The heart of the code is the UpdatePhysics procedure. It implements some really straightforward equations:

 

Velocity1 = Velocity0 + Acceleration

Position1 = Position0 + Velocity1

 

The devil, of course, is in the details: Shifting, rounding, handling signed vs. unsigned values, and of course, handling wraparound on the screen.

.

'' ======================================================================== ''
''  UpdatePhysics                                                           ''
''                                                                          ''
''  This computes the new position and velocity given the current velocity  ''
''  and acceleration.                                                       ''
''                                                                          ''
''  AX, AY give the new acceleration input for the selected object.         ''
''  The acceleration inputs are zero for all other objects.                 ''
'' ======================================================================== ''
UpdatePhysics:  PROCEDURE
    ' Compute new velocity for selected object.
    #V = VX(SEL) + AX
    IF #V > 127 THEN #V = 127
    IF #V < -127 THEN #V = -127
    VX(SEL) = #V

    #V = VY(SEL) + AY
    IF #V > 127 THEN #V = 127
    IF #V < -127 THEN #V = -127
    VY(SEL) = #V

    ' Compute new position for all 8 objects.
    FOR I = 0 to 7
        #V = VX(I) * 4
        #PX(I) = #V + #PX(I)
        ' Stay on visible display by keeping X in [0, 168].
        IF #PX(I) >= 168*256 THEN #PX(I) = (168*256 XOR (#V > 0)) + #PX(I)

        #V = VY(I) * 4
        #PY(I) = #V + #PY(I)
        ' Stay on visible display by keeping Y in [0, 104].
        IF #PY(I) >= 104*256 THEN #PY(I) = (104*256 XOR (#V > 0)) + #PY(I)
    NEXT I
END

.

Also, the code implements its own 16-direction disc direction decoder, since I didn't see how to do that with IntyBASIC's built-in primitives. I wanted to be sure I clearly separated each input into keypad vs. action vs. disc, while also supporting all 16 directions. So, I sample CONT once (ok, technically twice), and do the rest of the decode myself. This gave more consistent results than when I sampled CONT multiple times.

.

 

accel.zip

  • Like 4
Link to comment
Share on other sites

BTW, usage info, in case you're not bothered to read the source:

  • Pressing keypad 1 through 8 will select one of the 8 objects. The selected object will blink.
  • Pressing DISC will accelerate the selected object toward the direction pushed on the disc. Acceleration is smoothed somewhat.
  • Pressing any action button will decelerate the selected object.
  • Otherwise, all objects will coast with their current velocities.

That's about it. There's not much to it, really.

  • Like 1
Link to comment
Share on other sites

Also, you may wonder what this idiom is doing:

.

        #V = #V + ($1F XOR 2*(#V < 0)) ' Round away from 0

.

It appears a few places. What it's doing is adding "+x" if the value is positive, and "-x" if the value is negative. This idiom works if x has a '1' in its LSB.

 

Let's break it down:

  • +31 has the bit pattern $001F.
  • -31 has the bit pattern $FFE1.
  • The comparison (#V < 0) will return $0000 or $FFFF based on whether #V is less than 0 or not.
  • Therefore, 2*(#V < 0) will return $0000 or $FFFE.
  • $001F XOR $0000 = $001F.
  • $001F XOR $FFFE = $FFE1.

Tada!

 

The reason I round away from 0 in a few places is that I want small fractional values to still impact the result rather than getting rounded away. Otherwise, you get into situations where you can't get an object fully to the target velocity. This is especially a problem when stopping, when your acceleration is proportional to your velocity, but your velocity is heading to 0.

Edited by intvnut
Link to comment
Share on other sites

BTW, the "round away" idiom is slightly less expensive than the more obvious form:

#V = #V + SGN(#V)*15

That's mainly due to the cost of the multiply.

 

Does it make sense to have an "Apply Sign" primitive that, given the sign of the first argument, provide the the second argument unaltered, zeroed, or negated?

  • Like 1
Link to comment
Share on other sites

I was about to ask if SGN would work but in case I was missing something obvious, I didn't. Traditionally that would be the BASIC way to do it, in particular in dialects that don't implement XOR natively. While XOR can be simulated using AND and OR, the rewrite in those other dialects surely costs more than the SGN operator then does.

Link to comment
Share on other sites

OK, back in the day I considered myself pretty nimble with mental math etc. however, the fact is I learned assembly language programming in a semester at college back in 1981/82 on a cyber 730. If I recall it was an octal machine with a ten bit word. Now after 35 years of "business" programming using high level languages, raising two kids and (maybe) drinking a bit too much. I can't even follow a lot of what you guys are talking about. It's still fun to read though. I mean I remember XOR and feel I should know exactly how that impacts things, but when I'm coding intyBasic I am at such a different level. a much dumber level, than that.


#V = #V + ($1F XOR 2*(#V < 0)) ' Round away from 0 not a chance I figure that out without the great explanation. AND, it took me quite a while to understand it with the explanation.


(BTW, this entire post is intended as a compliment to all you guys posting these helpful tips even if it it doesn't seem like it some times)


so, I love this little demo. I have learned a bunch of stuff from it. I still am not sure I understand how it does all that it does, but I'm still working on it.


as far as the smooth movement: at a really basic level It looks the movement is smoothed a great deal by multiplying the location by 256, add the amount of movement (velocity) each frame (or every so often) and then divide by 256 when you are ready to position the sprite. In essence you shifted the significant number left (multiplying by 256) and then ignore, but don't lose, the incremental portions by positioning to the number divided by 256.


Please keep the comments, code and helpful suggestions coming.
  • Like 1
Link to comment
Share on other sites

however, the fact is I learned assembly language programming in a semester at college back in 1981/82 on a cyber 730.

 

Would that be one of these guys? http://www.bitsavers.org/pdf/cdc/cyber/cyber_170/60456100A_Cyber_170_Model_720_730_750_760_176_CPU_PPU_Instr_Set_Mar79.pdf

 

By the time I got to college, we had a Cyber 930. I never really got to program them, though, as I spent most of my time on the AT&T StarServer E and SparcStation 2s. In 1982, I was still poking around BASIC on my babysitter's Commodore VIC-20 and Commodore 64.

 

 

as far as the smooth movement: at a really basic level It looks the movement is smoothed a great deal by multiplying the location by 256, add the amount of movement (velocity) each frame (or every so often) and then divide by 256 when you are ready to position the sprite. In essence you shifted the significant number left (multiplying by 256) and then ignore, but don't lose, the incremental portions by positioning to the number divided by 256.

 

There are two pieces to the smoothing:

  • Maintaining a fractional position and fractional velocity. That's what the multiplying and dividing are about.
    • The position has 8 fractional bits. That's why I divide by 256 when I need the integer portion of the position.
    • The velocity has only 6 fractional bits. That's why I multiply by 4 before adding to the position.
  • Decoupling "desired velocity" from "actual velocity."
    • I keep track of each object's current velocity.
    • When you press the disc a particular direction, I interpret that as a desired velocity, and then compute the necessary acceleration from the difference between desired and actual.
    • I scale down the acceleration so it takes multiple frames to get from the current velocity to the desired velocity.

 

The first bullet allows you to specify velocities other than integer multiples of the frame rate. The second bullet allows you to smoothly accelerate the object, so it smoothly transitions between velocities.

 

The little trick with XOR is just a rounding trick to save some cycles. I could have also written something more straightforward (and more verbose):

.

' Round away from 0.
IF #V < 0 THEN #V = #V - $1F ELSE #V = #V + $1F

.

As I mentioned before, the reason I round away from zero is that when the acceleration value gets small, a simple divide will truncate the acceleration to 0. So, when you're trying to stop an object by accelerating by "-velocity / scale_factor", once the velocity gets below a certain threshold, you won't be able to decelerate further. The round-away-from-zero ensures you continue to decelerate until you reach a velocity of 0.

 

Similar reasoning applies elsewhere this idiom appears. For example, suppose you go from traveling east to traveling north. In this acceleration model, the horizontal velocity has to decay toward 0 while the vertical velocity has to decay toward maximum negative. Without the round-away-from-zero code, the horizontal velocity would never quite make it to 0 in this model.

Edited by intvnut
  • Like 1
Link to comment
Share on other sites

By the time I got to college, we had a Cyber 930. I never really got to program them, though, [...]

 

Also, that's not completely true. The Cyber 930 had a pre-ANSI C compiler. It was so pre-ANSI, it accepted =+ rather than +=. I did compile a maze generation program on there so I could print large mazes on the wide green-bar paper. I also tried porting my VT-100 animation demo to it; however, it ran extremely slowly and I hit my CPU quota before it finished running.

Link to comment
Share on other sites

 

OK, back in the day I considered myself pretty nimble with mental math etc. however, the fact is I learned assembly language programming in a semester at college back in 1981/82 on a cyber 730. If I recall it was an octal machine with a ten bit word. Now after 35 years of "business" programming using high level languages, raising two kids and (maybe) drinking a bit too much. I can't even follow a lot of what you guys are talking about. It's still fun to read though. I mean I remember XOR and feel I should know exactly how that impacts things, but when I'm coding intyBasic I am at such a different level. a much dumber level, than that.

 

Different story but similar result for me... I'm sure I could make a demo almost as good, except the code would be twice as long.

  • Like 1
Link to comment
Share on other sites

 

Would that be one of these guys? http://www.bitsavers.org/pdf/cdc/cyber/cyber_170/60456100A_Cyber_170_Model_720_730_750_760_176_CPU_PPU_Instr_Set_Mar79.pdf

 

By the time I got to college, we had a Cyber 930. I never really got to program them, though, as I spent most of my time on the AT&T StarServer E and SparcStation 2s. In 1982, I was still poking around BASIC on my babysitter's Commodore VIC-20 and Commodore 64.

 

 

That would be the one. though I admit I never really got to play with it, per se. key in assignments on teletypes mostly. if you were lucky you could occasionally use one of the three full screen editors in the lab but the TA's and lab monitors usually had them occupied. Key it in. request a compile. go down stairs to the computer room an hour or so later and a small window would occasionally slide open and place green bar reports rolled up with rubber bands around them. Find yours and then learn if it compiled and if so you could go back to the lab and try running the assignment scenarios, etc. I went off to college with my intellivision and my Atari 400. There were times I felt I had more computing power in my dorm room then at the lab.

 

After winter break things improved in that we had 12 full screens and just two teletype machines. by then all the assignments were in Pascal. We also had programming classes in Fortran and of course Cobol. Back then we didn't even have a computer science degree, Computers were just a specialty in the school of mathematics. (which would lead you to think I should be better at math). Just as I was leaving I think the Cray 205 came on line. I ended up getting a job on the IBM midrange (System 38, AS/400, System i) and programming in RPG III most of my 30+ years.

  • Like 1
Link to comment
Share on other sites

Back then we didn't even have a computer science degree, Computers were just a specialty in the school of mathematics.

 

Funny you should say that. The school I went to—Bradley University—had a CS department, but it wasn't accredited. It seemed like it was staffed almost entirely with former math professors who couldn't hack teaching math. The only reason I managed to learn how to program properly is that I got an EE degree instead. ;)

Link to comment
Share on other sites

After seeing a comment on another thread about not knowing how to perform smooth acceleration of objects, I decided to throw together a quick demo of how I implement such things.

 

This may not be the most idiomatic IntyBASIC code. But, looking at the compiled output, it's not too bad.

 

nanochess: If you think this is worth including in the contrib directory, be my guest.

 

The heart of the code is the UpdatePhysics procedure. It implements some really straightforward equations:

 

Velocity1 = Velocity0 + Acceleration

Position1 = Position0 + Velocity1

 

The devil, of course, is in the details: Shifting, rounding, handling signed vs. unsigned values, and of course, handling wraparound on the screen.

.

'' ======================================================================== ''
''  UpdatePhysics                                                           ''
''                                                                          ''
''  This computes the new position and velocity given the current velocity  ''
''  and acceleration.                                                       ''
''                                                                          ''
''  AX, AY give the new acceleration input for the selected object.         ''
''  The acceleration inputs are zero for all other objects.                 ''
'' ======================================================================== ''
UpdatePhysics:  PROCEDURE
    ' Compute new velocity for selected object.
    #V = VX(SEL) + AX
    IF #V > 127 THEN #V = 127
    IF #V < -127 THEN #V = -127
    VX(SEL) = #V

    #V = VY(SEL) + AY
    IF #V > 127 THEN #V = 127
    IF #V < -127 THEN #V = -127
    VY(SEL) = #V

    ' Compute new position for all 8 objects.
    FOR I = 0 to 7
        #V = VX(I) * 4
        #PX(I) = #V + #PX(I)
        ' Stay on visible display by keeping X in [0, 168].
        IF #PX(I) >= 168*256 THEN #PX(I) = (168*256 XOR (#V > 0)) + #PX(I)

        #V = VY(I) * 4
        #PY(I) = #V + #PY(I)
        ' Stay on visible display by keeping Y in [0, 104].
        IF #PY(I) >= 104*256 THEN #PY(I) = (104*256 XOR (#V > 0)) + #PY(I)
    NEXT I
END

.

Also, the code implements its own 16-direction disc direction decoder, since I didn't see how to do that with IntyBASIC's built-in primitives. I wanted to be sure I clearly separated each input into keypad vs. action vs. disc, while also supporting all 16 directions. So, I sample CONT once (ok, technically twice), and do the rest of the decode myself. This gave more consistent results than when I sampled CONT multiple times.

.

 

 

Thanks for this demo. I wish a lot more programmers treated input processing as a higher level physics problem rather than just scalar translation. Too many games just read the disc and translate it directly into a particular vector rather than apply a bit of physics and damping, which add not only a bit of "realism," but accuracy to the controls.

 

Too many times I play games where just tapping the disc results in moving a cursor or avatar more than intended, and so I spend more time correcting my movements back-and-forth than applying strategy or having fun.

 

Your particular example may be great for a spaceship sort of game, but increasing the acceleration and reducing the deceleration a bit more drastically could serve to use it as a "pointer" cursor in menus or other sort of games.

 

-dZ.

Link to comment
Share on other sites

 

To many times I play games where just tapping the disc results in moving a cursor or avatar more than intended, and so I spend more time correcting my movements back-and-forth than applying strategy or having fun.

 

Indeed. One game that actually frustrates me a lot in that regard is Astrosmash. It's impossible to nudge 1px, and so there are some small objects that are harder to hit than necessary.

 

 

 

Your particular example may be great for a spaceship sort of game, but increasing the acceleration and reducing the deceleration a bit more drastically could serve to use it as a "pointer" cursor in menus or other sort of games.

 

Indeed. I purposefully exaggerated the smoothing given its inspiration was a space game.

 

You can decrease the smoothing factor by changing the divisor here:

.

    ' Try to interpret the input as a DISC input.  If the disc is pressed,
    ' pick an acceleration that will eventually cause velocity to match the
    ' direction pushed.
    IF I < $20 AND DiscOK(I) THEN
        #V = DiscX(I) - VX(SEL)
        #V = #V + ($1F XOR 2*(#V < 0)) ' Round away from 0
        AX = #V / $20

        #V = DiscY(I) - VY(SEL)
        #V = #V + ($1F XOR 2*(#V < 0)) ' Round away from 0
        AY = #V / $20
    END IF

.

If you wanted to make it a compile time parameter, perhaps something like this makes sense:

.

    ' ACCEL_SMOOTHING determines how quickly we accelerate toward our target
    ' velocity.  For efficient execution, it should be a power of 2.
    ' DECEL_SMOOTHING is the same, but for deceleration.
    CONST ACCEL_SMOOTHING = $20
    CONST DECEL_SMOOTHING = $10

    ' If action button pressed, decelerate current object.
    IF HasAction(I / $20) THEN
        #V = -VX(SEL)
        #V = #V + ((DECEL_SMOOTHING - 1) XOR 2*(#V < 0)) ' Round away from 0
        AX = #V / DECEL_SMOOTHING

        #V = -VY(SEL)
        #V = #V + ((DECEL_SMOOTHING - 1) XOR 2*(#V < 0)) ' Round away from 0
        AY = #V / DECEL_SMOOTHING
        RETURN
    END IF

    ' Try to interpret the input as a DISC input.  If the disc is pressed,
    ' pick an acceleration that will eventually cause velocity to match the
    ' direction pushed.
    IF I < $20 AND DiscOK(I) THEN
        #V = DiscX(I) - VX(SEL)
        #V = #V + ((ACCEL_SMOOTHING - 1) XOR 2*(#V < 0)) ' Round away from 0
        AX = #V / ACCEL_SMOOTHING

        #V = DiscY(I) - VY(SEL)
        #V = #V + ((ACCEL_SMOOTHING - 1) XOR 2*(#V < 0)) ' Round away from 0
        AY = #V / ACCEL_SMOOTHING
    END IF

.

Indeed, I had intended to make the smoothing parameters compile-time constants with a better explanation of what they did. I'll post an update later with those tweaks rolled in.

 

In the meantime, here's the longer comment I've added in the code explaining the constants:

.

'' ======================================================================== ''
''  Movement smoothing:                                                     ''
''                                                                          ''
''  We accelerate/decelerate smoothly with a first-order differential       ''
''  equation.  All that really means is that we compute our new velocity    ''
''  in terms of the current velocity, plus a fraction of how far we are     ''
''  from our target velocity.                                               ''
''                                                                          ''
''     accel = (vel_target - vel_current) / smoothing_factor                ''
''     vel_current = vel_current + accel                                    ''
''                                                                          ''
''  The following two smoothing constants control the smoothing factors     ''
''  we use for acceleration and deceleration. Smaller factors converge      ''
''  faster.  For efficient computation, these should be powers of 2.        ''
'' ======================================================================== ''
CONST ACCEL_SMOOTHING = $20
CONST DECEL_SMOOTHING = $10

.

One other change I suppose you'd make if you're using this as a mouse pointer type of cursor is that I would make the object decelerate by default, rather than when the user presses a button.

 

This particular model of acceleration and velocity works in a wide variety of situations; however, it's not the only acceleration model. But, to your original point, the big first step is to break out of the "IF CONT.UP THEN Y = Y - 1" mindset.

  • Like 1
Link to comment
Share on other sites

 

Sorry, did someone just call my name?

 

*chuckle*

 

I've seen that in many authors' programs, myself included. Heck, that's essentially the control law I used in my Pong clone, to name one of many examples.

.

        MOVR    R0,     R1
        ANDI    #1,     R0      ; See if player pressed down
        BNEQ    @@notpaddown
@@domoveup
        INCR    R2              ; Yes?  Move down.
        CMPI    #89,    R2
        BLE     @@padok
@@badpadpos:
@@padposdone:
        JR      R5

@@notpaddown
        ANDI    #4,     R1      ; See if player pressed up.
        BNEQ    @@nopadpress
@@domovedown
        DECR    R2              ; Yes?  Move up.
        CMPI    #8,     R2
        BLT     @@badpadpos
@@padok:
        MVO@    R2,     R3      ; Store new Y position
        XORI    #512,   R2      ; Set 'YSize2' bit.
        ADDI    #8,     R4      ; Point to correct pad's Y position register
        MVO@    R2,     R4
        JR      R5

.

I think we all start there.

Edited by intvnut
Link to comment
Share on other sites

 

One other change I suppose you'd make if you're using this as a mouse pointer type of cursor is that I would make the object decelerate by default, rather than when the user presses a button.

 

 

Oh, I didn't notice that you could decelerate with a button press. I thought it just coasted until it stopped by friction, that's why I suggested a "faster deceleration."

 

Yes, I agree, if that were added by default (and the variables tweaked slightly), it would make for an awesome cursor pointer. :thumbsup:

 

-dZ.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...