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 {