TGC Codebase Backup



Mouse picking [DBC][memblocks] by Arkheii

5th Apr 2004 10:45
Summary

Polygon-perfect mouse picking example.



Description



Code
                                    ` This code was downloaded from The Game Creators
                                    ` It is reproduced here with full permission
                                    ` http://www.thegamecreators.com
                                    
                                    `Polygon mouse picking example by r_kabouter
`
`This code is very inefficient, but shows
`how this can be done without that raycasting
`and dot product crap. Optimize this in any
`way you wish. I recommend you do that anyway.
`
`If this helps you, please direct any credits to
`Yin Nadie, who wrote a tutorial over at
`http://www.ilogicgames.com because the
`equations here were found at his tutorial. It
`may also be good if you tell Yin that he was
`very helpful to you by e-mailing him at
`yin_nadie@hotmail.com
`
`Yin's tutorial was actually about collision,
`but I thought it could also be used to solve
`the mouse pick problem that everyone seems to
`have. So far this only works with objects and
`models, not matrices etc., but it's a start.
`
`Basically this works by taking a face and
`using it to make an imaginary triangle on
`the screen and test if the mouse is in that
`imaginary triangle.

set display mode 800,600,16
sync on
sync rate 0
autocam off
backdrop on
color backdrop 0
hide mouse

`Prepare a few objects: test1 is a cylinder, test2 is a cone.
`For this I made a few functions so I don't have to remember
`the object numbers. The functions are somewhere in the
`bottom of the code.
`
`Note that you can replace these with models that you want
`to load. You have to modify the code on your own though.
`I'd love to include my entire object loading library,
`but... naaah.
test1=makecylinder(4.0)
test2=makecone(4.0)

position object test1,-4,0,0
position object test2,4,0,0

`Dimmed an array of 3 elements. This is for the invisible
`plains that will be used to get the screen positions of
`vertices later. Since each face has 3 vertices, we have
`3 separate objects for each one of them. The makeplain()
`function is like the makecylinder() and makecone() functions
`earlier. For this I decided to use a plain since they take
`up less memory, having only 3 vertices and whatnot. They
`are also hidden because we don't have to see them.
`
`*****Make sure that you copy the following block of code
`in your program! These are needed for the function later.
`You may modify it so that it uses the standard
`make object plain command if you do not want to use the
`makeplain() function I added. Unless you want to dig
`into the function later and change variable names, I
`suggest that you stick to the array name pointerobj.*****
dim pointerobj(3)
for i=0 to 2
 pointerobj(i)=makeplain(1.0,1.0)
 hide object pointerobj(i)
next i

`Position the camera somewhere exciting :D
position camera 0,16,-16
point camera 0,0,0

`Beginning of the main loop.
do

`Duh.
ink rgb(255,255,255),0
set cursor 1,1
print screen fps()

`Just to show that the mouse pick function isn't a screen
`distance cheat, I rotate and move the objects. The float
`rot1# is the rotation value for the object test1, the
`cylinder. test1x# and test1z# are the x and z positions
`for the cylinder. rot2#, test2x#, and test2z# are the
`same as the previous 3 variables, except that they are
`the ones to be used by the test2 object, the cone.
rot1#=wrapvalue(rot1#+0.5)
rot2#=wrapvalue(rot2#-0.5)

`How convenient. We can use the rot variables in the sin
`and cos functions to make the objects move in circular
`directions.
test1x#=sin(rot1#)*4.0
test1z#=cos(rot1#)*4.0
test2x#=sin(rot2#)*8.0
test2z#=cos(rot2#)*8.0
rotate object test1,rot1#,rot1#,rot1#
rotate object test2,rot2#,rot2#,rot2#
position object test1,test1x#,0,test1z#
position object test2,test2x#,0,test2z#

`Replace the mouse image with something more precise.
dot mousex(),mousey()
circle mousex(),mousey(),4

`Just a simple mouse handler so that the mouse is only
`pressed once instead of being held down. Convenient for
`a big performance later on. If the mouse click is valid,
`the variable mouse is set to 1, which will be used later.
if mouseclick()
 if oldmouse=0
  mouse=1
  oldmouse=1
 else
  mouse=0
 endif
else
 oldmouse=0
 mouse=0
endif

`mouse=1, which means that a valid mouseclick was detected.
`Here it uses clickobject(objectNumber) to test if the
`mouse was on top of an object when the mouse was pressed.
`Notice that this will have to check for *all* the objects
`in the scene. In other words, it's not optimized. That's the
`problem you have to take care of on your own.
`
`If clickobject() says that the mouse was in fact on top of
`an object when the mouse was clicked, it returns a 1, else
`it returns a 0. For convenience, instead of using multiple
`if statements, I immediately assign the return values to
`the variables testXselected so we can re-check them again
`later.
`
`Also note that I used the mouse handler for this. If
`I didn't use the mouse handler and the mouse button was held
`down, it would keep checking the objects if the mouse is
`above them, which would result in terrible performance
`problems.
if mouse=1
 test1selected=clickobject(test1)
 test2selected=clickobject(test2)
endif

