Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 281 additions & 4 deletions CodeWalker.Core/World/Scenarios.cs

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions CodeWalker/Project/Panels/EditScenarioNodePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,7 @@ public void UpdateScenarioNodeUI()

UpdateTabVisibility();

if (CurrentScenarioNode != null)
{
if (ProjectForm.WorldForm != null)
{
ProjectForm.WorldForm.SelectObject(CurrentScenarioNode);
}
}
//Selection is now managed externally - removed SelectObject call to support multi-selection
}

private void UpdateTabVisibility()
Expand Down
210 changes: 209 additions & 1 deletion CodeWalker/Rendering/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public class Renderer
private Dictionary<YmapEntityDef, Renderable> RequiredParents = new Dictionary<YmapEntityDef, Renderable>();
private List<YmapEntityDef> RenderEntities = new List<YmapEntityDef>();

public Dictionary<uint, YmapEntityDef> HideEntities = new Dictionary<uint, YmapEntityDef>();//dictionary of entities to hide, for cutscenes to use
public Dictionary<uint, YmapEntityDef> HideEntities = new Dictionary<uint, YmapEntityDef>();//dictionary of entities to hide, for cutscenes to use

private Dictionary<MetaHash, Ped> ScenarioPeds = new Dictionary<MetaHash, Ped>();//cache for scenario ped models

public bool ShowScriptedYmaps = true;
public List<YmapFile> VisibleYmaps = new List<YmapFile>();
Expand Down Expand Up @@ -3451,6 +3453,212 @@ public void RenderCar(Vector3 pos, Quaternion ori, MetaHash modelHash, MetaHash
}
}

public void RenderScenarioNode(ScenarioNode node)
{
if (node == null) return;

var vpoint = node.MyPoint ?? node.ClusterMyPoint;

// Skip vehicle scenarios - they're rendered differently when selected
if ((vpoint != null) && (vpoint?.Type?.IsVehicle ?? false))
{
return;
}

// Render as ped model
var pedhash = (uint)0;
var modelSetHash = vpoint?.ModelSet?.NameHash ?? 0;

if ((modelSetHash != 0) && (modelSetHash != 493038497)) // "none"
{
// Get ped model from the model set
var stypes = Scenarios.ScenarioTypes;
if (stypes != null)
{
var modelset = stypes.GetPedModelSet(modelSetHash);
if ((modelset != null) && (modelset.Models != null) && (modelset.Models.Length > 0))
{
pedhash = JenkHash.GenHash(modelset.Models[0].NameLower);
}
}
}

// Default to mp_m_freemode_01 if no model found
if (pedhash == 0)
{
pedhash = JenkHash.GenHash("mp_m_freemode_01");
}

RenderScenarioPed(node.Position, node.Orientation, pedhash, vpoint);
}

