From 4cbe366ac9f337bd53d333f6d936007504d2a4bd Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 19 Apr 2020 20:21:29 +0100 Subject: [PATCH] third person character update --- Documentation/ScriptingAPI-Documentation.md | 2 + WickedEngine/wiIntersect_BindLua.cpp | 36 ++++ WickedEngine/wiIntersect_BindLua.h | 2 + WickedEngine/wiScene.cpp | 178 ++++++++++++++++++++ WickedEngine/wiScene.h | 11 ++ WickedEngine/wiScene_BindLua.cpp | 51 ++++++ WickedEngine/wiVersion.cpp | 2 +- models/playground.wiscene | Bin 27740 -> 28868 bytes scripts/character_controller_tps.lua | 98 ++++++----- 9 files changed, 329 insertions(+), 51 deletions(-) diff --git a/Documentation/ScriptingAPI-Documentation.md b/Documentation/ScriptingAPI-Documentation.md index 0b1fa089f..9030e1bc7 100644 --- a/Documentation/ScriptingAPI-Documentation.md +++ b/Documentation/ScriptingAPI-Documentation.md @@ -719,6 +719,8 @@ Sphere defined by center Vector and radius. Can be intersected with other primit - Intersects(Ray ray) : bool result - GetCenter() : Vector result - GetRadius() : float result +- SetCenter(Vector value) +- SetRadius(float value) ### Input Query input devices diff --git a/WickedEngine/wiIntersect_BindLua.cpp b/WickedEngine/wiIntersect_BindLua.cpp index 4c3bd3bb3..087392a40 100644 --- a/WickedEngine/wiIntersect_BindLua.cpp +++ b/WickedEngine/wiIntersect_BindLua.cpp @@ -249,6 +249,8 @@ namespace wiIntersect_BindLua lunamethod(Sphere_BindLua, Intersects), lunamethod(Sphere_BindLua, GetCenter), lunamethod(Sphere_BindLua, GetRadius), + lunamethod(Sphere_BindLua, SetCenter), + lunamethod(Sphere_BindLua, SetRadius), { NULL, NULL } }; Luna::PropertyType Sphere_BindLua::properties[] = { @@ -324,5 +326,39 @@ namespace wiIntersect_BindLua wiLua::SSetFloat(L, sphere.radius); return 1; } + int Sphere_BindLua::SetCenter(lua_State* L) + { + int argc = wiLua::SGetArgCount(L); + if (argc > 0) + { + Vector_BindLua* cV = Luna::lightcheck(L, 1); + if (cV) + { + XMStoreFloat3(&sphere.center, cV->vector); + } + else + { + wiLua::SError(L, "SetCenter(Vector value) requires first argument to be of Vector type!"); + } + } + else + { + wiLua::SError(L, "SetCenter(Vector value) not enough arguments!"); + } + return 0; + } + int Sphere_BindLua::SetRadius(lua_State* L) + { + int argc = wiLua::SGetArgCount(L); + if (argc > 0) + { + sphere.radius = wiLua::SGetFloat(L, 1); + } + else + { + wiLua::SError(L, "SetRadius(float value) not enough arguments!"); + } + return 0; + } } diff --git a/WickedEngine/wiIntersect_BindLua.h b/WickedEngine/wiIntersect_BindLua.h index 09ac897e6..ace140a76 100644 --- a/WickedEngine/wiIntersect_BindLua.h +++ b/WickedEngine/wiIntersect_BindLua.h @@ -62,5 +62,7 @@ namespace wiIntersect_BindLua int Intersects(lua_State* L); int GetCenter(lua_State* L); int GetRadius(lua_State* L); + int SetCenter(lua_State* L); + int SetRadius(lua_State* L); }; } diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 52c87c6a7..2c9df287b 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2729,4 +2729,182 @@ namespace wiScene return result; } + +#include + SceneIntersectSphereResult SceneIntersectSphere(const SPHERE& sphere, uint32_t renderTypeMask, uint32_t layerMask, const Scene& scene) + { + SceneIntersectSphereResult result; + XMVECTOR vCenter = XMLoadFloat3(&sphere.center); + XMVECTOR vRadius = XMVectorReplicatePtr(&sphere.radius); + XMVECTOR RadiusSq = XMVectorMultiply(vRadius, vRadius); + + if (scene.objects.GetCount() > 0) + { + + for (size_t i = 0; i < scene.aabb_objects.GetCount(); ++i) + { + const AABB& aabb = scene.aabb_objects[i]; + if (!sphere.intersects(aabb)) + { + 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))); + + // 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 (XMVectorGetX(Dist) > 0) + { + continue; + } + + // 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 Point = XMVectorNegativeMultiplySubtract(N, Dist, vCenter); + XMVECTOR planeIntersection = Point; + + // 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); + + 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); + + // 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; + } + + // Edge 1,2 + Point = 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; + } + + // Edge 2,0 + Point = 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; + } + + bool intersects = XMVector4EqualInt(XMVectorAndCInt(Intersection, NoIntersection), XMVectorTrueInt()); + + if (intersects && bestDist < result.distance_to_polygon) + { + result.entity = entity; + result.depth = sphere.radius - XMVectorGetX(XMVector3Length(vCenter - bestPoint)); + XMStoreFloat3(&result.position, bestPoint); + XMStoreFloat3(&result.normal, XMVector3Normalize(vCenter - bestPoint)); + } + } + subsetCounter++; + } + + } + } + + return result; + } + } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 077fee6d0..eecf414e3 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -1353,5 +1353,16 @@ namespace wiScene // layerMask : filter based on layer // scene : the scene that will be traced against the ray PickResult Pick(const RAY& ray, uint32_t renderTypeMask = RENDERTYPE_OPAQUE, uint32_t layerMask = ~0, const Scene& scene = GetScene()); + + struct SceneIntersectSphereResult + { + wiECS::Entity entity = wiECS::INVALID_ENTITY; + XMFLOAT3 position = XMFLOAT3(0, 0, 0); + XMFLOAT3 normal = XMFLOAT3(0, 0, 0); + float depth = 0; + float distance_to_polygon = FLT_MAX; + }; + SceneIntersectSphereResult SceneIntersectSphere(const SPHERE& sphere, 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 20bdd298a..66bcd1091 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -139,6 +139,56 @@ int Pick(lua_State* L) return 0; } +int SceneIntersectSphere(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(); + if (argc > 1) + { + renderTypeMask = (uint32_t)wiLua::SGetInt(L, 2); + if (argc > 2) + { + int mask = wiLua::SGetInt(L, 3); + layerMask = *reinterpret_cast(&mask); + + if (argc > 3) + { + Scene_BindLua* custom_scene = Luna::lightcheck(L, 4); + if (custom_scene) + { + scene = custom_scene->scene; + } + else + { + wiLua::SError(L, "SceneIntersectSphere(Sphere sphere, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) last argument is not of type Scene!"); + } + } + } + } + auto& pick = wiScene::SceneIntersectSphere(sphere->sphere, 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, "SceneIntersectSphere(Sphere sphere, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) first argument must be of Ray type!"); + } + else + { + wiLua::SError(L, "SceneIntersectSphere(Sphere sphere, opt PICKTYPE pickType, opt uint layerMask, opt Scene scene) not enough arguments!"); + } + + return 0; +} void Bind() { @@ -168,6 +218,7 @@ void Bind() wiLua::GetGlobal()->RegisterFunc("GetScene", GetScene); wiLua::GetGlobal()->RegisterFunc("LoadModel", LoadModel); wiLua::GetGlobal()->RegisterFunc("Pick", Pick); + wiLua::GetGlobal()->RegisterFunc("SceneIntersectSphere", SceneIntersectSphere); Luna::Register(L); Luna::Register(L); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index daaa6124e..a7a87e431 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 = 71; + const int revision = 72; long GetVersion() diff --git a/models/playground.wiscene b/models/playground.wiscene index d0281fc2e72b5fe001eeb84f2ee3c0ef2d5eedf4..e526d65e8693629486a791e230bcdba79e3de777 100644 GIT binary patch delta 6548 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 0) then + self.force = vector.Add(self.force, self.face:Multiply(Vector(f,f,f))) + end self.state = self.states.WALK end, @@ -163,13 +165,6 @@ Character = { local model_transform = scene.Component_GetTransform(self.model) local target_transform = scene.Component_GetTransform(self.target) - -- gravity: - self.force = vector.Add(self.force, Vector(0,-9.8 * 20,0)) - - -- apply force: - self.velocity = vector.Add(self.velocity, vector.Multiply(self.force, 0.016)) - self.force = Vector(0,0,0,0) - -- state and animation update if(self.state == self.states.STAND) then scene.Component_GetAnimation(self.walk_anim).Stop() @@ -198,50 +193,53 @@ Character = { end self.state_prev = self.state - -- front block shoots multiple rays in front to try to find obstruction - local rotations = {0, 3.1415*0.3, -3.1415*0.3} - for i,rot in ipairs(rotations) do - local origin = vector.Add(model_transform.GetPosition(), Vector(0,1,0)) -- this ray starts a little above character ground position - local dir = vector.Transform(self.face, matrix.RotationY(rot)) - local ray2 = Ray(origin,dir) - local o2,p2,n2 = Pick(ray2, PICK_OPAQUE, ~self.layerMask) - local dist = vector.Subtract(origin,p2):Length() - if(o2 ~= INVALID_ENTITY and dist < 1) then - -- run along wall instead of going through it + -- gravity: + self.force = vector.Add(self.force, Vector(0,-9.8 * 10,0)) + + -- apply force: + self.velocity = vector.Add(self.velocity, vector.Multiply(self.force, 0.016)) + self.force = Vector(0,0,0,0) + + -- Sphere collider for character: + local sphere = Sphere(vector.Add(model_transform.GetPosition(), Vector(0,1.1)), 1) + local pp = sphere.GetCenter() + local intersection = false + local ccd = 0 + local ccd_max = 5 + while(ccd < ccd_max) do + ccd = ccd + 1 + local step = vector.Multiply(self.velocity, 1.0 / ccd_max * 0.016) + + local prevpos = sphere.GetCenter() + sphere.SetCenter(vector.Add(prevpos, step)) + local o2, p2, n2, depth = SceneIntersectSphere(sphere, 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: 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) - end - end - - -- check what is below the character - self.ray = Ray(target_transform.GetPosition(),Vector(0,-1,0)) - local pPrev = self.p - self.o,self.p,self.n = Pick(self.ray, PICK_OPAQUE, ~self.layerMask) - if(self.o == INVALID_ENTITY) then - self.p=pPrev -- if nothing, go back to previous position - end - -- apply velocity: - model_transform.Translate(vector.Multiply(self.velocity, 0.016)) - model_transform.UpdateTransform() - - -- check if we are below or on the ground: - local posY = model_transform.GetPosition().GetY() - if(posY <= self.p.GetY() and self.velocity.GetY()<=0) then - self.state = self.states.STAND - if(self.o == INVALID_ENTITY) then - model_transform.Translate(vector.Subtract(self.p,model_transform.GetPosition())) -- snap back to last succesfully traced position - else - model_transform.Translate(Vector(0,self.p.GetY()-posY,0)) -- snap to ground + intersection = true + sphere.SetCenter(prevpos) end - self.velocity.SetY(0) -- don't fall below ground - self.velocity = vector.Multiply(self.velocity, 0.8) -- slow down on ground - else - self.velocity = vector.Multiply(self.velocity, 0.94) -- slow down in air end + --DrawPoint(sphere.GetCenter(), 0.5, Vector(0,1,0,1)) + + if intersection then + self.velocity = vector.Multiply(self.velocity, 0.8) -- 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.UpdateTransform() -- try to put water ripple if character head is directly above water local head_transform = scene.Component_GetTransform(self.head) @@ -441,7 +439,7 @@ runProcess(function() --face DrawLine(target_transform.GetPosition(),target_transform.GetPosition():Add(player.face:Normalize()),Vector(0,1,0,1)) --intersection - DrawAxis(player.p,0.5) + --DrawAxis(player.p,0.5) -- camera target box and axis DrawBox(target_transform.GetMatrix())