public void DrawHeightmapFlat(WorldPhysics worldPhysics, Position offset, SortedList <int, Color> heightColorGradient) { // Constrain rendering to the display bounds int startX = Math.Max(worldPhysics.StartX, displayBounds.Left - offset.X); int endX = Math.Min(worldPhysics.EndX, displayBounds.Right - offset.X); if (endX <= startX) { return; // Off screen } for (int z = worldPhysics.EndZ - 1; z >= worldPhysics.StartZ; z--) // From back to front of heightmap { for (int x = startX; x < endX; x++) { int height = worldPhysics.GetGroundHeightAt(x, z, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); if (height == WorldPhysics.MaximumHeight) { continue; } // Draw top surface DrawPixel(new Position(offset.X + x, 0, offset.Z + z), heightColorGradient.GetColorFromGradient(height + offset.Y)); } } }
/// <summary>Get the height of the lowest ground position we can stand at at the given XZ coordinates, in some given Y range.</summary> /// <returns>The resulting height, or WorldPhysics.MaximumHeight if it did not fit</returns> public static int LowestValidGroundHeight(ref CharacterPhysicsInfo info, WorldPhysics world, int x, int z, int startY, int endY, bool staticOnly = false) { int startX = x + info.startX; int endX = x + info.endX; if (endY > WorldPhysics.MaximumHeight) { endY = WorldPhysics.MaximumHeight; } // Repeatedly test the walkable height upwards, until it returns somewhere we are confirmed to fit int tryingHeight = startY; int resultingHeight; do { resultingHeight = world.GetGroundHeightInXRange(startX, endX, z, tryingHeight, tryingHeight + info.height, info.owner, staticOnly); if (resultingHeight <= startY) { return(startY); } if (resultingHeight == tryingHeight) { break; } tryingHeight = resultingHeight; }while(resultingHeight < endY); if (resultingHeight == tryingHeight && resultingHeight >= startY && resultingHeight < endY) { return(resultingHeight); } return(WorldPhysics.MaximumHeight); // <- Failed }
public static void TryMoveVertical(ref CharacterPhysicsInfo info, WorldPhysics world, int deltaY, ref Position position, bool staticOnly = false) { // // Move on Y axis first... // if (deltaY < 0) { int groundHeight = GroundHeight(ref info, world, position, staticOnly); if (position.Y > groundHeight) // Don't move down further if already below ground { int newY = position.Y + deltaY; if (newY < groundHeight) // Don't go below ground { position.Y = groundHeight; } else { position.Y = newY; } } } else if (deltaY > 0) { int startY = position.Y + info.height; int endY = startY + deltaY; endY = world.GetCeilingHeightInXRange(position.X + info.startX, position.X + info.endX, position.Z, startY, endY, info.owner, staticOnly); position.Y = endY - info.height; } }
/// <summary>Get the height of the topmost ground at a given position. Used for generating navigation data. (Eventually replace with a multi-layer heightmap.)</summary> public static int TopmostGroundHeight(ref CharacterPhysicsInfo info, WorldPhysics world, int x, int z, bool staticOnly = false) { int startX = x + info.startX; int endX = x + info.endX; // Hack: If we slip in just under the ceiling, we can get the result for the next-highest floor // This is a hack because it depends on knowledge of what GetGroundHeightInXRange is doing int minCeiling = WorldPhysics.MaximumHeight + info.height; if (world.levelCeiling != null) { for (int xx = startX; xx < endX; xx++) { int c = world.levelCeiling[xx, z]; if (c == 0) { continue; } if (c < minCeiling) { minCeiling = c; } } } return(world.GetGroundHeightInXRange(startX, endX, z, minCeiling - info.height, minCeiling, info.owner, staticOnly)); }
public static int GroundHeight(ref CharacterPhysicsInfo info, WorldPhysics world, Position position, bool staticOnly = false) { int startX = position.X + info.startX; int endX = position.X + info.endX; return(world.GetGroundHeightInXRange(startX, endX, position.Z, position.Y, position.Y + info.height, info.owner, staticOnly)); }
private static bool MoveStep(ref CharacterPhysicsInfo info, WorldPhysics world, int deltaX, int deltaZ, ref bool groundSnap, ref Position position, bool staticOnly = false) { int groundHeight = GroundHeight(ref info, world, new Position(position.X + deltaX, position.Y, position.Z + deltaZ), staticOnly); // NOTE: This ensures that we cannot get kicked up later on in the method, when we set to groundHeight if (position.Y + info.stairStepMaxHeight < groundHeight) { return(false); // Cannot move up that high } position.X += deltaX; position.Z += deltaZ; if (position.Y < groundHeight) { // TODO: BUG: We need to know if we're going to hit the ceiling by moving upwards here! // Do not go below ground position.Y = groundHeight; } else if (groundSnap) { if (position.Y < groundHeight + info.stairStepMaxHeight) { // Snap to the ground if we are nearby position.Y = groundHeight; } else { // We came "off" the ground, stop trying to snap to it groundSnap = false; } } return(true); }
// NOTE: Rendering from world heightmap is slow!! // NOTE: Much copy-pasted code from normal heightmap rendering public void DrawWorldPhysicsXZRegion(WorldPhysics worldPhysics, MaskData xzMask, SortedList <int, Color> heightColorGradient) { // Constrain rendering to the display bounds int startX = Math.Max(worldPhysics.StartX, displayBounds.Left); int endX = Math.Min(worldPhysics.EndX, displayBounds.Right); if (endX <= startX) { return; // Off screen } for (int z = worldPhysics.EndZ - 1; z >= worldPhysics.StartZ; z--) // From back to front of heightmap { for (int x = startX; x < endX; x++) { if (!xzMask.GetOrDefault(x, z)) // TODO: Fix up bounds so we never leave this area (and use regular [,] access) - PERF { continue; } int height = worldPhysics.GetGroundHeightAt(x, z, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); if (height == WorldPhysics.MaximumHeight) { continue; } int nextHeight = 0; // Height of the next Z-value (row) if (xzMask.GetOrDefault(x, z - 1)) { nextHeight = worldPhysics.GetGroundHeightAt(x, z - 1, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); } if (nextHeight != WorldPhysics.MaximumHeight && nextHeight > height) { continue; // Next row will cover this one entirely } // Draw top surface const int zTestOffset = 1; // <- The top surface should be "under" any other pixels DrawPixel(new Position(x, height, z), heightColorGradient.GetColorFromGradient(height), zTestOffset); if (nextHeight != WorldPhysics.MaximumHeight && nextHeight == height) { continue; // Next row covers this one's "solid" section } // Draw solidness for (int h = height - 1; h >= 0; h--) { Color c = Color.Lerp(heightColorGradient.GetColorFromGradient(h), Color.Black, 0.6f); DrawPixel(new Position(x, h, z), c); } } } }
public ActionResult Assets(string objectType, string objectId, string resource, string resourceId) { Response.Headers.Add("Content-type", "application/json"); switch (objectType.ToLower()) { case "map": { return(MapPath(objectId, resource, resourceId)); } case "characters": { return(CharacterPath(objectId, resource, resourceId)); } case "manifest": { var worldPhysics = new WorldPhysics(); worldPhysics.gravityVector = new GravityVector(); worldPhysics.gravityVector.x = 0; worldPhysics.gravityVector.y = -20; worldPhysics.gravityVector.z = 0; var manifest = new UrlManifest(); manifest.baseUrl = "/game"; manifest.world = worldPhysics; manifest.map.baseUrl = "/stargazer"; manifest.map.texture = "/ground.jpg"; manifest.map.skyBox = "/skybox"; manifest.map.heightMap = "/heightmap"; manifest.map.physics.friction = 0.5; manifest.map.physics.mass = 0; manifest.map.physics.restitution = 0.8; manifest.playerUsername = this.User.Identity.Name; //todo fetch from a manifest db return(Json(manifest, JsonRequestBehavior.AllowGet)); } case "campaign": { var IdAsInt = Convert.ToInt32(objectId); return(Json(db.Campaigns.Find(IdAsInt), JsonRequestBehavior.AllowGet)); } } return(HttpNotFound()); }
public static MotionResult PhysicsStepHorizontal(ref CharacterPhysicsInfo cpi, ref Position position, ref ThreeDVelocity velocity, bool onGround, int coefficientOfRestitution256, int groundFriction256, WorldPhysics physics) { MotionResult result = MotionResult.None; int newPositionX = velocity.X.Update(position.X); int newPositionZ = velocity.Z.Update(position.Z); int deltaX = newPositionX - position.X; int deltaZ = newPositionZ - position.Z; if (deltaX != 0) { if (CharacterPhysics.TryMoveHorizontalDidHitWall(ref cpi, physics, deltaX, 0, onGround, ref position)) { // Hit wall, bounce: velocity.X.Scale256(-coefficientOfRestitution256); result = MotionResult.HitWall; } } // Z-motion just stops if it hits anything if (deltaZ != 0 && CharacterPhysics.TryMoveHorizontalDidHitWall(ref cpi, physics, 0, deltaZ, onGround, ref position)) { velocity.Z.Reset(); // NOTE: Currently not even bothering to give a hit result, just carry on... } // TODO: Slope handling for rolling objects if (onGround) { // Friction: velocity.X.Scale256(groundFriction256); velocity.Z.Scale256(groundFriction256); } return(result); }
public void DrawHeightmapSolid(WorldPhysics worldPhysics, Position offset, SortedList <int, Color> heightColorGradient) { // Constrain rendering to the display bounds int startX = Math.Max(worldPhysics.StartX, displayBounds.Left - offset.X); int endX = Math.Min(worldPhysics.EndX, displayBounds.Right - offset.X); if (endX <= startX) { return; // Off screen } for (int z = worldPhysics.EndZ - 1; z >= worldPhysics.StartZ; z--) // From back to front of heightmap { for (int x = startX; x < endX; x++) { int height = worldPhysics.GetGroundHeightAt(x, z, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); if (height == WorldPhysics.MaximumHeight) { continue; } int nextHeight = worldPhysics.GetGroundHeightAt(x, z - 1, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); if (nextHeight != WorldPhysics.MaximumHeight && nextHeight > height) { continue; // Next row will cover this one entirely } // Draw top surface const int zTestOffset = 1; // <- The top surface should be "under" any other pixels DrawPixel(offset + new Position(x, height, z), heightColorGradient.GetColorFromGradient(height + offset.Y), zTestOffset); if (nextHeight != WorldPhysics.MaximumHeight && nextHeight == height) { continue; // Next row covers this one's "solid" section } // Draw solidness for (int h = height + offset.Y - 1; h >= offset.Y; h--) { Color c = Color.Lerp(heightColorGradient.GetColorFromGradient(h), Color.Black, 0.6f); DrawPixel(new Position(offset.X + x, h, offset.Z + z), c); } } } }
/// <summary>Get the height of the level walkable heightmap at this position, if it is flat, otherwise WorldPhysics.MaximumHeight</summary> public static int LevelOnlyFlatGround(ref CharacterPhysicsInfo info, WorldPhysics world, int x, int z) { if (world.levelHeightmap == null) { return(WorldPhysics.MaximumHeight); } int startX = x + info.startX; int endX = x + info.endX; int h = world.levelHeightmap[startX, z]; for (int xx = startX + 1; xx < endX; xx++) { int c = world.levelHeightmap[xx, z]; if (c != h) { return(WorldPhysics.MaximumHeight); } } return(h); }
public static bool DoLemmingsMotion(ref CharacterPhysicsInfo info, WorldPhysics world, int inputX, int inputZ, int deltaX, int deltaZ, bool groundSnap, bool wallSlide, ref Position position) { Debug.Assert(Math.Abs(inputX) <= 1 && Math.Abs(inputZ) <= 1); bool resultX = true, resultZ = true; // WARNING: Delicate copy-paste code ahead (for each direction: X+, X-, Z+, Z-) while (deltaX > 0) { if (MoveStep(ref info, world, 1, 0, ref groundSnap, ref position)) { deltaX--; } else if (wallSlide) // Move failed, try wall-slide { if (inputX > 0 && inputZ <= 0 && MoveStep(ref info, world, 1, -1, ref groundSnap, ref position)) { deltaX--; if (deltaZ < 0) { deltaZ++; } else if (deltaX > 0) { deltaX--; } } else if (inputX > 0 && inputZ >= 0 && MoveStep(ref info, world, 1, 1, ref groundSnap, ref position)) { deltaX--; if (deltaZ > 0) { deltaZ--; } else if (deltaX > 0) { deltaX--; } } else { resultX = false; goto doneX; // Failed to move on the X axis } } else { resultX = false; goto doneX; // Failed to move on the X axis } } while (deltaX < 0) { if (MoveStep(ref info, world, -1, 0, ref groundSnap, ref position)) { deltaX++; } else if (wallSlide) // Move failed, try wall-slide { if (inputX < 0 && inputZ <= 0 && MoveStep(ref info, world, -1, -1, ref groundSnap, ref position)) { deltaX++; if (deltaZ < 0) { deltaZ++; } else if (deltaX < 0) { deltaX++; } } else if (inputX < 0 && inputZ >= 0 && MoveStep(ref info, world, -1, 1, ref groundSnap, ref position)) { deltaX++; if (deltaZ > 0) { deltaZ--; } else if (deltaX < 0) { deltaX++; } } else { resultX = false; goto doneX; // Failed to move on the X axis } } else { resultX = false; goto doneX; // Failed to move on the X axis } } doneX: while (deltaZ > 0) { if (MoveStep(ref info, world, 0, 1, ref groundSnap, ref position)) { deltaZ--; } else if (wallSlide) // Move failed, try wall-slide { if (inputZ > 0 && inputX <= 0 && MoveStep(ref info, world, -1, 1, ref groundSnap, ref position)) { deltaZ--; if (deltaX < 0) { deltaX++; } else if (deltaZ > 0) { deltaZ--; } } else if (inputZ > 0 && inputX >= 0 && MoveStep(ref info, world, 1, 1, ref groundSnap, ref position)) { deltaZ--; if (deltaX > 0) { deltaX--; } else if (deltaZ > 0) { deltaZ--; } } else { resultZ = false; goto doneZ; // Failed to move on the Z axis } } else { resultZ = false; goto doneZ; // Failed to move on the Z axis } } while (deltaZ < 0) { if (MoveStep(ref info, world, 0, -1, ref groundSnap, ref position)) { deltaZ++; } else if (wallSlide) // Move failed, try wall-slide { if (inputZ < 0 && inputX <= 0 && MoveStep(ref info, world, -1, -1, ref groundSnap, ref position)) { deltaZ++; if (deltaX < 0) { deltaX++; } else if (deltaZ < 0) { deltaZ++; } } else if (inputZ < 0 && inputX >= 0 && MoveStep(ref info, world, 1, -1, ref groundSnap, ref position)) { deltaZ++; if (deltaX > 0) { deltaX--; } else if (deltaZ < 0) { deltaZ++; } } else { resultZ = false; goto doneZ; // Failed to move on the Z axis } } else { resultZ = false; goto doneZ; // Failed to move on the Z axis } } doneZ: return((resultX && inputX != 0) || (resultZ && inputZ != 0) || (inputX == 0 && inputZ == 0)); }
public static bool TryMoveHorizontalDidHitWall(ref CharacterPhysicsInfo info, WorldPhysics world, int deltaX, int deltaZ, bool groundSnap, ref Position position, bool staticOnly = false) { bool hitWall = false; // // Sweep on XZ plane (slow but safe)... // while (deltaX > 0) { deltaX--; if (!MoveStep(ref info, world, 1, 0, ref groundSnap, ref position, staticOnly)) { hitWall = true; goto doneX; } } while (deltaX < 0) { deltaX++; if (!MoveStep(ref info, world, -1, 0, ref groundSnap, ref position, staticOnly)) { hitWall = true; goto doneX; } } doneX: while (deltaZ > 0) { deltaZ--; if (!MoveStep(ref info, world, 0, 1, ref groundSnap, ref position, staticOnly)) { hitWall = true; goto doneZ; } } while (deltaZ < 0) { deltaZ++; if (!MoveStep(ref info, world, 0, -1, ref groundSnap, ref position, staticOnly)) { hitWall = true; goto doneZ; } } doneZ: return(hitWall); }
public static void TryMove(ref CharacterPhysicsInfo info, WorldPhysics world, Position delta, bool groundSnap, ref Position position, bool staticOnly = false) { TryMoveVertical(ref info, world, delta.Y, ref position, staticOnly); TryMoveHorizontalDidHitWall(ref info, world, delta.X, delta.Z, groundSnap, ref position, staticOnly); }
public static MotionResult PhysicsStepVertical(ref CharacterPhysicsInfo cpi, ref Position position, ref ThreeDVelocity velocity, ref bool onGround, int coefficientOfRestitution256, int gravityUp256, int gravityDown256, bool lockVertical, WorldPhysics physics, bool groundIsAPit) { if (lockVertical) { velocity.Y.Reset(); } else { if (velocity.Y.Velocity256 > 0) // Moving upwards { onGround = false; int startY = position.Y + cpi.height; position.Y = velocity.Y.Update(position.Y, gravityUp256); int endY = position.Y + cpi.height; if (endY > startY) { int ceiling = physics.GetCeilingHeightInXRange(position.X + cpi.startX, position.X + cpi.endX, position.Z, startY, endY, cpi.owner); if (ceiling < endY) // Hit ceiling { position.Y = ceiling - cpi.height; velocity.Y.Velocity256 = -((velocity.Y.Velocity256 * coefficientOfRestitution256) >> 8); // <- Bounce (fixed-point multiply) return(MotionResult.HitCeiling); } } } else // Moving downwards { int groundHeight = CharacterPhysics.GroundHeight(ref cpi, physics, position); if (position.Y > groundHeight) { onGround = false; // we came off the ground } else if (position.Y < groundHeight) // TODO: This needs some rethinking (added to let separation do its thing) { onGround = true; // we are embedded in the ground (we may start physics in this state, if we are put here by teleport/animation) } else if (position.Y == groundHeight && velocity.Y.Velocity256 == 0) { onGround = true; // we were gently placed on the ground (don't trigger OnHitGround) } if (!onGround) { position.Y = velocity.Y.Update(position.Y, gravityDown256); if (position.Y <= groundHeight) // Hit the ground { position.Y = groundHeight; if (groundHeight == 0 && groundIsAPit) { velocity = new ThreeDVelocity(); return(MotionResult.HitBottomOfPit); } else if (velocity.Y.Velocity256 > -128) // <- Kill velocity of we're moving too slowly downwards (start rolling) { onGround = true; velocity.Y.Reset(); } else { onGround = false; velocity.Y.Scale256(-coefficientOfRestitution256); velocity.X.Scale256(coefficientOfRestitution256); velocity.Z.Scale256(coefficientOfRestitution256); } return(MotionResult.HitGround); } } } } return(MotionResult.None); }
/// <summary>Determine whether a position is on (or inside) the ground at a given position.</summary> public static bool OnGround(ref CharacterPhysicsInfo info, WorldPhysics world, Position position) { return(position.Y <= GroundHeight(ref info, world, position)); }
/// <summary>Get the Y position of the ground for this actor.</summary> public int GroundHeight(WorldPhysics physics) { var cpi = new CharacterPhysicsInfo(animationSet, this); return(CharacterPhysics.GroundHeight(ref cpi, physics, position)); }
// Start is called before the first frame update void Start() { Instance = this; }