public void RenderScenarioPed(Vector3 pos, Quaternion ori, MetaHash pedHash, MCScenarioPoint point = null)
{
if (pedHash == 0)
{
pedHash = JenkHash.GenHash("mp_m_freemode_01");
}

// Get or create cached ped
Ped ped = null;
if (!ScenarioPeds.TryGetValue(pedHash, out ped))
{
ped = new Ped();
ped.Init(pedHash, gameFileCache);

// Load default components for the ped
if (ped.Ymt != null)
{
ped.LoadDefaultComponents(gameFileCache);
}

ScenarioPeds[pedHash] = ped;
}

if (ped?.Yft != null)
{
// Load animation based on scenario type
ClipMapEntry animClip = null;

// Try to get animation from ped's default clip dict first (idle animation)
if (ped.Ycd?.ClipMapEntries != null)
{
var idleHash = JenkHash.GenHash("idle");
animClip = ped.Ycd.ClipMapEntries.FirstOrDefault(c =>
c.Clip != null && c.Hash == idleHash);

if (animClip == null)
{
animClip = ped.Ycd.ClipMapEntries.FirstOrDefault(c => c.Clip != null);
}
}

// Try to load scenario-specific animation
string scenarioTypeName = null;
string clipDictName = null;

if (point?.Type != null)
{
var stypes = Scenarios.ScenarioTypes;
List<string> clipSetNames = null;

// Get the scenario type name for sitting detection
scenarioTypeName = JenkIndex.TryGetString(point.Type.NameHash);

// Check if this is a ScenarioTypePlayAnims with direct BaseAnimClipSets
if (!point.Type.IsGroup && point.Type.Type is ScenarioTypePlayAnims playAnimsType && playAnimsType.BaseAnimClipSets != null && playAnimsType.BaseAnimClipSets.Count > 0)
{
clipSetNames = playAnimsType.BaseAnimClipSets;
}
// Otherwise try ConditionalAnimsGroup
else
{
var animGroupHash = point.Type.ConditionalAnimsGroupHash;
if (animGroupHash != 0 && stypes != null)
{
var animGroup = stypes.GetAnimGroup(animGroupHash);
if (animGroup?.BaseAnimClipSets != null && animGroup.BaseAnimClipSets.Count > 0)
{
clipSetNames = animGroup.BaseAnimClipSets;
}
}
}

if (clipSetNames != null && clipSetNames.Count > 0 && stypes != null)
{
// Use the first base animation clipset
var clipSetName = clipSetNames[0];
var clipSetHash = JenkHash.GenHash(clipSetName.ToLowerInvariant());

// Look up the actual clipDictionaryName from clip_sets.ymt
clipDictName = stypes.GetClipSet(clipSetHash);

if (!string.IsNullOrEmpty(clipDictName))
{
var ycdHash = JenkHash.GenHash(clipDictName.ToLowerInvariant());
var ycd = gameFileCache.GetYcd(ycdHash);

if ((ycd != null) && (ycd.Loaded) && (ycd.ClipMapEntries != null))
{
// Try to find a "base" clip or use the first available clip
var baseHash = JenkHash.GenHash("base");
var scenarioClip = ycd.ClipMapEntries.FirstOrDefault(c =>
c.Clip != null && c.Hash == baseHash);

if (scenarioClip == null)
{
scenarioClip = ycd.ClipMapEntries.FirstOrDefault(c => c.Clip != null);
}

if (scenarioClip != null)
{
animClip = scenarioClip;
}
}
}
}
}

// Align ped to ground
float minz = ped.Yft.Fragment?.PhysicsLODGroup?.PhysicsLOD1?.Bound?.BoxMin.Z ?? 0.0f;
pos.Z -= minz;

// Offset ped up by 1 meter, unless they're sitting or an animal
bool skipOffset = false;

// Check model set for animal
if (point?.ModelSet != null)
{
var modelSetName = JenkIndex.TryGetString(point.ModelSet.NameHash);
if (!string.IsNullOrEmpty(modelSetName))
{
var modelSetLower = modelSetName.ToLowerInvariant();
if (modelSetLower.Contains("animal") || modelSetLower.Contains("bird"))
{
skipOffset = true;
}
}
}

// Check scenario type name for sitting
if (!skipOffset && !string.IsNullOrEmpty(scenarioTypeName))
{
var scenarioLower = scenarioTypeName.ToLowerInvariant();
skipOffset = scenarioLower.Contains("sit") || scenarioLower.Contains("seat");
}

// Check clip dictionary name for sitting
if (!skipOffset && !string.IsNullOrEmpty(clipDictName))
{
var clipDictLower = clipDictName.ToLowerInvariant();
skipOffset = clipDictLower.Contains("sit") || clipDictLower.Contains("seat");
}

// Check animation clip name for sitting
if (!skipOffset && animClip != null)
{
var animName = JenkIndex.TryGetString(animClip.Hash)?.ToLowerInvariant() ?? "";
skipOffset = animName.Contains("sit") || animName.Contains("seat");
}

if (!skipOffset)
{
pos.Z += 1.0f;
}

ped.Position = pos;
ped.Rotation = ori;
ped.RenderEntity.SetPosition(pos);
ped.RenderEntity.SetOrientation(ori);

// Update animation clip
ped.AnimClip = animClip;

// Render the ped with all its components and animation
RenderPed(ped);
}
}

