// read, but don't write particles to z-buffer _device->SetRenderState(D3DRS_ZWRITEENABLE, false);
}
void Firework::postRender()
{
PSystem::postRender();
|
_device->SetRenderState(D3DRS_ZWRITEENABLE, true); |
} |
|
|
Y |
|
|
|
Notice that both of the methods call the parent version. In this way, we |
|
|
L |
can still reuse some of the functionality of the parent while making |
|
|
F |
minimal specific changes to the irework system. |
|
|
M |
|
14.3.3 Sample Application: Particle Gun |
|
A |
|
|
E |
|
|
T |
|
|
Figure 14.4: A screen shot of the Laser (Particle Gun) sample
The Particle Gun system’s class is defined as:
class ParticleGun : public PSystem
{
public:
ParticleGun(Camera* camera);
void resetParticle(Attribute* attribute);
void update(float timeDelta);
private:
Camera* _camera;
};
The constructor takes a pointer to the camera. This is because the system needs to know the position and direction of the camera whenever it creates a new particle.
256Chapter 14
14.4Summary
Point sprites are a convenient and flexible way to display particles. They can change size and be textured. Furthermore, a single vertex can describe them.
A particle system maintains a collection of particles and is responsible for creating, destroying, updating, and displaying particles.
Some other particle system ideas that you can implement are: smoke, rocket trail, fountain/sprinkler, fire, lightning, explosion, and rain.
Chapter 15
Picking
Figure 15.1: The user picking the teapot
Suppose that the user clicked the screen point s = (x, y). From Figure 15.1 we can see that the user has picked the teapot. However, the application cannot immediately determine that the teapot was picked given just s. Therefore, we must come up with a technique to calculate this. We call this technique picking.
One thing that we know about the teapot and its relationship with s is that the teapot was projected to the area surrounding s. More correctly, it was projected to the area surrounding the point p on the projection window that corresponds to the screen point s. Since this problem relies on the relationship between a 3D object and its projection, we gain some insights by examining Figure 15.2.
257
Figure 15.2: A ray shooting through p will
intersect the object whose projection surrounds p. Note that the point p on the projection window corresponds to the clicked screen point s.
In Figure 15.2 we see that if we shoot a picking ray, originating at the origin, through p, we will intersect the object whose projection surrounds p, namely the teapot. Therefore, once we compute the picking ray, we can iterate through each object in the scene and test if the ray intersects it. The object that the ray intersects is the object that was picked by the user, which again is the teapot in this example.
The above example is specific to s and the teapot. Generally, we have an arbitrary clicked screen point. We then compute the picking ray and iterate through each object in the scene, testing if the ray intersects it. The object that the ray intersects is the object that was picked by the user. However, it is possible that the ray would not intersect any objects. For instance, in Figure 15.1, if the user doesn’t pick one of the five objects but clicks the white background, the picking ray would not intersect any of the objects. Thus, we conclude that if the ray doesn’t intersect any of the objects in the scene, then we, the user, didn’t pick an object, but rather the background of the screen or something we are not interested in.
Picking is applicable to all sorts of games and 3D applications. For example, players often interact with various objects in the world by clicking on them with the mouse. The player may click on an enemy to fire a projectile at the enemy or click on an item to pick up. In order for the game to respond appropriately, it needs to know the object that was picked (was it an enemy or item?) and its location in 3D space (where should the projectile be fired or where should the player move to pick up the item?). Picking allows us to answer these questions.
Assuming the X and Y members of the viewport are 0, which is usually the case, we can simply go a step further and get:
|
px |
|
2 sx |
|
1 |
|
Width |
|
|
|
|
|
|
py |
|
2 sy |
|
1 |
|
|
|
|
|
|
Height |
|
pz |
1 |
|
|
By definition, the projection window coincides with the z = 1 plane; therefore pz = 1.
However, we are not done. The projection matrix scales the points on the projection window to simulate different fields of views. To reclaim the point values before this scaling, we must transform the points by the inverse of the scaling operations. Let P be the projection matrix, and since entries P00 and P11 of a transformation matrix scale the x and y coordinates of a point, we get:
|
|
2x |
|
|
1 |
|
px |
|
|
1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
viewportWidth |
|
P00 |
|
|
|
2 y |
|
|
1 |
|
|
py |
|
|
|
1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
viewPortHeight |
P11 |
pz |
1 |
|
|
|
|
|
|
|
15.2 Computing the Picking Ray
Recall that a ray can be represented by the parametric equation p(t) = p0 + tu, where p0 is the origin of the ray describing its position and u is a vector describing its direction.
From Figure 15.2 we can see that the origin of the ray is also the origin of the view space, so p0 = (0, 0, 0). If p is the point on the projection window to shoot the ray through, the direction vector u is given
by: u = p – p0 = (px, py, 1) – (0, 0, 0) = p.
The following method computes the picking ray in view space given the x and y coordinates of the clicked point from screen space:
d3d::Ray CalcPickingRay(int x, int y)
{
float px = 0.0f; float py = 0.0f;
D3DVIEWPORT9 vp;
Device->GetViewport(&vp);
Picking 261
D3DXMATRIX proj;
Device->GetTransform(D3DTS_PROJECTION, &proj);
px = ((( 2.0f*x) / vp.Width) - 1.0f) / proj(0, 0); py = (((-2.0f*y) / vp.Height) + 1.0f) / proj(1, 1);
d3d::Ray ray;
ray._origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f); ray._direction = D3DXVECTOR3(px, py, 1.0f);
return ray;
}
where Ray is defined as:
struct Ray
{
D3DXVECTOR3 _origin; D3DXVECTOR3 _direction;
};
We update the d3dUtility.h file and d3d namespace by adding Ray to it.
15.3 Transforming Rays
The picking ray we computed in the previous section is described in view space. In order to perform a ray-object intersection test, the ray and the objects must be in the same coordinate system. Rather than transform all the objects into view space, it is often easier to transform the picking ray into world space or even an object’s local space.
We can transform a ray r(t) = p0 + tu by transforming its origin p0 and direction u by a transformation matrix. Note that the origin is transformed as a point and the direction is treated as a vector. The picking sample for this chapter implements the following function to transform a ray:
void TransformRay(d3d::Ray* ray, D3DXMATRIX* T)
{
//transform the ray's origin, w = 1. D3DXVec3TransformCoord(
&ray->_origin, &ray->_origin, T);
//transform the ray's direction, w = 0. D3DXVec3TransformNormal(
&ray->_direction, &ray->_direction, T);
//normalize the direction
D3DXVec3Normalize(&ray->_direction, &ray->_direction);
}
The D3DXVec3TransformCoord and D3DXVec3TransformNormal functions take 3D vectors as parameters, but observe that with the D3DXVec3TransformCoord function there is an understood w = 1 for the fourth component. Conversely, with the D3DXVec3TransformNormal function there is an understood w = 0 for the fourth component. Thus, we can use D3DXVec3TransformCoord to transform points, and we can use D3DXVec3TransformNormal to transform vectors.
15.4 Ray-Object Intersections
After we have the picking ray and the objects in the same coordinate system, we are ready to test which object the ray will hit. Since we represent objects as triangle meshes, one approach would be the following. For each object in the scene, iterate through its triangle list and test if the ray intersects one of the triangles. If it does, it must have hit the object that the triangle belongs to.
However, performing a ray intersection test for every triangle in the scene adds up in computation time. A faster method, albeit less accurate, is to approximate each object with a bounding sphere. Then we can perform a ray intersection test with each bounding sphere, and the sphere that gets intersected specifies the object that got picked.
Note: The picking ray may intersect multiple objects. However, the object closest to the camera is the object that was picked, since the closer object would have obscured the object behind it.
Given the center point c and the radius r of a sphere, we can test if a point p is on the sphere using the following implicit equation:
p c r 0
where p is a point on the sphere if the equation is satisfied. See Figure 15.3.
Figure 15.3: The length of the vector formed by p – c, denoted by p – c , is equal to the radius of the sphere if p lies on the sphere.
Note that we use a circle in the figure for simplicity, but the idea extends to three dimensions.
Picking 263
To determine if and where a ray p(t) = p0 + tu intersects a sphere, we plug the ray into an implicit sphere equation and solve for the parameter t that satisfies the sphere equation, giving us the parameter that yields the intersection point(s).
Plugging the ray into the sphere equation: p t c r 0
p0 tu c r 0
...from which we obtain the quadratic equation:
At2 Bt C 0
where A = u · u, B = 2(u · (p0 – c)), and C = (p0 – c) · (p0 – c) – r2. If u is normalized, then A = 1.
Assuming u is normalized, we solve for t0 and t1:
t0 |
B B2 4C |
t1 |
B B2 4C |
|
2 |
|
2 |
Figure 15.4 shows the possible results for t0 and t1 and shows what these results mean geometrically.
P a r t I I I
Figure 15.4: a) The ray misses the sphere; both t0 and t1 will result in imaginary solutions. b) The ray is in front of the sphere; both t0 and t1 will be negative. c) The ray is inside the sphere; one of the solutions will be positive and one will be negative. The positive solution yields the single intersection point. d) The ray intersects the sphere; both t0 and t1 are positive. e) The ray is tangent to the sphere, in which case the solutions are positive and t0 = t1.
The following method returns true if the ray passed in intersects the sphere passed in. It returns false if the ray misses the sphere:
bool PickApp::raySphereIntersectionTest(Ray* ray, BoundingSphere* sphere)
{
D3DXVECTOR3 v = ray->_origin - sphere->_center;