diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj
index e613082..3e40522 100644
--- a/CodeWalker.Core/CodeWalker.Core.csproj
+++ b/CodeWalker.Core/CodeWalker.Core.csproj
@@ -114,6 +114,7 @@
True
Resources.resx
+
diff --git a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs
index 7e61049..839e411 100644
--- a/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs
+++ b/CodeWalker.Core/GameFiles/FileTypes/YmapFile.cs
@@ -7,6 +7,8 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
+using CodeWalker.Core.Utils;
+using CodeWalker.World;
namespace CodeWalker.GameFiles
{
@@ -925,6 +927,7 @@ namespace CodeWalker.GameFiles
GrassInstanceBatches = batches.ToArray();
HasChanged = true;
+ UpdateGrassPhysDict(true);
}
public bool RemoveGrassBatch(YmapGrassInstanceBatch batch)
@@ -949,6 +952,10 @@ namespace CodeWalker.GameFiles
}
}
+ if (batches.Count <= 0)
+ {
+ UpdateGrassPhysDict(false);
+ }
GrassInstanceBatches = batches.ToArray();
@@ -1137,6 +1144,27 @@ namespace CodeWalker.GameFiles
}
+ private void UpdateGrassPhysDict(bool add)
+ {
+ var physDict = physicsDictionaries?.ToList() ?? new List();
+ var vproc1 = JenkHash.GenHash("v_proc1");
+ var vproc2 = JenkHash.GenHash("v_proc2"); // I think you need vproc2 as well.
+ var change = false;
+ if (!physDict.Contains(vproc1))
+ {
+ change = true;
+ if (add) physDict.Add(vproc1);
+ else physDict.Remove(vproc1);
+ }
+ if (!physDict.Contains(vproc2))
+ {
+ change = true;
+ if (add) physDict.Add(vproc2);
+ else physDict.Remove(vproc2);
+ }
+ if (change) physicsDictionaries = physDict.ToArray();
+ }
+
private static uint SetBit(uint value, int bit)
{
@@ -1493,6 +1521,8 @@ namespace CodeWalker.GameFiles
[TypeConverter(typeof(ExpandableObjectConverter))]
public class YmapGrassInstanceBatch
{
+ private const float BatchVertMultiplier = 0.00001525878f;
+
public Archetype Archetype { get; set; } //cached by GameFileCache on loading...
public rage__fwGrassInstanceListDef Batch { get; set; }
public rage__fwGrassInstanceListDef__InstanceData[] Instances { get; set; }
@@ -1504,10 +1534,478 @@ namespace CodeWalker.GameFiles
public float Distance; //used for rendering
public YmapFile Ymap { get; set; }
+ private List grassBounds; // for brush
+ public bool BrushEnabled; // for brush
+ public float BrushRadius = 5f; // for brush
+ public bool HasChanged; // for brush and renderer
+
+ // TODO: Make configurable.
+ const float BoundingSize = 0.3F;
+ static readonly Vector3 GrassMinMax = Vector3.One * BoundingSize;
+
public override string ToString()
{
return Batch.ToString();
}
+
+ public void UpdateInstanceCount()
+ {
+ var b = Batch;
+ var ins = b.InstanceList;
+ ins.Count1 = (ushort)Instances.Length;
+ b.InstanceList = ins;
+ Batch = b;
+ }
+
+ public bool IsPointBlockedByInstance(Vector3 point)
+ {
+ return grassBounds.Any(bb => bb.Contains(point) == ContainmentType.Contains);
+ }
+
+ private void ReInitializeBoundingCache()
+ {
+ // cache is already initialized correctly.
+ if (grassBounds != null && (grassBounds.Count == Instances.Length))
+ return;
+
+ // Clear the current bounding cache.
+ if (grassBounds == null)
+ grassBounds = new List();
+ else grassBounds?.Clear();
+
+ foreach (var inst in Instances)
+ {
+ // create bounding box for this instance.
+ var worldPos = GetGrassWorldPos(inst.Position, new BoundingBox(AABBMin, AABBMax));
+ var bb = new BoundingBox(worldPos - GrassMinMax, worldPos + GrassMinMax);
+ grassBounds.Add(bb);
+ }
+ }
+
+ public bool EraseInstancesAtMouse(
+ YmapGrassInstanceBatch batch,
+ SpaceRayIntersectResult mouseRay,
+ float radius)
+ {
+ rage__spdAABB batchAABB = batch.Batch.BatchAABB;
+ var oldInstanceBounds = new BoundingBox
+ (
+ batchAABB.min.XYZ(),
+ batchAABB.max.XYZ()
+ );
+ var deleteSphere = new BoundingSphere(mouseRay.Position, radius);
+
+ // check each instance to see if it's in the delete sphere
+ // thankfully we've just avoided an O(n^2) op using this bounds stuff (doesn't mean it's super fast though,
+ // but it's not super slow either, even at like 50,000 instances)
+ var insList = new List();
+ foreach (var instance in batch.Instances)
+ {
+ // get the world pos
+ var worldPos = GetGrassWorldPos(instance.Position, oldInstanceBounds);
+
+ // create a boundary around the instance.
+ var instanceBounds = new BoundingBox(worldPos - GrassMinMax, worldPos + GrassMinMax);
+
+ // check if the sphere contains the boundary.
+ var bb = new BoundingBox(instanceBounds.Minimum, instanceBounds.Maximum);
+ var ct = deleteSphere.Contains(ref bb);
+ if (ct == ContainmentType.Contains || ct == ContainmentType.Intersects)
+ {
+ //delInstances.Add(instance); // Add a copy of this instance
+ continue;
+ }
+ insList.Add(instance);
+ }
+ if (insList.Count == Instances.Length)
+ return false;
+
+ var newBounds = GetNewGrassBounds(insList, oldInstanceBounds);
+ // recalc instances
+ var b = RecalcBatch(newBounds, batch);
+ batch.Batch = b;
+ insList = RecalculateInstances(insList, oldInstanceBounds, newBounds);
+ batch.Instances = insList.ToArray();
+ return true;
+ }
+
+ public void CreateInstancesAtMouse(
+ YmapGrassInstanceBatch batch,
+ SpaceRayIntersectResult mouseRay,
+ float radius,
+ int amount,
+ Func spawnRayFunc,
+ Color color,
+ int ao,
+ int scale,
+ Vector3 pad,
+ bool randomScale)
+ {
+
+ ReInitializeBoundingCache();
+ var spawnPosition = mouseRay.Position;
+ var positions = new List();
+ var normals = new List();
+
+ // Get rand positions.
+ GetSpawns(spawnPosition, spawnRayFunc, positions, normals, radius, amount);
+ if (positions.Count <= 0) return;
+
+ // get the instance list
+ var instances =
+ batch.Instances?.ToList() ?? new List();
+ var batchAABB = batch.Batch.BatchAABB;
+
+ // make sure to store the old instance bounds for the original
+ // grass instances
+ var oldInstanceBounds = new BoundingBox(batchAABB.min.XYZ(), batchAABB.max.XYZ());
+
+ if (positions.Count <= 0)
+ return;
+
+ // Begin the spawn bounds.
+ var grassBound = new BoundingBox(positions[0] - GrassMinMax, positions[0] + GrassMinMax);
+ grassBound = EncapsulatePositions(positions, grassBound);
+
+ // Calculate the new spawn bounds.
+ var newInstanceBounds = new BoundingBox(oldInstanceBounds.Minimum, oldInstanceBounds.Maximum);
+ newInstanceBounds = instances.Count > 0
+ ? newInstanceBounds.Encapsulate(grassBound)
+ : new BoundingBox(grassBound.Minimum, grassBound.Maximum);
+
+ // now we need to recalculate the position of each instance
+ instances = RecalculateInstances(instances, oldInstanceBounds, newInstanceBounds);
+
+ // Add new instances at each spawn position with
+ // the parameters in the brush.
+ SpawnInstances(positions, normals, instances, newInstanceBounds, color, ao, scale, pad, randomScale);
+
+ // then recalc the bounds of the grass batch
+ var b = RecalcBatch(newInstanceBounds, batch);
+
+ // plug our values back in and refresh the ymap.
+ batch.Batch = b;
+
+ // Give back the new intsances
+ batch.Instances = instances.ToArray();
+ grassBounds.Clear();
+ }
+
+ // bhv approach recommended by dexy.
+ public YmapGrassInstanceBatch[] OptimizeInstances(YmapGrassInstanceBatch batch, float minRadius)
+ {
+ // this function will return an array of grass instance batches
+ // that are split up into sectors (groups) with a specific size.
+ // say for instance we have 30,000 instances spread across a large
+ // distance. We will split those instances into a grid-like group
+ // and return the groups as an array of batches.
+ var oldInstanceBounds = new BoundingBox(batch.Batch.BatchAABB.min.XYZ(), batch.Batch.BatchAABB.max.XYZ());
+
+ if (oldInstanceBounds.Radius() < minRadius)
+ {
+ return new [] { batch };
+ }
+
+ // Get our optimized grassInstances
+ var split = SplitGrassRecursive(batch.Instances.ToList(), oldInstanceBounds, minRadius);
+
+ // Initiate a new batch list.
+ var newBatches = new List();
+
+ foreach (var grassList in split)
+ {
+ // Create a new batch
+ var newBatch = new YmapGrassInstanceBatch
+ {
+ Archetype = batch.Archetype,
+ Ymap = batch.Ymap
+ };
+
+ // Get the boundary of the grassInstances
+ var newInstanceBounds = GetNewGrassBounds(grassList, oldInstanceBounds);
+
+ // Recalculate the batch boundaries.
+ var b = RecalcBatch(newInstanceBounds, newBatch);
+ newBatch.Batch = b;
+
+ var ins = RecalculateInstances(grassList, oldInstanceBounds, newInstanceBounds);
+ newBatch.Instances = ins.ToArray();
+ newBatches.Add(newBatch);
+ }
+
+ return newBatches.ToArray();
+ }
+
+ private List> SplitGrassRecursive(
+ IReadOnlyList grassInstances,
+ BoundingBox batchAABB,
+ float minRadius = 15F
+ )
+ {
+ var ret = new List>();
+ var oldPoints = SplitGrass(grassInstances, batchAABB);
+ while (true)
+ {
+ var stop = true;
+ var newPoints = new List>();
+ foreach (var mb in oldPoints)
+ {
+ // for some reason we got a null group?
+ if (mb == null)
+ continue;
+
+ // Get the bounds of the grassInstances list
+ var radius = GetNewGrassBounds(mb, batchAABB).Radius();
+
+ // check if the radius of the grassInstances
+ if (radius <= minRadius)
+ {
+ // this point list is within the minimum
+ // radius.
+ ret.Add(mb);
+ continue; // we don't need to continue.
+ }
+
+ // since we're here let's keep going
+ stop = false;
+
+ // split the grassInstances again
+ var s = SplitGrass(mb, batchAABB);
+
+ // add it into the new grassInstances list.
+ newPoints.AddRange(s);
+ }
+
+ // set the old grassInstances to the new grassInstances.
+ oldPoints = newPoints.ToArray();
+
+ // if we're done, and all grassInstances are within the desired size
+ // then end the loop.
+ if (stop) break;
+ }
+ return ret;
+ }
+
+ private List[] SplitGrass(
+ IReadOnlyList points,
+ BoundingBox batchAABB)
+ {
+ var pointGroup = new List[2];
+
+ // Calculate the bounds of these grassInstances.
+ var m = GetNewGrassBounds(points, batchAABB);
+
+ // Get the center and size
+ var mm = new Vector3
+ {
+ X = Math.Abs(m.Minimum.X - m.Maximum.X),
+ Y = Math.Abs(m.Minimum.Y - m.Maximum.Y),
+ Z = Math.Abs(m.Minimum.Z - m.Maximum.Z)
+ };
+
+ // x is the greatest axis...
+ if (mm.X > mm.Y && mm.X > mm.Z)
+ {
+ // Calculate both boundaries.
+ var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(mm.X * 0.5F, 0, 0));
+ var rhs = new BoundingBox(m.Minimum + new Vector3(mm.X * 0.5F, 0, 0), m.Maximum);
+
+ // Set the grassInstances accordingly.
+ pointGroup[0] = points
+ .Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ pointGroup[1] = points
+ .Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ }
+ // y is the greatest axis...
+ else if (mm.Y > mm.X && mm.Y > mm.Z)
+ {
+ // Calculate both boundaries.
+ var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(0, mm.Y * 0.5F, 0));
+ var rhs = new BoundingBox(m.Minimum + new Vector3(0, mm.Y * 0.5F, 0), m.Maximum);
+
+ // Set the grassInstances accordingly.
+ pointGroup[0] = points
+ .Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ pointGroup[1] = points
+ .Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ }
+ // z is the greatest axis...
+ else if (mm.Z > mm.X && mm.Z > mm.Y)
+ {
+ // Calculate both boundaries.
+ var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(0, 0, mm.Z * 0.5F));
+ var rhs = new BoundingBox(m.Minimum + new Vector3(0, 0, mm.Z * 0.5F), m.Maximum);
+
+ // Set the grassInstances accordingly.
+ pointGroup[0] = points
+ .Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ pointGroup[1] = points
+ .Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
+ }
+ return pointGroup;
+ }
+
+ private static BoundingBox GetNewGrassBounds(IReadOnlyList newGrass, BoundingBox oldAABB)
+ {
+ var grassPositions = newGrass.Select(x => GetGrassWorldPos(x.Position, oldAABB)).ToArray();
+ return BoundingBox.FromPoints(grassPositions).Expand(1f);
+ }
+
+ private void SpawnInstances(
+ IReadOnlyList positions,
+ IReadOnlyList normals,
+ ICollection instanceList,
+ BoundingBox instanceBounds,
+ Color color,
+ int ao,
+ int scale,
+ Vector3 pad,
+ bool randomScale)
+ {
+ for (var i = 0; i < positions.Count; i++)
+ {
+ var pos = positions[i];
+ // create the new instance.
+ var newInstance = CreateNewInstance(normals[i], color, ao, scale, pad, randomScale);
+
+ // get the grass position of the new instance and add it to the
+ // instance list
+ var grassPosition = GetGrassPos(pos, instanceBounds);
+ newInstance.Position = grassPosition;
+ instanceList.Add(newInstance);
+ }
+ }
+
+ private rage__fwGrassInstanceListDef__InstanceData CreateNewInstance(Vector3 normal, Color color, int ao, int scale, Vector3 pad,
+ bool randomScale = false)
+ {
+ //Vector3 pad = FloatUtil.ParseVector3String(PadTextBox.Text);
+ //int scale = (int)ScaleNumericUpDown.Value;
+ var rand = new Random();
+ if (randomScale)
+ scale = rand.Next(scale / 2, scale);
+ var newInstance = new rage__fwGrassInstanceListDef__InstanceData
+ {
+ Ao = (byte)ao,
+ Scale = (byte)scale,
+ Color = new ArrayOfBytes3 { b0 = color.R, b1 = color.G, b2 = color.B },
+ Pad = new ArrayOfBytes3 { b0 = (byte)pad.X, b1 = (byte)pad.Y, b2 = (byte)pad.Z },
+ NormalX = (byte)((normal.X + 1) * 0.5F * 255F),
+ NormalY = (byte)((normal.Y + 1) * 0.5F * 255F)
+ };
+ return newInstance;
+ }
+
+ private rage__fwGrassInstanceListDef RecalcBatch(BoundingBox newInstanceBounds, YmapGrassInstanceBatch batch)
+ {
+ batch.AABBMax = newInstanceBounds.Maximum;
+ batch.AABBMin = newInstanceBounds.Minimum;
+ batch.Position = newInstanceBounds.Center();
+ batch.Radius = newInstanceBounds.Radius();
+ var b = batch.Batch;
+ b.BatchAABB = new rage__spdAABB
+ {
+ min =
+ new Vector4(newInstanceBounds.Minimum,
+ 0), // Let's pass the new stuff into the batchabb as well just because.
+ max = new Vector4(newInstanceBounds.Maximum, 0)
+ };
+ return b;
+ }
+
+ private void GetSpawns(
+ Vector3 origin, Func spawnRayFunc,
+ ICollection positions,
+ ICollection normals,
+ float radius,
+ int resolution = 28)
+ {
+ var rand = new Random();
+ for (var i = 0; i < resolution; i++)
+ {
+ var randX = (float)rand.NextDouble(-radius, radius);
+ var randY = (float)rand.NextDouble(-radius, radius);
+ if (Math.Abs(randX) > 0 && Math.Abs(randY) > 0)
+ {
+ randX *= .7071f;
+ randY *= .7071f;
+ }
+ var posOffset = origin + new Vector3(randX, randY, 2f);
+ var spaceRay = spawnRayFunc.Invoke(posOffset);
+ if (!spaceRay.Hit) continue;
+ // not truly O(n^2) but may be slow...
+ // actually just did some testing, not slow at all.
+ if (IsPointBlockedByInstance(spaceRay.Position)) continue;
+ normals.Add(spaceRay.Normal);
+ positions.Add(spaceRay.Position);
+ }
+ }
+
+ private static List RecalculateInstances(
+ List instances,
+ BoundingBox oldInstanceBounds,
+ BoundingBox newInstanceBounds)
+ {
+ var refreshList = new List();
+ foreach (var inst in instances)
+ {
+ // Copy instance
+ var copy =
+ new rage__fwGrassInstanceListDef__InstanceData
+ {
+ Position = inst.Position,
+ Ao = inst.Ao,
+ Color = inst.Color,
+ NormalX = inst.NormalX,
+ NormalY = inst.NormalY,
+ Pad = inst.Pad,
+ Scale = inst.Scale
+ };
+ // get the position from where we would be in the old bounds, and move it to
+ // the position it needs to be in the new bounds.
+ var oldPos = GetGrassWorldPos(copy.Position, oldInstanceBounds);
+ //var oldPos = oldInstanceBounds.min + oldInstanceBounds.Size * (grassPos * BatchVertMultiplier);
+ copy.Position = GetGrassPos(oldPos, newInstanceBounds);
+ refreshList.Add(copy);
+ }
+ instances = refreshList.ToList();
+ return instances;
+ }
+
+ private static BoundingBox EncapsulatePositions(IEnumerable positions, BoundingBox bounds)
+ {
+ foreach (var pos in positions)
+ {
+ var posBounds = new BoundingBox(pos - (GrassMinMax + 0.1f), pos + (GrassMinMax + 0.1f));
+ bounds = bounds.Encapsulate(posBounds);
+ }
+ return bounds;
+ }
+
+ private static ArrayOfUshorts3 GetGrassPos(Vector3 worldPos, BoundingBox batchAABB)
+ {
+ var offset = worldPos - batchAABB.Minimum;
+ var size = batchAABB.Size();
+ var percentage =
+ new Vector3(
+ offset.X / size.X,
+ offset.Y / size.Y,
+ offset.Z / size.Z
+ );
+ var instancePos = percentage / BatchVertMultiplier;
+ return new ArrayOfUshorts3
+ {
+ u0 = (ushort)instancePos.X,
+ u1 = (ushort)instancePos.Y,
+ u2 = (ushort)instancePos.Z
+ };
+ }
+
+ private static Vector3 GetGrassWorldPos(ArrayOfUshorts3 grassPos, BoundingBox batchAABB)
+ {
+ return batchAABB.Minimum + batchAABB.Size() * (grassPos.XYZ() * BatchVertMultiplier);
+ }
}
[TypeConverter(typeof(ExpandableObjectConverter))]
@@ -1548,7 +2046,6 @@ namespace CodeWalker.GameFiles
}
}
-
[TypeConverter(typeof(ExpandableObjectConverter))]
public class YmapTimeCycleModifier
{
diff --git a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs
index f9d548e..08a312c 100644
--- a/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs
+++ b/CodeWalker.Core/GameFiles/FileTypes/YtypFile.cs
@@ -241,7 +241,121 @@ namespace CodeWalker.GameFiles
}
+ public void AddArchetype(Archetype arch)
+ {
+ List allArchs = new List();
+ if (AllArchetypes != null)
+ allArchs.AddRange(AllArchetypes);
+
+ allArchs.Add(arch);
+
+ AllArchetypes = allArchs.ToArray();
+ }
+
+ public void RemoveArchetype(Archetype arch)
+ {
+ List allArchs = new List();
+
+ if (AllArchetypes != null)
+ allArchs.AddRange(AllArchetypes);
+
+ if (allArchs.Contains(arch))
+ allArchs.Remove(arch);
+
+ AllArchetypes = allArchs.ToArray();
+ }
+
+ public byte[] Save()
+ {
+ MetaBuilder mb = new MetaBuilder();
+
+ var mdb = mb.EnsureBlock(MetaName.CMapTypes);
+
+ CMapTypes mapTypes = CMapTypes;
+
+ if((AllArchetypes != null) && (AllArchetypes.Length > 0))
+ {
+ MetaPOINTER[] archPtrs = new MetaPOINTER[AllArchetypes.Length];
+
+ for(int i=0; i 0))
+ {
+ MetaPOINTER[] cetPtrs = new MetaPOINTER[CompositeEntityTypes.Length] ;
+
+ for (int i = 0; i < cetPtrs.Length; i++)
+ {
+ var cet = CompositeEntityTypes[i];
+ cetPtrs[i] = mb.AddItemPtr(MetaName.CCompositeEntityType, cet);
+ }
+
+ mapTypes.compositeEntityTypes = mb.AddItemArrayPtr(MetaName.CCompositeEntityType, cetPtrs);
+ }
+
+ mb.AddItem(MetaName.CMapTypes, mapTypes);
+
+ mb.AddStructureInfo(MetaName.CMapTypes);
+ mb.AddStructureInfo(MetaName.CBaseArchetypeDef);
+ mb.AddStructureInfo(MetaName.CMloArchetypeDef);
+ mb.AddStructureInfo(MetaName.CTimeArchetypeDef);
+ mb.AddStructureInfo(MetaName.CMloRoomDef);
+ mb.AddStructureInfo(MetaName.CMloPortalDef);
+ mb.AddStructureInfo(MetaName.CMloEntitySet);
+ mb.AddStructureInfo(MetaName.CCompositeEntityType);
+
+ mb.AddEnumInfo((MetaName)1991964615);
+ mb.AddEnumInfo((MetaName)1294270217);
+ mb.AddEnumInfo((MetaName)1264241711);
+ mb.AddEnumInfo((MetaName)648413703);
+ mb.AddEnumInfo((MetaName)3573596290);
+ mb.AddEnumInfo((MetaName)700327466);
+ mb.AddEnumInfo((MetaName)193194928);
+ mb.AddEnumInfo((MetaName)2266515059);
+
+ Meta = mb.GetMeta();
+
+ byte[] data = ResourceBuilder.Build(Meta, 2); //ymap is version 2...
+
+ return data;
+
+ }
}
diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs
index d048d17..f9392c7 100644
--- a/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs
+++ b/CodeWalker.Core/GameFiles/MetaTypes/Archetype.cs
@@ -128,6 +128,9 @@ namespace CodeWalker.GameFiles
{
public override MetaName Type => MetaName.CMloArchetypeDef;
+ public CMloArchetypeDef _BaseMloArchetypeDef;
+ public CMloArchetypeDef BaseMloArchetypeDef { get { return _BaseMloArchetypeDef; } set { _BaseMloArchetypeDef = value; } }
+
public CMloArchetypeDefData _MloArchetypeDef;
public CMloArchetypeDefData MloArchetypeDef { get { return _MloArchetypeDef; } set { _MloArchetypeDef = value; } }
@@ -141,6 +144,7 @@ namespace CodeWalker.GameFiles
{
Ytyp = ytyp;
InitVars(ref arch._BaseArchetypeDef);
+ BaseMloArchetypeDef = arch;
MloArchetypeDef = arch.MloArchetypeDef;
}
diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs
index 7ee83cc..c617940 100644
--- a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs
+++ b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs
@@ -935,6 +935,10 @@ namespace CodeWalker.GameFiles
[TC(typeof(EXP))] public struct ArrayOfUshorts3 //array of 3 ushorts
{
public ushort u0, u1, u2;
+ public Vector3 XYZ()
+ {
+ return new Vector3(u0, u1, u2);
+ }
public override string ToString()
{
return u0.ToString() + ", " + u1.ToString() + ", " + u2.ToString();
diff --git a/CodeWalker.Core/GameFiles/Resources/Bounds.cs b/CodeWalker.Core/GameFiles/Resources/Bounds.cs
index 705dad5..1f25591 100644
--- a/CodeWalker.Core/GameFiles/Resources/Bounds.cs
+++ b/CodeWalker.Core/GameFiles/Resources/Bounds.cs
@@ -1443,6 +1443,13 @@ namespace CodeWalker.GameFiles
return Materials[type.Index];
}
+ public static BoundsMaterialData GetMaterial(byte index)
+ {
+ if (Materials == null) return null;
+ if ((int)index >= Materials.Count) return null;
+ return Materials[index];
+ }
+
public static string GetMaterialName(BoundsMaterialType type)
{
var m = GetMaterial(type);
diff --git a/CodeWalker.Core/GameFiles/Resources/Drawable.cs b/CodeWalker.Core/GameFiles/Resources/Drawable.cs
index f229e0b..72acf0b 100644
--- a/CodeWalker.Core/GameFiles/Resources/Drawable.cs
+++ b/CodeWalker.Core/GameFiles/Resources/Drawable.cs
@@ -718,16 +718,16 @@ namespace CodeWalker.GameFiles
//public float TranslationX { get; set; }
//public float TranslationY { get; set; }
//public float TranslationZ { get; set; }
- public uint Unknown_1Ch { get; set; } // 0x00000000
- public float Unknown_20h { get; set; } // 1.0
- public float Unknown_24h { get; set; } // 1.0
- public float Unknown_28h { get; set; } // 1.0
- public float Unknown_2Ch { get; set; } // 1.0
- public ushort Unknown_30h { get; set; } //limb end index? IK chain?
+ public uint Unknown_1Ch { get; set; } // 0x00000000 RHW?
+ public float ScaleX { get; set; } // 1.0
+ public float ScaleY { get; set; } // 1.0
+ public float ScaleZ { get; set; } // 1.0
+ public float Unknown_2Ch { get; set; } // 1.0 RHW?
+ public ushort NextSiblingIndex { get; set; } //limb end index? IK chain?
public short ParentIndex { get; set; }
public uint Unknown_34h { get; set; } // 0x00000000
public ulong NamePointer { get; set; }
- public ushort Unknown_40h { get; set; }
+ public ushort Flags { get; set; }
public ushort Unknown_42h { get; set; }
public ushort Id { get; set; }
public ushort Unknown_46h { get; set; }
@@ -756,15 +756,15 @@ namespace CodeWalker.GameFiles
//this.TranslationY = reader.ReadSingle();
//this.TranslationZ = reader.ReadSingle();
this.Unknown_1Ch = reader.ReadUInt32();
- this.Unknown_20h = reader.ReadSingle();
- this.Unknown_24h = reader.ReadSingle();
- this.Unknown_28h = reader.ReadSingle();
+ this.ScaleX = reader.ReadSingle();
+ this.ScaleY = reader.ReadSingle();
+ this.ScaleZ = reader.ReadSingle();
this.Unknown_2Ch = reader.ReadSingle();
- this.Unknown_30h = reader.ReadUInt16();
+ this.NextSiblingIndex = reader.ReadUInt16();
this.ParentIndex = reader.ReadInt16();
this.Unknown_34h = reader.ReadUInt32();
this.NamePointer = reader.ReadUInt64();
- this.Unknown_40h = reader.ReadUInt16();
+ this.Flags = reader.ReadUInt16();
this.Unknown_42h = reader.ReadUInt16();
this.Id = reader.ReadUInt16();
this.Unknown_46h = reader.ReadUInt16();
@@ -796,15 +796,15 @@ namespace CodeWalker.GameFiles
//writer.Write(this.TranslationY);
//writer.Write(this.TranslationZ);
writer.Write(this.Unknown_1Ch);
- writer.Write(this.Unknown_20h);
- writer.Write(this.Unknown_24h);
- writer.Write(this.Unknown_28h);
+ writer.Write(this.ScaleX);
+ writer.Write(this.ScaleY);
+ writer.Write(this.ScaleZ);
writer.Write(this.Unknown_2Ch);
- writer.Write(this.Unknown_30h);
+ writer.Write(this.NextSiblingIndex);
writer.Write(this.ParentIndex);
writer.Write(this.Unknown_34h);
writer.Write(this.NamePointer);
- writer.Write(this.Unknown_40h);
+ writer.Write(this.Flags);
writer.Write(this.Unknown_42h);
writer.Write(this.Id);
writer.Write(this.Unknown_46h);
@@ -968,12 +968,8 @@ namespace CodeWalker.GameFiles
public float Unknown_50h { get; set; } // -pi
public float Unknown_54h { get; set; } // pi
public float Unknown_58h { get; set; } // 1.0
- public float Unknown_5Ch { get; set; }
- public float Unknown_60h { get; set; }
- public float Unknown_64h { get; set; }
- public float Unknown_68h { get; set; }
- public float Unknown_6Ch { get; set; }
- public float Unknown_70h { get; set; }
+ public Vector3 Min { get; set; }
+ public Vector3 Max { get; set; }
public float Unknown_74h { get; set; } // pi
public float Unknown_78h { get; set; } // -pi
public float Unknown_7Ch { get; set; } // pi
diff --git a/CodeWalker.Core/Utils/BoundingBoxes.cs b/CodeWalker.Core/Utils/BoundingBoxes.cs
new file mode 100644
index 0000000..d602c8f
--- /dev/null
+++ b/CodeWalker.Core/Utils/BoundingBoxes.cs
@@ -0,0 +1,39 @@
+using System;
+using SharpDX;
+
+namespace CodeWalker.Core.Utils
+{
+ public static class BoundingBoxExtensions
+ {
+ public static Vector3 Size(this BoundingBox bounds)
+ {
+ return new Vector3(
+ Math.Abs(bounds.Maximum.X - bounds.Minimum.X),
+ Math.Abs(bounds.Maximum.Y - bounds.Minimum.Y),
+ Math.Abs(bounds.Maximum.Z - bounds.Minimum.Z));
+ }
+
+ public static Vector3 Center(this BoundingBox bounds)
+ {
+ return (bounds.Minimum + bounds.Maximum) * 0.5F;
+ }
+
+ public static BoundingBox Encapsulate(this BoundingBox box, BoundingBox bounds)
+ {
+ box.Minimum = Vector3.Min(box.Minimum, bounds.Minimum);
+ box.Maximum = Vector3.Max(box.Maximum, bounds.Maximum);
+ return box;
+ }
+
+ public static float Radius(this BoundingBox box)
+ {
+ var extents = (box.Maximum - box.Minimum) * 0.5F;
+ return extents.Length();
+ }
+
+ public static BoundingBox Expand(this BoundingBox b, float amount)
+ {
+ return new BoundingBox(b.Minimum - Vector3.One * amount, b.Maximum + Vector3.One * amount);
+ }
+ }
+}
diff --git a/CodeWalker.Core/Utils/Vectors.cs b/CodeWalker.Core/Utils/Vectors.cs
index b953850..19b354e 100644
--- a/CodeWalker.Core/Utils/Vectors.cs
+++ b/CodeWalker.Core/Utils/Vectors.cs
@@ -4,18 +4,17 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using CodeWalker.GameFiles;
namespace CodeWalker
{
public static class Vectors
{
-
public static Vector3 XYZ(this Vector4 v)
{
return new Vector3(v.X, v.Y, v.Z);
}
-
public static Vector3 Round(this Vector3 v)
{
return new Vector3((float)Math.Round(v.X), (float)Math.Round(v.Y), (float)Math.Round(v.Z));
@@ -25,7 +24,6 @@ namespace CodeWalker
{
return new Vector4((float)Math.Floor(v.X), (float)Math.Floor(v.Y), (float)Math.Floor(v.Z), (float)Math.Floor(v.W));
}
-
}
diff --git a/Project/Panels/EditProjectManifestPanel.cs b/Project/Panels/EditProjectManifestPanel.cs
index d15cb87..a253983 100644
--- a/Project/Panels/EditProjectManifestPanel.cs
+++ b/Project/Panels/EditProjectManifestPanel.cs
@@ -138,6 +138,18 @@ namespace CodeWalker.Project.Panels
}
}
+ if (ymap.GrassInstanceBatches != null)
+ {
+ foreach (var batch in ymap.GrassInstanceBatches)
+ {
+ var ytyp = batch.Archetype?.Ytyp;
+ var ytypname = getYtypName(ytyp);
+ if (ytyp != null)
+ {
+ mapdeps[ytypname] = ytyp;
+ }
+ }
+ }
sb.AppendLine(" - ");
sb.AppendLine(" " + ymapname + "");
diff --git a/Project/Panels/EditYmapGrassPanel.Designer.cs b/Project/Panels/EditYmapGrassPanel.Designer.cs
index 824c62a..c281c66 100644
--- a/Project/Panels/EditYmapGrassPanel.Designer.cs
+++ b/Project/Panels/EditYmapGrassPanel.Designer.cs
@@ -28,28 +28,642 @@
///
private void InitializeComponent()
{
+ this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EditYmapGrassPanel));
+ this.tabControl1 = new System.Windows.Forms.TabControl();
+ this.GrassBatchTab = new System.Windows.Forms.TabPage();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.label12 = new System.Windows.Forms.Label();
+ this.label11 = new System.Windows.Forms.Label();
+ this.OrientToTerrainNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.ScaleRangeTextBox = new System.Windows.Forms.TextBox();
+ this.label10 = new System.Windows.Forms.Label();
+ this.LodFadeRangeNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.label9 = new System.Windows.Forms.Label();
+ this.LodFadeStartDistanceNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.label6 = new System.Windows.Forms.Label();
+ this.LodDistNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.BrushTab = new System.Windows.Forms.TabPage();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.GrassColorLabel = new System.Windows.Forms.Label();
+ this.label15 = new System.Windows.Forms.Label();
+ this.label4 = new System.Windows.Forms.Label();
+ this.AoNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.PadTextBox = new System.Windows.Forms.TextBox();
+ this.ScaleNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.label2 = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.RandomizeScaleCheckBox = new System.Windows.Forms.CheckBox();
+ this.brushSettingsGroupBox = new System.Windows.Forms.GroupBox();
+ this.DensityNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.RadiusNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.label5 = new System.Windows.Forms.Label();
+ this.radiusLabel = new System.Windows.Forms.Label();
+ this.OptimizeBatchButtonTooltip = new System.Windows.Forms.ToolTip(this.components);
+ this.OptimizeBatchButton = new System.Windows.Forms.Button();
+ this.ExtentsMinTextBox = new System.Windows.Forms.TextBox();
+ this.label14 = new System.Windows.Forms.Label();
+ this.ExtentsMaxTextBox = new System.Windows.Forms.TextBox();
+ this.label13 = new System.Windows.Forms.Label();
+ this.GrassDeleteButton = new System.Windows.Forms.Button();
+ this.GrassAddToProjectButton = new System.Windows.Forms.Button();
+ this.HashLabel = new System.Windows.Forms.Label();
+ this.ArchetypeNameTextBox = new System.Windows.Forms.TextBox();
+ this.label7 = new System.Windows.Forms.Label();
+ this.PositionTextBox = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
+ this.GrassGoToButton = new System.Windows.Forms.Button();
+ this.label17 = new System.Windows.Forms.Label();
+ this.OptmizationThresholdNumericUpDown = new System.Windows.Forms.NumericUpDown();
+ this.BrushModeCheckBox = new System.Windows.Forms.CheckBox();
+ this.label8 = new System.Windows.Forms.Label();
+ this.tabControl1.SuspendLayout();
+ this.GrassBatchTab.SuspendLayout();
+ this.groupBox1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.OrientToTerrainNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodFadeRangeNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodFadeStartDistanceNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodDistNumericUpDown)).BeginInit();
+ this.BrushTab.SuspendLayout();
+ this.groupBox2.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.AoNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.ScaleNumericUpDown)).BeginInit();
+ this.brushSettingsGroupBox.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.DensityNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.RadiusNumericUpDown)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.OptmizationThresholdNumericUpDown)).BeginInit();
this.SuspendLayout();
//
+ // tabControl1
+ //
+ this.tabControl1.Controls.Add(this.GrassBatchTab);
+ this.tabControl1.Controls.Add(this.BrushTab);
+ this.tabControl1.Location = new System.Drawing.Point(12, 65);
+ this.tabControl1.Name = "tabControl1";
+ this.tabControl1.SelectedIndex = 0;
+ this.tabControl1.Size = new System.Drawing.Size(486, 181);
+ this.tabControl1.TabIndex = 37;
+ //
+ // GrassBatchTab
+ //
+ this.GrassBatchTab.Controls.Add(this.groupBox1);
+ this.GrassBatchTab.Location = new System.Drawing.Point(4, 22);
+ this.GrassBatchTab.Name = "GrassBatchTab";
+ this.GrassBatchTab.Padding = new System.Windows.Forms.Padding(3);
+ this.GrassBatchTab.Size = new System.Drawing.Size(478, 155);
+ this.GrassBatchTab.TabIndex = 0;
+ this.GrassBatchTab.Text = "Grass Batch";
+ this.GrassBatchTab.UseVisualStyleBackColor = true;
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.label12);
+ this.groupBox1.Controls.Add(this.label11);
+ this.groupBox1.Controls.Add(this.OrientToTerrainNumericUpDown);
+ this.groupBox1.Controls.Add(this.ScaleRangeTextBox);
+ this.groupBox1.Controls.Add(this.label10);
+ this.groupBox1.Controls.Add(this.LodFadeRangeNumericUpDown);
+ this.groupBox1.Controls.Add(this.label9);
+ this.groupBox1.Controls.Add(this.LodFadeStartDistanceNumericUpDown);
+ this.groupBox1.Controls.Add(this.label6);
+ this.groupBox1.Controls.Add(this.LodDistNumericUpDown);
+ this.groupBox1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.groupBox1.Location = new System.Drawing.Point(12, 6);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(460, 143);
+ this.groupBox1.TabIndex = 42;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Batch";
+ //
+ // label12
+ //
+ this.label12.AutoSize = true;
+ this.label12.Location = new System.Drawing.Point(197, 24);
+ this.label12.Name = "label12";
+ this.label12.Size = new System.Drawing.Size(69, 13);
+ this.label12.TabIndex = 47;
+ this.label12.Text = "Scale Range";
+ //
+ // label11
+ //
+ this.label11.AutoSize = true;
+ this.label11.Location = new System.Drawing.Point(8, 102);
+ this.label11.Name = "label11";
+ this.label11.Size = new System.Drawing.Size(87, 13);
+ this.label11.TabIndex = 7;
+ this.label11.Text = "Orient To Terrain";
+ //
+ // OrientToTerrainNumericUpDown
+ //
+ this.OrientToTerrainNumericUpDown.DecimalPlaces = 1;
+ this.OrientToTerrainNumericUpDown.Location = new System.Drawing.Point(121, 100);
+ this.OrientToTerrainNumericUpDown.Maximum = new decimal(new int[] {
+ 1,
+ 0,
+ 0,
+ 0});
+ this.OrientToTerrainNumericUpDown.Name = "OrientToTerrainNumericUpDown";
+ this.OrientToTerrainNumericUpDown.Size = new System.Drawing.Size(61, 20);
+ this.OrientToTerrainNumericUpDown.TabIndex = 6;
+ this.OrientToTerrainNumericUpDown.ValueChanged += new System.EventHandler(this.OrientToTerrainNumericUpDown_ValueChanged);
+ //
+ // ScaleRangeTextBox
+ //
+ this.ScaleRangeTextBox.Location = new System.Drawing.Point(270, 21);
+ this.ScaleRangeTextBox.Name = "ScaleRangeTextBox";
+ this.ScaleRangeTextBox.Size = new System.Drawing.Size(166, 20);
+ this.ScaleRangeTextBox.TabIndex = 46;
+ this.ScaleRangeTextBox.TextChanged += new System.EventHandler(this.ScaleRangeTextBox_TextChanged);
+ //
+ // label10
+ //
+ this.label10.AutoSize = true;
+ this.label10.Location = new System.Drawing.Point(8, 76);
+ this.label10.Name = "label10";
+ this.label10.Size = new System.Drawing.Size(107, 13);
+ this.label10.TabIndex = 5;
+ this.label10.Text = "Lod Inst Fade Range";
+ //
+ // LodFadeRangeNumericUpDown
+ //
+ this.LodFadeRangeNumericUpDown.DecimalPlaces = 4;
+ this.LodFadeRangeNumericUpDown.Increment = new decimal(new int[] {
+ 1,
+ 0,
+ 0,
+ 65536});
+ this.LodFadeRangeNumericUpDown.Location = new System.Drawing.Point(121, 74);
+ this.LodFadeRangeNumericUpDown.Maximum = new decimal(new int[] {
+ 99999,
+ 0,
+ 0,
+ 0});
+ this.LodFadeRangeNumericUpDown.Name = "LodFadeRangeNumericUpDown";
+ this.LodFadeRangeNumericUpDown.Size = new System.Drawing.Size(61, 20);
+ this.LodFadeRangeNumericUpDown.TabIndex = 4;
+ this.LodFadeRangeNumericUpDown.ValueChanged += new System.EventHandler(this.LodFadeRangeNumericUpDown_ValueChanged);
+ //
+ // label9
+ //
+ this.label9.AutoSize = true;
+ this.label9.Location = new System.Drawing.Point(8, 50);
+ this.label9.Name = "label9";
+ this.label9.Size = new System.Drawing.Size(98, 13);
+ this.label9.TabIndex = 3;
+ this.label9.Text = "Lod Fade Start Dist";
+ //
+ // LodFadeStartDistanceNumericUpDown
+ //
+ this.LodFadeStartDistanceNumericUpDown.DecimalPlaces = 4;
+ this.LodFadeStartDistanceNumericUpDown.Location = new System.Drawing.Point(121, 48);
+ this.LodFadeStartDistanceNumericUpDown.Maximum = new decimal(new int[] {
+ 99999,
+ 0,
+ 0,
+ 0});
+ this.LodFadeStartDistanceNumericUpDown.Name = "LodFadeStartDistanceNumericUpDown";
+ this.LodFadeStartDistanceNumericUpDown.Size = new System.Drawing.Size(61, 20);
+ this.LodFadeStartDistanceNumericUpDown.TabIndex = 2;
+ this.LodFadeStartDistanceNumericUpDown.ValueChanged += new System.EventHandler(this.LodFadeStartDistanceNumericUpDown_ValueChanged);
+ //
+ // label6
+ //
+ this.label6.AutoSize = true;
+ this.label6.Location = new System.Drawing.Point(8, 24);
+ this.label6.Name = "label6";
+ this.label6.Size = new System.Drawing.Size(39, 13);
+ this.label6.TabIndex = 1;
+ this.label6.Text = "lodDist";
+ //
+ // LodDistNumericUpDown
+ //
+ this.LodDistNumericUpDown.Location = new System.Drawing.Point(121, 22);
+ this.LodDistNumericUpDown.Maximum = new decimal(new int[] {
+ 99999,
+ 0,
+ 0,
+ 0});
+ this.LodDistNumericUpDown.Name = "LodDistNumericUpDown";
+ this.LodDistNumericUpDown.Size = new System.Drawing.Size(61, 20);
+ this.LodDistNumericUpDown.TabIndex = 0;
+ this.LodDistNumericUpDown.ValueChanged += new System.EventHandler(this.LodDistNumericUpDown_ValueChanged);
+ //
+ // BrushTab
+ //
+ this.BrushTab.Controls.Add(this.groupBox2);
+ this.BrushTab.Controls.Add(this.brushSettingsGroupBox);
+ this.BrushTab.Location = new System.Drawing.Point(4, 22);
+ this.BrushTab.Name = "BrushTab";
+ this.BrushTab.Padding = new System.Windows.Forms.Padding(3);
+ this.BrushTab.Size = new System.Drawing.Size(478, 155);
+ this.BrushTab.TabIndex = 1;
+ this.BrushTab.Text = " Brush";
+ this.BrushTab.UseVisualStyleBackColor = true;
+ //
+ // groupBox2
+ //
+ this.groupBox2.Controls.Add(this.GrassColorLabel);
+ this.groupBox2.Controls.Add(this.label15);
+ this.groupBox2.Controls.Add(this.label4);
+ this.groupBox2.Controls.Add(this.AoNumericUpDown);
+ this.groupBox2.Controls.Add(this.PadTextBox);
+ this.groupBox2.Controls.Add(this.ScaleNumericUpDown);
+ this.groupBox2.Controls.Add(this.label2);
+ this.groupBox2.Controls.Add(this.label3);
+ this.groupBox2.Controls.Add(this.RandomizeScaleCheckBox);
+ this.groupBox2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.groupBox2.Location = new System.Drawing.Point(191, 3);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Size = new System.Drawing.Size(281, 146);
+ this.groupBox2.TabIndex = 39;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "Instance Settings";
+ //
+ // GrassColorLabel
+ //
+ this.GrassColorLabel.BackColor = System.Drawing.Color.White;
+ this.GrassColorLabel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ this.GrassColorLabel.Location = new System.Drawing.Point(50, 22);
+ this.GrassColorLabel.Name = "GrassColorLabel";
+ this.GrassColorLabel.Size = new System.Drawing.Size(119, 21);
+ this.GrassColorLabel.TabIndex = 12;
+ this.GrassColorLabel.Click += new System.EventHandler(this.GrassColorLabel_Click);
+ //
+ // label15
+ //
+ this.label15.AutoSize = true;
+ this.label15.Location = new System.Drawing.Point(9, 106);
+ this.label15.Name = "label15";
+ this.label15.Size = new System.Drawing.Size(26, 13);
+ this.label15.TabIndex = 46;
+ this.label15.Text = "Pad";
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(9, 79);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(34, 13);
+ this.label4.TabIndex = 16;
+ this.label4.Text = "Scale";
+ //
+ // AoNumericUpDown
+ //
+ this.AoNumericUpDown.Location = new System.Drawing.Point(50, 51);
+ this.AoNumericUpDown.Maximum = new decimal(new int[] {
+ 255,
+ 0,
+ 0,
+ 0});
+ this.AoNumericUpDown.Name = "AoNumericUpDown";
+ this.AoNumericUpDown.Size = new System.Drawing.Size(120, 20);
+ this.AoNumericUpDown.TabIndex = 15;
+ this.AoNumericUpDown.Value = new decimal(new int[] {
+ 255,
+ 0,
+ 0,
+ 0});
+ //
+ // PadTextBox
+ //
+ this.PadTextBox.Location = new System.Drawing.Point(49, 103);
+ this.PadTextBox.Name = "PadTextBox";
+ this.PadTextBox.Size = new System.Drawing.Size(120, 20);
+ this.PadTextBox.TabIndex = 45;
+ this.PadTextBox.Text = "0, 0, 0";
+ //
+ // ScaleNumericUpDown
+ //
+ this.ScaleNumericUpDown.Location = new System.Drawing.Point(49, 77);
+ this.ScaleNumericUpDown.Maximum = new decimal(new int[] {
+ 255,
+ 0,
+ 0,
+ 0});
+ this.ScaleNumericUpDown.Name = "ScaleNumericUpDown";
+ this.ScaleNumericUpDown.Size = new System.Drawing.Size(120, 20);
+ this.ScaleNumericUpDown.TabIndex = 17;
+ this.ScaleNumericUpDown.Value = new decimal(new int[] {
+ 255,
+ 0,
+ 0,
+ 0});
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(9, 53);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(20, 13);
+ this.label2.TabIndex = 14;
+ this.label2.Text = "Ao";
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(9, 26);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(31, 13);
+ this.label3.TabIndex = 13;
+ this.label3.Text = "Color";
+ //
+ // RandomizeScaleCheckBox
+ //
+ this.RandomizeScaleCheckBox.AutoSize = true;
+ this.RandomizeScaleCheckBox.Location = new System.Drawing.Point(176, 79);
+ this.RandomizeScaleCheckBox.Name = "RandomizeScaleCheckBox";
+ this.RandomizeScaleCheckBox.Size = new System.Drawing.Size(66, 17);
+ this.RandomizeScaleCheckBox.TabIndex = 18;
+ this.RandomizeScaleCheckBox.Text = "Random";
+ this.RandomizeScaleCheckBox.UseVisualStyleBackColor = true;
+ //
+ // brushSettingsGroupBox
+ //
+ this.brushSettingsGroupBox.Controls.Add(this.DensityNumericUpDown);
+ this.brushSettingsGroupBox.Controls.Add(this.RadiusNumericUpDown);
+ this.brushSettingsGroupBox.Controls.Add(this.label5);
+ this.brushSettingsGroupBox.Controls.Add(this.radiusLabel);
+ this.brushSettingsGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.brushSettingsGroupBox.Location = new System.Drawing.Point(8, 3);
+ this.brushSettingsGroupBox.Name = "brushSettingsGroupBox";
+ this.brushSettingsGroupBox.Size = new System.Drawing.Size(177, 146);
+ this.brushSettingsGroupBox.TabIndex = 38;
+ this.brushSettingsGroupBox.TabStop = false;
+ this.brushSettingsGroupBox.Text = "Brush Settings";
+ //
+ // DensityNumericUpDown
+ //
+ this.DensityNumericUpDown.Location = new System.Drawing.Point(76, 57);
+ this.DensityNumericUpDown.Maximum = new decimal(new int[] {
+ 128,
+ 0,
+ 0,
+ 0});
+ this.DensityNumericUpDown.Name = "DensityNumericUpDown";
+ this.DensityNumericUpDown.Size = new System.Drawing.Size(84, 20);
+ this.DensityNumericUpDown.TabIndex = 20;
+ this.DensityNumericUpDown.Value = new decimal(new int[] {
+ 28,
+ 0,
+ 0,
+ 0});
+ //
+ // RadiusNumericUpDown
+ //
+ this.RadiusNumericUpDown.DecimalPlaces = 2;
+ this.RadiusNumericUpDown.Increment = new decimal(new int[] {
+ 1,
+ 0,
+ 0,
+ 131072});
+ this.RadiusNumericUpDown.Location = new System.Drawing.Point(76, 31);
+ this.RadiusNumericUpDown.Name = "RadiusNumericUpDown";
+ this.RadiusNumericUpDown.Size = new System.Drawing.Size(84, 20);
+ this.RadiusNumericUpDown.TabIndex = 11;
+ this.RadiusNumericUpDown.Value = new decimal(new int[] {
+ 5,
+ 0,
+ 0,
+ 0});
+ this.RadiusNumericUpDown.ValueChanged += new System.EventHandler(this.RadiusNumericUpDown_ValueChanged);
+ //
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.Location = new System.Drawing.Point(21, 59);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(42, 13);
+ this.label5.TabIndex = 19;
+ this.label5.Text = "Density";
+ //
+ // radiusLabel
+ //
+ this.radiusLabel.AutoSize = true;
+ this.radiusLabel.Location = new System.Drawing.Point(21, 33);
+ this.radiusLabel.Name = "radiusLabel";
+ this.radiusLabel.Size = new System.Drawing.Size(40, 13);
+ this.radiusLabel.TabIndex = 10;
+ this.radiusLabel.Text = "Radius";
+ //
+ // OptimizeBatchButton
+ //
+ this.OptimizeBatchButton.Location = new System.Drawing.Point(16, 280);
+ this.OptimizeBatchButton.Name = "OptimizeBatchButton";
+ this.OptimizeBatchButton.Size = new System.Drawing.Size(108, 24);
+ this.OptimizeBatchButton.TabIndex = 70;
+ this.OptimizeBatchButton.Text = "Optimize Batch";
+ this.OptimizeBatchButtonTooltip.SetToolTip(this.OptimizeBatchButton, "Will split your batch into multiple different batches based on the threshold. If " +
+ "your threshold is 5.0 then each batch will have a size of 5.0 meters.");
+ this.OptimizeBatchButton.UseVisualStyleBackColor = true;
+ this.OptimizeBatchButton.Click += new System.EventHandler(this.OptimizeBatchButton_Click);
+ //
+ // ExtentsMinTextBox
+ //
+ this.ExtentsMinTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.ExtentsMinTextBox.Location = new System.Drawing.Point(84, 319);
+ this.ExtentsMinTextBox.Name = "ExtentsMinTextBox";
+ this.ExtentsMinTextBox.ReadOnly = true;
+ this.ExtentsMinTextBox.Size = new System.Drawing.Size(380, 20);
+ this.ExtentsMinTextBox.TabIndex = 55;
+ //
+ // label14
+ //
+ this.label14.AutoSize = true;
+ this.label14.Location = new System.Drawing.Point(13, 349);
+ this.label14.Name = "label14";
+ this.label14.Size = new System.Drawing.Size(68, 13);
+ this.label14.TabIndex = 54;
+ this.label14.Text = "Extents Max:";
+ //
+ // ExtentsMaxTextBox
+ //
+ this.ExtentsMaxTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.ExtentsMaxTextBox.Location = new System.Drawing.Point(84, 345);
+ this.ExtentsMaxTextBox.Name = "ExtentsMaxTextBox";
+ this.ExtentsMaxTextBox.ReadOnly = true;
+ this.ExtentsMaxTextBox.Size = new System.Drawing.Size(380, 20);
+ this.ExtentsMaxTextBox.TabIndex = 53;
+ //
+ // label13
+ //
+ this.label13.AutoSize = true;
+ this.label13.Location = new System.Drawing.Point(13, 322);
+ this.label13.Name = "label13";
+ this.label13.Size = new System.Drawing.Size(65, 13);
+ this.label13.TabIndex = 52;
+ this.label13.Text = "Extents Min:";
+ //
+ // GrassDeleteButton
+ //
+ this.GrassDeleteButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
+ this.GrassDeleteButton.Location = new System.Drawing.Point(257, 397);
+ this.GrassDeleteButton.Name = "GrassDeleteButton";
+ this.GrassDeleteButton.Size = new System.Drawing.Size(95, 23);
+ this.GrassDeleteButton.TabIndex = 51;
+ this.GrassDeleteButton.Text = "Delete Batch";
+ this.GrassDeleteButton.UseVisualStyleBackColor = true;
+ this.GrassDeleteButton.Click += new System.EventHandler(this.GrassDeleteButton_Click);
+ //
+ // GrassAddToProjectButton
+ //
+ this.GrassAddToProjectButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
+ this.GrassAddToProjectButton.Location = new System.Drawing.Point(159, 397);
+ this.GrassAddToProjectButton.Name = "GrassAddToProjectButton";
+ this.GrassAddToProjectButton.Size = new System.Drawing.Size(95, 23);
+ this.GrassAddToProjectButton.TabIndex = 50;
+ this.GrassAddToProjectButton.Text = "Add to Project";
+ this.GrassAddToProjectButton.UseVisualStyleBackColor = true;
+ this.GrassAddToProjectButton.Click += new System.EventHandler(this.GrassAddToProjectButton_Click);
+ //
+ // HashLabel
+ //
+ this.HashLabel.AutoSize = true;
+ this.HashLabel.Location = new System.Drawing.Point(254, 14);
+ this.HashLabel.Name = "HashLabel";
+ this.HashLabel.Size = new System.Drawing.Size(44, 13);
+ this.HashLabel.TabIndex = 61;
+ this.HashLabel.Text = "Hash: 0";
+ //
+ // ArchetypeNameTextBox
+ //
+ this.ArchetypeNameTextBox.Location = new System.Drawing.Point(66, 12);
+ this.ArchetypeNameTextBox.Name = "ArchetypeNameTextBox";
+ this.ArchetypeNameTextBox.Size = new System.Drawing.Size(144, 20);
+ this.ArchetypeNameTextBox.TabIndex = 60;
+ this.ArchetypeNameTextBox.TextChanged += new System.EventHandler(this.ArchetypeNameTextBox_TextChanged);
+ //
+ // label7
+ //
+ this.label7.AutoSize = true;
+ this.label7.Location = new System.Drawing.Point(16, 15);
+ this.label7.Name = "label7";
+ this.label7.Size = new System.Drawing.Size(35, 13);
+ this.label7.TabIndex = 59;
+ this.label7.Text = "Name";
+ //
+ // PositionTextBox
+ //
+ this.PositionTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.PositionTextBox.Location = new System.Drawing.Point(66, 39);
+ this.PositionTextBox.Name = "PositionTextBox";
+ this.PositionTextBox.ReadOnly = true;
+ this.PositionTextBox.Size = new System.Drawing.Size(275, 20);
+ this.PositionTextBox.TabIndex = 58;
+ //
// label1
//
this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(52, 52);
+ this.label1.Location = new System.Drawing.Point(16, 42);
this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(105, 13);
- this.label1.TabIndex = 0;
- this.label1.Text = "Grass editing TODO!";
+ this.label1.Size = new System.Drawing.Size(44, 13);
+ this.label1.TabIndex = 57;
+ this.label1.Text = "Position";
+ //
+ // GrassGoToButton
+ //
+ this.GrassGoToButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.GrassGoToButton.Location = new System.Drawing.Point(347, 36);
+ this.GrassGoToButton.Name = "GrassGoToButton";
+ this.GrassGoToButton.Size = new System.Drawing.Size(56, 23);
+ this.GrassGoToButton.TabIndex = 56;
+ this.GrassGoToButton.Text = "Go To";
+ this.GrassGoToButton.UseVisualStyleBackColor = true;
+ this.GrassGoToButton.Click += new System.EventHandler(this.GrassGoToButton_Click);
+ //
+ // label17
+ //
+ this.label17.AutoSize = true;
+ this.label17.Location = new System.Drawing.Point(130, 286);
+ this.label17.Name = "label17";
+ this.label17.Size = new System.Drawing.Size(54, 13);
+ this.label17.TabIndex = 71;
+ this.label17.Text = "Threshold";
+ //
+ // OptmizationThresholdNumericUpDown
+ //
+ this.OptmizationThresholdNumericUpDown.Location = new System.Drawing.Point(190, 284);
+ this.OptmizationThresholdNumericUpDown.Maximum = new decimal(new int[] {
+ 999999,
+ 0,
+ 0,
+ 0});
+ this.OptmizationThresholdNumericUpDown.Name = "OptmizationThresholdNumericUpDown";
+ this.OptmizationThresholdNumericUpDown.Size = new System.Drawing.Size(51, 20);
+ this.OptmizationThresholdNumericUpDown.TabIndex = 69;
+ this.OptmizationThresholdNumericUpDown.Value = new decimal(new int[] {
+ 5,
+ 0,
+ 0,
+ 0});
+ //
+ // BrushModeCheckBox
+ //
+ this.BrushModeCheckBox.AutoSize = true;
+ this.BrushModeCheckBox.Location = new System.Drawing.Point(16, 252);
+ this.BrushModeCheckBox.Name = "BrushModeCheckBox";
+ this.BrushModeCheckBox.Size = new System.Drawing.Size(83, 17);
+ this.BrushModeCheckBox.TabIndex = 68;
+ this.BrushModeCheckBox.Text = "Brush Mode";
+ this.BrushModeCheckBox.UseVisualStyleBackColor = true;
+ this.BrushModeCheckBox.CheckedChanged += new System.EventHandler(this.BrushModeCheckBox_CheckedChanged);
+ //
+ // label8
+ //
+ this.label8.AutoSize = true;
+ this.label8.Location = new System.Drawing.Point(211, 14);
+ this.label8.Name = "label8";
+ this.label8.Size = new System.Drawing.Size(24, 13);
+ this.label8.TabIndex = 72;
+ this.label8.Text = ".ydr";
+ this.label8.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// EditYmapGrassPanel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(554, 355);
+ this.ClientSize = new System.Drawing.Size(510, 443);
+ this.Controls.Add(this.label8);
+ this.Controls.Add(this.label17);
+ this.Controls.Add(this.OptmizationThresholdNumericUpDown);
+ this.Controls.Add(this.OptimizeBatchButton);
+ this.Controls.Add(this.BrushModeCheckBox);
+ this.Controls.Add(this.HashLabel);
+ this.Controls.Add(this.ExtentsMinTextBox);
+ this.Controls.Add(this.ArchetypeNameTextBox);
+ this.Controls.Add(this.ExtentsMaxTextBox);
+ this.Controls.Add(this.label7);
+ this.Controls.Add(this.PositionTextBox);
this.Controls.Add(this.label1);
+ this.Controls.Add(this.GrassGoToButton);
+ this.Controls.Add(this.label14);
+ this.Controls.Add(this.label13);
+ this.Controls.Add(this.GrassDeleteButton);
+ this.Controls.Add(this.tabControl1);
+ this.Controls.Add(this.GrassAddToProjectButton);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "EditYmapGrassPanel";
this.Text = "Grass Batch";
+ this.tabControl1.ResumeLayout(false);
+ this.GrassBatchTab.ResumeLayout(false);
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.OrientToTerrainNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodFadeRangeNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodFadeStartDistanceNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.LodDistNumericUpDown)).EndInit();
+ this.BrushTab.ResumeLayout(false);
+ this.groupBox2.ResumeLayout(false);
+ this.groupBox2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.AoNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.ScaleNumericUpDown)).EndInit();
+ this.brushSettingsGroupBox.ResumeLayout(false);
+ this.brushSettingsGroupBox.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.DensityNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.RadiusNumericUpDown)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.OptmizationThresholdNumericUpDown)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
@@ -57,6 +671,52 @@
#endregion
+ private System.Windows.Forms.TabControl tabControl1;
+ private System.Windows.Forms.TabPage GrassBatchTab;
+ private System.Windows.Forms.TabPage BrushTab;
+ private System.Windows.Forms.GroupBox brushSettingsGroupBox;
+ private System.Windows.Forms.NumericUpDown RadiusNumericUpDown;
+ private System.Windows.Forms.Label radiusLabel;
+ private System.Windows.Forms.NumericUpDown DensityNumericUpDown;
+ private System.Windows.Forms.Label label5;
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.NumericUpDown LodDistNumericUpDown;
+ private System.Windows.Forms.Label label6;
+ private System.Windows.Forms.Label label9;
+ private System.Windows.Forms.NumericUpDown LodFadeStartDistanceNumericUpDown;
+ private System.Windows.Forms.Label label10;
+ private System.Windows.Forms.NumericUpDown LodFadeRangeNumericUpDown;
+ private System.Windows.Forms.Label label11;
+ private System.Windows.Forms.NumericUpDown OrientToTerrainNumericUpDown;
+ private System.Windows.Forms.Label label12;
+ private System.Windows.Forms.TextBox ScaleRangeTextBox;
+ private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.Label label15;
+ private System.Windows.Forms.TextBox PadTextBox;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.Label GrassColorLabel;
+ private System.Windows.Forms.CheckBox RandomizeScaleCheckBox;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.NumericUpDown ScaleNumericUpDown;
+ private System.Windows.Forms.NumericUpDown AoNumericUpDown;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.ToolTip OptimizeBatchButtonTooltip;
+ private System.Windows.Forms.TextBox ExtentsMinTextBox;
+ private System.Windows.Forms.Label label14;
+ private System.Windows.Forms.TextBox ExtentsMaxTextBox;
+ private System.Windows.Forms.Label label13;
+ private System.Windows.Forms.Button GrassDeleteButton;
+ private System.Windows.Forms.Button GrassAddToProjectButton;
+ private System.Windows.Forms.Label HashLabel;
+ private System.Windows.Forms.TextBox ArchetypeNameTextBox;
+ private System.Windows.Forms.Label label7;
+ private System.Windows.Forms.TextBox PositionTextBox;
private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Button GrassGoToButton;
+ private System.Windows.Forms.Label label17;
+ private System.Windows.Forms.NumericUpDown OptmizationThresholdNumericUpDown;
+ private System.Windows.Forms.Button OptimizeBatchButton;
+ private System.Windows.Forms.CheckBox BrushModeCheckBox;
+ private System.Windows.Forms.Label label8;
}
}
\ No newline at end of file
diff --git a/Project/Panels/EditYmapGrassPanel.cs b/Project/Panels/EditYmapGrassPanel.cs
index 702ecb1..dedd9d1 100644
--- a/Project/Panels/EditYmapGrassPanel.cs
+++ b/Project/Panels/EditYmapGrassPanel.cs
@@ -1,22 +1,30 @@
-using CodeWalker.GameFiles;
-using System;
+using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Forms;
+using CodeWalker.GameFiles;
+using CodeWalker.World;
+using SharpDX;
+
+// TODO
+// - COMPLETED -- Optimization feature.
+// - COMPLETED -- Remove grass instances using CTRL + SHIFT + LMB
+
+// - Better gizmo for grass brush (like a circle with a little line in the middle sticking upwards)
+// - Maybe some kind of auto coloring system? I've noticed that mostly all grass in GTA inherits it's color from the surface it's on.
+// - Grass area fill (generate grass on ydr based on colision materials?)
+// - Need to have a way to erase instances from other batches in the current batches ymap.
+// if we optimize our instances, we'd have to go through each batch to erase, this is very monotonous.
+
+// BUG
+// - I've added a "zoom" kind of feature when hitting the goto button, but when the bounds of the
+// grass batch are 0, the zoom of the camera is set to 0, which causes the end-user to have to scroll
+// out a lot in order to use any movement controls. I will need to clamp that to a minimum value.
namespace CodeWalker.Project.Panels
{
public partial class EditYmapGrassPanel : ProjectPanel
{
public ProjectForm ProjectForm;
- public YmapGrassInstanceBatch CurrentBatch { get; set; }
-
- //private bool populatingui = false;
public EditYmapGrassPanel(ProjectForm owner)
{
@@ -24,12 +32,44 @@ namespace CodeWalker.Project.Panels
InitializeComponent();
}
+ public YmapGrassInstanceBatch CurrentBatch { get; set; }
+
+ #region Form
+
public void SetBatch(YmapGrassInstanceBatch batch)
{
CurrentBatch = batch;
Tag = batch;
- LoadGrassBatch();
UpdateFormTitle();
+ UpdateControls();
+ }
+
+ private void UpdateControls()
+ {
+ if (ProjectForm?.CurrentProjectFile == null) return;
+ if (ProjectForm.GrassBatchExistsInProject(CurrentBatch))
+ {
+ GrassAddToProjectButton.Enabled = false;
+ GrassDeleteButton.Enabled = true;
+ }
+ else
+ {
+ GrassAddToProjectButton.Enabled = true;
+ GrassDeleteButton.Enabled = false;
+ }
+
+ ArchetypeNameTextBox.Text = CurrentBatch.Batch.archetypeName.ToString();
+ PositionTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.Position);
+ LodDistNumericUpDown.Value = CurrentBatch.Batch.lodDist;
+ LodFadeRangeNumericUpDown.Value = (decimal) CurrentBatch.Batch.LodInstFadeRange;
+ LodFadeStartDistanceNumericUpDown.Value = (decimal) CurrentBatch.Batch.LodFadeStartDist;
+ ScaleRangeTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.Batch.ScaleRange);
+ OrientToTerrainNumericUpDown.Value = (decimal)CurrentBatch.Batch.OrientToTerrain;
+ OptmizationThresholdNumericUpDown.Value = 15;
+ BrushModeCheckBox.Checked = CurrentBatch.BrushEnabled;
+ RadiusNumericUpDown.Value = (decimal)CurrentBatch.BrushRadius;
+ ExtentsMinTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.AABBMin);
+ ExtentsMaxTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.AABBMax);
}
private void UpdateFormTitle()
@@ -37,11 +77,225 @@ namespace CodeWalker.Project.Panels
Text = CurrentBatch?.Batch.archetypeName.ToString() ?? "Grass Batch";
}
+ #endregion
+ #region Events
- private void LoadGrassBatch()
+ #region BrushSettings
+
+ private void GrassGoToButton_Click(object sender, EventArgs e)
{
+ if (CurrentBatch == null) return;
+ ProjectForm.WorldForm?.GoToPosition(CurrentBatch.Position, CurrentBatch.AABBMax - CurrentBatch.AABBMin);
}
+ private void GrassAddToProjectButton_Click(object sender, EventArgs e)
+ {
+ ProjectForm.SetProjectItem(CurrentBatch);
+ ProjectForm.AddGrassBatchToProject();
+ }
+
+ private void GrassDeleteButton_Click(object sender, EventArgs e)
+ {
+ ProjectForm.SetProjectItem(CurrentBatch);
+ var ymap = CurrentBatch?.Ymap;
+ if (!ProjectForm.DeleteGrassBatch()) return;
+
+ ymap?.CalcExtents(); // Recalculate the extents after deleting the grass batch.
+ ProjectForm.WorldForm.SelectItem();
+ }
+
+ private void GrassColorLabel_Click(object sender, EventArgs e)
+ {
+ var colDiag = new ColorDialog {Color = GrassColorLabel.BackColor};
+ if (colDiag.ShowDialog(this) == DialogResult.OK)
+ GrassColorLabel.BackColor = colDiag.Color;
+ }
+
+ private void BrushModeCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ if (CurrentBatch == null) return;
+ CurrentBatch.BrushEnabled = BrushModeCheckBox.Checked;
+ }
+
+ private void RadiusNumericUpDown_ValueChanged(object sender, EventArgs e)
+ {
+ if (CurrentBatch == null) return;
+ CurrentBatch.BrushRadius = (float)RadiusNumericUpDown.Value;
+ }
+ #endregion
+
+ #region Batch Settings
+
+ private void ArchetypeNameTextBox_TextChanged(object sender, EventArgs e)
+ {
+ var archetypeHash = JenkHash.GenHash(ArchetypeNameTextBox.Text);
+ var archetype = ProjectForm.GameFileCache.GetArchetype(archetypeHash);
+ if (archetype == null)
+ {
+ HashLabel.Text = $@"Hash: {archetypeHash} (invalid)";
+ return;
+ }
+ CurrentBatch.Archetype = archetype;
+ var b = CurrentBatch.Batch;
+ b.archetypeName = archetypeHash;
+ CurrentBatch.Batch = b;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ HashLabel.Text = $@"Hash: {archetypeHash}";
+ UpdateFormTitle();
+ CurrentBatch.HasChanged = true;
+ ProjectForm.SetGrassBatchHasChanged(false);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void LodDistNumericUpDown_ValueChanged(object sender, EventArgs e)
+ {
+ var batch = CurrentBatch.Batch;
+ batch.lodDist = (uint) LodDistNumericUpDown.Value;
+ CurrentBatch.Batch = batch;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void LodFadeStartDistanceNumericUpDown_ValueChanged(object sender, EventArgs e)
+ {
+ var batch = CurrentBatch.Batch;
+ batch.LodFadeStartDist = (float) LodFadeStartDistanceNumericUpDown.Value;
+ CurrentBatch.Batch = batch;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void LodFadeRangeNumericUpDown_ValueChanged(object sender, EventArgs e)
+ {
+ var batch = CurrentBatch.Batch;
+ batch.LodInstFadeRange = (float) LodFadeRangeNumericUpDown.Value;
+ CurrentBatch.Batch = batch;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void OrientToTerrainNumericUpDown_ValueChanged(object sender, EventArgs e)
+ {
+ var batch = CurrentBatch.Batch;
+ batch.OrientToTerrain = (float) OrientToTerrainNumericUpDown.Value;
+ CurrentBatch.Batch = batch;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void ScaleRangeTextBox_TextChanged(object sender, EventArgs e)
+ {
+ var batch = CurrentBatch.Batch;
+ var v = FloatUtil.ParseVector3String(ScaleRangeTextBox.Text);
+ batch.ScaleRange = v;
+ CurrentBatch.Batch = batch;
+ ProjectForm.WorldForm.UpdateGrassBatchGraphics(CurrentBatch);
+ ProjectForm.SetYmapHasChanged(true);
+ }
+
+ private void OptimizeBatchButton_Click(object sender, EventArgs e)
+ {
+ if (CurrentBatch.Instances == null || CurrentBatch.Instances.Length <= 0) return;
+ lock (ProjectForm.WorldForm.RenderSyncRoot)
+ {
+ var newBatches = CurrentBatch?.OptimizeInstances(CurrentBatch, (float)OptmizationThresholdNumericUpDown.Value);
+ if (newBatches == null || newBatches.Length <= 0) return;
+
+ // Remove our batch from the ymap
+ CurrentBatch.Ymap.RemoveGrassBatch(CurrentBatch);
+ foreach (var batch in newBatches)
+ {
+ var b = batch.Batch;
+ b.lodDist = CurrentBatch.Batch.lodDist;
+ b.LodInstFadeRange = CurrentBatch.Batch.LodInstFadeRange;
+ b.LodFadeStartDist = CurrentBatch.Batch.LodFadeStartDist;
+ b.ScaleRange = CurrentBatch.Batch.ScaleRange;
+ b.OrientToTerrain = CurrentBatch.Batch.OrientToTerrain;
+ b.archetypeName = CurrentBatch.Batch.archetypeName;
+ batch.Batch = b;
+ batch.Archetype = CurrentBatch.Archetype;
+ batch.UpdateInstanceCount();
+ ProjectForm.NewGrassBatch(batch);
+ }
+ CurrentBatch.Ymap.CalcExtents();
+ CurrentBatch.Ymap.Save();
+
+ // TODO: Select the last grass batch in the new list on the project explorer.
+ ProjectForm.ProjectExplorer.TrySelectGrassBatchTreeNode(CurrentBatch.Ymap.GrassInstanceBatches[0]);
+ }
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Publics
+
+ public void CreateInstancesAtMouse(SpaceRayIntersectResult mouseRay)
+ {
+ var wf = ProjectForm.WorldForm;
+ if (wf == null) return;
+
+ lock (wf.RenderSyncRoot)
+ {
+ CurrentBatch.CreateInstancesAtMouse(
+ CurrentBatch,
+ mouseRay,
+ (float) RadiusNumericUpDown.Value,
+ (int) DensityNumericUpDown.Value,
+ SpawnRayFunc,
+ new Color(GrassColorLabel.BackColor.R, GrassColorLabel.BackColor.G, GrassColorLabel.BackColor.B),
+ (int) AoNumericUpDown.Value,
+ (int) ScaleNumericUpDown.Value,
+ FloatUtil.ParseVector3String(PadTextBox.Text),
+ RandomizeScaleCheckBox.Checked
+ );
+ wf.UpdateGrassBatchGraphics(CurrentBatch);
+ }
+
+ BatchChanged();
+ }
+
+ public void EraseInstancesAtMouse(SpaceRayIntersectResult mouseRay)
+ {
+ var wf = ProjectForm.WorldForm;
+ if (wf == null) return;
+ var changed = false;
+ lock (wf.RenderSyncRoot)
+ {
+ if (CurrentBatch.EraseInstancesAtMouse(
+ CurrentBatch,
+ mouseRay,
+ (float) RadiusNumericUpDown.Value
+ ))
+ {
+ wf.UpdateGrassBatchGraphics(CurrentBatch);
+ changed = true;
+ }
+ }
+
+ if (changed) BatchChanged();
+ }
+
+ #endregion
+
+ #region Privates
+
+ private SpaceRayIntersectResult SpawnRayFunc(Vector3 spawnPos)
+ {
+ var res = ProjectForm.WorldForm.Raycast(new Ray(spawnPos, -Vector3.UnitZ));
+ return res;
+ }
+
+ private void BatchChanged()
+ {
+ UpdateControls();
+ CurrentBatch.UpdateInstanceCount();
+ CurrentBatch.HasChanged = true;
+ ProjectForm.SetGrassBatchHasChanged(false);
+ }
+
+ #endregion
}
-}
+}
\ No newline at end of file
diff --git a/Project/Panels/EditYmapGrassPanel.resx b/Project/Panels/EditYmapGrassPanel.resx
index 1431f6b..6a9df94 100644
--- a/Project/Panels/EditYmapGrassPanel.resx
+++ b/Project/Panels/EditYmapGrassPanel.resx
@@ -117,6 +117,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ 17, 17
+
diff --git a/Project/Panels/ProjectExplorerPanel.cs b/Project/Panels/ProjectExplorerPanel.cs
index f83fc13..027b2e4 100644
--- a/Project/Panels/ProjectExplorerPanel.cs
+++ b/Project/Panels/ProjectExplorerPanel.cs
@@ -641,6 +641,20 @@ namespace CodeWalker.Project.Panels
}
}
}
+ public void SetGrassBatchHasChanged(YmapGrassInstanceBatch batch, bool changed)
+ {
+ if (ProjectTreeView.Nodes.Count > 0)
+ {
+ var gbnode = FindGrassTreeNode(batch);
+ if (gbnode == null) return;
+ string changestr = changed ? "*" : "";
+ if (gbnode.Tag == batch)
+ {
+ string name = batch.ToString();
+ gbnode.Text = changestr + name;
+ }
+ }
+ }
@@ -698,6 +712,21 @@ namespace CodeWalker.Project.Panels
}
return null;
}
+ public TreeNode FindGrassTreeNode(YmapGrassInstanceBatch batch)
+ {
+ if (batch == null) return null;
+ TreeNode ymapnode = FindYmapTreeNode(batch.Ymap);
+ if (ymapnode == null) return null;
+ var batchnode = GetChildTreeNode(ymapnode, "GrassBatches");
+ if (batchnode == null) return null;
+ for (int i = 0; i < batchnode.Nodes.Count; i++)
+ {
+ TreeNode grassnode = batchnode.Nodes[i];
+ if (grassnode.Tag == batch) return grassnode;
+ }
+ return null;
+ }
+
public TreeNode FindYndTreeNode(YndFile ynd)
{
if (ProjectTreeView.Nodes.Count <= 0) return null;
@@ -930,6 +959,21 @@ namespace CodeWalker.Project.Panels
}
}
}
+ public void TrySelectGrassBatchTreeNode(YmapGrassInstanceBatch grassBatch)
+ {
+ TreeNode grassNode = FindGrassTreeNode(grassBatch);
+ if (grassNode != null)
+ {
+ if (ProjectTreeView.SelectedNode == grassNode)
+ {
+ OnItemSelected?.Invoke(grassNode);
+ }
+ else
+ {
+ ProjectTreeView.SelectedNode = grassNode;
+ }
+ }
+ }
public void TrySelectPathNodeTreeNode(YndNode node)
{
TreeNode tnode = FindPathNodeTreeNode(node);
@@ -1240,6 +1284,16 @@ namespace CodeWalker.Project.Panels
tn.Parent.Nodes.Remove(tn);
}
}
+
+ public void RemoveGrassBatchTreeNode(YmapGrassInstanceBatch batch)
+ {
+ var tn = FindGrassTreeNode(batch);
+ if ((tn != null) && (tn.Parent != null))
+ {
+ tn.Parent.Text = "Grass Batches (" + batch.Ymap.GrassInstanceBatches.Length.ToString() + ")";
+ tn.Parent.Nodes.Remove(tn);
+ }
+ }
public void RemovePathNodeTreeNode(YndNode node)
{
var tn = FindPathNodeTreeNode(node);
diff --git a/Project/ProjectForm.Designer.cs b/Project/ProjectForm.Designer.cs
index 2c6e06b..9d89120 100644
--- a/Project/ProjectForm.Designer.cs
+++ b/Project/ProjectForm.Designer.cs
@@ -78,6 +78,7 @@
this.toolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator();
this.YmapNewEntityMenu = new System.Windows.Forms.ToolStripMenuItem();
this.YmapNewCarGenMenu = new System.Windows.Forms.ToolStripMenuItem();
+ this.YmapNewGrassBatchMenu = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator();
this.YmapAddToProjectMenu = new System.Windows.Forms.ToolStripMenuItem();
this.YmapRemoveFromProjectMenu = new System.Windows.Forms.ToolStripMenuItem();
@@ -522,6 +523,7 @@
this.toolStripSeparator11,
this.YmapNewEntityMenu,
this.YmapNewCarGenMenu,
+ this.YmapNewGrassBatchMenu,
this.toolStripSeparator12,
this.YmapAddToProjectMenu,
this.YmapRemoveFromProjectMenu});
@@ -558,6 +560,14 @@
this.YmapNewCarGenMenu.Text = "New Car Generator";
this.YmapNewCarGenMenu.Click += new System.EventHandler(this.YmapNewCarGenMenu_Click);
//
+ // YmapNewGrassBatchMenu
+ //
+ this.YmapNewGrassBatchMenu.Enabled = false;
+ this.YmapNewGrassBatchMenu.Name = "YmapNewGrassBatchMenu";
+ this.YmapNewGrassBatchMenu.Size = new System.Drawing.Size(199, 22);
+ this.YmapNewGrassBatchMenu.Text = "New Grass Batch";
+ this.YmapNewGrassBatchMenu.Click += new System.EventHandler(this.YmapNewGrassBatchMenu_Click);
+ //
// toolStripSeparator12
//
this.toolStripSeparator12.Name = "toolStripSeparator12";
@@ -1324,6 +1334,7 @@
private System.Windows.Forms.ToolStripSeparator toolStripSeparator22;
private System.Windows.Forms.ToolStripMenuItem ScenarioAddToProjectMenu;
private System.Windows.Forms.ToolStripMenuItem ScenarioRemoveFromProjectMenu;
+ private System.Windows.Forms.ToolStripMenuItem YmapNewGrassBatchMenu;
private System.Windows.Forms.ToolStripMenuItem ToolsNavMeshGeneratorMenu;
}
}
\ No newline at end of file
diff --git a/Project/ProjectForm.cs b/Project/ProjectForm.cs
index 47014eb..f805b64 100644
--- a/Project/ProjectForm.cs
+++ b/Project/ProjectForm.cs
@@ -104,7 +104,6 @@ namespace CodeWalker.Project
RpfMan = GameFileCache.RpfMan;
})).Start();
}
-
}
private void UpdateStatus(string text)
@@ -712,6 +711,25 @@ namespace CodeWalker.Project
//######## Public methods
+ // Possibly future proofing for procedural prop instances
+ public bool CanPaintInstances()
+ {
+ if (CurrentGrassBatch != null)
+ {
+ if (CurrentGrassBatch.BrushEnabled)
+ return true;
+ }
+
+ return false;
+ }
+ public float GetInstanceBrushRadius()
+ {
+ if (CurrentGrassBatch != null)
+ return CurrentGrassBatch.BrushRadius;
+
+ return 0f;
+ }
+
public void NewProject()
{
if (CurrentProjectFile != null)
@@ -1277,6 +1295,10 @@ namespace CodeWalker.Project
{
ProjectExplorer?.TrySelectCarGenTreeNode(CurrentCarGen);
}
+ else if (CurrentGrassBatch != null)
+ {
+ ProjectExplorer?.TrySelectGrassBatchTreeNode(CurrentGrassBatch);
+ }
}
public void RemoveYmapFromProject()
{
@@ -1442,6 +1464,155 @@ namespace CodeWalker.Project
return CurrentEntity == ent;
}
+ public void NewGrassBatch(YmapGrassInstanceBatch copy = null)
+ {
+ if (CurrentYmapFile == null) return;
+
+ rage__fwGrassInstanceListDef fwBatch = new rage__fwGrassInstanceListDef();
+ rage__fwGrassInstanceListDef__InstanceData[] instances = new rage__fwGrassInstanceListDef__InstanceData[0];
+
+ if (copy != null)
+ {
+ fwBatch = copy.Batch;
+ instances = copy.Instances;
+ }
+ else
+ {
+ fwBatch.archetypeName = new MetaHash(JenkHash.GenHash("proc_grasses01"));
+ fwBatch.lodDist = 120;
+ fwBatch.LodFadeStartDist = 15;
+ fwBatch.LodInstFadeRange = 0.75f;
+ fwBatch.OrientToTerrain = 1.0f;
+ fwBatch.ScaleRange = new Vector3(0.3f, 0.2f, 0.7f);
+ }
+
+ YmapGrassInstanceBatch batch = new YmapGrassInstanceBatch
+ {
+ AABBMin = fwBatch.BatchAABB.min.XYZ(),
+ AABBMax = fwBatch.BatchAABB.max.XYZ(),
+ Archetype = GameFileCache.GetArchetype(fwBatch.archetypeName),
+ Batch = fwBatch,
+ Instances = instances
+ };
+
+ batch.Position = (batch.AABBMin + batch.AABBMax) * 0.5f;
+ batch.Radius = (batch.AABBMax - batch.AABBMin).Length() * 0.5f;
+ batch.Ymap = CurrentYmapFile;
+
+ if (WorldForm != null)
+ {
+ lock (WorldForm.RenderSyncRoot) //don't try to do this while rendering...
+ {
+ CurrentYmapFile.AddGrassBatch(batch);
+ }
+ }
+ else
+ {
+ CurrentYmapFile.AddGrassBatch(batch);
+ }
+
+ LoadProjectTree();
+
+ ProjectExplorer?.TrySelectGrassBatchTreeNode(batch);
+ CurrentGrassBatch = batch;
+ ShowEditYmapGrassBatchPanel(false);
+ }
+ public void AddGrassBatchToProject()
+ {
+ if (CurrentGrassBatch == null) return;
+
+ CurrentYmapFile = CurrentGrassBatch.Ymap;
+ if (!YmapExistsInProject(CurrentYmapFile))
+ {
+ var grassBatch = CurrentGrassBatch;
+ CurrentYmapFile.HasChanged = true;
+ AddYmapToProject(CurrentYmapFile);
+
+ CurrentGrassBatch = grassBatch; //bug fix for some reason the treeview selects the project node here.
+ CurrentYmapFile = grassBatch.Ymap;
+ ProjectExplorer?.TrySelectGrassBatchTreeNode(grassBatch);
+ }
+ }
+ public bool DeleteGrassBatch()
+ {
+ if (CurrentYmapFile == null) return false;
+ if (CurrentGrassBatch == null) return false;
+ if (CurrentGrassBatch.Ymap != CurrentYmapFile) return false;
+ if (CurrentYmapFile.GrassInstanceBatches == null) return false; //nothing to delete..
+
+ if (MessageBox.Show("Are you sure you want to delete this grass batch?\n" + CurrentGrassBatch.ToString() + "\n\nThis operation cannot be undone. Continue?", "Confirm delete", MessageBoxButtons.YesNo) != DialogResult.Yes)
+ {
+ return true;
+ }
+
+ bool res = false;
+ if (WorldForm != null)
+ {
+ lock (WorldForm.RenderSyncRoot) //don't try to do this while rendering...
+ {
+ res = CurrentYmapFile.RemoveGrassBatch(CurrentGrassBatch);
+ //WorldForm.SelectItem(null, null, null);
+ }
+ }
+ else
+ {
+ res = CurrentYmapFile.RemoveGrassBatch(CurrentGrassBatch);
+ }
+ if (!res)
+ {
+ MessageBox.Show("Unable to delete the grass batch. This shouldn't happen!");
+ }
+
+ var delbatch = CurrentGrassBatch;
+
+ ProjectExplorer?.RemoveGrassBatchTreeNode(CurrentGrassBatch);
+ ProjectExplorer?.SetYmapHasChanged(CurrentYmapFile, true);
+
+ ClosePanel((EditYmapGrassPanel p) => { return p.Tag == delbatch; });
+
+ CurrentGrassBatch = null;
+
+ return true;
+ }
+ public void PaintGrass(SpaceRayIntersectResult mouseRay, bool erase)
+ {
+ try
+ {
+ if (InvokeRequired)
+ {
+ Invoke(new Action(() => { PaintGrass(mouseRay, erase); }));
+ return;
+ }
+
+ if (!mouseRay.Hit || !mouseRay.TestComplete) return;
+ if (CurrentGrassBatch == null || (!CurrentGrassBatch.BrushEnabled)) return; // brush isn't enabled right now
+ EditYmapGrassPanel panel = FindPanel(x => x.CurrentBatch == CurrentGrassBatch);
+ if (panel == null) return; // no panels with this batch
+
+ // TODO: Maybe move these functions into the batch instead of the grass panel?
+ // although, the panel does have the brush settings.
+ if (!erase)
+ panel.CreateInstancesAtMouse(mouseRay);
+ else panel.EraseInstancesAtMouse(mouseRay);
+ }
+ catch { }
+ }
+ public bool GrassBatchExistsInProject(YmapGrassInstanceBatch batch)
+ {
+ if (CurrentProjectFile?.YmapFiles == null) return false;
+ if (CurrentProjectFile.YmapFiles.Count <= 0) return false;
+ foreach (var ymapFile in CurrentProjectFile.YmapFiles)
+ {
+ if (ymapFile.GrassInstanceBatches == null) continue;
+ foreach (var b in ymapFile.GrassInstanceBatches)
+ {
+ if (batch == b)
+ return true;
+ }
+ }
+ return false;
+ }
+
public void NewCarGen(YmapCarGen copy = null, bool copyPosition = false)
{
if (CurrentYmapFile == null) return;
@@ -3827,9 +3998,11 @@ namespace CodeWalker.Project
for (int i = 0; i < CurrentProjectFile.YmapFiles.Count; i++)
{
var ymap = CurrentProjectFile.YmapFiles[i];
+ // make sure we're not hiding ymaps that have been added by the end-user.
+ var isnew = ymap.RpfFileEntry.ShortNameHash == 0;
if (ymap.Loaded)
{
- ymaps[ymap._CMapData.name] = ymap;
+ ymaps[isnew ? JenkHash.GenHash(ymap.Name) : ymap.RpfFileEntry.ShortNameHash] = ymap;
}
}
}
@@ -4033,6 +4206,11 @@ namespace CodeWalker.Project
{
ProjectExplorer?.TrySelectCarGenTreeNode(cargen);
}
+ if (grassbatch != CurrentGrassBatch)
+ {
+ ProjectExplorer?.TrySelectGrassBatchTreeNode(grassbatch);
+ }
+
}
else if (YndExistsInProject(ynd))
{
@@ -4706,7 +4884,19 @@ namespace CodeWalker.Project
PromoteIfPreviewPanelActive();
}
+ public void SetGrassBatchHasChanged(bool changed)
+ {
+ if (CurrentGrassBatch == null) return;
+ bool changechange = changed != CurrentGrassBatch.HasChanged;
+ if (!changechange) return;
+
+ CurrentGrassBatch.HasChanged = true;
+
+ ProjectExplorer?.SetGrassBatchHasChanged(CurrentGrassBatch, changed);
+
+ PromoteIfPreviewPanelActive();
+ }
@@ -4884,6 +5074,7 @@ namespace CodeWalker.Project
YmapNewEntityMenu.Enabled = enable && inproj;
YmapNewCarGenMenu.Enabled = enable && inproj;
+ YmapNewGrassBatchMenu.Enabled = enable && inproj;
if (CurrentYmapFile != null)
{
@@ -5110,7 +5301,7 @@ namespace CodeWalker.Project
FileSaveItemAsMenu.Text = "Save As...";
ToolbarSaveButton.Text = "Save";
}
-
+
FileSaveItemMenu.Tag = filename;
FileSaveItemAsMenu.Tag = filename;
@@ -5319,6 +5510,10 @@ namespace CodeWalker.Project
{
NewCarGen();
}
+ private void YmapNewGrassBatchMenu_Click(object sender, EventArgs e)
+ {
+ NewGrassBatch();
+ }
private void YmapAddToProjectMenu_Click(object sender, EventArgs e)
{
AddYmapToProject(CurrentYmapFile);
diff --git a/Rendering/RenderableCache.cs b/Rendering/RenderableCache.cs
index 6add1c4..301e971 100644
--- a/Rendering/RenderableCache.cs
+++ b/Rendering/RenderableCache.cs
@@ -195,6 +195,14 @@ namespace CodeWalker.Rendering
}
}
+ public void Invalidate(YmapGrassInstanceBatch batch)
+ {
+ lock (updateSyncRoot)
+ {
+ instbatches.Invalidate(batch);
+ }
+ }
+
}
diff --git a/Rendering/Renderer.cs b/Rendering/Renderer.cs
index aa74391..e5a1d7e 100644
--- a/Rendering/Renderer.cs
+++ b/Rendering/Renderer.cs
@@ -98,7 +98,7 @@ namespace CodeWalker.Rendering
public bool renderinteriors = true;
public bool renderproxies = false;
public bool renderchildents = false;//when rendering single ymap, render root only or not...
-
+ public bool renderentities = true;
public bool rendergrass = true;
public bool renderdistlodlights = true;
@@ -380,6 +380,11 @@ namespace CodeWalker.Rendering
renderableCache.Invalidate(path);
}
+ public void Invalidate(YmapGrassInstanceBatch batch)
+ {
+ renderableCache.Invalidate(batch);
+ }
+
public void UpdateSelectionDrawFlags(DrawableModel model, DrawableGeometry geom, bool rem)
{
@@ -679,6 +684,33 @@ namespace CodeWalker.Rendering
}
+ public void RenderBrushRadiusOutline(Vector3 position, Vector3 dir, Vector3 up, float radius, uint col)
+ {
+ const int Reso = 36;
+ const float MaxDeg = 360f;
+ const float DegToRad = 0.0174533f;
+ const float Ang = MaxDeg / Reso;
+
+ var axis = Vector3.Cross(dir, up);
+ var c = new VertexTypePC[Reso];
+
+ for (var i = 0; i < Reso; i++)
+ {
+ var rDir = Quaternion.RotationAxis(dir, (i * Ang) * DegToRad).Multiply(axis);
+ c[i].Position = position + (rDir * radius);
+ c[i].Colour = col;
+ }
+
+ for (var i = 0; i < c.Length; i++)
+ {
+ SelectionLineVerts.Add(c[i]);
+ SelectionLineVerts.Add(c[(i + 1) % c.Length]);
+ }
+
+ SelectionLineVerts.Add(new VertexTypePC{Colour = col, Position = position});
+ SelectionLineVerts.Add(new VertexTypePC { Colour = col, Position = position + dir * 2f});
+ }
+
public void RenderSelectionArrowOutline(Vector3 pos, Vector3 dir, Vector3 up, Quaternion ori, float len, float rad, uint colour)
{
Vector3 ax = Vector3.Cross(dir, up);
@@ -1563,13 +1595,16 @@ namespace CodeWalker.Rendering
}
}
- for (int i = 0; i < renderworldrenderables.Count; i++)
+ if(renderentities)
{
- var rent = renderworldrenderables[i];
- var ent = rent.Entity;
- var arch = ent.Archetype;
+ for (int i = 0; i < renderworldrenderables.Count; i++)
+ {
+ var rent = renderworldrenderables[i];
+ var ent = rent.Entity;
+ var arch = ent.Archetype;
- RenderArchetype(arch, ent, rent.Renderable, false);
+ RenderArchetype(arch, ent, rent.Renderable, false);
+ }
}
diff --git a/Rendering/Shaders/BasicShader.cs b/Rendering/Shaders/BasicShader.cs
index ae1ea37..e7b85e8 100644
--- a/Rendering/Shaders/BasicShader.cs
+++ b/Rendering/Shaders/BasicShader.cs
@@ -695,6 +695,10 @@ namespace CodeWalker.Rendering
{
var gb = batch.Key;
+ // sanity check
+ if (batch.GrassInstanceBuffer == null)
+ return;
+
VSEntityVars.Vars.CamRel = new Vector4(gb.CamRel, 0.0f);
VSEntityVars.Vars.Orientation = Quaternion.Identity;
VSEntityVars.Vars.Scale = Vector3.One;
diff --git a/WorldForm.Designer.cs b/WorldForm.Designer.cs
index f50fd73..11532c9 100644
--- a/WorldForm.Designer.cs
+++ b/WorldForm.Designer.cs
@@ -109,6 +109,7 @@ namespace CodeWalker
this.tabPage4 = new System.Windows.Forms.TabPage();
this.OptionsTabControl = new System.Windows.Forms.TabControl();
this.tabPage8 = new System.Windows.Forms.TabPage();
+ this.RenderEntitiesCheckBox = new System.Windows.Forms.CheckBox();
this.AdvancedSettingsButton = new System.Windows.Forms.Button();
this.ControlSettingsButton = new System.Windows.Forms.Button();
this.MapViewDetailLabel = new System.Windows.Forms.Label();
@@ -1295,6 +1296,7 @@ namespace CodeWalker
//
// tabPage8
//
+ this.tabPage8.Controls.Add(this.RenderEntitiesCheckBox);
this.tabPage8.Controls.Add(this.AdvancedSettingsButton);
this.tabPage8.Controls.Add(this.ControlSettingsButton);
this.tabPage8.Controls.Add(this.MapViewDetailLabel);
@@ -1327,6 +1329,19 @@ namespace CodeWalker
this.tabPage8.Text = "General";
this.tabPage8.UseVisualStyleBackColor = true;
//
+ // EntitiesCheckBox
+ //
+ this.RenderEntitiesCheckBox.AutoSize = true;
+ this.RenderEntitiesCheckBox.Checked = true;
+ this.RenderEntitiesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.RenderEntitiesCheckBox.Location = new System.Drawing.Point(10, 32);
+ this.RenderEntitiesCheckBox.Name = "EntitiesCheckBox";
+ this.RenderEntitiesCheckBox.Size = new System.Drawing.Size(89, 17);
+ this.RenderEntitiesCheckBox.TabIndex = 67;
+ this.RenderEntitiesCheckBox.Text = "Show entities";
+ this.RenderEntitiesCheckBox.UseVisualStyleBackColor = true;
+ this.RenderEntitiesCheckBox.CheckedChanged += new System.EventHandler(this.RenderEntitiesCheckBox_CheckedChanged);
+ //
// AdvancedSettingsButton
//
this.AdvancedSettingsButton.Location = new System.Drawing.Point(101, 456);
@@ -1411,7 +1426,7 @@ namespace CodeWalker
this.WaterQuadsCheckBox.AutoSize = true;
this.WaterQuadsCheckBox.Checked = true;
this.WaterQuadsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
- this.WaterQuadsCheckBox.Location = new System.Drawing.Point(10, 104);
+ this.WaterQuadsCheckBox.Location = new System.Drawing.Point(10, 129);
this.WaterQuadsCheckBox.Name = "WaterQuadsCheckBox";
this.WaterQuadsCheckBox.Size = new System.Drawing.Size(114, 17);
this.WaterQuadsCheckBox.TabIndex = 39;
@@ -1440,7 +1455,7 @@ namespace CodeWalker
// TimedEntitiesAlwaysOnCheckBox
//
this.TimedEntitiesAlwaysOnCheckBox.AutoSize = true;
- this.TimedEntitiesAlwaysOnCheckBox.Location = new System.Drawing.Point(131, 58);
+ this.TimedEntitiesAlwaysOnCheckBox.Location = new System.Drawing.Point(131, 83);
this.TimedEntitiesAlwaysOnCheckBox.Name = "TimedEntitiesAlwaysOnCheckBox";
this.TimedEntitiesAlwaysOnCheckBox.Size = new System.Drawing.Size(58, 17);
this.TimedEntitiesAlwaysOnCheckBox.TabIndex = 37;
@@ -1453,7 +1468,7 @@ namespace CodeWalker
this.GrassCheckBox.AutoSize = true;
this.GrassCheckBox.Checked = true;
this.GrassCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
- this.GrassCheckBox.Location = new System.Drawing.Point(10, 35);
+ this.GrassCheckBox.Location = new System.Drawing.Point(10, 57);
this.GrassCheckBox.Name = "GrassCheckBox";
this.GrassCheckBox.Size = new System.Drawing.Size(81, 17);
this.GrassCheckBox.TabIndex = 35;
@@ -1466,7 +1481,7 @@ namespace CodeWalker
this.InteriorsCheckBox.AutoSize = true;
this.InteriorsCheckBox.Checked = true;
this.InteriorsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
- this.InteriorsCheckBox.Location = new System.Drawing.Point(10, 81);
+ this.InteriorsCheckBox.Location = new System.Drawing.Point(10, 106);
this.InteriorsCheckBox.Name = "InteriorsCheckBox";
this.InteriorsCheckBox.Size = new System.Drawing.Size(92, 17);
this.InteriorsCheckBox.TabIndex = 38;
@@ -1586,7 +1601,7 @@ namespace CodeWalker
this.TimedEntitiesCheckBox.AutoSize = true;
this.TimedEntitiesCheckBox.Checked = true;
this.TimedEntitiesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
- this.TimedEntitiesCheckBox.Location = new System.Drawing.Point(10, 58);
+ this.TimedEntitiesCheckBox.Location = new System.Drawing.Point(10, 83);
this.TimedEntitiesCheckBox.Name = "TimedEntitiesCheckBox";
this.TimedEntitiesCheckBox.Size = new System.Drawing.Size(117, 17);
this.TimedEntitiesCheckBox.TabIndex = 36;
@@ -3581,5 +3596,6 @@ namespace CodeWalker
private System.Windows.Forms.ToolStripMenuItem ToolbarSnapToGroundGridButton;
private System.Windows.Forms.NumericUpDown SnapGridSizeUpDown;
private System.Windows.Forms.Label label26;
+ private System.Windows.Forms.CheckBox RenderEntitiesCheckBox;
}
}
\ No newline at end of file
diff --git a/WorldForm.cs b/WorldForm.cs
index 1e586dd..54dba20 100644
--- a/WorldForm.cs
+++ b/WorldForm.cs
@@ -76,6 +76,11 @@ namespace CodeWalker
bool ControlFireToggle = false;
+
+ int ControlBrushTimer = 0;
+ bool ControlBrushEnabled;
+ float ControlBrushRadius;
+
Entity camEntity = new Entity();
PedEntity pedEntity = new PedEntity();
@@ -99,7 +104,7 @@ namespace CodeWalker
-
+
@@ -487,7 +492,7 @@ namespace CodeWalker
}
- if (ControlMode == WorldControlMode.Free)
+ if (ControlMode == WorldControlMode.Free || ControlBrushEnabled)
{
if (Input.ShiftPressed)
{
@@ -1253,7 +1258,7 @@ namespace CodeWalker
if (MouseRayCollision.Hit)
{
var arup = GetPerpVec(MouseRayCollision.Normal);
- Renderer.RenderSelectionArrowOutline(MouseRayCollision.Position, MouseRayCollision.Normal, arup, Quaternion.Identity, 2.0f, 0.15f, cgrn);
+ Renderer.RenderBrushRadiusOutline(MouseRayCollision.Position, MouseRayCollision.Normal, arup, ProjectForm.GetInstanceBrushRadius(), cgrn);
}
}
@@ -1825,6 +1830,14 @@ namespace CodeWalker
}
}
+ public void UpdateGrassBatchGraphics(YmapGrassInstanceBatch grassBatch)
+ {
+ lock (Renderer.RenderSyncRoot)
+ {
+ Renderer.Invalidate(grassBatch);
+ }
+ }
+
public Vector3 GetCameraPosition()
{
@@ -2050,8 +2063,8 @@ namespace CodeWalker
if (mode == ControlMode) return;
- bool wasfree = (ControlMode == WorldControlMode.Free);
- bool isfree = (mode == WorldControlMode.Free);
+ bool wasfree = (ControlMode == WorldControlMode.Free || ControlBrushEnabled);
+ bool isfree = (mode == WorldControlMode.Free || ControlBrushEnabled);
if (isfree && !wasfree)
{
@@ -2105,17 +2118,17 @@ namespace CodeWalker
//reset variables for beginning the mouse hit test
CurMouseHit.Clear();
-
- MouseRayCollisionEnabled = Input.CtrlPressed; //temporary...!
- if (MouseRayCollisionEnabled)
+ // Get whether or not we can brush from the project form.
+ if (Input.CtrlPressed && ProjectForm != null && ProjectForm.CanPaintInstances())
{
- if (space.Inited && space.Grid != null)
- {
- Ray mray = new Ray();
- mray.Position = camera.MouseRay.Position + camera.Position;
- mray.Direction = camera.MouseRay.Direction;
- MouseRayCollision = space.RayIntersect(mray);
- }
+ ControlBrushEnabled = true;
+ MouseRayCollisionEnabled = true;
+ MouseRayCollision = GetSpaceMouseRay();
+ }
+ else if (MouseRayCollisionEnabled)
+ {
+ ControlBrushEnabled = false;
+ MouseRayCollisionEnabled = false;
}
@@ -2128,6 +2141,25 @@ namespace CodeWalker
}
+
+ public SpaceRayIntersectResult GetSpaceMouseRay()
+ {
+ SpaceRayIntersectResult ret = new SpaceRayIntersectResult();
+ if (space.Inited && space.Grid != null)
+ {
+ Ray mray = new Ray();
+ mray.Position = camera.MouseRay.Position + camera.Position;
+ mray.Direction = camera.MouseRay.Direction;
+ return space.RayIntersect(mray);
+ }
+ return ret;
+ }
+
+ public SpaceRayIntersectResult Raycast(Ray ray)
+ {
+ return space.RayIntersect(ray);
+ }
+
private void UpdateMouseHitsFromRenderer()
{
foreach (var rd in Renderer.RenderedDrawables)
@@ -4319,6 +4351,12 @@ namespace CodeWalker
{
camera.FollowEntity.Position = p;
}
+ public void GoToPosition(Vector3 p, Vector3 bound)
+ {
+ camera.FollowEntity.Position = p;
+ var bl = bound.Length();
+ camera.TargetDistance = bl > 1f ? bl : 1f;
+ }
private MapMarker AddMarker(Vector3 pos, string name, bool addtotxtbox = false)
{
@@ -5899,7 +5937,7 @@ namespace CodeWalker
MouseDownPoint = e.Location;
MouseLastPoint = MouseDownPoint;
- if (ControlMode == WorldControlMode.Free)
+ if (ControlMode == WorldControlMode.Free && !ControlBrushEnabled)
{
if (MouseLButtonDown)
{
@@ -5998,6 +6036,7 @@ namespace CodeWalker
SelectedMarker = null;
HideMarkerSelectionInfo();
}
+ ControlBrushTimer = 0;
}
}
@@ -6012,47 +6051,11 @@ namespace CodeWalker
dy = -dy;
}
- if (ControlMode == WorldControlMode.Free)
+ if (ControlMode == WorldControlMode.Free && !ControlBrushEnabled)
{
if (MouseLButtonDown)
{
- if (GrabbedMarker == null)
- {
- if (GrabbedWidget == null)
- {
- if (MapViewEnabled == false)
- {
- camera.MouseRotate(dx, dy);
- }
- else
- {
- //need to move the camera entity XY with mouse in mapview mode...
- MapViewDragX += dx;
- MapViewDragY += dy;
- }
- }
- else
- {
- //grabbed widget will move itself in Update() when IsDragging==true
- }
- }
- else
- {
- //move the grabbed marker...
- //float uptx = (CurrentMap != null) ? CurrentMap.UnitsPerTexelX : 1.0f;
- //float upty = (CurrentMap != null) ? CurrentMap.UnitsPerTexelY : 1.0f;
- //Vector3 wpos = GrabbedMarker.WorldPos;
- //wpos.X += dx * uptx;
- //wpos.Y += dy * upty;
- //GrabbedMarker.WorldPos = wpos;
- //UpdateMarkerTexturePos(GrabbedMarker);
- //if (GrabbedMarker == LocatorMarker)
- //{
- // LocateTextBox.Text = LocatorMarker.ToString();
- // WorldCoordTextBox.Text = LocatorMarker.Get2DWorldPosString();
- // TextureCoordTextBox.Text = LocatorMarker.Get2DTexturePosString();
- //}
- }
+ RotateCam(dx, dy);
}
if (MouseRButtonDown)
{
@@ -6076,11 +6079,31 @@ namespace CodeWalker
}
}
- MouseX = e.X;
- MouseY = e.Y;
- MouseLastPoint = e.Location;
+ UpdateMousePosition(e);
}
+ else if (ControlBrushEnabled)
+ {
+ if (MouseRButtonDown)
+ {
+ RotateCam(dx, dy);
+ }
+
+ UpdateMousePosition(e);
+
+ ControlBrushTimer++;
+ if (ControlBrushTimer > (Input.ShiftPressed ? 5 : 10))
+ {
+ lock (Renderer.RenderSyncRoot)
+ {
+ if (ProjectForm != null && MouseLButtonDown)
+ {
+ ProjectForm.PaintGrass(MouseRayCollision, Input.ShiftPressed);
+ }
+ ControlBrushTimer = 0;
+ }
+ }
+ }
else
{
lock (MouseControlSyncRoot)
@@ -6121,11 +6144,59 @@ namespace CodeWalker
}
}
+ private void UpdateMousePosition(MouseEventArgs e)
+ {
+ MouseX = e.X;
+ MouseY = e.Y;
+ MouseLastPoint = e.Location;
+ }
+
+ private void RotateCam(int dx, int dy)
+ {
+ if (GrabbedMarker == null)
+ {
+ if (GrabbedWidget == null)
+ {
+ if (MapViewEnabled == false)
+ {
+ camera.MouseRotate(dx, dy);
+ }
+ else
+ {
+ //need to move the camera entity XY with mouse in mapview mode...
+ MapViewDragX += dx;
+ MapViewDragY += dy;
+ }
+ }
+ else
+ {
+ //grabbed widget will move itself in Update() when IsDragging==true
+ }
+ }
+ else
+ {
+ //move the grabbed marker...
+ //float uptx = (CurrentMap != null) ? CurrentMap.UnitsPerTexelX : 1.0f;
+ //float upty = (CurrentMap != null) ? CurrentMap.UnitsPerTexelY : 1.0f;
+ //Vector3 wpos = GrabbedMarker.WorldPos;
+ //wpos.X += dx * uptx;
+ //wpos.Y += dy * upty;
+ //GrabbedMarker.WorldPos = wpos;
+ //UpdateMarkerTexturePos(GrabbedMarker);
+ //if (GrabbedMarker == LocatorMarker)
+ //{
+ // LocateTextBox.Text = LocatorMarker.ToString();
+ // WorldCoordTextBox.Text = LocatorMarker.Get2DWorldPosString();
+ // TextureCoordTextBox.Text = LocatorMarker.Get2DTexturePosString();
+ //}
+ }
+ }
+
private void WorldForm_MouseWheel(object sender, MouseEventArgs e)
{
if (e.Delta != 0)
{
- if (ControlMode == WorldControlMode.Free)
+ if (ControlMode == WorldControlMode.Free || ControlBrushEnabled)
{
camera.MouseZoom(e.Delta);
}
@@ -6255,7 +6326,7 @@ namespace CodeWalker
}
}
- if (ControlMode != WorldControlMode.Free)
+ if (ControlMode != WorldControlMode.Free || ControlBrushEnabled)
{
e.Handled = true;
}
@@ -7554,8 +7625,12 @@ namespace CodeWalker
{
SnapGridSize = (float)SnapGridSizeUpDown.Value;
}
- }
+ private void RenderEntitiesCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ Renderer.renderentities = RenderEntitiesCheckBox.Checked;
+ }
+ }
public enum WorldControlMode
{