Mouse picking [DBC][memblocks] by Arkheii5th 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 |