Skip to content

Commit

Permalink
Add MouseSelectSystem for trigger highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
48ca committed Aug 18, 2021
1 parent bd34e2e commit 6cc6e34
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/OpenH2.Engine/RealtimeWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public RealtimeWorld(GameWindow window,
Systems.Add(animationSystem);
Systems.Add(new ScriptSystem(this, audioSystem, cameraSystem, actorSystem, animationSystem));
Systems.Add(new RenderCollectorSystem(this, graphics));
Systems.Add(new MouseSelectSystem(this, window));

RenderSystems.Add(new RenderPipelineSystem(this, graphics));

Expand Down
128 changes: 128 additions & 0 deletions src/OpenH2.Engine/Systems/MouseSelectSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using OpenH2.Core.Architecture;
using OpenH2.Engine.Stores;
using OpenH2.Engine.Components;
using OpenH2.Engine.Entities;
using OpenH2.Rendering.Abstractions;
using System.Numerics;
using OpenTK.Windowing.Desktop;
using System;

namespace OpenH2.Engine.Systems
{
public class MouseSelectSystem : WorldSystem
{

private readonly GameWindow window;
private TriggerVolume selectedVolume;
public MouseSelectSystem(World world, GameWindow window) : base(world)
{
selectedVolume = null;
this.window = window;
}

// Find the trigger volume that the mouse is hovering over by raycasting from the camera.
private TriggerVolume FindNextTriggerVolume(InputStore input_store)
{
var cameras = world.Components<CameraComponent>();
var cam = cameras[0];

var ViewMatrix = cam.ViewMatrix;
var ProjectionMatrix = cam.ProjectionMatrix;
var VP = ViewMatrix * ProjectionMatrix;

// Get mouse position, and convert it to projection coordinates.
// That is, scale [0, width) -> [-1, 1] and [0, height) -> [-1, 1].
var mousePos = input_store.MousePos;
var scaledMousePos = new Vector2((2 * mousePos.X / window.Size.X - 1), -(2 * mousePos.Y / window.Size.Y - 1));

if (!Matrix4x4.Invert(VP, out var ViewProjectionInv))
{
System.Console.WriteLine("Unable to invert view+projection matrix");
return null;
}

// Convert ray from projection space to world space.
var ray_origin = Vector4.Transform(new Vector4(0, 0, 0, 1), ViewProjectionInv);
ray_origin /= ray_origin.W;
var ray_offset = Vector4.Transform(new Vector4(scaledMousePos, 1, 1), ViewProjectionInv);
ray_offset /= ray_offset.W;

float min = float.MaxValue;
TriggerVolume new_volume = null;
foreach (var entity in this.world.Scene.Entities.Values)
{
if (entity is not TriggerVolume tv)
{
continue;
}
// All trigger volumes have a TriggerGeometryComponent.
var trigGeomComp = entity.GetChildren<TriggerGeometryComponent>()[0];

var left_bottom = new Vector3(0, 0, 0);
var top_right = trigGeomComp.Size;
var entTransMat = trigGeomComp.Transform.TransformationMatrix;
if (!Matrix4x4.Invert(entTransMat, out var entTransMatInv))
{
System.Console.Error.WriteLine($"Unable to invert entity transformation mat: {entTransMat} for entity {entity.FriendlyName}");
return null;
}

// Handle non-axis-aligned triggers: transform the ray into the local space of the trigger.
var local_ray_origin = Vector4.Transform(ray_origin, entTransMatInv);
var local_ray_offset = Vector4.Transform(ray_offset, entTransMatInv);

// Now do standard axis-aligned bounding-box + ray intersection.
var ray_dir = local_ray_offset - local_ray_origin;
ray_dir /= ray_dir.Length();
var dirfrac = new Vector3(1.0f / ray_dir.X, 1.0f / ray_dir.Y, 1.0f / ray_dir.Z);

// Calculate the distance `t` to all 6 planes of the box.
float t1 = (left_bottom.X - local_ray_origin.X) * dirfrac.X;
float t2 = (top_right.X - local_ray_origin.X) * dirfrac.X;
float t3 = (left_bottom.Y - local_ray_origin.Y) * dirfrac.Y;
float t4 = (top_right.Y - local_ray_origin.Y) * dirfrac.Y;
float t5 = (left_bottom.Z - local_ray_origin.Z) * dirfrac.Z;
float t6 = (top_right.Z - local_ray_origin.Z) * dirfrac.Z;

float tmin = Math.Max(Math.Max(Math.Min(t1, t2), Math.Min(t3, t4)), Math.Min(t5, t6));
float tmax = Math.Min(Math.Min(Math.Max(t1, t2), Math.Max(t3, t4)), Math.Max(t5, t6));
if (tmin < 0 || tmax < 0 || tmin > tmax)
{
// Discard the box if we are partially inside it (tmin < 0),
// the box is completely behind us (tmax < 0), or the ray missed (tmin > tmax).
continue;
}
if (tmin < min)
{
// Otherwise, tmin is our total distance (in trigger-local space) to the box.
// Pick the lowest one (i.e., closest the camera).
min = tmin;
new_volume = tv;
}
}
return new_volume;
}

public override void Update(double timestep)
{
var input_store = this.world.GetGlobalResource<InputStore>();
if (input_store.RightMouseDown)
{
var new_volume = FindNextTriggerVolume(input_store);
if (new_volume != selectedVolume)
{
if (new_volume != null)
{
new_volume.ToggleSelected();
System.Console.WriteLine($"[TRIG] {new_volume.FriendlyName} selected");
}
if (selectedVolume != null)
{
selectedVolume.ToggleSelected();
}
selectedVolume = new_volume;
}
}
}
}
}

0 comments on commit 6cc6e34

Please sign in to comment.