Author Topic: hudpong2  (Read 1910 times)

0 Members and 1 Guest are viewing this topic.

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
i decided to give my epic hello world script a serious makeover, you can now play pong fullscreen in the lab, or start a game and have it texture your ship. it has better physics (still a wip), scoring, performs better,  and only uses 1 or 2 global variables. im putting it up early to help with debugging of rtt features, but il polish it up tomorrow and release a followup.

Code: [Select]
#Global Hooks

$GameInit:

[

--HudPong2 (tm)
--init bounding boxes for collision detection in local components
function initBoundingBox(w,h)
local bBox = {}
bBox.x1 = -(w/2)
bBox.x2 = w/2
bBox.y1 = -(h/2)
bBox.y2 = h/2
return bBox
end
--init player variables
function initPongPlayer(playerNum)
local player = {}
--read only
player.name = "Player "..playerNum
--writeable
player.score = 0
player.input = 0.5 --from 0 to 1
return player
end
--init paddle (centers at x,y, makes paddle of dimentions w,h)
function initPongPaddle(x,y,w,h)
local paddle = {}
--read only
paddle.bBoxLocal = initBoundingBox(w,h)
--writeable
paddle.xPos = x --world position
paddle.yPos = y
--paddle.xPosOld = x --frame old values for velocity computation (to impart ball spin and/or speed)
paddle.yPosOld = y
--paddle.xVel = 0 --deta data goes here
paddle.yVel = 0
paddle.bBoxWorld = xlateBoundingBox(paddle.bBoxLocal,x,y)
return paddle
end
--init ball (can be used at game start)
function initPongBall(x,y,r)
local ball = {}
--read only
ball.radius = r
ball.bBoxLocal = initBoundingBox(r*2,r*2)
--writeable
ball.xPos = x --ball position
ball.yPos = y
ball.bBoxWorld = xlateBoundingBox(ball.bBoxLocal,x,y)
--ball physics
--calculate velocity at game start
ball.yVel = (math.random()-0.5)-100 --vertical velocity in the rang of -0.5 to 0.5
if math.random() >= 0.5 then --choose serve direction at random
ball.xVel = 100 - ball.yVel --compensate for vertical component to always achieve a vector magnitude of 1
else
ball.xVel = -100 + ball.yVel
end
ball.sVel = 0 --ball spin velocity (not in any angular unit/s, rather in circumference units/s, but its proportional so who cares)
--other vars
ball.trans = 255 --transparency for death fade effect
ball.startDelay = 0.5 + (math.random()*1.5) --seconds till a new ball starts moving
return ball
end
--setup game to fit in xywh dimentions
function initPong(x,y,w,h)
--tableize!
local pong = {}
--gameinfo
pong.w = w --game size
pong.h = h
pong.x1 = x --game position (top left corner)
pong.y1 = y
pong.x2 = x+w --lower right corner
pong.y2 = y+h
pong.xC = x+(w/2)
pong.yC = y+(h/2)
pong.deathMessage = "Dont Loose Your Balls!" --message for when you die
pong.pause = false
pong.maxScore = 10 --you "win" when you get to this score first
--player info
pong.player = {}
pong.player[1] = initPongPlayer(1)
pong.player[2] = initPongPlayer(2)
--paddle info
pong.paddle = {}
pong.paddle[1] = initPongPaddle(x  +  (w/256), y+(h/2), w/128, h/16)
pong.paddle[2] = initPongPaddle((x+w)-(w/256), y+(h/2), w/128, h/16)
--ball info
pong.ball = initPongBall(x+(w/2), y+(h/2), ((w/64)+(h/64))/2)
--that is all
return pong
end
--draw funcs
function drawPongField(pong) --pong box here
--field
gr.setColor(0,50,0,20)
gr.drawRectangle(pong.x1, pong.y1, pong.x2, pong.y2, true)
--center
gr.setColor(0,100,0,40)
gr.drawCircle(pong.h/6,pong.xC,pong.yC)
gr.drawLine(pong.xC,pong.y1,pong.xC,pong.y2)
--sidelines
gr.setColor(0,200,0,255)
gr.drawLine(pong.x1, pong.y1, pong.x2, pong.y1)
gr.drawLine(pong.x1, pong.y2, pong.x2, pong.y2)
--scoreboard
gr.setColor(50,200,100,255)
local name = pong.player[1].name.." "
local score = tostring(pong.player[1].score).." "
gr.drawString(name.." "..pong.player[2].name,pong.xC - gr.getStringWidth(name), pong.y1 + gr.CurrentFont.Height)
gr.drawString(score.." "..tostring(pong.player[2].score),pong.xC - gr.getStringWidth(score), pong.y1 + (gr.CurrentFont.Height*2))
end
--draw paddle
function drawPongPaddle(paddle)
gr.setColor(0,0,200,255)
gr.drawRectangle(paddle.bBoxWorld.x1, paddle.bBoxWorld.y1, paddle.bBoxWorld.x2, paddle.bBoxWorld.y2, true)
end
--draw ball
function drawPongball(ball)
gr.setColor(200,0,0,ball.trans)
gr.drawCircle(ball.radius,ball.xPos,ball.yPos)
end
--draw everything
function drawPong(pong)
drawPongField(pong)
drawPongPaddle(pong.paddle[1])
drawPongPaddle(pong.paddle[2])
drawPongball(pong.ball)
end
--physics
--translate a bbox and returns a copy
function xlateBoundingBox(bBox,x,y)
local bBox2 = {}
--remember that tables are passed by reference, operate on components
bBox2.x1 = bBox.x1 + x
bBox2.x2 = bBox.x2 + x
bBox2.y1 = bBox.y1 + y
bBox2.y2 = bBox.y2 + y
return bBox2
end
--2d dot product
function dot2D(x1,y1,x2,y2)
return x1*y1+x2*y2
end
--check collision between bboxes (only used for paddle-ball collisions)
function collideBoundingBox(bBox1,bBox2)
--eliminate vertical non collision scenarios
if bBox1.y2 < bBox2.y1 or bBox1.y1 > bBox2.y2 then return false end
--eliminate horizontal non collision scenarios
if bBox1.x2 < bBox2.x1 or bBox1.x1 > bBox2.x2 then return false end
--no eliminations must have collided
return true
end
--check for collision between bounding box and the "walls" of the game  (second return indicates side "top" or "bottom")
function collideBoundingBoxWall(bBox, topWallPos, bottomWallPos)
--check top wall
if bBox.y1 < topWallPos then return true, "top" end
--check bottom wall
if bBox.y2 > bottomWallPos then return true, "bottom" end
--no collision
return false
end
--check for "collision" between bounding box and the invisible goal boundry (second return indicates side "left" or "right")
function collideBoundingBoxGoal(bBox, leftGoalPos, rightGoalPos)
--check left goal
if bBox.x1 < leftGoalPos then return true, "left" end
--check right goal
if bBox.x2 > rightGoalPos then return true, "right" end
--no collision
return false
end
--move ball
function movePongBall(ball)
--move ball using velocity
ball.xPos = ball.xPos + (ball.xVel*ba.getFrametime())
ball.yPos = ball.yPos + (ball.yVel*ba.getFrametime())
--update world bounding box
ball.bBoxWorld = xlateBoundingBox(ball.bBoxLocal,ball.xPos,ball.yPos)
end
--move paddle (also computes velocity)
function movePongPaddle(paddle, pos) --xPos, yPos)
--store old data
--paddle.xPosOld = paddle.xPos
paddle.yPosOld = paddle.yPos
--set paddle to new position
--paddle.xPos = xPos
paddle.yPos = pos --yPos
--compute velocity
--paddle.xVel = (paddle.xpos - paddle.xPosOld)*ba.getFrametime()
paddle.yVel = (paddle.yPos - paddle.yPosOld)*ba.getFrametime()
--recompute bounding box
paddle.bBoxWorld = xlateBoundingBox(paddle.bBoxLocal,paddle.xPos,paddle.yPos)
end
--run the ****ing game
function doPongFrame(pong)
--dont do anything if the game is paused
if not pong.pause then
--move paddles according to input for each player
for i=1, #pong.player do
local paddlePos = pong.y1 + (pong.player[i].input * pong.h)
movePongPaddle(pong.paddle[i], paddlePos)
--make sure we didnt cross a boundry with out paddle
if pong.paddle[i].yPos < pong.y1 + (pong.h/32) then
pong.paddle[i].yPos = pong.y1 + (pong.h/32)
--recompute bounding box position
pong.paddle.bBoxWorld = xlateBoundingBox(pong.paddle[i].bBoxLocal,pong.paddle[i].xPos,pong.paddle[i].yPos)
end
if pong.paddle[i].yPos > pong.y2 - (pong.h/32) then
pong.paddle[i].yPos = pong.y2 - (pong.h/32)
--recompute bounding box position
pong.paddle.bBoxWorld = xlateBoundingBox(pong.paddle[i].bBoxLocal,pong.paddle[i].xPos,pong.paddle[i].yPos)
end
end
--maybe move ball (this is easy)
if pong.ball.startDelay <= 0 then
movePongBall(pong.ball)
else --otherwise countdown till start
pong.ball.startDelay = pong.ball.startDelay - ba.getFrametime()
end
--check for collisions and respond appropriately
--ball-wall collisions first
local collide, direction = collideBoundingBoxWall(pong.ball.bBoxWorld,pong.y1,pong.y2)
if collide then
local dot, vs, n, d --dot product of velocity and wall, new spin value, normal y component, direction flip
if direction == "top" then n = 1 else n = -1 end --flip our normal y component if were hitting the bottom wall
if pong.ball.xVel < 0 then d = 1 else d = -1 end --ball travel direction matters too in determining how rotation is applied
--impart spin on the ball
dot = 1-dot2D(0,n,pong.ball.xVel,pong.ball.yVel) --more spin is imparted at glancing angles
vs = dot*n*d*ba.getFrametime() --do final calculation but dont apply it to the ball yet
--see if existing ball spin does anything to our velocity (we can store it on the ball now)
pong.ball.yVel = -pong.ball.yVel --we hit so flip our y velocity component to "bounce"
pong.ball.xVel = pong.ball.xVel-((pong.ball.sVel*n*d)*ba.getFrametime()) --spin affcts velocity only on the x axis, i think
pong.ball.sVel = pong.ball.sVel + vs --spin!
end
--ball-paddle collisions
for i=1, #pong.paddle do
collide = collideBoundingBox(pong.ball.bBoxWorld,pong.paddle[i].bBoxWorld)
if collide then
pong.ball.xVel = -pong.ball.xVel --cheap dirty hack because im tired and dont feel like doing the physics yet maybe il fill it in sometime tomorrow
break --we can only hit one paddle at a time
end
end
--this is mainly only used for scoring so it will be fairly easy
collide, direction = collideBoundingBoxGoal(pong.ball.bBoxWorld,pong.x1,pong.x2)
if collide then
if direction == "left" then --score for player 2
pong.player[2].score = pong.player[2].score + 1
else --score for player 1
pong.player[1].score = pong.player[1].score + 1
end
--reinit ball
pong.ball = initPongBall(pong.x1+(pong.w/2), pong.y1+(pong.h/2), ((pong.w/64)+(pong.h/64))/2)
end
end
--draw it
drawPong(pong)
end
--rtt Pong!
function initRttPong(w,h) --works the same, except x and y are considered 0, and returns a texture as a second argument
local pong = initPong(0,0,w,h)
local pongTex = gr.createTexture(w,h,TEXTURE_DYNAMIC)
return pong, pongTex
end
--render pong to texture
function doRttPongFrame(pong, pongTex) --also works about the same, returns a handle to the rtt texture
local oldtarget = gr.CurrentRenderTarget --save the old target while we work
gr.CurrentRenderTarget = pongTex --set new render target
doPongFrame(pong) --do the pong
gr.CurrentRenderTarget = oldtarget --go back to the previous target
return pongTex
end

]

