//--------------------------------------------------------------------------------------
// Test collisions between pairs of collision objects using XNACollision functions
//--------------------------------------------------------------------------------------
void Collide()
{
    // test collisions between objects and frustum
    g_SecondarySpheres[0].collision = g_PrimaryFrustum.Contains( g_SecondarySpheres[0].sphere );
    g_SecondaryOrientedBoxes[0].collision = g_PrimaryFrustum.Contains( g_SecondaryOrientedBoxes[0].obox );
    g_SecondaryAABoxes[0].collision = g_PrimaryFrustum.Contains( g_SecondaryAABoxes[0].aabox );
    g_SecondaryTriangles[0].collision = g_PrimaryFrustum.Contains( g_SecondaryTriangles[0].pointa,
                                                                   g_SecondaryTriangles[0].pointb,
                                                                   g_SecondaryTriangles[0].pointc );

    // test collisions between objects and aligned box
    g_SecondarySpheres[1].collision = g_PrimaryAABox.Contains( g_SecondarySpheres[1].sphere );
    g_SecondaryOrientedBoxes[1].collision = g_PrimaryAABox.Contains( g_SecondaryOrientedBoxes[1].obox );
    g_SecondaryAABoxes[1].collision = g_PrimaryAABox.Contains( g_SecondaryAABoxes[1].aabox );
    g_SecondaryTriangles[1].collision = g_PrimaryAABox.Contains( g_SecondaryTriangles[1].pointa,
                                                                 g_SecondaryTriangles[1].pointb,
                                                                 g_SecondaryTriangles[1].pointc );

    // test collisions between objects and oriented box
    g_SecondarySpheres[2].collision = g_PrimaryOrientedBox.Contains( g_SecondarySpheres[2].sphere );
    g_SecondaryOrientedBoxes[2].collision = g_PrimaryOrientedBox.Contains( g_SecondaryOrientedBoxes[2].obox );
    g_SecondaryAABoxes[2].collision = g_PrimaryOrientedBox.Contains( g_SecondaryAABoxes[2].aabox );
    g_SecondaryTriangles[2].collision = g_PrimaryOrientedBox.Contains( g_SecondaryTriangles[2].pointa,
                                                                       g_SecondaryTriangles[2].pointb,
                                                                       g_SecondaryTriangles[2].pointc );

    // test collisions between objects and ray
    float fDistance = -1.0f;

    float fDist;
    if ( g_SecondarySpheres[3].sphere.Intersects( g_PrimaryRay.origin, g_PrimaryRay.direction, fDist ) )
    {
        fDistance = fDist;
        g_SecondarySpheres[3].collision = INTERSECTS;
    }
    else
        g_SecondarySpheres[3].collision = DISJOINT;

    if ( g_SecondaryOrientedBoxes[3].obox.Intersects( g_PrimaryRay.origin, g_PrimaryRay.direction, fDist ) )
    {
        fDistance = fDist;
        g_SecondaryOrientedBoxes[3].collision = INTERSECTS;
    }
    else
        g_SecondaryOrientedBoxes[3].collision = DISJOINT;

    if ( g_SecondaryAABoxes[3].aabox.Intersects( g_PrimaryRay.origin, g_PrimaryRay.direction, fDist ) )
    {
        fDistance = fDist;
        g_SecondaryAABoxes[3].collision =  INTERSECTS;
    }
    else
        g_SecondaryAABoxes[3].collision =  DISJOINT;

    if ( TriangleTests::Intersects( g_PrimaryRay.origin, g_PrimaryRay.direction,
                                    g_SecondaryTriangles[3].pointa,
                                    g_SecondaryTriangles[3].pointb,
                                    g_SecondaryTriangles[3].pointc,
                                    fDist ) )
    {
        fDistance = fDist;
        g_SecondaryTriangles[3].collision = INTERSECTS;
    }
    else
        g_SecondaryTriangles[3].collision = DISJOINT;

    // If one of the ray intersection tests was successful, fDistance will be positive.
    // If so, compute the intersection location and store it in g_RayHitResultBox.
    if( fDistance > 0 )
    {
        // The primary ray's direction is assumed to be normalized.
        XMVECTOR HitLocation = XMVectorMultiplyAdd( g_PrimaryRay.direction, XMVectorReplicate( fDistance ),
                                                    g_PrimaryRay.origin );
        XMStoreFloat3( &g_RayHitResultBox.aabox.Center, HitLocation );
        g_RayHitResultBox.collision = INTERSECTS;
    }
    else
    {
        g_RayHitResultBox.collision = DISJOINT;
    }
}
Esempio n. 2
0
    DirectX::BoundingOrientedBox GetBoundsInOrientedSpace(_In_ bool findTightestBounds, _In_ function<bool(XMFLOAT3*)> vertGenerator)
    {
        // we find tight bounds by
        // 1. find the convex hull
        // 2. rotating calipers to find the ideal bounding box - http://en.wikipedia.org/wiki/Rotating_calipers

        // The idea behind rotating calipers is that we keep track of some extreme vertices, and slowly rotate our coordinate frame.
        // As we rotate, a vertex may no-longer be extreme in the new rotated coordinate frame, so we increment the index to the next vertex
        // in the convex hull that is now extreme.

        float zmin = FLT_MAX, zmax = -FLT_MAX;
        auto convexHull = FindConvexHull([&](XMFLOAT2 *planarVert, UINT32 *index) -> bool
        {
            *index = 0; // we dont' care about the index here - only useful when exposing the convex hull directly

            XMFLOAT3 vert;
            bool ret = vertGenerator(&vert);
            if (ret)
            {
                if (vert.z < zmin)
                {
                    zmin = vert.z;
                }

                if (vert.z > zmax)
                {
                    zmax = vert.z;
                }
            }
            *planarVert = { vert.x, vert.y };
            return ret;
        });

        // first we need to set up the calipers - extreme vertices that we will incrementally update as we rotate
        XMFLOAT2 maxv = convexHull[0].first;
        XMFLOAT2 minv = convexHull[0].first;
        struct RotatedBoundingBox
        {
            UINT32 maxx, maxy, minx, miny; // these represent the indices of the max/min x and y coordinates in a rotated coordated frame.
            float area;
            float minwidth;
            float angle;
        };

        // find the initial orientation's bounds:
        RotatedBoundingBox best = { 0, 0, 0, 0, FLT_MAX, FLT_MAX, XM_2PI };
        for (UINT32 i = 1; i < convexHull.size(); ++i)
        {
            const auto vertex = convexHull[i].first;
            if (vertex.x > maxv.x)
            {
                maxv.x = vertex.x;
                best.maxx = i;
            }
            else if (vertex.x < minv.x)
            {
                minv.x = vertex.x;
                best.minx = i;
            }

            if (vertex.y > maxv.y)
            {
                maxv.y = vertex.y;
                best.maxy = i;
            }
            else if (vertex.y < minv.y)
            {
                minv.y = vertex.y;
                best.miny = i;
            }
        }
        best.angle = 0;
        best.area = (maxv.x - minv.x) * (maxv.y - minv.y);
        best.minwidth = min(maxv.x - minv.x, maxv.y - minv.y);

        ASSERT(best.minx != best.maxx); // xmin and xmax indices should never be the same
        ASSERT(best.miny != best.maxy);

        ASSERT(best.minx <= best.maxy); // our vertices should be located around the convex hull xmin->ymax->xmax->ymin
        ASSERT(best.maxy <= best.maxx);
        ASSERT(best.maxx <= best.miny || best.minx == best.miny);
        ASSERT(best.miny <= best.minx + convexHull.size());

        ASSERT(best.minx <= convexHull.size()); // all of the indices should be in the convex hull
        ASSERT(best.maxx <= convexHull.size());
        ASSERT(best.miny <= convexHull.size());
        ASSERT(best.maxy <= convexHull.size());

        ASSERT(best.minx == 0); // we expect minx to be the first vertex in the convex hull

                                // Helper to calculate the rotation if we move from the given vertex to the next vertex in the convex hull
        auto getDeltaVectorForIndex = [&](UINT32 vert)
        {
            // return the delta between the given vertex and the subsequent vertex, so we can determine the angle our bounding box
            // would have to rotate to be parallel with this edge
            auto start = convexHull[vert % convexHull.size()].first;
            auto next = convexHull[(vert + 1) % convexHull.size()].first;

            ASSERT(start.x != next.x || start.y != next.y);

            return XMFLOAT2({ next.x - start.x, next.y - start.y });
        };

        // once we have the extreme vertices, we slowly rotate our coordinate system and adjust them
        // we rotate in such a way that only one extreme vertex changes at a time
        float angle = 0;
        RotatedBoundingBox current = best;

        BoundingOrientedBox bestBoxInPlaneSpace;
        bestBoxInPlaneSpace.Center = { (maxv.x + minv.x) / 2, (maxv.y + minv.y) / 2, (zmax + zmin) / 2 };
        bestBoxInPlaneSpace.Extents = { (maxv.x - minv.x) / 2, (maxv.y - minv.y) / 2, (zmax - zmin) / 2 };
        bestBoxInPlaneSpace.Orientation = { 0, 0, 0, 1 };

        if (findTightestBounds)
        {
            RotatedBoundingBox initial = best;

            // The tightest bounding box will share a side with the convex hull.  We start with a candidate bounding box oriented along
            // the x/y axes and iterate through all orientations where the box is aligned with an edge of the convex hull.  The maximum possible rotations
            // we need to consider is convexHull.size(), which would be a rotation of 90 degrees.
            // Each iteration through the loop, we pick the vertex from our extreme vertices that has the smallest incremental rotation along its outgoing edge.
            // A neat trick is that the other extreme vertices remain extreme in the new rotated orientation.
            while (angle <= XM_PIDIV2 + ROTATING_CALIPERS_EPSILON &&
                current.minx <= initial.maxy &&
                current.maxy <= initial.maxx &&
                current.maxx <= initial.miny &&
                current.miny <= convexHull.size())
            {
                const auto vectForXmin = getDeltaVectorForIndex(current.minx);
                const auto vectForXmax = getDeltaVectorForIndex(current.maxx);
                const auto vectForYmin = getDeltaVectorForIndex(current.miny);
                const auto vectForYmax = getDeltaVectorForIndex(current.maxy);

                UINT32* boundIndices[4] = { &current.minx, &current.maxx, &current.miny, &current.maxy };
                float angles[4] = {
                    atan2(vectForXmin.x, vectForXmin.y),
                    atan2(-vectForXmax.x, -vectForXmax.y),
                    atan2(vectForYmin.y, -vectForYmin.x),
                    atan2(-vectForYmax.y, vectForYmax.x) };

                int index = 0;
                float minAngle = XM_PI * 4 + angle;
                for (int i = 0; i < 4; ++i)
                {
                    if (angles[i] > -ROTATING_CALIPERS_EPSILON && angles[i] < 0)
                    {
                        // the vector between vertices are horizontal/vertical, so angle is close to 0, treat it as zero
                        // this can only occur with a rounding error
                        angles[i] = 0;
                    }
                    else if (angles[i] < 0)
                    {
                        angles[i] += XM_PI * 2;
                    }

                    if (angles[i] < minAngle)
                    {
                        minAngle = angles[i];
                        index = i;
                    }
                }

                *(boundIndices[index]) = ((*(boundIndices[index])) + 1);

                ASSERT(current.minx <= current.maxy); // we should remain ordering of vertices xmin->ymax->xmax->ymin as we rotate
                ASSERT(current.maxy <= current.maxx);
                ASSERT(current.maxx <= current.miny || best.minx == best.miny);
                ASSERT(current.miny <= current.minx + convexHull.size());

                ASSERT(current.minx != current.maxx); // and we shouldn't ever have min and max indices equal
                ASSERT(current.miny != current.maxy);


                // now update our box:
                angle = minAngle;
                current.angle = minAngle;
                if (angle < XM_PIDIV2 + ROTATING_CALIPERS_EPSILON)
                {
                    XMVECTOR vertsInRotatedPlaneSpace[4];
                    const XMMATRIX rotationTransform = XMMatrixRotationZ(angle);
                    for (int i = 0; i < 4; ++i)
                    {
                        vertsInRotatedPlaneSpace[i] = XMVector3TransformCoord(XMLoadFloat2(&convexHull[(*(boundIndices[i])) % convexHull.size()].first), rotationTransform);
                    }

                    BoundingOrientedBox xmBoundsInPlaneSpace;
                    xmBoundsInPlaneSpace.Center = { XMVectorGetX(vertsInRotatedPlaneSpace[0] + vertsInRotatedPlaneSpace[1]) / 2, XMVectorGetY(vertsInRotatedPlaneSpace[2] + vertsInRotatedPlaneSpace[3]) / 2, (zmax + zmin) / 2 };
                    xmBoundsInPlaneSpace.Extents = { XMVectorGetX(vertsInRotatedPlaneSpace[1] - vertsInRotatedPlaneSpace[0]) / 2, XMVectorGetY(vertsInRotatedPlaneSpace[3] - vertsInRotatedPlaneSpace[2]) / 2, (zmax - zmin) / 2 };
                    xmBoundsInPlaneSpace.Orientation = { 0, 0, 0, 1 };
                    xmBoundsInPlaneSpace.Transform(xmBoundsInPlaneSpace, XMMatrixTranspose(rotationTransform)); // rotate back to plane space from rotated plane space

                    const XMFLOAT2 size = { xmBoundsInPlaneSpace.Extents.x * 2, xmBoundsInPlaneSpace.Extents.y * 2 };
                    current.area = size.x * size.y;
                    current.minwidth = min(size.x, size.y);
                    if (current.area < best.area || (current.area == best.area && current.minwidth < best.minwidth))
                    {
                        best = current;
                        bestBoxInPlaneSpace = xmBoundsInPlaneSpace;
                    }
                }
            }
        }

        return bestBoxInPlaneSpace;
    }