`This checks if both objects were selected. Remember that
`this code is unoptimized, so this is very unreliable when
`you have many objects on the scene(especially if they have
`lots of polys). What this does is check which object is
`closer to the camera during the instance of having 2 objects
`under the mouse. Using an estimated distance equation (meaning
`without the square root), we can check if which object is closer
`to the camera. That way, the farther object is deselected so
`that only one object is selected at a time.
if test1selected=1 and test2selected=1
 test1dist#=object position x(test1)^2+object position y(test1)^2+object position z(test1)^2
 test2dist#=object position x(test2)^2+object position y(test2)^2+object position z(test2)^2
 if test1dist#<test2dist#
  test2selected=0
 endif
 if test2dist#<test1dist#
  test1selected=0
 endif
endif

`Here's what we do about selected objects. If the object
`is selected then color it red, else color it white.
`Easy.
if test1selected=1
 color object test1,rgb(255,0,0)
else
 color object test1,rgb(255,255,255)
endif
if test2selected=1
 color object test2,rgb(255,0,0)
else
 color object test2,rgb(255,255,255)
endif

`End of the loop.
sync
loop

`*****FUNCTIONS*****
function makecone(value#)
ctr=0
true=1
while ctr <= 65535 and true <> 0
 inc ctr
 if object exist(ctr)=0
  true=0
  make object cone ctr,value#
 endif
endwhile
endfunction ctr

function makecylinder(value#)
ctr=0
true=1
while ctr <= 65535 and true <> 0
 inc ctr
 if object exist(ctr)=0
  true=0
  make object cylinder ctr,value#
 endif
endwhile
endfunction ctr

function makeplain(width#,height#)
ctr=0
true=1
while ctr <= 65535 and true <> 0
 inc ctr
 if object exist(ctr)=0
  true=0
  make object plain ctr,width#,height#
 endif
endwhile
endfunction ctr

`I found the equations for this at www.ilogicgames.com
`I don't know how it works, and neither does the source,
`but that doesn't matter. Basically, we take 3 points on
`the screen to define a triangle on the screen. That's an
`x and y value for each vertex of the triangle, thus the
`first 6 variables we pass to the function. The last 2 are
`the mouse positions. If the mouse coords are within the
`2D triangle defined by our 3 vertices, it returns a 1.
`This will be used later in the clickobject() function.
`2D vertices are obtained by using the object screen x(),
`y() and z() functions so that 3D polygons can also be
`checked.
function plaincontain(xa#,ya#,xb#,yb#,xc#,yc#,x,y)

x#=x
y#=y

x1#=xb#-xa#
x2#=xc#-xa#
x3#=x#-xa#

y1#=yb#-ya#
y2#=yc#-ya#
y3#=y#-ya#

a# = ( x1# * y2# ) - ( x2# * y1# )
b# = ( x1# * y3# ) - ( x3# * y1# )
c# = ( x2# * y3# ) - ( x3# * y2# )
if ( a# * b# ) > 0 and ( a# * c# ) < 0 and ( a# * ( a# + c# - b# ) ) > 0
 collide=1
else
 collide=0
endif
endfunction collide

`The workhorse that takes up so much memory and cpu.
`Since we cannot obtain vertex data, I had to use
`memblocks. Make sure that you do not use mesh 1
`and memblock 1 because those are used by this function.
`This loads up the object specified by objnum as a mesh
`so it can be converted to a memblock. It then checks
`the faces and verts there. After it's all done, it
`deletes the mesh and the memblock. Not very efficient,
`but remember that this is DBC, hehe.
`
`To make it a bit quicker, it stops checking as soon
`as any polygon was detected to be under the mouse,
`that way it doesn't waste valuable cpu time checking
`faces when it already knows that the object is already
`under the mouse.
function clickobject(objnum)

selected=0

make mesh from object 1,objnum
make memblock from mesh 1,1

vertoffset=memblock dword(1,4)
numface=memblock dword(1,16)
faceoffset=memblock dword(1,20)

for face=0 to numface-1
vertindexa=memblock dword(1,faceoffset+(face*28)+4)
vertindexb=memblock dword(1,faceoffset+(face*28)+12)
vertindexc=memblock dword(1,faceoffset+(face*28)+20)

ax#=memblock float(1,vertoffset+(vertindexa*12))
ay#=memblock float(1,vertoffset+(vertindexa*12)+4)
az#=memblock float(1,vertoffset+(vertindexa*12)+8)

bx#=memblock float(1,vertoffset+(vertindexb*12))
by#=memblock float(1,vertoffset+(vertindexb*12)+4)
bz#=memblock float(1,vertoffset+(vertindexb*12)+8)

cx#=memblock float(1,vertoffset+(vertindexc*12))
cy#=memblock float(1,vertoffset+(vertindexc*12)+4)
cz#=memblock float(1,vertoffset+(vertindexc*12)+8)

position object pointerobj(0),ax#,ay#,az#
position object pointerobj(1),bx#,by#,bz#
position object pointerobj(2),cx#,cy#,cz#

asx#=object screen x(pointerobj(0))
asy#=object screen y(pointerobj(0))
bsx#=object screen x(pointerobj(1))
bsy#=object screen y(pointerobj(1))
csx#=object screen x(pointerobj(2))
csy#=object screen y(pointerobj(2))

if plaincontain(asx#,asy#,bsx#,bsy#,csx#,csy#,mousex(),mousey())
selected=1
exit
endif

next face

delete mesh 1
delete memblock 1

endfunction selected