#End
#Conditional Hooks
$State: GS_STATE_LAB ;lab pong
$On State Start:
[ labPong = initPong(100,100,gr.getScreenWidth()-200,gr.getScreenHeight()-200) ]
$On Frame:
[
--drive input
labPong.player[1].input = io.getMouseY()/gr.getScreenHeight()
labPong.player[2].input = io.getMouseX()/gr.getScreenWidth()
--run it
doPongFrame(labPong)
]
$On State End:
[ labPong = nil ]

$State: GS_STATE_GAME_PLAY
$On State Start:
[ rttPong,tex = initRttPong(512,256) ]
$On Frame:
[
--drive input
rttPong.player[1].input = io.getMouseY()/gr.getScreenHeight()
rttPong.player[2].input = io.getMouseX()/gr.getScreenWidth()
--run it
tex = doRttPongFrame(rttPong,tex)
--replace ship textures
ship = mn.Ships["Alpha 1"]
if ship:isValid() then
for i=1, #ship.Textures do
ship.Textures[i] = tex
end
end
]
$On State End:
[
rttPong = nil
tex:unload()
]
#End

I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN

 

Offline Galemp

  • Actual father of Samus
  • 212
  • Ask me about GORT!
    • Steam
    • User page on the FreeSpace Wiki
I can just see bored pilots on patrol playing Pong on their HUD...