Esempio n. 3
0
// recursively render meshes for all nodes
// lambda-free version
bool CompoundMesh::render( ID3D11DeviceContext *deviceContext, VanillaShaderClass *shader, DirectX::CXMMATRIX worldMatrix, DirectX::CXMMATRIX viewMatrix, DirectX::CXMMATRIX projectionMatrix, std::vector<Light> &lights, bool orthoProjection /*= false*/, double animationTick /*= 1.0*/, CompoundMeshNode *node /*= nullptr*/, DirectX::CXMMATRIX parentNodeTransform /*= XMMatrixIdentity()*/ )
{
    if (!node)
    {
        // top-level invocation; do some initialization and culling
        node = m_root.get();

        if (m_animation.loaded())
        {
            double ticksPerSec = m_aiScene->mAnimations[0]->mTicksPerSecond;

            if (ticksPerSec <= 0) ticksPerSec = 24;

            animationTick *= ticksPerSec;
            animationTick += 1; // time starts at 0 but ticks, alas, at 1?

            while (animationTick > m_animation.maxTick) animationTick -= m_animation.maxTick;

            updateNodeTransforms(animationTick);
        }

        // clip to view frustum

        BoundingSphere bSphere(m_bSphere);
        bSphere.Transform(bSphere, worldMatrix);

        //BoundingOrientedBox bBox;
        //m_bBox.Transform(bBox, worldMatrix);

        if (!orthoProjection)
        {
            BoundingFrustum frustum(projectionMatrix); // TODO this should be moved somewhere higher up if the engine ever becomes CPU-bound
            XMStoreFloat4(&frustum.Orientation, XMVectorSet(0, 0, 0, 1)); // identity quaternion
            frustum.Transform(frustum, XMMatrixInverse(nullptr, viewMatrix)); // move the frustum as though it was an object within the world; probably slow?

            if (!DirectX::Internal::XMQuaternionIsUnit(XMLoadFloat4(&frustum.Orientation)))
            {
                cerr << "Bad frustum quaternion! :( " << XMLoadFloat4(&frustum.Orientation) << endl;
                XMStoreFloat4(&frustum.Orientation, XMVectorSet(0, 0, 0, 1));
            }

            try 
            {
                if (!frustum.Intersects(bSphere)) return true;
            } catch (exception *e)
            {
                cerr << e->what() << endl;
            }
        } else
        {
            // make a bounding box from the orthographic projection matrix (is this weird? perhaps it's weird.)
            XMMATRIX unproj = XMMatrixInverse(nullptr, projectionMatrix);
            float x = unproj.r[0].m128_f32[0]; // half the width; for now, assuming projection centered at origin
            float y = unproj.r[1].m128_f32[1]; // half the height
            float near_plane = unproj.r[3].m128_f32[2];
            float far_plane = unproj.r[2].m128_f32[2] + near_plane;

            XMFLOAT3 points[2];
            points[0].x = -x;
            points[0].y = -y;
            points[0].z = near_plane;
            points[1].x = x;
            points[1].y = y;
            points[1].z = max(far_plane,10000); // /fp:fast workaround ಠ_ಠ
            //points[1].z = far_plane;

            // XXX XXX XXX Something about this code fails with /fp:fast XXX XXX XXX
            // one workaround is to set far_plane to 10000; we're probably not culling based on distance anyway so no harm done, at least?
            // another is, of course, to set /fp:precise ... this costs several FPS on the i7 laptop with the double-torus test scene! 

            BoundingOrientedBox viewBox;
            viewBox.CreateFromPoints(viewBox, 2, points, sizeof(XMFLOAT3));
            XMStoreFloat4(&viewBox.Orientation, XMVectorSet(0, 0, 0, 1)); // identity quaternion... strange that Orientation would be uninitialized though
            viewBox.Transform(viewBox, XMMatrixInverse(nullptr, viewMatrix));

            if (!DirectX::Internal::XMQuaternionIsUnit(XMLoadFloat4(&viewBox.Orientation)))
            {
                cerr << "Bad viewBox quaternion! :( " << XMLoadFloat4(&viewBox.Orientation) << endl;
                XMStoreFloat4(&viewBox.Orientation, XMVectorSet(0, 0, 0, 1));
            }


            // clip to viewbox
            try
            {
                if (!viewBox.Intersects(bSphere)) return true;
            } catch (exception *e)
            {
                cerr << e->what() << endl;
            }
        }

        // tell the vertex shader about the wonderful animation buffer we have for it:
        if (m_animation.loaded())
        {
            m_animation.updateCurrentBoneKeys(deviceContext, animationTick);
        }
    }



    for (auto mesh = node->meshes.begin(), end = node->meshes.end(); mesh != end; ++mesh)
    {

        if (mesh->m_indexBuffer == nullptr || mesh->m_vertexBuffer == nullptr || !mesh->getIndexCount())
        {
            cerr << "strange, empty mesh";
            continue;
        }

        mesh->setBuffers(deviceContext); // point the GPU at the right geometry data

        SimpleMesh::Material &mat = mesh->m_material;
        bool useNormalMap = mat.normalMap.getTexture() ? true : false;
		bool useSpecularMap = mat.specularMap.getTexture() ? true : false;
        if (!shader->SetPSMaterial(deviceContext, mat.ambient, mat.diffuse, mat.shininess, mat.specular, useNormalMap, useSpecularMap))
        {
            return false;
        }

        XMFLOAT4X4 *offsetMatrix = nullptr;

        if (m_animation.loaded())
        {
            m_animation.updateBoneTransforms(deviceContext, animationTick, mesh->m_OffsetMatrix, mesh->m_name, 
                [&](std::string nodeName) 
                    { 
                        return getNodeGlobalTransform(nodeName);
                    } 
            );
        }

        
        XMMATRIX finalWorldMatrix;
        if (!m_animation.loaded() && node->name.size() == 0)
        {
            finalWorldMatrix = worldMatrix; 
        } else
        {
            finalWorldMatrix = XMMatrixMultiply(getNodeGlobalTransform(node->name), worldMatrix);
        }

        if (!shader->Render(deviceContext, mesh->getIndexCount(), finalWorldMatrix, viewMatrix, projectionMatrix, mesh->m_material.normalMap.getTexture(), mesh->m_material.specularMap.getTexture(), &lights, mesh->m_material.diffuseTexture.getTexture(), 1, true, m_animation.loaded() ? animationTick : -1))
        {
            return false;
        }
    }

    for (auto i : node->children)
    {
        if (!render(deviceContext, shader, worldMatrix, viewMatrix, projectionMatrix, lights, orthoProjection, animationTick, i.get(), XMMatrixIdentity())) return false;
    }

    return true;
}