Skip to content

Commit

Permalink
Room mouseover (#56)
Browse files Browse the repository at this point in the history
Implements picking - allows the user to mouse over another room and have a little tooltip pop up to tell you the room number. The user can then can left click to move to that room.

When you click on a room it sets you in to orbit mode if you were in free mode - this seems to make sense.

Related: #37
  • Loading branch information
chreden authored Mar 11, 2018
1 parent 506b0fd commit 7d324f5
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 4 deletions.
26 changes: 26 additions & 0 deletions trview.ui/Control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,31 @@ namespace trview
}
return _focus_control;
}

// Set whether this control handles input when tested in is_mouse_over. Defaults to true.
// value: Whether the control handles input.
void Control::set_handles_input(bool value)
{
_handles_input = value;
}

bool Control::is_mouse_over(Point position) const
{
if (!(position.x >= 0 && position.y >= 0 && position.x <= _size.width && position.y <= _size.height))
{
return false;
}

bool is_over_child = false;
for (const auto& child : _child_elements)
{
if (child->visible())
{
is_over_child |= child->is_mouse_over(position - child->position());
}
}

return _handles_input || is_over_child;
}
}
}
11 changes: 11 additions & 0 deletions trview.ui/Control.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ namespace trview
// Process a mouse_move event at the position specified.
// Returns whether the mouse move was handled.
bool mouse_move(Point position);

// Determines whether the mouse is over the element or any child elements that are interested
// in taking input.
// position: The mouse position.
// Returns: True if the control or any child elements are under the cursor.
bool is_mouse_over(Point position) const;

// Set whether this control handles input when tested in is_mouse_over. Defaults to true.
// value: Whether the control handles input.
void set_handles_input(bool value);
protected:
// To be called when the user interface element has been clicked.
// This should be overriden by child elements to handle a click.
Expand All @@ -80,6 +90,7 @@ namespace trview
Point _position;
Size _size;
bool _visible;
bool _handles_input{ true };
};
}
}
15 changes: 15 additions & 0 deletions trview/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ namespace trview
calculate_projection_matrix(width, height);
}

DirectX::XMVECTOR Camera::target() const
{
return _target;
}

float Camera::rotation_pitch() const
{
return _rotation_pitch;
Expand Down Expand Up @@ -77,6 +82,16 @@ namespace trview
calculate_view_matrix();
}

DirectX::XMMATRIX Camera::view() const
{
return _view;
}

DirectX::XMMATRIX Camera::projection() const
{
return _projection;
}

DirectX::XMMATRIX Camera::view_projection() const
{
return _view_projection;
Expand Down
3 changes: 3 additions & 0 deletions trview/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ namespace trview
float rotation_yaw() const;
float rotation_pitch() const;
float zoom() const;
DirectX::XMVECTOR target() const;
void set_target(const DirectX::XMVECTOR& target);
DirectX::XMMATRIX view() const;
DirectX::XMMATRIX projection() const;
DirectX::XMMATRIX view_projection() const;
void set_rotation_yaw(float rotation);
void set_rotation_pitch(float rotation);
Expand Down
22 changes: 22 additions & 0 deletions trview/FreeCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,33 @@ namespace trview
_view_projection = _view * _projection;
}

DirectX::XMMATRIX FreeCamera::view() const
{
return _view;
}

DirectX::XMMATRIX FreeCamera::projection() const
{
return _projection;
}

DirectX::XMMATRIX FreeCamera::view_projection() const
{
return _view_projection;
}

DirectX::XMVECTOR FreeCamera::target() const
{
using namespace DirectX;
auto rotate = XMMatrixRotationRollPitchYaw(_rotation_pitch, _rotation_yaw, 0);
return XMVectorAdd(_position, XMVector3TransformCoord(XMVectorSet(0, 0, 1, 0), rotate));
}

DirectX::XMVECTOR FreeCamera::position() const
{
return _position;
}

