v0.1
This commit is contained in:
91
Subsurface/Source/Map/Entity.cs
Normal file
91
Subsurface/Source/Map/Entity.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Subsurface.Networking;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Entity
|
||||
{
|
||||
private static Dictionary<int, Entity> dictionary = new Dictionary<int, Entity>();
|
||||
|
||||
private int id;
|
||||
|
||||
protected AITarget aiTarget;
|
||||
//protected float soundRange;
|
||||
//protected float sightRange;
|
||||
|
||||
public int ID
|
||||
{
|
||||
get { return id; }
|
||||
set
|
||||
{
|
||||
Entity thisEntity;
|
||||
if (dictionary.TryGetValue(id, out thisEntity) && thisEntity == this)
|
||||
{
|
||||
dictionary.Remove(id);
|
||||
}
|
||||
//if there's already an entity with the same ID, give it the old ID of this one
|
||||
Entity existingEntity;
|
||||
if (dictionary.TryGetValue(value, out existingEntity))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(existingEntity+" had the same ID as "+this);
|
||||
dictionary.Remove(value);
|
||||
dictionary.Add(id, existingEntity);
|
||||
existingEntity.id = id;
|
||||
}
|
||||
|
||||
id = value;
|
||||
dictionary.Add(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Vector2 SimPosition
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public Entity()
|
||||
{
|
||||
//give an unique ID
|
||||
bool IDfound;
|
||||
id = 1;//Rand.Int(int.MaxValue);
|
||||
do
|
||||
{
|
||||
id += 1;
|
||||
IDfound = dictionary.ContainsKey(id);
|
||||
} while (IDfound);
|
||||
|
||||
dictionary.Add(id, this);
|
||||
}
|
||||
|
||||
public virtual void FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data) { }
|
||||
public virtual void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Find an entity based on the ID
|
||||
/// </summary>
|
||||
public static Entity FindEntityByID(int ID)
|
||||
{
|
||||
Entity matchingEntity;
|
||||
dictionary.TryGetValue(ID, out matchingEntity);
|
||||
|
||||
return matchingEntity;
|
||||
}
|
||||
|
||||
public static void RemoveAll()
|
||||
{
|
||||
List<Entity> list = new List<Entity>(dictionary.Values);
|
||||
foreach (Entity e in list)
|
||||
{
|
||||
e.Remove();
|
||||
}
|
||||
dictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual void Remove()
|
||||
{
|
||||
dictionary.Remove(ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Subsurface/Source/Map/Explosion.cs
Normal file
140
Subsurface/Source/Map/Explosion.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Subsurface.Lights;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Explosion
|
||||
{
|
||||
private Vector2 position;
|
||||
|
||||
private float range;
|
||||
private float damage;
|
||||
private float structureDamage;
|
||||
private float stun;
|
||||
|
||||
private float force;
|
||||
|
||||
private LightSource light;
|
||||
|
||||
public float CameraShake;
|
||||
|
||||
public Explosion(Vector2 position, float range, float damage, float structureDamage, float stun = 0.0f, float force = 0.0f)
|
||||
{
|
||||
this.position = position;
|
||||
this.range = Math.Max(range, 1.0f);
|
||||
this.damage = damage;
|
||||
this.structureDamage = structureDamage;
|
||||
this.stun = stun;
|
||||
this.force = force;
|
||||
|
||||
CameraShake = range*10.0f;
|
||||
}
|
||||
|
||||
public Explosion(XElement element)
|
||||
{
|
||||
range = Math.Max(ToolBox.GetAttributeFloat(element, "range", 1.0f), 1.0f);
|
||||
damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f);
|
||||
structureDamage = ToolBox.GetAttributeFloat(element, "structuredamage", 0.0f);
|
||||
stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f);
|
||||
|
||||
force = ToolBox.GetAttributeFloat(element, "force", 0.0f);
|
||||
}
|
||||
|
||||
public void Explode()
|
||||
{
|
||||
Explode(position);
|
||||
}
|
||||
|
||||
public void Explode(Vector2 simPosition)
|
||||
{
|
||||
for (int i = 0; i < range * 10; i++)
|
||||
{
|
||||
Game1.ParticleManager.CreateParticle("explosionfire", simPosition,
|
||||
Rand.Vector(Rand.Range(3.0f, 4.0f)), 0.0f);
|
||||
}
|
||||
|
||||
|
||||
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
|
||||
float displayRange = ConvertUnits.ToDisplayUnits(range);
|
||||
|
||||
light = new LightSource(displayPosition, displayRange, Color.LightYellow);
|
||||
CoroutineManager.StartCoroutine(DimLight());
|
||||
|
||||
float cameraDist = Vector2.Distance(Game1.GameScreen.Cam.Position, displayPosition)/2.0f;
|
||||
Game1.GameScreen.Cam.Shake = CameraShake * Math.Max((displayRange - cameraDist)/displayRange, 0.0f);
|
||||
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
List<Structure> structureList = new List<Structure>();
|
||||
|
||||
float dist = 600.0f;
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
{
|
||||
Structure structure = entity as Structure;
|
||||
if (structure == null) continue;
|
||||
|
||||
if (structure.HasBody &&
|
||||
!structure.IsPlatform &&
|
||||
Vector2.Distance(structure.Position, displayPosition) < dist*3.0f)
|
||||
{
|
||||
structureList.Add(structure);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Structure structure in structureList)
|
||||
{
|
||||
for (int i = 0; i < structure.SectionCount; i++)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i), displayPosition) / displayRange);
|
||||
if (distFactor > 0.0f) structure.AddDamage(i, structureDamage*distFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
float dist = Vector2.Distance(c.SimPosition, simPosition);
|
||||
|
||||
if (dist > range) continue;
|
||||
|
||||
float distFactor = 1.0f - dist / range;
|
||||
|
||||
foreach (Limb limb in c.AnimController.limbs)
|
||||
{
|
||||
distFactor = 1.0f - Vector2.Distance(limb.SimPosition, simPosition)/range;
|
||||
|
||||
c.AddDamage(limb.SimPosition, DamageType.None, damage / c.AnimController.limbs.Length * distFactor, 0.0f, stun * distFactor);
|
||||
|
||||
if (force>0.0f)
|
||||
{
|
||||
limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.SimPosition - simPosition) * distFactor * force);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Status> DimLight()
|
||||
{
|
||||
float currBrightness= 1.0f;
|
||||
float startRange = light.Range;
|
||||
|
||||
while (light.Color.A > 0.0f)
|
||||
{
|
||||
light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, currBrightness);
|
||||
light.Range = startRange * currBrightness;
|
||||
|
||||
currBrightness -= 0.1f;
|
||||
|
||||
yield return Status.Running;
|
||||
}
|
||||
|
||||
light.Remove();
|
||||
|
||||
yield return Status.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
588
Subsurface/Source/Map/Gap.cs
Normal file
588
Subsurface/Source/Map/Gap.cs
Normal file
@@ -0,0 +1,588 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Gap : MapEntity
|
||||
{
|
||||
public bool isHorizontal;
|
||||
|
||||
//private Sound waterSound;
|
||||
|
||||
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
|
||||
float open;
|
||||
|
||||
//the force of the water flow which is exerted on physics bodies
|
||||
Vector2 flowForce;
|
||||
|
||||
Hull flowTargetHull;
|
||||
|
||||
float higherSurface;
|
||||
float lowerSurface;
|
||||
|
||||
private int soundIndex;
|
||||
|
||||
float soundVolume;
|
||||
|
||||
public float Open
|
||||
{
|
||||
get { return open; }
|
||||
set { open = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public Vector2 FlowForce
|
||||
{
|
||||
get { return flowForce*soundVolume; }
|
||||
}
|
||||
|
||||
public Hull FlowTargetHull
|
||||
{
|
||||
get { return flowTargetHull; }
|
||||
}
|
||||
|
||||
public Gap(Rectangle newRect)
|
||||
{
|
||||
rect = newRect;
|
||||
linkedTo = new ObservableCollection<MapEntity>();
|
||||
|
||||
//waterSound = new Sound("waterstream", 0.0f);
|
||||
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
isHorizontal = (rect.Width < rect.Height);
|
||||
|
||||
open = 1.0f;
|
||||
|
||||
FindHulls();
|
||||
|
||||
mapEntityList.Add(this);
|
||||
}
|
||||
|
||||
public Gap(Rectangle newRect, bool isHorizontal)
|
||||
{
|
||||
rect = newRect;
|
||||
linkedTo = new ObservableCollection<MapEntity>();
|
||||
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
this.isHorizontal = isHorizontal;
|
||||
|
||||
open = 1.0f;
|
||||
|
||||
FindHulls();
|
||||
|
||||
mapEntityList.Add(this);
|
||||
}
|
||||
|
||||
public static void UpdateHulls()
|
||||
{
|
||||
foreach (MapEntity entity in mapEntityList)
|
||||
{
|
||||
Gap g = entity as Gap;
|
||||
if (g != null) g.FindHulls();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Contains(Vector2 position)
|
||||
{
|
||||
return (Submarine.RectContains(rect, position) &&
|
||||
!Submarine.RectContains(new Rectangle(rect.X + 4, rect.Y - 4, rect.Width - 8, rect.Height - 8), position));
|
||||
}
|
||||
|
||||
private void FindHulls()
|
||||
{
|
||||
Hull[] hulls = new Hull[2];
|
||||
|
||||
linkedTo.Clear();
|
||||
|
||||
foreach (Hull h in Hull.hullList)
|
||||
{
|
||||
if (!Submarine.RectsOverlap(h.Rect, rect, false)) continue;
|
||||
|
||||
//if the gap is inside the hull completely, ignore it
|
||||
if (rect.X > h.Rect.X && rect.X + rect.Width < h.Rect.X+h.Rect.Width &&
|
||||
rect.Y < h.Rect.Y && rect.Y - rect.Height > h.Rect.Y - h.Rect.Height) continue;
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
if (hulls[i] != null) continue;
|
||||
hulls[i] = h;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hulls[1] != null) break;
|
||||
}
|
||||
|
||||
if (hulls[0] == null && hulls[1] == null) return;
|
||||
|
||||
if (hulls[0]!=null && hulls[1]!=null)
|
||||
{
|
||||
if ((isHorizontal && hulls[0].Rect.X > hulls[1].Rect.X) || (!isHorizontal && hulls[0].Rect.Y < hulls[1].Rect.Y))
|
||||
{
|
||||
//make sure that hull1 is the lefthand room if the gap is horizontal,
|
||||
//or that hull1 is the upper hull if the gap is vertical
|
||||
|
||||
Hull temp = hulls[0];
|
||||
hulls[0] = hulls[1];
|
||||
hulls[1] = temp;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
linkedTo.Add(hulls[0]);
|
||||
if (hulls[1] != null) linkedTo.Add(hulls[1]);
|
||||
|
||||
//if (hull1 != null && hull2 != null)
|
||||
//{
|
||||
// if (isHorizontal)
|
||||
// {
|
||||
// //make sure that water1 is the lefthand room
|
||||
// //or that water2 is null if the gap doesn't lead to another room
|
||||
// if (hull1.Rect.X < hull2.Rect.X)
|
||||
// {
|
||||
// linkedTo.Add(hull1);
|
||||
// linkedTo.Add(hull2);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// linkedTo.Add(hull2);
|
||||
// linkedTo.Add(hull1);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //make sure that water1 is the room on the top
|
||||
// //or that water2 is null if the gap doesn't lead to another room
|
||||
// if (hull1.Rect.Y > hull2.Rect.Y)
|
||||
// {
|
||||
// linkedTo.Add(hull1);
|
||||
// linkedTo.Add(hull2);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// linkedTo.Add(hull2);
|
||||
// linkedTo.Add(hull1);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// linkedTo.Add(hull1);
|
||||
//}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch sb, bool editing)
|
||||
{
|
||||
//if (linkedTo[0] != null)
|
||||
// GUI.DrawLine(sb, new Vector2(Position.X, Position.Y),
|
||||
// new Vector2(linkedTo[0].Position.X, linkedTo[0].Position.Y), Color.Blue);
|
||||
|
||||
//if (linkedTo.Count > 1 && linkedTo[1] != null)
|
||||
// GUI.DrawLine(sb, new Vector2(Position.X, Position.Y),
|
||||
// new Vector2(linkedTo[1].Position.X, linkedTo[1].Position.Y), Color.Blue);
|
||||
|
||||
|
||||
//GUI.DrawLine(sb, new Vector2(Position.X, -Position.Y), new Vector2(Position.X, -Position.Y)+new Vector2(flowForce.X, -flowForce.Y), Color.LightBlue);
|
||||
|
||||
if (!editing) return;
|
||||
|
||||
Color clr = (open == 0.0f) ? Color.Red : Color.Cyan;
|
||||
|
||||
GUI.DrawRectangle(sb, new Rectangle(rect.X, -rect.Y, rect.Width, rect.Height), clr);
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
GUI.DrawRectangle(sb,
|
||||
new Vector2(rect.X - 5, -rect.Y - 5),
|
||||
new Vector2(rect.Width + 10, rect.Height + 10),
|
||||
Color.Red);
|
||||
}
|
||||
|
||||
//HUD.DrawLine(sb, new Vector2(position.X, -position.Y),
|
||||
// isHorizontal ? new Vector2(position.X, -position.Y + size) : new Vector2(position.X + size, -position.Y),
|
||||
// clr);
|
||||
}
|
||||
|
||||
public override void Update(Camera cam, float deltaTime)
|
||||
{
|
||||
|
||||
soundVolume = soundVolume + ((flowForce.Length() < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f);
|
||||
soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f);
|
||||
|
||||
int index = (int)Math.Floor(flowForce.Length() / 100.0f);
|
||||
index = Math.Min(index,2);
|
||||
|
||||
soundIndex = AmbientSoundManager.flowSounds[index].Loop(soundIndex, soundVolume, Position, 2000.0f);
|
||||
//soundVolume = Math.Max(0.0f, soundVolume-deltaTime);
|
||||
//Sound.UpdatePosition(soundIndex, Position, 2000.0f);
|
||||
|
||||
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
if (open == 0.0f) return;
|
||||
|
||||
UpdateOxygen();
|
||||
|
||||
|
||||
if (linkedTo.Count == 1)
|
||||
{
|
||||
//gap leading from a room to outside
|
||||
UpdateRoomToOut(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//gap leading from a room to another
|
||||
UpdateRoomToRoom(deltaTime);
|
||||
}
|
||||
|
||||
if (FlowForce.Length() > 150.0f && flowTargetHull!=null && flowTargetHull.Volume < flowTargetHull.FullVolume)
|
||||
{
|
||||
//UpdateFlowForce();
|
||||
|
||||
Vector2 pos = SimPosition;
|
||||
if (isHorizontal)
|
||||
{
|
||||
pos.Y = ConvertUnits.ToSimUnits(MathHelper.Clamp(lowerSurface, rect.Y-rect.Height, rect.Y));
|
||||
|
||||
Game1.ParticleManager.CreateParticle("watersplash",
|
||||
new Vector2(pos.X, pos.Y - Rand.Range(0.0f, 0.1f)),
|
||||
new Vector2(flowForce.X * Rand.Range(0.005f, 0.007f), flowForce.Y * Rand.Range(0.005f, 0.007f)));
|
||||
|
||||
pos.Y = ConvertUnits.ToSimUnits(Rand.Range(lowerSurface, rect.Y - rect.Height));
|
||||
Game1.ParticleManager.CreateParticle("bubbles", pos, flowForce / 200.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos.Y += Math.Sign(flowForce.Y) * ConvertUnits.ToSimUnits(rect.Height / 2.0f);
|
||||
for (int i = 0; i < rect.Width; i += (int)Rand.Range(80, 100))
|
||||
{
|
||||
pos.X = ConvertUnits.ToSimUnits(Rand.Range(rect.X, rect.X+rect.Width));
|
||||
Subsurface.Particles.Particle splash = Game1.ParticleManager.CreateParticle("watersplash", pos,
|
||||
new Vector2(flowForce.X * Rand.Range(0.005f, 0.008f), flowForce.Y * Rand.Range(0.005f, 0.008f)));
|
||||
|
||||
if (splash!=null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f);
|
||||
|
||||
Game1.ParticleManager.CreateParticle("bubbles", pos, flowForce / 200.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void UpdateRoomToRoom(float deltaTime)
|
||||
{
|
||||
if (linkedTo.Count < 2) return;
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
Hull hull2 = (Hull)linkedTo[1];
|
||||
|
||||
if (hull1.Volume == 0.0 && hull2.Volume == 0.0) return;
|
||||
|
||||
float size = (isHorizontal) ? rect.Height : rect.Width;
|
||||
|
||||
//a variable affecting the water flow through the gap
|
||||
//the larger the gap is, the faster the water flows
|
||||
float sizeModifier = size / 100.0f * open;
|
||||
|
||||
//horizontal gap (such as a regular door)
|
||||
if (isHorizontal)
|
||||
{
|
||||
//higherSurface = Math.Min(hull1.Surface,hull2.Surface);
|
||||
float delta=0.0f;
|
||||
//water level is above the lower boundary of the gap
|
||||
if (Math.Max(hull1.Surface+hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface+hull2.WaveY[0]) > rect.Y - size)
|
||||
{
|
||||
|
||||
int dir = (hull1.Pressure > hull2.Pressure) ? 1 : -1;
|
||||
|
||||
//water flowing from the righthand room to the lefthand room
|
||||
if (dir == -1)
|
||||
{
|
||||
if (!(hull2.Volume > 0.0f)) return;
|
||||
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
|
||||
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
|
||||
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
//make sure not to move more than what the room contains
|
||||
delta = Math.Min((hull2.Pressure - hull1.Pressure) * sizeModifier, Math.Min(hull2.Volume, hull2.FullVolume));
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - (hull1.Volume));
|
||||
hull1.Volume += delta;
|
||||
hull2.Volume -= delta;
|
||||
if (hull1.Volume > hull1.FullVolume)
|
||||
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure) / 2);
|
||||
|
||||
flowForce = new Vector2(-delta, 0.0f);
|
||||
}
|
||||
else if (dir == 1)
|
||||
{
|
||||
if (!(hull1.Volume > 0.0f)) return;
|
||||
lowerSurface = hull2.Surface - hull2.WaveY[1];
|
||||
|
||||
flowTargetHull = hull2;
|
||||
|
||||
//make sure not to move more than what the room contains
|
||||
delta = Math.Min((hull1.Pressure - hull2.Pressure) * sizeModifier, Math.Min(hull1.Volume, hull1.FullVolume));
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull2.FullVolume + Hull.MaxCompress - (hull2.Volume));
|
||||
hull1.Volume -= delta;
|
||||
hull2.Volume += delta;
|
||||
if (hull2.Volume > hull2.FullVolume)
|
||||
hull2.Pressure = Math.Max(hull2.Pressure, (hull1.Pressure + hull2.Pressure) / 2);
|
||||
|
||||
flowForce = new Vector2(delta, 0.0f);
|
||||
}
|
||||
|
||||
if (delta>100.0f)
|
||||
{
|
||||
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
|
||||
//float avgVel = (hull2.WaveVel[1] + hull1.WaveVel[hull1.WaveY.Length - 2]) / 2.0f;
|
||||
|
||||
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
|
||||
hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
|
||||
{
|
||||
hull1.WaveVel[hull1.WaveY.Length - 1] = (avg-(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1]))*0.1f;
|
||||
hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
|
||||
}
|
||||
|
||||
if (hull2.Volume < hull2.FullVolume - Hull.MaxCompress &&
|
||||
hull2.Surface + hull2.WaveY[0] < rect.Y)
|
||||
{
|
||||
hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
|
||||
hull2.WaveVel[1] = hull2.WaveVel[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//lower room is full of water
|
||||
if (hull2.Pressure > hull1.Pressure)
|
||||
{
|
||||
float delta = Math.Min(hull2.Volume - hull2.FullVolume + Hull.MaxCompress / 2.0f, deltaTime * 8000.0f * sizeModifier);
|
||||
|
||||
flowForce = new Vector2(0.0f, Math.Min(hull2.Pressure - hull1.Pressure, 500.0f));
|
||||
|
||||
delta = Math.Max(delta, 0.0f);
|
||||
hull1.Volume += delta;
|
||||
hull2.Volume -= delta;
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
//delta = (water2.Pressure - water1.Pressure) * 0.1f;
|
||||
//if (delta > 0.1f)
|
||||
//{
|
||||
// int posX = (int)((rect.X + size / 2.0f - water1.Rect.X) / Hull.WaveWidth);
|
||||
// //water1.WaveY[posX] = delta;
|
||||
// water1.WaveVel[posX] = delta * 0.01f;
|
||||
//}
|
||||
|
||||
if (hull1.Volume > hull1.FullVolume)
|
||||
{
|
||||
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure) / 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
//there's water in the upper room, drop to lower
|
||||
else if (hull1.Volume > 0)
|
||||
{
|
||||
flowTargetHull = hull2;
|
||||
|
||||
//make sure the amount of water moved isn't more than what the room contains
|
||||
float delta = Math.Min(hull1.Volume, deltaTime * 10000f * sizeModifier);
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, (hull2.FullVolume + Math.Max(hull1.Volume - hull1.FullVolume, 0.0f)) - hull2.Volume + Hull.MaxCompress / 4.0f);
|
||||
|
||||
hull1.Volume -= delta;
|
||||
hull2.Volume += delta;
|
||||
|
||||
if (hull2.Volume > hull2.FullVolume)
|
||||
{
|
||||
hull2.Pressure = Math.Max(hull2.Pressure, (hull1.Pressure + hull2.Pressure) / 2);
|
||||
}
|
||||
|
||||
flowForce = new Vector2(0.0f,-delta);
|
||||
|
||||
//if (water2.Volume < water2.FullVolume - Hull.MaxCompress)
|
||||
//{
|
||||
// int posX = (int)((rect.X + size / 2.0f - water1.Rect.X) / Hull.WaveWidth);
|
||||
// //water1.WaveY[posX] = -delta;
|
||||
// if (posX > -1 && posX < water2.WaveVel.Length)
|
||||
// water1.WaveVel[posX] = -delta * 0.01f;
|
||||
|
||||
// posX = (int)((rect.X + size / 2.0f - water2.Rect.X) / Hull.WaveWidth);
|
||||
// //water2.WaveY[posX] = delta;
|
||||
// if (posX > -1 && posX<water2.WaveVel.Length)
|
||||
// water2.WaveVel[posX] = delta * 0.01f;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (open > 0.0f)
|
||||
{
|
||||
if (hull1.Volume>hull1.FullVolume && hull2.Volume>hull2.FullVolume)
|
||||
{
|
||||
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
|
||||
hull1.LethalPressure = avgLethality;
|
||||
hull2.LethalPressure = avgLethality;
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure = 0.0f;
|
||||
hull2.LethalPressure = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRoomToOut(float deltaTime)
|
||||
{
|
||||
if (linkedTo.Count != 1) return;
|
||||
|
||||
float size = (isHorizontal) ? rect.Height : rect.Width;
|
||||
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
|
||||
//a variable affecting the water flow through the gap
|
||||
//the larger the gap is, the faster the water flows
|
||||
float sizeModifier = size * open;
|
||||
|
||||
float delta = Hull.MaxCompress * sizeModifier * deltaTime;
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - hull1.Volume);
|
||||
hull1.Volume += delta;
|
||||
|
||||
if (hull1.Volume > hull1.FullVolume) hull1.Pressure += 0.5f;
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
//water flowing from right to left
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
flowForce = new Vector2(-delta, 0.0f);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = new Vector2(delta, 0.0f);
|
||||
}
|
||||
|
||||
higherSurface = hull1.Surface;
|
||||
lowerSurface = rect.Y;
|
||||
|
||||
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
|
||||
hull1.Surface > -rect.Y)
|
||||
{
|
||||
float vel = (rect.Y + hull1.Surface) * 0.03f;
|
||||
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
hull1.WaveVel[hull1.WaveY.Length - 1] += vel;
|
||||
hull1.WaveVel[hull1.WaveY.Length - 2] += vel;
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.WaveVel[0] += vel;
|
||||
hull1.WaveVel[1] += vel;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rect.Y > hull1.Rect.Y - hull1.Rect.Height / 2.0f)
|
||||
{
|
||||
flowForce = new Vector2(0.0f, -delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = new Vector2(0.0f, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOxygen()
|
||||
{
|
||||
if (linkedTo.Count < 2) return;
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
Hull hull2 = (Hull)linkedTo[1];
|
||||
|
||||
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
|
||||
float totalVolume = (hull1.FullVolume + hull2.FullVolume);
|
||||
|
||||
hull1.Oxygen += Math.Sign(totalOxygen * hull1.FullVolume / (totalVolume) - hull1.Oxygen) * Hull.OxygenDistributionSpeed;
|
||||
hull2.Oxygen += Math.Sign(totalOxygen * hull2.FullVolume / (totalVolume) - hull2.Oxygen) * Hull.OxygenDistributionSpeed;
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
if (soundIndex > -1) Sounds.SoundManager.Stop(soundIndex);
|
||||
}
|
||||
|
||||
public override XElement Save(XDocument doc)
|
||||
{
|
||||
XElement element = new XElement("Gap");
|
||||
|
||||
element.Add(new XAttribute("ID", ID),
|
||||
new XAttribute("x", rect.X),
|
||||
new XAttribute("y", rect.Y),
|
||||
new XAttribute("width", rect.Width),
|
||||
new XAttribute("height", rect.Height));
|
||||
|
||||
//if (linkedTo != null)
|
||||
//{
|
||||
// int i = 0;
|
||||
// foreach (Entity e in linkedTo)
|
||||
// {
|
||||
// if (e == null) continue;
|
||||
// element.Add(new XAttribute("linkedto" + i, e.ID));
|
||||
// i += 1;
|
||||
// }
|
||||
//}
|
||||
|
||||
doc.Root.Add(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
public static void Load(XElement element)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
|
||||
Gap g = new Gap(rect);
|
||||
g.ID = int.Parse(element.Attribute("ID").Value);
|
||||
|
||||
g.linkedToID = new List<int>();
|
||||
//int i = 0;
|
||||
//while (element.Attribute("linkedto" + i) != null)
|
||||
//{
|
||||
// g.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
|
||||
// i += 1;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
436
Subsurface/Source/Map/Hull.cs
Normal file
436
Subsurface/Source/Map/Hull.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
|
||||
class Hull : MapEntity
|
||||
{
|
||||
public static List<Hull> hullList = new List<Hull>();
|
||||
|
||||
public static bool EditWater;
|
||||
|
||||
public static WaterRenderer renderer;
|
||||
|
||||
public const float OxygenDistributionSpeed = 500.0f;
|
||||
public const float OxygenDetoriationSpeed = 0.3f;
|
||||
public const float OxygenConsumptionSpeed = 1000.0f;
|
||||
|
||||
public const int WaveWidth = 16;
|
||||
const float WaveStiffness = 0.003f;
|
||||
const float WaveSpread = 0.05f;
|
||||
const float WaveDampening = 0.01f;
|
||||
|
||||
//how much excess water the room can contain (= more than the volume of the room)
|
||||
public const float MaxCompress = 10000f;
|
||||
|
||||
public readonly Dictionary<string, PropertyDescriptor> properties;
|
||||
|
||||
private float lethalPressure;
|
||||
|
||||
private float surface;
|
||||
private float volume;
|
||||
private float pressure;
|
||||
|
||||
private float oxygen;
|
||||
|
||||
private bool update;
|
||||
|
||||
float[] waveY; //displacement from the surface of the water
|
||||
float[] waveVel; //velocity of the point
|
||||
|
||||
float[] leftDelta;
|
||||
float[] rightDelta;
|
||||
|
||||
public override bool IsLinkable
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public float LethalPressure
|
||||
{
|
||||
get { return lethalPressure; }
|
||||
set { lethalPressure = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return new Vector2(rect.Width, rect.Height); }
|
||||
}
|
||||
|
||||
public float Surface
|
||||
{
|
||||
get { return surface; }
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get { return volume; }
|
||||
set
|
||||
{
|
||||
volume = MathHelper.Clamp(value, 0.0f, FullVolume + MaxCompress);
|
||||
if (volume < FullVolume) Pressure = rect.Y - rect.Height + volume / rect.Width;
|
||||
if (volume > 0.0f) update = true;
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(90.0f, true)]
|
||||
public float Oxygen
|
||||
{
|
||||
get { return oxygen; }
|
||||
set { oxygen = MathHelper.Clamp(value, 0.0f, FullVolume); }
|
||||
}
|
||||
|
||||
public float OxygenPercentage
|
||||
{
|
||||
get { return oxygen / FullVolume * 100.0f; }
|
||||
set { Oxygen = (value / 100.0f) * FullVolume; }
|
||||
}
|
||||
|
||||
public float FullVolume
|
||||
{
|
||||
get { return (rect.Width * rect.Height); }
|
||||
}
|
||||
|
||||
public float Pressure
|
||||
{
|
||||
get { return pressure; }
|
||||
set { pressure = value; }
|
||||
}
|
||||
|
||||
public float[] WaveY
|
||||
{
|
||||
get { return waveY; }
|
||||
}
|
||||
|
||||
public float[] WaveVel
|
||||
{
|
||||
get { return waveVel; }
|
||||
}
|
||||
|
||||
public Hull(Rectangle rectangle)
|
||||
{
|
||||
rect = rectangle;
|
||||
|
||||
OxygenPercentage = Rand.Range(90.0f, 100.0f, false);
|
||||
|
||||
properties = TypeDescriptor.GetProperties(GetType())
|
||||
.Cast<PropertyDescriptor>()
|
||||
.ToDictionary(pr => pr.Name);
|
||||
|
||||
int arraySize = (rectangle.Width / WaveWidth + 1);
|
||||
waveY = new float[arraySize];
|
||||
waveVel = new float[arraySize];
|
||||
|
||||
leftDelta = new float[arraySize];
|
||||
rightDelta = new float[arraySize];
|
||||
|
||||
surface = rect.Y - rect.Height;
|
||||
|
||||
aiTarget = new AITarget(this);
|
||||
aiTarget.SightRange = (rect.Width + rect.Height)*10.0f;
|
||||
|
||||
hullList.Add(this);
|
||||
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
|
||||
Volume = 0.0f;
|
||||
|
||||
//add to list of entities as well
|
||||
mapEntityList.Add(this);
|
||||
}
|
||||
|
||||
public override bool Contains(Vector2 position)
|
||||
{
|
||||
return (Submarine.RectContains(rect, position) &&
|
||||
!Submarine.RectContains(new Rectangle(rect.X + 8, rect.Y - 8, rect.Width - 16, rect.Height - 16), position));
|
||||
}
|
||||
|
||||
public int GetWaveIndex(Vector2 position)
|
||||
{
|
||||
int index = (int)(position.X - rect.X) / WaveWidth;
|
||||
index = MathHelper.Clamp(index, 0, waveY.Length-1);
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
|
||||
//renderer.Dispose();
|
||||
|
||||
hullList.Remove(this);
|
||||
}
|
||||
|
||||
public override void Update(Camera cam, float deltaTime)
|
||||
{
|
||||
Oxygen -= OxygenDetoriationSpeed * deltaTime;
|
||||
|
||||
if (EditWater)
|
||||
{
|
||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (Submarine.RectContains(rect, position))
|
||||
{
|
||||
if (PlayerInput.LeftButtonDown())
|
||||
{
|
||||
waveY[(int)(position.X - rect.X) / WaveWidth] = 100.0f;
|
||||
Volume = Volume + 1500.0f;
|
||||
}
|
||||
else if (PlayerInput.GetMouseState.RightButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
|
||||
{
|
||||
Volume = Volume - 1500.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!update) return;
|
||||
|
||||
float surfaceY = rect.Y - rect.Height + Volume / rect.Width;
|
||||
for (int i = 0; i < waveY.Length; i++)
|
||||
{
|
||||
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
|
||||
if (maxDelta > Rand.Range(0.2f,10.0f))
|
||||
{
|
||||
Game1.ParticleManager.CreateParticle("mist",
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + WaveWidth * i,surface + waveY[i])),
|
||||
new Vector2(0.0f, -0.5f));
|
||||
}
|
||||
|
||||
waveY[i] = waveY[i] + waveVel[i];
|
||||
|
||||
if (surfaceY + waveY[i] > rect.Y)
|
||||
{
|
||||
waveY[i] -= (surfaceY + waveY[i]) - rect.Y;
|
||||
waveVel[i] = waveVel[i] * -0.5f;
|
||||
}
|
||||
else if (surfaceY + waveY[i] < rect.Y - rect.Height)
|
||||
{
|
||||
waveY[i] -= (surfaceY + waveY[i]) - (rect.Y - rect.Height);
|
||||
waveVel[i] = waveVel[i] * -0.5f;
|
||||
}
|
||||
|
||||
|
||||
//acceleration
|
||||
float a = -WaveStiffness * waveY[i] - waveVel[i] * WaveDampening;
|
||||
waveVel[i] = waveVel[i] + a;
|
||||
|
||||
}
|
||||
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
leftDelta[i] = WaveSpread * (waveY[i] - waveY[i - 1]);
|
||||
waveVel[i - 1] = waveVel[i - 1] + leftDelta[i];
|
||||
|
||||
rightDelta[i] = WaveSpread * (waveY[i] - waveY[i + 1]);
|
||||
waveVel[i + 1] = waveVel[i + 1] + rightDelta[i];
|
||||
}
|
||||
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
waveY[i - 1] = waveY[i - 1] + leftDelta[i];
|
||||
waveY[i + 1] = waveY[i + 1] + rightDelta[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (volume<FullVolume)
|
||||
{
|
||||
LethalPressure -= 0.5f;
|
||||
if (Volume == 0.0f)
|
||||
{
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
if (waveY[i] > 0.1f) return;
|
||||
}
|
||||
update = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LethalPressure += 1.0f;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch, bool editing)
|
||||
{
|
||||
if (!editing && !Game1.DebugDraw) return;
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(rect.X, -rect.Y),
|
||||
new Vector2(rect.Width, rect.Height),
|
||||
isHighlighted ? Color.Green : Color.Blue);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle(rect.X, -rect.Y, rect.Width, rect.Height),
|
||||
Color.Red*((100.0f-OxygenPercentage)/400.0f), true);
|
||||
|
||||
spriteBatch.DrawString(GUI.Font, "Pressure: " + ((int)pressure - rect.Y).ToString() +
|
||||
" - Lethality: " + lethalPressure +
|
||||
" - Oxygen: "+((int)OxygenPercentage), new Vector2(rect.X+10, -rect.Y+10), Color.Black);
|
||||
spriteBatch.DrawString(GUI.Font, volume +" / "+ FullVolume, new Vector2(rect.X+10, -rect.Y+30), Color.Black);
|
||||
|
||||
if (isSelected && editing)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(rect.X - 5, -rect.Y - 5),
|
||||
new Vector2(rect.Width + 10, rect.Height + 10),
|
||||
Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(GraphicsDevice graphicsDevice, Camera cam)
|
||||
{
|
||||
if (renderer.positionInBuffer > renderer.vertices.Length - 6) return;
|
||||
|
||||
//calculate where the surface should be based on the water volume
|
||||
float top = rect.Y;
|
||||
float bottom = rect.Y - rect.Height;
|
||||
float surfaceY = bottom + Volume / rect.Width;
|
||||
|
||||
//interpolate the position of the rendered surface towards the "target surface"
|
||||
surface = surface + (surfaceY - surface) / 10.0f;
|
||||
|
||||
Matrix transform =
|
||||
cam.Transform
|
||||
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
|
||||
|
||||
if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) return;
|
||||
|
||||
if (!update)
|
||||
{
|
||||
// create the four corners of our triangle.
|
||||
Vector3 p1 = new Vector3(rect.X, top, 0.0f);
|
||||
Vector3 p2 = new Vector3(rect.X + rect.Width, top, 0.0f);
|
||||
|
||||
Vector3 p3 = new Vector3(p2.X, bottom, 0.0f);
|
||||
Vector3 p4 = new Vector3(p1.X, bottom, 0.0f);
|
||||
|
||||
renderer.vertices[renderer.positionInBuffer] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 1] = new WaterVertex(p2, new Vector2(p2.X, -p2.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 2] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
|
||||
|
||||
renderer.vertices[renderer.positionInBuffer + 3] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 4] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 5] = new WaterVertex(p4, new Vector2(p4.X, -p4.Y), transform);
|
||||
|
||||
renderer.positionInBuffer += 6;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int x = rect.X;
|
||||
int start = (int)Math.Floor((float)(cam.WorldView.X - x) / WaveWidth);
|
||||
start = Math.Max(start, 0);
|
||||
|
||||
int end = (waveY.Length - 1)
|
||||
- (int)Math.Floor((float)((x + rect.Width) - (cam.WorldView.X + cam.WorldView.Width)) / WaveWidth);
|
||||
end = Math.Min(end, waveY.Length - 1);
|
||||
|
||||
x += start * WaveWidth;
|
||||
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
if (renderer.positionInBuffer > renderer.vertices.Length - 6) return;
|
||||
|
||||
Vector3 p1 = new Vector3(x, top, 0.0f);
|
||||
Vector3 p4 = new Vector3(p1.X, surface + waveY[i], 0.0f);
|
||||
|
||||
//skip adjacent "water rects" if the surface of the water is roughly at the same position
|
||||
int width = WaveWidth;
|
||||
while (i<end-1 && Math.Abs(waveY[i + 1] - waveY[i]) < 1.0f)
|
||||
{
|
||||
width += WaveWidth;
|
||||
i++;
|
||||
}
|
||||
|
||||
Vector3 p2 = new Vector3(x + width, top, 0.0f);
|
||||
Vector3 p3 = new Vector3(p2.X, surface + waveY[i+1], 0.0f);
|
||||
|
||||
renderer.vertices[renderer.positionInBuffer] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 1] = new WaterVertex(p2, new Vector2(p2.X, -p2.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 2] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
|
||||
|
||||
renderer.vertices[renderer.positionInBuffer + 3] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 4] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
|
||||
renderer.vertices[renderer.positionInBuffer + 5] = new WaterVertex(p4, new Vector2(p4.X, -p4.Y), transform);
|
||||
|
||||
renderer.positionInBuffer += 6;
|
||||
|
||||
x += width;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//returns the water block which contains the point (or null if it isn't inside any)
|
||||
public static Hull FindHull(Vector2 position, Hull guess = null)
|
||||
{
|
||||
if (guess != null && hullList.Contains(guess))
|
||||
{
|
||||
if (Submarine.RectContains(guess.rect, position)) return guess;
|
||||
}
|
||||
|
||||
foreach (Hull w in hullList)
|
||||
{
|
||||
if (Submarine.RectContains(w.rect, position)) return w;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override XElement Save(XDocument doc)
|
||||
{
|
||||
XElement element = new XElement("Hull");
|
||||
|
||||
element.Add(new XAttribute("ID", ID),
|
||||
new XAttribute("x", rect.X),
|
||||
new XAttribute("y", rect.Y),
|
||||
new XAttribute("width", rect.Width),
|
||||
new XAttribute("height", rect.Height),
|
||||
new XAttribute("water", volume));
|
||||
|
||||
doc.Root.Add(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public static void Load(XElement element)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
|
||||
Hull h = new Hull(rect);
|
||||
|
||||
h.volume = ToolBox.GetAttributeFloat(element, "pressure", 0.0f);
|
||||
|
||||
h.ID = int.Parse(element.Attribute("ID").Value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
19
Subsurface/Source/Map/IDamageable.cs
Normal file
19
Subsurface/Source/Map/IDamageable.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
interface IDamageable
|
||||
{
|
||||
Vector2 SimPosition
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
float Health
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound=true);
|
||||
}
|
||||
}
|
||||
959
Subsurface/Source/Map/Levels/Level.cs
Normal file
959
Subsurface/Source/Map/Levels/Level.cs
Normal file
@@ -0,0 +1,959 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Level
|
||||
{
|
||||
public static Level Loaded
|
||||
{
|
||||
get { return loaded; }
|
||||
}
|
||||
|
||||
static Level loaded;
|
||||
|
||||
private static Texture2D shaftTexture;
|
||||
|
||||
//how close the sub has to be to start/endposition to exit
|
||||
const float ExitDistance = 3000.0f;
|
||||
|
||||
private string seed;
|
||||
|
||||
private int siteInterval;
|
||||
|
||||
public const int GridCellWidth = 2000;
|
||||
private List<VoronoiCell>[,] cellGrid;
|
||||
|
||||
private float shaftHeight;
|
||||
|
||||
//List<Body> bodies;
|
||||
private List<VoronoiCell> cells;
|
||||
|
||||
private static BasicEffect basicEffect;
|
||||
|
||||
private VertexPositionTexture[] vertices;
|
||||
private VertexBuffer vertexBuffer;
|
||||
|
||||
private Vector2 startPosition;
|
||||
private Vector2 endPosition;
|
||||
|
||||
private Rectangle borders;
|
||||
|
||||
private List<Body> bodies;
|
||||
|
||||
private List<Vector2> positionsOfInterest;
|
||||
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
}
|
||||
|
||||
public bool AtStartPosition
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
}
|
||||
|
||||
public bool AtEndPosition
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(cells[0].body.Position); }
|
||||
}
|
||||
|
||||
public List<Vector2> PositionsOfInterest
|
||||
{
|
||||
get { return positionsOfInterest; }
|
||||
}
|
||||
|
||||
public string Seed
|
||||
{
|
||||
get { return seed; }
|
||||
}
|
||||
|
||||
public float Difficulty
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Level(string seed, float difficulty, int width, int height, int siteInterval)
|
||||
{
|
||||
if (shaftTexture == null) shaftTexture = Game1.TextureLoader.FromFile("Content/Map/shaft.png");
|
||||
|
||||
if (basicEffect==null)
|
||||
{
|
||||
|
||||
basicEffect = new BasicEffect(Game1.CurrGraphicsDevice);
|
||||
basicEffect.VertexColorEnabled = false;
|
||||
|
||||
basicEffect.TextureEnabled = true;
|
||||
basicEffect.Texture = Game1.TextureLoader.FromFile("Content/Map/iceSurface.png");
|
||||
}
|
||||
|
||||
this.seed = seed;
|
||||
|
||||
this.siteInterval = siteInterval;
|
||||
|
||||
this.Difficulty = difficulty;
|
||||
|
||||
positionsOfInterest = new List<Vector2>();
|
||||
|
||||
borders = new Rectangle(0, 0, width, height);
|
||||
}
|
||||
|
||||
public static Level CreateRandom(LocationConnection locationConnection)
|
||||
{
|
||||
int seed = locationConnection.Locations[0].GetHashCode() | locationConnection.Locations[1].GetHashCode();
|
||||
return new Level(seed.ToString(), locationConnection.Difficulty, 100000, 40000, 2000);
|
||||
}
|
||||
|
||||
public static Level CreateRandom(string seed = "")
|
||||
{
|
||||
if (seed == "")
|
||||
{
|
||||
seed = Rand.Range(0, int.MaxValue, false).ToString();
|
||||
}
|
||||
return new Level(seed, Rand.Range(30.0f,80.0f,false), 100000, 40000, 2000);
|
||||
}
|
||||
|
||||
public void Generate(float minWidth, bool mirror=false)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
if (loaded != null)
|
||||
{
|
||||
loaded.Unload();
|
||||
}
|
||||
|
||||
loaded = this;
|
||||
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
bodies = new List<Body>();
|
||||
|
||||
Random rand = new Random(seed.GetHashCode());
|
||||
|
||||
float siteVariance = siteInterval * 0.8f;
|
||||
for (int x = siteInterval / 2; x < borders.Width; x += siteInterval)
|
||||
{
|
||||
for (int y = siteInterval / 2; y < borders.Height; y += siteInterval)
|
||||
{
|
||||
Vector2 site = new Vector2(
|
||||
x + (float)(rand.NextDouble() - 0.5) * siteVariance,
|
||||
y + (float)(rand.NextDouble() - 0.5) * siteVariance);
|
||||
|
||||
if (mirror) site.X = borders.Width - site.X;
|
||||
|
||||
sites.Add(site);
|
||||
}
|
||||
}
|
||||
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height);
|
||||
|
||||
|
||||
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
cellGrid = new List<VoronoiCell>[borders.Width / GridCellWidth, borders.Height / GridCellWidth];
|
||||
for (int x = 0; x < borders.Width / GridCellWidth; x++)
|
||||
{
|
||||
for (int y = 0; y < borders.Height / GridCellWidth; y++)
|
||||
{
|
||||
cellGrid[x, y] = new List<VoronoiCell>();
|
||||
}
|
||||
}
|
||||
|
||||
//construct voronoi cells based on the graph edges
|
||||
cells = new List<VoronoiCell>();
|
||||
foreach (GraphEdge ge in graphEdges)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Site site = (i == 0) ? ge.site1 : ge.site2;
|
||||
|
||||
VoronoiCell cell = cellGrid[
|
||||
(int)Math.Floor(site.coord.x / GridCellWidth),
|
||||
(int)Math.Floor(site.coord.y / GridCellWidth)].Find(c => c.site == site);
|
||||
|
||||
if (cell == null)
|
||||
{
|
||||
cell = new VoronoiCell(site);
|
||||
cellGrid[(int)Math.Floor(cell.Center.X / GridCellWidth), (int)Math.Floor(cell.Center.Y / GridCellWidth)].Add(cell);
|
||||
cells.Add(cell);
|
||||
}
|
||||
|
||||
if (ge.cell1 == null)
|
||||
{
|
||||
ge.cell1 = cell;
|
||||
}
|
||||
else
|
||||
{
|
||||
ge.cell2 = cell;
|
||||
}
|
||||
cell.edges.Add(ge);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
//generate a path from the left edge of the map to right edge
|
||||
Rectangle pathBorders = new Rectangle(
|
||||
borders.X + (int)minWidth * 2, borders.Y + (int)minWidth * 2,
|
||||
borders.Right - (int)minWidth * 4, borders.Y + borders.Height - (int)minWidth * 4);
|
||||
|
||||
List<Vector2> pathNodes = new List<Vector2>();
|
||||
|
||||
startPosition = new Vector2((int)minWidth * 2, rand.Next((int)minWidth * 2, borders.Height - (int)minWidth * 2));
|
||||
endPosition = new Vector2(borders.Width - (int)minWidth * 2, rand.Next((int)minWidth * 2, borders.Height - (int)minWidth * 2));
|
||||
|
||||
pathNodes.Add(new Vector2(startPosition.X, borders.Height));
|
||||
pathNodes.Add(startPosition);
|
||||
pathNodes.Add(endPosition);
|
||||
pathNodes.Add(new Vector2(endPosition.X, borders.Height));
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
pathNodes.Reverse();
|
||||
}
|
||||
|
||||
List<VoronoiCell> pathCells = GeneratePath(rand,
|
||||
pathNodes, cells, pathBorders, minWidth, 0.3f, mirror, true);
|
||||
|
||||
//place some enemy spawnpoints at random points in the path
|
||||
for (int i = 0; i <3 ; i++ )
|
||||
{
|
||||
Vector2 position = pathCells[rand.Next((int)(pathCells.Count * 0.5f), pathCells.Count - 2)].Center;
|
||||
WayPoint wayPoint = new WayPoint(new Rectangle((int)position.X, (int)position.Y, 10, 10));
|
||||
wayPoint.MoveWithLevel = true;
|
||||
wayPoint.SpawnType = SpawnType.Enemy;
|
||||
}
|
||||
|
||||
startPosition = pathCells[0].Center;
|
||||
endPosition = pathCells[pathCells.Count - 1].Center;
|
||||
|
||||
//generate a couple of random paths
|
||||
for (int i = 0; i <= rand.Next() % 3; i++)
|
||||
{
|
||||
//pathBorders = new Rectangle(
|
||||
//borders.X + siteInterval * 2, borders.Y - siteInterval * 2,
|
||||
//borders.Right - siteInterval * 2, borders.Y + borders.Height - siteInterval * 2);
|
||||
|
||||
Vector2 start = pathCells[rand.Next(1, pathCells.Count - 2)].Center;
|
||||
|
||||
float x = pathBorders.X + (float)rand.NextDouble() * (pathBorders.Right - pathBorders.X);
|
||||
float y = pathBorders.Y + (float)rand.NextDouble() * (pathBorders.Bottom - pathBorders.Y);
|
||||
|
||||
if (mirror) x = borders.Width - x;
|
||||
|
||||
Vector2 end = new Vector2(x, y);
|
||||
|
||||
var newPathCells = GeneratePath(rand, new List<Vector2> { start, end }, cells, pathBorders, 0.0f, 0.8f, mirror);
|
||||
|
||||
for (int n = 0; n < newPathCells.Count-5; n += 3)
|
||||
{
|
||||
positionsOfInterest.Add(newPathCells[n].Center);
|
||||
}
|
||||
|
||||
pathCells.AddRange(newPathCells);
|
||||
}
|
||||
|
||||
Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
//for (int i = 0; i < 2; i++ )
|
||||
//{
|
||||
// Vector2 tunnelStart = (i == 0) ? startPosition : endPosition;
|
||||
|
||||
// pathCells.AddRange
|
||||
// (
|
||||
// GeneratePath(rand, tunnelStart, new Vector2(tunnelStart.X, borders.Height), cells, pathBorders, minWidth, 0.1f, mirror)
|
||||
// );
|
||||
//}
|
||||
|
||||
cells = CleanCells(pathCells);
|
||||
|
||||
foreach (VoronoiCell cell in pathCells)
|
||||
{
|
||||
cells.Remove(cell);
|
||||
}
|
||||
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
cellGrid[x, y].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
cellGrid[(int)Math.Floor(cell.Center.X / GridCellWidth), (int)Math.Floor(cell.Center.Y / GridCellWidth)].Add(cell);
|
||||
}
|
||||
|
||||
startPosition.Y = borders.Height;
|
||||
endPosition.Y = borders.Height;
|
||||
//for (int i = 0; i < 2; i++)
|
||||
//{
|
||||
// Vector2 tunnelStart = (i == 0) ? startPosition : endPosition;
|
||||
|
||||
// for (int n = -1; n < 2; n += 2)
|
||||
// {
|
||||
// int cellIndex = FindCellIndex(new Vector2(tunnelStart.X + minWidth * 0.5f * n, tunnelStart.Y), 3);
|
||||
|
||||
// foreach (GraphEdge ge in cells[cellIndex].edges)
|
||||
// {
|
||||
// if (ge.point1.Y > cells[cellIndex].Center.Y) ge.point1.Y = borders.Height + shaftHeight;
|
||||
// if (ge.point2.Y > cells[cellIndex].Center.Y) ge.point2.Y = borders.Height + shaftHeight;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//startPosition.Y += shaftHeight;
|
||||
//endPosition.Y += shaftHeight;
|
||||
|
||||
GeneratePolygons(cells, pathCells);
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
edge.cell1 = null;
|
||||
edge.cell2 = null;
|
||||
edge.site1 = null;
|
||||
edge.site2 = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
vertexBuffer = new VertexBuffer(Game1.CurrGraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
|
||||
vertexBuffer.SetData(vertices);
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
Vector2 temp = startPosition;
|
||||
startPosition = endPosition;
|
||||
endPosition = temp;
|
||||
}
|
||||
|
||||
Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GeneratePath(Random rand, List<Vector2> points, List<VoronoiCell> cells, Microsoft.Xna.Framework.Rectangle limits, float minWidth, float wanderAmount = 0.3f, bool mirror=false, bool placeWaypoints=false)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
//how heavily the path "steers" towards the endpoint
|
||||
//lower values will cause the path to "wander" more, higher will make it head straight to the end
|
||||
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell[] targetCells = new VoronoiCell[points.Count];
|
||||
for (int i = 0; i <targetCells.Length; i++)
|
||||
{
|
||||
targetCells[i]= cells[FindCellIndex(points[i])];
|
||||
}
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
int currentTargetIndex = 1;
|
||||
|
||||
|
||||
do
|
||||
{
|
||||
int edgeIndex = 0;
|
||||
|
||||
//steer towards target
|
||||
if (rand.NextDouble() > wanderAmount)
|
||||
{
|
||||
for (int i = 0; i < currentCell.edges.Count; i++)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(currentCell.Center, targetCells[currentTargetIndex].Center, currentCell.edges[i].point1, currentCell.edges[i].point2)) continue;
|
||||
edgeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//choose random edge (ignoring ones where the adjacent cell is outside limits)
|
||||
else
|
||||
{
|
||||
List<GraphEdge> allowedEdges = new List<GraphEdge>();
|
||||
|
||||
foreach (GraphEdge edge in currentCell.edges)
|
||||
{
|
||||
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
|
||||
|
||||
allowedEdges.Add(edge);
|
||||
}
|
||||
if (allowedEdges.Count==0)
|
||||
{
|
||||
edgeIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeIndex = rand.Next() % allowedEdges.Count;
|
||||
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
|
||||
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
if (currentCell==targetCells[currentTargetIndex])
|
||||
{
|
||||
currentTargetIndex += 1;
|
||||
if (currentTargetIndex>=targetCells.Length) break;
|
||||
}
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Length-1]);
|
||||
|
||||
if (placeWaypoints)
|
||||
{
|
||||
WayPoint newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, (int)(borders.Height + shaftHeight), 10, 10));
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
|
||||
WayPoint prevWaypoint = newWaypoint;
|
||||
|
||||
for (int i = 0; i < pathCells.Count; i++)
|
||||
{
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10));
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
if (prevWaypoint != null)
|
||||
{
|
||||
prevWaypoint.linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(prevWaypoint);
|
||||
}
|
||||
prevWaypoint = newWaypoint;
|
||||
}
|
||||
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, (int)(borders.Height + shaftHeight), 10, 10));
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
|
||||
if (prevWaypoint != null)
|
||||
{
|
||||
prevWaypoint.linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(prevWaypoint);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine("genpath: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
|
||||
foreach (VoronoiCell removedCell in removedCells)
|
||||
{
|
||||
if (pathCells.Contains(removedCell)) continue;
|
||||
pathCells.Add(removedCell);
|
||||
}
|
||||
|
||||
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
Vector2 position = emptyCells[0].Center;
|
||||
|
||||
if (minDistance == 0.0f) return tooCloseCells;
|
||||
|
||||
float step = 100.0f;
|
||||
|
||||
int targetCellIndex = 1;
|
||||
|
||||
minDistance *= 0.5f;
|
||||
do
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
if (x == 0 && y == 0) continue;
|
||||
Vector2 cornerPos = position + new Vector2(x * minDistance, y * minDistance);
|
||||
|
||||
int cellIndex = FindCellIndex(cornerPos);
|
||||
if (cellIndex == -1) continue;
|
||||
if (!tooCloseCells.Contains(cells[cellIndex]))
|
||||
{
|
||||
tooCloseCells.Add(cells[cellIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
|
||||
|
||||
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
|
||||
|
||||
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// remove all cells except those that are adjacent to the empty cells
|
||||
/// </summary>
|
||||
private List<VoronoiCell> CleanCells(List<VoronoiCell> emptyCells)
|
||||
{
|
||||
List<VoronoiCell> newCells = new List<VoronoiCell>();
|
||||
|
||||
foreach (VoronoiCell cell in emptyCells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
VoronoiCell adjacent = edge.AdjacentCell(cell);
|
||||
if (!newCells.Contains(adjacent)) newCells.Add(adjacent);
|
||||
}
|
||||
}
|
||||
|
||||
return newCells;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find the index of the cell which the point is inside
|
||||
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
|
||||
/// </summary>
|
||||
private int FindCellIndex(Vector2 position, int searchDepth = 1)
|
||||
{
|
||||
float closestDist = 0.0f;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
int gridPosX = (int)Math.Floor(position.X / GridCellWidth);
|
||||
int gridPosY = (int)Math.Floor(position.Y / GridCellWidth);
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
float dist = Vector2.Distance(cellGrid[x, y][i].Center, position);
|
||||
if (closestDist != 0.0f && dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
|
||||
private void GeneratePolygons(List<VoronoiCell> cells, List<VoronoiCell> emptyCells)
|
||||
{
|
||||
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
|
||||
//bodies = new List<Body>();
|
||||
|
||||
List<Vector2> tempVertices = new List<Vector2>();
|
||||
List<Vector2> bodyPoints = new List<Vector2>();
|
||||
|
||||
for (int n = cells.Count - 1; n >= 0; n-- )
|
||||
{
|
||||
VoronoiCell cell = cells[n];
|
||||
|
||||
bodyPoints.Clear();
|
||||
tempVertices.Clear();
|
||||
foreach (GraphEdge ge in cell.edges)
|
||||
{
|
||||
if (ge.point1 == ge.point2) continue;
|
||||
if (!tempVertices.Contains(ge.point1)) tempVertices.Add(ge.point1);
|
||||
if (!tempVertices.Contains(ge.point2)) tempVertices.Add(ge.point2);
|
||||
|
||||
VoronoiCell adjacentCell = ge.AdjacentCell(cell);
|
||||
if (!emptyCells.Contains(adjacentCell)) continue;
|
||||
|
||||
ge.isSolid = true;
|
||||
|
||||
if (!bodyPoints.Contains(ge.point1)) bodyPoints.Add(ge.point1);
|
||||
if (!bodyPoints.Contains(ge.point2)) bodyPoints.Add(ge.point2);
|
||||
}
|
||||
|
||||
if (tempVertices.Count < 3 || bodyPoints.Count < 2)
|
||||
{
|
||||
cells.RemoveAt(n);
|
||||
continue;
|
||||
}
|
||||
|
||||
var triangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
foreach (Vector2 vertex in triangles[i])
|
||||
{
|
||||
verticeList.Add(new VertexPositionTexture(new Vector3(vertex, 0.0f), vertex/1000.0f));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (bodyPoints.Count < 2) continue;
|
||||
|
||||
if (bodyPoints.Count < 3)
|
||||
{
|
||||
foreach (Vector2 vertex in tempVertices)
|
||||
{
|
||||
if (bodyPoints.Contains(vertex)) continue;
|
||||
bodyPoints.Add(vertex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bodyPoints.Count; i++)
|
||||
{
|
||||
cell.bodyVertices.Add(bodyPoints[i]);
|
||||
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
|
||||
}
|
||||
|
||||
triangles = MathUtils.TriangulateConvexHull(bodyPoints, cell.Center);
|
||||
|
||||
Body edgeBody = new Body(Game1.World);
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
if (triangles[i][0].Y == triangles[i][1].Y && triangles[i][0].Y == triangles[i][2].Y) continue;
|
||||
if (triangles[i][0].X == triangles[i][1].X && triangles[i][0].X == triangles[i][2].X) continue;
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
FixtureFactory.AttachPolygon(bodyVertices, 5.0f, edgeBody);
|
||||
}
|
||||
|
||||
edgeBody.UserData = cell;
|
||||
edgeBody.SleepingAllowed = false;
|
||||
edgeBody.BodyType = BodyType.Kinematic;
|
||||
edgeBody.CollisionCategories = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
|
||||
cell.body = edgeBody;
|
||||
bodies.Add(edgeBody);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
Body shaftBody = BodyFactory.CreateRectangle(Game1.World, 100.0f, 10.0f, 5.0f);
|
||||
shaftBody.BodyType = BodyType.Kinematic;
|
||||
shaftBody.CollisionCategories = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
shaftBody.SetTransform(ConvertUnits.ToSimUnits((i==0) ? startPosition : endPosition), 0.0f);
|
||||
shaftBody.SleepingAllowed = false;
|
||||
bodies.Add(shaftBody);
|
||||
}
|
||||
|
||||
vertices = verticeList.ToArray();
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 pos)
|
||||
{
|
||||
Vector2 amount = pos - Position;
|
||||
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
|
||||
//foreach (VoronoiCell cell in cells)
|
||||
//{
|
||||
// if (cell.body == null) continue;
|
||||
// cell.body.SleepingAllowed = false;
|
||||
// cell.body.SetTransform(cell.body.Position + simAmount, cell.body.Rotation);
|
||||
//}
|
||||
|
||||
int i = 0;
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(i);
|
||||
i++;
|
||||
body.SetTransform(body.Position + simAmount, body.Rotation);
|
||||
}
|
||||
|
||||
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
|
||||
{
|
||||
Item item = mapEntity as Item;
|
||||
if (item == null)
|
||||
{
|
||||
//if (!mapEntity.MoveWithLevel) continue;
|
||||
//mapEntity.Move(amount);
|
||||
}
|
||||
else if (item.body != null)
|
||||
{
|
||||
if (item.CurrentHull != null) continue;
|
||||
item.SetTransform(item.SimPosition+amount, item.body.Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 prevVelocity;
|
||||
public void Move(Vector2 amount)
|
||||
{
|
||||
Vector2 velocity = amount;
|
||||
Vector2 simVelocity = ConvertUnits.ToSimUnits(amount / (float)Physics.step);
|
||||
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
body.LinearVelocity = simVelocity;
|
||||
}
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
foreach (Limb limb in character.AnimController.limbs)
|
||||
{
|
||||
if (character.AnimController.CurrentHull != null) continue;
|
||||
|
||||
limb.body.LinearVelocity += simVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
|
||||
{
|
||||
Item item = mapEntity as Item;
|
||||
if (item == null)
|
||||
{
|
||||
//if (!mapEntity.MoveWithLevel) continue;
|
||||
//mapEntity.Move(velocity);
|
||||
}
|
||||
else if (item.body!=null)
|
||||
{
|
||||
if (item.CurrentHull != null) continue;
|
||||
item.body.LinearVelocity += simVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
AtStartPosition = Vector2.Distance(startPosition, -Position) < ExitDistance;
|
||||
AtEndPosition = Vector2.Distance(endPosition, -Position) < ExitDistance;
|
||||
|
||||
prevVelocity = simVelocity;
|
||||
}
|
||||
|
||||
public static void AfterWorldStep()
|
||||
{
|
||||
if (loaded == null) return;
|
||||
|
||||
loaded.ResetBodyVelocities();
|
||||
}
|
||||
|
||||
private void ResetBodyVelocities()
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.AnimController.CurrentHull != null) continue;
|
||||
|
||||
foreach (Limb limb in character.AnimController.limbs)
|
||||
{
|
||||
limb.body.LinearVelocity -= prevVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in Item.itemList)
|
||||
{
|
||||
if (item.body == null || item.CurrentHull != null) continue;
|
||||
item.body.LinearVelocity -= prevVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void DebugCheckPos()
|
||||
{
|
||||
|
||||
Vector2 avgPos = Vector2.Zero;
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
if (cell.body == null) continue;
|
||||
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(cell.body.Position);
|
||||
avgPos += cell.body.Position;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("avgpos: " + avgPos / cells.Count);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("pos: " + Position);
|
||||
}
|
||||
|
||||
Vector2 observerPosition;
|
||||
public void SetObserverPosition(Vector2 position)
|
||||
{
|
||||
//observerPosition = position - this.Position;
|
||||
//int gridPosX = (int)Math.Floor(observerPosition.X / GridCellWidth);
|
||||
//int gridPosY = (int)Math.Floor(observerPosition.Y / GridCellWidth);
|
||||
//int searchOffset = 2;
|
||||
|
||||
//int startX = Math.Max(gridPosX - searchOffset, 0);
|
||||
//int endX = Math.Min(gridPosX + searchOffset, cellGrid.GetLength(0) - 1);
|
||||
|
||||
//int startY = Math.Max(gridPosY - searchOffset, 0);
|
||||
//int endY = Math.Min(gridPosY + searchOffset, cellGrid.GetLength(1) - 1);
|
||||
|
||||
//for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
//{
|
||||
// for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
// {
|
||||
// for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
// {
|
||||
// //foreach (Body b in cellGrid[x, y][i].bodies)
|
||||
// //{
|
||||
// if (cellGrid[x, y][i].body == null) continue;
|
||||
// cellGrid[x, y][i].body.Enabled = true;// (x >= startX && x <= endX && y >= startY && y <= endY);
|
||||
// //}
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
Vector2 pos = endPosition;
|
||||
pos.X += Position.X;
|
||||
pos.Y = -pos.Y - Position.Y;
|
||||
|
||||
int shaftWidth = 10000;
|
||||
|
||||
spriteBatch.Draw(shaftTexture,
|
||||
new Rectangle((int)(pos.X - shaftWidth / 2), (int)pos.Y, shaftWidth, 512),
|
||||
new Rectangle(0, 0, shaftWidth, 256),
|
||||
Color.White, 0.0f,
|
||||
Vector2.Zero,
|
||||
SpriteEffects.None, 0.0f);
|
||||
|
||||
pos = startPosition;
|
||||
pos.X += Position.X;
|
||||
pos.Y = -pos.Y - Position.Y;
|
||||
|
||||
spriteBatch.Draw(shaftTexture,
|
||||
new Rectangle((int)(pos.X - shaftWidth/2), (int)pos.Y, shaftWidth, 512),
|
||||
new Rectangle(0, 0, shaftWidth, 256),
|
||||
Color.White, 0.0f,
|
||||
Vector2.Zero,
|
||||
SpriteEffects.None, 0.0f);
|
||||
|
||||
//List<Vector2[]> edges = GetCellEdges(observerPosition, 1, false);
|
||||
|
||||
//foreach (VoronoiCell cell in cells)
|
||||
//{
|
||||
// for (int i = 0; i < cell.bodyVertices.Count - 1; i++)
|
||||
// {
|
||||
// Vector2 start = cell.bodyVertices[i];
|
||||
// start.X += Position.X;
|
||||
// start.Y = -start.Y - Position.Y;
|
||||
// start.X += Rand.Range(-10.0f, 10.0f);
|
||||
|
||||
// Vector2 end = cell.bodyVertices[i + 1];
|
||||
// end.X += Position.X;
|
||||
// end.Y = -end.Y - Position.Y;
|
||||
// end.X += Rand.Range(-10.0f, 10.0f);
|
||||
|
||||
// GUI.DrawLine(spriteBatch, start, end, (cell.body != null && cell.body.Enabled) ? Color.Red : Color.Red);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public List<Vector2[]> GetCellEdges(Vector2 refPos, int searchDepth = 2, bool onlySolid = true)
|
||||
{
|
||||
|
||||
int gridPosX = (int)Math.Floor(refPos.X / GridCellWidth);
|
||||
int gridPosY = (int)Math.Floor(refPos.Y / GridCellWidth);
|
||||
|
||||
int startX = Math.Max(gridPosX - searchDepth, 0);
|
||||
int endX = Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1);
|
||||
|
||||
int startY = Math.Max(gridPosY - searchDepth, 0);
|
||||
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
|
||||
|
||||
|
||||
List<Vector2[]> edges = new List<Vector2[]>();
|
||||
|
||||
for (int x = startX; x < endX; x++)
|
||||
{
|
||||
for (int y = startY; y < endY; y++)
|
||||
{
|
||||
foreach (VoronoiCell cell in cellGrid[x, y])
|
||||
{
|
||||
for (int i = 0; i < cell.edges.Count; i++)
|
||||
{
|
||||
if (onlySolid && !cell.edges[i].isSolid) continue;
|
||||
Vector2 start = cell.edges[i].point1 + Position;
|
||||
start.Y = -start.Y;
|
||||
|
||||
Vector2 end = cell.edges[i].point2 + Position;
|
||||
end.Y = -end.Y;
|
||||
|
||||
edges.Add(new Vector2[] { start, end });
|
||||
//GUI.DrawLine(spriteBatch, start, end, (cell.body != null && cell.body.Enabled) ? Color.Green : Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
public void Render(GraphicsDevice graphicsDevice, Camera cam)
|
||||
{
|
||||
if (vertices == null) return;
|
||||
if (vertices.Length <= 0) return;
|
||||
|
||||
basicEffect.World = Matrix.CreateTranslation(new Vector3(Position, 0.0f)) * cam.ShaderTransform
|
||||
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
|
||||
|
||||
basicEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
|
||||
|
||||
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, (int)Math.Floor(vertices.Length / 3.0f));
|
||||
}
|
||||
|
||||
private void Unload()
|
||||
{
|
||||
//position = Vector2.Zero;
|
||||
|
||||
//foreach (VoronoiCell cell in cells)
|
||||
//{
|
||||
// //foreach (Body b in cell.bodies)
|
||||
// //{
|
||||
// Game1.world.RemoveBody(cell.body);
|
||||
// //}
|
||||
//}
|
||||
|
||||
|
||||
//bodies = null;
|
||||
|
||||
vertices = null;
|
||||
|
||||
cells = null;
|
||||
|
||||
bodies.Clear();
|
||||
bodies = null;
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
vertexBuffer = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
988
Subsurface/Source/Map/Levels/Voronoi.cs
Normal file
988
Subsurface/Source/Map/Levels/Voronoi.cs
Normal file
@@ -0,0 +1,988 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 11:30 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
|
||||
* Bell Laboratories.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code was originally written by Stephan Fortune in C code. I, Shane O'Sullivan,
|
||||
* have since modified it, encapsulating it in a C++ class and, fixing memory leaks and
|
||||
* adding accessors to the Voronoi Edges.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Java Version by Zhenyu Pan
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of Voronoi.
|
||||
/// </summary>
|
||||
public class Voronoi
|
||||
{
|
||||
// ************* Private members ******************
|
||||
double borderMinX, borderMaxX, borderMinY, borderMaxY;
|
||||
int siteidx;
|
||||
double xmin, xmax, ymin, ymax, deltax, deltay;
|
||||
int nvertices;
|
||||
int nedges;
|
||||
int nsites;
|
||||
Site[] sites;
|
||||
Site bottomsite;
|
||||
int sqrt_nsites;
|
||||
double minDistanceBetweenSites;
|
||||
int PQcount;
|
||||
int PQmin;
|
||||
int PQhashsize;
|
||||
Halfedge[] PQhash;
|
||||
|
||||
const int LE = 0;
|
||||
const int RE = 1;
|
||||
|
||||
int ELhashsize;
|
||||
Halfedge[] ELhash;
|
||||
Halfedge ELleftend, ELrightend;
|
||||
List<GraphEdge> allEdges;
|
||||
|
||||
|
||||
// ************* Public methods ******************
|
||||
// ******************************************
|
||||
|
||||
// constructor
|
||||
public Voronoi ( double minDistanceBetweenSites )
|
||||
{
|
||||
siteidx = 0;
|
||||
sites = null;
|
||||
|
||||
allEdges = null;
|
||||
this.minDistanceBetweenSites = minDistanceBetweenSites;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param xValuesIn Array of X values for each site.
|
||||
* @param yValuesIn Array of Y values for each site. Must be identical length to yValuesIn
|
||||
* @param minX The minimum X of the bounding box around the voronoi
|
||||
* @param maxX The maximum X of the bounding box around the voronoi
|
||||
* @param minY The minimum Y of the bounding box around the voronoi
|
||||
* @param maxY The maximum Y of the bounding box around the voronoi
|
||||
* @return
|
||||
*/
|
||||
// تستدعى هذه العملية لإنشاء مخطط فورونوي
|
||||
public List<GraphEdge> generateVoronoi ( double[] xValuesIn, double[] yValuesIn, double minX, double maxX, double minY, double maxY )
|
||||
{
|
||||
sort(xValuesIn, yValuesIn, xValuesIn.Length);
|
||||
|
||||
// Check bounding box inputs - if mins are bigger than maxes, swap them
|
||||
double temp = 0;
|
||||
if ( minX > maxX )
|
||||
{
|
||||
temp = minX;
|
||||
minX = maxX;
|
||||
maxX = temp;
|
||||
}
|
||||
if ( minY > maxY )
|
||||
{
|
||||
temp = minY;
|
||||
minY = maxY;
|
||||
maxY = temp;
|
||||
}
|
||||
|
||||
borderMinX = minX;
|
||||
borderMinY = minY;
|
||||
borderMaxX = maxX;
|
||||
borderMaxY = maxY;
|
||||
|
||||
siteidx = 0;
|
||||
voronoi_bd ();
|
||||
return allEdges;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************
|
||||
* Private methods - implementation details
|
||||
********************************************************/
|
||||
|
||||
private void sort ( double[] xValuesIn, double[] yValuesIn, int count )
|
||||
{
|
||||
sites = null;
|
||||
allEdges = new List<GraphEdge>();
|
||||
|
||||
nsites = count;
|
||||
nvertices = 0;
|
||||
nedges = 0;
|
||||
|
||||
double sn = (double)nsites + 4;
|
||||
sqrt_nsites = (int) Math.Sqrt ( sn );
|
||||
|
||||
// Copy the inputs so we don't modify the originals
|
||||
double[] xValues = new double[count];
|
||||
double[] yValues = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
xValues[i] = xValuesIn[i];
|
||||
yValues[i] = yValuesIn[i];
|
||||
}
|
||||
sortNode ( xValues, yValues, count );
|
||||
}
|
||||
|
||||
private void qsort ( Site[] sites )
|
||||
{
|
||||
List<Site> listSites = new List<Site>( sites.Length );
|
||||
for ( int i = 0; i < sites.Length; i++ )
|
||||
{
|
||||
listSites.Add ( sites[i] );
|
||||
}
|
||||
|
||||
listSites.Sort ( new SiteSorterYX () );
|
||||
|
||||
// Copy back into the array
|
||||
for (int i=0; i < sites.Length; i++)
|
||||
{
|
||||
sites[i] = listSites[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNode ( double[] xValues, double[] yValues, int numPoints )
|
||||
{
|
||||
nsites = numPoints;
|
||||
sites = new Site[nsites];
|
||||
xmin = xValues[0];
|
||||
ymin = yValues[0];
|
||||
xmax = xValues[0];
|
||||
ymax = yValues[0];
|
||||
|
||||
for ( int i = 0; i < nsites; i++ )
|
||||
{
|
||||
sites[i] = new Site();
|
||||
sites[i].coord.setPoint ( xValues[i], yValues[i] );
|
||||
sites[i].sitenbr = i;
|
||||
|
||||
if ( xValues[i] < xmin )
|
||||
xmin = xValues[i];
|
||||
else if ( xValues[i] > xmax )
|
||||
xmax = xValues[i];
|
||||
|
||||
if ( yValues[i] < ymin )
|
||||
ymin = yValues[i];
|
||||
else if ( yValues[i] > ymax )
|
||||
ymax = yValues[i];
|
||||
}
|
||||
|
||||
qsort ( sites );
|
||||
deltax = xmax - xmin;
|
||||
deltay = ymax - ymin;
|
||||
}
|
||||
|
||||
private Site nextone ()
|
||||
{
|
||||
Site s;
|
||||
if ( siteidx < nsites )
|
||||
{
|
||||
s = sites[siteidx];
|
||||
siteidx++;
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Edge bisect ( Site s1, Site s2 )
|
||||
{
|
||||
double dx, dy, adx, ady;
|
||||
Edge newedge;
|
||||
|
||||
newedge = new Edge();
|
||||
|
||||
newedge.reg[0] = s1;
|
||||
newedge.reg[1] = s2;
|
||||
|
||||
newedge.ep [0] = null;
|
||||
newedge.ep[1] = null;
|
||||
|
||||
dx = s2.coord.x - s1.coord.x;
|
||||
dy = s2.coord.y - s1.coord.y;
|
||||
|
||||
adx = dx > 0 ? dx : -dx;
|
||||
ady = dy > 0 ? dy : -dy;
|
||||
newedge.c = (double)(s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy* dy) * 0.5);
|
||||
|
||||
if ( adx > ady )
|
||||
{
|
||||
newedge.a = 1.0;
|
||||
newedge.b = dy / dx;
|
||||
newedge.c /= dx;
|
||||
}
|
||||
else
|
||||
{
|
||||
newedge.a = dx / dy;
|
||||
newedge.b = 1.0;
|
||||
newedge.c /= dy;
|
||||
}
|
||||
|
||||
newedge.edgenbr = nedges;
|
||||
nedges++;
|
||||
|
||||
return newedge;
|
||||
}
|
||||
|
||||
private void makevertex ( Site v )
|
||||
{
|
||||
v.sitenbr = nvertices;
|
||||
nvertices++;
|
||||
}
|
||||
|
||||
private bool PQinitialize ()
|
||||
{
|
||||
PQcount = 0;
|
||||
PQmin = 0;
|
||||
PQhashsize = 4 * sqrt_nsites;
|
||||
PQhash = new Halfedge[ PQhashsize ];
|
||||
|
||||
for ( int i = 0; i < PQhashsize; i++ )
|
||||
{
|
||||
PQhash [i] = new Halfedge();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int PQbucket ( Halfedge he )
|
||||
{
|
||||
int bucket;
|
||||
|
||||
bucket = (int) ((he.ystar - ymin) / deltay * PQhashsize);
|
||||
if ( bucket < 0 )
|
||||
bucket = 0;
|
||||
if ( bucket >= PQhashsize )
|
||||
bucket = PQhashsize - 1;
|
||||
if ( bucket < PQmin )
|
||||
PQmin = bucket;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
// push the HalfEdge into the ordered linked list of vertices
|
||||
private void PQinsert ( Halfedge he, Site v, double offset )
|
||||
{
|
||||
Halfedge last, next;
|
||||
|
||||
he.vertex = v;
|
||||
he.ystar = (double)(v.coord.y + offset);
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
|
||||
while
|
||||
(
|
||||
(next = last.PQnext) != null
|
||||
&&
|
||||
(he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))
|
||||
)
|
||||
{
|
||||
last = next;
|
||||
}
|
||||
|
||||
he.PQnext = last.PQnext;
|
||||
last.PQnext = he;
|
||||
PQcount++;
|
||||
}
|
||||
|
||||
// remove the HalfEdge from the list of vertices
|
||||
private void PQdelete ( Halfedge he )
|
||||
{
|
||||
Halfedge last;
|
||||
|
||||
if (he.vertex != null)
|
||||
{
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
while ( last.PQnext != he )
|
||||
{
|
||||
last = last.PQnext;
|
||||
}
|
||||
|
||||
last.PQnext = he.PQnext;
|
||||
PQcount--;
|
||||
he.vertex = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PQempty ()
|
||||
{
|
||||
return ( PQcount == 0 );
|
||||
}
|
||||
|
||||
private Point PQ_min ()
|
||||
{
|
||||
Point answer = new Point ();
|
||||
|
||||
while ( PQhash[PQmin].PQnext == null )
|
||||
{
|
||||
PQmin++;
|
||||
}
|
||||
|
||||
answer.x = PQhash[PQmin].PQnext.vertex.coord.x;
|
||||
answer.y = PQhash[PQmin].PQnext.ystar;
|
||||
return answer;
|
||||
}
|
||||
|
||||
private Halfedge PQextractmin ()
|
||||
{
|
||||
Halfedge curr;
|
||||
|
||||
curr = PQhash[PQmin].PQnext;
|
||||
PQhash[PQmin].PQnext = curr.PQnext;
|
||||
PQcount--;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
private Halfedge HEcreate(Edge e, int pm)
|
||||
{
|
||||
Halfedge answer = new Halfedge();
|
||||
answer.ELedge = e;
|
||||
answer.ELpm = pm;
|
||||
answer.PQnext = null;
|
||||
answer.vertex = null;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private bool ELinitialize()
|
||||
{
|
||||
ELhashsize = 2 * sqrt_nsites;
|
||||
ELhash = new Halfedge[ELhashsize];
|
||||
|
||||
for (int i = 0; i < ELhashsize; i++)
|
||||
{
|
||||
ELhash[i] = null;
|
||||
}
|
||||
|
||||
ELleftend = HEcreate ( null, 0 );
|
||||
ELrightend = HEcreate ( null, 0 );
|
||||
ELleftend.ELleft = null;
|
||||
ELleftend.ELright = ELrightend;
|
||||
ELrightend.ELleft = ELleftend;
|
||||
ELrightend.ELright = null;
|
||||
ELhash[0] = ELleftend;
|
||||
ELhash[ELhashsize - 1] = ELrightend;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Halfedge ELright( Halfedge he )
|
||||
{
|
||||
return he.ELright;
|
||||
}
|
||||
|
||||
private Halfedge ELleft( Halfedge he )
|
||||
{
|
||||
return he.ELleft;
|
||||
}
|
||||
|
||||
private Site leftreg( Halfedge he )
|
||||
{
|
||||
if (he.ELedge == null)
|
||||
{
|
||||
return bottomsite;
|
||||
}
|
||||
return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]);
|
||||
}
|
||||
|
||||
private void ELinsert( Halfedge lb, Halfedge newHe )
|
||||
{
|
||||
newHe.ELleft = lb;
|
||||
newHe.ELright = lb.ELright;
|
||||
(lb.ELright).ELleft = newHe;
|
||||
lb.ELright = newHe;
|
||||
}
|
||||
|
||||
/*
|
||||
* This delete routine can't reclaim node, since pointers from hash table
|
||||
* may be present.
|
||||
*/
|
||||
private void ELdelete( Halfedge he )
|
||||
{
|
||||
(he.ELleft).ELright = he.ELright;
|
||||
(he.ELright).ELleft = he.ELleft;
|
||||
he.deleted = true;
|
||||
}
|
||||
|
||||
/* Get entry from hash table, pruning any deleted nodes */
|
||||
private Halfedge ELgethash( int b )
|
||||
{
|
||||
Halfedge he;
|
||||
if (b < 0 || b >= ELhashsize)
|
||||
return null;
|
||||
|
||||
he = ELhash[b];
|
||||
if (he == null || !he.deleted )
|
||||
return he;
|
||||
|
||||
/* Hash table points to deleted half edge. Patch as necessary. */
|
||||
ELhash[b] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Halfedge ELleftbnd( Point p )
|
||||
{
|
||||
int bucket;
|
||||
Halfedge he;
|
||||
|
||||
/* Use hash table to get close to desired halfedge */
|
||||
// use the hash function to find the place in the hash map that this
|
||||
// HalfEdge should be
|
||||
bucket = (int) ((p.x - xmin) / deltax * ELhashsize);
|
||||
|
||||
// make sure that the bucket position is within the range of the hash
|
||||
// array
|
||||
if ( bucket < 0 ) bucket = 0;
|
||||
if ( bucket >= ELhashsize ) bucket = ELhashsize - 1;
|
||||
|
||||
he = ELgethash ( bucket );
|
||||
|
||||
// if the HE isn't found, search backwards and forwards in the hash map
|
||||
// for the first non-null entry
|
||||
if ( he == null )
|
||||
{
|
||||
for ( int i = 1; i < ELhashsize; i++ )
|
||||
{
|
||||
if ( (he = ELgethash ( bucket - i ) ) != null )
|
||||
break;
|
||||
if ( (he = ELgethash ( bucket + i ) ) != null )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now search linear list of halfedges for the correct one */
|
||||
if ( he == ELleftend || ( he != ELrightend && right_of (he, p) ) )
|
||||
{
|
||||
// keep going right on the list until either the end is reached, or
|
||||
// you find the 1st edge which the point isn't to the right of
|
||||
do
|
||||
{
|
||||
he = he.ELright;
|
||||
}
|
||||
while ( he != ELrightend && right_of(he, p) );
|
||||
he = he.ELleft;
|
||||
}
|
||||
else
|
||||
// if the point is to the left of the HalfEdge, then search left for
|
||||
// the HE just to the left of the point
|
||||
{
|
||||
do
|
||||
{
|
||||
he = he.ELleft;
|
||||
}
|
||||
while ( he != ELleftend && !right_of(he, p) );
|
||||
}
|
||||
|
||||
/* Update hash table and reference counts */
|
||||
if ( bucket > 0 && bucket < ELhashsize - 1)
|
||||
{
|
||||
ELhash[bucket] = he;
|
||||
}
|
||||
|
||||
return he;
|
||||
}
|
||||
|
||||
private void pushGraphEdge( Site leftSite, Site rightSite, Vector2 point1, Vector2 point2 )
|
||||
{
|
||||
GraphEdge newEdge = new GraphEdge ();
|
||||
allEdges.Add ( newEdge );
|
||||
newEdge.point1 = point1;
|
||||
newEdge.point2 = point2;
|
||||
|
||||
newEdge.site1 = leftSite;
|
||||
newEdge.site2 = rightSite;
|
||||
}
|
||||
|
||||
private void clip_line( Edge e )
|
||||
{
|
||||
double pxmin, pxmax, pymin, pymax;
|
||||
Site s1, s2;
|
||||
|
||||
double x1 = e.reg[0].coord.x;
|
||||
double y1 = e.reg[0].coord.y;
|
||||
double x2 = e.reg[1].coord.x;
|
||||
double y2 = e.reg[1].coord.y;
|
||||
double x = x2- x1;
|
||||
double y = y2 - y1;
|
||||
|
||||
// if the distance between the two points this line was created from is
|
||||
// less than the square root of 2 عن جد؟, then ignore it
|
||||
if ( Math.Sqrt ( (x*x) + (y*y) ) < minDistanceBetweenSites )
|
||||
{
|
||||
return;
|
||||
}
|
||||
pxmin = borderMinX;
|
||||
pymin = borderMinY;
|
||||
pxmax = borderMaxX;
|
||||
pymax = borderMaxY;
|
||||
|
||||
if ( e.a == 1.0 && e.b >= 0.0 )
|
||||
{
|
||||
s1 = e.ep[1];
|
||||
s2 = e.ep[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
s1 = e.ep[0];
|
||||
s2 = e.ep[1];
|
||||
}
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
y1 = pymin;
|
||||
|
||||
if ( s1 != null && s1.coord.y > pymin )
|
||||
y1 = s1.coord.y;
|
||||
if ( y1 > pymax )
|
||||
y1 = pymax;
|
||||
x1 = e.c - e.b * y1;
|
||||
y2 = pymax;
|
||||
|
||||
if ( s2 != null && s2.coord.y < pymax )
|
||||
y2 = s2.coord.y;
|
||||
if ( y2 < pymin )
|
||||
y2 = pymin;
|
||||
x2 = e.c - e.b * y2;
|
||||
if ( ( (x1 > pxmax) & (x2 > pxmax) ) | ( (x1 < pxmin) & (x2 < pxmin) ) )
|
||||
return;
|
||||
|
||||
if ( x1 > pxmax )
|
||||
{
|
||||
x1 = pxmax;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x1 < pxmin )
|
||||
{
|
||||
x1 = pxmin;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x2 > pxmax )
|
||||
{
|
||||
x2 = pxmax;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
if ( x2 < pxmin )
|
||||
{
|
||||
x2 = pxmin;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = pxmin;
|
||||
if ( s1 != null && s1.coord.x > pxmin )
|
||||
x1 = s1.coord.x;
|
||||
if ( x1 > pxmax )
|
||||
x1 = pxmax;
|
||||
y1 = e.c - e.a * x1;
|
||||
|
||||
x2 = pxmax;
|
||||
if ( s2 != null && s2.coord.x < pxmax )
|
||||
x2 = s2.coord.x;
|
||||
if ( x2 < pxmin )
|
||||
x2 = pxmin;
|
||||
y2 = e.c - e.a * x2;
|
||||
|
||||
if (((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin)))
|
||||
return;
|
||||
|
||||
if ( y1 > pymax )
|
||||
{
|
||||
y1 = pymax;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y1 < pymin )
|
||||
{
|
||||
y1 = pymin;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y2 > pymax )
|
||||
{
|
||||
y2 = pymax;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
if ( y2 < pymin )
|
||||
{
|
||||
y2 = pymin;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
}
|
||||
|
||||
pushGraphEdge(e.reg[0], e.reg[1], new Vector2((float)x1, (float)y1), new Vector2((float)x2, (float)y2));
|
||||
}
|
||||
|
||||
private void endpoint( Edge e, int lr, Site s )
|
||||
{
|
||||
e.ep[lr] = s;
|
||||
if ( e.ep[RE - lr] == null )
|
||||
return;
|
||||
clip_line ( e );
|
||||
}
|
||||
|
||||
/* returns true if p is to right of halfedge e */
|
||||
private bool right_of(Halfedge el, Point p)
|
||||
{
|
||||
Edge e;
|
||||
Site topsite;
|
||||
bool right_of_site;
|
||||
bool above, fast;
|
||||
double dxp, dyp, dxs, t1, t2, t3, yl;
|
||||
|
||||
e = el.ELedge;
|
||||
topsite = e.reg[1];
|
||||
|
||||
if ( p.x > topsite.coord.x )
|
||||
right_of_site = true;
|
||||
else
|
||||
right_of_site = false;
|
||||
|
||||
if ( right_of_site && el.ELpm == LE )
|
||||
return true;
|
||||
if (!right_of_site && el.ELpm == RE )
|
||||
return false;
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
dxp = p.x - topsite.coord.x;
|
||||
dyp = p.y - topsite.coord.y;
|
||||
fast = false;
|
||||
|
||||
if ( (!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0)) )
|
||||
{
|
||||
above = dyp >= e.b * dxp;
|
||||
fast = above;
|
||||
}
|
||||
else
|
||||
{
|
||||
above = p.x + p.y * e.b > e.c;
|
||||
if ( e.b < 0.0 )
|
||||
above = !above;
|
||||
if ( !above )
|
||||
fast = true;
|
||||
}
|
||||
if ( !fast )
|
||||
{
|
||||
dxs = topsite.coord.x - ( e.reg[0] ).coord.x;
|
||||
above = e.b * (dxp * dxp - dyp * dyp)
|
||||
< dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b);
|
||||
|
||||
if ( e.b < 0 )
|
||||
above = !above;
|
||||
}
|
||||
}
|
||||
else // e.b == 1.0
|
||||
{
|
||||
yl = e.c - e.a * p.x;
|
||||
t1 = p.y - yl;
|
||||
t2 = p.x - topsite.coord.x;
|
||||
t3 = yl - topsite.coord.y;
|
||||
above = t1 * t1 > t2 * t2 + t3 * t3;
|
||||
}
|
||||
return ( el.ELpm == LE ? above : !above );
|
||||
}
|
||||
|
||||
private Site rightreg(Halfedge he)
|
||||
{
|
||||
if (he.ELedge == (Edge) null)
|
||||
// if this halfedge has no edge, return the bottom site (whatever
|
||||
// that is)
|
||||
{
|
||||
return (bottomsite);
|
||||
}
|
||||
|
||||
// if the ELpm field is zero, return the site 0 that this edge bisects,
|
||||
// otherwise return site number 1
|
||||
return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]);
|
||||
}
|
||||
|
||||
private double dist( Site s, Site t )
|
||||
{
|
||||
double dx, dy;
|
||||
dx = s.coord.x - t.coord.x;
|
||||
dy = s.coord.y - t.coord.y;
|
||||
return Math.Sqrt ( dx * dx + dy * dy );
|
||||
}
|
||||
|
||||
// create a new site where the HalfEdges el1 and el2 intersect - note that
|
||||
// the Point in the argument list is not used, don't know why it's there
|
||||
private Site intersect( Halfedge el1, Halfedge el2 )
|
||||
{
|
||||
Edge e1, e2, e;
|
||||
Halfedge el;
|
||||
double d, xint, yint;
|
||||
bool right_of_site;
|
||||
Site v; // vertex
|
||||
|
||||
e1 = el1.ELedge;
|
||||
e2 = el2.ELedge;
|
||||
|
||||
if ( e1 == null || e2 == null )
|
||||
return null;
|
||||
|
||||
// if the two edges bisect the same parent, return null
|
||||
if ( e1.reg[1] == e2.reg[1] )
|
||||
return null;
|
||||
|
||||
d = e1.a * e2.b - e1.b * e2.a;
|
||||
if ( -1.0e-10 < d && d < 1.0e-10 )
|
||||
return null;
|
||||
|
||||
xint = ( e1.c * e2.b - e2.c * e1.b ) / d;
|
||||
yint = ( e2.c * e1.a - e1.c * e2.a ) / d;
|
||||
|
||||
if ( (e1.reg[1].coord.y < e2.reg[1].coord.y)
|
||||
|| (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x) )
|
||||
{
|
||||
el = el1;
|
||||
e = e1;
|
||||
}
|
||||
else
|
||||
{
|
||||
el = el2;
|
||||
e = e2;
|
||||
}
|
||||
|
||||
right_of_site = xint >= e.reg[1].coord.x;
|
||||
if ((right_of_site && el.ELpm == LE)
|
||||
|| (!right_of_site && el.ELpm == RE))
|
||||
return null;
|
||||
|
||||
// create a new site at the point of intersection - this is a new vector
|
||||
// event waiting to happen
|
||||
v = new Site();
|
||||
v.coord.x = xint;
|
||||
v.coord.y = yint;
|
||||
return v;
|
||||
}
|
||||
|
||||
/*
|
||||
* implicit parameters: nsites, sqrt_nsites, xmin, xmax, ymin, ymax, deltax,
|
||||
* deltay (can all be estimates). Performance suffers if they are wrong;
|
||||
* better to make nsites, deltax, and deltay too big than too small. (?)
|
||||
*/
|
||||
private bool voronoi_bd()
|
||||
{
|
||||
Site newsite, bot, top, temp, p;
|
||||
Site v;
|
||||
Point newintstar = null;
|
||||
int pm;
|
||||
Halfedge lbnd, rbnd, llbnd, rrbnd, bisector;
|
||||
Edge e;
|
||||
|
||||
PQinitialize();
|
||||
ELinitialize();
|
||||
|
||||
bottomsite = nextone();
|
||||
newsite = nextone();
|
||||
while (true)
|
||||
{
|
||||
if (!PQempty())
|
||||
{
|
||||
newintstar = PQ_min();
|
||||
}
|
||||
// if the lowest site has a smaller y value than the lowest vector
|
||||
// intersection,
|
||||
// process the site otherwise process the vector intersection
|
||||
|
||||
if (newsite != null && (PQempty()
|
||||
|| newsite.coord.y < newintstar.y
|
||||
|| (newsite.coord.y == newintstar.y
|
||||
&& newsite.coord.x < newintstar.x)))
|
||||
{
|
||||
/* new site is smallest -this is a site event */
|
||||
// get the first HalfEdge to the LEFT of the new site
|
||||
lbnd = ELleftbnd((newsite.coord));
|
||||
// get the first HalfEdge to the RIGHT of the new site
|
||||
rbnd = ELright(lbnd);
|
||||
// if this halfedge has no edge,bot =bottom site (whatever that
|
||||
// is)
|
||||
bot = rightreg(lbnd);
|
||||
// create a new edge that bisects
|
||||
e = bisect(bot, newsite);
|
||||
|
||||
// create a new HalfEdge, setting its ELpm field to 0
|
||||
bisector = HEcreate(e, LE);
|
||||
// insert this new bisector edge between the left and right
|
||||
// vectors in a linked list
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if the new bisector intersects with the left edge,
|
||||
// remove the left edge's vertex, and put in the new one
|
||||
if ((p = intersect(lbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(lbnd);
|
||||
PQinsert(lbnd, p, dist(p, newsite));
|
||||
}
|
||||
lbnd = bisector;
|
||||
// create a new HalfEdge, setting its ELpm field to 1
|
||||
bisector = HEcreate(e, RE);
|
||||
// insert the new HE to the right of the original bisector
|
||||
// earlier in the IF stmt
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if this new bisector intersects with the new HalfEdge
|
||||
if ((p = intersect(bisector, rbnd)) != null)
|
||||
{
|
||||
// push the HE into the ordered linked list of vertices
|
||||
PQinsert(bisector, p, dist(p, newsite));
|
||||
}
|
||||
newsite = nextone();
|
||||
} else if (!PQempty())
|
||||
/* intersection is smallest - this is a vector event */
|
||||
{
|
||||
// pop the HalfEdge with the lowest vector off the ordered list
|
||||
// of vectors
|
||||
lbnd = PQextractmin();
|
||||
// get the HalfEdge to the left of the above HE
|
||||
llbnd = ELleft(lbnd);
|
||||
// get the HalfEdge to the right of the above HE
|
||||
rbnd = ELright(lbnd);
|
||||
// get the HalfEdge to the right of the HE to the right of the
|
||||
// lowest HE
|
||||
rrbnd = ELright(rbnd);
|
||||
// get the Site to the left of the left HE which it bisects
|
||||
bot = leftreg(lbnd);
|
||||
// get the Site to the right of the right HE which it bisects
|
||||
top = rightreg(rbnd);
|
||||
|
||||
v = lbnd.vertex; // get the vertex that caused this event
|
||||
makevertex(v); // set the vertex number - couldn't do this
|
||||
// earlier since we didn't know when it would be processed
|
||||
endpoint(lbnd.ELedge, lbnd.ELpm, v);
|
||||
// set the endpoint of
|
||||
// the left HalfEdge to be this vector
|
||||
endpoint(rbnd.ELedge, rbnd.ELpm, v);
|
||||
// set the endpoint of the right HalfEdge to
|
||||
// be this vector
|
||||
ELdelete(lbnd); // mark the lowest HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
PQdelete(rbnd);
|
||||
// remove all vertex events to do with the right HE
|
||||
ELdelete(rbnd); // mark the right HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
pm = LE; // set the pm variable to zero
|
||||
|
||||
if (bot.coord.y > top.coord.y)
|
||||
// if the site to the left of the event is higher than the
|
||||
// Site
|
||||
{ // to the right of it, then swap them and set the 'pm'
|
||||
// variable to 1
|
||||
temp = bot;
|
||||
bot = top;
|
||||
top = temp;
|
||||
pm = RE;
|
||||
}
|
||||
e = bisect(bot, top); // create an Edge (or line)
|
||||
// that is between the two Sites. This creates the formula of
|
||||
// the line, and assigns a line number to it
|
||||
bisector = HEcreate(e, pm); // create a HE from the Edge 'e',
|
||||
// and make it point to that edge
|
||||
// with its ELedge field
|
||||
ELinsert(llbnd, bisector); // insert the new bisector to the
|
||||
// right of the left HE
|
||||
endpoint(e, RE - pm, v); // set one endpoint to the new edge
|
||||
// to be the vector point 'v'.
|
||||
// If the site to the left of this bisector is higher than the
|
||||
// right Site, then this endpoint
|
||||
// is put in position 0; otherwise in pos 1
|
||||
|
||||
// if left HE and the new bisector intersect, then delete
|
||||
// the left HE, and reinsert it
|
||||
if ((p = intersect(llbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(llbnd);
|
||||
PQinsert(llbnd, p, dist(p, bot));
|
||||
}
|
||||
|
||||
// if right HE and the new bisector intersect, then
|
||||
// reinsert it
|
||||
if ((p = intersect(bisector, rrbnd)) != null)
|
||||
{
|
||||
PQinsert(bisector, p, dist(p, bot));
|
||||
}
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (lbnd = ELright(ELleftend); lbnd != ELrightend; lbnd = ELright(lbnd))
|
||||
{
|
||||
e = lbnd.ELedge;
|
||||
clip_line(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, int width, int height)
|
||||
{
|
||||
double[] xVal = new double[sites.Count];
|
||||
double[] yVal = new double[sites.Count];
|
||||
for (int i = 0; i < sites.Count; i++)
|
||||
{
|
||||
xVal[i] = sites[i].X;
|
||||
yVal[i] = sites[i].Y;
|
||||
}
|
||||
return generateVoronoi(xVal, yVal, 0, width, 0, height);
|
||||
}
|
||||
|
||||
} // Voronoi Class End
|
||||
} // namespace Voronoi2 End
|
||||
181
Subsurface/Source/Map/Levels/VoronoiElements.cs
Normal file
181
Subsurface/Source/Map/Levels/VoronoiElements.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 09:29 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2011 James Humphreys. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY James Humphreys ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of James Humphreys.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Subsurface;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
public class Point
|
||||
{
|
||||
public double x, y;
|
||||
|
||||
public Point ()
|
||||
{
|
||||
}
|
||||
|
||||
public void setPoint ( double x, double y )
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// use for sites and vertecies
|
||||
public class Site
|
||||
{
|
||||
public Point coord;
|
||||
public int sitenbr;
|
||||
|
||||
public Site ()
|
||||
{
|
||||
coord = new Point();
|
||||
}
|
||||
}
|
||||
|
||||
public class Edge
|
||||
{
|
||||
public double a = 0, b = 0, c = 0;
|
||||
public Site[] ep;
|
||||
public Site[] reg;
|
||||
public int edgenbr;
|
||||
|
||||
public Edge ()
|
||||
{
|
||||
ep = new Site[2];
|
||||
reg = new Site[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Halfedge
|
||||
{
|
||||
public Halfedge ELleft, ELright;
|
||||
public Edge ELedge;
|
||||
public bool deleted;
|
||||
public int ELpm;
|
||||
public Site vertex;
|
||||
public double ystar;
|
||||
public Halfedge PQnext;
|
||||
|
||||
public Halfedge ()
|
||||
{
|
||||
PQnext = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class VoronoiCell
|
||||
{
|
||||
public List<GraphEdge> edges;
|
||||
public Site site;
|
||||
|
||||
public List<Vector2> bodyVertices;
|
||||
|
||||
public Body body;
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return new Vector2((float)site.coord.x, (float)site.coord.y); }
|
||||
}
|
||||
|
||||
public VoronoiCell(Site site)
|
||||
{
|
||||
edges = new List<GraphEdge>();
|
||||
bodyVertices = new List<Vector2>();
|
||||
//bodies = new List<Body>();
|
||||
this.site = site;
|
||||
}
|
||||
}
|
||||
|
||||
public class GraphEdge
|
||||
{
|
||||
public Vector2 point1, point2;
|
||||
public Site site1, site2;
|
||||
public VoronoiCell cell1, cell2;
|
||||
|
||||
public bool isSolid;
|
||||
|
||||
public VoronoiCell AdjacentCell(VoronoiCell cell)
|
||||
{
|
||||
if (cell1==cell)
|
||||
{
|
||||
return cell2;
|
||||
}
|
||||
else if (cell2==cell)
|
||||
{
|
||||
return cell1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// للترتيب
|
||||
public class SiteSorterYX : IComparer<Site>
|
||||
{
|
||||
public int Compare ( Site p1, Site p2 )
|
||||
{
|
||||
Point s1 = p1.coord;
|
||||
Point s2 = p2.coord;
|
||||
if ( s1.y < s2.y ) return -1;
|
||||
if ( s1.y > s2.y ) return 1;
|
||||
if ( s1.x < s2.x ) return -1;
|
||||
if ( s1.x > s2.x ) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Subsurface/Source/Map/Lights/ConvexHull.cs
Normal file
243
Subsurface/Source/Map/Lights/ConvexHull.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Subsurface.Lights
|
||||
{
|
||||
class ConvexHull
|
||||
{
|
||||
public static List<ConvexHull> list = new List<ConvexHull>();
|
||||
static BasicEffect losEffect;
|
||||
static BasicEffect shadowEffect;
|
||||
|
||||
private VertexPositionColor[] vertices;
|
||||
private short[] indices;
|
||||
int primitiveCount;
|
||||
|
||||
bool[] backFacing;
|
||||
VertexPositionColor[] shadowVertices;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ConvexHull(Vector2[] points, Color color)
|
||||
{
|
||||
int vertexCount = points.Length;
|
||||
vertices = new VertexPositionColor[vertexCount + 1];
|
||||
Vector2 center = Vector2.Zero;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vertices[i] = new VertexPositionColor(new Vector3(points[i], 0), color);
|
||||
center += points[i];
|
||||
}
|
||||
center /= points.Length;
|
||||
vertices[vertexCount] = new VertexPositionColor(new Vector3(center, 0), color);
|
||||
|
||||
primitiveCount = points.Length;
|
||||
indices = new short[primitiveCount * 3];
|
||||
|
||||
for (int i = 0; i < primitiveCount; i++)
|
||||
{
|
||||
indices[3 * i] = (short)i;
|
||||
indices[3 * i + 1] = (short)((i + 1) % vertexCount);
|
||||
indices[3 * i + 2] = (short)vertexCount;
|
||||
}
|
||||
backFacing = new bool[vertexCount];
|
||||
|
||||
Enabled = true;
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public void Move(Vector2 amount)
|
||||
{
|
||||
for (int i = 0; i < vertices.Count(); i++)
|
||||
{
|
||||
vertices[i].Position = new Vector3(vertices[i].Position.X + amount.X, vertices[i].Position.Y + amount.Y, vertices[i].Position.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVertices(Vector2[] points)
|
||||
{
|
||||
int vertexCount = points.Length;
|
||||
vertices = new VertexPositionColor[vertexCount + 1];
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vertices[i] = new VertexPositionColor(new Vector3(points[i], 0), Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
//public void Draw(GameTime gameTime)
|
||||
//{
|
||||
// device.RasterizerState = RasterizerState.CullNone;
|
||||
// device.BlendState = BlendState.Opaque;
|
||||
|
||||
// drawingEffect.World = Matrix.CreateTranslation(position.X, position.Y, 0);
|
||||
|
||||
// foreach (EffectPass pass in drawingEffect.CurrentTechnique.Passes)
|
||||
// {
|
||||
// pass.Apply();
|
||||
// device.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, primitiveCount);
|
||||
// }
|
||||
//}
|
||||
|
||||
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, bool los = true)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
if (losEffect == null)
|
||||
{
|
||||
losEffect = new BasicEffect(graphicsDevice);
|
||||
losEffect.VertexColorEnabled = true;
|
||||
}
|
||||
if (shadowEffect==null)
|
||||
{
|
||||
shadowEffect = new BasicEffect(graphicsDevice);
|
||||
shadowEffect.TextureEnabled = true;
|
||||
//shadowEffect.VertexColorEnabled = true;
|
||||
shadowEffect.LightingEnabled = false;
|
||||
shadowEffect.Texture = Game1.TextureLoader.FromFile("Content/lights/penumbra.png");
|
||||
}
|
||||
|
||||
//compute facing of each edge, using N*L
|
||||
for (int i = 0; i < primitiveCount; i++)
|
||||
{
|
||||
Vector2 firstVertex = new Vector2(vertices[i].Position.X, vertices[i].Position.Y);
|
||||
int secondIndex = (i + 1) % primitiveCount;
|
||||
Vector2 secondVertex = new Vector2(vertices[secondIndex].Position.X, vertices[secondIndex].Position.Y);
|
||||
Vector2 middle = (firstVertex + secondVertex) / 2;
|
||||
|
||||
Vector2 L = lightSourcePos - middle;
|
||||
|
||||
Vector2 N = new Vector2();
|
||||
N.X = -(secondVertex.Y - firstVertex.Y);
|
||||
N.Y = secondVertex.X - firstVertex.X;
|
||||
|
||||
backFacing[i] = (Vector2.Dot(N, L) < 0);
|
||||
}
|
||||
|
||||
//find beginning and ending vertices which
|
||||
//belong to the shadow
|
||||
int startingIndex = 0;
|
||||
int endingIndex = 0;
|
||||
for (int i = 0; i < primitiveCount; i++)
|
||||
{
|
||||
int currentEdge = i;
|
||||
int nextEdge = (i + 1) % primitiveCount;
|
||||
|
||||
if (backFacing[currentEdge] && !backFacing[nextEdge])
|
||||
endingIndex = nextEdge;
|
||||
|
||||
if (!backFacing[currentEdge] && backFacing[nextEdge])
|
||||
startingIndex = nextEdge;
|
||||
}
|
||||
|
||||
VertexPositionTexture[] penumbraVertices = new VertexPositionTexture[6];
|
||||
|
||||
if (los)
|
||||
{
|
||||
for (int n = 0; n < 4; n+=3)
|
||||
{
|
||||
Vector3 penumbraStart = (n == 0) ? vertices[startingIndex].Position : vertices[endingIndex].Position;
|
||||
|
||||
penumbraVertices[n] = new VertexPositionTexture();
|
||||
penumbraVertices[n].Position = penumbraStart;
|
||||
penumbraVertices[n].TextureCoordinate = new Vector2(0.0f, 1.0f);
|
||||
//penumbraVertices[0].te = fow ? Color.Black : Color.Transparent;
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
penumbraVertices[n + i + 1] = new VertexPositionTexture();
|
||||
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
|
||||
vertexDir.Normalize();
|
||||
|
||||
Vector3 normal = (i == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f)*0.05f;
|
||||
if (n > 0) normal = -normal;
|
||||
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
|
||||
vertexDir.Normalize();
|
||||
penumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000;
|
||||
|
||||
if (los)
|
||||
{
|
||||
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
var temp = penumbraVertices[4];
|
||||
penumbraVertices[4] = penumbraVertices[5];
|
||||
penumbraVertices[5] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int shadowVertexCount;
|
||||
|
||||
//nr of vertices that are in the shadow
|
||||
if (endingIndex > startingIndex)
|
||||
shadowVertexCount = endingIndex - startingIndex + 1;
|
||||
else
|
||||
shadowVertexCount = primitiveCount + 1 - startingIndex + endingIndex;
|
||||
|
||||
shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
|
||||
|
||||
//create a triangle strip that has the shape of the shadow
|
||||
int currentIndex = startingIndex;
|
||||
int svCount = 0;
|
||||
while (svCount != shadowVertexCount * 2)
|
||||
{
|
||||
Vector3 vertexPos = vertices[currentIndex].Position;
|
||||
|
||||
//one vertex on the hull
|
||||
shadowVertices[svCount] = new VertexPositionColor();
|
||||
shadowVertices[svCount].Color = los ? Color.Black : Color.Transparent;
|
||||
shadowVertices[svCount].Position = vertexPos;
|
||||
|
||||
//one extruded by the light direction
|
||||
shadowVertices[svCount + 1] = new VertexPositionColor();
|
||||
shadowVertices[svCount + 1].Color = los ? Color.Black : Color.Transparent;
|
||||
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
|
||||
L2P.Normalize();
|
||||
shadowVertices[svCount + 1].Position = new Vector3(lightSourcePos, 0) + L2P * 9000;
|
||||
|
||||
svCount += 2;
|
||||
currentIndex = (currentIndex + 1) % primitiveCount;
|
||||
}
|
||||
|
||||
losEffect.World = cam.ShaderTransform
|
||||
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
|
||||
losEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
graphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 - 2);
|
||||
|
||||
if (los)
|
||||
{
|
||||
shadowEffect.World = cam.ShaderTransform
|
||||
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
|
||||
shadowEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, penumbraVertices, 0, 2, VertexPositionTexture.VertexDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
66
Subsurface/Source/Map/Lights/Light.cs
Normal file
66
Subsurface/Source/Map/Lights/Light.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Subsurface.Lights
|
||||
{
|
||||
class LightSource
|
||||
{
|
||||
|
||||
private static Texture2D lightTexture;
|
||||
|
||||
private Color color;
|
||||
|
||||
private float range;
|
||||
|
||||
private Texture2D texture;
|
||||
|
||||
public Vector2 Position;
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return color; }
|
||||
set { color = value; }
|
||||
}
|
||||
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set
|
||||
{
|
||||
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public LightSource(Vector2 position, float range, Color color)
|
||||
{
|
||||
Position = position;
|
||||
this.range = range;
|
||||
this.color = color;
|
||||
|
||||
if (lightTexture == null)
|
||||
{
|
||||
lightTexture = Game1.TextureLoader.FromFile("Content/Lights/light.png");
|
||||
}
|
||||
|
||||
texture = lightTexture;
|
||||
|
||||
Game1.LightManager.AddLight(this);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
Vector2 center = new Vector2(lightTexture.Width / 2, lightTexture.Height / 2);
|
||||
float scale = range / ((float)lightTexture.Width / 2.0f);
|
||||
spriteBatch.Draw(lightTexture, new Vector2(Position.X, -Position.Y), null, color, 0, center, scale, SpriteEffects.None, 1);
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Game1.LightManager.RemoveLight(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
Subsurface/Source/Map/Lights/LightManager.cs
Normal file
139
Subsurface/Source/Map/Lights/LightManager.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Subsurface.Lights
|
||||
{
|
||||
class LightManager
|
||||
{
|
||||
public static Vector2 ViewPos;
|
||||
|
||||
public Color AmbientLight;
|
||||
|
||||
RenderTarget2D lightMap;
|
||||
|
||||
private static Texture2D alphaClearTexture;
|
||||
|
||||
private List<LightSource> lights;
|
||||
|
||||
public bool LosEnabled = true;
|
||||
|
||||
public bool LightingEnabled = true;
|
||||
|
||||
public RenderTarget2D LightMap
|
||||
{
|
||||
get { return lightMap; }
|
||||
}
|
||||
|
||||
public LightManager(GraphicsDevice graphics)
|
||||
{
|
||||
lights = new List<LightSource>();
|
||||
|
||||
AmbientLight = new Color(80, 80, 80, 255);
|
||||
|
||||
var pp = graphics.PresentationParameters;
|
||||
|
||||
lightMap = new RenderTarget2D(graphics, Game1.GraphicsWidth, Game1.GraphicsHeight, false,
|
||||
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
|
||||
RenderTargetUsage.DiscardContents);
|
||||
|
||||
|
||||
if (alphaClearTexture==null)
|
||||
{
|
||||
alphaClearTexture = Game1.TextureLoader.FromFile("Content/Lights/alphaOne.png");
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLight(LightSource light)
|
||||
{
|
||||
lights.Add(light);
|
||||
}
|
||||
|
||||
public void RemoveLight(LightSource light)
|
||||
{
|
||||
lights.Remove(light);
|
||||
}
|
||||
|
||||
public void DrawLOS(GraphicsDevice graphics, Camera cam, Vector2 pos)
|
||||
{
|
||||
if (!LosEnabled) return;
|
||||
foreach (ConvexHull convexHull in ConvexHull.list)
|
||||
{
|
||||
convexHull.DrawShadows(graphics, cam, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawLightmap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
graphics.SetRenderTarget(lightMap);
|
||||
|
||||
Rectangle viewRect = cam.WorldView;
|
||||
viewRect.Y -= cam.WorldView.Height;
|
||||
|
||||
//clear to some small ambient light
|
||||
graphics.Clear(AmbientLight);
|
||||
|
||||
foreach (LightSource light in lights)
|
||||
{
|
||||
if (light.Color.A < 0.01f || light.Range < 0.01f) continue;
|
||||
//clear alpha to 1
|
||||
ClearAlphaToOne(graphics, spriteBatch);
|
||||
|
||||
if (!MathUtils.CircleIntersectsRectangle(light.Position, light.Range, viewRect)) continue;
|
||||
|
||||
//draw all shadows
|
||||
//write only to the alpha channel, which sets alpha to 0
|
||||
graphics.RasterizerState = RasterizerState.CullNone;
|
||||
graphics.BlendState = CustomBlendStates.WriteToAlpha;
|
||||
|
||||
foreach (ConvexHull ch in ConvexHull.list)
|
||||
{
|
||||
//draw shadow
|
||||
ch.DrawShadows(graphics, cam, light.Position, false);
|
||||
}
|
||||
|
||||
//draw the light shape
|
||||
//where Alpha is 0, nothing will be written
|
||||
spriteBatch.Begin(SpriteSortMode.Immediate, CustomBlendStates.MultiplyWithAlpha, null, null, null, null, cam.Transform);
|
||||
light.Draw(spriteBatch);
|
||||
spriteBatch.End();
|
||||
}
|
||||
//clear alpha, to avoid messing stuff up later
|
||||
ClearAlphaToOne(graphics, spriteBatch);
|
||||
graphics.SetRenderTarget(null);
|
||||
}
|
||||
|
||||
private void ClearAlphaToOne(GraphicsDevice graphics, SpriteBatch spriteBatch)
|
||||
{
|
||||
spriteBatch.Begin(SpriteSortMode.Immediate, CustomBlendStates.WriteToAlpha);
|
||||
spriteBatch.Draw(alphaClearTexture, new Rectangle(0, 0,graphics.Viewport.Width, graphics.Viewport.Height), Color.White);
|
||||
spriteBatch.End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CustomBlendStates
|
||||
{
|
||||
static CustomBlendStates()
|
||||
{
|
||||
Multiplicative = new BlendState();
|
||||
Multiplicative.ColorSourceBlend = Multiplicative.AlphaSourceBlend = Blend.Zero;
|
||||
Multiplicative.ColorDestinationBlend = Multiplicative.AlphaDestinationBlend = Blend.SourceColor;
|
||||
Multiplicative.ColorBlendFunction = Multiplicative.AlphaBlendFunction = BlendFunction.Add;
|
||||
|
||||
WriteToAlpha = new BlendState();
|
||||
WriteToAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
|
||||
|
||||
MultiplyWithAlpha = new BlendState();
|
||||
MultiplyWithAlpha.ColorDestinationBlend = MultiplyWithAlpha.AlphaDestinationBlend = Blend.One;
|
||||
MultiplyWithAlpha.ColorSourceBlend = MultiplyWithAlpha.AlphaSourceBlend = Blend.DestinationAlpha;
|
||||
}
|
||||
public static BlendState Multiplicative { get; private set; }
|
||||
public static BlendState WriteToAlpha { get; private set; }
|
||||
public static BlendState MultiplyWithAlpha { get; private set; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
73
Subsurface/Source/Map/Location.cs
Normal file
73
Subsurface/Source/Map/Location.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
|
||||
class Location
|
||||
{
|
||||
private string name;
|
||||
|
||||
private Vector2 mapPosition;
|
||||
|
||||
private LocationType type;
|
||||
|
||||
private HireManager hireManager;
|
||||
|
||||
public List<LocationConnection> Connections;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public Vector2 MapPosition
|
||||
{
|
||||
get { return mapPosition; }
|
||||
}
|
||||
|
||||
public bool Discovered;
|
||||
|
||||
public LocationType Type
|
||||
{
|
||||
get { return type; }
|
||||
}
|
||||
|
||||
public HireManager HireManager
|
||||
{
|
||||
get { return hireManager; }
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition)
|
||||
{
|
||||
this.type = LocationType.Random();
|
||||
|
||||
this.name = RandomName(type);
|
||||
|
||||
this.mapPosition = mapPosition;
|
||||
|
||||
if (type.HasHireableCharacters)
|
||||
{
|
||||
hireManager = new HireManager();
|
||||
hireManager.GenerateCharacters(Character.HumanConfigFile, 10);
|
||||
}
|
||||
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
public static Location CreateRandom(Vector2 position)
|
||||
{
|
||||
return new Location(position);
|
||||
}
|
||||
|
||||
private string RandomName(LocationType type)
|
||||
{
|
||||
string name = ToolBox.GetRandomLine("Content/Map/locationNames.txt");
|
||||
int nameFormatIndex = Rand.Int(type.NameFormats.Count, false);
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Subsurface/Source/Map/LocationType.cs
Normal file
97
Subsurface/Source/Map/LocationType.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class LocationType
|
||||
{
|
||||
private static List<LocationType> list = new List<LocationType>();
|
||||
//sum of the commonness-values of each location type
|
||||
private static int totalWeight;
|
||||
|
||||
private string name;
|
||||
|
||||
private int commonness;
|
||||
|
||||
private List<string> nameFormats;
|
||||
|
||||
private Sprite sprite;
|
||||
|
||||
public bool HasHireableCharacters
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public List<string> NameFormats
|
||||
{
|
||||
get { return nameFormats; }
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return sprite; }
|
||||
}
|
||||
|
||||
private LocationType(XElement element)
|
||||
{
|
||||
name = element.Name.ToString();
|
||||
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
totalWeight += commonness;
|
||||
|
||||
HasHireableCharacters = ToolBox.GetAttributeBool(element, "hireablecharacters", false);
|
||||
|
||||
nameFormats = new List<string>();
|
||||
foreach (XAttribute nameFormat in element.Element("nameformats").Attributes())
|
||||
{
|
||||
nameFormats.Add(nameFormat.Value.ToString());
|
||||
}
|
||||
|
||||
string spritePath = ToolBox.GetAttributeString(element, "symbol", "Content/Map/beaconSymbol.png");
|
||||
sprite = new Sprite(spritePath, new Microsoft.Xna.Framework.Vector2(0.5f, 0.5f));
|
||||
//sprite.Origin = ;
|
||||
|
||||
}
|
||||
|
||||
public static LocationType Random()
|
||||
{
|
||||
Debug.Assert(list.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
|
||||
int randInt = Rand.Int(totalWeight, false);
|
||||
|
||||
foreach (LocationType type in list)
|
||||
{
|
||||
if (randInt < type.commonness) return type;
|
||||
randInt -= type.commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Init(string file)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
|
||||
if (doc==null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
LocationType locationType = new LocationType(element);
|
||||
list.Add(locationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
441
Subsurface/Source/Map/Map.cs
Normal file
441
Subsurface/Source/Map/Map.cs
Normal file
@@ -0,0 +1,441 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Map
|
||||
{
|
||||
Vector2 difficultyIncrease = new Vector2(5.0f,10.0f);
|
||||
Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f);
|
||||
|
||||
private List<Level> levels;
|
||||
|
||||
private List<Location> locations;
|
||||
|
||||
private List<LocationConnection> connections;
|
||||
|
||||
private string seed;
|
||||
private int size;
|
||||
|
||||
private static Sprite iceTexture;
|
||||
private static Texture2D iceCraters;
|
||||
private static Texture2D iceCrack;
|
||||
|
||||
private Location currentLocation;
|
||||
private Location selectedLocation;
|
||||
|
||||
private LocationConnection selectedConnection;
|
||||
|
||||
public Location CurrentLocation
|
||||
{
|
||||
get { return currentLocation; }
|
||||
}
|
||||
|
||||
public int CurrentLocationIndex
|
||||
{
|
||||
get { return locations.IndexOf(currentLocation); }
|
||||
}
|
||||
|
||||
public Location SelectedLocation
|
||||
{
|
||||
get { return selectedLocation; }
|
||||
}
|
||||
|
||||
public LocationConnection SelectedConnection
|
||||
{
|
||||
get { return selectedConnection; }
|
||||
}
|
||||
|
||||
public string Seed
|
||||
{
|
||||
get { return seed; }
|
||||
}
|
||||
|
||||
public Map(string seed, int size)
|
||||
{
|
||||
this.seed = seed;
|
||||
|
||||
this.size = size;
|
||||
|
||||
levels = new List<Level>();
|
||||
|
||||
locations = new List<Location>();
|
||||
|
||||
connections = new List<LocationConnection>();
|
||||
|
||||
if (iceTexture==null) iceTexture = new Sprite("Content/Map/iceSurface.png", Vector2.Zero);
|
||||
if (iceCraters == null) iceCraters = Game1.TextureLoader.FromFile("Content/Map/iceCraters.png");
|
||||
if (iceCrack == null) iceCrack = Game1.TextureLoader.FromFile("Content/Map/iceCrack.png");
|
||||
|
||||
Rand.SetSyncedSeed(this.seed.GetHashCode());
|
||||
|
||||
GenerateLocations();
|
||||
|
||||
currentLocation = locations[locations.Count / 2];
|
||||
GenerateDifficulties(currentLocation, new List<LocationConnection> (connections), 10.0f);
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
connection.Level = Level.CreateRandom(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLocations()
|
||||
{
|
||||
Voronoi voronoi = new Voronoi(0.5f);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
sites.Add(new Vector2(Rand.Range(0.0f, size, false), Rand.Range(0.0f, size, false)));
|
||||
}
|
||||
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
|
||||
|
||||
sites.Clear();
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.point1 == edge.point2) continue;
|
||||
|
||||
//remove points from the edge of the map
|
||||
if (edge.point1.X == 0 || edge.point1.X == size) continue;
|
||||
if (edge.point1.Y == 0 || edge.point1.Y == size) continue;
|
||||
if (edge.point2.X == 0 || edge.point2.X == size) continue;
|
||||
if (edge.point2.Y == 0 || edge.point2.Y == size) continue;
|
||||
|
||||
Location[] newLocations = new Location[2];
|
||||
newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2);
|
||||
newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2));
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (newLocations[i] != null) continue;
|
||||
|
||||
Vector2[] points = new Vector2[] { edge.point1, edge.point2 };
|
||||
|
||||
int positionIndex = Rand.Int(1,false);
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
|
||||
|
||||
newLocations[i] = Location.CreateRandom(position);
|
||||
locations.Add(newLocations[i]);
|
||||
}
|
||||
int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode());
|
||||
connections.Add(new LocationConnection(newLocations[0], newLocations[1]));
|
||||
}
|
||||
|
||||
float minDistance = 50.0f;
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
LocationConnection connection = connections[i];
|
||||
|
||||
if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
locations.Remove(connection.Locations[0]);
|
||||
connections.Remove(connection);
|
||||
|
||||
foreach (LocationConnection connection2 in connections)
|
||||
{
|
||||
if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1];
|
||||
if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
connection.Locations[0].Connections.Add(connection);
|
||||
connection.Locations[1].Connections.Add(connection);
|
||||
}
|
||||
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
i = Math.Min(i, connections.Count - 1);
|
||||
|
||||
LocationConnection connection = connections[i];
|
||||
|
||||
for (int n = Math.Min(i - 1,connections.Count - 1); n >= 0; n--)
|
||||
{
|
||||
if (connection.Locations.Contains(connections[n].Locations[0])
|
||||
&& connection.Locations.Contains(connections[n].Locations[1]))
|
||||
{
|
||||
connections.RemoveAt(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
Vector2 start = connection.Locations[0].MapPosition;
|
||||
Vector2 end = connection.Locations[1].MapPosition;
|
||||
int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f));
|
||||
connection.CrackSegments = GenerateCrack(start, end, generations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void GenerateDifficulties(Location start, List<LocationConnection> locations, float currDifficulty)
|
||||
{
|
||||
//start.Difficulty = currDifficulty;
|
||||
currDifficulty += Rand.Range(difficultyIncrease.X, difficultyIncrease.Y, false);
|
||||
if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, false)) currDifficulty = 10.0f;
|
||||
|
||||
foreach (LocationConnection connection in start.Connections)
|
||||
{
|
||||
if (!locations.Contains(connection)) continue;
|
||||
|
||||
Location nextLocation = connection.OtherLocation(start);
|
||||
locations.Remove(connection);
|
||||
|
||||
connection.Difficulty = currDifficulty;
|
||||
|
||||
GenerateDifficulties(nextLocation, locations, currDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Vector2[]> GenerateCrack(Vector2 start, Vector2 end, int generations)
|
||||
{
|
||||
List<Vector2[]> segments = new List<Vector2[]>();
|
||||
|
||||
segments.Add(new Vector2[] {start, end});
|
||||
|
||||
float offsetAmount = 5.0f;
|
||||
|
||||
for (int n = 0; n < generations; n++)
|
||||
{
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
Vector2 startSegment = segments[i][0];
|
||||
Vector2 endSegment = segments[i][1];
|
||||
|
||||
segments.RemoveAt(i);
|
||||
|
||||
Vector2 midPoint = (startSegment + endSegment) / 2.0f;
|
||||
|
||||
Vector2 normal = Vector2.Normalize(endSegment - startSegment);
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
midPoint += normal * Rand.Range(-offsetAmount, offsetAmount, false);
|
||||
|
||||
segments.Insert(i, new Vector2[] { startSegment, midPoint });
|
||||
segments.Insert(i+1, new Vector2[] { midPoint, endSegment });
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
public void MoveToNextLocation()
|
||||
{
|
||||
currentLocation = selectedLocation;
|
||||
selectedLocation = null;
|
||||
}
|
||||
|
||||
public void SetLocation(int index)
|
||||
{
|
||||
if (index < 0 || index >= locations.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Location index out of bounds");
|
||||
return;
|
||||
}
|
||||
currentLocation = locations[index];
|
||||
}
|
||||
|
||||
private Location highlightedLocation;
|
||||
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float scale = 1.0f)
|
||||
{
|
||||
//GUI.DrawRectangle(spriteBatch, rect, Color.DarkBlue, true);
|
||||
|
||||
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
|
||||
Vector2 offset = -currentLocation.MapPosition;
|
||||
|
||||
iceTexture.DrawTiled(spriteBatch, new Vector2(rect.X, rect.Y), new Vector2(rect.Width, rect.Height), offset, Color.White);
|
||||
//spriteBatch.Draw(iceTexture, offset, rect, null, null, 0f, null, Color.White, SpriteEffects.None, 0.0f);
|
||||
|
||||
//Vector2 scale = new Vector2((float)rect.Width/ size, (float)rect.Height/size);
|
||||
|
||||
float maxDist = 20.0f;
|
||||
float closestDist = 0.0f;
|
||||
highlightedLocation = null;
|
||||
for (int i = 0; i < locations.Count;i++ )
|
||||
{
|
||||
Location location = locations[i];
|
||||
Vector2 pos = rectCenter + (location.MapPosition+offset) * scale;
|
||||
|
||||
if (!rect.Contains(pos)) continue;
|
||||
|
||||
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
|
||||
if (dist < maxDist && (highlightedLocation == null || dist < closestDist))
|
||||
{
|
||||
closestDist = dist;
|
||||
highlightedLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
Color crackColor = Color.White * Math.Max(connection.Difficulty/100.0f, 0.5f);
|
||||
|
||||
if (highlightedLocation != currentLocation &&
|
||||
connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation))
|
||||
{
|
||||
crackColor = Color.Red;
|
||||
|
||||
if (PlayerInput.LeftButtonClicked()&&
|
||||
selectedLocation != highlightedLocation && highlightedLocation != null)
|
||||
{
|
||||
selectedConnection = connection;
|
||||
selectedLocation = highlightedLocation;
|
||||
Game1.LobbyScreen.SelectLocation(highlightedLocation, connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (selectedLocation != currentLocation &&
|
||||
(connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation)))
|
||||
{
|
||||
crackColor = Color.Red;
|
||||
}
|
||||
|
||||
foreach (Vector2[] segment in connection.CrackSegments)
|
||||
{
|
||||
Vector2 start = rectCenter + (segment[0] + offset) * scale;
|
||||
Vector2 end = rectCenter + (segment[1] + offset) * scale;
|
||||
|
||||
if (!rect.Contains(start) || !rect.Contains(end)) continue;
|
||||
|
||||
float dist = Vector2.Distance(start, end);
|
||||
|
||||
spriteBatch.Draw(iceCrack,
|
||||
new Rectangle((int)start.X, (int)start.Y, (int)dist+2, 30),
|
||||
new Rectangle(0, 0, iceCrack.Width, 60), crackColor, MathUtils.VectorToAngle(end -start),
|
||||
new Vector2(0, 30), SpriteEffects.None, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < locations.Count; i++)
|
||||
{
|
||||
Location location = locations[i];
|
||||
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
|
||||
|
||||
if (!rect.Contains(pos)) continue;
|
||||
|
||||
Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation))==null ? Color.White : Color.Green;
|
||||
|
||||
color *= (location.Discovered) ? 0.8f : 0.4f;
|
||||
|
||||
if (location == currentLocation) color = Color.Orange;
|
||||
|
||||
location.Type.Sprite.Draw(spriteBatch, pos, color, 0.0f, scale/3.0f);
|
||||
|
||||
//int imgIndex = i % 16;
|
||||
//int xCell = imgIndex % 4;
|
||||
//int yCell = (int)Math.Floor(imgIndex / 4.0f);
|
||||
//spriteBatch.Draw(iceCraters, pos,
|
||||
// new Rectangle(xCell * 64, yCell * 64, 64, 64),
|
||||
// Color.White, i,
|
||||
// new Vector2(32, 32), 0.5f*scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++ )
|
||||
{
|
||||
Location location = (i == 0) ? highlightedLocation : selectedLocation;
|
||||
if (i == 2) location = currentLocation;
|
||||
|
||||
if (location == null) continue;
|
||||
|
||||
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
|
||||
pos.X = (int)pos.X;
|
||||
pos.Y = (int)pos.Y;
|
||||
if (highlightedLocation == location)
|
||||
{
|
||||
spriteBatch.DrawString(GUI.Font, location.Name, pos + new Vector2(0, 50), Color.DarkRed, 0.0f, GUI.Font.MeasureString(location.Name)/2.0f, 1.0f, SpriteEffects.None, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LocationConnection
|
||||
{
|
||||
private Location[] locations;
|
||||
private Level level;
|
||||
|
||||
public float Difficulty;
|
||||
|
||||
public List<Vector2[]> CrackSegments;
|
||||
|
||||
private int questsCompleted;
|
||||
|
||||
private Quest quest;
|
||||
public Quest Quest
|
||||
{
|
||||
get
|
||||
{
|
||||
if (quest==null || quest.Completed)
|
||||
{
|
||||
if (quest !=null && quest.Completed) questsCompleted++;
|
||||
|
||||
int seed = (int)locations[0].MapPosition.X + (int)locations[0].MapPosition.Y * 100;
|
||||
seed += (int)locations[1].MapPosition.X*10000 + (int)locations[1].MapPosition.Y * 1000000;
|
||||
|
||||
Random rand = new Random(seed + questsCompleted);
|
||||
|
||||
if (rand.NextDouble() < 0.3f) return null;
|
||||
|
||||
quest = Quest.LoadRandom(locations, rand);
|
||||
}
|
||||
|
||||
return quest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Location[] Locations
|
||||
{
|
||||
get { return locations; }
|
||||
}
|
||||
|
||||
public Level Level
|
||||
{
|
||||
get { return level; }
|
||||
set { level = value; }
|
||||
}
|
||||
|
||||
public LocationConnection(Location location1, Location location2)
|
||||
{
|
||||
locations = new Location[] { location1, location2 };
|
||||
|
||||
questsCompleted = 0;
|
||||
}
|
||||
|
||||
public Location OtherLocation(Location location)
|
||||
{
|
||||
if (locations[0] == location)
|
||||
{
|
||||
return locations[1];
|
||||
}
|
||||
else if (locations[1] == location)
|
||||
{
|
||||
return locations[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
471
Subsurface/Source/Map/MapEntity.cs
Normal file
471
Subsurface/Source/Map/MapEntity.cs
Normal file
@@ -0,0 +1,471 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class MapEntity : Entity
|
||||
{
|
||||
public static List<MapEntity> mapEntityList = new List<MapEntity>();
|
||||
|
||||
//which entities have been selected for editing
|
||||
protected static List<MapEntity> selectedList = new List<MapEntity>();
|
||||
|
||||
protected static GUIComponent editingHUD;
|
||||
|
||||
protected static Vector2 selectionPos = Vector2.Zero;
|
||||
protected static Vector2 selectionSize = Vector2.Zero;
|
||||
|
||||
protected static Vector2 startMovingPos = Vector2.Zero;
|
||||
|
||||
protected List<int> linkedToID;
|
||||
|
||||
//observable collection because some entities may need to be notified when the collection is modified
|
||||
public ObservableCollection<MapEntity> linkedTo;
|
||||
|
||||
//protected float soundRange;
|
||||
//protected float sightRange;
|
||||
|
||||
//is the mouse inside the rect
|
||||
protected bool isHighlighted;
|
||||
|
||||
protected bool isSelected;
|
||||
|
||||
private static bool disableSelect;
|
||||
public static bool DisableSelect
|
||||
{
|
||||
get { return disableSelect; }
|
||||
set {
|
||||
disableSelect = value;
|
||||
if (disableSelect==true)
|
||||
{
|
||||
startMovingPos = Vector2.Zero;
|
||||
selectionSize = Vector2.Zero;
|
||||
selectionPos = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveWithLevel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//the position and dimensions of the entity
|
||||
protected Rectangle rect;
|
||||
|
||||
public virtual Rectangle Rect {
|
||||
get { return rect; }
|
||||
set { rect = value; }
|
||||
}
|
||||
|
||||
public virtual Sprite sprite
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public virtual bool IsLinkable
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual Vector2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 rectPos = new Vector2(
|
||||
rect.X + rect.Width / 2.0f,
|
||||
rect.Y - rect.Height / 2.0f);
|
||||
|
||||
if (MoveWithLevel) rectPos += Level.Loaded.Position;
|
||||
return rectPos;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 SimPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return ConvertUnits.ToSimUnits(Position);
|
||||
}
|
||||
}
|
||||
|
||||
public float SoundRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (aiTarget == null) return 0.0f;
|
||||
return aiTarget.SoundRange;
|
||||
}
|
||||
set { aiTarget.SoundRange = value; }
|
||||
}
|
||||
|
||||
public float SightRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (aiTarget == null) return 0.0f;
|
||||
return aiTarget.SightRange;
|
||||
}
|
||||
set { aiTarget.SightRange = value; }
|
||||
}
|
||||
|
||||
public bool IsHighlighted {
|
||||
get { return isHighlighted; }
|
||||
set { isHighlighted = value; }
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get { return isSelected; }
|
||||
set { isSelected = value; }
|
||||
}
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get { return ""; }
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
}
|
||||
|
||||
public virtual bool Contains(Vector2 position)
|
||||
{
|
||||
return (Submarine.RectContains(rect, position));
|
||||
}
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch, bool editing) {}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
mapEntityList.Remove(this);
|
||||
|
||||
if (aiTarget != null) aiTarget.Remove();
|
||||
|
||||
if (linkedTo != null)
|
||||
{
|
||||
foreach (MapEntity e in linkedTo)
|
||||
{
|
||||
e.RemoveLinked(this);
|
||||
}
|
||||
linkedTo.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call Update() on every object in Entity.list
|
||||
/// </summary>
|
||||
public static void UpdateAll(Camera cam, float deltaTime)
|
||||
{
|
||||
foreach (Item item in Item.itemList)
|
||||
{
|
||||
item.Updated = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mapEntityList.Count; i++)
|
||||
{
|
||||
mapEntityList[i].Update(cam, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Update(Camera cam, float deltaTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// Update the selection logic in editmap-screen
|
||||
/// </summary>
|
||||
public static void UpdateSelecting(Camera cam)
|
||||
{
|
||||
if (GUIComponent.MouseOn != null) return;
|
||||
|
||||
if (DisableSelect)
|
||||
{
|
||||
DisableSelect = false;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
e.isHighlighted = false;
|
||||
e.isSelected = false;
|
||||
}
|
||||
|
||||
if (MapEntityPrefab.Selected != null)
|
||||
{
|
||||
selectionPos = Vector2.Zero;
|
||||
selectedList.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.Delete))
|
||||
{
|
||||
foreach (MapEntity e in selectedList) e.Remove();
|
||||
selectedList.Clear();
|
||||
}
|
||||
|
||||
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
|
||||
position = cam.ScreenToWorld(position);
|
||||
|
||||
MapEntity highLightedEntity = null;
|
||||
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (highLightedEntity == null || e.sprite == null ||
|
||||
(highLightedEntity.sprite!=null && e.sprite.Depth < highLightedEntity.sprite.Depth))
|
||||
{
|
||||
if (e.Contains(position)) highLightedEntity = e;
|
||||
}
|
||||
e.isSelected = false;
|
||||
}
|
||||
|
||||
if (highLightedEntity != null)
|
||||
highLightedEntity.isHighlighted = true;
|
||||
|
||||
foreach (MapEntity e in selectedList)
|
||||
{
|
||||
e.isSelected = true;
|
||||
}
|
||||
|
||||
//started moving selected entities
|
||||
if (startMovingPos != Vector2.Zero)
|
||||
{
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
|
||||
{
|
||||
//mouse released -> move the entities to the new position of the mouse
|
||||
|
||||
Vector2 moveAmount = position - startMovingPos;
|
||||
moveAmount = Submarine.VectorToWorldGrid(moveAmount);
|
||||
|
||||
if (moveAmount != Vector2.Zero)
|
||||
{
|
||||
foreach (MapEntity e in selectedList)
|
||||
e.Move(moveAmount);
|
||||
}
|
||||
|
||||
startMovingPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
}
|
||||
//started dragging a "selection rectangle"
|
||||
else if (selectionPos != Vector2.Zero)
|
||||
{
|
||||
selectionSize.X = position.X - selectionPos.X;
|
||||
selectionSize.Y = selectionPos.Y - position.Y;
|
||||
|
||||
List<MapEntity> newSelection = new List<MapEntity>();// FindSelectedEntities(selectionPos, selectionSize);
|
||||
if (Math.Abs(selectionSize.X) > Submarine.GridSize.X || Math.Abs(selectionSize.Y) > Submarine.GridSize.Y)
|
||||
{
|
||||
newSelection = FindSelectedEntities(selectionPos, selectionSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (highLightedEntity != null) newSelection.Add(highLightedEntity);
|
||||
}
|
||||
|
||||
foreach (MapEntity e in newSelection)
|
||||
{
|
||||
e.isHighlighted = true;
|
||||
}
|
||||
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
|
||||
{
|
||||
if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.LeftControl) ||
|
||||
PlayerInput.GetKeyboardState.IsKeyDown(Keys.RightControl))
|
||||
{
|
||||
foreach (MapEntity e in newSelection)
|
||||
{
|
||||
bool alreadySelected = false;
|
||||
|
||||
foreach (MapEntity e2 in selectedList)
|
||||
{
|
||||
if (e.ID == e2.ID) alreadySelected = true;
|
||||
}
|
||||
|
||||
if (alreadySelected)
|
||||
selectedList.Remove(e);
|
||||
else
|
||||
selectedList.Add(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedList = newSelection;
|
||||
}
|
||||
|
||||
selectionPos = Vector2.Zero;
|
||||
Debug.WriteLine("zero");
|
||||
selectionSize = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
//default, not doing anything specific yet
|
||||
else
|
||||
{
|
||||
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed &&
|
||||
PlayerInput.GetKeyboardState.IsKeyUp(Keys.Space))
|
||||
{
|
||||
//if clicking a selected entity, start moving it
|
||||
foreach (MapEntity e in selectedList)
|
||||
{
|
||||
if (e.Contains(position)) startMovingPos = position;
|
||||
}
|
||||
|
||||
selectionPos = position;
|
||||
Debug.WriteLine("pos");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the "selection rectangle" and outlines of entities that are being dragged (if any)
|
||||
/// </summary>
|
||||
public static void DrawSelecting(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (GUIComponent.MouseOn != null) return;
|
||||
|
||||
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
|
||||
position = cam.ScreenToWorld(position);
|
||||
|
||||
if (startMovingPos != Vector2.Zero)
|
||||
{
|
||||
Vector2 moveAmount = position - startMovingPos;
|
||||
moveAmount = Submarine.VectorToWorldGrid(moveAmount);
|
||||
moveAmount.Y = -moveAmount.Y;
|
||||
//started moving the selected entities
|
||||
if (moveAmount != Vector2.Zero)
|
||||
{
|
||||
foreach (MapEntity e in selectedList)
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(e.rect.X, -e.rect.Y) + moveAmount,
|
||||
new Vector2(e.rect.Width, e.rect.Height),
|
||||
Color.DarkRed);
|
||||
|
||||
//stop dragging the "selection rectangle"
|
||||
selectionPos = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
if (selectionPos != null && selectionPos != Vector2.Zero)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call DrawEditing() if only one entity is selected
|
||||
/// </summary>
|
||||
public static void Edit(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (selectedList.Count == 1)
|
||||
{
|
||||
selectedList[0].DrawEditing(spriteBatch, cam);
|
||||
}
|
||||
else
|
||||
{
|
||||
editingHUD = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SelectEntity(MapEntity entity)
|
||||
{
|
||||
foreach (MapEntity e in selectedList)
|
||||
{
|
||||
e.isSelected = false;
|
||||
}
|
||||
selectedList.Clear();
|
||||
|
||||
entity.isSelected = true;
|
||||
selectedList.Add(entity);
|
||||
}
|
||||
|
||||
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) {}
|
||||
|
||||
public static List<MapEntity> FindMapEntities(Vector2 pos)
|
||||
{
|
||||
List<MapEntity> foundEntities = new List<MapEntity>();
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (Submarine.RectContains(e.rect, pos)) foundEntities.Add(e);
|
||||
}
|
||||
return foundEntities;
|
||||
}
|
||||
|
||||
public static MapEntity FindMapEntity(Vector2 pos)
|
||||
{
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (Submarine.RectContains(e.rect, pos)) return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find entities whose rect intersects with the "selection rect"
|
||||
/// </summary>
|
||||
public static List<MapEntity> FindSelectedEntities(Vector2 pos, Vector2 size)
|
||||
{
|
||||
List<MapEntity> foundEntities = new List<MapEntity>();
|
||||
|
||||
Rectangle selectionRect = Submarine.AbsRect(pos, size);
|
||||
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (Submarine.RectsOverlap(selectionRect, e.rect))
|
||||
foundEntities.Add(e);
|
||||
}
|
||||
|
||||
return foundEntities;
|
||||
}
|
||||
|
||||
|
||||
public virtual XElement Save(XDocument doc)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving entity " + GetType() + " failed.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the linkedTo-lists of the entities based on the linkedToID-lists
|
||||
/// Has to be done after all the entities have been loaded (an entity can't
|
||||
/// be linked to some other entity that hasn't been loaded yet)
|
||||
/// </summary>
|
||||
public static void LinkAll()
|
||||
{
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (e.linkedToID == null) continue;
|
||||
if (e.linkedToID.Count == 0) continue;
|
||||
|
||||
e.linkedTo.Clear();
|
||||
|
||||
foreach (int i in e.linkedToID)
|
||||
{
|
||||
MapEntity linked = FindEntityByID(i) as MapEntity;
|
||||
|
||||
if (linked != null)
|
||||
e.linkedTo.Add(linked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLinked(MapEntity e)
|
||||
{
|
||||
if (linkedTo == null) return;
|
||||
if (linkedTo.Contains(e)) linkedTo.Remove(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
152
Subsurface/Source/Map/MapEntityPrefab.cs
Normal file
152
Subsurface/Source/Map/MapEntityPrefab.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class MapEntityPrefab
|
||||
{
|
||||
public static List<MapEntityPrefab> list = new List<MapEntityPrefab>();
|
||||
|
||||
protected string name;
|
||||
|
||||
protected bool isLinkable;
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
//the position where the structure is being placed (needed when stretching the structure)
|
||||
protected static Vector2 placePosition;
|
||||
|
||||
protected ConstructorInfo constructor;
|
||||
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
protected bool resizeHorizontal;
|
||||
protected bool resizeVertical;
|
||||
|
||||
//which prefab has been selected for placing
|
||||
protected static MapEntityPrefab selected;
|
||||
|
||||
protected int price;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public static MapEntityPrefab Selected
|
||||
{
|
||||
get { return selected; }
|
||||
}
|
||||
|
||||
public virtual bool IsLinkable
|
||||
{
|
||||
get { return isLinkable; }
|
||||
}
|
||||
|
||||
public bool ResizeHorizontal
|
||||
{
|
||||
get { return resizeHorizontal; }
|
||||
}
|
||||
|
||||
public bool ResizeVertical
|
||||
{
|
||||
get { return resizeVertical; }
|
||||
}
|
||||
|
||||
public int Price
|
||||
{
|
||||
get { return price; }
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
MapEntityPrefab ep = new MapEntityPrefab();
|
||||
ep.name = "hull";
|
||||
ep.constructor = typeof(Hull).GetConstructor(new Type[] { typeof(Rectangle) });
|
||||
ep.resizeHorizontal = true;
|
||||
ep.resizeVertical = true;
|
||||
list.Add(ep);
|
||||
|
||||
ep = new MapEntityPrefab();
|
||||
ep.name = "gap";
|
||||
ep.constructor = typeof(Gap).GetConstructor(new Type[] { typeof(Rectangle) });
|
||||
ep.resizeHorizontal = true;
|
||||
ep.resizeVertical = true;
|
||||
list.Add(ep);
|
||||
|
||||
ep = new MapEntityPrefab();
|
||||
ep.name = "waypoint";
|
||||
ep.constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(Rectangle) });
|
||||
list.Add(ep);
|
||||
}
|
||||
|
||||
|
||||
public virtual void UpdatePlacing(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
Vector2 placeSize = Submarine.GridSize;
|
||||
|
||||
if (placePosition == Vector2.Zero)
|
||||
{
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed)
|
||||
placePosition = Submarine.MouseToWorldGrid(cam);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 position = Submarine.MouseToWorldGrid(cam);
|
||||
|
||||
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
|
||||
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
|
||||
|
||||
Rectangle newRect = Submarine.AbsRect(placePosition, placeSize);
|
||||
newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X);
|
||||
newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y);
|
||||
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
|
||||
{
|
||||
object[] lobject = new object[] { newRect };
|
||||
constructor.Invoke(lobject);
|
||||
placePosition = Vector2.Zero;
|
||||
selected = null;
|
||||
}
|
||||
|
||||
newRect.Y = -newRect.Y;
|
||||
GUI.DrawRectangle(spriteBatch, newRect, Color.DarkBlue);
|
||||
}
|
||||
|
||||
if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed)
|
||||
{
|
||||
placePosition = Vector2.Zero;
|
||||
selected = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool SelectPrefab(object selection)
|
||||
{
|
||||
if ((selected = selection as MapEntityPrefab) != null)
|
||||
{
|
||||
placePosition = Vector2.Zero;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
|
||||
public static object GetSelected()
|
||||
{
|
||||
return (object)selected;
|
||||
}
|
||||
|
||||
public void DrawListLine(SpriteBatch spriteBatch, Vector2 pos, Color color)
|
||||
{
|
||||
spriteBatch.DrawString(GUI.Font, name, pos, color);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
95
Subsurface/Source/Map/Md5Hash.cs
Normal file
95
Subsurface/Source/Map/Md5Hash.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
public class Md5Hash
|
||||
{
|
||||
private string hash;
|
||||
private string shortHash;
|
||||
|
||||
public string Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public string ShortHash
|
||||
{
|
||||
get
|
||||
{
|
||||
return shortHash;
|
||||
}
|
||||
}
|
||||
|
||||
public Md5Hash(string md5Hash)
|
||||
{
|
||||
this.hash = md5Hash;
|
||||
|
||||
shortHash = GetShortHash(md5Hash);
|
||||
}
|
||||
|
||||
public Md5Hash(byte[] bytes)
|
||||
{
|
||||
hash = CalculateHash(bytes);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
public Md5Hash(FileStream fileStream)
|
||||
{
|
||||
hash = CalculateHash(fileStream);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
public Md5Hash(XDocument doc)
|
||||
{
|
||||
string docString = Regex.Replace(doc.ToString(), @"\s+", "");
|
||||
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(docString);
|
||||
|
||||
hash = CalculateHash(inputBytes);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
private string CalculateHash(FileStream stream)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] byteHash = md5.ComputeHash(stream);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string CalculateHash(byte[] bytes)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] byteHash = md5.ComputeHash(bytes);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetShortHash(string fullHash)
|
||||
{
|
||||
return fullHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
630
Subsurface/Source/Map/Structure.cs
Normal file
630
Subsurface/Source/Map/Structure.cs
Normal file
@@ -0,0 +1,630 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Subsurface.Networking;
|
||||
using Subsurface.Lights;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class WallSection
|
||||
{
|
||||
public Rectangle rect;
|
||||
public float damage;
|
||||
public Gap gap;
|
||||
|
||||
public bool isHighLighted;
|
||||
|
||||
public WallSection(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
damage = 0.0f;
|
||||
}
|
||||
|
||||
public WallSection(Rectangle rect, float damage)
|
||||
{
|
||||
this.rect = rect;
|
||||
this.damage = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
class Structure : MapEntity, IDamageable
|
||||
{
|
||||
static int wallSectionSize = 100;
|
||||
public static List<Structure> wallList = new List<Structure>();
|
||||
|
||||
ConvexHull convexHull;
|
||||
|
||||
StructurePrefab prefab;
|
||||
|
||||
//farseer physics bodies, separated by gaps
|
||||
List<Body> bodies;
|
||||
|
||||
//sections of the wall that are supposed to be rendered
|
||||
private WallSection[] sections;
|
||||
|
||||
bool isHorizontal;
|
||||
|
||||
public override Sprite sprite
|
||||
{
|
||||
get { return prefab.sprite; }
|
||||
}
|
||||
|
||||
public bool IsPlatform
|
||||
{
|
||||
get { return prefab.IsPlatform; }
|
||||
}
|
||||
|
||||
public Direction StairDirection
|
||||
{
|
||||
get { return prefab.StairDirection; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "structure"; }
|
||||
}
|
||||
|
||||
public bool HasBody
|
||||
{
|
||||
get { return prefab.HasBody; }
|
||||
}
|
||||
|
||||
public bool CastShadow
|
||||
{
|
||||
get { return prefab.CastShadow; }
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public int SectionCount
|
||||
{
|
||||
get { return sections.Length; }
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get { return prefab.MaxHealth; }
|
||||
}
|
||||
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
for (int i = 0; i < sections.Count(); i++)
|
||||
{
|
||||
Rectangle r = sections[i].rect;
|
||||
r.X += (int)amount.X;
|
||||
r.Y += (int)amount.Y;
|
||||
sections[i].rect = r;
|
||||
}
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
|
||||
foreach (Body b in bodies)
|
||||
{
|
||||
b.SetTransform(b.Position + simAmount, b.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
if (convexHull!=null)
|
||||
{
|
||||
convexHull.Move(amount);
|
||||
}
|
||||
//if (gaps != null)
|
||||
//{
|
||||
// foreach (Gap g in gaps)
|
||||
// {
|
||||
// g.Move(amount);
|
||||
// //g.position.X += amount.X;
|
||||
// //g.position.Y -= amount.Y;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp)
|
||||
{
|
||||
if (rectangle.Width == 0 || rectangle.Height == 0) return;
|
||||
|
||||
rect = rectangle;
|
||||
prefab = sp;
|
||||
|
||||
isHorizontal = (rect.Width>rect.Height);
|
||||
|
||||
if (prefab.HasBody)
|
||||
{
|
||||
bodies = new List<Body>();
|
||||
//gaps = new List<Gap>();
|
||||
|
||||
Body newBody = BodyFactory.CreateRectangle(Game1.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
1.5f);
|
||||
newBody.BodyType = BodyType.Static;
|
||||
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
|
||||
newBody.Friction = 0.5f;
|
||||
|
||||
newBody.OnCollision += OnWallCollision;
|
||||
|
||||
newBody.UserData = this;
|
||||
|
||||
newBody.CollisionCategories = (prefab.IsPlatform) ? Physics.CollisionPlatform : Physics.CollisionWall;
|
||||
|
||||
bodies.Add(newBody);
|
||||
|
||||
wallList.Add(this);
|
||||
|
||||
int xsections = 1;
|
||||
int ysections = 1;
|
||||
int width, height;
|
||||
if (isHorizontal)
|
||||
{
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / wallSectionSize);
|
||||
sections = new WallSection[xsections];
|
||||
width = (int)wallSectionSize;
|
||||
height = rect.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
ysections = (int)Math.Ceiling((float)rect.Height / wallSectionSize);
|
||||
sections = new WallSection[ysections];
|
||||
width = rect.Width;
|
||||
height = (int)wallSectionSize;
|
||||
}
|
||||
|
||||
for (int x = 0; x < xsections; x++ )
|
||||
{
|
||||
for (int y = 0; y < ysections; y++)
|
||||
{
|
||||
Rectangle sectionRect = new Rectangle(rect.X + x * width, rect.Y - y * height, width, height);
|
||||
sectionRect.Width -= (int)Math.Max((sectionRect.X + sectionRect.Width) - (rect.X + rect.Width), 0.0f);
|
||||
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height)-(sectionRect.Y - sectionRect.Height), 0.0f);
|
||||
|
||||
sections[x+y] = new WallSection(sectionRect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sections = new WallSection[1];
|
||||
sections[0] = new WallSection(rect);
|
||||
|
||||
if (StairDirection!=Direction.None)
|
||||
{
|
||||
bodies = new List<Body>();
|
||||
|
||||
Body newBody = BodyFactory.CreateRectangle(Game1.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0) + Submarine.GridSize.X*3.0f),
|
||||
ConvertUnits.ToSimUnits(10),
|
||||
1.5f);
|
||||
|
||||
newBody.BodyType = BodyType.Static;
|
||||
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + rect.Width / 2.0f);
|
||||
stairPos += new Vector2(
|
||||
(StairDirection == Direction.Right) ? -Submarine.GridSize.X*1.5f : Submarine.GridSize.X*1.5f,
|
||||
- Submarine.GridSize.Y*2.0f);
|
||||
|
||||
newBody.Position = ConvertUnits.ToSimUnits(stairPos);
|
||||
newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
|
||||
newBody.Friction = 0.8f;
|
||||
|
||||
newBody.CollisionCategories = Physics.CollisionStairs;
|
||||
|
||||
newBody.UserData = this;
|
||||
bodies.Add(newBody);
|
||||
|
||||
//newBody = BodyFactory.CreateRectangle(Game1.World,
|
||||
// ConvertUnits.ToSimUnits(Submarine.GridSize.X*2),
|
||||
// ConvertUnits.ToSimUnits(10.0f),
|
||||
// 1.5f);
|
||||
|
||||
//newBody.BodyType = BodyType.Static;
|
||||
////newBody.IsSensor = true;
|
||||
|
||||
//newBody.Position = ConvertUnits.ToSimUnits(
|
||||
// new Vector2(Position.X + (rect.Width/2 + Submarine.GridSize.X) * ((StairDirection == Direction.Right) ? -1.0f : 1.0f), rect.Y + 5.0f));
|
||||
////newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
|
||||
////newBody.Friction = 0.8f;
|
||||
|
||||
//newBody.CollisionCategories = Physics.CollisionStairs;
|
||||
|
||||
//newBody.UserData = this;
|
||||
|
||||
//bodies.Add(newBody);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (prefab.CastShadow)
|
||||
{
|
||||
|
||||
Vector2[] corners = new Vector2[4];
|
||||
corners[0] = new Vector2(rect.X, rect.Y - rect.Height);
|
||||
corners[1] = new Vector2(rect.X, rect.Y);
|
||||
corners[2] = new Vector2(rect.Right, rect.Y);
|
||||
corners[3] = new Vector2(rect.Right, rect.Y - rect.Height);
|
||||
|
||||
convexHull = new ConvexHull(corners, Color.Black);
|
||||
}
|
||||
|
||||
mapEntityList.Add(this);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
if (wallList.Contains(this)) wallList.Remove(this);
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
foreach (Body b in bodies)
|
||||
Game1.World.RemoveBody(b);
|
||||
}
|
||||
|
||||
if (convexHull != null) convexHull.Remove();
|
||||
}
|
||||
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch, bool editing)
|
||||
{
|
||||
if (prefab.sprite == null) return;
|
||||
|
||||
Color color = (isHighlighted) ? Color.Green : Color.White;
|
||||
if (isSelected && editing) color = Color.Red;
|
||||
|
||||
prefab.sprite.DrawTiled(spriteBatch, new Vector2(rect.X, -rect.Y), new Vector2(rect.Width, rect.Height), Vector2.Zero, color);
|
||||
|
||||
foreach (WallSection s in sections)
|
||||
{
|
||||
if (s.isHighLighted)
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle((int)s.rect.X, (int)-s.rect.Y, (int)s.rect.Width, (int)s.rect.Height),
|
||||
new Color((s.damage / prefab.MaxHealth), 1.0f - (s.damage / prefab.MaxHealth), 0.0f, 1.0f), true);
|
||||
|
||||
s.isHighLighted = false;
|
||||
|
||||
if (s.damage == 0.0f) continue;
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle((int)s.rect.X, (int)-s.rect.Y, (int)s.rect.Width, (int)s.rect.Height),
|
||||
Color.Black * (s.damage / prefab.MaxHealth), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
//Structure structure = f1.Body.UserData as Structure;
|
||||
|
||||
//if (f2.Body.UserData as Item != null)
|
||||
//{
|
||||
// if (prefab.IsPlatform || prefab.StairDirection != Direction.None) return false;
|
||||
//}
|
||||
|
||||
if (prefab.IsPlatform)
|
||||
{
|
||||
Limb limb;
|
||||
if ((limb = f2.Body.UserData as Limb) != null)
|
||||
{
|
||||
if (limb.character.AnimController.IgnorePlatforms) return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefab.IsPlatform && prefab.StairDirection == Direction.None)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
|
||||
|
||||
int section = FindSectionIndex(pos);
|
||||
if (section>0)
|
||||
{
|
||||
Vector2 normal = contact.Manifold.LocalNormal;
|
||||
|
||||
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal)*f2.Body.Mass*0.1f;
|
||||
|
||||
if (impact < 10.0f) return true;
|
||||
|
||||
AmbientSoundManager.PlayDamageSound(DamageSoundType.StructureBlunt, impact,
|
||||
new Vector2(
|
||||
sections[section].rect.X + sections[section].rect.Width / 2,
|
||||
sections[section].rect.Y - sections[section].rect.Height / 2));
|
||||
|
||||
AddDamage(section, impact);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void HighLightSection(int sectionIndex)
|
||||
{
|
||||
sections[sectionIndex].isHighLighted = true;
|
||||
}
|
||||
|
||||
public bool SectionHasHole(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
|
||||
|
||||
return (sections[sectionIndex].damage>=prefab.MaxHealth);
|
||||
}
|
||||
|
||||
public void AddDamage(int sectionIndex, float damage)
|
||||
{
|
||||
if (!prefab.HasBody || prefab.IsPlatform) return;
|
||||
|
||||
if (Game1.Client==null)
|
||||
SetDamage(sectionIndex, sections[sectionIndex].damage + damage);
|
||||
|
||||
}
|
||||
|
||||
public int FindSectionIndex(Vector2 pos)
|
||||
{
|
||||
int index = (isHorizontal) ?
|
||||
(int)Math.Floor((pos.X - rect.X) / wallSectionSize) :
|
||||
(int)Math.Floor((rect.Y - pos.Y) / wallSectionSize);
|
||||
|
||||
if (index < 0 || index > sections.Length - 1) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
public float SectionDamage(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return 0.0f;
|
||||
|
||||
return sections[sectionIndex].damage;
|
||||
}
|
||||
|
||||
public Vector2 SectionPosition(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return Vector2.Zero;
|
||||
|
||||
return new Vector2(
|
||||
sections[sectionIndex].rect.X + sections[sectionIndex].rect.Width / 2.0f,
|
||||
sections[sectionIndex].rect.Y - sections[sectionIndex].rect.Height / 2.0f);
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound = false)
|
||||
{
|
||||
if (!prefab.HasBody || prefab.IsPlatform) return new AttackResult(0.0f, 0.0f);
|
||||
|
||||
int i = FindSectionIndex(ConvertUnits.ToDisplayUnits(position));
|
||||
if (i == -1) return new AttackResult(0.0f, 0.0f);
|
||||
|
||||
Game1.ParticleManager.CreateParticle("dustcloud", ConvertUnits.ToSimUnits(SectionPosition(i)), 0.0f, 0.0f);
|
||||
|
||||
if (playSound && !SectionHasHole(i))
|
||||
{
|
||||
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash;
|
||||
AmbientSoundManager.PlayDamageSound(damageSoundType, amount, position);
|
||||
}
|
||||
|
||||
AddDamage(i, amount);
|
||||
|
||||
return new AttackResult(amount, 0.0f);
|
||||
}
|
||||
|
||||
private void SetDamage(int sectionIndex, float damage)
|
||||
{
|
||||
if (!prefab.HasBody) return;
|
||||
|
||||
if (damage != sections[sectionIndex].damage)
|
||||
new NetworkEvent(ID, false);
|
||||
|
||||
if (damage < prefab.MaxHealth*0.5f)
|
||||
{
|
||||
if (sections[sectionIndex].gap != null)
|
||||
{
|
||||
//remove existing gap if damage is below 50%
|
||||
sections[sectionIndex].gap.Remove();
|
||||
sections[sectionIndex].gap = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sections[sectionIndex].gap == null)
|
||||
{
|
||||
Rectangle gapRect = sections[sectionIndex].rect;
|
||||
gapRect.X -= 10;
|
||||
gapRect.Y += 10;
|
||||
gapRect.Width += 20;
|
||||
gapRect.Height += 20;
|
||||
sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal);
|
||||
}
|
||||
}
|
||||
|
||||
if (sections[sectionIndex].gap != null)
|
||||
sections[sectionIndex].gap.Open = (float)Math.Pow(((damage / prefab.MaxHealth)-0.5)*2.0, 2.0);
|
||||
|
||||
bool hadHole = SectionHasHole(sectionIndex);
|
||||
sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.MaxHealth);
|
||||
|
||||
bool hasHole = SectionHasHole(sectionIndex);
|
||||
|
||||
if (hadHole != hasHole) UpdateSections();
|
||||
|
||||
}
|
||||
|
||||
private void UpdateSections()
|
||||
{
|
||||
foreach (Body b in bodies)
|
||||
{
|
||||
Game1.World.RemoveBody(b);
|
||||
}
|
||||
bodies.Clear();
|
||||
|
||||
int x = sections[0].rect.X;
|
||||
int y = sections[0].rect.Y;
|
||||
int width = sections[0].rect.Width;
|
||||
int height = sections[0].rect.Height;
|
||||
|
||||
bool hasHoles = false;
|
||||
|
||||
for (int i = 1; i < sections.Length; i++)
|
||||
{
|
||||
bool hasHole = SectionHasHole(i);
|
||||
if (hasHole) hasHoles = true;
|
||||
if (hasHole || i == sections.Length - 1)
|
||||
{
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
CreateRectBody(new Rectangle(x, y, width, height));
|
||||
}
|
||||
if (isHorizontal)
|
||||
{
|
||||
x = sections[i].rect.X+ sections[i].rect.Width;
|
||||
width = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = sections[i].rect.Y - sections[i].rect.Height;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
width += sections[i].rect.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
height += sections[i].rect.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHoles)
|
||||
{
|
||||
CreateRectBody(rect).IsSensor = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Body CreateRectBody(Rectangle rect)
|
||||
{
|
||||
Body newBody = BodyFactory.CreateRectangle(Game1.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
1.5f);
|
||||
newBody.BodyType = BodyType.Static;
|
||||
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
|
||||
newBody.Friction = 0.5f;
|
||||
|
||||
newBody.OnCollision += OnWallCollision;
|
||||
|
||||
newBody.CollisionCategories = Physics.CollisionWall;
|
||||
|
||||
newBody.UserData = this;
|
||||
|
||||
bodies.Add(newBody);
|
||||
|
||||
return newBody;
|
||||
}
|
||||
|
||||
|
||||
public override XElement Save(XDocument doc)
|
||||
{
|
||||
XElement element = new XElement("Structure");
|
||||
|
||||
element.Add(new XAttribute("name", prefab.Name),
|
||||
new XAttribute("ID", ID),
|
||||
new XAttribute("rect", rect.X + "," + rect.Y+","+rect.Width+","+rect.Height));
|
||||
|
||||
for (int i = 0; i < sections.Count(); i++)
|
||||
{
|
||||
if (sections[i].damage == 0.0f) continue;
|
||||
|
||||
element.Add(new XElement("section",
|
||||
new XAttribute("i", i),
|
||||
new XAttribute("damage", sections[i].damage)));
|
||||
}
|
||||
|
||||
doc.Root.Add(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public static void Load(XElement element)
|
||||
{
|
||||
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
|
||||
string[] rectValues = rectString.Split(',');
|
||||
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(rectValues[0]),
|
||||
int.Parse(rectValues[1]),
|
||||
int.Parse(rectValues[2]),
|
||||
int.Parse(rectValues[3]));
|
||||
|
||||
string name = element.Attribute("name").Value;
|
||||
|
||||
Debug.WriteLine(name+" - "+rect);
|
||||
|
||||
Structure s = null;
|
||||
|
||||
foreach (MapEntityPrefab ep in MapEntityPrefab.list)
|
||||
{
|
||||
if (ep.Name == name)
|
||||
{
|
||||
s = new Structure(rect, (StructurePrefab)ep);
|
||||
s.ID = int.Parse(element.Attribute("ID").Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Structure prefab " + name + " not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "section":
|
||||
if (subElement.Attribute("i") == null) continue;
|
||||
|
||||
s.sections[int.Parse(subElement.Attribute("i").Value)].damage =
|
||||
ToolBox.GetAttributeFloat(subElement, "damage", 0.0f);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data)
|
||||
{
|
||||
for (int i = 0; i < sections.Length; i++ )
|
||||
{
|
||||
message.Write(sections[i].damage);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message)
|
||||
{
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
{
|
||||
float damage = message.ReadFloat();
|
||||
if (damage != sections[i].damage) SetDamage(i, damage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
161
Subsurface/Source/Map/StructurePrefab.cs
Normal file
161
Subsurface/Source/Map/StructurePrefab.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class StructurePrefab : MapEntityPrefab
|
||||
{
|
||||
//public static List<StructurePrefab> list = new List<StructurePrefab>();
|
||||
|
||||
//does the structure have a physics body
|
||||
bool hasBody;
|
||||
|
||||
bool castShadow;
|
||||
|
||||
bool isPlatform;
|
||||
Direction stairDirection;
|
||||
|
||||
float maxHealth;
|
||||
|
||||
//default size
|
||||
Vector2 size;
|
||||
|
||||
public bool HasBody
|
||||
{
|
||||
get { return hasBody; }
|
||||
}
|
||||
|
||||
public bool IsPlatform
|
||||
{
|
||||
get { return isPlatform; }
|
||||
}
|
||||
|
||||
public float MaxHealth
|
||||
{
|
||||
get { return maxHealth; }
|
||||
}
|
||||
|
||||
public bool CastShadow
|
||||
{
|
||||
get { return castShadow; }
|
||||
}
|
||||
|
||||
public Direction StairDirection
|
||||
{
|
||||
get { return stairDirection; }
|
||||
}
|
||||
|
||||
public static void LoadAll(List<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
if (doc == null) return;
|
||||
|
||||
foreach (XElement el in doc.Root.Elements())
|
||||
{
|
||||
StructurePrefab sp = Load(el);
|
||||
|
||||
Debug.WriteLine(sp.name);
|
||||
|
||||
list.Add(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static StructurePrefab Load(XElement el)
|
||||
{
|
||||
StructurePrefab sp = new StructurePrefab();
|
||||
sp.name = el.Name.ToString();
|
||||
|
||||
Vector4 sourceVector = ToolBox.GetAttributeVector4(el, "sourcerect", new Vector4(0,0,1,1));
|
||||
|
||||
Rectangle sourceRect = new Rectangle(
|
||||
(int)sourceVector.X,
|
||||
(int)sourceVector.Y,
|
||||
(int)sourceVector.Z,
|
||||
(int)sourceVector.W);
|
||||
|
||||
if (el.Attribute("sprite") != null)
|
||||
{
|
||||
sp.sprite = new Sprite(el.Attribute("sprite").Value, sourceRect, Vector2.Zero);
|
||||
|
||||
sp.sprite.Depth = ToolBox.GetAttributeFloat(el, "depth", 0.0f);
|
||||
|
||||
if (ToolBox.GetAttributeBool(el, "fliphorizontal", false)) sp.sprite.effects = SpriteEffects.FlipHorizontally;
|
||||
if (ToolBox.GetAttributeBool(el, "flipvertical", false)) sp.sprite.effects = SpriteEffects.FlipVertically;
|
||||
}
|
||||
|
||||
sp.size = Vector2.Zero;
|
||||
sp.size.X = ToolBox.GetAttributeFloat(el, "width", 0.0f);
|
||||
sp.size.Y = ToolBox.GetAttributeFloat(el, "height", 0.0f);
|
||||
|
||||
sp.maxHealth = ToolBox.GetAttributeFloat(el, "health", 100.0f);
|
||||
|
||||
sp.resizeHorizontal = ToolBox.GetAttributeBool(el, "resizehorizontal", false);
|
||||
sp.resizeVertical = ToolBox.GetAttributeBool(el, "resizevertical", false);
|
||||
|
||||
sp.isPlatform = ToolBox.GetAttributeBool(el, "platform", false);
|
||||
sp.stairDirection = (Direction)Enum.Parse(typeof(Direction), ToolBox.GetAttributeString(el, "stairdirection", "None"));
|
||||
|
||||
sp.castShadow = ToolBox.GetAttributeBool(el, "castshadow", false);
|
||||
|
||||
|
||||
sp.hasBody = ToolBox.GetAttributeBool(el, "body", false);
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
public override void UpdatePlacing(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
Vector2 position = Submarine.MouseToWorldGrid(cam);
|
||||
//Vector2 placeSize = size;
|
||||
|
||||
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
|
||||
|
||||
|
||||
if (placePosition == Vector2.Zero)
|
||||
{
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed)
|
||||
placePosition = Submarine.MouseToWorldGrid(cam);
|
||||
|
||||
newRect.X = (int)position.X;
|
||||
newRect.Y = (int)position.Y;
|
||||
|
||||
//sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y), placeSize, Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 placeSize = size;
|
||||
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
|
||||
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
|
||||
|
||||
newRect = Submarine.AbsRect(placePosition, placeSize);
|
||||
|
||||
//newRect.Width = (int)Math.Max(newRect.Width, Map.gridSize.X);
|
||||
//newRect.Height = (int)Math.Max(newRect.Height, Map.gridSize.Y);
|
||||
|
||||
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
|
||||
{
|
||||
new Structure(newRect, this);
|
||||
selected = null;
|
||||
return;
|
||||
}
|
||||
|
||||
//position = placePosition;
|
||||
}
|
||||
|
||||
sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), Color.White);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - Game1.GraphicsWidth, -newRect.Y, newRect.Width + Game1.GraphicsWidth*2, newRect.Height), Color.White);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - Game1.GraphicsHeight, newRect.Width, newRect.Height + Game1.GraphicsHeight*2), Color.White);
|
||||
|
||||
if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
891
Subsurface/Source/Map/Submarine.cs
Normal file
891
Subsurface/Source/Map/Submarine.cs
Normal file
@@ -0,0 +1,891 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Common.Decomposition;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Subsurface.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
public enum Direction : byte
|
||||
{
|
||||
None = 0, Left = 1, Right = 2
|
||||
}
|
||||
|
||||
class Submarine : Entity
|
||||
{
|
||||
public static List<Submarine> SavedSubmarines = new List<Submarine>();
|
||||
|
||||
public static readonly Vector2 GridSize = new Vector2(16.0f, 16.0f);
|
||||
|
||||
private static Submarine loaded;
|
||||
|
||||
private static Vector2 lastPickedPosition;
|
||||
private static float lastPickedFraction;
|
||||
|
||||
static string SaveFolder;
|
||||
Md5Hash hash;
|
||||
|
||||
Vector2 speed;
|
||||
|
||||
Vector2 targetPosition;
|
||||
|
||||
private Rectangle borders;
|
||||
|
||||
private Body hullBody;
|
||||
|
||||
private string filePath;
|
||||
private string name;
|
||||
|
||||
|
||||
private double lastNetworkUpdate;
|
||||
|
||||
|
||||
//properties ----------------------------------------------------
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public static Vector2 LastPickedPosition
|
||||
{
|
||||
get { return lastPickedPosition; }
|
||||
}
|
||||
|
||||
public static float LastPickedFraction
|
||||
{
|
||||
get { return lastPickedFraction; }
|
||||
}
|
||||
|
||||
public List<Vector2> HullVertices
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Md5Hash Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hash != null) return hash;
|
||||
|
||||
XDocument doc = OpenDoc(filePath);
|
||||
hash = new Md5Hash(doc);
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public static Submarine Loaded
|
||||
{
|
||||
get { return loaded; }
|
||||
}
|
||||
|
||||
public static Rectangle Borders
|
||||
{
|
||||
get
|
||||
{
|
||||
return (loaded==null) ? Rectangle.Empty : loaded.borders;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return new Vector2(borders.X+borders.Width/2, borders.Y - borders.Height/2); }
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return (Level.Loaded == null) ? Vector2.Zero : -Level.Loaded.Position; }
|
||||
}
|
||||
|
||||
public Vector2 Speed
|
||||
{
|
||||
get { return speed; }
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get { return filePath; }
|
||||
}
|
||||
|
||||
//constructors & generation ----------------------------------------------------
|
||||
|
||||
public Submarine(string filePath, string hash = "")
|
||||
{
|
||||
this.filePath = filePath;
|
||||
try
|
||||
{
|
||||
name = System.IO.Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error loading map " + filePath + "!", e);
|
||||
}
|
||||
|
||||
if (hash != "")
|
||||
{
|
||||
this.hash = new Md5Hash(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
//XDocument doc = OpenDoc(filePath);
|
||||
|
||||
//string md5Hash = ToolBox.GetAttributeString(doc.Root, "md5hash", "");
|
||||
//if (md5Hash == "" || md5Hash.Length < 16)
|
||||
//{
|
||||
// DebugConsole.ThrowError("Couldn't find a valid MD5 hash in the map file");
|
||||
//}
|
||||
|
||||
//this.mapHash = new MapHash(md5Hash);
|
||||
}
|
||||
|
||||
base.Remove();
|
||||
ID = -1;
|
||||
}
|
||||
|
||||
private List<Vector2> GenerateConvexHull()
|
||||
{
|
||||
List<Vector2> points = new List<Vector2>();
|
||||
|
||||
Vector2 leftMost = Vector2.Zero;
|
||||
|
||||
foreach (Structure wall in Structure.wallList)
|
||||
{
|
||||
for (int x = -1; x <= 1; x += 2)
|
||||
{
|
||||
for (int y = -1; y <= 1; y += 2)
|
||||
{
|
||||
Vector2 corner = new Vector2(wall.Rect.X + wall.Rect.Width / 2.0f, wall.Rect.Y - wall.Rect.Height / 2.0f);
|
||||
corner.X += x * wall.Rect.Width / 2.0f;
|
||||
corner.Y += y * wall.Rect.Height / 2.0f;
|
||||
|
||||
if (points.Contains(corner)) continue;
|
||||
|
||||
points.Add(corner);
|
||||
if (leftMost == Vector2.Zero || corner.X < leftMost.X) leftMost = corner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Vector2> hullPoints = new List<Vector2>();
|
||||
|
||||
Vector2 currPoint = leftMost;
|
||||
Vector2 endPoint;
|
||||
do
|
||||
{
|
||||
hullPoints.Add(currPoint);
|
||||
endPoint = points[0];
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
if ((currPoint == endPoint)
|
||||
|| (Orientation(currPoint, endPoint, points[i]) == -1))
|
||||
{
|
||||
endPoint = points[i];
|
||||
}
|
||||
}
|
||||
|
||||
currPoint = endPoint;
|
||||
|
||||
}
|
||||
while (endPoint != hullPoints[0]);
|
||||
|
||||
return hullPoints;
|
||||
}
|
||||
|
||||
private static int Orientation(Vector2 p1, Vector2 p2, Vector2 p)
|
||||
{
|
||||
// Determinant
|
||||
float Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
|
||||
|
||||
if (Orin > 0)
|
||||
return -1; // (* Orientation is to the left-hand side *)
|
||||
if (Orin < 0)
|
||||
return 1; // (* Orientation is to the right-hand side *)
|
||||
|
||||
return 0; // (* Orientation is neutral aka collinear *)
|
||||
}
|
||||
|
||||
//drawing ----------------------------------------------------
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch, bool editing = false)
|
||||
{
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++ )
|
||||
{
|
||||
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawFront(SpriteBatch spriteBatch, bool editing = false)
|
||||
{
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth < 0.5f)
|
||||
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
|
||||
}
|
||||
|
||||
if (loaded == null) return;
|
||||
|
||||
//foreach (HullBody hb in loaded.hullBodies)
|
||||
//{
|
||||
// spriteBatch.Draw(
|
||||
// hb.shapeTexture,
|
||||
// ConvertUnits.ToDisplayUnits(new Vector2(hb.body.Position.X, -hb.body.Position.Y)),
|
||||
// null,
|
||||
// Color.White,
|
||||
// -hb.body.Rotation,
|
||||
// new Vector2(hb.shapeTexture.Width / 2, hb.shapeTexture.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
|
||||
//}
|
||||
}
|
||||
|
||||
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false)
|
||||
{
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth >= 0.5f)
|
||||
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
|
||||
}
|
||||
}
|
||||
|
||||
//math/physics stuff ----------------------------------------------------
|
||||
|
||||
public static Vector2 MouseToWorldGrid(Camera cam)
|
||||
{
|
||||
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
|
||||
position = cam.ScreenToWorld(position);
|
||||
|
||||
return VectorToWorldGrid(position);
|
||||
}
|
||||
|
||||
public static Vector2 VectorToWorldGrid(Vector2 position)
|
||||
{
|
||||
position.X = (float)Math.Floor(Convert.ToDouble(position.X / GridSize.X)) * GridSize.X;
|
||||
position.Y = (float)Math.Ceiling(Convert.ToDouble(position.Y / GridSize.Y)) * GridSize.Y;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public static Rectangle AbsRect(Vector2 pos, Vector2 size)
|
||||
{
|
||||
if (size.X < 0.0f)
|
||||
{
|
||||
pos.X += size.X;
|
||||
size.X = -size.X;
|
||||
}
|
||||
if (size.Y < 0.0f)
|
||||
{
|
||||
pos.Y -= size.Y;
|
||||
size.Y = -size.Y;
|
||||
}
|
||||
|
||||
return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);
|
||||
}
|
||||
|
||||
public static bool RectContains(Rectangle rect, Vector2 pos)
|
||||
{
|
||||
return (pos.X > rect.X && pos.X < rect.X + rect.Width
|
||||
&& pos.Y < rect.Y && pos.Y > rect.Y - rect.Height);
|
||||
}
|
||||
|
||||
public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true)
|
||||
{
|
||||
if (inclusive)
|
||||
{
|
||||
return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
|
||||
rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
|
||||
rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null)
|
||||
{
|
||||
|
||||
|
||||
float closestFraction = 1.0f;
|
||||
Body closestBody = null;
|
||||
Game1.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
if (fixture == null || fixture.CollisionCategories == Category.None) return -1;
|
||||
if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1;
|
||||
|
||||
Structure structure = fixture.Body.UserData as Structure;
|
||||
if (structure != null && (structure.IsPlatform || !structure.HasBody)) return -1;
|
||||
|
||||
if (fraction < closestFraction)
|
||||
{
|
||||
closestFraction = fraction;
|
||||
if (fixture.Body!=null) closestBody = fixture.Body;
|
||||
}
|
||||
return fraction;
|
||||
}
|
||||
, rayStart, rayEnd);
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
return closestBody;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check visibility between two points (in sim units)
|
||||
/// </summary>
|
||||
/// <returns>a physics body that was between the points (or null)</returns>
|
||||
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd)
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestFraction = 1.0f;
|
||||
|
||||
if (Vector2.Distance(rayStart, rayEnd) < 0.01f)
|
||||
{
|
||||
closestFraction = 0.01f;
|
||||
return null;
|
||||
}
|
||||
|
||||
Game1.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
if (fixture == null || fixture.CollisionCategories != Physics.CollisionWall) return -1;
|
||||
|
||||
Structure structure = fixture.Body.UserData as Structure;
|
||||
if (structure != null)
|
||||
{
|
||||
if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1;
|
||||
int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point));
|
||||
if (sectionIndex > -1 && structure.SectionHasHole(sectionIndex)) return -1;
|
||||
}
|
||||
|
||||
if (fraction < closestFraction)
|
||||
{
|
||||
closestBody = fixture.Body;
|
||||
closestFraction = fraction;
|
||||
}
|
||||
return closestFraction;
|
||||
}
|
||||
, rayStart, rayEnd);
|
||||
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
return closestBody;
|
||||
}
|
||||
|
||||
public static Body PickBody(Vector2 point)
|
||||
{
|
||||
Body foundBody = null;
|
||||
AABB aabb = new AABB(point, point);
|
||||
|
||||
Game1.World.QueryAABB(p =>
|
||||
{
|
||||
foundBody = p.Body;
|
||||
|
||||
return true;
|
||||
|
||||
}, ref aabb);
|
||||
|
||||
return foundBody;
|
||||
}
|
||||
|
||||
public static bool InsideWall(Vector2 point)
|
||||
{
|
||||
Body foundBody = PickBody(point);
|
||||
if (foundBody==null) return false;
|
||||
|
||||
Structure wall = foundBody.UserData as Structure;
|
||||
if (wall == null || wall.IsPlatform) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//movement ----------------------------------------------------
|
||||
|
||||
float collisionRigidness = 1.0f;
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
Vector2 translateAmount = speed * deltaTime;
|
||||
translateAmount += ConvertUnits.ToDisplayUnits(hullBody.Position) * collisionRigidness;
|
||||
|
||||
if (targetPosition != Vector2.Zero && Vector2.Distance(targetPosition, Position) > 50.0f)
|
||||
{
|
||||
translateAmount += (targetPosition - Position) * 0.01f;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition = Vector2.Zero;
|
||||
}
|
||||
|
||||
Translate(translateAmount);
|
||||
|
||||
ApplyForce(CalculateBuoyancy());
|
||||
|
||||
float dragCoefficient = 0.00001f;
|
||||
|
||||
float speedLength = speed.Length();
|
||||
float drag = speedLength * speedLength * dragCoefficient * mass;
|
||||
|
||||
if (speed != Vector2.Zero)
|
||||
{
|
||||
ApplyForce(-Vector2.Normalize(speed) * drag);
|
||||
}
|
||||
//hullBodies[0].body.LinearVelocity = -hullBodies[0].body.Position;
|
||||
|
||||
//hullBody.SetTransform(Vector2.Zero , 0.0f);
|
||||
hullBody.LinearVelocity = -hullBody.Position/(float)Physics.step;
|
||||
|
||||
if (collidingCell == null)
|
||||
{
|
||||
collisionRigidness = MathHelper.Lerp(collisionRigidness, 1.0f, 0.1f);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (GraphEdge ge in collidingCell.edges)
|
||||
{
|
||||
Body body = PickBody(
|
||||
ConvertUnits.ToSimUnits(ge.point1+ Game1.GameSession.Level.Position),
|
||||
ConvertUnits.ToSimUnits(ge.point2 + Game1.GameSession.Level.Position), new List<Body>(){collidingCell.body});
|
||||
if (body == null || body.UserData == null) continue;
|
||||
|
||||
Structure structure = body.UserData as Structure;
|
||||
if (structure == null) continue;
|
||||
structure.AddDamage(lastPickedPosition, DamageType.Blunt, 50.0f, 0.0f, 0.0f, true);
|
||||
}
|
||||
|
||||
//hullBodies[0].body.SetTransform(Vector2.Zero, 0.0f);
|
||||
|
||||
//position = hullBodies[0].body.Position;
|
||||
|
||||
//Level.Loaded.Move(-ConvertUnits.ToDisplayUnits(position - prevPosition));
|
||||
|
||||
//prevPosition = hullBodies[0].body.Position;
|
||||
|
||||
}
|
||||
|
||||
private Vector2 CalculateBuoyancy()
|
||||
{
|
||||
float waterVolume = 0.0f;
|
||||
float volume = 0.0f;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
waterVolume += hull.Volume;
|
||||
volume += hull.FullVolume;
|
||||
}
|
||||
|
||||
float waterPercentage = waterVolume / volume;
|
||||
|
||||
float neutralPercentage = 0.07f;
|
||||
|
||||
float buoyancy = neutralPercentage-waterPercentage;
|
||||
buoyancy *= mass * 30.0f;
|
||||
|
||||
return new Vector2(0.0f, buoyancy);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
//hullBodies[0].body.SetTransform(position, 0.0f);
|
||||
Level.Loaded.SetPosition(-position);
|
||||
//prevPosition = position;
|
||||
}
|
||||
|
||||
private void Translate(Vector2 amount)
|
||||
{
|
||||
if (amount == Vector2.Zero) return;
|
||||
|
||||
Level.Loaded.Move(-amount);
|
||||
}
|
||||
|
||||
float mass = 10000.0f;
|
||||
public void ApplyForce(Vector2 force)
|
||||
{
|
||||
speed += force/mass;
|
||||
}
|
||||
|
||||
//public void Move(Vector2 amount)
|
||||
//{
|
||||
// speed = Vector2.Lerp(speed, amount, 0.05f);
|
||||
//}
|
||||
|
||||
VoronoiCell collidingCell;
|
||||
public bool OnCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("colliding");
|
||||
VoronoiCell cell = f2.Body.UserData as VoronoiCell;
|
||||
if (cell==null) return true;
|
||||
|
||||
Vector2 normal = -contact.Manifold.LocalNormal;
|
||||
Vector2 simSpeed = ConvertUnits.ToSimUnits(speed);
|
||||
float impact = -Vector2.Dot(simSpeed, normal);
|
||||
|
||||
Vector2 u = Vector2.Dot(simSpeed, normal)*normal;
|
||||
Vector2 w = simSpeed - u;
|
||||
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("IMPACT:"+impact);
|
||||
if (impact < 4.0f)
|
||||
{
|
||||
speed = ConvertUnits.ToDisplayUnits(w * 0.9f - u * 0.2f);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
speed = ConvertUnits.ToDisplayUnits(w * 0.9f + u * 0.5f);
|
||||
}
|
||||
|
||||
if (contact.Manifold.PointCount >= 1)
|
||||
{
|
||||
FixedArray2<Vector2> worldPoints;
|
||||
contact.GetWorldManifold(out normal, out worldPoints);
|
||||
|
||||
Game1.GameScreen.Cam.Shake = impact;
|
||||
}
|
||||
|
||||
collisionRigidness = 0.8f;
|
||||
|
||||
collidingCell = cell;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnSeparation(Fixture f1, Fixture f2)
|
||||
{
|
||||
collidingCell = null;
|
||||
}
|
||||
|
||||
public override void FillNetworkData(Networking.NetworkEventType type, NetOutgoingMessage message, object data)
|
||||
{
|
||||
message.Write(NetTime.Now);
|
||||
message.Write(Position.X);
|
||||
message.Write(Position.Y);
|
||||
|
||||
message.Write(speed.X);
|
||||
message.Write(speed.Y);
|
||||
|
||||
}
|
||||
|
||||
public override void ReadNetworkData(Networking.NetworkEventType type, NetIncomingMessage message)
|
||||
{
|
||||
double sendingTime;
|
||||
Vector2 newTargetPosition, newSpeed;
|
||||
try
|
||||
{
|
||||
sendingTime = message.ReadDouble();
|
||||
|
||||
if (sendingTime <= lastNetworkUpdate) return;
|
||||
|
||||
newTargetPosition = new Vector2(message.ReadFloat(), message.ReadFloat());
|
||||
newSpeed = new Vector2(message.ReadFloat(), message.ReadFloat());
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//newTargetPosition = newTargetPosition + newSpeed * (float)(NetTime.Now - sendingTime);
|
||||
|
||||
targetPosition = newTargetPosition;
|
||||
speed = newSpeed;
|
||||
|
||||
lastNetworkUpdate = sendingTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//saving/loading ----------------------------------------------------
|
||||
|
||||
public void Save()
|
||||
{
|
||||
SaveAs(filePath);
|
||||
}
|
||||
|
||||
public void SaveAs(string filePath)
|
||||
{
|
||||
//if (filePath=="")
|
||||
//{
|
||||
// DebugConsole.ThrowError("No save file selected");
|
||||
// return;
|
||||
//}
|
||||
XDocument doc = new XDocument(new XElement("Submarine"));
|
||||
doc.Root.Add(new XAttribute("name", name));
|
||||
|
||||
foreach (MapEntity e in MapEntity.mapEntityList)
|
||||
{
|
||||
e.Save(doc);
|
||||
}
|
||||
|
||||
hash = new Md5Hash(doc);
|
||||
doc.Root.Add(new XAttribute("md5hash", hash.Hash));
|
||||
|
||||
try
|
||||
{
|
||||
SaveUtil.CompressStringToFile(filePath, doc.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving submarine ''" + filePath + "'' failed!", e);
|
||||
}
|
||||
|
||||
|
||||
//doc.Save(filePath);
|
||||
}
|
||||
|
||||
public static void SaveCurrent(string savePath)
|
||||
{
|
||||
if (loaded==null)
|
||||
{
|
||||
loaded = new Submarine(savePath);
|
||||
// return;
|
||||
}
|
||||
|
||||
loaded.SaveAs(savePath);
|
||||
}
|
||||
|
||||
public static void Preload(string folder)
|
||||
{
|
||||
SaveFolder = folder;
|
||||
|
||||
//string[] mapFilePaths;
|
||||
Unload();
|
||||
SavedSubmarines.Clear();
|
||||
|
||||
if (!Directory.Exists(SaveFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(SaveFolder);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
DebugConsole.ThrowError("Directory ''"+SaveFolder+"'' not found and creating the directory failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string[] filePaths;
|
||||
|
||||
try
|
||||
{
|
||||
filePaths = Directory.GetFiles(SaveFolder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't open directory ''" + SaveFolder + "''!", e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string path in filePaths)
|
||||
{
|
||||
//Map savedMap = new Map(mapPath);
|
||||
SavedSubmarines.Add(new Submarine(path));
|
||||
}
|
||||
}
|
||||
|
||||
private XDocument OpenDoc(string file)
|
||||
{
|
||||
XDocument doc = null;
|
||||
string extension = "";
|
||||
|
||||
try
|
||||
{
|
||||
extension = System.IO.Path.GetExtension(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (extension == ".gz")
|
||||
{
|
||||
Stream stream = SaveUtil.DecompressFiletoStream(file);
|
||||
if (stream == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
stream.Position = 0;
|
||||
doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file);
|
||||
stream.Close();
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (extension == ".xml")
|
||||
{
|
||||
try
|
||||
{
|
||||
doc = XDocument.Load(file);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)");
|
||||
return null;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
Unload();
|
||||
//string file = filePath;
|
||||
|
||||
XDocument doc = OpenDoc(filePath);
|
||||
if (doc == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
string typeName = element.Name.ToString();
|
||||
|
||||
Type t;
|
||||
try
|
||||
{
|
||||
// Get the type of a specified class.
|
||||
t = Type.GetType("Subsurface." + typeName + ", Subsurface", true, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo loadMethod = t.GetMethod("Load");
|
||||
loadMethod.Invoke(t, new object[] { element });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the method ''Load'' in " + t + ".", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<Vector2> convexHull = GenerateConvexHull();
|
||||
|
||||
HullVertices = convexHull;
|
||||
|
||||
for (int i = 0; i < convexHull.Count; i++)
|
||||
{
|
||||
convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
|
||||
}
|
||||
|
||||
convexHull.Reverse();
|
||||
|
||||
//get farseer 'vertices' from vectors
|
||||
Vertices shapevertices = new Vertices(convexHull);
|
||||
|
||||
AABB hullAABB = shapevertices.GetAABB();
|
||||
|
||||
borders = new Rectangle(
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.LowerBound.X),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.UpperBound.Y),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.X * 2.0f),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.Y * 2.0f));
|
||||
|
||||
|
||||
var triangulatedVertices = Triangulate.ConvexPartition(shapevertices, TriangulationAlgorithm.Bayazit);
|
||||
|
||||
hullBody = BodyFactory.CreateCompoundPolygon(Game1.World, triangulatedVertices, 5.0f);
|
||||
hullBody.BodyType = BodyType.Dynamic;
|
||||
|
||||
hullBody.CollisionCategories = Physics.CollisionMisc;
|
||||
hullBody.CollidesWith = Physics.CollisionLevel;
|
||||
hullBody.FixedRotation = true;
|
||||
hullBody.Awake = true;
|
||||
hullBody.SleepingAllowed = false;
|
||||
hullBody.GravityScale = 0.0f;
|
||||
hullBody.OnCollision += OnCollision;
|
||||
hullBody.OnSeparation += OnSeparation;
|
||||
|
||||
MapEntity.LinkAll();
|
||||
|
||||
foreach (Item item in Item.itemList)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(item.ID);
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
ic.OnMapLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
ID = int.MaxValue-10;
|
||||
|
||||
loaded = this;
|
||||
}
|
||||
|
||||
public static Submarine Load(string file)
|
||||
{
|
||||
Unload();
|
||||
|
||||
Submarine sub = new Submarine(file);
|
||||
sub.Load();
|
||||
|
||||
//Entity.dictionary.Add(int.MaxValue, sub);
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
if (loaded == null) return;
|
||||
|
||||
loaded.Remove();
|
||||
|
||||
loaded.Clear();
|
||||
loaded = null;
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
if (Game1.GameScreen.Cam != null) Game1.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
|
||||
Entity.RemoveAll();
|
||||
|
||||
PhysicsBody.list.Clear();
|
||||
|
||||
Ragdoll.list.Clear();
|
||||
|
||||
Game1.World.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
163
Subsurface/Source/Map/WaterRenderer.cs
Normal file
163
Subsurface/Source/Map/WaterRenderer.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
struct WaterVertex
|
||||
{
|
||||
public Vector3 position;
|
||||
private Vector2 texCoord;
|
||||
|
||||
public WaterVertex(Vector3 position, Vector2 texCoord, Matrix transform)
|
||||
{
|
||||
this.position = position;
|
||||
|
||||
this.texCoord = Vector2.Transform(texCoord, transform);
|
||||
}
|
||||
|
||||
public WaterVertex(Vector3 position, Vector2 texCoord)
|
||||
{
|
||||
this.position = position;
|
||||
|
||||
this.texCoord = texCoord;
|
||||
}
|
||||
|
||||
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
|
||||
(
|
||||
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
|
||||
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
|
||||
);
|
||||
|
||||
public void TransformTexCoord(Matrix transform)
|
||||
{
|
||||
texCoord = Vector2.Transform(texCoord, transform);
|
||||
}
|
||||
}
|
||||
|
||||
class WaterRenderer : IDisposable
|
||||
{
|
||||
const int DefaultBufferSize = 1500;
|
||||
|
||||
Effect effect;
|
||||
|
||||
public Vector2 wavePos;
|
||||
|
||||
public WaterVertex[] vertices = new WaterVertex[DefaultBufferSize];
|
||||
|
||||
private VertexBuffer vertexBuffer;
|
||||
|
||||
public int positionInBuffer = 0;
|
||||
|
||||
public WaterRenderer(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
//vertexBuffer = new VertexBuffer(graphicsDevice, WaterVertex.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
|
||||
//vertexBuffer.SetData(vertices);
|
||||
|
||||
//effect = Game1.game.Content.Load<Effect>("effects");
|
||||
|
||||
byte[] bytecode = File.ReadAllBytes("Content/effects.mgfx");
|
||||
effect = new Effect(graphicsDevice, bytecode);
|
||||
|
||||
//Texture2D waterBumpMap = Game1.textureLoader.FromFile("Content/waterbump.jpg");
|
||||
//effect.Parameters["xBump"].SetValue(waterBumpMap);
|
||||
//effect.Parameters["xWaveLength"].SetValue(0.5f);
|
||||
//effect.Parameters["xWaveHeight"].SetValue(0.03f);
|
||||
effect.Parameters["xProjection"].SetValue(Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1));
|
||||
effect.Parameters["xColor"].SetValue(new Vector4(0.75f, 0.8f, 0.9f, 1.0f));
|
||||
effect.Parameters["xBlurDistance"].SetValue(0.0005f);
|
||||
|
||||
effect.Parameters["xWaterBumpMap"].SetValue(Game1.TextureLoader.FromFile("Content/waterbump.jpg"));
|
||||
effect.Parameters["xWaveWidth"].SetValue(0.1f);
|
||||
effect.Parameters["xWaveHeight"].SetValue(0.1f);
|
||||
|
||||
vertexBuffer = new VertexBuffer(graphicsDevice, WaterVertex.VertexDeclaration, DefaultBufferSize, BufferUsage.WriteOnly);
|
||||
}
|
||||
|
||||
public void RenderBack(GraphicsDevice graphicsDevice, RenderTarget2D texture, Matrix transform)
|
||||
{
|
||||
WaterVertex[] verts = new WaterVertex[6];
|
||||
|
||||
// create the four corners of our triangle.
|
||||
Vector3 p1 = new Vector3(-graphicsDevice.Viewport.Width / 2.0f, graphicsDevice.Viewport.Height / 2.0f, 0.0f);
|
||||
Vector3 p2 = new Vector3(-p1.X, p1.Y, 0.0f);
|
||||
|
||||
Vector3 p3 = new Vector3(p2.X, -p1.Y, 0.0f);
|
||||
Vector3 p4 = new Vector3(p1.X, -p1.Y, 0.0f);
|
||||
|
||||
verts[0] = new WaterVertex(p1, new Vector2(0, 0));
|
||||
verts[1] = new WaterVertex(p2, new Vector2(1, 0));
|
||||
verts[2] = new WaterVertex(p3, new Vector2(1, 1));
|
||||
|
||||
verts[3] = new WaterVertex(p1, new Vector2(0, 0));
|
||||
verts[4] = new WaterVertex(p3, new Vector2(1, 1));
|
||||
verts[5] = new WaterVertex(p4, new Vector2(0, 1));
|
||||
|
||||
vertexBuffer.SetData(verts);
|
||||
|
||||
wavePos.X += 0.0001f;
|
||||
wavePos.Y += 0.0001f;
|
||||
|
||||
effect.Parameters["xWavePos"].SetValue(wavePos);
|
||||
|
||||
effect.CurrentTechnique = effect.Techniques["WaterShader"];
|
||||
effect.Parameters["xTexture"].SetValue(texture);
|
||||
effect.Parameters["xView"].SetValue(Matrix.Identity);
|
||||
|
||||
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
|
||||
{
|
||||
pass.Apply();
|
||||
|
||||
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, verts, 0, verts.Length / 3, WaterVertex.VertexDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(GraphicsDevice graphicsDevice, Camera cam, RenderTarget2D texture, Matrix transform)
|
||||
{
|
||||
if (vertices == null) return;
|
||||
if (vertices.Length < 0) return;
|
||||
|
||||
vertexBuffer.SetData(vertices);
|
||||
|
||||
effect.Parameters["xBumpPos"].SetValue(cam.Position / Game1.GraphicsWidth / cam.Zoom);
|
||||
|
||||
effect.CurrentTechnique = effect.Techniques["EmptyShader"];
|
||||
effect.Parameters["xTexture"].SetValue(texture);
|
||||
effect.Parameters["xView"].SetValue(transform);
|
||||
|
||||
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
|
||||
{
|
||||
pass.Apply();
|
||||
|
||||
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3, WaterVertex.VertexDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (vertexBuffer != null)
|
||||
{
|
||||
vertexBuffer.Dispose();
|
||||
vertexBuffer = null;
|
||||
}
|
||||
|
||||
if (effect != null)
|
||||
{
|
||||
effect.Dispose();
|
||||
effect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
332
Subsurface/Source/Map/WayPoint.cs
Normal file
332
Subsurface/Source/Map/WayPoint.cs
Normal file
@@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
public enum SpawnType { None, Human, Enemy, Cargo };
|
||||
class WayPoint : MapEntity
|
||||
{
|
||||
public static List<WayPoint> WayPointList = new List<WayPoint>();
|
||||
|
||||
private SpawnType spawnType;
|
||||
|
||||
//characters spawning at the waypoint will be given an ID card with these tags
|
||||
private string[] idCardTags;
|
||||
|
||||
//only characters with this job will be spawned at the waypoint
|
||||
private JobPrefab assignedJob;
|
||||
|
||||
public SpawnType SpawnType
|
||||
{
|
||||
get { return spawnType; }
|
||||
set { spawnType = value; }
|
||||
}
|
||||
|
||||
public string[] IdCardTags
|
||||
{
|
||||
get { return idCardTags; }
|
||||
private set
|
||||
{
|
||||
idCardTags = value;
|
||||
for (int i = 0; i<idCardTags.Length; i++)
|
||||
{
|
||||
idCardTags[i] = idCardTags[i].Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WayPoint(Rectangle newRect)
|
||||
{
|
||||
rect = newRect;
|
||||
linkedTo = new ObservableCollection<MapEntity>();
|
||||
idCardTags = new string[0];
|
||||
|
||||
mapEntityList.Add(this);
|
||||
WayPointList.Add(this);
|
||||
}
|
||||
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch, bool editing)
|
||||
{
|
||||
if (!editing && !Game1.DebugDraw) return;
|
||||
|
||||
Point pos = new Point((int)Position.X, (int)Position.Y);
|
||||
|
||||
Color clr = (isSelected) ? Color.Red : Color.LightGreen;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle(pos.X - rect.Width / 2, -pos.Y - rect.Height / 2, rect.Width, rect.Height), clr, true);
|
||||
|
||||
foreach (MapEntity e in linkedTo)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(pos.X, -pos.Y),
|
||||
new Vector2(e.Position.X + e.Rect.Width / 2, -e.Position.Y + e.Rect.Height / 2),
|
||||
Color.Green);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawEditing(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (editingHUD==null)
|
||||
{
|
||||
editingHUD = CreateEditingHUD();
|
||||
}
|
||||
|
||||
editingHUD.Update((float)Physics.step);
|
||||
editingHUD.Draw(spriteBatch);
|
||||
|
||||
if (!PlayerInput.LeftButtonClicked()) return;
|
||||
|
||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (e.GetType()!=typeof(WayPoint)) continue;
|
||||
if (e == this) continue;
|
||||
|
||||
if (!Submarine.RectContains(e.Rect, position)) continue;
|
||||
|
||||
linkedTo.Add(e);
|
||||
e.linkedTo.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ChangeSpawnType(GUIButton button, object obj)
|
||||
{
|
||||
GUITextBlock spawnTypeText = button.Parent as GUITextBlock;
|
||||
|
||||
spawnType += (int)button.UserData;
|
||||
|
||||
if (spawnType > SpawnType.Cargo) spawnType = SpawnType.None;
|
||||
if (spawnType < SpawnType.None) spawnType = SpawnType.Enemy;
|
||||
|
||||
spawnTypeText.Text = spawnType.ToString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnterIDCardTags(GUITextBox textBox, string text)
|
||||
{
|
||||
IdCardTags = text.Split(',');
|
||||
textBox.Text = text;
|
||||
textBox.Color = Color.White;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnterAssignedJob(GUITextBox textBox, string text)
|
||||
{
|
||||
string trimmedName = text.ToLower().Trim();
|
||||
assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLower() == trimmedName);
|
||||
|
||||
if (assignedJob !=null && trimmedName!="none")
|
||||
{
|
||||
textBox.Color = Color.White;
|
||||
textBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TextBoxChanged(GUITextBox textBox, string text)
|
||||
{
|
||||
textBox.Color = Color.Red;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private GUIComponent CreateEditingHUD(bool inGame = false)
|
||||
{
|
||||
int width = 500;
|
||||
int x = Game1.GraphicsWidth / 2 - width / 2, y = 10;
|
||||
|
||||
editingHUD = new GUIFrame(new Rectangle(x, y, width, 150), Color.Black * 0.5f);
|
||||
editingHUD.Padding = new Vector4(10, 10, 0, 0);
|
||||
editingHUD.UserData = this;
|
||||
|
||||
new GUITextBlock(new Rectangle(0, 0, 100, 20), "Editing waypoint", GUI.style, editingHUD);
|
||||
new GUITextBlock(new Rectangle(0, 20, 100, 20), "Hold space to link to another entity", GUI.style, editingHUD);
|
||||
new GUITextBlock(new Rectangle(0, 40, 100, 20), "Spawnpoint: ", GUI.style, editingHUD);
|
||||
|
||||
var spawnTypeText = new GUITextBlock(new Rectangle(0, 40, 200, 20), spawnType.ToString(), GUI.style, Alignment.Right, Alignment.TopLeft, editingHUD);
|
||||
|
||||
var button = new GUIButton(new Rectangle(-30,0,20,20), "-", Alignment.Right, GUI.style, spawnTypeText);
|
||||
button.UserData = -1;
|
||||
button.OnClicked = ChangeSpawnType;
|
||||
|
||||
button = new GUIButton(new Rectangle(0, 0, 20, 20), "+", Alignment.Right, GUI.style, spawnTypeText);
|
||||
button.UserData = 1;
|
||||
button.OnClicked = ChangeSpawnType;
|
||||
|
||||
//spriteBatch.DrawString(GUI.font, "Spawnpoint: " + spawnType.ToString() + " +/-", new Vector2(x, y + 40), Color.Black);
|
||||
|
||||
y = 40+20;
|
||||
|
||||
new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.Black, Alignment.TopLeft, null, editingHUD);
|
||||
GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), GUI.style, editingHUD);
|
||||
propertyBox.Text = string.Join(", ", idCardTags);
|
||||
propertyBox.OnEnter = EnterIDCardTags;
|
||||
propertyBox.OnTextChanged = TextBoxChanged;
|
||||
y = y + 30;
|
||||
|
||||
new GUITextBlock(new Rectangle(0, y, 100, 20), "Assigned job:", Color.Transparent, Color.Black, Alignment.TopLeft, null, editingHUD);
|
||||
propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), GUI.style, editingHUD);
|
||||
propertyBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
|
||||
|
||||
propertyBox.OnEnter = EnterAssignedJob;
|
||||
propertyBox.OnTextChanged = TextBoxChanged;
|
||||
y = y + 30;
|
||||
|
||||
return editingHUD;
|
||||
}
|
||||
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.None, Job assignedJob = null)
|
||||
{
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (spawnType != SpawnType.None && wp.spawnType != spawnType) continue;
|
||||
if (assignedJob != null && wp.assignedJob != assignedJob.Prefab) continue;
|
||||
|
||||
wayPoints.Add(wp);
|
||||
}
|
||||
|
||||
if (!wayPoints.Any()) return null;
|
||||
|
||||
return wayPoints[Rand.Int(wayPoints.Count())];
|
||||
}
|
||||
|
||||
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew)
|
||||
{
|
||||
List<WayPoint> unassignedWayPoints = new List<WayPoint>();
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (wp.spawnType == SpawnType.Human) unassignedWayPoints.Add(wp);
|
||||
}
|
||||
|
||||
WayPoint[] assignedWayPoints = new WayPoint[crew.Count];
|
||||
|
||||
for (int i = 0; i < crew.Count; i++ )
|
||||
{
|
||||
//try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job
|
||||
for (int n = 0; n < unassignedWayPoints.Count; n++)
|
||||
{
|
||||
|
||||
if (crew[i].Job.Prefab != unassignedWayPoints[n].assignedJob) continue;
|
||||
assignedWayPoints[i] = unassignedWayPoints[n];
|
||||
unassignedWayPoints.RemoveAt(n);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//go through the crewmembers that don't have a spawnpoint yet (if any)
|
||||
for (int i = 0; i < crew.Count; i++)
|
||||
{
|
||||
if (assignedWayPoints[i] != null) continue;
|
||||
|
||||
//try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) continue;
|
||||
|
||||
assignedWayPoints[i] = wp;
|
||||
break;
|
||||
}
|
||||
|
||||
if (assignedWayPoints[i] != null) continue;
|
||||
|
||||
//everything else failed -> just give a random spawnpoint
|
||||
assignedWayPoints[i] = GetRandom(SpawnType.Human);
|
||||
}
|
||||
|
||||
|
||||
return assignedWayPoints;
|
||||
}
|
||||
|
||||
|
||||
public override XElement Save(XDocument doc)
|
||||
{
|
||||
if (MoveWithLevel) return null;
|
||||
XElement element = new XElement("WayPoint");
|
||||
|
||||
element.Add(new XAttribute("ID", ID),
|
||||
new XAttribute("x", rect.X),
|
||||
new XAttribute("y", rect.Y),
|
||||
new XAttribute("spawn", spawnType));
|
||||
|
||||
if (idCardTags.Length > 0)
|
||||
{
|
||||
element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags)));
|
||||
}
|
||||
|
||||
if (assignedJob != null)
|
||||
{
|
||||
element.Add(new XAttribute("job", assignedJob.Name));
|
||||
}
|
||||
|
||||
doc.Root.Add(element);
|
||||
|
||||
if (linkedTo != null)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (MapEntity e in linkedTo)
|
||||
{
|
||||
element.Add(new XAttribute("linkedto" + i, e.ID));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public static void Load(XElement element)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
(int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
|
||||
|
||||
WayPoint w = new WayPoint(rect);
|
||||
|
||||
w.ID = int.Parse(element.Attribute("ID").Value);
|
||||
w.spawnType = (SpawnType)Enum.Parse(typeof(SpawnType),
|
||||
ToolBox.GetAttributeString(element, "spawn", "None"));
|
||||
|
||||
string idCardTagString = ToolBox.GetAttributeString(element, "idcardtags", "");
|
||||
if (!string.IsNullOrWhiteSpace(idCardTagString))
|
||||
{
|
||||
w.IdCardTags = idCardTagString.Split(',');
|
||||
}
|
||||
|
||||
string jobName = ToolBox.GetAttributeString(element, "job", "").ToLower();
|
||||
if (!string.IsNullOrWhiteSpace(jobName))
|
||||
{
|
||||
w.assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLower() == jobName);
|
||||
}
|
||||
|
||||
w.linkedToID = new List<int>();
|
||||
int i = 0;
|
||||
while (element.Attribute("linkedto" + i) != null)
|
||||
{
|
||||
w.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
WayPointList.Remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user