...so if we have scripts that can render to texture, what other cool stuff can we do? Could we, for example, get the green grid ship select effects rendered by the engine in real time?
"Anyone can do any amount of work, provided it isn't the work he's supposed to be doing at that moment." -- Robert Benchley

Members I've personally met: RedStreblo, Goober5000, Sandwich, Splinter, Su-tehp, Hippo, CP5670, Terran Emperor, Karajorma, Dekker, McCall, Admiral Wolf, mxlm, RedSniper, Stealth, Black Wolf...

 

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
in the case of the green grid animation i don see why we just dont have an animation of the grid only, and sync the 3d model to it using keyframes in the animation, and then composite the two. you wouldnt need to do any render to texture at all.

way i figure it, best uses for rtt is:
cockpit textures: panels, displays, camera monitors (think descent), mirrors
procedural texture animation: like damage textures or glow animations

its actually a lot faster than most people think. my local version of the cockpit demo attempts to solve the issue buy capping the refresh rates and interleaving the rtt jobs so as to not be changing modes every frame. and not all rtt textures need to mess with video modes all that much, if youre just drawing primitives and not calling renderFrame() then things should be a little faster, and those can be run more frequently. you might want your radar updated every frame, but your mirrors and monitors could stay at 15-30fps, depending on how many you use.