void FreeCamera::move(DirectX::XMVECTOR movement)
{
using namespace DirectX;
Expand Down
4 changes: 4 additions & 0 deletions trview/FreeCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ namespace trview
public:
FreeCamera(uint32_t width, uint32_t height);
void move(DirectX::XMVECTOR movement);
DirectX::XMMATRIX view() const;
DirectX::XMMATRIX projection() const;
DirectX::XMMATRIX view_projection() const;

DirectX::XMVECTOR target() const;
DirectX::XMVECTOR position() const;
float rotation_yaw() const;
float rotation_pitch() const;
void set_rotation_yaw(float rotation);
Expand Down
39 changes: 39 additions & 0 deletions trview/Level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,43 @@ namespace trview
}
}
}

// Determine whether the specified ray hits any of the triangles in any of the room geometry.
// position: The world space position of the source of the ray.
// direction: The direction of the ray.
// Returns: The result of the operation. If 'hit' is true, distance and position contain
// how far along the ray the hit was and the position in world space. The room that was hit
// is also specified.
Level::PickResult Level::pick(DirectX::XMVECTOR position, DirectX::XMVECTOR direction) const
{
PickResult final_result;
for (uint32_t i = 0; i < _rooms.size(); ++i)
{
if (room_visible(i))
{
const auto& room = _rooms[i];
auto result = room->pick(position, direction);
if (result.hit && result.distance < final_result.distance)
{
final_result.hit = true;
final_result.distance = result.distance;
final_result.position = result.position;
final_result.room = i;
}
}
}
return final_result;
}

// Determines whether the room is currently being rendered.
// room: The room index.
// Returns: True if the room is visible.
bool Level::room_visible(uint32_t room) const
{
if (_room_highlight_mode != RoomHighlightMode::Neighbours)
{
return true;
}
return _neighbours.find(room) != _neighbours.end();
}
}
23 changes: 22 additions & 1 deletion trview/Level.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ namespace trview
Neighbours
};

struct PickResult
{
bool hit{ false };
uint32_t room;
DirectX::XMVECTOR position;
float distance{ FLT_MAX };
};

// Temporary, for the room info and texture window.

std::vector<RoomInfo> room_info() const;
Expand All @@ -41,8 +49,16 @@ namespace trview

uint16_t selected_room() const;

// Determine whether the specified ray hits any of the triangles in any of the room geometry.
// position: The world space position of the source of the ray.
// direction: The direction of the ray.
// Returns: The result of the operation. If 'hit' is true, distance and position contain
// how far along the ray the hit was and the position in world space. The room that was hit
// is also specified.
PickResult pick(DirectX::XMVECTOR position, DirectX::XMVECTOR direction) const;

void render(CComPtr<ID3D11DeviceContext> context, DirectX::XMMATRIX view_projection);

RoomHighlightMode highlight_mode() const;
void set_highlight_mode(RoomHighlightMode mode);
void set_selected_room(uint16_t index);
Expand All @@ -55,6 +71,11 @@ namespace trview

void render_rooms(CComPtr<ID3D11DeviceContext> context, const DirectX::XMMATRIX& view_projection);

// Determines whether the room is currently being rendered.
// room: The room index.
// Returns: True if the room is visible.
bool room_visible(uint32_t room) const;

const trlevel::ILevel* _level;
std::vector<std::unique_ptr<Room>> _rooms;
std::vector<std::unique_ptr<Entity>> _entities;
Expand Down
71 changes: 70 additions & 1 deletion trview/Room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "IMeshStorage.h"

#include <directxmath.h>
#include <DirectXCollision.h>
#include <array>

namespace trview
Expand All @@ -19,7 +20,7 @@ namespace trview
{
using namespace DirectX;
_room_offset = XMMatrixTranslation(room.info.x / 1024.f, 0, room.info.z / 1024.f);

generate_geometry(level, room, texture_storage);
generate_adjacency(level, room);
generate_static_meshes(level, room, mesh_storage);
Expand All @@ -35,6 +36,52 @@ namespace trview
return _neighbours;
}

