diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 2167d2aa6..53637aa33 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2808,7 +2808,7 @@ namespace wiScene p2 = SkinVertex(mesh, *armature, i2); } } - + p0 = XMVector3Transform(p0, objectMat); p1 = XMVector3Transform(p1, objectMat); p2 = XMVector3Transform(p2, objectMat); @@ -2822,10 +2822,10 @@ namespace wiScene // Find the nearest feature on the triangle to the sphere. XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(vCenter, p0), N); - //if (XMVectorGetX(Dist) > 0) - //{ - // continue; - //} + if (!mesh.IsDoubleSided() && XMVectorGetX(Dist) > 0) + { + continue; // pass through back faces + } // If the center of the sphere is farther from the plane of the triangle than // the radius of the sphere, then there cannot be an intersection. @@ -2833,69 +2833,270 @@ namespace wiScene NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, vRadius)); // Project the center of the sphere onto the plane of the triangle. - XMVECTOR Point = XMVectorNegativeMultiplySubtract(N, Dist, vCenter); - XMVECTOR planeIntersection = Point; + XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, vCenter); // Is it inside all the edges? If so we intersect because the distance // to the plane is less than the radius. - XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point, p0, p1, p2); + XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); - XMVECTOR bestPoint = Point; - float bestDist = inside ? 0 : FLT_MAX; // Find the nearest point on each edge. // Edge 0,1 - Point = DirectX::Internal::PointOnLineSegmentNearestPoint(p0, p1, vCenter); + XMVECTOR Point1 = DirectX::Internal::PointOnLineSegmentNearestPoint(p0, p1, vCenter); // If the distance to the center of the sphere to the point is less than // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point)), RadiusSq)); - - float d = abs(XMVectorGetX(XMVector3LinePointDistance(p0, p1, planeIntersection))); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point; - } + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point1)), RadiusSq)); // Edge 1,2 - Point = DirectX::Internal::PointOnLineSegmentNearestPoint(p1, p2, vCenter); + XMVECTOR Point2 = DirectX::Internal::PointOnLineSegmentNearestPoint(p1, p2, vCenter); // If the distance to the center of the sphere to the point is less than // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point)), RadiusSq)); - - d = abs(XMVectorGetX(XMVector3LinePointDistance(p1, p2, planeIntersection))); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point; - } + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point2)), RadiusSq)); // Edge 2,0 - Point = DirectX::Internal::PointOnLineSegmentNearestPoint(p2, p0, vCenter); + XMVECTOR Point3 = DirectX::Internal::PointOnLineSegmentNearestPoint(p2, p0, vCenter); // If the distance to the center of the sphere to the point is less than // the radius of the sphere then it must intersect. - Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point)), RadiusSq)); - - d = abs(XMVectorGetX(XMVector3LinePointDistance(p2, p0, planeIntersection))); - if (d < bestDist) - { - bestDist = d; - bestPoint = Point; - } + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point3)), RadiusSq)); bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); if (intersects) { + XMVECTOR bestPoint = Point0; + if (!inside) + { + // If the sphere center's projection on the triangle plane is not within the triangle, + // determine the closest point on triangle to the sphere center + float bestDist = FLT_MAX; + float d = abs(XMVectorGetX(XMVector3LengthSq(Point1 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point1; + } + d = abs(XMVectorGetX(XMVector3LengthSq(Point2 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point2; + } + d = abs(XMVectorGetX(XMVector3LengthSq(Point3 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point3; + } + } + XMVECTOR intersectionVec = vCenter - bestPoint; + XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); + result.entity = entity; - result.depth = sphere.radius - XMVectorGetX(XMVector3Length(vCenter - bestPoint)); + result.depth = sphere.radius - XMVectorGetX(intersectionVecLen); XMStoreFloat3(&result.position, bestPoint); - XMStoreFloat3(&result.normal, XMVector3Normalize(vCenter - bestPoint)); + XMStoreFloat3(&result.normal, intersectionVec / intersectionVecLen); + return result; + } + } + subsetCounter++; + } + + } + } + + return result; + } + SceneIntersectSphereResult SceneIntersectCapsule(const SPHERE& sphere, float height, uint32_t renderTypeMask, uint32_t layerMask, const Scene& scene) + { + SceneIntersectSphereResult result; + XMVECTOR vCenterBase = XMLoadFloat3(&sphere.center); + XMVECTOR vTip = vCenterBase + XMVectorSet(0, height, 0, 0); + XMVECTOR vRadius = XMVectorReplicate(sphere.radius); + XMVECTOR RadiusSq = XMVectorMultiply(vRadius, vRadius); + + AABB base_aabb; + base_aabb.createFromHalfWidth(sphere.center, XMFLOAT3(sphere.radius, sphere.radius, sphere.radius)); + XMFLOAT3 centertip; + XMStoreFloat3(¢ertip, vTip); + AABB tip_aabb; + tip_aabb.createFromHalfWidth(centertip, XMFLOAT3(sphere.radius, sphere.radius, sphere.radius)); + AABB capsule_aabb = AABB::Merge(base_aabb, tip_aabb); + + if (scene.objects.GetCount() > 0) + { + + for (size_t i = 0; i < scene.aabb_objects.GetCount(); ++i) + { + const AABB& aabb = scene.aabb_objects[i]; + if (capsule_aabb.intersects(aabb) == AABB::INTERSECTION_TYPE::OUTSIDE) + { + continue; + } + + const ObjectComponent& object = scene.objects[i]; + if (object.meshID == INVALID_ENTITY) + { + continue; + } + if (!(renderTypeMask & object.GetRenderTypes())) + { + continue; + } + + Entity entity = scene.aabb_objects.GetEntity(i); + const LayerComponent* layer = scene.layers.GetComponent(entity); + if (layer != nullptr && !(layer->GetLayerMask() & layerMask)) + { + continue; + } + + const MeshComponent& mesh = *scene.meshes.GetComponent(object.meshID); + const SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object.meshID); + const bool softbody_active = softbody != nullptr && !softbody->vertex_positions_simulation.empty(); + + const XMMATRIX objectMat = object.transform_index >= 0 ? XMLoadFloat4x4(&scene.transforms[object.transform_index].world) : XMMatrixIdentity(); + + const ArmatureComponent* armature = mesh.IsSkinned() ? scene.armatures.GetComponent(mesh.armatureID) : nullptr; + + int subsetCounter = 0; + for (auto& subset : mesh.subsets) + { + for (size_t i = 0; i < subset.indexCount; i += 3) + { + const uint32_t i0 = mesh.indices[subset.indexOffset + i + 0]; + const uint32_t i1 = mesh.indices[subset.indexOffset + i + 1]; + const uint32_t i2 = mesh.indices[subset.indexOffset + i + 2]; + + XMVECTOR p0; + XMVECTOR p1; + XMVECTOR p2; + + if (softbody_active) + { + p0 = softbody->vertex_positions_simulation[i0].LoadPOS(); + p1 = softbody->vertex_positions_simulation[i1].LoadPOS(); + p2 = softbody->vertex_positions_simulation[i2].LoadPOS(); + } + else + { + if (armature == nullptr) + { + p0 = XMLoadFloat3(&mesh.vertex_positions[i0]); + p1 = XMLoadFloat3(&mesh.vertex_positions[i1]); + p2 = XMLoadFloat3(&mesh.vertex_positions[i2]); + } + else + { + p0 = SkinVertex(mesh, *armature, i0); + p1 = SkinVertex(mesh, *armature, i1); + p2 = SkinVertex(mesh, *armature, i2); + } + } + + p0 = XMVector3Transform(p0, objectMat); + p1 = XMVector3Transform(p1, objectMat); + p2 = XMVector3Transform(p2, objectMat); + + // Compute the plane of the triangle (has to be normalized). + XMVECTOR N = XMVector3Normalize(XMVector3Cross(XMVectorSubtract(p1, p0), XMVectorSubtract(p2, p0))); + + // Intersect capsule line with triangle plane: + XMVECTOR Plane = XMPlaneFromPointNormal(p0, N); + XMVECTOR LinePlaneIntersection = XMPlaneIntersectLine(Plane, vCenterBase, vTip); + // Place a sphere on closest point on line segment to intersection: + XMVECTOR ab = vTip - vCenterBase; + XMVECTOR t = XMVector3Dot(LinePlaneIntersection - vCenterBase, ab) / XMVector3Dot(ab, ab); + XMVECTOR vCenter = vCenterBase + XMVectorSaturate(t) * ab; + + // Assert that the triangle is not degenerate. + assert(!XMVector3Equal(N, XMVectorZero())); + + // Find the nearest feature on the triangle to the sphere. + XMVECTOR Dist = XMVector3Dot(XMVectorSubtract(vCenter, p0), N); + + if (!mesh.IsDoubleSided() && XMVectorGetX(Dist) > 0) + { + continue; // pass through back faces + } + + // If the center of the sphere is farther from the plane of the triangle than + // the radius of the sphere, then there cannot be an intersection. + XMVECTOR NoIntersection = XMVectorLess(Dist, XMVectorNegate(vRadius)); + NoIntersection = XMVectorOrInt(NoIntersection, XMVectorGreater(Dist, vRadius)); + + // Project the center of the sphere onto the plane of the triangle. + XMVECTOR Point0 = XMVectorNegativeMultiplySubtract(N, Dist, vCenter); + + // Is it inside all the edges? If so we intersect because the distance + // to the plane is less than the radius. + XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle(Point0, p0, p1, p2); + + bool inside = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + + // Find the nearest point on each edge. + + // Edge 0,1 + XMVECTOR Point1 = DirectX::Internal::PointOnLineSegmentNearestPoint(p0, p1, vCenter); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point1)), RadiusSq)); + + // Edge 1,2 + XMVECTOR Point2 = DirectX::Internal::PointOnLineSegmentNearestPoint(p1, p2, vCenter); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point2)), RadiusSq)); + + // Edge 2,0 + XMVECTOR Point3 = DirectX::Internal::PointOnLineSegmentNearestPoint(p2, p0, vCenter); + + // If the distance to the center of the sphere to the point is less than + // the radius of the sphere then it must intersect. + Intersection = XMVectorOrInt(Intersection, XMVectorLessOrEqual(XMVector3LengthSq(XMVectorSubtract(vCenter, Point3)), RadiusSq)); + + bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + + if (intersects) + { + XMVECTOR bestPoint = Point0; + if (!inside) + { + // If the sphere center's projection on the triangle plane is not within the triangle, + // determine the closest point on triangle to the sphere center + float bestDist = FLT_MAX; + float d = abs(XMVectorGetX(XMVector3LengthSq(Point1 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point1; + } + d = abs(XMVectorGetX(XMVector3LengthSq(Point2 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point2; + } + d = abs(XMVectorGetX(XMVector3LengthSq(Point3 - vCenter))); + if (d < bestDist) + { + bestDist = d; + bestPoint = Point3; + } + } + XMVECTOR intersectionVec = vCenter - bestPoint; + XMVECTOR intersectionVecLen = XMVector3Length(intersectionVec); + + result.entity = entity; + result.depth = sphere.radius - XMVectorGetX(intersectionVecLen); + XMStoreFloat3(&result.position, bestPoint); + XMStoreFloat3(&result.normal, intersectionVec / intersectionVecLen); return result; } } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 0c4fced06..53f78b1a6 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -1362,6 +1362,7 @@ namespace wiScene float depth = 0; }; SceneIntersectSphereResult SceneIntersectSphere(const SPHERE& sphere, uint32_t renderTypeMask = RENDERTYPE_OPAQUE, uint32_t layerMask = ~0, const Scene& scene = GetScene()); + SceneIntersectSphereResult SceneIntersectCapsule(const SPHERE& sphere, float height, uint32_t renderTypeMask = RENDERTYPE_OPAQUE, uint32_t layerMask = ~0, const Scene& scene = GetScene()); } diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 66bcd1091..35e2d2f1f 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -189,6 +189,61 @@ int SceneIntersectSphere(lua_State* L) return 0; } +int SceneIntersectCapsule(lua_State* L) +{ + int argc = wiLua::SGetArgCount(L); + if (argc > 0) + { + Sphere_BindLua* sphere = Luna::lightcheck(L, 1); + if (sphere != nullptr) + { + uint32_t renderTypeMask = RENDERTYPE_OPAQUE; + uint32_t layerMask = 0xFFFFFFFF; + Scene* scene = &wiScene::GetScene(); + float height = 1; + if (argc > 1) + { + height = wiLua::SGetFloat(L, 2); + if (argc > 2) + { + renderTypeMask = (uint32_t)wiLua::SGetInt(L, 3); + if (argc > 3) + { + int mask = wiLua::SGetInt(L, 4); + layerMask = *reinterpret_cast(&mask); + + if (argc > 4) + { + Scene_BindLua* custom_scene = Luna::lightcheck(L, 5); + if (custom_scene) + { + scene = custom_scene->scene; + } + else + { + wiLua::SError(L, "SceneIntersectCapsule(Sphere sphere, float height, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) last argument is not of type Scene!"); + } + } + } + } + } + auto& pick = wiScene::SceneIntersectCapsule(sphere->sphere, height, renderTypeMask, layerMask, *scene); + wiLua::SSetInt(L, (int)pick.entity); + Luna::push(L, new Vector_BindLua(XMLoadFloat3(&pick.position))); + Luna::push(L, new Vector_BindLua(XMLoadFloat3(&pick.normal))); + wiLua::SSetFloat(L, pick.depth); + return 4; + } + + wiLua::SError(L, "SceneIntersectCapsule(Sphere sphere, float height, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) first argument must be of Ray type!"); + } + else + { + wiLua::SError(L, "SceneIntersectCapsule(Sphere sphere, float height, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) not enough arguments!"); + } + + return 0; +} void Bind() { @@ -219,6 +274,7 @@ void Bind() wiLua::GetGlobal()->RegisterFunc("LoadModel", LoadModel); wiLua::GetGlobal()->RegisterFunc("Pick", Pick); wiLua::GetGlobal()->RegisterFunc("SceneIntersectSphere", SceneIntersectSphere); + wiLua::GetGlobal()->RegisterFunc("SceneIntersectCapsule", SceneIntersectCapsule); Luna::Register(L); Luna::Register(L); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 97925679b..64bdb644e 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates const int minor = 39; // minor bug fixes, alterations, refactors, updates - const int revision = 73; + const int revision = 74; long GetVersion() diff --git a/models/playground.wiscene b/models/playground.wiscene index e526d65e8..72f87d7fe 100644 Binary files a/models/playground.wiscene and b/models/playground.wiscene differ diff --git a/scripts/character_controller_tps.lua b/scripts/character_controller_tps.lua index c15738d9c..8ee29d570 100644 --- a/scripts/character_controller_tps.lua +++ b/scripts/character_controller_tps.lua @@ -192,9 +192,9 @@ Character = { end end self.state_prev = self.state - + -- gravity: - self.force = vector.Add(self.force, Vector(0,-9.8 * 20,0)) + self.force = vector.Add(self.force, Vector(0,-9.8 * 15,0)) -- apply force: self.velocity = vector.Add(self.velocity, vector.Multiply(self.force, 0.016)) @@ -203,8 +203,8 @@ Character = { -- Sphere collider for character: local radius = 0.7 local sphere = Sphere(vector.Add(model_transform.GetPosition(), Vector(0,radius)), radius) - local pp = sphere.GetCenter() - local intersection = false + local original_spherecenter = sphere.GetCenter() + local ground_intersection = false local ccd = 0 local ccd_max = 5 while(ccd < ccd_max) do @@ -213,33 +213,39 @@ Character = { local prevpos = sphere.GetCenter() sphere.SetCenter(vector.Add(prevpos, step)) - local o2, p2, n2, depth = SceneIntersectSphere(sphere, PICK_OPAQUE, ~self.layerMask) + local o2, p2, n2, depth = SceneIntersectCapsule(sphere, 3, PICK_OPAQUE, ~self.layerMask) if(o2 ~= INVALID_ENTITY) then DrawPoint(p2,0.5,Vector(1,1,0,1)) DrawLine(p2, vector.Add(p2, n2), Vector(1,1,0,1)) - -- Slide on surface: + -- Slide on contact surface: local velocityLen = self.velocity.Length() local velocityNormalized = self.velocity.Normalize() local undesiredMotion = n2:Multiply(vector.Dot(velocityNormalized, n2)) local desiredMotion = vector.Subtract(velocityNormalized, undesiredMotion) self.velocity = vector.Multiply(desiredMotion, velocityLen) - intersection = true - sphere.SetCenter(vector.Add(sphere.GetCenter(), vector.Multiply(n2, depth))) -- remove penetration + -- Remove penetration (penetration upscaled to avoid precision error): + sphere.SetCenter(vector.Add(sphere.GetCenter(), vector.Multiply(n2, depth * 1.2))) + + -- Check whether it is intersecting the ground (ground normal is upwards) + if(vector.Dot(n2, Vector(0,1,0)) > 0) then + ground_intersection = true + end + end end --DrawPoint(sphere.GetCenter(), 0.5, Vector(0,1,0,1)) - if intersection then - self.velocity = vector.Multiply(self.velocity, 0.87) -- ground friction + if ground_intersection then + self.velocity = vector.Multiply(self.velocity, 0.85) -- ground friction else self.velocity = vector.Multiply(self.velocity, 0.94) -- air friction end if(vector.Length(self.velocity) < 0.001) then self.state = self.states.STAND end - model_transform.Translate(vector.Subtract(sphere.GetCenter(), pp)) + model_transform.Translate(vector.Subtract(sphere.GetCenter(), original_spherecenter)) -- transform by the sphere offset model_transform.UpdateTransform() -- try to put water ripple if character head is directly above water