diff --git a/examples/testbed/framework/test.cpp b/examples/testbed/framework/test.cpp index d612064..a8304fe 100644 --- a/examples/testbed/framework/test.cpp +++ b/examples/testbed/framework/test.cpp @@ -35,16 +35,18 @@ void b3EndProfileScope() g_profiler->PopEvent(); } -Test::Test() : m_bodyDragger(&m_bodyRay, &m_world) +Test::Test() : + m_bodyDragger(&m_ray, &m_world), + m_clothDragger(&m_ray, &m_world) { b3Draw_draw = g_draw; b3_convexCache = g_testSettings->convexCache; m_world.SetContactListener(this); - m_bodyRay.origin.SetZero(); - m_bodyRay.direction.Set(0.0f, 0.0f, -1.0f); - m_bodyRay.fraction = g_camera->m_zFar; + m_ray.origin.SetZero(); + m_ray.direction.Set(0.0f, 0.0f, -1.0f); + m_ray.fraction = g_camera->m_zFar; m_groundHull.Set(50.0f, 1.0f, 50.0f); m_groundMesh.BuildTree(); @@ -117,31 +119,51 @@ void Test::Step() void Test::MouseMove(const b3Ray3& pw) { - m_bodyRay = pw; + m_ray = pw; - if (m_bodyDragger.IsSelected() == true) + if (m_bodyDragger.IsDragging() == true) { m_bodyDragger.Drag(); } + + if (m_clothDragger.IsDragging() == true) + { + m_clothDragger.Drag(); + } } void Test::MouseLeftDown(const b3Ray3& pw) { - if (m_bodyDragger.IsSelected() == false) + if (m_bodyDragger.IsDragging() == false) { if (m_bodyDragger.StartDragging() == true) { BeginDragging(); } } + + if (m_clothDragger.IsDragging() == false) + { + if (m_clothDragger.StartDragging() == true) + { + BeginDragging(); + } + } } void Test::MouseLeftUp(const b3Ray3& pw) { - if (m_bodyDragger.IsSelected() == true) + if (m_bodyDragger.IsDragging() == true) { m_bodyDragger.StopDragging(); EndDragging(); } + + if (m_clothDragger.IsDragging() == true) + { + m_clothDragger.StopDragging(); + + EndDragging(); + } } \ No newline at end of file diff --git a/examples/testbed/framework/test.h b/examples/testbed/framework/test.h index 7c3bed2..baeb29c 100644 --- a/examples/testbed/framework/test.h +++ b/examples/testbed/framework/test.h @@ -45,7 +45,13 @@ public: return 1.0f; } - b3RayCastSingleOutput hit; + float32 ReportCloth(b3Shape* shape, const b3Vec3& point, const b3Vec3& normal, float32 fraction) + { + B3_ASSERT(false); + return 1.0f; + } + + b3ShapeRayCastSingleOutput hit; }; class Test : public b3ContactListener @@ -73,7 +79,8 @@ public: b3World m_world; - b3Ray3 m_bodyRay; + b3Ray3 m_ray; + b3ClothDragger m_clothDragger; b3BodyDragger m_bodyDragger; b3BoxHull m_groundHull; diff --git a/examples/testbed/tests/cloth_test.h b/examples/testbed/tests/cloth_test.h index fb8f3a7..dbc1913 100644 --- a/examples/testbed/tests/cloth_test.h +++ b/examples/testbed/tests/cloth_test.h @@ -22,14 +22,9 @@ class ClothTest : public Test { public: - ClothTest() : m_clothDragger(&m_clothRay, m_cloth) + ClothTest() { m_world.SetGravity(b3Vec3(0.0f, -10.0f, 0.0f)); - - m_clothRay.origin.SetZero(); - m_clothRay.direction.Set(0.0f, 0.0f, -1.0f); - m_clothRay.fraction = g_camera->m_zFar; - m_cloth = nullptr; } @@ -47,41 +42,13 @@ public: float32 E = m_cloth->GetEnergy(); g_draw->DrawString(b3Color_white, "E = %f", E); - if (m_clothDragger.IsSelected() == true) + if (m_clothDragger.IsDragging() == true) { g_draw->DrawSegment(m_clothDragger.GetPointA(), m_clothDragger.GetPointB(), b3Color_white); } } - - void MouseMove(const b3Ray3& pw) - { - m_clothRay = pw; - - if (m_clothDragger.IsSelected() == true) - { - m_clothDragger.Drag(); - } - } - - void MouseLeftDown(const b3Ray3& pw) - { - if (m_clothDragger.IsSelected() == false) - { - m_clothDragger.StartDragging(); - } - } - - void MouseLeftUp(const b3Ray3& pw) - { - if (m_clothDragger.IsSelected() == true) - { - m_clothDragger.StopDragging(); - } - } - - b3Ray3 m_clothRay; + b3Cloth* m_cloth; - b3ClothDragger m_clothDragger; }; #endif \ No newline at end of file diff --git a/examples/testbed/tests/point_click.h b/examples/testbed/tests/point_click.h index 3c24366..0f1f3a1 100644 --- a/examples/testbed/tests/point_click.h +++ b/examples/testbed/tests/point_click.h @@ -69,7 +69,7 @@ public: void Step() { - if (m_bodyDragger.IsSelected()) + if (m_bodyDragger.IsDragging()) { if (m_bodyDragger.GetBody() != m_character) { diff --git a/examples/testbed/tests/ray_cast.h b/examples/testbed/tests/ray_cast.h index 610627f..5417ff2 100644 --- a/examples/testbed/tests/ray_cast.h +++ b/examples/testbed/tests/ray_cast.h @@ -178,8 +178,8 @@ public: void CastRay(const b3Vec3 p1, const b3Vec3 p2) const { - b3RayCastSingleOutput out; - if (m_world.RayCastSingle(&out, p1, p2)) + b3ShapeRayCastSingleOutput out; + if (m_world.RayCastSingleShape(&out, p1, p2)) { g_draw->DrawSegment(p1, out.point, b3Color_green); diff --git a/examples/testbed/tests/tension_mapping.h b/examples/testbed/tests/tension_mapping.h index 39f9f23..61530b0 100644 --- a/examples/testbed/tests/tension_mapping.h +++ b/examples/testbed/tests/tension_mapping.h @@ -156,7 +156,7 @@ public: g_draw->DrawSolidTriangle(n2, v1, v3, v2, color); } - if (m_clothDragger.IsSelected() == true) + if (m_clothDragger.IsDragging() == true) { g_draw->DrawSegment(m_clothDragger.GetPointA(), m_clothDragger.GetPointB(), b3Color_white); } diff --git a/include/bounce/controllers/body_dragger.h b/include/bounce/controllers/body_dragger.h index aa1e051..ca86fa1 100644 --- a/include/bounce/controllers/body_dragger.h +++ b/include/bounce/controllers/body_dragger.h @@ -39,12 +39,17 @@ public: } + bool IsDragging() const + { + return m_shape != nullptr; + } + bool StartDragging() { B3_ASSERT(m_mouseJoint == nullptr); - b3RayCastSingleOutput out; - if (m_world->RayCastSingle(&out, m_ray->A(), m_ray->B()) == false) + b3ShapeRayCastSingleOutput out; + if (m_world->RayCastSingleShape(&out, m_ray->A(), m_ray->B()) == false) { return false; } @@ -88,12 +93,6 @@ public: m_shape = nullptr; } - bool IsSelected() const - { - return m_mouseJoint != nullptr; - } - - b3Ray3* GetRay() const { return m_ray; diff --git a/include/bounce/controllers/cloth_dragger.h b/include/bounce/controllers/cloth_dragger.h index d9fcd28..e8d5ac8 100644 --- a/include/bounce/controllers/cloth_dragger.h +++ b/include/bounce/controllers/cloth_dragger.h @@ -21,18 +21,21 @@ #include #include +#include #include #include -#include +#include // A cloth triangle dragger. class b3ClothDragger { public: - b3ClothDragger(b3Ray3* ray, b3Cloth*& cloth) : m_ray(ray), m_cloth(cloth) + b3ClothDragger(b3Ray3* ray, b3World* world) { - m_isSelected = false; m_spring = false; + m_ray = ray; + m_world = world; + m_cloth = nullptr; } ~b3ClothDragger() @@ -40,57 +43,29 @@ public: } - bool IsSelected() const + bool IsDragging() const { - return m_isSelected; - } - - b3Vec3 GetPointA() const - { - B3_ASSERT(m_isSelected); - - b3ClothMesh* m = m_cloth->GetMesh(); - b3ClothMeshTriangle* t = m->triangles + m_selection; - - b3Vec3 A = m->vertices[t->v1]; - b3Vec3 B = m->vertices[t->v2]; - b3Vec3 C = m->vertices[t->v3]; - - return m_u * A + m_v * B + (1.0f - m_u - m_v) * C; - } - - b3Vec3 GetPointB() const - { - B3_ASSERT(m_isSelected); - return (1.0f - m_x) * m_ray->A() + m_x * m_ray->B(); + return m_cloth != nullptr; } bool StartDragging() { - B3_ASSERT(m_isSelected == false); + B3_ASSERT(IsDragging() == false); - b3RayCastInput rayIn; - rayIn.p1 = m_ray->A(); - rayIn.p2 = m_ray->B(); - rayIn.maxFraction = B3_MAX_FLOAT; - - b3ClothRayCastOutput rayOut; - if (m_cloth->RayCast(&rayOut, &rayIn) == false) + b3ClothRayCastSingleOutput rayOut; + if (m_world->RayCastSingleCloth(&rayOut, m_ray->A(), m_ray->B()) == false) { return false; } - m_isSelected = true; - - m_selection = rayOut.triangle; + m_cloth = rayOut.cloth; + m_mesh = m_cloth->GetMesh(); + m_triangle = m_mesh->triangles + rayOut.triangle; m_x = rayOut.fraction; - b3ClothMesh* m = m_cloth->GetMesh(); - b3ClothMeshTriangle* t = m->triangles + m_selection; - - b3Particle* p1 = m->particles[t->v1]; - b3Particle* p2 = m->particles[t->v2]; - b3Particle* p3 = m->particles[t->v3]; + b3Particle* p1 = m_mesh->particles[m_triangle->v1]; + b3Particle* p2 = m_mesh->particles[m_triangle->v2]; + b3Particle* p3 = m_mesh->particles[m_triangle->v3]; b3Vec3 v1 = p1->GetPosition(); b3Vec3 v2 = p2->GetPosition(); @@ -163,10 +138,7 @@ public: void Drag() { - B3_ASSERT(m_isSelected); - - b3ClothMesh* m = m_cloth->GetMesh(); - b3ClothMeshTriangle* t = m->triangles + m_selection; + B3_ASSERT(IsDragging() == true); b3Vec3 A = GetPointA(); b3Vec3 B = GetPointB(); @@ -179,22 +151,22 @@ public: } else { - b3Particle* p1 = m->particles[t->v1]; + b3Particle* p1 = m_mesh->particles[m_triangle->v1]; p1->ApplyTranslation(dx); - b3Particle* p2 = m->particles[t->v2]; + b3Particle* p2 = m_mesh->particles[m_triangle->v2]; p2->ApplyTranslation(dx); - b3Particle* p3 = m->particles[t->v3]; + b3Particle* p3 = m_mesh->particles[m_triangle->v3]; p3->ApplyTranslation(dx); } } void StopDragging() { - B3_ASSERT(m_isSelected); + B3_ASSERT(IsDragging() == true); - m_isSelected = false; + m_cloth = nullptr; if (m_spring) { @@ -205,28 +177,42 @@ public: } else { - b3ClothMesh* m = m_cloth->GetMesh(); - b3ClothMeshTriangle* t = m->triangles + m_selection; - - b3Particle* p1 = m->particles[t->v1]; + b3Particle* p1 = m_mesh->particles[m_triangle->v1]; p1->SetType(m_t1); - b3Particle* p2 = m->particles[t->v2]; + b3Particle* p2 = m_mesh->particles[m_triangle->v2]; p2->SetType(m_t2); - b3Particle* p3 = m->particles[t->v3]; + b3Particle* p3 = m_mesh->particles[m_triangle->v3]; p3->SetType(m_t3); } } -private: - bool m_isSelected; + b3Vec3 GetPointA() const + { + B3_ASSERT(IsDragging() == true); + + b3Vec3 A = m_mesh->vertices[m_triangle->v1]; + b3Vec3 B = m_mesh->vertices[m_triangle->v2]; + b3Vec3 C = m_mesh->vertices[m_triangle->v3]; + return m_u * A + m_v * B + (1.0f - m_u - m_v) * C; + } + + b3Vec3 GetPointB() const + { + B3_ASSERT(IsDragging() == true); + + return (1.0f - m_x) * m_ray->A() + m_x * m_ray->B(); + } +private: b3Ray3* m_ray; float32 m_x; - b3Cloth*& m_cloth; - u32 m_selection; + b3World* m_world; + b3Cloth* m_cloth; + b3ClothMesh* m_mesh; + b3ClothMeshTriangle* m_triangle; float32 m_u, m_v; bool m_spring; diff --git a/include/bounce/dynamics/cloth/cloth.h b/include/bounce/dynamics/cloth/cloth.h index 9897b6e..fd9a99d 100644 --- a/include/bounce/dynamics/cloth/cloth.h +++ b/include/bounce/dynamics/cloth/cloth.h @@ -34,15 +34,10 @@ struct b3ForceDef; struct b3ClothMesh; -struct b3RayCastInput; +class b3RayCastListener; -struct b3ClothRayCastOutput -{ - u32 triangle; // intersected triangle - b3Vec3 point; // intersection point on surface - b3Vec3 normal; // surface normal of intersection - float32 fraction; // time of intersection on segment -}; +struct b3RayCastInput; +struct b3ClothRayCastSingleOutput; // Cloth definition // This requires defining a cloth mesh which is typically bound to a render mesh @@ -94,11 +89,14 @@ public: // Destroy a given force. void DestroyForce(b3Force* force); - // Perform a ray cast with the cloth. - bool RayCast(b3ClothRayCastOutput* output, const b3RayCastInput* input) const; - // Perform a ray cast with a given cloth mesh triangle. - bool RayCast(b3ClothRayCastOutput* output, const b3RayCastInput* input, u32 triangleIndex) const; + bool RayCast(b3ClothRayCastSingleOutput* output, const b3RayCastInput* input, u32 triangleIndex) const; + + // Perform a ray cast with the cloth. + void RayCast(b3RayCastListener* listener, const b3RayCastInput* input) const; + + // Perform a ray cast with the cloth. + bool RayCastSingle(b3ClothRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const; // Return the cloth mesh proxy. b3ClothMesh* GetMesh() const; diff --git a/include/bounce/dynamics/world.h b/include/bounce/dynamics/world.h index 7228c00..569fc8f 100644 --- a/include/bounce/dynamics/world.h +++ b/include/bounce/dynamics/world.h @@ -36,7 +36,7 @@ class b3RayCastListener; class b3ContactListener; class b3ContactFilter; -struct b3RayCastSingleOutput +struct b3ShapeRayCastSingleOutput { b3Shape* shape; // shape b3Vec3 point; // intersection point on surface @@ -44,6 +44,15 @@ struct b3RayCastSingleOutput float32 fraction; // time of intersection on segment }; +struct b3ClothRayCastSingleOutput +{ + b3Cloth* cloth; // cloth + u32 triangle; // triangle + b3Vec3 point; // intersection point on surface + b3Vec3 normal; // surface normal of intersection + float32 fraction; // time of intersection on segment +}; + // Use a physics world to create/destroy rigid bodies, execute ray cast and volume queries. class b3World { @@ -92,13 +101,6 @@ public: // The function parameters are the ammount of time to simulate, // and the number of constraint solver iterations. void Step(float32 dt, u32 velocityIterations, u32 positionIterations); - - // Perform a ray cast with the world. - // If the ray doesn't intersect with a shape in the world then return false. - // The ray cast output is the intercepted shape, the intersection - // point in world space, the face normal on the shape associated with the point, - // and the intersection fraction. - bool RayCastSingle(b3RayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const; // Perform a ray cast with the world. // The given ray cast listener will be notified when a ray intersects a shape @@ -106,7 +108,29 @@ public: // The ray cast output is the intercepted shape, the intersection // point in world space, the face normal on the shape associated with the point, // and the intersection fraction. - void RayCast(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const; + void RayCastShape(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const; + + // Perform a ray cast with the world. + // If the ray doesn't intersect with a shape in the world then return false. + // The ray cast output is the intercepted shape, the intersection + // point in world space, the face normal on the shape associated with the point, + // and the intersection fraction. + bool RayCastSingleShape(b3ShapeRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const; + + // Perform a ray cast with the world. + // The given ray cast listener will be notified when a ray intersects a shape + // in the world. + // The ray cast output is the intercepted cloth, the intersection + // point in world space, the face normal on the cloth associated with the point, + // and the intersection fraction. + void RayCastCloth(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const; + + // Perform a ray cast with the world. + // If the ray doesn't intersect with a cloth in the world then return false. + // The ray cast output is the intercepted cloth, the intersection + // point in world space, the face normal on the cloth associated with the point, + // and the intersection fraction. + bool RayCastSingleCloth(b3ClothRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const; // Perform a AABB query with the world. // The query listener will be notified when two shape AABBs are overlapping. diff --git a/include/bounce/dynamics/world_listeners.h b/include/bounce/dynamics/world_listeners.h index 4b87efa..cc9fc02 100644 --- a/include/bounce/dynamics/world_listeners.h +++ b/include/bounce/dynamics/world_listeners.h @@ -22,6 +22,7 @@ #include class b3Shape; +class b3Cloth; class b3Contact; class b3QueryListener @@ -46,6 +47,12 @@ public: // the intersection point on the shape, the surface normal associated with the point, and the // intersection fraction for the ray. virtual float32 ReportShape(b3Shape* shape, const b3Vec3& point, const b3Vec3& normal, float32 fraction) = 0; + + // Report that a cloth was hit by the ray to this contact listener. + // The reported information are the shape hit by the ray, + // the intersection point on the cloth, the surface normal associated with the point, the + // intersection fraction for the ray, and the triangle. + virtual float32 ReportCloth(b3Cloth* cloth, const b3Vec3& point, const b3Vec3& normal, float32 fraction, u32 triangle) = 0; }; class b3ContactListener diff --git a/src/bounce/dynamics/cloth/cloth.cpp b/src/bounce/dynamics/cloth/cloth.cpp index e4ff249..4420aed 100644 --- a/src/bounce/dynamics/cloth/cloth.cpp +++ b/src/bounce/dynamics/cloth/cloth.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -347,15 +348,38 @@ void b3Cloth::ComputeMass() } } -bool b3Cloth::RayCast(b3ClothRayCastOutput* output, const b3RayCastInput* input) const +void b3Cloth::RayCast(b3RayCastListener* listener, const b3RayCastInput* input) const { + for (u32 i = 0; i < m_mesh->triangleCount; ++i) + { + b3ClothRayCastSingleOutput subOutput; + if (RayCast(&subOutput, input, i)) + { + float32 newFraction = listener->ReportCloth(subOutput.cloth, subOutput.point, subOutput.normal, subOutput.fraction, subOutput.triangle); + + if (newFraction == 0.0f) + { + // The client has stopped the query. + return; + } + } + } +} + +bool b3Cloth::RayCastSingle(b3ClothRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const +{ + b3RayCastInput input; + input.p1 = p1; + input.p2 = p2; + input.maxFraction = 1.0f; + output->triangle = ~0; - output->fraction = input->maxFraction; + output->fraction = B3_MAX_FLOAT; for (u32 i = 0; i < m_mesh->triangleCount; ++i) { - b3ClothRayCastOutput subOutput; - if (RayCast(&subOutput, input, i)) + b3ClothRayCastSingleOutput subOutput; + if (RayCast(&subOutput, &input, i)) { if (subOutput.fraction < output->fraction) { @@ -372,7 +396,7 @@ bool b3Cloth::RayCast(b3ClothRayCastOutput* output, const b3RayCastInput* input) return false; } -bool b3Cloth::RayCast(b3ClothRayCastOutput* output, const b3RayCastInput* input, u32 triangleIndex) const +bool b3Cloth::RayCast(b3ClothRayCastSingleOutput* output, const b3RayCastInput* input, u32 triangleIndex) const { B3_ASSERT(triangleIndex < m_mesh->triangleCount); b3ClothMeshTriangle* triangle = m_mesh->triangles + triangleIndex; diff --git a/src/bounce/dynamics/cloth/particle.cpp b/src/bounce/dynamics/cloth/particle.cpp index 650f81d..234293e 100644 --- a/src/bounce/dynamics/cloth/particle.cpp +++ b/src/bounce/dynamics/cloth/particle.cpp @@ -24,22 +24,7 @@ void b3FrictionForce::Apply(const b3ClothSolverData* data) { - b3DenseVec3& v = *data->v; - b3DenseVec3& f = *data->f; - b3SparseSymMat33& dfdv = *data->dfdv; - - u32 i = m_p->m_solverId; - - if (m_kd > 0.0f) - { - f[i] += -m_kd * v[i]; - - b3Mat33 I; I.SetIdentity(); - - b3Mat33 Jv = -m_kd * I; - - dfdv(i, i) += Jv; - } + // TODO } b3Particle::b3Particle(const b3ParticleDef& def, b3Cloth* cloth) diff --git a/src/bounce/dynamics/world.cpp b/src/bounce/dynamics/world.cpp index 5ccb474..d2c704d 100644 --- a/src/bounce/dynamics/world.cpp +++ b/src/bounce/dynamics/world.cpp @@ -378,7 +378,7 @@ void b3World::StepCloth(float32 dt) } } -struct b3RayCastCallback +struct b3ShapeRayCastCallback { float32 Report(const b3RayCastInput& input, u32 proxyId) { @@ -413,20 +413,20 @@ struct b3RayCastCallback const b3BroadPhase* broadPhase; }; -void b3World::RayCast(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const +void b3World::RayCastShape(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const { b3RayCastInput input; input.p1 = p1; input.p2 = p2; input.maxFraction = 1.0f; - b3RayCastCallback callback; + b3ShapeRayCastCallback callback; callback.listener = listener; callback.broadPhase = &m_contactMan.m_broadPhase; m_contactMan.m_broadPhase.RayCast(&callback, input); } -struct b3RayCastSingleCallback +struct b3ShapeRayCastSingleCallback { float32 Report(const b3RayCastInput& input, u32 proxyId) { @@ -458,14 +458,14 @@ struct b3RayCastSingleCallback const b3BroadPhase* broadPhase; }; -bool b3World::RayCastSingle(b3RayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const +bool b3World::RayCastSingleShape(b3ShapeRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const { b3RayCastInput input; input.p1 = p1; input.p2 = p2; input.maxFraction = 1.0f; - b3RayCastSingleCallback callback; + b3ShapeRayCastSingleCallback callback; callback.shape0 = NULL; callback.output0.fraction = B3_MAX_FLOAT; callback.broadPhase = &m_contactMan.m_broadPhase; @@ -491,6 +491,46 @@ bool b3World::RayCastSingle(b3RayCastSingleOutput* output, const b3Vec3& p1, con return false; } +void b3World::RayCastCloth(b3RayCastListener* listener, const b3Vec3& p1, const b3Vec3& p2) const +{ + b3RayCastInput input; + input.p1 = p1; + input.p2 = p2; + input.maxFraction = B3_MAX_FLOAT; + + for (b3Cloth* c = m_clothList.m_head; c; c = c->m_next) + { + c->RayCast(listener, &input); + } +} + +bool b3World::RayCastSingleCloth(b3ClothRayCastSingleOutput* output, const b3Vec3& p1, const b3Vec3& p2) const +{ + output->cloth = NULL; + output->triangle = ~0; + output->fraction = B3_MAX_FLOAT; + + for (b3Cloth* c = m_clothList.m_head; c; c = c->m_next) + { + b3ClothRayCastSingleOutput subOutput; + if (c->RayCastSingle(&subOutput, p1, p2)) + { + if (subOutput.fraction < output->fraction) + { + subOutput.cloth = c; + *output = subOutput; + } + } + } + + if (output->cloth != NULL) + { + return true; + } + + return false; +} + struct b3QueryAABBCallback { bool Report(u32 proxyID)