private bool IsInViewingFrustum(GameClient gameClient, EntityOffset offset, ChunkPos chunkPos) { /* Check if the bounding sphere of the chunk is in the viewing frustum. */ var df = (double)GeometryConstants.ChunkSize; // Determine the chunk center relative to the viewer. double dx = (chunkPos.X + 0.5) * df - offset.X; double dy = (chunkPos.Y + 0.5) * df - offset.Y; double dz = (chunkPos.Z + 0.5) * df - offset.Z; double t0, t1; // Perform mouselook rotation double ha = gameClient.PositionData.Placement.Orientation.Horizontal; double hc = Math.Cos(ha), hs = Math.Sin(ha); t0 = dx * hc - dz * hs; t1 = dz * hc + dx * hs; dx = t0; dz = t1; double va = -gameClient.PositionData.Placement.Orientation.Vertical; double vc = Math.Cos(va), vs = Math.Sin(va); t0 = dz * vc - dy * vs; t1 = dy * vc + dz * vs; dz = t0; dy = t1; // Check if the chunk is behind the viewer. if (dz > ChunkRadius && dz > ChunkRadius) return false; /* TODO: We can discard even more chunks by taking the left, right, top and bottom planes into account. */ return true; }
public static void MoveEntity(PhysicsValues physicsValues, World world, PositionData positionData, GameDuration elapsedDuration, EntityOffset offset) { offset.Y -= 0.5 * elapsedDuration.Seconds * physicsValues.Gravity; if (MoveY(physicsValues, world, positionData, offset.Y * elapsedDuration.Seconds)) { positionData.IsFalling = false; } else { if (!positionData.IsFalling) { /* Re-evaluate the fall from the apparent position. */ positionData.InternalPos = positionData.Placement.Pos; MoveY(physicsValues, world, positionData, offset.Y * elapsedDuration.Seconds); positionData.IsFalling = true; } } if (positionData.IsFalling) { offset.Y -= 0.5 * elapsedDuration.Seconds * physicsValues.Gravity; } else { if (Math.Abs(offset.Y) > Math.Sqrt(2 * physicsValues.Gravity * physicsValues.TerminalHeight)) { Respawn(positionData); } offset.Y = 0; } if (offset.X != 0 || offset.Z != 0) { long savedY = positionData.InternalPos.Y; if (!positionData.IsFalling && world[(BlockPos)positionData.InternalPos - BlockOffset.UnitY] != 0) { // Temporarily move the character up a block so that it can climb up stairs. MoveY(physicsValues, world, positionData, 1); } EntityPos positionBeforeMovement = positionData.InternalPos; double moveX = offset.X * elapsedDuration.Seconds; double moveZ = offset.Z * elapsedDuration.Seconds; if (offset.X > offset.Z) { MoveX(physicsValues, world, positionData, moveX); MoveZ(physicsValues, world, positionData, moveZ); } else { MoveZ(physicsValues, world, positionData, moveZ); MoveX(physicsValues, world, positionData, moveX); } var moveDelta = positionData.InternalPos - positionBeforeMovement; moveX -= moveDelta.X; moveZ -= moveDelta.Z; if (!positionData.IsFalling) { // Attempt to move the character back down to the ground in case we didn't climb a stair. MoveY(physicsValues, world, positionData, (savedY - positionData.InternalPos.Y) / (double)(1L << 32)); savedY = positionData.InternalPos.Y; // Attempt to move the caracter down an additional block so that it can walk down stairs. if (!MoveY(physicsValues, world, positionData, -((1L << 32) + 1) / (double)(1L << 32))) { positionData.InternalPos.Y = savedY; positionData.IsFalling = true; } } // Attempt to continue movement at this new (lower) Y position. if (offset.X > offset.Z) { if (MoveX(physicsValues, world, positionData, moveX)) offset.X = 0; if (MoveZ(physicsValues, world, positionData, moveZ)) offset.Z = 0; } else { if (MoveZ(physicsValues, world, positionData, moveZ)) offset.Z = 0; if (MoveX(physicsValues, world, positionData, moveX)) offset.X = 0; } } positionData.Velocity = offset; SetPositionFromCollisionPosition(physicsValues, world, positionData); }
private static void SetPositionFromCollisionPosition(PhysicsValues physicsValues, World world, PositionData positionData) { if (positionData.IsFalling) { positionData.Placement.Pos = positionData.InternalPos; return; } var entityPos = positionData.InternalPos; var blockPos = (BlockPos)entityPos; /* * When standing on an edge, the entities bottom center might be hanging in the air. * Find the true floor position by iterating downward and looking for the floor. */ if (world[blockPos] == 0 && world[blockPos - BlockOffset.UnitY] == 0) { blockPos -= BlockOffset.UnitY; entityPos -= EntityOffset.UnitY; if (world[blockPos - new BlockOffset(0, 2, 0)] == 0) { blockPos -= BlockOffset.UnitY; entityPos -= EntityOffset.UnitY; } } var entityOffset = entityPos - blockPos; double yOffset = 0; /* * Take the integral of all floor heights in a 2 by 2 area around the center. * Since the height is piecewise constant, this is a weighted sum of the heights around the entity position. */ yOffset += (1 - entityOffset.X) * FindHeight(world, blockPos - BlockOffset.UnitX); yOffset += entityOffset.X * FindHeight(world, blockPos + BlockOffset.UnitX); yOffset += (1 - entityOffset.Z) * FindHeight(world, blockPos - BlockOffset.UnitZ); yOffset += entityOffset.Z * FindHeight(world, blockPos + BlockOffset.UnitZ); yOffset += (1 - entityOffset.X) * (1 - entityOffset.Z) * FindHeight(world, blockPos + new BlockOffset(-1, 0, -1)); yOffset += (1 - entityOffset.X) * entityOffset.Z * FindHeight(world, blockPos + new BlockOffset(-1, 0, 1)); yOffset += entityOffset.X * (1 - entityOffset.Z) * FindHeight(world, blockPos + new BlockOffset(1, 0, -1)); yOffset += entityOffset.X * entityOffset.Z * FindHeight(world, blockPos + new BlockOffset(1, 0, 1)); // Normalize. yOffset *= 0.25; entityPos += new EntityOffset(0, yOffset, 0); positionData.Placement.Pos = entityPos; }
public RayCastResult CastRay(EntityPos entityPos, EntityOffset direction, double max) { long x = entityPos.X, y = entityPos.Y, z = entityPos.Z; double dx = direction.X, dy = direction.Y, dz = direction.Z; int sdx = (int)Math.Sign(dx); int sdy = (int)Math.Sign(dy); int sdz = (int)Math.Sign(dz); if (sdx == 0 && sdy == 0 && sdz == 0) { return null; } const long edgeLength = 1L << 32; const double oneOverEdge = 1.0 / edgeLength; var blockPos = (BlockPos)entityPos; if (this[blockPos] != 0) { return new RayCastResult { EntityPos = entityPos, BlockPos = blockPos }; } while (max > 0 && IsNumber(max)) { /* Check which edge of the current block is hit first */ long edgeX; if (sdx > 0) { edgeX = (x + edgeLength) & _upperMask; } else { edgeX = (x - 1) & _upperMask; } double tx = (edgeX - x) * oneOverEdge / dx; long edgeY; if (sdy > 0) { edgeY = (y + edgeLength) & _upperMask; } else { edgeY = (y - 1) & _upperMask; } double ty = (edgeY - y) * oneOverEdge / dy; long edgeZ; if (sdz > 0) { edgeZ = (z + edgeLength) & _upperMask; } else { edgeZ = (z - 1) & _upperMask; } double tz = (edgeZ - z) * oneOverEdge / dz; if (Math.Abs(edgeX - x) > edgeLength || Math.Abs(edgeY - y) > edgeLength || Math.Abs(edgeZ - z) > edgeLength) { // Shouldn't happen. throw new Exception(); } if (IsNumber(tx) && (tx <= ty || !IsNumber(ty)) && (tx <= tz || !IsNumber(tz))) { x = edgeX; y += (long)(tx * dy * edgeLength); z += (long)(tx * dz * edgeLength); max -= tx; blockPos.X += sdx; if (max >= 0 && this[blockPos] != 0) { return new RayCastResult { EntityPos = new EntityPos(x, y, z), BlockPos = blockPos, Normal = new BlockOffset(-sdx, 0, 0) }; } } else if (IsNumber(ty) && (ty <= tz || !IsNumber(tz))) { x += (long)(ty * dx * edgeLength); y = edgeY; z += (long)(ty * dz * edgeLength); max -= ty; blockPos.Y += sdy; if (max >= 0 && this[blockPos] != 0) { return new RayCastResult { EntityPos = new EntityPos(x, y, z), BlockPos = blockPos, Normal = new BlockOffset(0, -sdy, 0) }; } } else if (IsNumber(tz)) { x += (long)(tz * dx * edgeLength); y += (long)(tz * dy * edgeLength); z = edgeZ; max -= tz; blockPos.Z += sdz; if (max >= 0 && this[blockPos] != 0) { return new RayCastResult { EntityPos = new EntityPos(x, y, z), BlockPos = blockPos, Normal = new BlockOffset(0, 0, -sdz) }; } } else { // Shouldn't happen. throw new Exception(); } } return null; }