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 d0281fc2e..e526d65e8 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 57cf2bb9a..7df43ad26 100644 --- a/scripts/character_controller_tps.lua +++ b/scripts/character_controller_tps.lua @@ -32,9 +32,6 @@ Character = { face = Vector(0,0,1), -- forward direction force = Vector(), velocity = Vector(), - ray = Ray(Vector(),Vector()), - o = INVALID_ENTITY, -- collision prop with scene (entity) - p,n = Vector(), -- collision props with scene (position,normal) savedPointerPos = Vector(), moveSpeed = 90, layerMask = 0x2, -- The character will be tagged to use this layer, so scene Picking can filter out the character @@ -87,9 +84,12 @@ Character = { model_transform.ClearTransform() dir = vector.Transform(dir:Normalize(), target_transform.GetMatrix()) dir.SetY(0) - dir = dir.Normalize() + local dot = vector.Dot(self.face, dir) + if(dot < 0) then + self.face = vector.TransformNormal(self.face, matrix.RotationY(3.1415 * 0.01)) + end self.face = vector.Lerp(self.face, dir, 0.2); - self.face = self.face.Normalize() + self.face.Normalize() model_transform.MatrixTransform(matrix.LookTo(Vector(),self.face):Inverse()) model_transform.Scale(self.scale) model_transform.Rotate(self.rotation) @@ -97,7 +97,9 @@ Character = { model_transform.UpdateTransform() scene.Component_Detach(self.target) scene.Component_Attach(self.target, self.model) - self.force = vector.Add(self.force, self.face:Multiply(Vector(f,f,f))) + if(dot > 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())