public void RenderVehicle(Vehicle vehicle, ClipMapEntry animClip = null)
{

Expand Down
3 changes: 2 additions & 1 deletion CodeWalker/Rendering/ShaderManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public class ShaderManager
private Camera Camera;
public ShaderGlobalLights GlobalLights = new ShaderGlobalLights();
public bool PathsDepthClip = true;//false;//
public Vector3? SelectedScenarioNodePosition = null; // Position of selected scenario node to exclude from cube rendering

private GameFileCache GameFileCache;
private RenderableCache RenderableCache;
Expand Down Expand Up @@ -654,7 +655,7 @@ public void RenderQueued(DeviceContext context, Camera camera, Vector4 wind)
context.OutputMerger.DepthStencilState = PathsDepthClip ? dsDisableWrite : dsDisableAll;// dsEnabled; //
context.Rasterizer.State = rsSolid;

Paths.RenderBatches(context, RenderPathBatches, camera, GlobalLights);
Paths.RenderBatches(context, RenderPathBatches, camera, GlobalLights, SelectedScenarioNodePosition);
}


Expand Down
39 changes: 36 additions & 3 deletions CodeWalker/Rendering/Shaders/PathShader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom)
}


public void RenderBatches(DeviceContext context, List<RenderablePathBatch> batches, Camera camera, ShaderGlobalLights lights)
public void RenderBatches(DeviceContext context, List<RenderablePathBatch> batches, Camera camera, ShaderGlobalLights lights, Vector3? excludeNodePosition = null)
{
UseDynamicVerts = false;
SetShader(context);
Expand Down Expand Up @@ -162,10 +162,43 @@ public void RenderBatches(DeviceContext context, List<RenderablePathBatch> batch
foreach (var batch in batches)
{
if (batch.NodeBuffer == null) continue;
if (batch.Nodes == null) continue;

context.VertexShader.SetShaderResource(0, batch.NodeBuffer.SRV);
int nodeCount = batch.Nodes.Length;

cube.DrawInstanced(context, batch.Nodes.Length);
// If we need to exclude a specific node, filter it out
if (excludeNodePosition.HasValue && nodeCount > 0)
{
var excludePos = excludeNodePosition.Value;
var filteredNodes = new List<Vector4>();

foreach (var node in batch.Nodes)
{
// Check if this node matches the position to exclude (with small epsilon for float comparison)
var nodePos = new Vector3(node.X, node.Y, node.Z);
float distSq = (nodePos - excludePos).LengthSquared();
if (distSq > 0.01f) // If not the selected node, include it
{
filteredNodes.Add(node);
}
}

// Only render if we have nodes left after filtering
if (filteredNodes.Count > 0)
{
// Create temporary buffer with filtered nodes
var tempBuffer = new GpuSBuffer<Vector4>(context.Device, filteredNodes.ToArray());
context.VertexShader.SetShaderResource(0, tempBuffer.SRV);
cube.DrawInstanced(context, filteredNodes.Count);
tempBuffer.Dispose();
}
}
else
{
// No filtering needed, render all nodes
context.VertexShader.SetShaderResource(0, batch.NodeBuffer.SRV);
cube.DrawInstanced(context, nodeCount);
}
}

UnbindResources(context);
Expand Down
8 changes: 8 additions & 0 deletions CodeWalker/WorldForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ public void RenderScene(DeviceContext context)

Renderer.SelectedDrawable = SelectedItem.Drawable;

// Set the selected scenario node position to exclude its cube from rendering
Renderer.shaders.SelectedScenarioNodePosition = SelectedItem.ScenarioNode?.Position;

if (renderworld)
{
RenderWorld();
Expand Down Expand Up @@ -1456,6 +1459,11 @@ private void RenderSelection(ref MapSelection selectionItem)

Renderer.RenderCar(sn.Position, sn.Orientation, 0, vhash, true);
}
else
{
// Render ped model for non-vehicle scenarios
Renderer.RenderScenarioNode(sn);
}

}
if (selectionItem.ScenarioEdge != null)
Expand Down