以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 29-lesson 30 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54186) |
-- 作者:一分之千 -- 发布时间:10/22/2007 8:54:00 PM -- Lesson 29 This tutorial was originally written by Andreas L鰂fler. He also wrote all of the original HTML for the tutorial. A few days later Rob Fletcher emailed me an Irix version of lesson 29. In his version he rewrote most of the code. So I ported Rob's Irix / GLUT code to Visual C++ / Win32. I then modified the message loop code, and the fullscreen code. When the program is minimized it should use 0% of the CPU (or close to). When switching to and from fullscreen mode, most of the problems should be gone (screen not restoring properly, messed up display, etc). Andreas tutorial is now better than ever. Unfortunately, the code has been modifed quite a bit, so all of the HTML has been rewritten by myself. Huge Thanks to Andreas for getting the ball rolling, and working his butt off to make a killer tutorial. Thanks to Rob for the modifications! Lets begin... We create a device mode structure called DMsaved. We will use this structure to store information about the users default desktop resolution, color depth, etc., before we switch to fullscreen mode. More on this later! Notice we only allocate enough storage space for one texture (texture[1]). #include <windows.h> // Header File For Windows HDC hDC=NULL; // Private GDI Device Context bool keys[256]; // Array Used For The Keyboard Routine DEVMODE DMsaved; // Saves The Previous Screen Settings (NEW) GLfloat xrot; // X Rotation GLuint texture[1]; // Storage For 1 Texture typedef struct Texture_Image typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE; // A Pointer To The Texture Image Data Type P_TEXTURE_IMAGE t1; // Pointer To The Texture Image Data Type LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc // Allocate An Image Structure And Inside Allocate Its Memory Requirements After allocating the memory, and checking to make sure ti is not equal to NULL, we can fill the structure with the image attributes. First we set the width (w), then the height (h) and lastly the format (f). Keep in mind format is bytes per pixel. ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE)); // One Image Struct Please if( ti != NULL ) { c = (unsigned char *)malloc( w * h * f); If there was a problem, we pop up an error message on the screen letting the user know that the program was unable to allocate memory for the texture buffer. NULL is returned. if ( c != NULL ) { If there were no problems, we return ti which is a pointer to our newly allocated image structure. Whew... Hope that all made sense. else // Free Up The Image Data free(t); // Free Itself We set up a pointer (p), and then attempt to open the file. // Read A .RAW File In To The Allocated Image Buffer Using data In The Image Structure Header. f = fopen(filename, "rb"); // Open "filename" For Reading Bytes The j loop moves from left (0) to right (width of line in pixels, not bytes). for( i = buffer->height-1; i >= 0 ; i-- ) // Loop Through Height (Bottoms Up - Flip Image) Notice in the loop we also increase the pointer (p) and a variable called done. More about done later. the line inside the loop reads a character from our file and stores it in the texture buffer at our current pointer location. If our image has 4 bytes per pixel, the first 3 bytes will be read from the .RAW file (format-1), and the 4th byte will be manually set to 255. After we set the 4th byte to 255 we increase the pointer location by one so that our 4th byte is not overwritten with the next byte in the file. After a all of the bytes have been read in per pixel, and all of the pixels have been read in per row, and all of the rows have been read in, we are done! We can close the file. for ( k = 0 ; k < buffer->format-1 ; k++, p++, done++ ) The last thing we do is return done. If the file couldn't be opened, done will equal 0. If everything went ok, done should equal the number of bytes read from the file. Remember, we were increasing done every time we read a byte in the loop above (k loop). else // Otherwise void BuildTexture (P_TEXTURE_IMAGE tex) src is the TEXTURE_IMAGE structure to use as the source image. dst is the TEXTURE_IMAGE structure to use for the destination image. src_xstart is where you want to start copying from on the x axis of the source image. src_ystart is where you want to start copying from on the y axis of the source image. src_width is the width in pixels of the area you want to copy from the source image. src_height is the height in pixels of the area you want to copy from the source image. dst_xstart and dst_ystart is where you want to place the copied pixels from the source image onto the destination image. If blend is 1, the two images will be blended. alpha sets how tranparent the copied image will be when it mapped onto the destination image. 0 is completely clear, and 255 is solid. We set up all our misc loop variables, along with pointers for our source image (s) and destination image (d). We check to see if the alpha value is within range. If not, we clamp it. We do the same for the blend value. If it's not 0-off or 1-on, we clamp it. void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height, // Clamp Alpha If Value Is Out Of Range // Check For Incorrect Blend Flag Values We do pretty much the same thing for the source pointer. The source pointer is the location of the source data plus the starting location on the source images y axis (src_ystart) * the source images width in pixels * the source images bytes per pixel (format). This should give us the starting row for our source image. i loops from 0 to src_height which is the number of pixels to copy up and down from the source image. d = dst->data + (dst_ystart * dst->width * dst->format); // Start Row - dst (Row * Width In Pixels * Bytes Per Pixel) for (i = 0 ; i < src_height ; i++ ) // Height Loop We do the exact same thing for the destination pointer. We increase the location of the destination pointer (d) by dst_xstart which is the starting location on the x axis of the destination image multiplied by the destination images bytes per pixel (format). This moves the destination (d) pointer to the starting pixel location on the x axis (from left to right) on the destination image. After we have calculated where in memory we want to grab our pixels from (s) and where we want to move them to (d), we start the j loop. We'll use the j loop to travel from left to right through the source image. s = s + (src_xstart * src->format); // Move Through Src Data By Bytes Per Pixel Inside the loop we check to see if blending is on or off. If blend is 1, meaning we should blend, we do some fancy math to calculate the color of our blended pixels. The destination value (d) will equal our source value (s) multiplied by our alpha value + our current destination value (d) times 255 minus the alpha value. The shift operator (>>8) keeps the value in a 0-255 range. If blending is disabled (0), we copy the data from the source image directly into the destination image. No blending is done and the alpha value is ignored. for( k = 0 ; k < src->format ; k++, d++, s++) // "n" Bytes At A Time After allocating memory for our image, we attempt to load the image. We pass ReadTextureData() the name of the file we wish to open, along with a pointer to our Image Structure (t1). If we were unable to load the .RAW image, a message box will pop up on the screen to let the user know there was a problem loading the texture. We then do the same thing for t2. We allocate memory, and attempt to read in our second .RAW image. If anything goes wrong we pop up a message box. int InitGL(GLvoid) // This Will Be Called Right After The GL Window Is Created t2 = AllocateTextureBuffer( 256, 256, 4 ); // Second Image Structure We start off by passing Blit() t2 and t1, both point to our TEXTURE_IMAGE structures (t2 is the second image, t1 is the first image. Then we have to tell blit where to start grabbing data from on the source image. If you load the source image into Adobe Photoshop or any other program capable of loading .RAW images you will see that the entire image is blank except for the top right corner. The top right has a picture of the ball with GL written on it. The bottom left corner of the image is 0,0. The top right of the image is the width of the image-1 (255), the height of the image-1 (255). Knowing that we only want to copy 1/4 of the src image (top right), we tell Blit() to start grabbing from 127,127 (center of our source image). Next we tell blit how many pixels we want to copy from our source point to the right, and from our source point up. We want to grab a 1/4 chunk of our image. Our image is 256x256 pixels, 1/4 of that is 128x128 pixels. All of the source information is done. Blit() now knows that it should copy from 127 on the x axis to 127+128 (255) on the x axis, and from 127 on the y axis to 127+128 (255) on the y axis. So Blit() knows what to copy, and where to get the data from, but it doesn't know where to put the data once it's gotten it. We want to draw the ball with GL written on it in the middle our the monitor image. You find the center of the destination image (256x256) which is 128x128 and subtract half the width and height of the source image (128x128) which is 64x64. So (128-64) x (128-64) gives us a starting location of 64,64. Last thing to do is tell our blitter routine we want to blend the two image (A one means blend, a zero means do not blend), and how much to blend the images. If the last value is 0, we blend the images 0%, meaning anything we copy will replace what was already there. If we use a value of 127, the two images blend together at 50%, and if you use 255, the image you are copying will be completely transparent and will not show up at all. The pixels are copied from image2 (t2) to image1 (t1). The mixed image will be stored in t1. // Image To Blend In, Original Image, Src Start X & Y, Src Width & Height, Dst Location X & Y, Blend Flag, Alpha Value After the texture has been created, we can deallocate the memory holding our two TEXTURE_IMAGE structures. The rest of the code is pretty standard. We enable texture mapping, depth testing, etc. BuildTexture (t1); // Load The Texture Map Into Texture Memory DeallocateTexture( t1 ); // Clean Up Image Memory Because Texture Is glEnable(GL_TEXTURE_2D); // Enable Texture Mapping glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading return TRUE; GLvoid DrawGLScene(GLvoid) glRotatef(xrot,1.0f,0.0f,0.0f); glBindTexture(GL_TEXTURE_2D, texture[0]); glBegin(GL_QUADS); xrot+=0.3f; GLvoid KillGLWindow(GLvoid) // Properly Kill The Window ShowCursor(TRUE); // Show Mouse Pointer if (hRC) // Do We Have A Rendering Context? if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC? if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window? if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) fullscreen=fullscreenflag; // Set The Global Fullscreen Flag hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved); // Save The Current Display State (NEW) if (fullscreen) // Attempt Fullscreen Mode? // Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar. int WINAPI WinMain( HINSTANCE hInstance, // Instance // Ask The User Which Screen Mode They Prefer // Create Our OpenGL Window while(!done) // Loop That Runs While done=FALSE if (!active) // Program Inactive? if (keys[VK_ESCAPE]) // Was Escape Pressed? if (keys[VK_F1]) // Is F1 Being Pressed? DrawGLScene(); // Draw The Scene // Shutdown Some information about Andreas: I磎 an 18 years old pupil who is currently studying to be a software engineer. I磛e been programming for nearly 10 years now. I've been programming in OpenGL for about 1.5 years. Andreas L鰂fler & Rob Fletcher Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/22/2007 9:03:00 PM -- 第三十课 这是一课激动的教程,你也许等待它多时了。你将学会碰撞剪裁,物理模拟太多的东西,慢慢期待吧。 碰撞检测 这是一个我遇到的最困难的题目,因为它没有一个简单的解决办法.对于每一个程序都有一种检测碰撞的方法.当然这里有一种蛮力,它适用于各种不同的应用,当它非常的费时. 1) 碰撞检测
1) 碰撞检测 我们使用射线来完成相关的算法,它的定义为: 射线上的点 = 射线的原点+ t * 射线的方向 t 用来描述它距离原点的位置,它的范围是[0, 无限远). 现在我们可以使用射线来计算它和平面以及圆柱的交点了。 射线和平面的碰撞检测: 平面被描述为: Xn dot X = d Xn 是平面的法线. 现在我们得到射线和平面的两个方程: PointOnRay = Raystart + t * Raydirection 如果他们相交,则上诉方程组有解,如下所示: Xn dot PointOnRay = d (Xn dot Raystart) + t * (Xn dot Raydirection) = d 解得 t: t = (d - Xn dot Raystart) / (Xn dot Raydirection) t代表原点到与平面相交点的参数,把t带回原方程我们会得到与平面的碰撞点.如果Xn*Raydirection=0。则说明它与平面平行,则将不产生碰撞。如果t为负值,则说明交点在射线的相反方向,也不会产生碰撞。 //判断是否和平面相交,是则返回1,否则返回0int TestIntersionPlane(const Plane& plane,const TVector& position,const TVector& direction, double& lamda, TVector& pNormal){ //判断是否平行于平面 l2=(plane._Normal.dot(plane._Position-position))/DotProduct; if (l2<-ZERO) pNormal=plane._Normal; 计算射线和圆柱方程组得解。 int TestIntersionCylinder(const Cylinder& cylinder,const TVector& position,const TVector& direction, double& lamda, TVector& pNormal,TVector& newposition) 球被表示为中心和它的半径,决定两个球是否相交就是求出它们之间的距离是否小于它们的直径。 在处理两个移动的球是否相交时,有一个bug就是,当它们的移动速度太快,回出现它们相交,但在相邻的两步检测不出它们是否相交的情况,如下图所示:
图 1 //判断球和球是否相交,是则返回1,否则返回0int FindBallCol(TVector& point, double& TimePoint, double Time2, int& BallNr1, int& BallNr2){ TVector RelativeV; TRay rays; double MyTime=0.0, Add=Time2/150.0, Timedummy=10000, Timedummy2=-1; TVector posi; //判断球和球是否相交 for (int i=0;i<NrOfBalls-1;i++) { for (int j=i+1;j<NrOfBalls;j++) { RelativeV=ArrayVel[i]-ArrayVel[j]; rays=TRay(OldPos[i],TVector::unit(RelativeV)); MyTime=0.0; while (MyTime<Time2) } } if (Timedummy!=10000) { TimePoint=Timedummy; return 0; 现在我们已经可以决定射线和平面/圆柱的交点了,如下图所示:
图 2a 图 2b 当我们找到了碰撞位置后,下一步我们需要知道它是否发生在当前这一步中.如果距离碰撞点的位置小于这一步球体运动的间隔,则碰撞发生.我们使用如下的方程计算运动到碰撞时所需的时间: 2) 基于物理的模拟 为了计算对于一个静止物体的碰撞,我们需要知道以下信息:碰撞点,碰撞法线,碰撞时间. 它是基于以下物理规律的,碰撞的入射角等于反射角.如下图所示:
图 3 反射方向有以下公式计算 : R= 2*(-I dot N)*N + I rt2=ArrayVel[BallNr].mag(); // 返回速度向量的模 // 计算反射向量 由于它很复杂,我们用下图来说明这个原理.
图 4 在我们的例子里,所有球的质量都相等,解得方程为,在垂直轴上的速度不变,在X_Axis轴上互相交换速度。代码如下: TVector pb1,pb2,xaxis,U1x,U1y,U2x,U2y,V1x,V1y,V2x,V2y; 我们使用欧拉方程来模拟万有引力,如下所示: 在每次模拟中,我们用上面公式计算的速度取代旧的速度 3) 特殊效果 爆炸 最好的表示爆炸效果的就是使用两个互相垂直的平面,并使用alpha混合在窗口中显示它们。接着让alpha变为0,设定爆炸效果不可见。代码如下所示: // 渲染/混合爆炸效果 在Windows下我们简单的调用PlaySound()函数播放声音。 4) 代码的流程 如果你成功的读完了理论部分,在你开始运行程序并播放声音以前。我们将用伪代码向你介绍一些整个流程,以便你能成功的看懂代码。 While (Timestep!=0) If (碰撞发生) //模拟函数,计算碰撞检测和物理模拟void idle(){ double rt,rt2,rt4,lamda=10000; TVector norm,uveloc; TVector normal,point,time; double RestTime,BallTime; TVector Pos2; int BallNr=0,dummy=0,BallColNr1,BallColNr2; TVector Nc; RestTime=Time; //计算重力加速度 //如果在一步的模拟时间内(如果来不及计算,则跳过几步) //对于每个球,找到它们最近的碰撞点 //测试是否和墙面碰撞 //如果小于当前保存的碰撞时间,则更新它 if (TestIntersionPlane(pl2,OldPos[i],uveloc,rt,norm)) if (rt4<=lamda) } if (TestIntersionPlane(pl3,OldPos[i],uveloc,rt,norm)) if (rt4<=lamda) if (TestIntersionPlane(pl4,OldPos[i],uveloc,rt,norm)) if (rt4<=lamda) if (TestIntersionPlane(pl5,OldPos[i],uveloc,rt,norm)) if (rt4<=lamda) //测试是否与三个圆柱相碰 if (rt4<=lamda) } if (rt4<=lamda) } if (rt4<=lamda) } if ( (lamda==10000) || (lamda>BallTime) ) TVector pb1,pb2,xaxis,U1x,U1y,U2x,U2y,V1x,V1y,V2x,V2y; pb1=OldPos[BallColNr1]+ArrayVel[BallColNr1]*BallTime; a=xaxis.dot(ArrayVel[BallColNr1]); xaxis=(pb1-pb2).unit(); V1x=(U1x+U2x-(U1x-U2x))*0.5; for (j=0;j<NrOfBalls;j++) ArrayVel[BallColNr1]=V1x+V1y; //Update explosion array continue; //最后的测试,替换下次碰撞的时间,并更新爆炸效果的数组 for (j=0;j<NrOfBalls;j++) rt2=ArrayVel[BallNr].mag(); for(j=0;j<20;j++) } }
就像我开头所说的,碰撞检测这个题目是非常难得,你已经学会了很多新的知识,并能够用它创建出非常棒的演示.但在这个课题,你认友很多需要学习,既然你已经开始了,其它的原理和模型就非常容易了. |
-- 作者:一分之千 -- 发布时间:10/22/2007 9:03:00 PM -- Lesson 30 Collision Detection and Physically Based Modeling Tutorial by Dimitrios Christopoulos (christop@fhw.gr). The source code upon which this tutorial is based, is from an older contest entry of mine (at OGLchallenge.dhs.org). The theme was Collision Crazy and my entry (which by the way took the 1st place :)) was called Magic Room. It features collision detection, physically based modeling and effects. Collision Detection A difficult subject and to be honest as far as I have seen up until now, there has been no easy solution for it. For every application there is a different way of finding and testing for collisions. Of course there are brute force algorithms which are very general and would work with any kind of objects, but they are expensive. We are going to investigate algorithms which are very fast, easy to understand and to some extent quite flexible. Furthermore importance must be given on what to do once a collision is detected and how to move the objects, in accordance to the laws of physics. We have a lot stuff to cover. Lets review what we are going to learn: 1) Collision Detection A lot of handy code! The Vector, Ray and Matrix classes are very useful. I used them until now for personal projects of my own. 1) Collision Detection For the collision detection we are going to use algorithms which are mostly used in ray tracing. Lets first define a ray. A ray using vector representation is represented using a vector which denotes the start and a vector (usually normalized) which is the direction in which the ray travels. Essentially a ray starts from the start point and travels in the direction of the direction vector. So our ray equation is: PointOnRay = Raystart + t * Raydirection t is a float which takes values from [0, infinity). With 0 we get the start point and substituting other values we get the corresponding points along the ray. PointOnRay, Raystart, Raydirection, are 3D Vectors with values (x,y,z). Now we can use this ray representation and calculate the intersections with plane or cylinders. Ray - Plane Intersection Detection A plane is represented using its Vector representation as: Xn dot X = d Xn, X are vectors and d is a floating point value. Essentially a plane represents a half space. So all that we need to define a plane is a 3D point and a normal from that point which is perpendicular to that plane. These two vectors form a plane, ie. if we take for the 3D point the vector (0,0,0) and for the normal (0,1,0) we essentially define a plane across x,z axes. Therefore defining a point and a normal is enough to compute the Vector representation of a plane. Using the vector equation of the plane the normal is substituted as Xn and the 3D point from which the normal originates is substituted as X. The only value that is missing is d which can easily be computed using a dot product (from the vector equation). (Note: This Vector representation is equivalent to the widely known parametric form of the plane Ax + By + Cz + D=0 just take the three x,y,z values of the normal as A,B,C and set D=-d). The two equations we have so far are: PointOnRay = Raystart + t * Raydirection If a ray intersects the plane at some point then there must be some point on the ray which satisfies the plane equation as follows: Xn dot PointOnRay = d or (Xn dot Raystart) + t * (Xn dot Raydirection) = d solving for t: t = (d - Xn dot Raystart) / (Xn dot Raydirection) replacing d: t= (Xn dot PointOnRay - Xn dot Raystart) / (Xn dot Raydirection) summing it up: t= (Xn dot (PointOnRay - Raystart)) / (Xn dot Raydirection) t represents the distance from the start until the intersection point along the direction of the ray. Therefore substituting t into the ray equation we can get the collision point. There are a few special cases though. If Xn dot Raydirection = 0 then these two vectors are perpendicular (ray runs parallel to plane) and there will be no collision. If t is negative the collision takes place behind the starting point of the ray along the opposite direction and again there is no intersection. int TestIntersionPlane(const Plane& plane,const TVector& position,const TVector& direction, double& lamda, TVector& pNormal) // Determine If Ray Parallel To Plane l2=(plane._Normal.dot(plane._Position-position))/DotProduct; // Find Distance To Collision Point if (l2<-ZERO) // Test If Collision Behind Start pNormal=plane._Normal; Ray - Cylinder Intersection Computing the intersection between an infinite cylinder and a ray is much more complicated that is why I won't explain it here. There is way too much math involved too easily explain and my goal is primarily to give you tools how to do it without getting into alot of detail (this is not a geometry class). If anyone is interested in the theory behind the intersection code, please look at the Graphic Gems II Book (pp 35, intersection of a with a cylinder). A cylinder is represented as a ray, using a start and direction (here it represents the axis) vector and a radius (radius around the axis of the cylinder). The relevant function is: int TestIntersionCylinder(const Cylinder& cylinder,const TVector& position,const TVector& direction, double& lamda, TVector& pNormal,TVector& newposition) The parameters are the cylinder structure (look at the code explanation further down), the start, direction vectors of the ray. The values returned through the parameters are the distance, the normal at the intersection point and the intersection point itself. Sphere - Sphere Collision A sphere is represented using its center and its radius. Determining if two spheres collide is easy. By finding the distance between the two centers (dist method of the TVector class) we can determine if they intersect, if the distance is less than the sum of their two radius. The problem lies in determining if 2 MOVING spheres collide. Bellow is an example where 2 sphere move during a time step from one point to another. Their paths cross in-between but this is not enough to prove that an intersection occurred (they could pass at a different time) nor can the collision point be determined.
Figure 1 The smaller the time steps, the more slices we use the more accurate the method is. As an example lets say the time step is 1 and our slices are 3. We would check the two balls for collision at time 0 , 0.33, 0.66, 1. Easy !!!! The code which performs this is: /*****************************************************************************************/ int FindBallCol(TVector& point, double& TimePoint, double Time2, int& BallNr1, int& BallNr2) if ( (rays.dist(OldPos[j])) > 40) continue; // If Distance Between Centers Greater Than 2*radius if (Timedummy!=10000) So now that we can determine the intersection point between a ray and a plane/cylinder we have to use it somehow to determine the collision between a sphere and one of these primitives. What we can do so far is determine the exact collision point between a particle and a plane/cylinder. The start position of the ray is the position of the particle and the direction of the ray is its velocity (speed and direction). To make it usable for spheres is quite easy. Look at Figure 2a to see how this can be accomplished.
Figure 2a Figure 2b Having determined where the collision takes place we have to determine if the intersection takes place in our current time step. Timestep is the time we move our sphere from its current point according to its velocity. Because we are testing with infinite rays there is always the possibility that the collision point is after the new position of the sphere. To determine this we move the sphere, calculate its new position and find the distance between the start and end point. From our collision detection procedure we also get the distance from the start point to its collision point. If this distance is less than the distance between start and end point then there is a collision. To calculate the exact time we solve the following simple equation. Represent the distance between start - end point with Dst, the distance between start - collision point Dsc, and the time step as T. The time where the collision takes place (Tc) is: Tc= Dsc*T / Dst All this is performed of course if an intersection is determined. The returned time is a fraction of the whole time step, so if the time step was 1 sec, and we found an intersection exactly in the middle of the distance, the calculated collision time would be 0.5 sec. this is interpreted as "0.5 sec after the start there is an intersection". Now the intersection point can be calculated by just multiplying Tc with the current velocity and adding it to the start point. Collision point= Start + Velocity*Tc This is the collision point on the offset primitive, to find the collision point on the real primitive we add to that point the reverse of the normal at that point (which is also returned by the intersection routines) by the radius of the sphere. Note that the cylinder intersection routine returns the intersection point if there is one so it does not need to be calculated. 2) Physically Based Modeling Collision Response To determine how to respond after hitting Static Objects like Planes, Cylinders is as important as finding the collision point itself. Using the algorithms and functions described, the exact collision point, the normal at the collision point and the time within a time step in which the collision occurs can be found. To determine how to respond to a collision, laws of physics have to be applied. When an object collides with the surface its direction changes i.e.. it bounces off. The angle of the of the new direction (or reflection vector) with the normal at the collision point is the same as the original direction vector. Figure 3 shows a collision with a sphere.
Figure 3 The new vector R is calculated as follows: R= 2*(-I dot N)*N + I The restriction is that the I and N vectors have to be unit vectors. The velocity vector as used in our examples represents speed and direction. Therefore it can not be plugged into the equation in the place of I, without any transformation. The speed has to be extracted. The speed for such a velocity vector is extracted finding the magnitude of the vector. Once the magnitude is found, the vector can be transformed to a unit vector and plugged into the equation giving the reflection vector R. R shows us now the direction, of the reflected ray, but in order to be used as a velocity vector it must also incorporate the speed. Therefore it gets, multiplied with the magnitude of the original ray, thus resulting in the correct velocity vector. In the example this procedure is applied to compute the collision response if a ball hits a plane or a cylinder. But it works also for arbitrary surfaces, it does not matter what the shape of the surface is. As long as a collision point and a Normal can be found the collision response method is always the same. The code which does these operations is: rt2=ArrayVel[BallNr].mag(); // Find Magnitude Of Velocity // Compute Reflection Determining the collision response, if two balls hit each other is much more difficult. Complex equations of particle dynamics have to be solved and therefore I will just post the final solution without any proof. Just trust me on this one :) During the collision of 2 balls we have a situation as it is depicted in Figure 4.
Figure 4 U1y and U2y are the projected vectors of the velocity vectors U1,U2 onto the axis which is perpendicular to the X_Axis. To find these vectors a few simple dot products are needed. M1, M2 is the mass of the two spheres respectively. V1,V2 are the new velocities after the impact, and V1x, V1y, V2x, V2y are the projections of the velocity vectors onto the X_Axis. In More Detail: a) Find X_Axis X_Axis = (center2 - center1); b) Find Projections U1x= X_Axis * (X_Axis dot U1) c)Find New Velocities (U1x * M1)+(U2x*M2)-(U1x-U2x)*M2 In our application we set the M1=M2=1, so the equations get even simpler. d)Find The Final Velocities V1y=U1y The derivation of that equations has a lot of work, but once they are in a form like the above they can be used quite easily. The code which does the actual collision response is: TVector pb1,pb2,xaxis,U1x,U1y,U2x,U2y,V1x,V1y,V2x,V2y; To simulate realistic movement with collisions, determining the the collision point and computing the response is not enough. Movement based upon physical laws also has to be simulated. The most widely used method for doing this is using Euler equations. As indicated all the computations are going to be performed using time steps. This means that the whole simulation is advanced in certain time steps during which all the movement, collision and response tests are performed. As an example we can advanced a simulation 2 sec. on each frame. Based on Euler equations, the velocity and position at each new time step is computed as follows: Velocity_New = Velovity_Old + Acceleration*TimeStep Now the objects are moved and tested angainst collision using this new velocity. The Acceleration for each object is determined by accumulating the forces which are acted upon it and divide by its mass according to this equation: Force = mass * acceleration A lot of physics formulas :) But in our case the only force the objects get is the gravity, which can be represented right away as a vector indicating acceleration. In our case something negative in the Y direction like (0,-0.5,0). This means that at the beginning of each time step, we calculate the new velocity of each sphere and move them testing for collisions. If a collision occurs during a time step (say after 0.5 sec with a time step equal to 1 sec.) we advance the object to this position, compute the reflection (new velocity vector) and move the object for the remaining time (which is 0.5 in our example) testing again for collisions during this time. This procedure gets repeated until the time step is completed. When multiple moving objects are present, each moving object is tested with the static geometry for intersections and the nearest intersection is recorded. Then the intersection test is performed for collisions among moving objects, where each object is tested with everyone else. The returned intersection is compared with the intersection returned by the static objects and the closest one is taken. The whole simulation is updated to that point, (i.e. if the closest intersection would be after 0.5 sec. we would move all the objects for 0.5 seconds), the reflection vector is calculated for the colliding object and the loop is run again for the remaining time. 3) Special Effects Explosions Every time a collision takes place an explosion is triggered at the collision point. A nice way to model explosions is to alpha blend two polygons which are perpendicular to each other and have as the center the point of interest (here intersection point). The polygons are scaled and disappear over time. The disappearing is done by changing the alpha values of the vertices from 1 to 0, over time. Because a lot of alpha blended polygons can cause problems and overlap each other (as it is stated in the Red Book in the chapter about transparency and blending) because of the Z buffer, we borrow a technique used in particle rendering. To be correct we had to sort the polygons from back to front according to their eye point distance, but disabling the Depth buffer writes (not reads) also does the trick (this is also documented in the red book). Notice that we limit our number of explosions to maximum 20 per frame, if additional explosions occur and the buffer is full, the explosion is discarded. The source which updates and renders the explosions is: // Render / Blend Explosions For the sound the windows multimedia function PlaySound() is used. This is a quick and dirty way to play wav files quickly and without trouble. 4) Explaining the Code Congratulations... If you are still with me you have survived successfully the theory section ;) Before having fun playing around with the demo, some further explanations about the source code are necessary. The main flow and steps of the simulation are as follows (in pseudo code): While (Timestep!=0) // While Time Step Not Over if (TestIntersionPlane(pl2,OldPos[i],uveloc,rt,norm)) // ...The Same As Above Omitted For Space Reasons if (TestIntersionPlane(pl3,OldPos[i],uveloc,rt,norm)) // ...The Same As Above Omitted For Space Reasons if (TestIntersionPlane(pl4,OldPos[i],uveloc,rt,norm)) // ...The Same As Above Omitted For Space Reasons if (TestIntersionPlane(pl5,OldPos[i],uveloc,rt,norm)) // ...The Same As Above Omitted For Space Reasons // Now Test Intersection With The 3 Cylinders if (TestIntersionCylinder(cyl2,OldPos[i],uveloc,rt,norm,Nc)) if (TestIntersionCylinder(cyl3,OldPos[i],uveloc,rt,norm,Nc)) } // After All Balls Were Tested With Planes / Cylinders Test For Collision if ( (lamda==10000) || (lamda>BallTime) ) continue; // End Of Tests // Update Explosion Array And Insert Explosion Represent the direction and position of the camera. The camera is moved using the LookAt function. As you will probably notice, if not in hook mode (which I will explain later), the whole scene rotates around, the degree of rotation is handled with camera_rotation. TVector dir Perform Intersection tests with primitives int TestIntersionPlane(....); As I stated at the beginning of this tutorial, the subject of collision detection is a very difficult subject to cover in one tutorial. You will learn a lot in this tutorial, enough to create some pretty impressive demos of your own, but there is still alot more to learn on this subject. Now that you have the basics, all the other sources on Collision Detection and Physically Based Modeling out there should become easier to understand. With this said, I send you on your way and wish you happy collisions!!! Some information about Dimitrios Christopoulos: He is currently working as a Virtual Reality software engineer at the Foundation of the Hellenic World in Athens/Greece (www.fhw.gr). Although Born in Germany, he studied in Greece at the University of Patras for a B.Sc. in Computer Engineering and Informatics. He holds also a MSc degree (honours) from the University of Hull (UK) in Computer Graphics and Virtual Environments. He did his first steps in game programming using Basic on an Commodore 64, and switched to C/C++/Assembly on the PC platform after the start of his studium. During the last few years OpenGL has become his graphics API of choice. For more information visit his site at: http://members.xoom.com/D_Christop. Dimitrios Christopoulos Jeff Molofee (NeHe) |
-- 作者:和你一样 -- 发布时间:8/15/2008 12:18:00 AM -- 感激涕零~~ |
-- 作者:Jokcy -- 发布时间:4/1/2011 9:25:00 AM -- 学习一下 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
250.000ms |