2018-06-08 00:42:41 +08:00
|
|
|
|
using System;
|
2018-03-03 21:03:08 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Windows.Forms;
|
2018-06-08 00:42:41 +08:00
|
|
|
|
using CodeWalker.GameFiles;
|
|
|
|
|
using CodeWalker.World;
|
|
|
|
|
using SharpDX;
|
|
|
|
|
|
2018-06-10 21:36:50 +08:00
|
|
|
|
// TODO
|
|
|
|
|
// - COMPLETED -- Optimization feature.
|
2018-06-08 00:42:41 +08:00
|
|
|
|
// - COMPLETED -- Remove grass instances using CTRL + SHIFT + LMB
|
|
|
|
|
|
2018-06-10 21:36:50 +08:00
|
|
|
|
// - Better gizmo for grass brush (like a circle with a little line in the middle sticking upwards)
|
2018-06-08 00:42:41 +08:00
|
|
|
|
// - 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?)
|
2018-06-10 21:36:50 +08:00
|
|
|
|
// - 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.
|
2018-06-08 00:42:41 +08:00
|
|
|
|
|
2018-06-10 21:36:50 +08:00
|
|
|
|
// 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.
|
2018-03-03 21:03:08 +08:00
|
|
|
|
|
|
|
|
|
namespace CodeWalker.Project.Panels
|
|
|
|
|
{
|
|
|
|
|
public partial class EditYmapGrassPanel : ProjectPanel
|
|
|
|
|
{
|
2018-03-03 21:09:31 +08:00
|
|
|
|
public ProjectForm ProjectForm;
|
2018-03-03 21:03:08 +08:00
|
|
|
|
|
2018-03-03 21:09:31 +08:00
|
|
|
|
public EditYmapGrassPanel(ProjectForm owner)
|
2018-03-03 21:03:08 +08:00
|
|
|
|
{
|
|
|
|
|
ProjectForm = owner;
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
public YmapGrassInstanceBatch CurrentBatch { get; set; }
|
|
|
|
|
|
|
|
|
|
#region Form
|
|
|
|
|
|
2018-03-03 21:03:08 +08:00
|
|
|
|
public void SetBatch(YmapGrassInstanceBatch batch)
|
|
|
|
|
{
|
|
|
|
|
CurrentBatch = batch;
|
|
|
|
|
Tag = batch;
|
|
|
|
|
UpdateFormTitle();
|
2018-06-08 00:42:41 +08:00
|
|
|
|
UpdateControls();
|
2018-12-03 16:54:04 +08:00
|
|
|
|
ProjectForm.WorldForm?.SelectGrassBatch(batch);
|
2018-06-08 00:42:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2018-06-10 21:36:50 +08:00
|
|
|
|
RadiusNumericUpDown.Value = (decimal)CurrentBatch.BrushRadius;
|
|
|
|
|
ExtentsMinTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.AABBMin);
|
|
|
|
|
ExtentsMaxTextBox.Text = FloatUtil.GetVector3String(CurrentBatch.AABBMax);
|
2018-03-03 21:03:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateFormTitle()
|
|
|
|
|
{
|
|
|
|
|
Text = CurrentBatch?.Batch.archetypeName.ToString() ?? "Grass Batch";
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Events
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
}
|
2018-06-10 21:36:50 +08:00
|
|
|
|
|
|
|
|
|
private void RadiusNumericUpDown_ValueChanged(object sender, EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (CurrentBatch == null) return;
|
|
|
|
|
CurrentBatch.BrushRadius = (float)RadiusNumericUpDown.Value;
|
|
|
|
|
}
|
2018-06-08 00:42:41 +08:00
|
|
|
|
#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;
|
2018-12-03 16:54:04 +08:00
|
|
|
|
var d = MessageBox.Show(
|
|
|
|
|
@"You are about to split the selected batch into multiple parts. Are you sure you want to do this?",
|
|
|
|
|
@"Instance Optimizer", MessageBoxButtons.YesNo);
|
|
|
|
|
|
|
|
|
|
if (d == DialogResult.No)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
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;
|
|
|
|
|
|
2020-01-06 20:21:32 +08:00
|
|
|
|
lock (wf.RenderSyncRoot)
|
2018-06-08 00:42:41 +08:00
|
|
|
|
{
|
|
|
|
|
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;
|
2020-01-06 20:21:32 +08:00
|
|
|
|
lock (wf.RenderSyncRoot)
|
2018-06-08 00:42:41 +08:00
|
|
|
|
{
|
|
|
|
|
if (CurrentBatch.EraseInstancesAtMouse(
|
|
|
|
|
CurrentBatch,
|
|
|
|
|
mouseRay,
|
|
|
|
|
(float) RadiusNumericUpDown.Value
|
|
|
|
|
))
|
|
|
|
|
{
|
|
|
|
|
wf.UpdateGrassBatchGraphics(CurrentBatch);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (changed) BatchChanged();
|
|
|
|
|
}
|
2018-03-03 21:03:08 +08:00
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Privates
|
|
|
|
|
|
|
|
|
|
private SpaceRayIntersectResult SpawnRayFunc(Vector3 spawnPos)
|
|
|
|
|
{
|
|
|
|
|
var res = ProjectForm.WorldForm.Raycast(new Ray(spawnPos, -Vector3.UnitZ));
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2018-03-03 21:03:08 +08:00
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
private void BatchChanged()
|
2018-03-03 21:03:08 +08:00
|
|
|
|
{
|
2018-06-08 00:42:41 +08:00
|
|
|
|
UpdateControls();
|
|
|
|
|
CurrentBatch.UpdateInstanceCount();
|
|
|
|
|
CurrentBatch.HasChanged = true;
|
|
|
|
|
ProjectForm.SetGrassBatchHasChanged(false);
|
2018-03-03 21:03:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-08 00:42:41 +08:00
|
|
|
|
#endregion
|
2018-03-03 21:03:08 +08:00
|
|
|
|
}
|
2018-06-08 00:42:41 +08:00
|
|
|
|
}
|