// Determine whether the specified ray hits any of the triangles in the room geometry.
// position: The world space position of the source of the ray.
// direction: The direction of the ray.
// Returns: The result of the operation. If 'hit' is true, distance and position contain
// how far along the ray the hit was and the position in world space.
Room::PickResult Room::pick(DirectX::XMVECTOR position, DirectX::XMVECTOR direction) const
{
using namespace DirectX;
using namespace DirectX::TriangleTests;

PickResult result;

auto room_offset = XMMatrixTranslation(-_info.x / 1024.f, 0, -_info.z / 1024.f);
auto transformed_position = XMVector3TransformCoord(position, room_offset);

// Test against bounding box for the room first, to avoid more expensive mesh-ray intersection
float box_distance = 0;
if (!_bounding_box.Intersects(transformed_position, direction, box_distance))
{
return result;
}

bool any_hit = false;
result.distance = FLT_MAX;
for (const auto& tri : _collision_triangles)
{
float distance = 0;
if (XMVectorGetX(XMVector3Dot(direction, tri.normal)) < 0 &&
Intersects(transformed_position, direction, tri.v0, tri.v1, tri.v2, distance))
{
result.hit = true;
if (distance < result.distance)
{
result.distance = distance;
}
}
}

// Calculate the world space hit position, if there was a hit.
if (result.hit)
{
result.position = XMVectorAdd(position, XMVectorScale(direction, result.distance));
}
return result;
}

void Room::render(CComPtr<ID3D11DeviceContext> context, const DirectX::XMMATRIX& view_projection, const ILevelTextureStorage& texture_storage, SelectionMode selected)
{
// There are no vertices.
Expand Down Expand Up @@ -161,6 +208,9 @@ namespace trview
tex_indices.push_back(base + 2);
tex_indices.push_back(base + 3);
tex_indices.push_back(base + 0);

_collision_triangles.push_back(Triangle(XMLoadFloat3(&vertices[base].pos), XMLoadFloat3(&vertices[base + 1].pos), XMLoadFloat3(&vertices[base + 2].pos)));
_collision_triangles.push_back(Triangle(XMLoadFloat3(&vertices[base + 2].pos), XMLoadFloat3(&vertices[base + 3].pos), XMLoadFloat3(&vertices[base + 0].pos)));
}

for (const auto& tri : room.data.triangles)
Expand Down Expand Up @@ -197,6 +247,8 @@ namespace trview
tex_indices.push_back(base);
tex_indices.push_back(base + 1);
tex_indices.push_back(base + 2);

_collision_triangles.push_back(Triangle(XMLoadFloat3(&vertices[base].pos), XMLoadFloat3(&vertices[base + 1].pos), XMLoadFloat3(&vertices[base + 2].pos)));
}

if (!vertices.empty())
Expand Down Expand Up @@ -265,6 +317,23 @@ namespace trview

_device->CreateBuffer(&matrix_desc, nullptr, &_matrix_buffer);
}

// Generate the bounding box for use in picking.
XMFLOAT3 minimum(FLT_MAX, FLT_MAX, FLT_MAX);
XMFLOAT3 maximum(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (const auto& v : vertices)
{
if (v.pos.x < minimum.x) { minimum.x = v.pos.x; }
if (v.pos.y < minimum.y) { minimum.y = v.pos.y; }
if (v.pos.z < minimum.z) { minimum.z = v.pos.z; }
if (v.pos.x > maximum.x) { maximum.x = v.pos.x; }
if (v.pos.y > maximum.y) { maximum.y = v.pos.y; }
if (v.pos.z > maximum.z) { maximum.z = v.pos.z; }
}

const XMFLOAT3 half_size((maximum.x - minimum.x) * 0.5f, (maximum.y - minimum.y) * 0.5f, (maximum.z - minimum.z) * 0.5f);
_bounding_box.Extents = half_size;
_bounding_box.Center = XMFLOAT3(minimum.x + half_size.x, minimum.y + half_size.y, minimum.z + half_size.z);
}

void Room::generate_adjacency(const trlevel::ILevel& level, const trlevel::tr3_room& room)
Expand Down
Loading

0 comments on commit 7d324f5

Please sign in to comment.