biggest problem with rtt is probibly using it with renderFrame(). renderFrame() doesnt let us set any options, it tries to render the game to texture as if it was rendering it to the screen. i mean sometimes you just want it to render wireframes, or you may want to use lower graphics settings, lower end effects and more aggressive lod-ing, your essentially running at a very low resolution, and in that case it might pay off to disable specular, env, and normal maps.

things worked really good as of 3.6.12, bakground bitmaps ligned up with the starfield (ive seen situations where the nebulas went one way and the stars went another), most of the primitives worked, text and huds displayed correctly, etc. but newer nighties break the cockpit scripts, this one still works which frankly surprised me. ive hear that it may have been hud changes or go faster which may have broke it, but i mantised the issue to make sure.
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN

  

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
new version:

collision fixes
ball spin now affected by paddle
paddle motion during impact can induce spin
curveballs are now possible, though practically unavoidable

Code: [Select]
#Conditional Hooks
$Application: FS2_Open
$On Game Init:
[
--HudPong2 (tm)
--------------------------------
----- init data structures -----
--------------------------------
--init bounding boxes for collision detection in local components
function initBoundingBox(w,h)
local bBox = {}
bBox.x1 = -(w/2)
bBox.x2 = w/2
bBox.y1 = -(h/2)
bBox.y2 = h/2
return bBox
end
--init player variables
function initPongPlayer(playerNum)
local player = {}
--read only
player.name = "Player "..playerNum
--writeable
player.score = 0
--input data in the range of 0 to 1
player.inputX = 0.5
player.inputY = 0.5
return player
end
--init paddle (centers at x,y, makes paddle of dimentions w,h)
function initPongPaddle(x,y,w,h)
local paddle = {}
--read only
paddle.w = w
paddle.h = h
paddle.bBoxLocal = initBoundingBox(paddle.w,paddle.h)
--writeable
paddle.xPos = x --world position
paddle.yPos = y
paddle.xPosOld = paddle.xPos --frame old values for velocity computation (to impart ball spin and/or speed)
paddle.yPosOld = paddle.yPos
paddle.xVel = 0 --velocity data goes here
paddle.yVel = 0
--translate local bbox to world
paddle.bBoxWorld = xlateBoundingBox(paddle.bBoxLocal,paddle.xPos,paddle.yPos)
return paddle
end
--init (or re-serve) the ball (centers ball at x/y, of radius r, and initial speed s)
--optional string argument d indicates serve direction: "left" or "right", if ommited direction is chosen at random
function initPongBall(x,y,r,s,d)
local ball = {}
--read only
ball.radius = r
ball.bBoxLocal = initBoundingBox(ball.radius*2,ball.radius*2)
--writeable
ball.xPos = x --ball position
ball.yPos = y
ball.aRot = 0 --used for spin animation
--translate local bbox to world
ball.bBoxWorld = xlateBoundingBox(ball.bBoxLocal,ball.xPos,ball.yPos)
--ball physics (will assume ball mass of 1kg)
ball.lVel = 0 --magnitude of linear velocity
ball.aVel = 0 --ball spin velocity (rads a sec)
--figure out direction of serve
local dir
if type(d) ~= "string" then
dir = (math.random() >= 0.5) --no direction specified so choose serve direction at random
else
dir = (d == "left") -- left is boolean true in this case
end
--calculate velocity at game start
ball.yVel = (math.random()-0.5)*s --vertical velocity in the rang of -0.5*s to 0.5*s
if dir then --left serve
ball.xVel = -s + math.abs(ball.yVel) --compensate for vertical component to always achieve a vector magnitude of 1*s
else --right serve
ball.xVel = s - math.abs(ball.yVel)
end
--misc data (mostly for debug)
ball.deltaLinVel = 0 --last change in linear/angular velocity (magnitudes only) (computed each collision)
ball.deltaAngVel = 0
ball.lift = 0 --this is our fudged lift force (acceleration because mass = 1) that makes curve balls possible
--seconds till a new ball starts moving (randomize it a little)
ball.startDelay = 0.5 + (math.random()*1.5)
--seconds after the ball "hits" the goal boundry to remove it from play
ball.killDelay = 0.5
ball.killed = false --goes true when the ball "dies"
return ball
end
--setup game to fit in xywh dimentions
function initPong(x,y,w,h)
--tableize!
local pong = {}
--gameinfo
pong.w = w --game size
pong.h = h
pong.x1 = x --game position (top left corner)
pong.y1 = y
pong.x2 = x+w --lower right corner
pong.y2 = y+h
pong.xC = x+(w/2)
pong.yC = y+(h/2)
pong.pause = false
pong.maxScore = 10 --you "win" when you get to this score first
--side indicies
pong.leftIdx = 1
pong.rightIdx = 2
--player info
pong.player = {}
pong.player[pong.leftIdx] = initPongPlayer(pong.leftIdx)
pong.player[pong.rightIdx] = initPongPlayer(pong.rightIdx)
--paddle info
pong.paddle = {}
pong.paddle[pong.leftIdx] = initPongPaddle(x  +  (w/256), y+(h/2), w/128, h/16)
pong.paddle[pong.rightIdx] = initPongPaddle((x+w)-(w/256), y+(h/2), w/128, h/16)
--ball info
pong.ball = initPongBall(x+(w/2), y+(h/2), ((w/128)+(h/128))/2, w/4)
--enable debug readouts
pong.debug = true
--that is all
return pong
end
--------------------------
----- draw functions -----
--------------------------
--draw funcs
function drawPongField(pong) --pong box here
--field
gr.setColor(0,50,0,20)
gr.drawRectangle(pong.x1, pong.y1, pong.x2, pong.y2, true)
--center
gr.setColor(0,100,0,40)
gr.drawCircle(pong.h/6,pong.xC,pong.yC)
gr.drawLine(pong.xC,pong.y1,pong.xC,pong.y2)
--sidelines
gr.setColor(0,200,0,255)
gr.drawLine(pong.x1, pong.y1, pong.x2, pong.y1)
gr.drawLine(pong.x1, pong.y2, pong.x2, pong.y2)
--scoreboard
gr.setColor(50,200,100,255)
local name = pong.player[pong.leftIdx].name.." "
local score = tostring(pong.player[pong.leftIdx].score).." "
gr.drawString(name.." "..pong.player[pong.rightIdx].name,pong.xC - gr.getStringWidth(name), pong.y1 + gr.CurrentFont.Height)
gr.drawString(score.." "..tostring(pong.player[pong.rightIdx].score),pong.xC - gr.getStringWidth(score), pong.y1 + (gr.CurrentFont.Height*2))
--debug
if pong.debug then
gr.setColor(255,255,255,255)
gr.drawString("Debug info:", pong.x1 + gr.CurrentFont.Height, pong.y1 + gr.CurrentFont.Height)
gr.drawString("Current linear velocity: "..tostring(pong.ball.lVel))
gr.drawString("Current angular velocity: "..tostring(pong.ball.aVel))
gr.drawString("Last linear delta velocity: "..tostring(pong.ball.deltaLinVel))
gr.drawString("Last angular delta velocity: "..tostring(pong.ball.deltaAngVel))
gr.drawString("Current lift 'force': "..tostring(pong.ball.lift))
end
end
--draw paddle
function drawPongPaddle(paddle)
gr.setColor(0,0,200,255)
gr.drawRectangle(paddle.bBoxWorld.x1, paddle.bBoxWorld.y1, paddle.bBoxWorld.x2, paddle.bBoxWorld.y2, true)
end
--this is a vector image of the radiation symbol, just to inform people who wrote this
pongBallEffect = {
{x = 0.0000, y = 0.0000},
{x = -0.500, y = 0.866},
{x = -1.000, y = 0.000},
{x = 0.0000, y = 0.0000},
{x = -0.500, y = -0.866},
{x = 0.500, y = -0.866},
{x = 0.0000, y = 0.0000},
{x = 1.000, y = 0.000},
{x = 0.500, y = 0.866},
{x = 0.0000, y = 0.0000},
}
--draw ball and related effects
function drawPongBall(ball)
gr.setColor(50,0,0,255)
gr.drawCircle(ball.radius,ball.xPos,ball.yPos)
--animate rotation effect
ball.aRot = ball.aRot + ball.aVel*ba.getFrametime()*0.1 --(ball onlu showed as spinning at 1/10th velocity)
--transform effect
local newEffect = {}
--transform
for i=1, #pongBallEffect do
newEffect[i] = {}
newEffect[i].x = pongBallEffect[i].x
newEffect[i].y = pongBallEffect[i].y
--rotate
newEffect[i].x, newEffect[i].y = rot2D(newEffect[i].x, newEffect[i].y, ball.aRot)
--scale
newEffect[i].x, newEffect[i].y = scale2D(newEffect[i].x, newEffect[i].y, ball.radius)
--translate
newEffect[i].x, newEffect[i].y = add2D(newEffect[i].x, newEffect[i].y, ball.xPos, ball.yPos)
end
--render
gr.setColor(200,0,0,255)
for i=1, #newEffect-1 do
gr.drawLine(newEffect[i].x,newEffect[i].y,newEffect[i+1].x,newEffect[i+1].y)
end
end
--draw everything
function drawPong(pong)
drawPongField(pong)
drawPongPaddle(pong.paddle[pong.leftIdx])
drawPongPaddle(pong.paddle[pong.rightIdx])
drawPongBall(pong.ball)
end
-----------------
----- maths -----
-----------------
--2d add
function add2D(x1,y1,x2,y2) return x1+x2, y1+y2 end
--2d sub
function sub2D(x1,y1,x2,y2) return x1-x2, y1-y2 end
--2d scale
function scale2D(x,y,s) return x*s, y*s end
--2d dot product
function dot2D(x1,y1,x2,y2) return x1*x2+y1*y2 end
--get prependicular vector (right angle)
function prep2D(x,y) return -y,x end
--get magnitude
function mag2D(x,y) return math.sqrt(x*x+y*y) end
--normalize (also returns magnitude as a 3rd argument)
function norm2D(x,y)
local mag = mag2D(x,y)
local magR = 1/mag
return x*magR, y*magR, mag
end
--rotate
function rot2D(x,y,r)
local s = math.sin(r)
local c = math.cos(r)
return x*c-y*s,x*s+y*c
end
--translate a bbox and returns a copy
function xlateBoundingBox(bBox,x,y)
local bBox2 = {}
--remember that tables are passed by reference, operate on components
bBox2.x1 = bBox.x1 + x
bBox2.x2 = bBox.x2 + x
bBox2.y1 = bBox.y1 + y
bBox2.y2 = bBox.y2 + y
return bBox2
end
-------------------------
----- PONG PHYSICS! -----
-------------------------
--check collision between bboxes (only used for paddle-ball collisions)
function collideBoundingBox(bBox1,bBox2)
--eliminate vertical non collision scenarios
if bBox1.y2 < bBox2.y1 or bBox1.y1 > bBox2.y2 then return false end
--eliminate horizontal non collision scenarios
if bBox1.x2 < bBox2.x1 or bBox1.x1 > bBox2.x2 then return false end
--no eliminations must have collided
return true
end
--check for collision between bounding box and the "walls" of the game  (second return indicates side "top" or "bottom")
function collideBoundingBoxWall(bBox, topWallPos, bottomWallPos)
--check top wall
if bBox.y1 < topWallPos then return true, "top" end
--check bottom wall
if bBox.y2 > bottomWallPos then return true, "bottom" end
--no collision
return false
end
--check for "collision" between bounding box and the invisible goal boundry (second return indicates side "left" or "right")
function collideBoundingBoxGoal(bBox, leftGoalPos, rightGoalPos)
--check left goal
if bBox.x1 < leftGoalPos then return true, "left" end
--check right goal
if bBox.x2 > rightGoalPos then return true, "right" end
--no collision
return false
end
--move ball
function movePongBall(ball)
--[[do not work
apply "lift"
local d = 1 --fudged density value
local v = 2*math.pi*ball.radius*ball.aVel --nasa calls this rotational velocity
local g = 2*math.pi*ball.radius*v --and this is called vortex strength
local l = d*g*mag2D(ball.xVel, ball.yVel) --this is the lift force, but since mass = 1 and accel=force/mass this is also our acceleration
--]]
--normalize velocity to get flight direction (also get magnitude)
local vX,vY
vX,vY,ball.lVel = norm2D(ball.xVel, ball.yVel)
--get side vector
vX, vY = prep2D(vX, vY)
--compute "lift" "force"
ball.lift = ball.lVel*ball.aVel*ba.getFrametime()*0.1 --moar fudge factors!
--scale prependicular vector by our lift vector (adjusted for frametime)
vX, vY = scale2D(vX, vY, ball.lift*ba.getFrametime())
--apply to velocity
ball.xVel, ball.yVel = add2D(ball.xVel, ball.yVel, vX, vY )
--move ball using velocity
ball.xPos, ball.yPos = add2D(ball.xPos, ball.yPos, ball.xVel*ba.getFrametime(), ball.yVel*ba.getFrametime())
--update world bounding box
ball.bBoxWorld = xlateBoundingBox(ball.bBoxLocal,ball.xPos,ball.yPos)
end
--move paddle (also computes velocity)
function movePongPaddle(paddle, xPos, yPos)
--store old data
paddle.xPosOld = paddle.xPos
paddle.yPosOld = paddle.yPos
--set paddle to new position
paddle.xPos = xPos
paddle.yPos = yPos
--compute velocity
paddle.xVel = (paddle.xPos - paddle.xPosOld)*ba.getFrametime()*60 --yet another fudge factor!
paddle.yVel = (paddle.yPos - paddle.yPosOld) *ba.getFrametime()*60 --make it a little bit faster but still comp for variable framerate
--recompute bounding box
paddle.bBoxWorld = xlateBoundingBox(paddle.bBoxLocal,paddle.xPos,paddle.yPos)
end
---------------
---- pong -----
---------------
--do input for players, takes pong game, input value for p1 0 to 1, input value for p2  0 to 1
function doPongInput(pong,lInputX,lInputY,rInputX,rInputY)
--qualify input (must be in the range 0-1)
if lInputX < 0 then lInputX = 0 end
if lInputX > 1 then lInputX = 1 end
if lInputY < 0 then lInputY = 0 end
if lInputY > 1 then lInputY = 1 end
if rInputX < 0 then rInputX = 0 end
if rInputX > 1 then rInputX = 1 end
if rInputY < 0 then rInputY = 0 end
if rInputY > 1 then rInputY = 1 end
--post input
pong.player[pong.leftIdx].inputX = lInputX
pong.player[pong.leftIdx].inputY = lInputY
pong.player[pong.rightIdx].inputX = rInputX
pong.player[pong.rightIdx].inputY = rInputY
end
function doPongFrame(pong)
--dont do anything if the game is paused
if not pong.pause then
--move paddles according to input for each player
for i=1, #pong.player do
--keep the paddles between the lines
local newPosX = pong.paddle[i].xPos
--[[expiremental 2 axis mode
if i==pong.leftIdx then
newPosX = pong.x1 + (pong.paddle[i].w/2) + (pong.player[i].inputX * (pong.w/4))
else
newPosX = ((pong.x1 + (pong.w*0.75)) - (pong.paddle[i].w/2)) + (pong.player[i].inputX * (pong.w/4))
end
]]--
local newPosY = (pong.y1+1+(pong.paddle[i].h/2)) + ((pong.h-pong.paddle[i].h)*pong.player[i].inputY)
--move that sucker
movePongPaddle(pong.paddle[i], newPosX, newPosY)
end
--maybe move ball (this is easy)
if pong.ball.startDelay <= 0 then
movePongBall(pong.ball)
else --otherwise countdown till start
pong.ball.startDelay = pong.ball.startDelay - ba.getFrametime()
end
--check to see if ball is dead
if pong.ball.killed then
pong.ball.killDelay = pong.ball.killDelay - ba.getFrametime()
--maybe re-init the ball
if pong.ball.killDelay <= 0 then
pong.ball = initPongBall(pong.x1+(pong.w/2), pong.y1+(pong.h/2), ((pong.w/128)+(pong.h/128))/2, pong.w/4)
end
else --check for collisions and respond appropriately
--ball-wall collisions
local hit, dir = collideBoundingBoxWall(pong.ball.bBoxWorld,pong.y1,pong.y2)
if hit then --do collision response
--face normals for our sideline
local fVecX,fVecY
if dir == "top" then
fVecX, fVecY = 0,1 --front normal
else
fVecX, fVecY = 0,-1
end
--do collision response only if the ball is going at the wall when the collision takes place
if dot2D(fVecX,fVecY,pong.ball.xVel,pong.ball.yVel) < 0 then
local velX,velY,velA
--this is a fudged zero entropy deflection
velY = -pong.ball.yVel
--calculate spin (multiply by radius to calculate torque, since mass (and likewise moi) = 1, torque = angular acceleration)
velA = pong.ball.radius*pong.ball.xVel*-fVecY
--calculate change in velocity caused by ball rotation during impact
pong.ball.deltaLinVel = -(pong.ball.aVel/pong.ball.radius)*fVecY
velX = pong.ball.xVel + pong.ball.deltaLinVel
velA = velA - pong.ball.deltaLinVel
--apply new physics
pong.ball.xVel = velX
pong.ball.yVel = velY
pong.ball.deltaAngVel = (velA*ba.getFrametime())
pong.ball.aVel = pong.ball.aVel + pong.ball.deltaAngVel
end
end
--ball-paddle collisions
for i=1, #pong.paddle do --do for each paddle
hit = collideBoundingBox(pong.ball.bBoxWorld,pong.paddle[i].bBoxWorld)
if hit then --do collision response
--since both ball and paddle can have velocity, calculate paddel-local velocity for ball
local lpvX,lpvY = sub2D(pong.ball.xVel, pong.ball.yVel, pong.paddle[i].xVel, pong.paddle[i].yVel)
--side and face normals for our paddles
local fVecX,fVecY
if i == pong.leftIdx then
fVecX,fVecY = 1,0
else
fVecX,fVecY = -1,0
end
--do collision response only if the ball is going at the paddle when the collision takes place
if dot2D(fVecX,fVecY,lpvX,lpvY) < 0 then
local velX,velY,velA
--this is a fudged zero entropy deflection
velX = -lpvX
--calculate spin (multiply by radius to calculate torque, since mass (and likewise moi) = 1, torque = angular acceleration)
velA = pong.ball.radius*lpvY*fVecX
--calculate change in velocity caused by ball rotation during impact
pong.ball.deltaLinVel = -(pong.ball.aVel/pong.ball.radius)*-fVecX
velY = lpvY + pong.ball.deltaLinVel
velA = velA - pong.ball.deltaLinVel
--apply new physics
pong.ball.xVel = velX
pong.ball.yVel = velY
pong.ball.deltaAngVel = (velA*ba.getFrametime())
pong.ball.aVel = pong.ball.aVel + pong.ball.deltaAngVel
break --we can only hit one paddle at a time
end
end
end
--this is mainly only used for scoring so it will be fairly easy
hit, dir = collideBoundingBoxGoal(pong.ball.bBoxWorld,pong.x1,pong.x2)
if hit then
--face normals for our goals and who gets the point should a collision occure
local fVecX,fVecY,sIdx
if dir == "left" then --score for player 2 (right player)
fVecX,fVecY,sIdx = 1,0,pong.rightIdx
else --score for player 1 (left player)
fVecX,fVecY,sIdx = -1,0,pong.leftIdx
end
--do collision response only if the ball is going twards the goal (it may have been deflected by the paddle this frame)
if dot2D(fVecX,fVecY,pong.ball.xVel,pong.ball.yVel) < 0 then
--score goal
pong.player[sIdx].score = pong.player[sIdx].score + 1
--kill the ball
pong.ball.killed = true
end
end
end
end
--draw it
drawPong(pong)
end
---------------------
----- rtt Pong! -----
---------------------
--works the same as initPong(), except x and y are considered 0, and returns a texture handle as a second return value
function initRttPong(w,h)
local pong = initPong(0,0,w,h)
local pongTex = gr.createTexture(w,h,TEXTURE_DYNAMIC)
return pong, pongTex
end
--works about the same as doPongFrame(), except requires a texture handle to the rtt texture (also returns said handle)
function doRttPongFrame(pong, pongTex)
local oldtarget = gr.CurrentRenderTarget --save the old target while we work
gr.CurrentRenderTarget = pongTex --set new render target
doPongFrame(pong) --do the pong
gr.CurrentRenderTarget = oldtarget --go back to the previous target
return pongTex
end
]
; lab pong
$State: GS_STATE_LAB ;lab pong
$On State Start:
[ labPong = initPong(100,100,gr.getScreenWidth()-200,gr.getScreenHeight()-200) ]
$On Frame:
[
--drive input
doPongInput(labPong, io.getMouseX()/gr.getScreenWidth(), io.getMouseY()/gr.getScreenHeight(), io.getMouseX()/gr.getScreenWidth(), io.getMouseY()/gr.getScreenHeight())
--run it
doPongFrame(labPong)
]
$On State End:
[ labPong = nil ]
; rtt pong
$State: GS_STATE_GAME_PLAY
$On State Start:
[ rttPong,tex = initRttPong(512,256) ]
$On Frame:
[
--drive input
doPongInput(rttPong, io.getMouseX()/gr.getScreenWidth(), io.getMouseY()/gr.getScreenHeight(), io.getMouseX()/gr.getScreenWidth(), io.getMouseY()/gr.getScreenHeight())
--run it
tex = doRttPongFrame(rttPong,tex)
--replace ship textures
ship = mn.Ships["Alpha 1"]
if ship:isValid() then
for i=1, #ship.Textures do
ship.Textures[i] = tex
end
end
]
$On State End:
[
rttPong = nil
tex:unload()
]
#End
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN