Merge pull request #28 from sollaholla/master

added grass painting (second revision)
This commit is contained in:
dexyfex 2018-08-15 08:49:12 +10:00 committed by GitHub
commit f520ca4991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2437 additions and 588 deletions

View File

@ -114,6 +114,7 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Utils\BoundingBoxes.cs" />
<Compile Include="Utils\Cache.cs" /> <Compile Include="Utils\Cache.cs" />
<Compile Include="Utils\Matrices.cs" /> <Compile Include="Utils\Matrices.cs" />
<Compile Include="Utils\Quaternions.cs" /> <Compile Include="Utils\Quaternions.cs" />

View File

@ -7,6 +7,8 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using CodeWalker.Core.Utils;
using CodeWalker.World;
namespace CodeWalker.GameFiles namespace CodeWalker.GameFiles
{ {
@ -925,6 +927,7 @@ namespace CodeWalker.GameFiles
GrassInstanceBatches = batches.ToArray(); GrassInstanceBatches = batches.ToArray();
HasChanged = true; HasChanged = true;
UpdateGrassPhysDict(true);
} }
public bool RemoveGrassBatch(YmapGrassInstanceBatch batch) public bool RemoveGrassBatch(YmapGrassInstanceBatch batch)
@ -949,6 +952,10 @@ namespace CodeWalker.GameFiles
} }
} }
if (batches.Count <= 0)
{
UpdateGrassPhysDict(false);
}
GrassInstanceBatches = batches.ToArray(); GrassInstanceBatches = batches.ToArray();
@ -1137,6 +1144,27 @@ namespace CodeWalker.GameFiles
} }
private void UpdateGrassPhysDict(bool add)
{
var physDict = physicsDictionaries?.ToList() ?? new List<MetaHash>();
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) private static uint SetBit(uint value, int bit)
{ {
@ -1493,6 +1521,8 @@ namespace CodeWalker.GameFiles
[TypeConverter(typeof(ExpandableObjectConverter))] [TypeConverter(typeof(ExpandableObjectConverter))]
public class YmapGrassInstanceBatch public class YmapGrassInstanceBatch
{ {
private const float BatchVertMultiplier = 0.00001525878f;
public Archetype Archetype { get; set; } //cached by GameFileCache on loading... public Archetype Archetype { get; set; } //cached by GameFileCache on loading...
public rage__fwGrassInstanceListDef Batch { get; set; } public rage__fwGrassInstanceListDef Batch { get; set; }
public rage__fwGrassInstanceListDef__InstanceData[] Instances { get; set; } public rage__fwGrassInstanceListDef__InstanceData[] Instances { get; set; }
@ -1504,10 +1534,478 @@ namespace CodeWalker.GameFiles
public float Distance; //used for rendering public float Distance; //used for rendering
public YmapFile Ymap { get; set; } public YmapFile Ymap { get; set; }
private List<BoundingBox> 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() public override string ToString()
{ {
return Batch.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<BoundingBox>();
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<rage__fwGrassInstanceListDef__InstanceData>();
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<Vector3, SpaceRayIntersectResult> spawnRayFunc,
Color color,
int ao,
int scale,
Vector3 pad,
bool randomScale)
{
ReInitializeBoundingCache();
var spawnPosition = mouseRay.Position;
var positions = new List<Vector3>();
var normals = new List<Vector3>();
// 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<rage__fwGrassInstanceListDef__InstanceData>();
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<YmapGrassInstanceBatch>();
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<List<rage__fwGrassInstanceListDef__InstanceData>> SplitGrassRecursive(
IReadOnlyList<rage__fwGrassInstanceListDef__InstanceData> grassInstances,
BoundingBox batchAABB,
float minRadius = 15F
)
{
var ret = new List<List<rage__fwGrassInstanceListDef__InstanceData>>();
var oldPoints = SplitGrass(grassInstances, batchAABB);
while (true)
{
var stop = true;
var newPoints = new List<List<rage__fwGrassInstanceListDef__InstanceData>>();
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<rage__fwGrassInstanceListDef__InstanceData>[] SplitGrass(
IReadOnlyList<rage__fwGrassInstanceListDef__InstanceData> points,
BoundingBox batchAABB)
{
var pointGroup = new List<rage__fwGrassInstanceListDef__InstanceData>[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<rage__fwGrassInstanceListDef__InstanceData> newGrass, BoundingBox oldAABB)
{
var grassPositions = newGrass.Select(x => GetGrassWorldPos(x.Position, oldAABB)).ToArray();
return BoundingBox.FromPoints(grassPositions).Expand(1f);
}
private void SpawnInstances(
IReadOnlyList<Vector3> positions,
IReadOnlyList<Vector3> normals,
ICollection<rage__fwGrassInstanceListDef__InstanceData> 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<Vector3,
SpaceRayIntersectResult> spawnRayFunc,
ICollection<Vector3> positions,
ICollection<Vector3> 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<rage__fwGrassInstanceListDef__InstanceData> RecalculateInstances(
List<rage__fwGrassInstanceListDef__InstanceData> instances,
BoundingBox oldInstanceBounds,
BoundingBox newInstanceBounds)
{
var refreshList = new List<rage__fwGrassInstanceListDef__InstanceData>();
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<Vector3> 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))] [TypeConverter(typeof(ExpandableObjectConverter))]
@ -1548,7 +2046,6 @@ namespace CodeWalker.GameFiles
} }
} }
[TypeConverter(typeof(ExpandableObjectConverter))] [TypeConverter(typeof(ExpandableObjectConverter))]
public class YmapTimeCycleModifier public class YmapTimeCycleModifier
{ {

View File

@ -935,6 +935,10 @@ namespace CodeWalker.GameFiles
[TC(typeof(EXP))] public struct ArrayOfUshorts3 //array of 3 ushorts [TC(typeof(EXP))] public struct ArrayOfUshorts3 //array of 3 ushorts
{ {
public ushort u0, u1, u2; public ushort u0, u1, u2;
public Vector3 XYZ()
{
return new Vector3(u0, u1, u2);
}
public override string ToString() public override string ToString()
{ {
return u0.ToString() + ", " + u1.ToString() + ", " + u2.ToString(); return u0.ToString() + ", " + u1.ToString() + ", " + u2.ToString();

View File

@ -1443,6 +1443,13 @@ namespace CodeWalker.GameFiles
return Materials[type.Index]; 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) public static string GetMaterialName(BoundsMaterialType type)
{ {
var m = GetMaterial(type); var m = GetMaterial(type);

View File

@ -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);
}
}
}

View File

@ -4,18 +4,17 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using CodeWalker.GameFiles;
namespace CodeWalker namespace CodeWalker
{ {
public static class Vectors public static class Vectors
{ {
public static Vector3 XYZ(this Vector4 v) public static Vector3 XYZ(this Vector4 v)
{ {
return new Vector3(v.X, v.Y, v.Z); return new Vector3(v.X, v.Y, v.Z);
} }
public static Vector3 Round(this Vector3 v) 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)); 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)); return new Vector4((float)Math.Floor(v.X), (float)Math.Floor(v.Y), (float)Math.Floor(v.Z), (float)Math.Floor(v.W));
} }
} }

View File

@ -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(" <Item>"); sb.AppendLine(" <Item>");
sb.AppendLine(" <imapName>" + ymapname + "</imapName>"); sb.AppendLine(" <imapName>" + ymapname + "</imapName>");

View File

@ -28,28 +28,642 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EditYmapGrassPanel)); 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.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(); 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 // label1
// //
this.label1.AutoSize = true; 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.Name = "label1";
this.label1.Size = new System.Drawing.Size(105, 13); this.label1.Size = new System.Drawing.Size(44, 13);
this.label1.TabIndex = 0; this.label1.TabIndex = 57;
this.label1.Text = "Grass editing TODO!"; 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 // EditYmapGrassPanel
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 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.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.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "EditYmapGrassPanel"; this.Name = "EditYmapGrassPanel";
this.Text = "Grass Batch"; 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.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@ -57,6 +671,52 @@
#endregion #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.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;
} }
} }

View File

@ -1,22 +1,30 @@
using CodeWalker.GameFiles; using System;
using System;
using System.Collections.Generic; 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 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 namespace CodeWalker.Project.Panels
{ {
public partial class EditYmapGrassPanel : ProjectPanel public partial class EditYmapGrassPanel : ProjectPanel
{ {
public ProjectForm ProjectForm; public ProjectForm ProjectForm;
public YmapGrassInstanceBatch CurrentBatch { get; set; }
//private bool populatingui = false;
public EditYmapGrassPanel(ProjectForm owner) public EditYmapGrassPanel(ProjectForm owner)
{ {
@ -24,12 +32,44 @@ namespace CodeWalker.Project.Panels
InitializeComponent(); InitializeComponent();
} }
public YmapGrassInstanceBatch CurrentBatch { get; set; }
#region Form
public void SetBatch(YmapGrassInstanceBatch batch) public void SetBatch(YmapGrassInstanceBatch batch)
{ {
CurrentBatch = batch; CurrentBatch = batch;
Tag = batch; Tag = batch;
LoadGrassBatch();
UpdateFormTitle(); 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() private void UpdateFormTitle()
@ -37,11 +77,225 @@ namespace CodeWalker.Project.Panels
Text = CurrentBatch?.Batch.archetypeName.ToString() ?? "Grass Batch"; 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
} }
} }

View File

@ -117,6 +117,9 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<metadata name="OptimizeBatchButtonTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>

View File

@ -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; 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) public TreeNode FindYndTreeNode(YndFile ynd)
{ {
if (ProjectTreeView.Nodes.Count <= 0) return null; 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) public void TrySelectPathNodeTreeNode(YndNode node)
{ {
TreeNode tnode = FindPathNodeTreeNode(node); TreeNode tnode = FindPathNodeTreeNode(node);
@ -1240,6 +1284,16 @@ namespace CodeWalker.Project.Panels
tn.Parent.Nodes.Remove(tn); 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) public void RemovePathNodeTreeNode(YndNode node)
{ {
var tn = FindPathNodeTreeNode(node); var tn = FindPathNodeTreeNode(node);

File diff suppressed because it is too large Load Diff

View File

@ -104,7 +104,6 @@ namespace CodeWalker.Project
RpfMan = GameFileCache.RpfMan; RpfMan = GameFileCache.RpfMan;
})).Start(); })).Start();
} }
} }
private void UpdateStatus(string text) private void UpdateStatus(string text)
@ -705,6 +704,25 @@ namespace CodeWalker.Project
//######## Public methods //######## 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() public void NewProject()
{ {
if (CurrentProjectFile != null) if (CurrentProjectFile != null)
@ -1270,6 +1288,10 @@ namespace CodeWalker.Project
{ {
ProjectExplorer?.TrySelectCarGenTreeNode(CurrentCarGen); ProjectExplorer?.TrySelectCarGenTreeNode(CurrentCarGen);
} }
else if (CurrentGrassBatch != null)
{
ProjectExplorer?.TrySelectGrassBatchTreeNode(CurrentGrassBatch);
}
} }
public void RemoveYmapFromProject() public void RemoveYmapFromProject()
{ {
@ -1435,6 +1457,155 @@ namespace CodeWalker.Project
return CurrentEntity == ent; 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<EditYmapGrassPanel>(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) public void NewCarGen(YmapCarGen copy = null, bool copyPosition = false)
{ {
if (CurrentYmapFile == null) return; if (CurrentYmapFile == null) return;
@ -3820,9 +3991,11 @@ namespace CodeWalker.Project
for (int i = 0; i < CurrentProjectFile.YmapFiles.Count; i++) for (int i = 0; i < CurrentProjectFile.YmapFiles.Count; i++)
{ {
var ymap = CurrentProjectFile.YmapFiles[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) if (ymap.Loaded)
{ {
ymaps[ymap._CMapData.name] = ymap; ymaps[isnew ? JenkHash.GenHash(ymap.Name) : ymap.RpfFileEntry.ShortNameHash] = ymap;
} }
} }
} }
@ -4026,6 +4199,11 @@ namespace CodeWalker.Project
{ {
ProjectExplorer?.TrySelectCarGenTreeNode(cargen); ProjectExplorer?.TrySelectCarGenTreeNode(cargen);
} }
if (grassbatch != CurrentGrassBatch)
{
ProjectExplorer?.TrySelectGrassBatchTreeNode(grassbatch);
}
} }
else if (YndExistsInProject(ynd)) else if (YndExistsInProject(ynd))
{ {
@ -4699,7 +4877,19 @@ namespace CodeWalker.Project
PromoteIfPreviewPanelActive(); 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();
}
@ -4877,6 +5067,7 @@ namespace CodeWalker.Project
YmapNewEntityMenu.Enabled = enable && inproj; YmapNewEntityMenu.Enabled = enable && inproj;
YmapNewCarGenMenu.Enabled = enable && inproj; YmapNewCarGenMenu.Enabled = enable && inproj;
YmapNewGrassBatchMenu.Enabled = enable && inproj;
if (CurrentYmapFile != null) if (CurrentYmapFile != null)
{ {
@ -5312,6 +5503,10 @@ namespace CodeWalker.Project
{ {
NewCarGen(); NewCarGen();
} }
private void YmapNewGrassBatchMenu_Click(object sender, EventArgs e)
{
NewGrassBatch();
}
private void YmapAddToProjectMenu_Click(object sender, EventArgs e) private void YmapAddToProjectMenu_Click(object sender, EventArgs e)
{ {
AddYmapToProject(CurrentYmapFile); AddYmapToProject(CurrentYmapFile);

View File

@ -195,6 +195,14 @@ namespace CodeWalker.Rendering
} }
} }
public void Invalidate(YmapGrassInstanceBatch batch)
{
lock (updateSyncRoot)
{
instbatches.Invalidate(batch);
}
}
} }

View File

@ -380,6 +380,11 @@ namespace CodeWalker.Rendering
renderableCache.Invalidate(path); renderableCache.Invalidate(path);
} }
public void Invalidate(YmapGrassInstanceBatch batch)
{
renderableCache.Invalidate(batch);
}
public void UpdateSelectionDrawFlags(DrawableModel model, DrawableGeometry geom, bool rem) 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) public void RenderSelectionArrowOutline(Vector3 pos, Vector3 dir, Vector3 up, Quaternion ori, float len, float rad, uint colour)
{ {
Vector3 ax = Vector3.Cross(dir, up); Vector3 ax = Vector3.Cross(dir, up);

View File

@ -695,6 +695,10 @@ namespace CodeWalker.Rendering
{ {
var gb = batch.Key; var gb = batch.Key;
// sanity check
if (batch.GrassInstanceBuffer == null)
return;
VSEntityVars.Vars.CamRel = new Vector4(gb.CamRel, 0.0f); VSEntityVars.Vars.CamRel = new Vector4(gb.CamRel, 0.0f);
VSEntityVars.Vars.Orientation = Quaternion.Identity; VSEntityVars.Vars.Orientation = Quaternion.Identity;
VSEntityVars.Vars.Scale = Vector3.One; VSEntityVars.Vars.Scale = Vector3.One;

View File

@ -75,6 +75,11 @@ namespace CodeWalker
bool ControlFireToggle = false; bool ControlFireToggle = false;
int ControlBrushTimer = 0;
bool ControlBrushEnabled;
float ControlBrushRadius;
Entity camEntity = new Entity(); Entity camEntity = new Entity();
PedEntity pedEntity = new PedEntity(); PedEntity pedEntity = new PedEntity();
@ -486,7 +491,7 @@ namespace CodeWalker
} }
if (ControlMode == WorldControlMode.Free) if (ControlMode == WorldControlMode.Free || ControlBrushEnabled)
{ {
if (Input.ShiftPressed) if (Input.ShiftPressed)
{ {
@ -1252,7 +1257,7 @@ namespace CodeWalker
if (MouseRayCollision.Hit) if (MouseRayCollision.Hit)
{ {
var arup = GetPerpVec(MouseRayCollision.Normal); 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);
} }
} }
@ -1824,6 +1829,14 @@ namespace CodeWalker
} }
} }
public void UpdateGrassBatchGraphics(YmapGrassInstanceBatch grassBatch)
{
lock (Renderer.RenderSyncRoot)
{
Renderer.Invalidate(grassBatch);
}
}
public Vector3 GetCameraPosition() public Vector3 GetCameraPosition()
{ {
@ -2049,8 +2062,8 @@ namespace CodeWalker
if (mode == ControlMode) return; if (mode == ControlMode) return;
bool wasfree = (ControlMode == WorldControlMode.Free); bool wasfree = (ControlMode == WorldControlMode.Free || ControlBrushEnabled);
bool isfree = (mode == WorldControlMode.Free); bool isfree = (mode == WorldControlMode.Free || ControlBrushEnabled);
if (isfree && !wasfree) if (isfree && !wasfree)
{ {
@ -2104,17 +2117,17 @@ namespace CodeWalker
//reset variables for beginning the mouse hit test //reset variables for beginning the mouse hit test
CurMouseHit.Clear(); CurMouseHit.Clear();
// Get whether or not we can brush from the project form.
MouseRayCollisionEnabled = Input.CtrlPressed; //temporary...! if (Input.CtrlPressed && ProjectForm != null && ProjectForm.CanPaintInstances())
if (MouseRayCollisionEnabled)
{ {
if (space.Inited && space.Grid != null) ControlBrushEnabled = true;
{ MouseRayCollisionEnabled = true;
Ray mray = new Ray(); MouseRayCollision = GetSpaceMouseRay();
mray.Position = camera.MouseRay.Position + camera.Position; }
mray.Direction = camera.MouseRay.Direction; else if (MouseRayCollisionEnabled)
MouseRayCollision = space.RayIntersect(mray); {
} ControlBrushEnabled = false;
MouseRayCollisionEnabled = false;
} }
@ -2127,6 +2140,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() private void UpdateMouseHitsFromRenderer()
{ {
foreach (var rd in Renderer.RenderedDrawables) foreach (var rd in Renderer.RenderedDrawables)
@ -4318,6 +4350,12 @@ namespace CodeWalker
{ {
camera.FollowEntity.Position = p; 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) private MapMarker AddMarker(Vector3 pos, string name, bool addtotxtbox = false)
{ {
@ -5898,7 +5936,7 @@ namespace CodeWalker
MouseDownPoint = e.Location; MouseDownPoint = e.Location;
MouseLastPoint = MouseDownPoint; MouseLastPoint = MouseDownPoint;
if (ControlMode == WorldControlMode.Free) if (ControlMode == WorldControlMode.Free && !ControlBrushEnabled)
{ {
if (MouseLButtonDown) if (MouseLButtonDown)
{ {
@ -5997,6 +6035,7 @@ namespace CodeWalker
SelectedMarker = null; SelectedMarker = null;
HideMarkerSelectionInfo(); HideMarkerSelectionInfo();
} }
ControlBrushTimer = 0;
} }
} }
@ -6011,47 +6050,11 @@ namespace CodeWalker
dy = -dy; dy = -dy;
} }
if (ControlMode == WorldControlMode.Free) if (ControlMode == WorldControlMode.Free && !ControlBrushEnabled)
{ {
if (MouseLButtonDown) if (MouseLButtonDown)
{ {
if (GrabbedMarker == null) RotateCam(dx, dy);
{
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();
//}
}
} }
if (MouseRButtonDown) if (MouseRButtonDown)
{ {
@ -6075,11 +6078,31 @@ namespace CodeWalker
} }
} }
MouseX = e.X; UpdateMousePosition(e);
MouseY = e.Y;
MouseLastPoint = e.Location;
} }
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 else
{ {
lock (MouseControlSyncRoot) lock (MouseControlSyncRoot)
@ -6120,11 +6143,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) private void WorldForm_MouseWheel(object sender, MouseEventArgs e)
{ {
if (e.Delta != 0) if (e.Delta != 0)
{ {
if (ControlMode == WorldControlMode.Free) if (ControlMode == WorldControlMode.Free || ControlBrushEnabled)
{ {
camera.MouseZoom(e.Delta); camera.MouseZoom(e.Delta);
} }
@ -6254,7 +6325,7 @@ namespace CodeWalker
} }
} }
if (ControlMode != WorldControlMode.Free) if (ControlMode != WorldControlMode.Free || ControlBrushEnabled)
{ {
e.Handled = true; e.Handled = true;
} }
@ -7555,7 +7626,6 @@ namespace CodeWalker
} }
} }
public enum WorldControlMode public enum WorldControlMode
{ {
Free = 0, Free = 0,